[
  {
    "path": ".dockerignore",
    "content": ".git*\n.idea*\n*.md\n.venv/"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*.py]\nmax_line_length = 120\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "content": "name: 'Bug Report'\ndescription: 'Report an Bug'\ntitle: \"[Bug] \"\nassignees: zyyfit\nbody:\n  - type: markdown\n    attributes:\n      value: \"## Contact Information\"\n  - type: input\n    validations:\n      required: false\n    attributes:\n      label: \"Contact Information\"\n      description: \"The ways to quickly contact you: WeChat group number and nickname, email, etc.\"\n  - type: markdown\n    attributes:\n      value: \"## Environment Information\"\n  - type: input\n    validations:\n      required: true\n    attributes:\n      label: \"MaxKB Version\"\n      description: \"Log in to the MaxKB Web Console and check the current version on the `About` page in the top right corner.\"\n  - type: markdown\n    attributes:\n      value: \"## Detailed information\"\n  - type: textarea\n    attributes:\n      label: \"Problem Description\"\n      description: \"Briefly describe the issue you’ve encountered.\"\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: \"Steps to Reproduce\"\n      description: \"How can this issue be reproduced.\"\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: \"The expected correct result\"\n  - type: textarea\n    attributes:\n      label: \"Related log output\"\n      description: \"Please paste any relevant log output here. It will automatically be formatted as code, so no backticks are necessary.\"\n      render: shell\n  - type: textarea\n    attributes:\n      label: \"Additional Information\"\n      description: \"If you have any additional information to provide, you can include it here (screenshots, videos, etc., are welcome).\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yml",
    "content": "name: 'Feature Request'\ndescription: 'Suggest an idea'\ntitle: '[Feature] '\nassignees: baixin513\nbody:\n  - type: markdown\n    attributes:\n      value: \"## Environment Information\"\n  - type: input\n    validations:\n      required: true\n    attributes:\n      label: \"MaxKB Version\"\n      description: \"Log in to the MaxKB Web Console and check the current version on the `About` page in the top right corner.\"\n  - type: markdown\n    attributes:\n      value: \"## Detailed information\"\n  - type: textarea\n    attributes:\n      label: \"Please describe your needs or suggestions for improvements\"\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: \"Please describe the solution you suggest\"\n  - type: textarea\n    attributes:\n      label: \"Additional Information\"\n      description: \"If you have any additional information to provide, you can include it here (screenshots, videos, etc., are welcome).\""
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "#### What this PR does / why we need it?\n\n#### Summary of your change\n\n#### Please indicate you've done the following:\n\n- [ ] Made sure tests are passing and test coverage is added if needed.\n- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/).\n- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed."
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"pip\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n      timezone: \"Asia/Shanghai\"\n      day: \"friday\"\n    target-branch: \"v3\""
  },
  {
    "path": ".github/workflows/build-and-push-python-pg.yml",
    "content": "name: build-and-push-python-pg\n\non:\n  workflow_dispatch:\n    inputs:\n      architecture:\n        description: 'Architecture'\n        required: true\n        default: 'linux/amd64'\n        type: choice\n        options:\n          - linux/amd64\n          - linux/arm64\n          - linux/amd64,linux/arm64\njobs:\n  build-and-push-python-pg-to-ghcr:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.ref_name }}\n      - name: Prepare\n        id: prepare\n        run: |\n          DOCKER_IMAGE=ghcr.io/1panel-dev/maxkb-base\n          DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}\n          TAG_NAME=python3.11-pg17.7-20260212\n          DOCKER_IMAGE_TAGS=\"--tag ${DOCKER_IMAGE}:${TAG_NAME}\"\n          echo ::set-output name=docker_image::${DOCKER_IMAGE}\n          echo ::set-output name=version::${TAG_NAME}\n          echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --no-cache \\\n            --build-arg VERSION=${TAG_NAME} \\\n            --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \\\n            --build-arg VCS_REF=${GITHUB_SHA::8} \\\n            ${DOCKER_IMAGE_TAGS} .\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          cache-image: false\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GH_TOKEN }}\n      - name: Docker Buildx (build-and-push)\n        run: |\n          docker buildx build --output \"type=image,push=true\" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile-base"
  },
  {
    "path": ".github/workflows/build-and-push-vector-model.yml",
    "content": "name: build-and-push-vector-model\n\non:\n  workflow_dispatch:\n    inputs:\n      dockerImageTag:\n        description: 'Docker Image Tag'\n        default: 'v2.0.3'\n        required: true\n      architecture:\n        description: 'Architecture'\n        required: true\n        default: 'linux/amd64'\n        type: choice\n        options:\n          - linux/amd64\n          - linux/arm64\n          - linux/amd64,linux/arm64\n\njobs:\n  build-and-push-vector-model-to-ghcr:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.ref_name }}\n      - name: Prepare\n        id: prepare\n        run: |\n          DOCKER_IMAGE=ghcr.io/1panel-dev/maxkb-vector-model\n          DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}\n          TAG_NAME=${{ github.event.inputs.dockerImageTag }}\n          DOCKER_IMAGE_TAGS=\"--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:latest\"\n          echo ::set-output name=docker_image::${DOCKER_IMAGE}\n          echo ::set-output name=version::${TAG_NAME}\n          echo ::set-output name=buildx_args::--platform ${DOCKER_PLATFORMS} --no-cache \\\n            --build-arg VERSION=${TAG_NAME} \\\n            --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \\\n            --build-arg VCS_REF=${GITHUB_SHA::8} \\\n            ${DOCKER_IMAGE_TAGS} .\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          cache-image: false\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GH_TOKEN }}\n      - name: Docker Buildx (build-and-push)\n        run: |\n          docker buildx build --output \"type=image,push=true\" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile-vector-model"
  },
  {
    "path": ".github/workflows/build-and-push.yml",
    "content": "name: build-and-push\n\nrun-name: 构建镜像并推送仓库 ${{ github.event.inputs.dockerImageTag }} (${{ github.event.inputs.registry }}) (${{ github.event.inputs.architecture }})\n\non:\n  workflow_dispatch:\n    inputs:\n      dockerImageTag:\n        description: 'Image Tag'\n        default: 'v2.6.0-dev'\n        required: true\n      dockerImageTagWithLatest:\n        description: '是否发布latest tag（正式发版时选择，测试版本切勿选择）'\n        default: false\n        required: true\n        type: boolean\n      architecture:\n        description: 'Architecture'\n        required: true\n        default: 'linux/amd64'\n        type: choice\n        options:\n          - linux/amd64\n          - linux/arm64\n          - linux/amd64,linux/arm64\n      registry:\n        description: 'Push To Registry'\n        required: true\n        default: 'fit2cloud-registry'\n        type: choice\n        options:\n          - fit2cloud-registry\n          - dockerhub\n          - dockerhub, fit2cloud-registry\n\njobs:\n  build-and-push-to-fit2cloud-registry:\n    if: ${{ contains(github.event.inputs.registry, 'fit2cloud') }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clear Work Dir\n        run: |\n          ls -la\n          rm -rf -- ./* ./.??*\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.ref_name }}\n      - name: Prepare\n        id: prepare\n        run: |\n          DOCKER_IMAGE=${{ secrets.FIT2CLOUD_REGISTRY_HOST }}/maxkb/maxkb\n          DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}\n          TAG_NAME=${{ github.event.inputs.dockerImageTag }}\n          TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}\n          if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then            \n            DOCKER_IMAGE_TAGS=\"--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*} --tag ${DOCKER_IMAGE}:latest\"\n          else\n            DOCKER_IMAGE_TAGS=\"--tag ${DOCKER_IMAGE}:${TAG_NAME}\"\n          fi\n          echo \"buildx_args=--platform ${DOCKER_PLATFORMS} --memory-swap -1 \\\n            --build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \\\n            ${DOCKER_IMAGE_TAGS} .\" >> $GITHUB_OUTPUT\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          cache-image: false\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GH_TOKEN }}\n      - name: Login to FIT2CLOUD Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ${{ secrets.FIT2CLOUD_REGISTRY_HOST }}\n          username: ${{ secrets.FIT2CLOUD_REGISTRY_USERNAME }}\n          password: ${{ secrets.FIT2CLOUD_REGISTRY_PASSWORD }}\n      - name: Build Web\n        run: |\n          docker buildx build --no-cache --target web-build --output type=local,dest=./web-build-output . -f installer/Dockerfile\n          rm -rf ./ui\n          cp -r ./web-build-output/ui ./\n          rm -rf ./web-build-output\n      - name: Docker Buildx (build-and-push)\n        run: |\n          sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m\n          docker buildx build --output \"type=image,push=true\" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile\n\n  build-and-push-to-dockerhub:\n    if: ${{ contains(github.event.inputs.registry, 'dockerhub') }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clear Work Dir\n        run: |\n          ls -la\n          rm -rf -- ./* ./.??*\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.ref_name }}\n      - name: Prepare\n        id: prepare\n        run: |\n          DOCKER_IMAGE=1panel/maxkb\n          DOCKER_PLATFORMS=${{ github.event.inputs.architecture }}\n          TAG_NAME=${{ github.event.inputs.dockerImageTag }}          \n          TAG_NAME_WITH_LATEST=${{ github.event.inputs.dockerImageTagWithLatest }}\n          if [[ ${TAG_NAME_WITH_LATEST} == 'true' ]]; then            \n            DOCKER_IMAGE_TAGS=\"--tag ${DOCKER_IMAGE}:${TAG_NAME} --tag ${DOCKER_IMAGE}:${TAG_NAME%%.*} --tag ${DOCKER_IMAGE}:latest\"\n          else\n            DOCKER_IMAGE_TAGS=\"--tag ${DOCKER_IMAGE}:${TAG_NAME}\"\n          fi\n          echo \"buildx_args=--platform ${DOCKER_PLATFORMS} --memory-swap -1 \\\n            --build-arg DOCKER_IMAGE_TAG=${{ github.event.inputs.dockerImageTag }} --build-arg BUILD_AT=$(TZ=Asia/Shanghai date +'%Y-%m-%dT%H:%M') --build-arg GITHUB_COMMIT=`git rev-parse --short HEAD` --no-cache \\\n            ${DOCKER_IMAGE_TAGS} .\" >> $GITHUB_OUTPUT\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n        with:\n          cache-image: false\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v3\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GH_TOKEN }}\n      - name: Login to Docker Hub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n      - name: Build Web\n        run: |\n          docker buildx build --no-cache --target web-build --output type=local,dest=./web-build-output . -f installer/Dockerfile\n          rm -rf ./ui\n          cp -r ./web-build-output/ui ./\n          rm -rf ./web-build-output\n      - name: Docker Buildx (build-and-push)\n        run: |\n          sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && free -m\n          docker buildx build --output \"type=image,push=true\" ${{ steps.prepare.outputs.buildx_args }} -f installer/Dockerfile\n"
  },
  {
    "path": ".github/workflows/create-pr-from-push.yml",
    "content": "on:\n  push:\n    branches:    \n      - 'pr@**'\n      - 'repr@**'\n\nname: 针对特定分支名自动创建 PR\n\njobs:\n  generic_handler:\n    name: 自动创建 PR\n    runs-on: ubuntu-latest\n    steps:\n      - name: Create pull request\n        uses: jumpserver/action-generic-handler@master\n        env:\n          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/issue-translator.yml",
    "content": "name: Issue Translator\non: \n  issue_comment: \n    types: [created]\n  issues: \n    types: [opened]\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: usthe/issues-translate-action@v2.7\n        with:\n          IS_MODIFY_TITLE: true\n          BOT_GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/llm-code-review.yml",
    "content": "name: LLM Code Review\n\npermissions:\n  contents: read\n  pull-requests: write\n\non:\n  pull_request:\n    types: [opened, reopened, synchronize]\n\njobs:\n  llm-code-review:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: fit2cloud/LLM-CodeReview-Action@main\n        env:\n          GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }}\n          OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }}\n          LANGUAGE: English\n          OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1\n          MODEL: qwen2.5-coder-3b-instruct\n          PROMPT: \"Please check the following code for any irregularities, potential issues, or optimization suggestions, and provide your answers in English.\"\n          top_p: 1\n          temperature: 1\n          # max_tokens: 10000\n          MAX_PATCH_LENGTH: 10000\n          IGNORE_PATTERNS: \"/node_modules,*.md,/dist,/.github\"\n          FILE_PATTERNS: \"*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html\"\n"
  },
  {
    "path": ".github/workflows/sync2gitee.yml",
    "content": "name: sync2gitee\non: [push]\n\njobs:\n  repo-sync:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Mirror the Github organization repos to Gitee.\n        uses: Yikun/hub-mirror-action@master\n        with:\n          src: 'github/1Panel-dev'\n          dst: 'gitee/fit2cloud-feizhiyun'\n          dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}\n          dst_token:  ${{ secrets.GITEE_TOKEN }}\n          static_list: \"MaxKB\"\n          force_update: true"
  },
  {
    "path": ".github/workflows/typos_check.yml",
    "content": "name: Typos Check\non:\n  workflow_dispatch:\n  push:\n  pull_request:\n    types: [opened, synchronize, reopened]\n\njobs:\n  run:\n    name: Spell Check with Typos\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout Actions Repository\n      uses: actions/checkout@v4\n      with:\n        ref: ${{ github.ref_name }}\n    - name: Create config file\n      run: |\n        cat <<EOF > typo-check-config.toml\n        [files]\n        extend-exclude = [\n          \"**/*_svg\",\n          \"**/migrations/**\",\n          \"**/loopEdge.ts\",\n          \"**/edge.ts\",\n        ]\n        EOF\n    - name: Check spelling\n      uses: crate-ci/typos@master\n      with:\n        config: ./typo-check-config.toml\n"
  },
  {
    "path": ".gitignore",
    "content": "# Mac\n.DS_Store\n*/.DS_Store\n\n# VS Code\n.vscode\n*.project\n*.factorypath\n\n# IntelliJ IDEA\n.idea/*\n!.idea/icon.png\n*.iws\n*.iml\n*.ipr\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script forms a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\n# env/\nvenv/\n# ENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\nui/package-lock.json\nui/node_modules\nui/dist\napps/static\nmodels/\napps/xpack\n!apps/**/models/\ndata\n.dev\npoetry.lock\nuv.lock\napps/models_provider/impl/*/icon/\napps/models_provider/impl/tencent_model_provider/credential/stt.py\napps/models_provider/impl/tencent_model_provider/model/stt.py\ntmp/\nconfig.yml\n.SANDBOX_BANNED_HOSTS"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nsupport@fit2cloud.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations."
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nAs a contributor, you should agree that:\n\n- The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary.\n- Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations.\n\n## Create pull request\nPR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it.\n\nPlease submit a PR broken down into small changes bit by bit. A PR consisting of a lot of features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion.\n\nThis [development guideline](https://github.com/1Panel-dev/MaxKB/wiki/3-%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA) contains information about repository structure, how to set up development environment, how to run it, and more.\n\nNote: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete.\n\n## Report issues\nIt is a great way to contribute by reporting an issue. Well-written and complete bug reports are always welcome! Please open an issue and follow the template to fill in required information.\n\nBefore opening any issue, please look up the existing issues to avoid submitting a duplication.\nIf you find a match, you can \"subscribe\" to it to get notified on updates. If you have additional helpful information about the issue, please leave a comment.\n\nWhen reporting issues, always include:\n\n* Which version you are using.\n* Steps to reproduce the issue.\n* Snapshots or log files if needed\n\nBecause the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can\nreplace those parts with \"REDACTED\" or other strings like \"****\"."
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 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 General Public License is a free, copyleft license for\nsoftware and other kinds of works.\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,\nthe GNU General Public License is 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.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\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  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\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 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. Use with the GNU Affero General Public License.\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 Affero 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 special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe 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 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 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 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 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 General Public License for more details.\n\n    You should have received a copy of the GNU 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 the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\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 GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>."
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><img src= \"https://github.com/1Panel-dev/maxkb/assets/52996290/c0694996-0eed-40d8-b369-322bf2a380bf\" alt=\"MaxKB\" width=\"300\" /></p>\n<h3 align=\"center\">Open-source platform for building enterprise-grade agents</h3>\n<h3 align=\"center\">强大易用的企业级智能体平台</h3>\n<p align=\"center\"><a href=\"https://trendshift.io/repositories/9113\" target=\"_blank\"><img src=\"https://trendshift.io/api/badge/repositories/9113\" alt=\"1Panel-dev%2FMaxKB | Trendshift\" style=\"width: 250px; height: 55px;\" width=\"250\" height=\"55\"/></a></p>\n<p align=\"center\">\n  <a href=\"https://www.gnu.org/licenses/gpl-3.0.html#license-text\"><img src=\"https://img.shields.io/github/license/1Panel-dev/maxkb?color=%231890FF\" alt=\"License: GPL v3\"></a>\n  <a href=\"https://github.com/1Panel-dev/maxkb/releases/latest\"><img src=\"https://img.shields.io/github/v/release/1Panel-dev/maxkb\" alt=\"Latest release\"></a>\n  <a href=\"https://github.com/1Panel-dev/maxkb\"><img src=\"https://img.shields.io/github/stars/1Panel-dev/maxkb?color=%231890FF&style=flat-square\" alt=\"Stars\"></a>    \n  <a href=\"https://hub.docker.com/r/1panel/maxkb\"><img src=\"https://img.shields.io/docker/pulls/1panel/maxkb?label=downloads\" alt=\"Download\"></a><br/>\n [<a href=\"/README_CN.md\">中文(简体)</a>] | [<a href=\"/README.md\">English</a>] \n</p>\n<hr/>\n\nMaxKB = Max Knowledge Brain, it is an open-source platform for building enterprise-grade agents. MaxKB integrates Retrieval-Augmented Generation (RAG) pipelines, supports robust workflows, and provides advanced MCP tool-use capabilities. MaxKB is widely applied in scenarios such as intelligent customer service, corporate internal knowledge bases, academic research, and education.\n\n- **RAG Pipeline**: Supports direct uploading of documents / automatic crawling of online documents, with features for automatic text splitting, vectorization. This effectively reduces hallucinations in large models, providing a superior smart Q&A interaction experience.\n- **Agentic Workflow**: Equipped with a powerful workflow engine, function library and MCP tool-use, enabling the orchestration of AI processes to meet the needs of complex business scenarios.\n- **Seamless Integration**: Facilitates zero-coding rapid integration into third-party business systems, quickly equipping existing systems with intelligent Q&A capabilities to enhance user satisfaction.\n- **Model-Agnostic**: Supports various large models, including private models (such as DeepSeek, Llama, Qwen, etc.) and public models (like OpenAI, Claude, Gemini, etc.).\n- **Multi Modal**: Native support for input and output text, image, audio and video.\n\n## Quick start\n\nExecute the script below to start a MaxKB container using Docker:\n\n```bash\ndocker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/opt/maxkb 1panel/maxkb\n```\n\nAccess MaxKB web interface at `http://your_server_ip:8080` with default admin credentials:\n\n- username: admin\n- password: MaxKB@123..\n\n中国用户如遇到 Docker 镜像 Pull 失败问题，请参照该 [离线安装文档](https://maxkb.cn/docs/v2/installation/offline_installtion/) 进行安装。\n\n## Screenshots\n\n<table style=\"border-collapse: collapse; border: 1px solid black;\">\n  <tr>\n    <td style=\"padding: 5px;background-color:#fff;\"><img src= \"https://github.com/user-attachments/assets/eb285512-a66a-4752-8941-c65ed1592238\" alt=\"MaxKB Demo1\"   /></td>\n    <td style=\"padding: 5px;background-color:#fff;\"><img src= \"https://github.com/user-attachments/assets/f732f1f5-472c-4fd2-93c1-a277eda83d04\" alt=\"MaxKB Demo2\"   /></td>\n  </tr>\n  <tr>\n    <td style=\"padding: 5px;background-color:#fff;\"><img src= \"https://github.com/user-attachments/assets/c927474a-9a23-4830-822f-5db26025c9b2\" alt=\"MaxKB Demo3\"   /></td>\n    <td style=\"padding: 5px;background-color:#fff;\"><img src= \"https://github.com/user-attachments/assets/e6268996-a46d-4e58-9f30-31139df78ad2\" alt=\"MaxKB Demo4\"   /></td>\n  </tr>\n</table>\n\n## Technical stack\n\n- Frontend：[Vue.js](https://vuejs.org/)\n- Backend：[Python / Django](https://www.djangoproject.com/)\n- LLM Framework：[LangChain](https://www.langchain.com/)\n- Database：[PostgreSQL + pgvector](https://www.postgresql.org/)\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/MaxKB&type=Date)](https://star-history.com/#1Panel-dev/MaxKB&Date)\n\n## License\n\nLicensed under The GNU General Public License version 3 (GPLv3)  (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\n<https://www.gnu.org/licenses/gpl-3.0.html>\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n"
  },
  {
    "path": "README_CN.md",
    "content": "<p align=\"center\"><img src= \"https://github.com/1Panel-dev/maxkb/assets/52996290/c0694996-0eed-40d8-b369-322bf2a380bf\" alt=\"MaxKB\" width=\"300\" /></p>\n<h3 align=\"center\">强大易用的企业级智能体平台</h3>\n<p align=\"center\">\n    <a href=\"https://trendshift.io/repositories/9113\" target=\"_blank\"><img src=\"https://trendshift.io/api/badge/repositories/9113\" alt=\"1Panel-dev%2FMaxKB | Trendshift\" style=\"width: 250px; height: auto;\" /></a>\n</p>\n<p align=\"center\">\n  <a href=\"README_EN.md\"><img src=\"https://img.shields.io/badge/English_README-blue\" alt=\"English README\"></a>\n  <a href=\"https://www.gnu.org/licenses/gpl-3.0.html#license-text\"><img src=\"https://img.shields.io/github/license/1Panel-dev/maxkb?color=%231890FF\" alt=\"License: GPL v3\"></a>\n  <a href=\"https://github.com/1Panel-dev/maxkb/releases/latest\"><img src=\"https://img.shields.io/github/v/release/1Panel-dev/maxkb\" alt=\"Latest release\"></a>\n  <a href=\"https://github.com/1Panel-dev/maxkb\"><img src=\"https://img.shields.io/github/stars/1Panel-dev/maxkb?style=flat-square\" alt=\"Stars\"></a>\n  <a href=\"https://hub.docker.com/r/1panel/maxkb\"><img src=\"https://img.shields.io/docker/pulls/1panel/maxkb?label=downloads\" alt=\"Download\"></a>\n  <a href=\"https://gitee.com/fit2cloud-feizhiyun/MaxKB\"><img src=\"https://gitee.com/fit2cloud-feizhiyun/MaxKB/badge/star.svg?theme=gvp\" alt=\"Gitee Stars\"></a>\n  <a href=\"https://gitcode.com/feizhiyun/MaxKB\"><img src=\"https://gitcode.com/feizhiyun/MaxKB/star/badge.svg\" alt=\"GitCode Stars\"></a>\n</p>\n<hr/>\n\nMaxKB = Max Knowledge Brain，是一个强大易用的企业级智能体平台，致力于解决企业 AI 落地面临的技术门槛高、部署成本高、迭代周期长等问题，助力企业在人工智能时代赢得先机。秉承“开箱即用，伴随成长”的设计理念，MaxKB 支持企业快速接入主流大模型，高效构建专属知识库，并提供从基础问答（RAG）、复杂流程自动化（工作流）到智能体（Agent）的渐进式升级路径，全面赋能智能客服、智能办公助手等多种应用场景。\n\n- **RAG 检索增强生成**：高效搭建本地 AI 知识库，支持直接上传文档 / 自动爬取在线文档，支持文本自动拆分、向量化，有效减少大模型幻觉，提升问答效果；\n- **灵活编排**：内置强大的工作流引擎、函数库和 MCP 工具调用能力，支持编排 AI 工作过程，满足复杂业务场景下的需求；\n- **无缝嵌入**：支持零编码快速嵌入到第三方业务系统，让已有系统快速拥有智能问答能力，提高用户满意度；\n- **模型中立**：支持对接各种大模型，包括本地私有大模型（DeepSeek R1 / Qwen 3 等）、国内公共大模型（通义千问 / 腾讯混元 / 字节豆包 / 百度千帆 / 智谱 AI / Kimi 等）和国外公共大模型（OpenAI / Claude / Gemini 等）。\n\nMaxKB 三分钟视频介绍：https://www.bilibili.com/video/BV18JypYeEkj/\n\n## 快速开始\n\n```\n# Linux 机器\ndocker run -d --name=maxkb --restart=always -p 8080:8080 -v ~/.maxkb:/opt/maxkb registry.fit2cloud.com/maxkb/maxkb\n\n# Windows 机器\ndocker run -d --name=maxkb --restart=always -p 8080:8080 -v C:/maxkb:/opt/maxkb registry.fit2cloud.com/maxkb/maxkb\n\n# 用户名: admin\n# 密码: MaxKB@123..\n```\n\n- 你也可以通过 [1Panel 应用商店](https://apps.fit2cloud.com/1panel) 快速部署 MaxKB；\n- 如果是内网环境，推荐使用 [离线安装包](https://community.fit2cloud.com/#/products/maxkb/downloads) 进行安装部署；\n- MaxKB 不同产品产品版本的对比请参见：[MaxKB 产品版本对比](https://maxkb.cn/price)；\n- 如果您需要向团队介绍 MaxKB，可以使用这个 [官方 PPT 材料](https://fit2cloud.com/maxkb/download/introduce-maxkb_2026.pdf)。\n\n如你有更多问题，可以查看使用手册，或者通过论坛与我们交流。\n\n- [案例展示](USE-CASES.md)\n- [使用手册](https://maxkb.cn/docs/)\n- [论坛求助](https://bbs.fit2cloud.com/c/mk/11)\n- 技术交流群\n\n<image height=\"150px\" width=\"150px\" src=\"https://github.com/1Panel-dev/MaxKB/assets/52996290/a083d214-02be-4178-a1db-4f428124153a\"/>\n\n## UI 展示\n\n<table style=\"border-collapse: collapse; border: 1px solid black;\">\n  <tr>\n    <td style=\"padding: 5px;background-color:#fff;\"><img src= \"https://github.com/user-attachments/assets/eb285512-a66a-4752-8941-c65ed1592238\" alt=\"MaxKB Demo1\"   /></td>\n    <td style=\"padding: 5px;background-color:#fff;\"><img src= \"https://github.com/user-attachments/assets/f732f1f5-472c-4fd2-93c1-a277eda83d04\" alt=\"MaxKB Demo2\"   /></td>\n  </tr>\n  <tr>\n    <td style=\"padding: 5px;background-color:#fff;\"><img src= \"https://github.com/user-attachments/assets/c927474a-9a23-4830-822f-5db26025c9b2\" alt=\"MaxKB Demo3\"   /></td>\n    <td style=\"padding: 5px;background-color:#fff;\"><img src= \"https://github.com/user-attachments/assets/e6268996-a46d-4e58-9f30-31139df78ad2\" alt=\"MaxKB Demo4\"   /></td>\n  </tr>\n</table>\n\n## 技术栈\n\n- 前端：[Vue.js](https://cn.vuejs.org/)\n- 后端：[Python / Django](https://www.djangoproject.com/)\n- LangChain：[LangChain](https://www.langchain.com/)\n- 向量数据库：[PostgreSQL / pgvector](https://www.postgresql.org/)\n\n## 飞致云的其他明星项目\n\n- [Cordys CRM](https://github.com/1Panel-dev/CordysCRM) - 新一代的开源 AI CRM 系统\n- [1Panel](https://github.com/1panel-dev/1panel/) - 现代化、开源的 Linux 服务器运维管理面板\n- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机\n- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具\n- [MeterSphere](https://github.com/metersphere/metersphere/) - 新一代的开源持续测试工具\n- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具\n\n## License\n\nCopyright (c) 2014-2026 飞致云 FIT2CLOUD, All rights reserved.\n\nLicensed under The GNU General Public License version 3 (GPLv3)  (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\n<https://www.gnu.org/licenses/gpl-3.0.html>\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# 安全说明\n\n如果您发现安全问题，请直接联系我们：\n\n- support@fit2cloud.com\n- 400-052-0755\n\n感谢您的支持！\n\n# Security Policy\n\nAll security bugs should be reported to the contact as below:\n\n- support@fit2cloud.com\n- 400-052-0755\n\nThanks for your support!"
  },
  {
    "path": "USE-CASES.md",
    "content": "<h3 align=\"center\">MaxKB 应用案例，持续更新中...</h3>\n\n------------------------------\n\n- [MaxKB 应用案例：中国农业大学-小鹉哥](https://mp.weixin.qq.com/s/4g_gySMBQZCJ9OZ-yBkmvw)\n- [MaxKB 应用案例：东北财经大学-小银杏](https://mp.weixin.qq.com/s/3BoxkY7EMomMmmvFYxvDIA)\n- [MaxKB 应用案例：中铁水务](https://mp.weixin.qq.com/s/voNAddbK2CJOrJJs1ewZ8g)\n- [MaxKB 应用案例：解放军总医院](https://mp.weixin.qq.com/s/ETrZC-vrA4Aap0eF-15EeQ)\n- [MaxKB 应用案例：无锡市数据局](https://mp.weixin.qq.com/s/enfUFLevvL_La74PQ0kIXw)\n- [MaxKB 应用案例：中核西仪研究院-西仪睿答](https://mp.weixin.qq.com/s/CbKr4mev8qahKLAtV6Dxdg)\n- [MaxKB 应用案例：南京中医药大学](https://mp.weixin.qq.com/s/WUmAKYbZjp3272HIecpRFA)\n- [MaxKB 应用案例：西北电力设计院-AI数字助理Memex](https://mp.weixin.qq.com/s/ezHFdB7C7AVL9MTtDwYGSA)\n- [MaxKB 应用案例：西安国际医院中心医院-国医小助](https://mp.weixin.qq.com/s/DSOUvwrQrxbqQxKBilTCFQ)\n- [MaxKB 应用案例：华莱士智能AI客服助手上线啦！](https://www.bilibili.com/video/BV1hQtVeXEBL)\n- [MaxKB 应用案例：把医疗行业知识转化为知识库问答助手！](https://www.bilibili.com/video/BV157wme9EgB)\n- [MaxKB 应用案例：会展AI智能客服体验](https://www.bilibili.com/video/BV1J7BqY6EKA)\n- [MaxKB 应用案例：孩子要上幼儿园了，AI 智能助手择校好帮手](https://www.bilibili.com/video/BV1wKrhYvEer)\n- [MaxKB 应用案例：产品使用指南AI助手，新手小白也能轻松搞定！](https://www.bilibili.com/video/BV1Yz6gYtEqX)\n- [MaxKB 应用案例：生物医药AI客服智能体验!](https://www.bilibili.com/video/BV13JzvYsE3e)\n- [MaxKB 应用案例：高校行政管理AI小助手](https://www.bilibili.com/video/BV1yvBMYvEdy)\n- [MaxKB 应用案例：岳阳市人民医院-OA小助手](https://mp.weixin.qq.com/s/O94Qo3UH-MiUtDdWCVg8sQ)\n- [MaxKB 应用案例：常熟市第一人民医院](https://mp.weixin.qq.com/s/s5XXGTR3_MUo41NbJ8WzZQ)\n- [MaxKB 应用案例：华北水利水电大学](https://mp.weixin.qq.com/s/PoOFAcMCr9qJdvSj8c08qg)\n- [MaxKB 应用案例：唐山海事局-“小海”AI语音助手](https://news.qq.com/rain/a/20250223A030BE00)\n- [MaxKB 应用案例：湖南汉寿政务](http://hsds.hsdj.gov.cn:19999/ui/chat/a2c976736739aadc)\n- [MaxKB 应用案例：广州市妇女儿童医疗中心-AI医疗数据分类分级小助手](https://mp.weixin.qq.com/s/YHUMkUOAaUomBV8bswpK3g)\n- [MaxKB 应用案例：苏州热工研究院有限公司-维修大纲评估质量自查AI小助手](https://mp.weixin.qq.com/s/Ts5FQdnv7Tu9Jp7bvofCVA)\n- [MaxKB 应用案例：国核自仪系统工程有限公司-NuCON AI帮](https://mp.weixin.qq.com/s/HNPc7u5xVfGLJr8IQz3vjQ)\n- [MaxKB 应用案例：深圳通开启Deep Seek智能应用新篇章](https://mp.weixin.qq.com/s/SILN0GSescH9LyeQqYP0VQ)\n- [MaxKB 应用案例：南通智慧出行领跑长三角！首款接入DeepSeek的\"畅行南通\"APP上线AI新场景](https://mp.weixin.qq.com/s/WEC9UQ6msY0VS8LhTZh-Ew)\n- [MaxKB 应用案例：中船动力人工智能\"智慧动力云助手\"及首批数字员工正式上线](https://mp.weixin.qq.com/s/OGcEkjh9DzGO1Tkc9nr7qg)\n- [MaxKB 应用案例：AI+矿山：DeepSeek助力绿色智慧矿山智慧“升级”](https://mp.weixin.qq.com/s/SZstxTvVoLZg0ECbZbfpIA)\n- [MaxKB 应用案例：DeepSeek落地弘盛铜业：国产大模型点亮\"黑灯工厂\"新引擎](https://mp.weixin.qq.com/s/Eczdx574MS5RMF7WfHN7_A)\n- [MaxKB 应用案例：拥抱智能时代！中国五矿以 “AI+”赋能企业发展](https://mp.weixin.qq.com/s/D5vBtlX2E81pWE3_2OgWSw)\n- [MaxKB 应用案例：DeepSeek赋能中冶武勘AI智能体](https://mp.weixin.qq.com/s/8m0vxGcWXNdZazziQrLyxg)\n- [MaxKB 应用案例：重磅！陕西广电网络“秦岭云”平台实现DeepSeek本地化部署](https://mp.weixin.qq.com/s/ZKmEU_wWShK1YDomKJHQeA)\n- [MaxKB 应用案例：粤海集团完成DeepSeek私有化部署，助力集团智能化管理](https://mp.weixin.qq.com/s/2JbVp0-kr9Hfp-0whH4cvg)\n- [MaxKB 应用案例：建筑材料工业信息中心完成DeepSeek本地化部署，推动行业数智化转型新发展](https://mp.weixin.qq.com/s/HThGSnND3qDF8ySEqiM4jw)\n- [MaxKB 应用案例：一起DeepSeek！福建设计以AI大模型开启新篇章](https://mp.weixin.qq.com/s/m67e-H7iQBg3d24NM82UjA)"
  },
  {
    "path": "apps/__init__.py",
    "content": ""
  },
  {
    "path": "apps/application/__init__.py",
    "content": ""
  },
  {
    "path": "apps/application/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "apps/application/api/application_access_token.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_access_token.py\n    @date：2025/6/9 17:46\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom application.serializers.application_access_token import AccessTokenEditSerializer\nfrom common.mixins.api_mixin import APIMixin\n\n\nclass ApplicationAccessTokenAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"workspace_id\",\n            description=\"工作空间id\",\n            type=OpenApiTypes.STR,\n            location='path',\n            required=True,\n        ), OpenApiParameter(\n            name=\"application_id\",\n            description=\"应用id\",\n            type=OpenApiTypes.STR,\n            location='path',\n            required=True,\n        )]\n\n    @staticmethod\n    def get_request():\n        return AccessTokenEditSerializer\n"
  },
  {
    "path": "apps/application/api/application_api.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application.py\n    @date：2025/5/26 16:59\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\nfrom rest_framework import serializers\n\nfrom application.serializers.application import ApplicationCreateSerializer, ApplicationListResponse, \\\n    ApplicationImportRequest, ApplicationEditSerializer, TextToSpeechRequest, SpeechToTextRequest, PlayDemoTextRequest\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, ResultPageSerializer, DefaultResultSerializer\n\n\nclass ApplicationCreateRequest(ApplicationCreateSerializer.SimplateRequest):\n    work_flow = serializers.DictField(required=True, label=_(\"Workflow Objects\"))\n\n\nclass ApplicationCreateResponse(ResultSerializer):\n    def get_data(self):\n        return ApplicationCreateSerializer.ApplicationResponse()\n\n\nclass ApplicationListResult(ResultSerializer):\n    def get_data(self):\n        return ApplicationListResponse(many=True)\n\n\nclass ApplicationPageResult(ResultPageSerializer):\n    def get_data(self):\n        return ApplicationListResponse(many=True)\n\n\nclass ApplicationQueryAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"current_page\",\n                description=_(\"Current page\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"page_size\",\n                description=_(\"Page size\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"folder_id\",\n                description=_(\"folder id\"),\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"name\",\n                description=_(\"Application Name\"),\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"desc\",\n                description=_(\"Application Description\"),\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"user_id\",\n                description=_(\"User ID\"),\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"publish_status\",\n                description=_(\"Publish status\") + '(published|unpublished)',\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            )\n        ]\n\n    @staticmethod\n    def get_response():\n        return ApplicationListResult\n\n    @staticmethod\n    def get_page_response():\n        return ApplicationPageResult\n\n\nclass ApplicationCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_request():\n        return ApplicationCreateRequest\n\n    @staticmethod\n    def get_response():\n        return ApplicationCreateResponse\n\n\nclass ApplicationImportAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        ApplicationCreateAPI.get_parameters()\n\n    @staticmethod\n    def get_request():\n        return ApplicationImportRequest\n\n\nclass ApplicationOperateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"application_id\",\n                description=\"应用id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n\nclass ApplicationExportAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return ApplicationOperateAPI.get_parameters()\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ApplicationEditAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return ApplicationEditSerializer\n\n\nclass TextToSpeechAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return ApplicationOperateAPI.get_parameters()\n\n    @staticmethod\n    def get_request():\n        return TextToSpeechRequest\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass SpeechToTextAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return ApplicationOperateAPI.get_parameters()\n\n    @staticmethod\n    def get_request():\n        return SpeechToTextRequest\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass PlayDemoTextAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return ApplicationOperateAPI.get_parameters()\n\n    @staticmethod\n    def get_request():\n        return PlayDemoTextRequest\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n"
  },
  {
    "path": "apps/application/api/application_api_key.py",
    "content": "from drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom application.serializers.application_api_key import EditApplicationKeySerializer, ApplicationKeySerializerModel\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer\n\n\nclass ApplicationKeyListResult(ResultSerializer):\n    def get_data(self):\n        return ApplicationKeySerializerModel(many=True)\n\n\nclass ApplicationKeyResult(ResultSerializer):\n    def get_data(self):\n        return ApplicationKeySerializerModel()\n\n\nclass ApplicationKeyAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"application_id\",\n                description=\"application ID\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_response():\n        return ApplicationKeyResult\n\n    class List(APIMixin):\n        @staticmethod\n        def get_response():\n            return ApplicationKeyListResult\n\n    class Operate(APIMixin):\n        @staticmethod\n        def get_parameters():\n            return [*ApplicationKeyAPI.get_parameters(), OpenApiParameter(\n                name=\"api_key_id\",\n                description=\"ApiKeyId\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )]\n\n        @staticmethod\n        def get_request():\n            return EditApplicationKeySerializer\n"
  },
  {
    "path": "apps/application/api/application_chat.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_chat.py\n    @date：2025/6/10 13:54\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom application.serializers.application_chat import ApplicationChatQuerySerializers, \\\n    ApplicationChatResponseSerializers, ApplicationChatRecordExportRequest\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, ResultPageSerializer\n\n\nclass ApplicationChatListResponseSerializers(ResultSerializer):\n    def get_data(self):\n        return ApplicationChatResponseSerializers(many=True)\n\n\nclass ApplicationChatPageResponseSerializers(ResultPageSerializer):\n    def get_data(self):\n        return ApplicationChatResponseSerializers(many=True)\n\n\nclass ApplicationChatQueryAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return ApplicationChatQuerySerializers\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"application_id\",\n                description=\"application ID\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ), OpenApiParameter(\n                name=\"start_time\",\n                description=\"start Time\",\n                type=OpenApiTypes.STR,\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"end_time\",\n                description=\"end Time\",\n                type=OpenApiTypes.STR,\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"abstract\",\n                description=\"summary\",\n                type=OpenApiTypes.STR,\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"username\",\n                description=\"username\",\n                type=OpenApiTypes.STR,\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"min_star\",\n                description=_(\"Minimum number of likes\"),\n                type=OpenApiTypes.INT,\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"min_trample\",\n                description=_(\"Minimum number of clicks\"),\n                type=OpenApiTypes.INT,\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"comparer\",\n                description=_(\"Comparator\"),\n                type=OpenApiTypes.STR,\n                required=False,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return ApplicationChatListResponseSerializers\n\n\nclass ApplicationChatQueryPageAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return ApplicationChatQueryAPI.get_request()\n\n    @staticmethod\n    def get_parameters():\n        return [\n            *ApplicationChatQueryAPI.get_parameters(),\n            OpenApiParameter(\n                name=\"current_page\",\n                description=_(\"Current page\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"page_size\",\n                description=_(\"Page size\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n\n        ]\n\n    @staticmethod\n    def get_response():\n        return ApplicationChatPageResponseSerializers\n\n\nclass ApplicationChatExportAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return ApplicationChatRecordExportRequest\n\n    @staticmethod\n    def get_parameters():\n        return ApplicationChatQueryAPI.get_parameters()\n\n    @staticmethod\n    def get_response():\n        return None\n"
  },
  {
    "path": "apps/application/api/application_chat_link.py",
    "content": "\"\"\"\n    @project: MaxKB\n    @Author: niu\n    @file: application_chat_link.py\n    @date: 2026/2/9 16:59\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\nfrom django.utils.translation import gettext_lazy as _\n\nfrom application.serializers.application_chat_link import ChatRecordShareLinkRequestSerializer\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import DefaultResultSerializer\n\n\nclass ChatRecordLinkAPI(APIMixin):\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n    @staticmethod\n    def get_request():\n        return ChatRecordShareLinkRequestSerializer\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"application_id\",\n                description=\"Application ID\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"chat_id\",\n                description=_(\"Chat ID\"),\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\nclass ChatRecordDetailShareAPI(APIMixin):\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"link\",\n                description=\"链接\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]"
  },
  {
    "path": "apps/application/api/application_chat_record.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_chat_record.py\n    @date：2025/6/10 15:19\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom application.serializers.application_chat_record import ApplicationChatRecordAddKnowledgeSerializer, \\\n    ApplicationChatRecordImproveInstanceSerializer\nfrom common.mixins.api_mixin import APIMixin\n\n\nclass ApplicationChatRecordQueryAPI(APIMixin):\n    @staticmethod\n    def get_response():\n        pass\n\n    @staticmethod\n    def get_request():\n        pass\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"application_id\",\n                description=\"Application ID\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"chat_id\",\n                description=_(\"Chat ID\"),\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"order_asc\",\n                description=_(\"Is it in order\"),\n                type=OpenApiTypes.BOOL,\n                required=True,\n            )\n        ]\n\n\nclass ApplicationChatRecordPageQueryAPI(APIMixin):\n    @staticmethod\n    def get_response():\n        pass\n\n    @staticmethod\n    def get_request():\n        pass\n\n    @staticmethod\n    def get_parameters():\n        return [*ApplicationChatRecordQueryAPI.get_parameters(),\n                OpenApiParameter(\n                    name=\"current_page\",\n                    description=_(\"Current page\"),\n                    type=OpenApiTypes.INT,\n                    location='path',\n                    required=True,\n                ),\n                OpenApiParameter(\n                    name=\"page_size\",\n                    description=_(\"Page size\"),\n                    type=OpenApiTypes.INT,\n                    location='path',\n                    required=True,\n                )]\n\n\nclass ApplicationChatRecordImproveParagraphAPI(APIMixin):\n    @staticmethod\n    def get_response():\n        pass\n\n    @staticmethod\n    def get_request():\n        return ApplicationChatRecordImproveInstanceSerializer\n\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"workspace_id\",\n            description=\"工作空间id\",\n            type=OpenApiTypes.STR,\n            location='path',\n            required=True,\n        ),\n            OpenApiParameter(\n                name=\"application_id\",\n                description=\"Application ID\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"chat_id\",\n                description=_(\"Chat ID\"),\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"chat_record_id\",\n                description=_(\"Chat Record ID\"),\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=_(\"Knowledge ID\"),\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=_(\"Document ID\"),\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    class Operate(APIMixin):\n        @staticmethod\n        def get_parameters():\n            return [*ApplicationChatRecordImproveParagraphAPI.get_parameters(), OpenApiParameter(\n                name=\"paragraph_id\",\n                description=_(\"Paragraph ID\"),\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )]\n\n\nclass ApplicationChatRecordAddKnowledgeAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return ApplicationChatRecordAddKnowledgeSerializer\n\n    @staticmethod\n    def get_response():\n        return None\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"application_id\",\n                description=\"Application ID\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )]\n"
  },
  {
    "path": "apps/application/api/application_stats.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_stats.py\n    @date：2025/6/9 20:45\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom application.serializers.application_stats import ApplicationStatsSerializer\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer\n\n\nclass ApplicationStatsResult(ResultSerializer):\n    def get_data(self):\n        return ApplicationStatsSerializer(many=True)\n\n\nclass ApplicationStatsAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"workspace_id\",\n            description=\"工作空间id\",\n            type=OpenApiTypes.STR,\n            location='path',\n            required=True,\n        ),\n            OpenApiParameter(\n                name=\"application_id\",\n                description=\"application ID\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"start_time\",\n                description=\"start Time\",\n                type=OpenApiTypes.STR,\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"end_time\",\n                description=\"end Time\",\n                type=OpenApiTypes.STR,\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return ApplicationStatsResult\n"
  },
  {
    "path": "apps/application/api/application_version.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_version.py\n    @date：2025/6/4 17:33\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom application.serializers.application_version import ApplicationVersionModelSerializer\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, PageDataResponse, ResultPageSerializer\n\n\nclass ApplicationListVersionResult(ResultSerializer):\n    def get_data(self):\n        return ApplicationVersionModelSerializer(many=True)\n\n\nclass ApplicationPageVersionResult(ResultPageSerializer):\n    def get_data(self):\n        return ApplicationVersionModelSerializer(many=True)\n\n\nclass ApplicationWorkflowVersionResult(ResultSerializer):\n    def get_data(self):\n        return ApplicationVersionModelSerializer()\n\n\nclass ApplicationVersionAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"application_id\",\n                description=\"application ID\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n\nclass ApplicationVersionOperateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"application_version_id\",\n                description=\"工作流版本id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n            , *ApplicationVersionAPI.get_parameters()\n        ]\n\n    @staticmethod\n    def get_response():\n        return ApplicationWorkflowVersionResult\n\n\nclass ApplicationVersionListAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"name\",\n                description=\"Version Name\",\n                type=OpenApiTypes.STR,\n                required=False,\n            )\n            , *ApplicationVersionAPI.get_parameters()]\n\n    @staticmethod\n    def get_response():\n        return ApplicationListVersionResult\n\n\nclass ApplicationVersionPageAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return ApplicationVersionListAPI.get_parameters()\n\n    @staticmethod\n    def get_response():\n        return ApplicationPageVersionResult\n"
  },
  {
    "path": "apps/application/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass ApplicationConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'application'\n"
  },
  {
    "path": "apps/application/chat_pipeline/I_base_chat_pipeline.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： I_base_chat_pipeline.py\n    @date：2024/1/9 17:25\n    @desc:\n\"\"\"\nimport time\nfrom abc import abstractmethod\nfrom typing import Type\nimport uuid_utils.compat as uuid\nfrom rest_framework import serializers\n\nfrom knowledge.models import Paragraph\n\n\nclass ParagraphPipelineModel:\n\n    def __init__(self, _id: str, document_id: str, knowledge_id: str, content: str, title: str, status: str,\n                 is_active: bool, comprehensive_score: float, similarity: float, knowledge_name: str,\n                 document_name: str,\n                 hit_handling_method: str, directly_return_similarity: float, knowledge_type, meta: dict = None):\n        self.id = _id\n        self.document_id = document_id\n        self.knowledge_id = knowledge_id\n        self.content = content\n        self.title = title\n        self.status = status,\n        self.is_active = is_active\n        self.comprehensive_score = comprehensive_score\n        self.similarity = similarity\n        self.knowledge_name = knowledge_name\n        self.document_name = document_name\n        self.hit_handling_method = hit_handling_method\n        self.directly_return_similarity = directly_return_similarity\n        self.meta = meta\n        self.knowledge_type = knowledge_type\n\n    def to_dict(self):\n        return {\n            'id': self.id,\n            'document_id': self.document_id,\n            'knowledge_id': self.knowledge_id,\n            'content': self.content,\n            'title': self.title,\n            'status': self.status,\n            'is_active': self.is_active,\n            'comprehensive_score': self.comprehensive_score,\n            'similarity': self.similarity,\n            'knowledge_name': self.knowledge_name,\n            'document_name': self.document_name,\n            'knowledge_type': self.knowledge_type,\n            'meta': self.meta,\n        }\n\n    class builder:\n        def __init__(self):\n            self.similarity = None\n            self.paragraph = {}\n            self.comprehensive_score = None\n            self.document_name = None\n            self.knowledge_name = None\n            self.knowledge_type = None\n            self.hit_handling_method = None\n            self.directly_return_similarity = 0.9\n            self.meta = {}\n\n        def add_paragraph(self, paragraph):\n            if isinstance(paragraph, Paragraph):\n                self.paragraph = {'id': paragraph.id,\n                                  'document_id': paragraph.document_id,\n                                  'knowledge_id': paragraph.knowledge_id,\n                                  'content': paragraph.content,\n                                  'title': paragraph.title,\n                                  'status': paragraph.status,\n                                  'is_active': paragraph.is_active,\n                                  }\n            else:\n                self.paragraph = paragraph\n            return self\n\n        def add_knowledge_name(self, knowledge_name):\n            self.knowledge_name = knowledge_name\n            return self\n\n        def add_knowledge_type(self, knowledge_type):\n            self.knowledge_type = knowledge_type\n            return self\n\n        def add_document_name(self, document_name):\n            self.document_name = document_name\n            return self\n\n        def add_hit_handling_method(self, hit_handling_method):\n            self.hit_handling_method = hit_handling_method\n            return self\n\n        def add_directly_return_similarity(self, directly_return_similarity):\n            self.directly_return_similarity = directly_return_similarity\n            return self\n\n        def add_comprehensive_score(self, comprehensive_score: float):\n            self.comprehensive_score = comprehensive_score\n            return self\n\n        def add_similarity(self, similarity: float):\n            self.similarity = similarity\n            return self\n\n        def add_meta(self, meta: dict):\n            self.meta = meta\n            return self\n\n        def build(self):\n            return ParagraphPipelineModel(str(self.paragraph.get('id')), str(self.paragraph.get('document_id')),\n                                          str(self.paragraph.get('knowledge_id')),\n                                          self.paragraph.get('content'), self.paragraph.get('title'),\n                                          self.paragraph.get('status'),\n                                          self.paragraph.get('is_active'),\n                                          self.comprehensive_score, self.similarity, self.knowledge_name,\n                                          self.document_name, self.hit_handling_method, self.directly_return_similarity,\n                                          self.knowledge_type,\n                                          self.meta)\n\n\nclass IBaseChatPipelineStep:\n    def __init__(self):\n        # 当前步骤上下文,用于存储当前步骤信息\n        self.context = {}\n        self.status = 200\n        self.err_message = ''\n\n    @abstractmethod\n    def get_step_serializer(self, manage) -> Type[serializers.Serializer]:\n        pass\n\n    def valid_args(self, manage):\n        step_serializer_clazz = self.get_step_serializer(manage)\n        step_serializer = step_serializer_clazz(data=manage.context)\n        step_serializer.is_valid(raise_exception=True)\n        self.context['step_args'] = step_serializer.data\n\n    def run(self, manage):\n        \"\"\"\n\n        :param manage:      步骤管理器\n        :return: 执行结果\n        \"\"\"\n        try:\n            start_time = time.time()\n            self.context['start_time'] = start_time\n            # 校验参数,\n            self.valid_args(manage)\n            self._run(manage)\n            self.context['run_time'] = time.time() - start_time\n        except Exception as e:\n            self.err_message = str(e)\n            self.status = 500\n            chat_record_id = manage.context.get('chat_record_id') or str(uuid.uuid7())\n            manage.context['message_tokens'] = 0\n            manage.context['answer_tokens'] = 0\n            end_time = time.time()\n            manage.context['run_time'] = end_time - (manage.context.get('start_time') or end_time)\n            post_response_handler = manage.context.get('post_response_handler')\n            post_response_handler.handler(manage.context.get('chat_id'), chat_record_id,\n                                          manage.context.get('paragraph_list') or [],\n                                          manage.context.get('problem_text'),\n                                          str(e), manage, self, manage.context.get('padding_problem_text'),\n                                          reasoning_content='')\n\n            raise e\n\n    def _run(self, manage):\n        pass\n\n    def execute(self, **kwargs):\n        pass\n\n    def get_details(self, manage, **kwargs):\n        \"\"\"\n        运行详情\n        :return: 步骤详情\n        \"\"\"\n        return None\n"
  },
  {
    "path": "apps/application/chat_pipeline/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/1/9 17:23\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/chat_pipeline/pipeline_manage.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： pipeline_manage.py\n    @date：2024/1/9 17:40\n    @desc:\n\"\"\"\nimport time\nfrom functools import reduce\nfrom typing import List, Type, Dict\n\nfrom application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep\nfrom common.handle.base_to_response import BaseToResponse\nfrom common.handle.impl.response.system_to_response import SystemToResponse\n\n\nclass PipelineManage:\n    def __init__(self, step_list: List[Type[IBaseChatPipelineStep]],\n                 base_to_response: BaseToResponse = SystemToResponse(),\n                 debug=False):\n        # 步骤执行器\n        self.step_list = [step() for step in step_list]\n        self.run_step_list = []\n        # 上下文\n        self.context = {'message_tokens': 0, 'answer_tokens': 0}\n        self.base_to_response = base_to_response\n        self.debug = debug\n\n    def run(self, context: Dict = None):\n        self.context['start_time'] = time.time()\n        if context is not None:\n            for key, value in context.items():\n                self.context[key] = value\n        for step in self.step_list:\n            self.run_step_list.append(step)\n            step.run(self)\n\n    def get_details(self):\n        return reduce(lambda x, y: {**x, **y}, [{item.get('step_type'): item} for item in\n                                                filter(lambda r: r is not None,\n                                                       [row.get_details(self) for row in self.run_step_list])], {})\n\n    def get_base_to_response(self):\n        return self.base_to_response\n\n    class builder:\n        def __init__(self):\n            self.step_list: List[Type[IBaseChatPipelineStep]] = []\n            self.base_to_response = SystemToResponse()\n            self.debug = False\n\n        def append_step(self, step: Type[IBaseChatPipelineStep]):\n            self.step_list.append(step)\n            return self\n\n        def add_base_to_response(self, base_to_response: BaseToResponse):\n            self.base_to_response = base_to_response\n            return self\n\n        def add_debug(self, debug):\n            self.debug = debug\n            return self\n\n        def build(self):\n            return PipelineManage(step_list=self.step_list, base_to_response=self.base_to_response, debug=self.debug)\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/1/9 18:23\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/chat_step/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/1/9 18:23\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/chat_step/i_chat_step.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_chat_step.py\n    @date：2024/1/9 18:17\n    @desc: 对话\n\"\"\"\nfrom abc import abstractmethod\nfrom typing import Type, List\n\nfrom django.utils.translation import gettext_lazy as _\nfrom langchain.chat_models.base import BaseChatModel\nfrom langchain_core.messages import BaseMessage\nfrom rest_framework import serializers\n\nfrom application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel\nfrom application.chat_pipeline.pipeline_manage import PipelineManage\nfrom application.serializers.application import NoReferencesSetting\nfrom common.field.common import InstanceField\n\n\nclass ModelField(serializers.Field):\n    def to_internal_value(self, data):\n        if not isinstance(data, BaseChatModel):\n            self.fail(_('Model type error'), value=data)\n        return data\n\n    def to_representation(self, value):\n        return value\n\n\nclass MessageField(serializers.Field):\n    def to_internal_value(self, data):\n        if not isinstance(data, BaseMessage):\n            self.fail(_('Message type error'), value=data)\n        return data\n\n    def to_representation(self, value):\n        return value\n\n\nclass PostResponseHandler:\n    @abstractmethod\n    def handler(self, chat_id, chat_record_id, paragraph_list: List[ParagraphPipelineModel], problem_text: str,\n                answer_text,\n                manage, step, padding_problem_text: str = None, **kwargs):\n        pass\n\n\nclass IChatStep(IBaseChatPipelineStep):\n    class InstanceSerializer(serializers.Serializer):\n        # 对话列表\n        message_list = serializers.ListField(required=True, child=MessageField(required=True),\n                                             label=_(\"Conversation list\"))\n        model_id = serializers.UUIDField(required=False, allow_null=True, label=_(\"Model id\"))\n        # 段落列表\n        paragraph_list = serializers.ListField(label=_(\"Paragraph List\"))\n        # 对话id\n        chat_id = serializers.UUIDField(required=True, label=_(\"Conversation ID\"))\n        # 用户问题\n        problem_text = serializers.CharField(required=True, label=_(\"User Questions\"))\n        # 后置处理器\n        post_response_handler = InstanceField(model_type=PostResponseHandler,\n                                              label=_(\"Post-processor\"))\n        # 补全问题\n        padding_problem_text = serializers.CharField(required=False,\n                                                     label=_(\"Completion Question\"))\n        # 是否使用流的形式输出\n        stream = serializers.BooleanField(required=False, label=_(\"Streaming Output\"))\n        chat_user_id = serializers.CharField(required=True, label=_(\"Chat user id\"))\n        chat_record_id = serializers.CharField(required=False, label=_(\"Chat record id\"))\n\n        chat_user_type = serializers.CharField(required=True, label=_(\"Chat user Type\"))\n        # 未查询到引用分段\n        no_references_setting = NoReferencesSetting(required=True,\n                                                    label=_(\"No reference segment settings\"))\n\n        workspace_id = serializers.CharField(required=True, label=_(\"Workspace ID\"))\n\n        model_setting = serializers.DictField(required=True, allow_null=True,\n                                              label=_(\"Model settings\"))\n\n        model_params_setting = serializers.DictField(required=False, allow_null=True,\n                                                     label=_(\"Model parameter settings\"))\n        mcp_tool_ids = serializers.JSONField(label=\"MCP工具ID列表\", required=False, default=list)\n        mcp_servers = serializers.JSONField(label=\"MCP服务列表\", required=False, default=dict)\n        mcp_source = serializers.CharField(label=\"MCP Source\", required=False, default=\"referencing\")\n        tool_ids = serializers.JSONField(label=\"工具ID列表\", required=False, default=list)\n        application_ids = serializers.JSONField(label=\"应用ID列表\", required=False, default=list)\n        skill_tool_ids = serializers.JSONField(label=\"技能ID列表\", required=False, default=list)\n        mcp_output_enable = serializers.BooleanField(label=\"MCP输出是否启用\", required=False, default=True)\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            message_list: List = self.initial_data.get('message_list')\n            for message in message_list:\n                if not isinstance(message, BaseMessage):\n                    raise Exception(_(\"message type error\"))\n\n    def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:\n        return self.InstanceSerializer\n\n    def _run(self, manage: PipelineManage):\n        chat_result = self.execute(**self.context['step_args'], manage=manage)\n        manage.context['chat_result'] = chat_result\n\n    @abstractmethod\n    def execute(self, message_list: List[BaseMessage],\n                chat_id, problem_text,\n                post_response_handler: PostResponseHandler,\n                model_id: str = None,\n                workspace_id: str = None,\n                paragraph_list=None,\n                manage: PipelineManage = None,\n                padding_problem_text: str = None, stream: bool = True, chat_user_id=None, chat_user_type=None,\n                no_references_setting=None, model_params_setting=None, model_setting=None,\n                mcp_tool_ids=None, mcp_servers='', mcp_source=\"referencing\",\n                tool_ids=None, application_ids=None, skill_tool_ids=None, mcp_output_enable=True,\n                **kwargs):\n        pass\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_chat_step.py\n    @date：2024/1/9 18:25\n    @desc: 对话step Base实现\n\"\"\"\nimport json\nimport time\nimport traceback\nfrom typing import List\n\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\nfrom django.http import StreamingHttpResponse\nfrom django.utils.translation import gettext as _\nfrom langchain.chat_models.base import BaseChatModel\nfrom langchain_core.messages import AIMessageChunk, SystemMessage, BaseMessage, HumanMessage, AIMessage\nfrom rest_framework import status\n\nfrom application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel\nfrom application.chat_pipeline.pipeline_manage import PipelineManage\nfrom application.chat_pipeline.step.chat_step.i_chat_step import IChatStep, PostResponseHandler\nfrom application.flow.tools import Reasoning, mcp_response_generator\nfrom application.models import ApplicationChatUserStats, ChatUserType, Application, ApplicationApiKey, \\\n    ApplicationAccessToken\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.rsa_util import rsa_long_decrypt\nfrom common.utils.shared_resource_auth import filter_authorized_ids\nfrom common.utils.tool_code import ToolExecutor\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\nfrom tools.models import Tool\n\n\ndef add_access_num(chat_user_id=None, chat_user_type=None, application_id=None):\n    if [ChatUserType.ANONYMOUS_USER.value, ChatUserType.CHAT_USER.value].__contains__(\n            chat_user_type) and application_id is not None:\n        application_public_access_client = (QuerySet(ApplicationChatUserStats).filter(chat_user_id=chat_user_id,\n                                                                                      chat_user_type=chat_user_type,\n                                                                                      application_id=application_id)\n                                            .first())\n        if application_public_access_client is not None:\n            application_public_access_client.access_num = application_public_access_client.access_num + 1\n            application_public_access_client.intraday_access_num = application_public_access_client.intraday_access_num + 1\n            application_public_access_client.save()\n\n\ndef write_context(step, manage, request_token, response_token, all_text):\n    step.context['message_tokens'] = request_token\n    step.context['answer_tokens'] = response_token\n    current_time = time.time()\n    step.context['answer_text'] = all_text\n    step.context['run_time'] = current_time - step.context['start_time']\n    manage.context['run_time'] = current_time - manage.context['start_time']\n    manage.context['message_tokens'] = manage.context['message_tokens'] + request_token\n    manage.context['answer_tokens'] = manage.context['answer_tokens'] + response_token\n\n\ndef event_content(response,\n                  chat_id,\n                  chat_record_id,\n                  paragraph_list: List[ParagraphPipelineModel],\n                  post_response_handler: PostResponseHandler,\n                  manage,\n                  step,\n                  chat_model,\n                  message_list: List[BaseMessage],\n                  problem_text: str,\n                  padding_problem_text: str = None,\n                  chat_user_id=None, chat_user_type=None,\n                  is_ai_chat: bool = None,\n                  model_setting=None):\n    if model_setting is None:\n        model_setting = {}\n    reasoning_content_enable = model_setting.get('reasoning_content_enable', False)\n    reasoning_content_start = model_setting.get('reasoning_content_start', '<think>')\n    reasoning_content_end = model_setting.get('reasoning_content_end', '</think>')\n    reasoning = Reasoning(reasoning_content_start,\n                          reasoning_content_end)\n    all_text = ''\n    reasoning_content = ''\n    try:\n        response_reasoning_content = False\n        for chunk in response:\n            reasoning_chunk = reasoning.get_reasoning_content(chunk)\n            content_chunk = reasoning_chunk.get('content')\n            if 'reasoning_content' in chunk.additional_kwargs:\n                response_reasoning_content = True\n                reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '')\n            else:\n                reasoning_content_chunk = reasoning_chunk.get('reasoning_content')\n            content_chunk = reasoning._normalize_content(content_chunk)\n            all_text += content_chunk\n            if reasoning_content_chunk is None:\n                reasoning_content_chunk = ''\n            reasoning_content += reasoning_content_chunk\n            yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',\n                                                                         [], content_chunk,\n                                                                         False,\n                                                                         0, 0, {'node_is_end': False,\n                                                                                'view_type': 'many_view',\n                                                                                'node_type': 'ai-chat-node',\n                                                                                'real_node_id': 'ai-chat-node',\n                                                                                'reasoning_content': reasoning_content_chunk if reasoning_content_enable else ''})\n        reasoning_chunk = reasoning.get_end_reasoning_content()\n        all_text += reasoning_chunk.get('content')\n        reasoning_content_chunk = \"\"\n        if not response_reasoning_content:\n            reasoning_content_chunk = reasoning_chunk.get(\n                'reasoning_content')\n        yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',\n                                                                     [], reasoning_chunk.get('content'),\n                                                                     False,\n                                                                     0, 0, {'node_is_end': False,\n                                                                            'view_type': 'many_view',\n                                                                            'node_type': 'ai-chat-node',\n                                                                            'real_node_id': 'ai-chat-node',\n                                                                            'reasoning_content'\n                                                                            : reasoning_content_chunk if reasoning_content_enable else ''})\n        # 获取token\n        if is_ai_chat:\n            try:\n                request_token = chat_model.get_num_tokens_from_messages(message_list)\n                response_token = chat_model.get_num_tokens(all_text)\n            except Exception as e:\n                request_token = 0\n                response_token = 0\n        else:\n            request_token = 0\n            response_token = 0\n        write_context(step, manage, request_token, response_token, all_text)\n        post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,\n                                      all_text, manage, step, padding_problem_text,\n                                      reasoning_content=reasoning_content if reasoning_content_enable else '')\n        yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',\n                                                                     [], '', True,\n                                                                     request_token, response_token,\n                                                                     {'node_is_end': True, 'view_type': 'many_view',\n                                                                      'node_type': 'ai-chat-node'})\n        if not manage.debug:\n            add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id'))\n    except Exception as e:\n        maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}')\n        all_text = 'Exception:' + str(e)\n        write_context(step, manage, 0, 0, all_text)\n        post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,\n                                      all_text, manage, step, padding_problem_text, reasoning_content='')\n        if not manage.debug:\n            add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id'))\n        yield manage.get_base_to_response().to_stream_chunk_response(chat_id, str(chat_record_id), 'ai-chat-node',\n                                                                     [], all_text,\n                                                                     False,\n                                                                     0, 0, {'node_is_end': False,\n                                                                            'view_type': 'many_view',\n                                                                            'node_type': 'ai-chat-node',\n                                                                            'real_node_id': 'ai-chat-node',\n                                                                            'reasoning_content': ''})\n\n\nclass BaseChatStep(IChatStep):\n    def execute(self, message_list: List[BaseMessage],\n                chat_id,\n                problem_text,\n                post_response_handler: PostResponseHandler,\n                model_id: str = None,\n                workspace_id: str = None,\n                paragraph_list=None,\n                manage: PipelineManage = None,\n                padding_problem_text: str = None,\n                stream: bool = True,\n                chat_user_id=None, chat_user_type=None,\n                no_references_setting=None,\n                model_params_setting=None,\n                model_setting=None,\n                mcp_tool_ids=None,\n                mcp_servers='',\n                mcp_source=\"referencing\",\n                tool_ids=None,\n                application_ids=None,\n                skill_tool_ids=None,\n                mcp_output_enable=True,\n                **kwargs):\n        chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,\n                                                              **model_params_setting) if model_id is not None else None\n        if stream:\n            return self.execute_stream(message_list, chat_id, problem_text, post_response_handler, chat_model,\n                                       paragraph_list,\n                                       manage, padding_problem_text, chat_user_id, chat_user_type,\n                                       no_references_setting,\n                                       model_setting,\n                                       mcp_tool_ids, mcp_servers, mcp_source, tool_ids,\n                                       application_ids,\n                                       skill_tool_ids,\n                                       workspace_id,\n                                       mcp_output_enable)\n        else:\n            return self.execute_block(message_list, chat_id, problem_text, post_response_handler, chat_model,\n                                      paragraph_list,\n                                      manage, padding_problem_text, chat_user_id, chat_user_type, no_references_setting,\n                                      model_setting,\n                                      mcp_tool_ids, mcp_servers, mcp_source, tool_ids,\n                                      application_ids,\n                                      skill_tool_ids,\n                                      workspace_id,\n                                      mcp_output_enable)\n\n    def get_details(self, manage, **kwargs):\n        return {\n            'status': self.status,\n            'err_message': self.err_message,\n            'step_type': 'chat_step',\n            'run_time': self.context.get('run_time') or 0,\n            'model_id': str(manage.context['model_id']),\n            'message_list': self.reset_message_list(self.context['step_args'].get('message_list'),\n                                                    self.context.get('answer_text')),\n            'message_tokens': self.context.get('message_tokens'),\n            'answer_tokens': self.context.get('answer_tokens'),\n            'cost': 0,\n        }\n\n    @staticmethod\n    def reset_message_list(message_list: List[BaseMessage], answer_text):\n        result = [{'role': 'user' if isinstance(message, HumanMessage) else (\n            'system' if isinstance(message, SystemMessage) else 'ai'), 'content': message.content} for\n                  message\n                  in\n                  message_list]\n        result.append({'role': 'ai', 'content': answer_text})\n        return result\n\n    def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_ids, tool_ids,\n                            application_ids, skill_tool_ids, mcp_output_enable, chat_model, message_list, agent_id,\n                            chat_id):\n\n        mcp_servers_config = {}\n\n        # 迁移过来mcp_source是None\n        if mcp_source is None:\n            mcp_source = 'custom'\n        # 兼容老数据\n        if not mcp_tool_ids:\n            mcp_tool_ids = []\n        if mcp_source == 'custom' and mcp_servers and '\"stdio\"' not in mcp_servers:\n            mcp_servers_config = json.loads(mcp_servers)\n        elif mcp_tool_ids:\n            mcp_tools = QuerySet(Tool).filter(id__in=mcp_tool_ids).values()\n            for mcp_tool in mcp_tools:\n                if mcp_tool and mcp_tool['is_active']:\n                    mcp_servers_config = {**mcp_servers_config, **json.loads(mcp_tool['code'])}\n\n        tool_init_params = {}\n        if tool_ids and len(tool_ids) > 0:  # 如果有工具ID，则将其转换为MCP\n            self.context['tool_ids'] = tool_ids\n            for tool_id in tool_ids:\n                tool = QuerySet(Tool).filter(id=tool_id).first()\n                if tool is None or tool.is_active is False:\n                    continue\n                executor = ToolExecutor()\n                if tool.init_params is not None:\n                    params = json.loads(rsa_long_decrypt(tool.init_params))\n                    tool_init_params = json.loads(rsa_long_decrypt(tool.init_params))\n                else:\n                    params = {}\n                tool_config = executor.get_tool_mcp_config(tool, params)\n\n                mcp_servers_config[str(tool.id)] = tool_config\n\n        if application_ids and len(application_ids) > 0:\n            self.context['application_ids'] = application_ids\n            for application_id in application_ids:\n                app = QuerySet(Application).filter(id=application_id, is_publish=True).first()\n                if app is None:\n                    continue\n                app_key = QuerySet(ApplicationApiKey).filter(application_id=application_id, is_active=True).first()\n                if app_key is not None:\n                    api_key = app_key.secret_key\n                    application_access_token = QuerySet(ApplicationAccessToken).filter(\n                        application_id=app_key.application_id\n                    ).first()\n                    if application_access_token is not None and application_access_token.authentication:\n                        raise AppApiException(\n                            500,\n                            _('Agent 【{name}】 access token authentication is not supported for agent tool').format(\n                                name=app.name)\n                        )\n                else:\n                    raise AppApiException(\n                        500,\n                        _('Agent Key is required for agent tool 【{name}】').format(name=app.name)\n                    )\n                executor = ToolExecutor()\n                app_config = executor.get_app_mcp_config(api_key)\n                mcp_servers_config[app.name] = app_config\n\n        if skill_tool_ids and len(skill_tool_ids) > 0:\n            self.context['skill_tool_ids'] = skill_tool_ids\n            skill_file_items = []\n\n            for tool_id in skill_tool_ids:\n                tool = QuerySet(Tool).filter(id=tool_id, is_active=True).first()\n                if tool is None or tool.is_active is False:\n                    continue\n                init_params_default_value = {i[\"field\"]: i.get('default_value') for i in tool.init_field_list}\n                if tool.init_params is not None:\n                    params = init_params_default_value | json.loads(rsa_long_decrypt(tool.init_params))\n                else:\n                    params = init_params_default_value\n\n                skill_file_items.append({\n                    'tool_id': str(tool.id),\n                    'file_id': tool.code,\n                    'params': params\n                })\n            mcp_servers_config['skills'] = skill_file_items\n\n        if len(mcp_servers_config) > 0:\n            source_id = agent_id\n            source_type = 'APPLICATION'\n            return mcp_response_generator(\n                chat_model, message_list, json.dumps(mcp_servers_config), mcp_output_enable,\n                tool_init_params, source_id, source_type, chat_id\n            )\n\n        return None\n\n    def get_stream_result(self, message_list: List[BaseMessage],\n                          chat_model: BaseChatModel = None,\n                          paragraph_list=None,\n                          no_references_setting=None,\n                          problem_text=None,\n                          mcp_tool_ids=None,\n                          mcp_servers='',\n                          mcp_source=\"referencing\",\n                          tool_ids=None,\n                          application_ids=None,\n                          skill_tool_ids=None,\n                          workspace_id=None,\n                          mcp_output_enable=True,\n                          agent_id=None,\n                          chat_id=None\n                          ):\n        if paragraph_list is None:\n            paragraph_list = []\n        directly_return_chunk_list = [AIMessageChunk(content=paragraph.content)\n                                      for paragraph in paragraph_list if (\n                                              paragraph.hit_handling_method == 'directly_return' and paragraph.similarity >= paragraph.directly_return_similarity)]\n        if directly_return_chunk_list is not None and len(directly_return_chunk_list) > 0:\n            return iter(directly_return_chunk_list), False\n        elif len(paragraph_list) == 0 and no_references_setting.get(\n                'status') == 'designated_answer':\n            return iter(\n                [AIMessageChunk(content=no_references_setting.get('value').replace('{question}', problem_text))]), False\n        if chat_model is None:\n            return iter([AIMessageChunk(\n                _('Sorry, the AI model is not configured. Please go to the application to set up the AI model first.'))]), False\n        else:\n            # 过滤tool_id\n            all_tool_ids = list(set(\n                (mcp_tool_ids or []) +\n                (tool_ids or []) +\n                (skill_tool_ids or [])\n            ))\n            authorized_set = set(filter_authorized_ids('tool', all_tool_ids, workspace_id))\n\n            mcp_tool_ids = [i for i in (mcp_tool_ids or []) if i in authorized_set]\n            tool_ids = [i for i in (tool_ids or []) if i in authorized_set]\n            skill_tool_ids = [i for i in (skill_tool_ids or []) if i in authorized_set]\n            # 处理 MCP 请求\n            mcp_result = self._handle_mcp_request(\n                mcp_source, mcp_servers, mcp_tool_ids, tool_ids,\n                application_ids, skill_tool_ids, mcp_output_enable, chat_model,\n                message_list, agent_id, chat_id\n            )\n            if mcp_result:\n                return mcp_result, True\n            return chat_model.stream(message_list), True\n\n    def execute_stream(self, message_list: List[BaseMessage],\n                       chat_id,\n                       problem_text,\n                       post_response_handler: PostResponseHandler,\n                       chat_model: BaseChatModel = None,\n                       paragraph_list=None,\n                       manage: PipelineManage = None,\n                       padding_problem_text: str = None,\n                       chat_user_id=None, chat_user_type=None,\n                       no_references_setting=None,\n                       model_setting=None,\n                       mcp_tool_ids=None,\n                       mcp_servers='',\n                       mcp_source=\"referencing\",\n                       tool_ids=None,\n                       application_ids=None,\n                       skill_tool_ids=None,\n                       workspace_id=None,\n                       mcp_output_enable=True):\n        chat_result, is_ai_chat = self.get_stream_result(message_list, chat_model, paragraph_list,\n                                                         no_references_setting, problem_text, mcp_tool_ids,\n                                                         mcp_servers, mcp_source, tool_ids,\n                                                         application_ids, skill_tool_ids, workspace_id,\n                                                         mcp_output_enable, manage.context.get('application_id'),\n                                                         chat_id)\n        chat_record_id = self.context.get('step_args', {}).get('chat_record_id') if self.context.get('step_args',\n                                                                                                     {}).get(\n            'chat_record_id') else uuid.uuid7()\n        r = StreamingHttpResponse(\n            streaming_content=event_content(chat_result, chat_id, chat_record_id, paragraph_list,\n                                            post_response_handler, manage, self, chat_model, message_list, problem_text,\n                                            padding_problem_text, chat_user_id, chat_user_type, is_ai_chat,\n                                            model_setting),\n            content_type='text/event-stream;charset=utf-8')\n\n        r['Cache-Control'] = 'no-cache'\n        return r\n\n    def get_block_result(self, message_list: List[BaseMessage],\n                         chat_model: BaseChatModel = None,\n                         paragraph_list=None,\n                         no_references_setting=None,\n                         problem_text=None,\n                         mcp_tool_ids=None,\n                         mcp_servers='',\n                         mcp_source=\"referencing\",\n                         tool_ids=None,\n                         application_ids=None,\n                         skill_tool_ids=None,\n                         workspace_id=None,\n                         mcp_output_enable=True,\n                         application_id=None,\n                         chat_id=None\n                         ):\n        if paragraph_list is None:\n            paragraph_list = []\n        directly_return_chunk_list = [AIMessageChunk(content=paragraph.content)\n                                      for paragraph in paragraph_list if (\n                                              paragraph.hit_handling_method == 'directly_return' and paragraph.similarity >= paragraph.directly_return_similarity)]\n        if directly_return_chunk_list is not None and len(directly_return_chunk_list) > 0:\n            return directly_return_chunk_list[0], False\n        elif len(paragraph_list) == 0 and no_references_setting.get(\n                'status') == 'designated_answer':\n            return AIMessage(no_references_setting.get('value').replace('{question}', problem_text)), False\n        if chat_model is None:\n            return AIMessage(\n                _('Sorry, the AI model is not configured. Please go to the application to set up the AI model first.')), False\n        else:\n            # 过滤tool_id\n            all_tool_ids = list(set(\n                (mcp_tool_ids or []) +\n                (tool_ids or []) +\n                (skill_tool_ids or [])\n            ))\n            authorized_set = set(filter_authorized_ids('tool', all_tool_ids, workspace_id))\n\n            mcp_tool_ids = [i for i in (mcp_tool_ids or []) if i in authorized_set]\n            tool_ids = [i for i in (tool_ids or []) if i in authorized_set]\n            skill_tool_ids = [i for i in (skill_tool_ids or []) if i in authorized_set]\n            # 处理 MCP 请求\n            mcp_result = self._handle_mcp_request(\n                mcp_source, mcp_servers, mcp_tool_ids, tool_ids,\n                application_ids, skill_tool_ids, mcp_output_enable,\n                chat_model, message_list, application_id, chat_id\n            )\n            if mcp_result:\n                return mcp_result, True\n            return chat_model.invoke(message_list), True\n\n    def execute_block(self, message_list: List[BaseMessage],\n                      chat_id,\n                      problem_text,\n                      post_response_handler: PostResponseHandler,\n                      chat_model: BaseChatModel = None,\n                      paragraph_list=None,\n                      manage: PipelineManage = None,\n                      padding_problem_text: str = None,\n                      chat_user_id=None, chat_user_type=None, no_references_setting=None,\n                      model_setting=None,\n                      mcp_tool_ids=None,\n                      mcp_servers='',\n                      mcp_source=\"referencing\",\n                      tool_ids=None,\n                      application_ids=None,\n                      skill_tool_ids=None,\n                      workspace_id=None,\n                      mcp_output_enable=True):\n        reasoning_content_enable = model_setting.get('reasoning_content_enable', False)\n        reasoning_content_start = model_setting.get('reasoning_content_start', '<think>')\n        reasoning_content_end = model_setting.get('reasoning_content_end', '</think>')\n        reasoning = Reasoning(reasoning_content_start,\n                              reasoning_content_end)\n        chat_record_id = uuid.uuid7()\n        # 调用模型\n        try:\n            chat_result, is_ai_chat = self.get_block_result(message_list, chat_model, paragraph_list,\n                                                            no_references_setting, problem_text,\n                                                            mcp_tool_ids, mcp_servers, mcp_source,\n                                                            tool_ids, application_ids, skill_tool_ids,workspace_id,\n                                                            mcp_output_enable, manage.context.get('application_id'),\n                                                            chat_id)\n            if is_ai_chat:\n                request_token = chat_model.get_num_tokens_from_messages(message_list)\n                response_token = chat_model.get_num_tokens(chat_result.content)\n            else:\n                request_token = 0\n                response_token = 0\n            write_context(self, manage, request_token, response_token, chat_result.content)\n            reasoning_result = reasoning.get_reasoning_content(chat_result)\n            reasoning_result_end = reasoning.get_end_reasoning_content()\n            content = reasoning_result.get('content') + reasoning_result_end.get('content')\n            if 'reasoning_content' in chat_result.response_metadata:\n                reasoning_content = (chat_result.response_metadata.get('reasoning_content', '') or '')\n            else:\n                reasoning_content = (reasoning_result.get('reasoning_content') or \"\") + (reasoning_result_end.get(\n                    'reasoning_content') or \"\")\n            post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,\n                                          content, manage, self, padding_problem_text,\n                                          reasoning_content=reasoning_content)\n            if not manage.debug:\n                add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id'))\n            return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id),\n                                                                   content, True,\n                                                                   request_token, response_token,\n                                                                   {\n                                                                       'reasoning_content': reasoning_content if reasoning_content_enable else '',\n                                                                       'answer_list': [{\n                                                                           'content': content,\n                                                                           'reasoning_content': reasoning_content if reasoning_content_enable else ''\n                                                                       }]})\n        except Exception as e:\n            all_text = 'Exception:' + str(e)\n            write_context(self, manage, 0, 0, all_text)\n            post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text,\n                                          all_text, manage, self, padding_problem_text, reasoning_content='')\n            if not manage.debug:\n                add_access_num(chat_user_id, chat_user_type, manage.context.get('application_id'))\n            return manage.get_base_to_response().to_block_response(str(chat_id), str(chat_record_id), all_text, True, 0,\n                                                                   0, _status=status.HTTP_500_INTERNAL_SERVER_ERROR)\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/generate_human_message_step/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/1/9 18:23\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_generate_human_message_step.py\n    @date：2024/1/9 18:15\n    @desc: 生成对话模板\n\"\"\"\nfrom abc import abstractmethod\nfrom typing import Type, List\n\nfrom django.utils.translation import gettext_lazy as _\nfrom langchain_core.messages import BaseMessage\nfrom rest_framework import serializers\n\nfrom application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel\nfrom application.chat_pipeline.pipeline_manage import PipelineManage\nfrom application.models import ChatRecord\nfrom application.serializers.application import NoReferencesSetting\nfrom common.field.common import InstanceField\n\n\nclass IGenerateHumanMessageStep(IBaseChatPipelineStep):\n    class InstanceSerializer(serializers.Serializer):\n        # 问题\n        problem_text = serializers.CharField(required=True, label=_(\"question\"))\n        # 段落列表\n        paragraph_list = serializers.ListField(child=InstanceField(model_type=ParagraphPipelineModel, required=True),\n                                               label=_(\"Paragraph List\"))\n        # 历史对答\n        history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),\n                                                    label=_(\"History Questions\"))\n        # 多轮对话数量\n        dialogue_number = serializers.IntegerField(required=True, label=_(\"Number of multi-round conversations\"))\n        # 最大携带知识库段落长度\n        max_paragraph_char_number = serializers.IntegerField(required=True,\n                                                             label=_(\"Maximum length of the knowledge base paragraph\"))\n        # 模板\n        prompt = serializers.CharField(required=True, label=_(\"Prompt word\"))\n        system = serializers.CharField(required=False, allow_null=True, allow_blank=True,\n                                       label=_(\"System prompt words (role)\"))\n        # 补齐问题\n        padding_problem_text = serializers.CharField(required=False,\n                                                     label=_(\"Completion problem\"))\n        # 未查询到引用分段\n        no_references_setting = NoReferencesSetting(required=True,\n                                                    label=_(\"No reference segment settings\"))\n\n    def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:\n        return self.InstanceSerializer\n\n    def _run(self, manage: PipelineManage):\n        message_list = self.execute(**self.context['step_args'])\n        manage.context['message_list'] = message_list\n\n    @abstractmethod\n    def execute(self,\n                problem_text: str,\n                paragraph_list: List[ParagraphPipelineModel],\n                history_chat_record: List[ChatRecord],\n                dialogue_number: int,\n                max_paragraph_char_number: int,\n                prompt: str,\n                padding_problem_text: str = None,\n                no_references_setting=None,\n                system=None,\n                **kwargs) -> List[BaseMessage]:\n        \"\"\"\n\n        :param problem_text:               原始问题文本\n        :param paragraph_list:             段落列表\n        :param history_chat_record:        历史对话记录\n        :param dialogue_number:            多轮对话数量\n        :param max_paragraph_char_number:  最大段落长度\n        :param prompt:                     模板\n        :param padding_problem_text        用户修改文本\n        :param kwargs:                     其他参数\n        :param no_references_setting:     无引用分段设置\n        :param system                     系统提示称\n        :return:\n        \"\"\"\n        pass\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/generate_human_message_step/impl/base_generate_human_message_step.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_generate_human_message_step.py.py\n    @date：2024/1/10 17:50\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom langchain_core.messages import SystemMessage, BaseMessage, HumanMessage\n\nfrom application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel\nfrom application.chat_pipeline.step.generate_human_message_step.i_generate_human_message_step import \\\n    IGenerateHumanMessageStep\nfrom application.models import ChatRecord\nfrom common.utils.common import flat_map\n\n\nclass BaseGenerateHumanMessageStep(IGenerateHumanMessageStep):\n\n    def execute(self, problem_text: str,\n                paragraph_list: List[ParagraphPipelineModel],\n                history_chat_record: List[ChatRecord],\n                dialogue_number: int,\n                max_paragraph_char_number: int,\n                prompt: str,\n                padding_problem_text: str = None,\n                no_references_setting=None,\n                system=None,\n                **kwargs) -> List[BaseMessage]:\n        prompt = prompt if (paragraph_list is not None and len(paragraph_list) > 0) else no_references_setting.get(\n            'value')\n        exec_problem_text = padding_problem_text if padding_problem_text is not None else problem_text\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = [[history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()]\n                           for index in\n                           range(start_index if start_index > 0 else 0, len(history_chat_record))]\n        if system is not None and len(system) > 0:\n            return [SystemMessage(system), *flat_map(history_message),\n                    self.to_human_message(prompt, exec_problem_text, max_paragraph_char_number, paragraph_list,\n                                          no_references_setting)]\n\n        return [*flat_map(history_message),\n                self.to_human_message(prompt, exec_problem_text, max_paragraph_char_number, paragraph_list,\n                                      no_references_setting)]\n\n    @staticmethod\n    def to_human_message(prompt: str,\n                         problem: str,\n                         max_paragraph_char_number: int,\n                         paragraph_list: List[ParagraphPipelineModel],\n                         no_references_setting: Dict):\n        if paragraph_list is None or len(paragraph_list) == 0:\n            if no_references_setting.get('status') == 'ai_questioning':\n                return HumanMessage(\n                    content=no_references_setting.get('value').replace('{question}', problem))\n            else:\n                return HumanMessage(content=prompt.replace('{data}', \"\").replace('{question}', problem))\n        temp_data = \"\"\n        data_list = []\n        for p in paragraph_list:\n            content = f\"{p.title}:{p.content}\"\n            temp_data += content\n            if len(temp_data) > max_paragraph_char_number:\n                row_data = content[0:max_paragraph_char_number - len(temp_data)]\n                data_list.append(f\"<data>{row_data}</data>\")\n                break\n            else:\n                data_list.append(f\"<data>{content}</data>\")\n        data = \"\\n\".join(data_list)\n        return HumanMessage(content=prompt.replace('{data}', data).replace('{question}', problem))\n\n    def get_details(self, manage, **kwargs):\n        return {\n            'status': self.status,\n            'err_message': self.err_message,\n            'step_type': 'generate_human_message',\n        }\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/reset_problem_step/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/1/9 18:23\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_reset_problem_step.py\n    @date：2024/1/9 18:12\n    @desc: 重写处理问题\n\"\"\"\nfrom abc import abstractmethod\nfrom typing import Type, List\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep\nfrom application.chat_pipeline.pipeline_manage import PipelineManage\nfrom application.models import ChatRecord\nfrom common.field.common import InstanceField\n\n\nclass IResetProblemStep(IBaseChatPipelineStep):\n    class InstanceSerializer(serializers.Serializer):\n        # 问题文本\n        problem_text = serializers.CharField(required=True, label=_(\"question\"))\n        # 历史对答\n        history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),\n                                                    label=_(\"History Questions\"))\n        # 大语言模型\n        model_id = serializers.UUIDField(required=False, allow_null=True, label=_(\"Model id\"))\n        workspace_id = serializers.CharField(required=True, label=_(\"User ID\"))\n        problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,\n                                                            label=_(\"Question completion prompt\"))\n\n    def get_step_serializer(self, manage: PipelineManage) -> Type[serializers.Serializer]:\n        return self.InstanceSerializer\n\n    def _run(self, manage: PipelineManage):\n        padding_problem = self.execute(**self.context.get('step_args'))\n        # 用户输入问题\n        source_problem_text = self.context.get('step_args').get('problem_text')\n        self.context['problem_text'] = source_problem_text\n        self.context['padding_problem_text'] = padding_problem\n        manage.context['problem_text'] = source_problem_text\n        manage.context['padding_problem_text'] = padding_problem\n        # 累加tokens\n        manage.context['message_tokens'] = manage.context.get('message_tokens', 0) + self.context.get('message_tokens',\n                                                                                                      0)\n        manage.context['answer_tokens'] = manage.context.get('answer_tokens', 0) + self.context.get('answer_tokens', 0)\n\n    @abstractmethod\n    def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None,\n                problem_optimization_prompt=None,\n                workspace_id=None,\n                **kwargs):\n        pass\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_reset_problem_step.py\n    @date：2024/1/10 14:35\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom django.utils.translation import gettext as _\nfrom langchain_core.messages import HumanMessage\n\nfrom application.chat_pipeline.step.reset_problem_step.i_reset_problem_step import IResetProblemStep\nfrom application.models import ChatRecord\nfrom common.utils.split_model import flat_map\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\n\nprompt = _(\n    \"() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the <data></data> tag\")\n\n\nclass BaseResetProblemStep(IResetProblemStep):\n    def execute(self, problem_text: str, history_chat_record: List[ChatRecord] = None, model_id: str = None,\n                problem_optimization_prompt=None,\n                workspace_id=None,\n                **kwargs) -> str:\n        chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id) if model_id is not None else None\n        if chat_model is None:\n            return problem_text\n        start_index = len(history_chat_record) - 3\n        history_message = [[history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()]\n                           for index in\n                           range(start_index if start_index > 0 else 0, len(history_chat_record))]\n        reset_prompt = problem_optimization_prompt if problem_optimization_prompt else prompt\n        message_list = [*flat_map(history_message),\n                        HumanMessage(content=reset_prompt.replace('{question}', problem_text))]\n        response = chat_model.invoke(message_list)\n        padding_problem = problem_text\n        if response.content.__contains__(\"<data>\") and response.content.__contains__('</data>'):\n            padding_problem_data = response.content[\n                                   response.content.index('<data>') + 6:response.content.index('</data>')]\n            if padding_problem_data is not None and len(padding_problem_data.strip()) > 0:\n                padding_problem = padding_problem_data\n        elif len(response.content) > 0:\n            padding_problem = response.content\n\n        try:\n            request_token = chat_model.get_num_tokens_from_messages(message_list)\n            response_token = chat_model.get_num_tokens(padding_problem)\n        except Exception as e:\n            request_token = 0\n            response_token = 0\n        self.context['message_tokens'] = request_token\n        self.context['answer_tokens'] = response_token\n        return padding_problem\n\n    def get_details(self, manage, **kwargs):\n        return {'status': self.status,\n                'err_message': self.err_message,\n                'step_type': 'problem_padding',\n                'run_time': self.context['run_time'],\n                'model_id': str(manage.context['model_id']) if 'model_id' in manage.context else None,\n                'message_tokens': self.context.get('message_tokens', 0),\n                'answer_tokens': self.context.get('answer_tokens', 0),\n                'cost': 0,\n                'padding_problem_text': self.context.get('padding_problem_text'),\n                'problem_text': self.context.get(\"step_args\").get('problem_text'),\n                }\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/search_dataset_step/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/1/9 18:24\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_search_dataset_step.py\n    @date：2024/1/9 18:10\n    @desc: 检索知识库\n\"\"\"\nimport re\nfrom abc import abstractmethod\nfrom typing import List, Type\n\nfrom django.core import validators\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.chat_pipeline.I_base_chat_pipeline import IBaseChatPipelineStep, ParagraphPipelineModel\nfrom application.chat_pipeline.pipeline_manage import PipelineManage\n\n\nclass ISearchDatasetStep(IBaseChatPipelineStep):\n    class InstanceSerializer(serializers.Serializer):\n        # 原始问题文本\n        problem_text = serializers.CharField(required=True, label=_(\"question\"))\n        # 系统补全问题文本\n        padding_problem_text = serializers.CharField(required=False,\n                                                     label=_(\"System completes question text\"))\n        # 需要查询的数据集id列表\n        knowledge_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),\n                                                  label=_(\"Dataset id list\"))\n        # 需要排除的文档id\n        exclude_document_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),\n                                                         label=_(\"List of document ids to exclude\"))\n        # 需要排除向量id\n        exclude_paragraph_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),\n                                                          label=_(\"List of exclusion vector ids\"))\n        # 需要查询的条数\n        top_n = serializers.IntegerField(required=True,\n                                         label=_(\"Reference segment number\"))\n        # 相似度 0-1之间\n        similarity = serializers.FloatField(required=True, max_value=1, min_value=0,\n                                            label=_(\"Similarity\"))\n        search_mode = serializers.CharField(required=True, validators=[\n            validators.RegexValidator(regex=re.compile(\"^embedding|keywords|blend$\"),\n                                      message=_(\"The type only supports embedding|keywords|blend\"), code=500)\n        ], label=_(\"Retrieval Mode\"))\n        workspace_id = serializers.CharField(required=True, label=_(\"Workspace ID\"))\n\n    def get_step_serializer(self, manage: PipelineManage) -> Type[InstanceSerializer]:\n        return self.InstanceSerializer\n\n    def _run(self, manage: PipelineManage):\n        paragraph_list = self.execute(**self.context['step_args'], manage=manage)\n        manage.context['paragraph_list'] = paragraph_list\n        self.context['paragraph_list'] = paragraph_list\n\n    @abstractmethod\n    def execute(self, problem_text: str, knowledge_id_list: list[str], exclude_document_id_list: list[str],\n                exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None,\n                search_mode: str = None,\n                workspace_id=None,\n                manage: PipelineManage = None,\n                **kwargs) -> List[ParagraphPipelineModel]:\n        \"\"\"\n        关于 用户和补全问题 说明: 补全问题如果有就使用补全问题去查询 反之就用用户原始问题查询\n        :param similarity:                         相关性\n        :param top_n:                              查询多少条\n        :param problem_text:                       用户问题\n        :param knowledge_id_list:                  需要查询的数据集id列表\n        :param exclude_document_id_list:           需要排除的文档id\n        :param exclude_paragraph_id_list:          需要排除段落id\n        :param padding_problem_text                补全问题\n        :param search_mode                         检索模式\n        :param workspace_id                        工作空间id\n        :return: 段落列表\n        \"\"\"\n        pass\n"
  },
  {
    "path": "apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_search_dataset_step.py\n    @date：2024/1/10 10:33\n    @desc:\n\"\"\"\nimport os\nfrom typing import List, Dict\n\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework.utils.formatting import lazy_format\n\nfrom application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineModel\nfrom application.chat_pipeline.step.search_dataset_step.i_search_dataset_step import ISearchDatasetStep\nfrom common.config.embedding_config import VectorStore, ModelManage\nfrom common.constants.permission_constants import RoleConstants\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.db.search import native_search\nfrom common.utils.common import get_file_content\nfrom knowledge.models import Paragraph, Knowledge\nfrom knowledge.models import SearchMode\nfrom maxkb.conf import PROJECT_DIR\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model, get_model_by_id, get_model_default_params\n\n\ndef reset_meta(meta):\n    if not meta.get('allow_download', False):\n        return {'allow_download': False}\n    return meta\n\n\ndef get_embedding_id(knowledge_id_list):\n    knowledge_list = QuerySet(Knowledge).filter(id__in=knowledge_id_list)\n    if len(set([knowledge.embedding_model_id for knowledge in knowledge_list])) > 1:\n        raise Exception(\n            _(\"The vector model of the associated knowledge base is inconsistent and the segmentation cannot be recalled.\"))\n    if len(knowledge_list) == 0:\n        raise Exception(_(\"The knowledge base setting is wrong, please reset the knowledge base\"))\n    return knowledge_list[0].embedding_model_id\n\n\nclass BaseSearchDatasetStep(ISearchDatasetStep):\n\n    def execute(self, problem_text: str, knowledge_id_list: list[str], exclude_document_id_list: list[str],\n                exclude_paragraph_id_list: list[str], top_n: int, similarity: float, padding_problem_text: str = None,\n                search_mode: str = None,\n                workspace_id=None,\n                manage=None,\n                **kwargs) -> List[ParagraphPipelineModel]:\n        get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized')\n        chat_user_type = manage.context.get('chat_user_type')\n        if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type:\n            knowledge_id_list = get_knowledge_list_of_authorized(manage.context.get('chat_user_id'),\n                                                                 knowledge_id_list)\n        if len(knowledge_id_list) == 0:\n            return []\n        exec_problem_text = padding_problem_text if padding_problem_text is not None else problem_text\n        model_id = get_embedding_id(knowledge_id_list)\n        model = get_model_by_id(model_id, workspace_id)\n        if model.model_type != \"EMBEDDING\":\n            raise Exception(_(\"Model does not exist\"))\n        self.context['model_name'] = model.name\n        default_params = get_model_default_params(model)\n        embedding_model = ModelManage.get_model(model_id, lambda _id: get_model(model, **{**default_params}))\n        embedding_value = embedding_model.embed_query(exec_problem_text)\n        vector = VectorStore.get_embedding_vector()\n        embedding_list = vector.query(exec_problem_text, embedding_value, knowledge_id_list, None,\n                                      exclude_document_id_list,\n                                      exclude_paragraph_id_list, True, top_n, similarity, SearchMode(search_mode))\n        if embedding_list is None:\n            return []\n        paragraph_list = self.list_paragraph(embedding_list, vector)\n        result = [self.reset_paragraph(paragraph, embedding_list) for paragraph in paragraph_list]\n        return result\n\n    @staticmethod\n    def reset_paragraph(paragraph: Dict, embedding_list: List) -> ParagraphPipelineModel:\n        filter_embedding_list = [embedding for embedding in embedding_list if\n                                 str(embedding.get('paragraph_id')) == str(paragraph.get('id'))]\n        if filter_embedding_list is not None and len(filter_embedding_list) > 0:\n            find_embedding = filter_embedding_list[-1]\n            return (ParagraphPipelineModel.builder()\n                    .add_paragraph(paragraph)\n                    .add_similarity(find_embedding.get('similarity'))\n                    .add_comprehensive_score(find_embedding.get('comprehensive_score'))\n                    .add_knowledge_name(paragraph.get('knowledge_name'))\n                    .add_knowledge_type(paragraph.get('knowledge_type'))\n                    .add_document_name(paragraph.get('document_name'))\n                    .add_hit_handling_method(paragraph.get('hit_handling_method'))\n                    .add_directly_return_similarity(paragraph.get('directly_return_similarity'))\n                    .add_meta(reset_meta(paragraph.get('meta')))\n                    .build())\n\n    @staticmethod\n    def get_similarity(paragraph, embedding_list: List):\n        filter_embedding_list = [embedding for embedding in embedding_list if\n                                 str(embedding.get('paragraph_id')) == str(paragraph.get('id'))]\n        if filter_embedding_list is not None and len(filter_embedding_list) > 0:\n            find_embedding = filter_embedding_list[-1]\n            return find_embedding.get('comprehensive_score')\n        return 0\n\n    @staticmethod\n    def list_paragraph(embedding_list: List, vector):\n        paragraph_id_list = [row.get('paragraph_id') for row in embedding_list]\n        if paragraph_id_list is None or len(paragraph_id_list) == 0:\n            return []\n        paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list),\n                                       get_file_content(\n                                           os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql',\n                                                        'list_knowledge_paragraph_by_paragraph_id.sql')),\n                                       with_table_name=True)\n        # 如果向量库中存在脏数据 直接删除\n        if len(paragraph_list) != len(paragraph_id_list):\n            exist_paragraph_list = [row.get('id') for row in paragraph_list]\n            for paragraph_id in paragraph_id_list:\n                if not exist_paragraph_list.__contains__(paragraph_id):\n                    vector.delete_by_paragraph_id(paragraph_id)\n        # 如果存在直接返回的则取直接返回段落\n        hit_handling_method_paragraph = [paragraph for paragraph in paragraph_list if\n                                         (paragraph.get(\n                                             'hit_handling_method') == 'directly_return' and BaseSearchDatasetStep.get_similarity(\n                                             paragraph, embedding_list) >= paragraph.get(\n                                             'directly_return_similarity'))]\n        if len(hit_handling_method_paragraph) > 0:\n            # 找到评分最高的\n            return [sorted(hit_handling_method_paragraph,\n                           key=lambda p: BaseSearchDatasetStep.get_similarity(p, embedding_list))[-1]]\n        return paragraph_list\n\n    def get_details(self, manage, **kwargs):\n        step_args = self.context.get('step_args') or {}\n\n        return {\n            'status': self.status,\n            'err_message': self.err_message,\n            'step_type': 'search_step',\n            'paragraph_list': [row.to_dict() for row in (self.context.get('paragraph_list') or [])],\n            'run_time': self.context.get('run_time') or 0,\n            'problem_text': step_args.get(\n                'padding_problem_text') if 'padding_problem_text' in step_args else step_args.get('problem_text'),\n            'model_name': self.context.get('model_name'),\n            'message_tokens': 0,\n            'answer_tokens': 0,\n            'cost': 0\n        }\n"
  },
  {
    "path": "apps/application/flow/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/6/7 14:43\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/flow/backend/__init__.py",
    "content": ""
  },
  {
    "path": "apps/application/flow/backend/sandbox_shell.py",
    "content": "import getpass\nimport os\nimport re\n\nfrom deepagents.backends import LocalShellBackend\nfrom deepagents.backends.protocol import ExecuteResponse\n\nfrom maxkb.const import CONFIG\n\n_enable_sandbox = bool(int(CONFIG.get('SANDBOX', 0)))\n_run_user = 'sandbox' if _enable_sandbox else getpass.getuser()\n_sandbox_python_sys_path = CONFIG.get_sandbox_python_package_paths().replace(',', ':')\n\n\nclass SandboxShellBackend(LocalShellBackend):\n    def __init__(self, root_dir: str, **kwargs):\n        if 'env' not in kwargs and not kwargs.get('inherit_env', False):\n            env = os.environ.copy()\n            path = env.get('PATH', '/usr/bin:/bin')\n\n            # 将 sandbox 路径分解为列表，检查每个路径是否已存在\n            existing_paths = set(path.split(os.pathsep))\n            sandbox_paths = _sandbox_python_sys_path.split(os.pathsep) if _sandbox_python_sys_path else []\n            new_paths = [p for p in sandbox_paths if p and p not in existing_paths]\n\n            if new_paths:\n                env['PATH'] = f\"{os.pathsep.join(new_paths)}{os.pathsep}{path}\"\n\n            kwargs['env'] = env\n        super().__init__(root_dir=root_dir, **kwargs)\n\n    def _translate_virtual_paths(self, command: str) -> str:\n        \"\"\"Translate virtual absolute paths in the command to real filesystem paths.\n\n        In virtual_mode=True, file tools (ls, glob, read_file) return virtual absolute\n        paths like /skills/foo.py which map to {root_dir}/skills/foo.py.  But execute()\n        runs a real shell where /skills/foo.py does not exist.  This method replaces\n        any path token that exists under root_dir with its real path, while leaving\n        genuine system paths (e.g. /usr/bin/python3) untouched.\n        \"\"\"\n        root = str(self.cwd)\n\n        def translate(m: re.Match) -> str:\n            virtual_path = m.group(0)\n            real_path = root + virtual_path\n            return real_path if os.path.lexists(real_path) else virtual_path\n\n        # Match absolute-path-like tokens: / followed by a non-whitespace sequence\n        # that isn't clearly a flag (e.g. avoid matching -/something).\n        # Only translate when virtual_mode is active.\n        return re.sub(r'(?<![.\\w\\-])/[A-Za-z_][^\\s\\'\"\\\\;|&><:,]*', translate, command)\n\n    def execute(\n            self,\n            command: str,\n            *,\n            timeout: int | None = None,\n    ) -> ExecuteResponse:\n        if self.virtual_mode:\n            command = self._translate_virtual_paths(command)\n\n        if _enable_sandbox:\n            # 用 runuser 在子进程里切换用户，父进程凭据保持不变，\n            # 避免父进程 ruid/euid 不一致导致 execve 报 Permission denied\n            command = f\"runuser -u {_run_user} -- env -i LD_PRELOAD=/opt/maxkb-app/sandbox/lib/sandbox.so PATH=${{PATH}} {command}\"\n            # command = f\"runuser -u {_run_user} -- env -i PATH=${{PATH}} {command}\"\n\n        # print(f\"Executing command in sandbox: {command}\")\n        return super().execute(command=command, timeout=timeout)\n"
  },
  {
    "path": "apps/application/flow/common.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： common.py\n    @date：2024/12/11 17:57\n    @desc:\n\"\"\"\nfrom enum import Enum\nfrom typing import List, Dict\n\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext as _\nfrom rest_framework.exceptions import ErrorDetail, ValidationError\n\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import group_by\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model_credential\nfrom tools.models.tool import Tool\n\nend_nodes = ['ai-chat-node', 'reply-node', 'function-node', 'function-lib-node', 'application-node',\n             'image-understand-node', 'speech-to-text-node', 'text-to-speech-node', 'image-generate-node',\n             'variable-assign-node']\n\n\nclass Answer:\n    def __init__(self, content, view_type, runtime_node_id, chat_record_id, child_node, real_node_id,\n                 reasoning_content):\n        self.view_type = view_type\n        self.content = content\n        self.reasoning_content = reasoning_content\n        self.runtime_node_id = runtime_node_id\n        self.chat_record_id = chat_record_id\n        self.child_node = child_node\n        self.real_node_id = real_node_id\n\n    def to_dict(self):\n        return {'view_type': self.view_type, 'content': self.content, 'runtime_node_id': self.runtime_node_id,\n                'chat_record_id': self.chat_record_id,\n                'child_node': self.child_node,\n                'reasoning_content': self.reasoning_content,\n                'real_node_id': self.real_node_id}\n\n\nclass NodeChunk:\n    def __init__(self):\n        self.status = 0\n        self.chunk_list = []\n\n    def add_chunk(self, chunk):\n        self.chunk_list.append(chunk)\n\n    def end(self, chunk=None):\n        if chunk is not None:\n            self.add_chunk(chunk)\n        self.status = 200\n\n    def is_end(self):\n        return self.status == 200\n\n\nclass Edge:\n    def __init__(self, _id: str, _type: str, sourceNodeId: str, targetNodeId: str, **keywords):\n        self.id = _id\n        self.type = _type\n        self.sourceNodeId = sourceNodeId\n        self.targetNodeId = targetNodeId\n        for keyword in keywords:\n            self.__setattr__(keyword, keywords.get(keyword))\n\n\nclass Node:\n    def __init__(self, _id: str, _type: str, x: int, y: int, properties: dict, **kwargs):\n        self.id = _id\n        self.type = _type\n        self.x = x\n        self.y = y\n        self.properties = properties\n        for keyword in kwargs:\n            self.__setattr__(keyword, kwargs.get(keyword))\n\n\nclass EdgeNode:\n    edge: Edge\n    node: Node\n\n    def __init__(self, edge, node):\n        self.edge = edge\n        self.node = node\n\n\nclass WorkflowMode(Enum):\n    APPLICATION = \"application\"\n\n    APPLICATION_LOOP = \"application-loop\"\n\n    KNOWLEDGE = \"knowledge\"\n\n    KNOWLEDGE_LOOP = \"knowledge-loop\"\n\n    TOOL = \"tool\"\n\n    TOOL_LOOP = \"tool-loop\"\n\n\nclass Workflow:\n    \"\"\"\n    节点列表\n    \"\"\"\n    nodes: List[Node]\n    \"\"\"\n    线列表\n    \"\"\"\n    edges: List[Edge]\n    \"\"\"\n    节点id:node\n    \"\"\"\n    node_map: Dict[str, Node]\n    \"\"\"\n    节点id:当前节点id上面的所有节点\n    \"\"\"\n    up_node_map: Dict[str, List[EdgeNode]]\n    \"\"\"\n     节点id:当前节点id下面的所有节点\n    \"\"\"\n    next_node_map: Dict[str, List[EdgeNode]]\n\n    workflow_mode: WorkflowMode\n\n    def __init__(self, nodes: List[Node], edges: List[Edge],\n                 workflow_mode: WorkflowMode = WorkflowMode.APPLICATION.value):\n        self.nodes = nodes\n        self.edges = edges\n        self.node_map = {node.id: node for node in nodes}\n\n        self.up_node_map = {key: [EdgeNode(edge, self.node_map.get(edge.sourceNodeId)) for\n                                  edge in edges] for\n                            key, edges in\n                            group_by(edges, key=lambda edge: edge.targetNodeId).items()}\n\n        self.next_node_map = {key: [EdgeNode(edge, self.node_map.get(edge.targetNodeId)) for edge in edges] for\n                              key, edges in\n                              group_by(edges, key=lambda edge: edge.sourceNodeId).items()}\n        self.workflow_mode = workflow_mode\n\n    def get_node(self, node_id):\n        \"\"\"\n        根据node_id 获取节点信息\n        @param node_id: node_id\n        @return: 节点信息\n        \"\"\"\n        return self.node_map.get(node_id)\n\n    def get_up_edge_nodes(self, node_id) -> List[EdgeNode]:\n        \"\"\"\n        根据节点id 获取当前连接前置节点和连线\n        @param node_id: 节点id\n        @return: 节点连线列表\n        \"\"\"\n        return self.up_node_map.get(node_id)\n\n    def get_next_edge_nodes(self, node_id) -> List[EdgeNode]:\n        \"\"\"\n        根据节点id 获取当前连接目标节点和连线\n        @param node_id: 节点id\n        @return: 节点连线列表\n        \"\"\"\n        return self.next_node_map.get(node_id)\n\n    def get_up_nodes(self, node_id) -> List[Node]:\n        \"\"\"\n        根据节点id 获取当前连接前置节点\n        @param node_id: 节点id\n        @return: 节点列表\n        \"\"\"\n        return [en.node for en in (self.up_node_map.get(node_id) or [])]\n\n    def get_next_nodes(self, node_id) -> List[Node]:\n        \"\"\"\n        根据节点id 获取当前连接目标节点\n        @param node_id: 节点id\n        @return: 节点列表\n        \"\"\"\n        return [en.node for en in self.next_node_map.get(node_id, [])]\n\n    @staticmethod\n    def new_instance(flow_obj: Dict, workflow_mode: WorkflowMode = WorkflowMode.APPLICATION):\n        nodes = flow_obj.get('nodes')\n        edges = flow_obj.get('edges')\n        nodes = [Node(node.get('id'), node.get('type'), **node)\n                 for node in nodes]\n        edges = [Edge(edge.get('id'), edge.get('type'), **edge) for edge in edges]\n        return Workflow(nodes, edges, workflow_mode)\n\n    def get_start_node(self):\n        return self.get_node('start-node')\n\n    def get_search_node(self):\n        return [node for node in self.nodes if node.type == 'search-dataset-node']\n\n    def is_valid(self):\n        \"\"\"\n        校验工作流数据\n        \"\"\"\n        self.is_valid_model_params()\n        self.is_valid_start_node()\n        self.is_valid_base_node()\n        self.is_valid_work_flow()\n\n    def is_valid_node_params(self, node: Node):\n        from application.flow.step_node import get_node\n        get_node(node.type, self.workflow_mode)(node, None, None)\n\n    def is_valid_node(self, node: Node):\n        self.is_valid_node_params(node)\n        if node.type == 'condition-node':\n            branch_list = node.properties.get('node_data').get('branch')\n            for branch in branch_list:\n                source_anchor_id = f\"{node.id}_{branch.get('id')}_right\"\n                edge_list = [edge for edge in self.edges if edge.sourceAnchorId == source_anchor_id]\n                if len(edge_list) == 0:\n                    raise AppApiException(500,\n                                          _('The branch {branch} of the {node} node needs to be connected').format(\n                                              node=node.properties.get(\"stepName\"), branch=branch.get(\"type\")))\n\n        else:\n            edge_list = [edge for edge in self.edges if edge.sourceNodeId == node.id]\n            if len(edge_list) == 0 and not end_nodes.__contains__(node.type):\n                raise AppApiException(500, _(\"{node} Nodes cannot be considered as end nodes\").format(\n                    node=node.properties.get(\"stepName\")))\n\n    def is_valid_work_flow(self, up_node=None):\n        if up_node is None:\n            up_node = self.get_start_node()\n        self.is_valid_node(up_node)\n        next_nodes = self.get_next_nodes(up_node)\n        for next_node in next_nodes:\n            self.is_valid_work_flow(next_node)\n\n    def is_valid_start_node(self):\n        start_node_list = [node for node in self.nodes if node.id == 'start-node']\n        if len(start_node_list) == 0:\n            raise AppApiException(500, _('The starting node is required'))\n        if len(start_node_list) > 1:\n            raise AppApiException(500, _('There can only be one starting node'))\n\n    def is_valid_model_params(self):\n        node_list = [node for node in self.nodes if (\n                node.type == 'ai-chat-node' or node.type == 'question-node' or node.type == 'parameter-extraction-node')]\n        for node in node_list:\n            model = QuerySet(Model).filter(id=node.properties.get('node_data', {}).get('model_id')).first()\n            if model is None:\n                raise ValidationError(ErrorDetail(\n                    _('The node {node} model does not exist').format(node=node.properties.get(\"stepName\"))))\n            credential = get_model_credential(model.provider, model.model_type, model.model_name)\n            model_params_setting = node.properties.get('node_data', {}).get('model_params_setting')\n            model_params_setting_form = credential.get_model_params_setting_form(\n                model.model_name)\n            if model_params_setting is None:\n                model_params_setting = model_params_setting_form.get_default_form_data()\n                node.properties.get('node_data', {})['model_params_setting'] = model_params_setting\n            if node.properties.get('status', 200) != 200:\n                raise ValidationError(\n                    ErrorDetail(_(\"Node {node} is unavailable\").format(node.properties.get(\"stepName\"))))\n        node_list = [node for node in self.nodes if (node.type == 'function-lib-node')]\n        for node in node_list:\n            function_lib_id = node.properties.get('node_data', {}).get('function_lib_id')\n            if function_lib_id is None:\n                raise ValidationError(ErrorDetail(\n                    _('The library ID of node {node} cannot be empty').format(node=node.properties.get(\"stepName\"))))\n            f_lib = QuerySet(Tool).filter(id=function_lib_id).first()\n            if f_lib is None:\n                raise ValidationError(ErrorDetail(_(\"The function library for node {node} is not available\").format(\n                    node=node.properties.get(\"stepName\"))))\n\n    def is_valid_base_node(self):\n        base_node_list = [node for node in self.nodes if node.id == 'base-node']\n        if len(base_node_list) == 0:\n            raise AppApiException(500, _('Basic information node is required'))\n        if len(base_node_list) > 1:\n            raise AppApiException(500, _('There can only be one basic information node'))\n"
  },
  {
    "path": "apps/application/flow/compare/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/6/7 14:43\n    @desc:\n\"\"\"\n\nfrom .contain_compare import *\nfrom .end_with import EndWithCompare\nfrom .equal_compare import *\nfrom .ge_compare import *\nfrom .gt_compare import *\nfrom .is_not_null_compare import *\nfrom .is_not_true import IsNotTrueCompare\nfrom .is_null_compare import *\nfrom .is_true import IsTrueCompare\nfrom .le_compare import *\nfrom .len_equal_compare import *\nfrom .len_ge_compare import *\nfrom .len_gt_compare import *\nfrom .len_le_compare import *\nfrom .len_lt_compare import *\nfrom .lt_compare import *\nfrom .not_contain_compare import *\nfrom .not_equal_compare import *\nfrom .start_with import StartWithCompare\n\ncompare_handle_list = [GECompare(), GTCompare(), ContainCompare(), EqualCompare(), LTCompare(), LECompare(),\n                       LenLECompare(), LenGECompare(), LenEqualCompare(), LenGTCompare(), LenLTCompare(),\n                       IsNullCompare(),\n                       IsNotNullCompare(), NotContainCompare(), NotEqualCompare(), IsTrueCompare(), IsNotTrueCompare(), StartWithCompare(),\n                       EndWithCompare()]\n"
  },
  {
    "path": "apps/application/flow/compare/compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： compare.py\n    @date：2024/6/7 14:37\n    @desc:\n\"\"\"\nfrom abc import abstractmethod\nfrom typing import List\n\n\nclass Compare:\n    @abstractmethod\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        pass\n\n    @abstractmethod\n    def compare(self, source_value, compare, target_value):\n        pass\n"
  },
  {
    "path": "apps/application/flow/compare/contain_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： contain_compare.py\n    @date：2024/6/11 10:02\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare.compare import Compare\n\n\nclass ContainCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'contain':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        if isinstance(source_value, str):\n            return str(target_value) in source_value\n        elif isinstance(source_value, list):\n            return any([str(item) == str(target_value) for item in source_value])\n        else:\n            return str(target_value) in str(source_value)\n"
  },
  {
    "path": "apps/application/flow/compare/end_with.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： start_with.py\n    @date：2025/10/20 10:37\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass EndWithCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'end_with':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        source_value = str(source_value)\n        return source_value.endswith(str(target_value))\n"
  },
  {
    "path": "apps/application/flow/compare/equal_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： equal_compare.py\n    @date：2024/6/7 14:44\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass EqualCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'eq':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        return str(source_value) == str(target_value)\n"
  },
  {
    "path": "apps/application/flow/compare/ge_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： lt_compare.py\n    @date：2024/6/11 9:52\n    @desc: 大于比较器\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass GECompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'ge':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return float(source_value) >= float(target_value)\n        except Exception as e:\n            try:\n                return str(source_value) >= str(target_value)\n            except Exception as _:\n                pass\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/gt_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： lt_compare.py\n    @date：2024/6/11 9:52\n    @desc: 大于比较器\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass GTCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'gt':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return float(source_value) > float(target_value)\n        except Exception as e:\n            try:\n                return str(source_value) > str(target_value)\n            except Exception as _:\n                pass\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/is_not_null_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： is_not_null_compare.py\n    @date：2024/6/28 10:45\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass IsNotNullCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'is_not_null':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        return source_value is not None and len(source_value) > 0\n"
  },
  {
    "path": "apps/application/flow/compare/is_not_true.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： is_not_true.py\n    @date：2025/4/7 13:44\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass IsNotTrueCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'is_not_true':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return source_value is False\n        except Exception as e:\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/is_null_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： is_null_compare.py\n    @date：2024/6/28 10:45\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass IsNullCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'is_null':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return source_value is None or len(source_value) == 0\n        except Exception as e:\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/is_true.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： IsTrue.py\n    @date：2025/4/7 13:38\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass IsTrueCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'is_true':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return source_value is True\n        except Exception as e:\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/le_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： lt_compare.py\n    @date：2024/6/11 9:52\n    @desc: 小于比较器\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass LECompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'le':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return float(source_value) <= float(target_value)\n        except Exception as e:\n            try:\n                return str(source_value) <= str(target_value)\n            except Exception as _:\n                pass\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/len_equal_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： equal_compare.py\n    @date：2024/6/7 14:44\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass LenEqualCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'len_eq':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return len(source_value) == int(target_value)\n        except Exception as e:\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/len_ge_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： lt_compare.py\n    @date：2024/6/11 9:52\n    @desc: 大于比较器\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass LenGECompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'len_ge':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return len(source_value) >= int(target_value)\n        except Exception as e:\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/len_gt_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： lt_compare.py\n    @date：2024/6/11 9:52\n    @desc: 大于比较器\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass LenGTCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'len_gt':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return len(source_value) > int(target_value)\n        except Exception as e:\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/len_le_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： lt_compare.py\n    @date：2024/6/11 9:52\n    @desc: 小于比较器\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass LenLECompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'len_le':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return len(source_value) <= int(target_value)\n        except Exception as e:\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/len_lt_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： lt_compare.py\n    @date：2024/6/11 9:52\n    @desc: 小于比较器\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass LenLTCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'len_lt':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return len(source_value) < int(target_value)\n        except Exception as e:\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/lt_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： lt_compare.py\n    @date：2024/6/11 9:52\n    @desc: 小于比较器\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass LTCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'lt':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        try:\n            return float(source_value) < float(target_value)\n        except Exception as e:\n            try:\n                return str(source_value) < str(target_value)\n            except Exception as _:\n                pass\n            return False\n"
  },
  {
    "path": "apps/application/flow/compare/not_contain_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： contain_compare.py\n    @date：2024/6/11 10:02\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass NotContainCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'not_contain':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        if isinstance(source_value, str):\n            return str(target_value) not in source_value\n        elif isinstance(self, list):\n            return not any([str(item) == str(target_value) for item in source_value])\n        else:\n            return str(target_value) not in str(source_value)\n"
  },
  {
    "path": "apps/application/flow/compare/not_equal_compare.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：wangliang181230\n    @file： not_equal_compare.py\n    @date：2026/3/17 9:41\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass NotEqualCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'not_eq':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        return str(source_value) != str(target_value)\n"
  },
  {
    "path": "apps/application/flow/compare/start_with.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： start_with.py\n    @date：2025/10/20 10:37\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import Compare\n\n\nclass StartWithCompare(Compare):\n\n    def support(self, node_id, fields: List[str], source_value, compare, target_value):\n        if compare == 'start_with':\n            return True\n\n    def compare(self, source_value, compare, target_value):\n        source_value = str(source_value)\n        return source_value.startswith(str(target_value))\n"
  },
  {
    "path": "apps/application/flow/default_workflow.json",
    "content": "{\n    \"nodes\": [\n        {\n            \"id\": \"base-node\",\n            \"type\": \"base-node\",\n            \"x\": 360,\n            \"y\": 2810,\n            \"properties\": {\n                \"config\": {\n\n                },\n                \"height\": 825.6,\n                \"stepName\": \"基本信息\",\n                \"node_data\": {\n                    \"desc\": \"\",\n                    \"name\": \"maxkbapplication\",\n                    \"prologue\": \"您好，我是 MaxKB 小助手，您可以向我提出 MaxKB 使用问题。\\n- MaxKB 主要功能有什么？\\n- MaxKB 支持哪些大语言模型？\\n- MaxKB 支持哪些文档类型？\"\n                },\n                \"input_field_list\": [\n\n                ]\n            }\n        },\n        {\n            \"id\": \"start-node\",\n            \"type\": \"start-node\",\n            \"x\": 430,\n            \"y\": 3660,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"用户问题\",\n                            \"value\": \"question\"\n                        }\n                    ],\n                    \"globalFields\": [\n                        {\n                            \"label\": \"当前时间\",\n                            \"value\": \"time\"\n                        }\n                    ]\n                },\n                \"fields\": [\n                    {\n                        \"label\": \"用户问题\",\n                        \"value\": \"question\"\n                    }\n                ],\n                \"height\": 276,\n                \"stepName\": \"开始\",\n                \"globalFields\": [\n                    {\n                        \"label\": \"当前时间\",\n                        \"value\": \"time\"\n                    }\n                ]\n            }\n        },\n        {\n            \"id\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"type\": \"search-dataset-node\",\n            \"x\": 840,\n            \"y\": 3210,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"检索结果的分段列表\",\n                            \"value\": \"paragraph_list\"\n                        },\n                        {\n                            \"label\": \"满足直接回答的分段列表\",\n                            \"value\": \"is_hit_handling_method_list\"\n                        },\n                        {\n                            \"label\": \"检索结果\",\n                            \"value\": \"data\"\n                        },\n                        {\n                            \"label\": \"满足直接回答的分段内容\",\n                            \"value\": \"directly_return\"\n                        }\n                    ]\n                },\n                \"height\": 794,\n                \"stepName\": \"知识库检索\",\n                \"node_data\": {\n                    \"dataset_id_list\": [\n\n                    ],\n                    \"dataset_setting\": {\n                        \"top_n\": 3,\n                        \"similarity\": 0.6,\n                        \"search_mode\": \"embedding\",\n                        \"max_paragraph_char_number\": 5000\n                    },\n                    \"question_reference_address\": [\n                        \"start-node\",\n                        \"question\"\n                    ],\n                    \"source_dataset_id_list\": [\n\n                    ]\n                }\n            }\n        },\n        {\n            \"id\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"type\": \"condition-node\",\n            \"x\": 1490,\n            \"y\": 3210,\n            \"properties\": {\n                \"width\": 600,\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"分支名称\",\n                            \"value\": \"branch_name\"\n                        }\n                    ]\n                },\n                \"height\": 543.675,\n                \"stepName\": \"判断器\",\n                \"node_data\": {\n                    \"branch\": [\n                        {\n                            \"id\": \"1009\",\n                            \"type\": \"IF\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n                                {\n                                    \"field\": [\n                                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                                        \"is_hit_handling_method_list\"\n                                    ],\n                                    \"value\": \"1\",\n                                    \"compare\": \"len_ge\"\n                                }\n                            ]\n                        },\n                        {\n                            \"id\": \"4908\",\n                            \"type\": \"ELSE IF 1\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n                                {\n                                    \"field\": [\n                                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                                        \"paragraph_list\"\n                                    ],\n                                    \"value\": \"1\",\n                                    \"compare\": \"len_ge\"\n                                }\n                            ]\n                        },\n                        {\n                            \"id\": \"161\",\n                            \"type\": \"ELSE\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n\n                            ]\n                        }\n                    ]\n                },\n                \"branch_condition_list\": [\n                    {\n                        \"index\": 0,\n                        \"height\": 121.225,\n                        \"id\": \"1009\"\n                    },\n                    {\n                        \"index\": 1,\n                        \"height\": 121.225,\n                        \"id\": \"4908\"\n                    },\n                    {\n                        \"index\": 2,\n                        \"height\": 44,\n                        \"id\": \"161\"\n                    }\n                ]\n            }\n        },\n        {\n            \"id\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26\",\n            \"type\": \"reply-node\",\n            \"x\": 2170,\n            \"y\": 2480,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"内容\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 378,\n                \"stepName\": \"指定回复\",\n                \"node_data\": {\n                    \"fields\": [\n                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                        \"directly_return\"\n                    ],\n                    \"content\": \"\",\n                    \"reply_type\": \"referencing\",\n                    \"is_result\": true\n                }\n            }\n        },\n        {\n            \"id\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb\",\n            \"type\": \"ai-chat-node\",\n            \"x\": 2160,\n            \"y\": 3200,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"AI 回答内容\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 763,\n                \"stepName\": \"AI 对话\",\n                \"node_data\": {\n                    \"prompt\": \"已知信息：\\n{{知识库检索.data}}\\n问题：\\n{{开始.question}}\",\n                    \"system\": \"\",\n                    \"model_id\": \"\",\n                    \"dialogue_number\": 0,\n                    \"is_result\": true\n                }\n            }\n        },\n        {\n            \"id\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7\",\n            \"type\": \"ai-chat-node\",\n            \"x\": 2160,\n            \"y\": 3970,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"AI 回答内容\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 763,\n                \"stepName\": \"AI 对话1\",\n                \"node_data\": {\n                    \"prompt\": \"{{开始.question}}\",\n                    \"system\": \"\",\n                    \"model_id\": \"\",\n                    \"dialogue_number\": 0,\n                    \"is_result\": true\n                }\n            }\n        }\n    ],\n    \"edges\": [\n        {\n            \"id\": \"7d0f166f-c472-41b2-b9a2-c294f4c83d73\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"start-node\",\n            \"targetNodeId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"startPoint\": {\n                \"x\": 590,\n                \"y\": 3660\n            },\n            \"endPoint\": {\n                \"x\": 680,\n                \"y\": 3210\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 590,\n                    \"y\": 3660\n                },\n                {\n                    \"x\": 700,\n                    \"y\": 3660\n                },\n                {\n                    \"x\": 570,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 680,\n                    \"y\": 3210\n                }\n            ],\n            \"sourceAnchorId\": \"start-node_right\",\n            \"targetAnchorId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left\"\n        },\n        {\n            \"id\": \"35cb86dd-f328-429e-a973-12fd7218b696\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"targetNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"startPoint\": {\n                \"x\": 1000,\n                \"y\": 3210\n            },\n            \"endPoint\": {\n                \"x\": 1200,\n                \"y\": 3210\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1000,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1110,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1090,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1200,\n                    \"y\": 3210\n                }\n            ],\n            \"sourceAnchorId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right\",\n            \"targetAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_left\"\n        },\n        {\n            \"id\": \"e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3073.775\n            },\n            \"endPoint\": {\n                \"x\": 2010,\n                \"y\": 2480\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3073.775\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3073.775\n                },\n                {\n                    \"x\": 1900,\n                    \"y\": 2480\n                },\n                {\n                    \"x\": 2010,\n                    \"y\": 2480\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right\",\n            \"targetAnchorId\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26_left\"\n        },\n        {\n            \"id\": \"994ff325-6f7a-4ebc-b61b-10e15519d6d2\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3203\n            },\n            \"endPoint\": {\n                \"x\": 2000,\n                \"y\": 3200\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3203\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3203\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3200\n                },\n                {\n                    \"x\": 2000,\n                    \"y\": 3200\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right\",\n            \"targetAnchorId\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left\"\n        },\n        {\n            \"id\": \"19270caf-bb9f-4ba7-9bf8-200aa70fecd5\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3293.6124999999997\n            },\n            \"endPoint\": {\n                \"x\": 2000,\n                \"y\": 3970\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3293.6124999999997\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3293.6124999999997\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3970\n                },\n                {\n                    \"x\": 2000,\n                    \"y\": 3970\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right\",\n            \"targetAnchorId\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7_left\"\n        }\n    ]\n}"
  },
  {
    "path": "apps/application/flow/default_workflow_en.json",
    "content": "{\n    \"nodes\": [\n        {\n            \"id\": \"base-node\",\n            \"type\": \"base-node\",\n            \"x\": 360,\n            \"y\": 2810,\n            \"properties\": {\n                \"config\": {\n\n                },\n                \"height\": 825.6,\n                \"stepName\": \"Base\",\n                \"node_data\": {\n                    \"desc\": \"\",\n                    \"name\": \"maxkbapplication\",\n                    \"prologue\": \"Hello, I am the MaxKB assistant. You can ask me about MaxKB usage issues.\\n-What are the main functions of MaxKB?\\n-What major language models does MaxKB support?\\n-What document types does MaxKB support?\"\n                },\n                \"input_field_list\": [\n\n                ]\n            }\n        },\n        {\n            \"id\": \"start-node\",\n            \"type\": \"start-node\",\n            \"x\": 430,\n            \"y\": 3660,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"User Question\",\n                            \"value\": \"question\"\n                        }\n                    ],\n                    \"globalFields\": [\n                        {\n                            \"label\": \"Current Time\",\n                            \"value\": \"time\"\n                        }\n                    ]\n                },\n                \"fields\": [\n                    {\n                        \"label\": \"User Question\",\n                        \"value\": \"question\"\n                    }\n                ],\n                \"height\": 276,\n                \"stepName\": \"Start\",\n                \"globalFields\": [\n                    {\n                        \"label\": \"Current Time\",\n                        \"value\": \"time\"\n                    }\n                ]\n            }\n        },\n        {\n            \"id\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"type\": \"search-dataset-node\",\n            \"x\": 840,\n            \"y\": 3210,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"List of Retrieved Paragraphs\",\n                            \"value\": \"paragraph_list\"\n                        },\n                        {\n                            \"label\": \"List of Paragraphs Satisfying Direct Answer\",\n                            \"value\": \"is_hit_handling_method_list\"\n                        },\n                        {\n                            \"label\": \"Search Results\",\n                            \"value\": \"data\"\n                        },\n                        {\n                            \"label\": \"Content of Paragraphs Satisfying Direct Answer\",\n                            \"value\": \"directly_return\"\n                        }\n                    ]\n                },\n                \"height\": 794,\n                \"stepName\": \"Knowledge Search\",\n                \"node_data\": {\n                    \"dataset_id_list\": [\n\n                    ],\n                    \"dataset_setting\": {\n                        \"top_n\": 3,\n                        \"similarity\": 0.6,\n                        \"search_mode\": \"embedding\",\n                        \"max_paragraph_char_number\": 5000\n                    },\n                    \"question_reference_address\": [\n                        \"start-node\",\n                        \"question\"\n                    ],\n                    \"source_dataset_id_list\": [\n\n                    ]\n                }\n            }\n        },\n        {\n            \"id\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"type\": \"condition-node\",\n            \"x\": 1490,\n            \"y\": 3210,\n            \"properties\": {\n                \"width\": 600,\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"Branch Name\",\n                            \"value\": \"branch_name\"\n                        }\n                    ]\n                },\n                \"height\": 543.675,\n                \"stepName\": \"Conditional Branch\",\n                \"node_data\": {\n                    \"branch\": [\n                        {\n                            \"id\": \"1009\",\n                            \"type\": \"IF\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n                                {\n                                    \"field\": [\n                                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                                        \"is_hit_handling_method_list\"\n                                    ],\n                                    \"value\": \"1\",\n                                    \"compare\": \"len_ge\"\n                                }\n                            ]\n                        },\n                        {\n                            \"id\": \"4908\",\n                            \"type\": \"ELSE IF 1\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n                                {\n                                    \"field\": [\n                                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                                        \"paragraph_list\"\n                                    ],\n                                    \"value\": \"1\",\n                                    \"compare\": \"len_ge\"\n                                }\n                            ]\n                        },\n                        {\n                            \"id\": \"161\",\n                            \"type\": \"ELSE\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n\n                            ]\n                        }\n                    ]\n                },\n                \"branch_condition_list\": [\n                    {\n                        \"index\": 0,\n                        \"height\": 121.225,\n                        \"id\": \"1009\"\n                    },\n                    {\n                        \"index\": 1,\n                        \"height\": 121.225,\n                        \"id\": \"4908\"\n                    },\n                    {\n                        \"index\": 2,\n                        \"height\": 44,\n                        \"id\": \"161\"\n                    }\n                ]\n            }\n        },\n        {\n            \"id\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26\",\n            \"type\": \"reply-node\",\n            \"x\": 2170,\n            \"y\": 2480,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"Content\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 378,\n                \"stepName\": \"Specified Reply\",\n                \"node_data\": {\n                    \"fields\": [\n                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                        \"directly_return\"\n                    ],\n                    \"content\": \"\",\n                    \"reply_type\": \"referencing\",\n                    \"is_result\": true\n                }\n            }\n        },\n        {\n            \"id\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb\",\n            \"type\": \"ai-chat-node\",\n            \"x\": 2160,\n            \"y\": 3200,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"AI Answer Content\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 763,\n                \"stepName\": \"AI Chat\",\n                \"node_data\": {\n                    \"prompt\": \"Known information:\\n{{Knowledge Search.data}}\\nQuestion:\\n{{Start.question}}\",\n                    \"system\": \"\",\n                    \"model_id\": \"\",\n                    \"dialogue_number\": 0,\n                    \"is_result\": true\n                }\n            }\n        },\n        {\n            \"id\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7\",\n            \"type\": \"ai-chat-node\",\n            \"x\": 2160,\n            \"y\": 3970,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"AI Answer Content\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 763,\n                \"stepName\": \"AI Chat1\",\n                \"node_data\": {\n                    \"prompt\": \"{{Start.question}}\",\n                    \"system\": \"\",\n                    \"model_id\": \"\",\n                    \"dialogue_number\": 0,\n                    \"is_result\": true\n                }\n            }\n        }\n    ],\n    \"edges\": [\n        {\n            \"id\": \"7d0f166f-c472-41b2-b9a2-c294f4c83d73\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"start-node\",\n            \"targetNodeId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"startPoint\": {\n                \"x\": 590,\n                \"y\": 3660\n            },\n            \"endPoint\": {\n                \"x\": 680,\n                \"y\": 3210\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 590,\n                    \"y\": 3660\n                },\n                {\n                    \"x\": 700,\n                    \"y\": 3660\n                },\n                {\n                    \"x\": 570,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 680,\n                    \"y\": 3210\n                }\n            ],\n            \"sourceAnchorId\": \"start-node_right\",\n            \"targetAnchorId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left\"\n        },\n        {\n            \"id\": \"35cb86dd-f328-429e-a973-12fd7218b696\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"targetNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"startPoint\": {\n                \"x\": 1000,\n                \"y\": 3210\n            },\n            \"endPoint\": {\n                \"x\": 1200,\n                \"y\": 3210\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1000,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1110,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1090,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1200,\n                    \"y\": 3210\n                }\n            ],\n            \"sourceAnchorId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right\",\n            \"targetAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_left\"\n        },\n        {\n            \"id\": \"e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3073.775\n            },\n            \"endPoint\": {\n                \"x\": 2010,\n                \"y\": 2480\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3073.775\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3073.775\n                },\n                {\n                    \"x\": 1900,\n                    \"y\": 2480\n                },\n                {\n                    \"x\": 2010,\n                    \"y\": 2480\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right\",\n            \"targetAnchorId\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26_left\"\n        },\n        {\n            \"id\": \"994ff325-6f7a-4ebc-b61b-10e15519d6d2\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3203\n            },\n            \"endPoint\": {\n                \"x\": 2000,\n                \"y\": 3200\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3203\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3203\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3200\n                },\n                {\n                    \"x\": 2000,\n                    \"y\": 3200\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right\",\n            \"targetAnchorId\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left\"\n        },\n        {\n            \"id\": \"19270caf-bb9f-4ba7-9bf8-200aa70fecd5\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3293.6124999999997\n            },\n            \"endPoint\": {\n                \"x\": 2000,\n                \"y\": 3970\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3293.6124999999997\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3293.6124999999997\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3970\n                },\n                {\n                    \"x\": 2000,\n                    \"y\": 3970\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right\",\n            \"targetAnchorId\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7_left\"\n        }\n    ]\n}"
  },
  {
    "path": "apps/application/flow/default_workflow_zh.json",
    "content": "{\n    \"nodes\": [\n        {\n            \"id\": \"base-node\",\n            \"type\": \"base-node\",\n            \"x\": 360,\n            \"y\": 2810,\n            \"properties\": {\n                \"config\": {\n\n                },\n                \"height\": 825.6,\n                \"stepName\": \"基本信息\",\n                \"node_data\": {\n                    \"desc\": \"\",\n                    \"name\": \"maxkbapplication\",\n                    \"prologue\": \"您好，我是 MaxKB 小助手，您可以向我提出 MaxKB 使用问题。\\n- MaxKB 主要功能有什么？\\n- MaxKB 支持哪些大语言模型？\\n- MaxKB 支持哪些文档类型？\"\n                },\n                \"input_field_list\": [\n\n                ]\n            }\n        },\n        {\n            \"id\": \"start-node\",\n            \"type\": \"start-node\",\n            \"x\": 430,\n            \"y\": 3660,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"用户问题\",\n                            \"value\": \"question\"\n                        }\n                    ],\n                    \"globalFields\": [\n                        {\n                            \"label\": \"当前时间\",\n                            \"value\": \"time\"\n                        }\n                    ]\n                },\n                \"fields\": [\n                    {\n                        \"label\": \"用户问题\",\n                        \"value\": \"question\"\n                    }\n                ],\n                \"height\": 276,\n                \"stepName\": \"开始\",\n                \"globalFields\": [\n                    {\n                        \"label\": \"当前时间\",\n                        \"value\": \"time\"\n                    }\n                ]\n            }\n        },\n        {\n            \"id\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"type\": \"search-dataset-node\",\n            \"x\": 840,\n            \"y\": 3210,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"检索结果的分段列表\",\n                            \"value\": \"paragraph_list\"\n                        },\n                        {\n                            \"label\": \"满足直接回答的分段列表\",\n                            \"value\": \"is_hit_handling_method_list\"\n                        },\n                        {\n                            \"label\": \"检索结果\",\n                            \"value\": \"data\"\n                        },\n                        {\n                            \"label\": \"满足直接回答的分段内容\",\n                            \"value\": \"directly_return\"\n                        }\n                    ]\n                },\n                \"height\": 794,\n                \"stepName\": \"知识库检索\",\n                \"node_data\": {\n                    \"dataset_id_list\": [\n\n                    ],\n                    \"dataset_setting\": {\n                        \"top_n\": 3,\n                        \"similarity\": 0.6,\n                        \"search_mode\": \"embedding\",\n                        \"max_paragraph_char_number\": 5000\n                    },\n                    \"question_reference_address\": [\n                        \"start-node\",\n                        \"question\"\n                    ],\n                    \"source_dataset_id_list\": [\n\n                    ]\n                }\n            }\n        },\n        {\n            \"id\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"type\": \"condition-node\",\n            \"x\": 1490,\n            \"y\": 3210,\n            \"properties\": {\n                \"width\": 600,\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"分支名称\",\n                            \"value\": \"branch_name\"\n                        }\n                    ]\n                },\n                \"height\": 543.675,\n                \"stepName\": \"判断器\",\n                \"node_data\": {\n                    \"branch\": [\n                        {\n                            \"id\": \"1009\",\n                            \"type\": \"IF\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n                                {\n                                    \"field\": [\n                                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                                        \"is_hit_handling_method_list\"\n                                    ],\n                                    \"value\": \"1\",\n                                    \"compare\": \"len_ge\"\n                                }\n                            ]\n                        },\n                        {\n                            \"id\": \"4908\",\n                            \"type\": \"ELSE IF 1\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n                                {\n                                    \"field\": [\n                                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                                        \"paragraph_list\"\n                                    ],\n                                    \"value\": \"1\",\n                                    \"compare\": \"len_ge\"\n                                }\n                            ]\n                        },\n                        {\n                            \"id\": \"161\",\n                            \"type\": \"ELSE\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n\n                            ]\n                        }\n                    ]\n                },\n                \"branch_condition_list\": [\n                    {\n                        \"index\": 0,\n                        \"height\": 121.225,\n                        \"id\": \"1009\"\n                    },\n                    {\n                        \"index\": 1,\n                        \"height\": 121.225,\n                        \"id\": \"4908\"\n                    },\n                    {\n                        \"index\": 2,\n                        \"height\": 44,\n                        \"id\": \"161\"\n                    }\n                ]\n            }\n        },\n        {\n            \"id\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26\",\n            \"type\": \"reply-node\",\n            \"x\": 2170,\n            \"y\": 2480,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"内容\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 378,\n                \"stepName\": \"指定回复\",\n                \"node_data\": {\n                    \"fields\": [\n                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                        \"directly_return\"\n                    ],\n                    \"content\": \"\",\n                    \"reply_type\": \"referencing\",\n                    \"is_result\": true\n                }\n            }\n        },\n        {\n            \"id\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb\",\n            \"type\": \"ai-chat-node\",\n            \"x\": 2160,\n            \"y\": 3200,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"AI 回答内容\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 763,\n                \"stepName\": \"AI 对话\",\n                \"node_data\": {\n                    \"prompt\": \"已知信息：\\n{{知识库检索.data}}\\n问题：\\n{{开始.question}}\",\n                    \"system\": \"\",\n                    \"model_id\": \"\",\n                    \"dialogue_number\": 0,\n                    \"is_result\": true\n                }\n            }\n        },\n        {\n            \"id\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7\",\n            \"type\": \"ai-chat-node\",\n            \"x\": 2160,\n            \"y\": 3970,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"AI 回答内容\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 763,\n                \"stepName\": \"AI 对话1\",\n                \"node_data\": {\n                    \"prompt\": \"{{开始.question}}\",\n                    \"system\": \"\",\n                    \"model_id\": \"\",\n                    \"dialogue_number\": 0,\n                    \"is_result\": true\n                }\n            }\n        }\n    ],\n    \"edges\": [\n        {\n            \"id\": \"7d0f166f-c472-41b2-b9a2-c294f4c83d73\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"start-node\",\n            \"targetNodeId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"startPoint\": {\n                \"x\": 590,\n                \"y\": 3660\n            },\n            \"endPoint\": {\n                \"x\": 680,\n                \"y\": 3210\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 590,\n                    \"y\": 3660\n                },\n                {\n                    \"x\": 700,\n                    \"y\": 3660\n                },\n                {\n                    \"x\": 570,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 680,\n                    \"y\": 3210\n                }\n            ],\n            \"sourceAnchorId\": \"start-node_right\",\n            \"targetAnchorId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left\"\n        },\n        {\n            \"id\": \"35cb86dd-f328-429e-a973-12fd7218b696\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"targetNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"startPoint\": {\n                \"x\": 1000,\n                \"y\": 3210\n            },\n            \"endPoint\": {\n                \"x\": 1200,\n                \"y\": 3210\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1000,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1110,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1090,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1200,\n                    \"y\": 3210\n                }\n            ],\n            \"sourceAnchorId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right\",\n            \"targetAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_left\"\n        },\n        {\n            \"id\": \"e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3073.775\n            },\n            \"endPoint\": {\n                \"x\": 2010,\n                \"y\": 2480\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3073.775\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3073.775\n                },\n                {\n                    \"x\": 1900,\n                    \"y\": 2480\n                },\n                {\n                    \"x\": 2010,\n                    \"y\": 2480\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right\",\n            \"targetAnchorId\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26_left\"\n        },\n        {\n            \"id\": \"994ff325-6f7a-4ebc-b61b-10e15519d6d2\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3203\n            },\n            \"endPoint\": {\n                \"x\": 2000,\n                \"y\": 3200\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3203\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3203\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3200\n                },\n                {\n                    \"x\": 2000,\n                    \"y\": 3200\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right\",\n            \"targetAnchorId\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left\"\n        },\n        {\n            \"id\": \"19270caf-bb9f-4ba7-9bf8-200aa70fecd5\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3293.6124999999997\n            },\n            \"endPoint\": {\n                \"x\": 2000,\n                \"y\": 3970\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3293.6124999999997\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3293.6124999999997\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3970\n                },\n                {\n                    \"x\": 2000,\n                    \"y\": 3970\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right\",\n            \"targetAnchorId\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7_left\"\n        }\n    ]\n}"
  },
  {
    "path": "apps/application/flow/default_workflow_zh_Hant.json",
    "content": "{\n    \"nodes\": [\n        {\n            \"id\": \"base-node\",\n            \"type\": \"base-node\",\n            \"x\": 360,\n            \"y\": 2810,\n            \"properties\": {\n                \"config\": {\n\n                },\n                \"height\": 825.6,\n                \"stepName\": \"基本資訊\",\n                \"node_data\": {\n                    \"desc\": \"\",\n                    \"name\": \"maxkbapplication\",\n                    \"prologue\": \"您好，我是 MaxKB 小助手，您可以向我提出 MaxKB 使用問題。\\n- MaxKB 主要功能有哪些？\\n- MaxKB 支援哪些大型語言模型？\\n- MaxKB 支援哪些文件類型？\"\n                },\n                \"input_field_list\": [\n\n                ]\n            }\n        },\n        {\n            \"id\": \"start-node\",\n            \"type\": \"start-node\",\n            \"x\": 430,\n            \"y\": 3660,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"用戶問題\",\n                            \"value\": \"question\"\n                        }\n                    ],\n                    \"globalFields\": [\n                        {\n                            \"label\": \"當前時間\",\n                            \"value\": \"time\"\n                        }\n                    ]\n                },\n                \"fields\": [\n                    {\n                        \"label\": \"用戶問題\",\n                        \"value\": \"question\"\n                    }\n                ],\n                \"height\": 276,\n                \"stepName\": \"開始\",\n                \"globalFields\": [\n                    {\n                        \"label\": \"當前時間\",\n                        \"value\": \"time\"\n                    }\n                ]\n            }\n        },\n        {\n            \"id\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"type\": \"search-dataset-node\",\n            \"x\": 840,\n            \"y\": 3210,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"檢索結果的分段列表\",\n                            \"value\": \"paragraph_list\"\n                        },\n                        {\n                            \"label\": \"滿足直接回答的分段列表\",\n                            \"value\": \"is_hit_handling_method_list\"\n                        },\n                        {\n                            \"label\": \"檢索結果\",\n                            \"value\": \"data\"\n                        },\n                        {\n                            \"label\": \"滿足直接回答的分段內容\",\n                            \"value\": \"directly_return\"\n                        }\n                    ]\n                },\n                \"height\": 794,\n                \"stepName\": \"知識庫檢索\",\n                \"node_data\": {\n                    \"dataset_id_list\": [\n\n                    ],\n                    \"dataset_setting\": {\n                        \"top_n\": 3,\n                        \"similarity\": 0.6,\n                        \"search_mode\": \"embedding\",\n                        \"max_paragraph_char_number\": 5000\n                    },\n                    \"question_reference_address\": [\n                        \"start-node\",\n                        \"question\"\n                    ],\n                    \"source_dataset_id_list\": [\n\n                    ]\n                }\n            }\n        },\n        {\n            \"id\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"type\": \"condition-node\",\n            \"x\": 1490,\n            \"y\": 3210,\n            \"properties\": {\n                \"width\": 600,\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"分支名稱\",\n                            \"value\": \"branch_name\"\n                        }\n                    ]\n                },\n                \"height\": 543.675,\n                \"stepName\": \"判斷器\",\n                \"node_data\": {\n                    \"branch\": [\n                        {\n                            \"id\": \"1009\",\n                            \"type\": \"IF\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n                                {\n                                    \"field\": [\n                                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                                        \"is_hit_handling_method_list\"\n                                    ],\n                                    \"value\": \"1\",\n                                    \"compare\": \"len_ge\"\n                                }\n                            ]\n                        },\n                        {\n                            \"id\": \"4908\",\n                            \"type\": \"ELSE IF 1\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n                                {\n                                    \"field\": [\n                                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                                        \"paragraph_list\"\n                                    ],\n                                    \"value\": \"1\",\n                                    \"compare\": \"len_ge\"\n                                }\n                            ]\n                        },\n                        {\n                            \"id\": \"161\",\n                            \"type\": \"ELSE\",\n                            \"condition\": \"and\",\n                            \"conditions\": [\n\n                            ]\n                        }\n                    ]\n                },\n                \"branch_condition_list\": [\n                    {\n                        \"index\": 0,\n                        \"height\": 121.225,\n                        \"id\": \"1009\"\n                    },\n                    {\n                        \"index\": 1,\n                        \"height\": 121.225,\n                        \"id\": \"4908\"\n                    },\n                    {\n                        \"index\": 2,\n                        \"height\": 44,\n                        \"id\": \"161\"\n                    }\n                ]\n            }\n        },\n        {\n            \"id\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26\",\n            \"type\": \"reply-node\",\n            \"x\": 2170,\n            \"y\": 2480,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"內容\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 378,\n                \"stepName\": \"指定回覆\",\n                \"node_data\": {\n                    \"fields\": [\n                        \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n                        \"directly_return\"\n                    ],\n                    \"content\": \"\",\n                    \"reply_type\": \"referencing\",\n                    \"is_result\": true\n                }\n            }\n        },\n        {\n            \"id\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb\",\n            \"type\": \"ai-chat-node\",\n            \"x\": 2160,\n            \"y\": 3200,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"AI 回答內容\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 763,\n                \"stepName\": \"AI 對話\",\n                \"node_data\": {\n                    \"prompt\": \"已知資訊：\\n{{知識庫檢索.data}}\\n問題：\\n{{開始.question}}\",\n                    \"system\": \"\",\n                    \"model_id\": \"\",\n                    \"dialogue_number\": 0,\n                    \"is_result\": true\n                }\n            }\n        },\n        {\n            \"id\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7\",\n            \"type\": \"ai-chat-node\",\n            \"x\": 2160,\n            \"y\": 3970,\n            \"properties\": {\n                \"config\": {\n                    \"fields\": [\n                        {\n                            \"label\": \"AI 回答內容\",\n                            \"value\": \"answer\"\n                        }\n                    ]\n                },\n                \"height\": 763,\n                \"stepName\": \"AI 對話1\",\n                \"node_data\": {\n                    \"prompt\": \"{{開始.question}}\",\n                    \"system\": \"\",\n                    \"model_id\": \"\",\n                    \"dialogue_number\": 0,\n                    \"is_result\": true\n                }\n            }\n        }\n    ],\n    \"edges\": [\n        {\n            \"id\": \"7d0f166f-c472-41b2-b9a2-c294f4c83d73\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"start-node\",\n            \"targetNodeId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"startPoint\": {\n                \"x\": 590,\n                \"y\": 3660\n            },\n            \"endPoint\": {\n                \"x\": 680,\n                \"y\": 3210\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 590,\n                    \"y\": 3660\n                },\n                {\n                    \"x\": 700,\n                    \"y\": 3660\n                },\n                {\n                    \"x\": 570,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 680,\n                    \"y\": 3210\n                }\n            ],\n            \"sourceAnchorId\": \"start-node_right\",\n            \"targetAnchorId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5_left\"\n        },\n        {\n            \"id\": \"35cb86dd-f328-429e-a973-12fd7218b696\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5\",\n            \"targetNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"startPoint\": {\n                \"x\": 1000,\n                \"y\": 3210\n            },\n            \"endPoint\": {\n                \"x\": 1200,\n                \"y\": 3210\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1000,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1110,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1090,\n                    \"y\": 3210\n                },\n                {\n                    \"x\": 1200,\n                    \"y\": 3210\n                }\n            ],\n            \"sourceAnchorId\": \"b931efe5-5b66-46e0-ae3b-0160cb18eeb5_right\",\n            \"targetAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_left\"\n        },\n        {\n            \"id\": \"e8f6cfe6-7e48-41cd-abd3-abfb5304d0d8\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3073.775\n            },\n            \"endPoint\": {\n                \"x\": 2010,\n                \"y\": 2480\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3073.775\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3073.775\n                },\n                {\n                    \"x\": 1900,\n                    \"y\": 2480\n                },\n                {\n                    \"x\": 2010,\n                    \"y\": 2480\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_1009_right\",\n            \"targetAnchorId\": \"4ffe1086-25df-4c85-b168-979b5bbf0a26_left\"\n        },\n        {\n            \"id\": \"994ff325-6f7a-4ebc-b61b-10e15519d6d2\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3203\n            },\n            \"endPoint\": {\n                \"x\": 2000,\n                \"y\": 3200\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3203\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3203\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3200\n                },\n                {\n                    \"x\": 2000,\n                    \"y\": 3200\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_4908_right\",\n            \"targetAnchorId\": \"f1f1ee18-5a02-46f6-b4e6-226253cdffbb_left\"\n        },\n        {\n            \"id\": \"19270caf-bb9f-4ba7-9bf8-200aa70fecd5\",\n            \"type\": \"app-edge\",\n            \"sourceNodeId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b\",\n            \"targetNodeId\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7\",\n            \"startPoint\": {\n                \"x\": 1780,\n                \"y\": 3293.6124999999997\n            },\n            \"endPoint\": {\n                \"x\": 2000,\n                \"y\": 3970\n            },\n            \"properties\": {\n\n            },\n            \"pointsList\": [\n                {\n                    \"x\": 1780,\n                    \"y\": 3293.6124999999997\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3293.6124999999997\n                },\n                {\n                    \"x\": 1890,\n                    \"y\": 3970\n                },\n                {\n                    \"x\": 2000,\n                    \"y\": 3970\n                }\n            ],\n            \"sourceAnchorId\": \"fc60863a-dec2-4854-9e5a-7a44b7187a2b_161_right\",\n            \"targetAnchorId\": \"309d0eef-c597-46b5-8d51-b9a28aaef4c7_left\"\n        }\n    ]\n}"
  },
  {
    "path": "apps/application/flow/i_step_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_step_node.py\n    @date：2024/6/3 14:57\n    @desc:\n\"\"\"\nimport time\nimport uuid\nfrom abc import abstractmethod\nfrom hashlib import sha1\nfrom typing import Type, Dict, List\n\nfrom django.core import cache\nfrom django.db.models import QuerySet\nfrom rest_framework import serializers\nfrom rest_framework.exceptions import ValidationError, ErrorDetail\n\nfrom application.flow.common import Answer, NodeChunk\nfrom application.models import ApplicationChatUserStats\nfrom application.models import ChatRecord, ChatUserType\nfrom common.field.common import InstanceField\nfrom knowledge.models.knowledge_action import KnowledgeAction, State\nfrom tools.models import ToolRecord\n\nchat_cache = cache\n\n\ndef write_context(step_variable: Dict, global_variable: Dict, node, workflow):\n    if step_variable is not None:\n        for key in step_variable:\n            node.context[key] = step_variable[key]\n        if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'answer' in step_variable:\n            answer = step_variable['answer']\n            yield answer\n            node.answer_text = answer\n    if global_variable is not None:\n        for key in global_variable:\n            workflow.context[key] = global_variable[key]\n    node.context['run_time'] = time.time() - node.context['start_time']\n\n\ndef is_interrupt(node, step_variable: Dict, global_variable: Dict):\n    return node.type == 'form-node' and not node.context.get('is_submit', False)\n\n\nclass WorkFlowPostHandler:\n    def __init__(self, chat_info):\n        self.chat_info = chat_info\n\n    def handler(self, workflow):\n        workflow_body = workflow.get_body()\n        question = workflow_body.get('question')\n        chat_record_id = workflow_body.get('chat_record_id')\n        chat_id = workflow_body.get('chat_id')\n        details = workflow.get_runtime_details()\n        message_tokens = sum([row.get('message_tokens') for row in details.values() if\n                              'message_tokens' in row and row.get('message_tokens') is not None])\n        answer_tokens = sum([row.get('answer_tokens') for row in details.values() if\n                             'answer_tokens' in row and row.get('answer_tokens') is not None])\n        answer_text_list = workflow.get_answer_text_list()\n        answer_text = '\\n\\n'.join(\n            '\\n\\n'.join([a.get('content') for a in answer]) for answer in\n            answer_text_list)\n        if workflow.chat_record is not None:\n            chat_record = workflow.chat_record\n            chat_record.problem_text = question\n            chat_record.answer_text = answer_text\n            chat_record.details = details\n            chat_record.message_tokens = message_tokens\n            chat_record.answer_tokens = answer_tokens\n            chat_record.answer_text_list = answer_text_list\n            chat_record.run_time = time.time() - workflow.context['start_time']\n        else:\n            chat_record = ChatRecord(id=chat_record_id,\n                                     chat_id=chat_id,\n                                     problem_text=question,\n                                     answer_text=answer_text,\n                                     details=details,\n                                     message_tokens=message_tokens,\n                                     answer_tokens=answer_tokens,\n                                     answer_text_list=answer_text_list,\n                                     run_time=time.time() - workflow.context.get('start_time') if workflow.context.get(\n                                         'start_time') is not None else 0,\n                                     index=0,\n                                     ip_address=self.chat_info.ip_address,\n                                     source=self.chat_info.source)\n\n        self.chat_info.append_chat_record(chat_record)\n        self.chat_info.set_cache()\n\n        if not self.chat_info.debug and [ChatUserType.ANONYMOUS_USER.value, ChatUserType.CHAT_USER.value].__contains__(\n                workflow_body.get('chat_user_type')):\n            application_public_access_client = (QuerySet(ApplicationChatUserStats)\n                                                .filter(chat_user_id=workflow_body.get('chat_user_id'),\n                                                        chat_user_type=workflow_body.get('chat_user_type'),\n                                                        application_id=self.chat_info.application_id).first())\n            if application_public_access_client is not None:\n                application_public_access_client.access_num = application_public_access_client.access_num + 1\n                application_public_access_client.intraday_access_num = application_public_access_client.intraday_access_num + 1\n                application_public_access_client.save()\n        self.chat_info = None\n\n\nclass KnowledgeWorkflowPostHandler(WorkFlowPostHandler):\n    def __init__(self, chat_info, knowledge_action_id):\n        super().__init__(chat_info)\n        self.knowledge_action_id = knowledge_action_id\n\n    def handler(self, workflow):\n        state = get_workflow_state(workflow)\n        QuerySet(KnowledgeAction).filter(id=self.knowledge_action_id).update(\n            state=state,\n            run_time=time.time() - workflow.context.get('start_time') if workflow.context.get(\n                'start_time') is not None else 0)\n\n\ndef get_tool_workflow_state(workflow):\n    if workflow.is_the_task_interrupted():\n        return State.REVOKED\n    details = workflow.get_runtime_details()\n    node_list = details.values()\n    all_node = [*node_list, *get_loop_workflow_node(node_list)]\n    err = any([True for value in all_node if value.get('status') == 500 and not value.get('enableException')])\n    if err:\n        return State.FAILURE\n    return State.SUCCESS\n\n\nclass ToolWorkflowPostHandler(WorkFlowPostHandler):\n    def __init__(self, chat_info, tool_id):\n        super().__init__(chat_info)\n        self.tool_id = tool_id\n\n    def handler(self, workflow):\n        state = get_tool_workflow_state(workflow)\n        record = ToolRecord(id=self.chat_info.tool_record_id, tool_id=self.tool_id,\n                            workspace_id=self.chat_info.workspace_id,\n                            source_type=self.chat_info.source_type,\n                            source_id=self.chat_info.source_id,\n                            state=state,\n                            meta={\n                                'output': workflow.out_context,\n                                'details': workflow.get_runtime_details(),\n                                'answer_text_list': workflow.get_answer_text_list()\n                            })\n        self.chat_info.set_record(record)\n        self.chat_info = None\n        self.tool_id = None\n\n\ndef get_loop_workflow_node(node_list):\n    result = []\n    for item in node_list:\n        if item.get('type') == 'loop-node':\n            for loop_item in item.get('loop_node_data') or []:\n                for inner_item in loop_item.values():\n                    result.append(inner_item)\n    return result\n\n\ndef get_workflow_state(workflow):\n    if workflow.is_the_task_interrupted():\n        return State.REVOKED\n    details = workflow.get_runtime_details()\n    node_list = details.values()\n    all_node = [*node_list, *get_loop_workflow_node(node_list)]\n    err = any([True for value in all_node if value.get('status') == 500 and not value.get('enableException')])\n    if err:\n        return State.FAILURE\n    write_is_exist = any([True for value in all_node if value.get('type') == 'knowledge-write-node'])\n    if not write_is_exist:\n        return State.FAILURE\n    return State.SUCCESS\n\n\nclass NodeResult:\n    def __init__(self, node_variable: Dict, workflow_variable: Dict,\n                 _write_context=write_context, _is_interrupt=is_interrupt):\n        self._write_context = _write_context\n        self.node_variable = node_variable\n        self.workflow_variable = workflow_variable\n        self._is_interrupt = _is_interrupt\n\n    def write_context(self, node, workflow):\n        return self._write_context(self.node_variable, self.workflow_variable, node, workflow)\n\n    def is_assertion_result(self):\n        return 'branch_id' in self.node_variable\n\n    def is_interrupt_exec(self, current_node):\n        \"\"\"\n        是否中断执行\n        @param current_node:\n        @return:\n        \"\"\"\n        return self._is_interrupt(current_node, self.node_variable, self.workflow_variable)\n\n\nclass ReferenceAddressSerializer(serializers.Serializer):\n    node_id = serializers.CharField(required=True, label=\"节点id\")\n    fields = serializers.ListField(\n        child=serializers.CharField(required=True, label=\"节点字段\"), required=True,\n        label=\"节点字段数组\")\n\n\nclass FlowParamsSerializer(serializers.Serializer):\n    # 历史对答\n    history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),\n                                                label=\"历史对答\")\n\n    question = serializers.CharField(required=True, label=\"用户问题\")\n\n    chat_id = serializers.CharField(required=True, label=\"对话id\")\n\n    chat_record_id = serializers.CharField(required=True, label=\"对话记录id\")\n\n    stream = serializers.BooleanField(required=True, label=\"流式输出\")\n\n    chat_user_id = serializers.CharField(required=False, label=\"对话用户id\")\n\n    chat_user_type = serializers.CharField(required=False, label=\"对话用户类型\")\n\n    workspace_id = serializers.CharField(required=True, label=\"工作空间id\")\n\n    application_id = serializers.CharField(required=True, label=\"应用id\")\n\n    re_chat = serializers.BooleanField(required=True, label=\"换个答案\")\n\n    debug = serializers.BooleanField(required=True, label=\"是否debug\")\n\n\nclass KnowledgeFlowParamsSerializer(serializers.Serializer):\n    knowledge_id = serializers.UUIDField(required=True, label=\"知识库id\")\n    workspace_id = serializers.CharField(required=True, label=\"工作空间id\")\n    knowledge_action_id = serializers.UUIDField(required=True, label=\"知识库任务执行器id\")\n    data_source = serializers.DictField(required=True, label=\"数据源\")\n    knowledge_base = serializers.DictField(required=False, label=\"知识库设置\")\n\n\nclass ToolFlowParamsSerializer(serializers.Serializer):\n    tool_id = serializers.UUIDField(required=True, label=\"工具id\")\n    workspace_id = serializers.CharField(required=True, label=\"工作空间id\")\n\n\nclass INode:\n    view_type = 'many_view'\n\n    @abstractmethod\n    def save_context(self, details, workflow_manage):\n        pass\n\n    def get_answer_list(self) -> List[Answer] | None:\n        if self.answer_text is None:\n            return None\n        reasoning_content_enable = self.context.get('model_setting', {}).get('reasoning_content_enable', False)\n        return [\n            Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params.get('chat_record_id'),\n                   {},\n                   self.runtime_node_id, self.context.get('reasoning_content', '') if reasoning_content_enable else '')]\n\n    def __init__(self, node, workflow_params, workflow_manage, up_node_id_list=None,\n                 get_node_params=lambda node: node.properties.get('node_data'), salt=None):\n        # 当前步骤上下文,用于存储当前步骤信息\n        self.status = 200\n        self.err_message = ''\n        self.node = node\n        self.node_params = get_node_params(node)\n        self.workflow_params = workflow_params\n        self.workflow_manage = workflow_manage\n        self.node_params_serializer = None\n        self.flow_params_serializer = None\n        self.context = {}\n        self.answer_text = None\n        self.id = node.id\n        if up_node_id_list is None:\n            up_node_id_list = []\n        self.up_node_id_list = up_node_id_list\n        self.node_chunk = NodeChunk()\n        self.runtime_node_id = sha1(uuid.NAMESPACE_DNS.bytes + bytes(str(uuid.uuid5(uuid.NAMESPACE_DNS,\n                                                                                    \"\".join([*sorted(up_node_id_list),\n                                                                                             node.id]))),\n                                                                     \"utf-8\")).hexdigest() + (\n                                   \"__\" + str(salt) if salt is not None else '')\n\n    def valid_args(self, node_params, flow_params):\n        flow_params_serializer_class = self.get_flow_params_serializer_class()\n        node_params_serializer_class = self.get_node_params_serializer_class()\n        if flow_params_serializer_class is not None and flow_params is not None:\n            self.flow_params_serializer = flow_params_serializer_class(data=flow_params)\n            self.flow_params_serializer.is_valid(raise_exception=True)\n        if node_params_serializer_class is not None:\n            self.node_params_serializer = node_params_serializer_class(data=node_params)\n            self.node_params_serializer.is_valid(raise_exception=True)\n        if self.node.properties.get('status', 200) != 200:\n            raise ValidationError(ErrorDetail(f'节点{self.node.properties.get(\"stepName\")} 不可用'))\n\n    def get_reference_field(self, fields: List[str]):\n        return self.get_field(self.context, fields)\n\n    @staticmethod\n    def get_field(obj, fields: List[str]):\n        for field in fields:\n            value = obj.get(field)\n            if value is None:\n                return None\n            else:\n                obj = value\n        return obj\n\n    @abstractmethod\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        pass\n\n    def get_flow_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return self.workflow_manage.get_params_serializer_class()\n\n    def get_write_error_context(self, e):\n        self.status = 500\n        self.answer_text = str(e)\n        self.err_message = str(e)\n        current_time = time.time()\n        self.context['run_time'] = current_time - (self.context.get('start_time') or current_time)\n\n        def write_error_context(answer, status=200):\n            pass\n\n        return write_error_context\n\n    def run(self) -> NodeResult:\n        \"\"\"\n        :return: 执行结果\n        \"\"\"\n        start_time = time.time()\n        self.context['start_time'] = start_time\n        result = self._run()\n        self.context['run_time'] = time.time() - start_time\n        return result\n\n    def _run(self):\n        result = self.execute()\n        return result\n\n    def execute(self, **kwargs) -> NodeResult:\n        pass\n\n    def get_details(self, index: int, **kwargs):\n        \"\"\"\n        运行详情\n        :return: 步骤详情\n        \"\"\"\n        return {}\n"
  },
  {
    "path": "apps/application/flow/knowledge_loop_workflow_manage.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： workflow_manage.py\n    @date：2024/1/9 17:40\n    @desc:\n\"\"\"\nfrom application.flow.i_step_node import KnowledgeFlowParamsSerializer\nfrom application.flow.loop_workflow_manage import LoopWorkflowManage\n\n\nclass KnowledgeLoopWorkflowManage(LoopWorkflowManage):\n    def get_params_serializer_class(self):\n        return KnowledgeFlowParamsSerializer\n\n    def get_source_type(self):\n        return \"KNOWLEDGE\"\n\n    def get_source_id(self):\n        return self.params.get('knowledge_id')\n"
  },
  {
    "path": "apps/application/flow/knowledge_workflow_manage.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： Knowledge_workflow_manage.py\n    @date：2025/11/13 19:02\n    @desc:\n\"\"\"\nimport time\nimport traceback\nfrom concurrent.futures import ThreadPoolExecutor\n\nfrom django.db.models import QuerySet\nfrom django.utils.translation import get_language\n\nfrom application.flow.common import Workflow\nfrom application.flow.i_step_node import WorkFlowPostHandler, KnowledgeFlowParamsSerializer, NodeResult\nfrom application.flow.workflow_manage import WorkflowManage\nfrom common.handle.base_to_response import BaseToResponse\nfrom common.handle.impl.response.system_to_response import SystemToResponse\nfrom knowledge.models.knowledge_action import KnowledgeAction, State\n\nexecutor = ThreadPoolExecutor(max_workers=200)\n\n\nclass KnowledgeWorkflowManage(WorkflowManage):\n\n    def __init__(self, flow: Workflow,\n                 params,\n                 work_flow_post_handler: WorkFlowPostHandler,\n                 base_to_response: BaseToResponse = SystemToResponse(),\n                 start_node_id=None,\n                 start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False):\n        super().__init__(flow, params, work_flow_post_handler, base_to_response, None, None, None,\n                         None,\n                         None, None, start_node_id, start_node_data, chat_record, child_node, is_the_task_interrupted)\n\n    def get_params_serializer_class(self):\n        return KnowledgeFlowParamsSerializer\n\n    def get_start_node(self):\n        start_node_list = [node for node in self.flow.nodes if\n                           self.params.get('data_source', {}).get('node_id') == node.id]\n        return start_node_list[0]\n\n    def run(self):\n        self.context['start_time'] = time.time()\n        executor.submit(self._run)\n\n    def _run(self):\n        QuerySet(KnowledgeAction).filter(id=self.params.get('knowledge_action_id')).update(\n            state=State.STARTED)\n        language = get_language()\n        self.run_chain_async(self.start_node, None, language)\n        while self.is_run():\n            pass\n        self.work_flow_post_handler.handler(self)\n\n    @staticmethod\n    def get_node_details(current_node, node, index):\n        if current_node == node:\n            return {\n                'name': node.node.properties.get('stepName'),\n                \"index\": index,\n                'run_time': 0,\n                'type': node.type,\n                'status': 202,\n                'err_message': \"\"\n            }\n\n        return node.get_details(index)\n\n    def run_chain(self, current_node, node_result_future=None):\n        QuerySet(KnowledgeAction).filter(id=self.params.get('knowledge_action_id')).update(\n            details=self.get_runtime_details(lambda node, index: self.get_node_details(current_node, node, index)))\n        if node_result_future is None:\n            node_result_future = self.run_node_future(current_node)\n        try:\n            result = self.hand_node_result(current_node, node_result_future)\n            return result\n        except Exception as e:\n            traceback.print_exc()\n        return None\n\n    def hand_node_result(self, current_node, node_result_future):\n        try:\n            current_result = node_result_future.result()\n            result = current_result.write_context(current_node, self)\n            if result is not None:\n                # 阻塞获取结果\n                list(result)\n            if current_node.status == 500:\n                enableException = current_node.node.properties.get('enableException')\n                if not enableException:\n                    return None\n                current_node.context['exception_message'] = current_node.err_message\n                current_node.context['branch_id'] = 'exception'\n                r = NodeResult({'branch_id': 'exception', 'exception': current_node.err_message}, {},\n                               _is_interrupt=lambda node, step_variable, global_variable: False)\n                r.write_context(current_node, self)\n                return r\n            if self.is_the_task_interrupted():\n                current_node.status = 201\n                return None\n            return current_result\n        except Exception as e:\n            traceback.print_exc()\n            self.status = 500\n            current_node.get_write_error_context(e)\n            self.answer += str(e)\n            if self.is_the_task_interrupted():\n                current_node.status = 201\n                return None\n            enableException = current_node.node.properties.get('enableException')\n            if enableException:\n                current_node.context['exception_message'] = current_node.err_message\n                current_node.context['branch_id'] = 'exception'\n                return NodeResult({'branch_id': 'exception', 'exception': current_node.err_message}, {},\n                                  _is_interrupt=lambda node, step_variable, global_variable: False)\n            QuerySet(KnowledgeAction).filter(id=self.params.get('knowledge_action_id')).update(state=State.FAILURE)\n        finally:\n            current_node.node_chunk.end()\n            QuerySet(KnowledgeAction).filter(id=self.params.get('knowledge_action_id')).update(\n                details=self.get_runtime_details())\n\n    def get_source_type(self):\n        return \"KNOWLEDGE\"\n\n    def get_source_id(self):\n        return self.params.get('knowledge_id')\n"
  },
  {
    "path": "apps/application/flow/loop_workflow_manage.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： workflow_manage.py\n    @date：2024/1/9 17:40\n    @desc:\n\"\"\"\nfrom concurrent.futures import ThreadPoolExecutor\nfrom typing import List\n\nfrom django.db import close_old_connections\nfrom django.utils.translation import get_language\nfrom langchain_core.prompts import PromptTemplate\n\nfrom application.flow.common import Workflow\nfrom application.flow.i_step_node import WorkFlowPostHandler, INode\nfrom application.flow.step_node import get_node\nfrom application.flow.workflow_manage import WorkflowManage\nfrom common.handle.base_to_response import BaseToResponse\nfrom common.handle.impl.response.system_to_response import SystemToResponse\n\nexecutor = ThreadPoolExecutor(max_workers=200)\n\n\nclass NodeResultFuture:\n    def __init__(self, r, e, status=200):\n        self.r = r\n        self.e = e\n        self.status = status\n\n    def result(self):\n        if self.status == 200:\n            return self.r\n        else:\n            raise self.e\n\n\ndef await_result(result, timeout=1):\n    try:\n        result.result(timeout)\n        return False\n    except Exception as e:\n        return True\n\n\nclass NodeChunkManage:\n\n    def __init__(self, work_flow):\n        self.node_chunk_list = []\n        self.current_node_chunk = None\n        self.work_flow = work_flow\n\n    def add_node_chunk(self, node_chunk):\n        self.node_chunk_list.append(node_chunk)\n\n    def contains(self, node_chunk):\n        return self.node_chunk_list.__contains__(node_chunk)\n\n    def pop(self):\n        if self.current_node_chunk is None:\n            try:\n                current_node_chunk = self.node_chunk_list.pop(0)\n                self.current_node_chunk = current_node_chunk\n            except IndexError as e:\n                pass\n        if self.current_node_chunk is not None:\n            try:\n                chunk = self.current_node_chunk.chunk_list.pop(0)\n                return chunk\n            except IndexError as e:\n                if self.current_node_chunk.is_end():\n                    self.current_node_chunk = None\n                    if self.work_flow.answer_is_not_empty():\n                        chunk = self.work_flow.base_to_response.to_stream_chunk_response(\n                            self.work_flow.params['chat_id'],\n                            self.work_flow.params['chat_record_id'],\n                            '\\n\\n', False, 0, 0)\n                        self.work_flow.append_answer('\\n\\n')\n                        return chunk\n                    return self.pop()\n        return None\n\n\nclass LoopWorkflowManage(WorkflowManage):\n\n    def __init__(self, flow: Workflow,\n                 params,\n                 work_flow_post_handler: WorkFlowPostHandler,\n                 parentWorkflowManage,\n                 loop_params,\n                 get_loop_context,\n                 base_to_response: BaseToResponse = SystemToResponse(),\n                 start_node_id=None,\n                 start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False):\n        self.parentWorkflowManage = parentWorkflowManage\n        self.loop_params = loop_params\n        self.get_loop_context = get_loop_context\n        self.loop_field_list = []\n        super().__init__(flow, params, work_flow_post_handler, base_to_response, None, None, None,\n                         None,\n                         None, None, start_node_id, start_node_data, chat_record, child_node, is_the_task_interrupted)\n\n    def get_node_cls_by_id(self, node_id, up_node_id_list=None,\n                           get_node_params=lambda node: node.properties.get('node_data')):\n        for node in self.flow.nodes:\n            if node.id == node_id:\n                node_instance = get_node(node.type, self.flow.workflow_mode)(node,\n                                                                             self.params, self, up_node_id_list,\n                                                                             get_node_params,\n                                                                             salt=self.get_index())\n                return node_instance\n        return None\n\n    def stream(self):\n        close_old_connections()\n        language = get_language()\n        self.run_chain_async(self.start_node, None, language)\n        return self.await_result(is_cleanup=False)\n\n    def get_index(self):\n        return self.loop_params.get('index')\n\n    def get_start_node(self):\n        start_node_list = [node for node in self.flow.nodes if\n                           ['loop-start-node'].__contains__(node.type)]\n        return start_node_list[0]\n\n    def get_reference_field(self, node_id: str, fields: List[str]):\n        \"\"\"\n        @param node_id: 节点id\n        @param fields:  字段\n        @return:\n        \"\"\"\n        if node_id == 'global':\n            return self.parentWorkflowManage.get_reference_field(node_id, fields)\n        elif node_id == 'chat':\n            return self.parentWorkflowManage.get_reference_field(node_id, fields)\n        elif node_id == 'loop':\n            loop_context = self.get_loop_context()\n            return INode.get_field(loop_context, fields)\n        else:\n            node = self.get_node_by_id(node_id)\n            if node:\n                return node.get_reference_field(fields)\n            return self.parentWorkflowManage.get_reference_field(node_id, fields)\n\n    def get_workflow_content(self):\n        context = {\n            'global': self.context,\n            'chat': self.chat_context,\n            'loop': self.get_loop_context(),\n        }\n\n        for node in self.node_context:\n            context[node.id] = node.context\n        return context\n\n    def init_fields(self):\n        super().init_fields()\n        loop_field_list = []\n        loop_start_node = self.flow.get_node('loop-start-node')\n        loop_input_field_list = loop_start_node.properties.get('loop_input_field_list')\n        node_name = loop_start_node.properties.get('stepName')\n        node_id = loop_start_node.id\n        if loop_input_field_list is not None:\n            for f in loop_input_field_list:\n                loop_field_list.append(\n                    {'label': f.get('label'), 'value': f.get('field'), 'node_id': node_id, 'node_name': node_name})\n        self.loop_field_list = loop_field_list\n\n    def reset_prompt(self, prompt: str):\n        prompt = super().reset_prompt(prompt)\n        for field in self.loop_field_list:\n            chatLabel = f\"loop.{field.get('value')}\"\n            chatValue = f\"context.get('loop').get('{field.get('value', '')}','')\"\n            prompt = prompt.replace(chatLabel, chatValue)\n\n        prompt = self.parentWorkflowManage.reset_prompt(prompt)\n        return prompt\n\n    def generate_prompt(self, prompt: str):\n        \"\"\"\n        格式化生成提示词\n        @param prompt: 提示词信息\n        @return: 格式化后的提示词\n        \"\"\"\n\n        context = {**self.get_workflow_content(), **self.parentWorkflowManage.get_workflow_content()}\n        prompt = self.reset_prompt(prompt)\n        prompt_template = PromptTemplate.from_template(prompt, template_format='jinja2')\n        value = prompt_template.format(context=context)\n        return value\n\n    def get_source_type(self):\n        return \"APPLICATION\"\n\n    def get_source_id(self):\n        return self.params.get('application_id')\n"
  },
  {
    "path": "apps/application/flow/step_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/6/7 14:43\n    @desc:\n\"\"\"\nfrom .ai_chat_step_node import *\nfrom .application_node import BaseApplicationNode\nfrom .condition_node import *\nfrom .data_source_local_node.impl.base_data_source_local_node import BaseDataSourceLocalNode\nfrom .data_source_web_node.impl.base_data_source_web_node import BaseDataSourceWebNode\nfrom .direct_reply_node import *\nfrom .document_extract_node import *\nfrom .form_node import *\nfrom .image_generate_step_node import *\nfrom .image_to_video_step_node import BaseImageToVideoNode\nfrom .image_understand_step_node import *\nfrom .intent_node import *\nfrom .knowledge_write_node.impl.base_knowledge_write_node import BaseKnowledgeWriteNode\nfrom .loop_break_node import BaseLoopBreakNode\nfrom .loop_continue_node import BaseLoopContinueNode\nfrom .loop_node import *\nfrom .loop_start_node import *\nfrom .mcp_node import BaseMcpNode\nfrom .parameter_extraction_node import BaseParameterExtractionNode\nfrom .question_node import *\nfrom .reranker_node import *\nfrom .search_document_node import BaseSearchDocumentNode\nfrom .search_knowledge_node import *\nfrom .speech_to_text_step_node import BaseSpeechToTextNode\nfrom .start_node import *\nfrom .text_to_speech_step_node.impl.base_text_to_speech_node import BaseTextToSpeechNode\nfrom .text_to_video_step_node.impl.base_text_to_video_node import BaseTextToVideoNode\nfrom .tool_lib_node import *\nfrom .tool_node import *\nfrom .tool_workflow_lib_node import BaseToolWorkflowLibNodeNode\nfrom .variable_aggregation_node.impl.base_variable_aggregation_node import BaseVariableAggregationNode\nfrom .variable_assign_node import BaseVariableAssignNode\nfrom .variable_splitting_node import BaseVariableSplittingNode\nfrom .video_understand_step_node import BaseVideoUnderstandNode\nfrom .document_split_node import BaseDocumentSplitNode\nfrom .tool_start_node import BaseToolStartStepNode\n\nnode_list = [BaseStartStepNode, BaseChatNode, BaseSearchKnowledgeNode, BaseSearchDocumentNode, BaseQuestionNode,\n             BaseConditionNode, BaseReplyNode,\n             BaseToolNodeNode, BaseToolLibNodeNode, BaseRerankerNode, BaseApplicationNode,\n             BaseDocumentExtractNode,\n             BaseImageUnderstandNode, BaseFormNode, BaseSpeechToTextNode, BaseTextToSpeechNode,\n             BaseImageGenerateNode, BaseVariableAssignNode, BaseMcpNode, BaseTextToVideoNode, BaseImageToVideoNode,\n             BaseVideoUnderstandNode,\n             BaseIntentNode, BaseLoopNode, BaseLoopStartStepNode,\n             BaseLoopContinueNode,\n             BaseLoopBreakNode, BaseVariableSplittingNode, BaseParameterExtractionNode, BaseVariableAggregationNode,\n             BaseDataSourceLocalNode, BaseDataSourceWebNode, BaseKnowledgeWriteNode, BaseDocumentSplitNode,\n             BaseToolStartStepNode, BaseToolWorkflowLibNodeNode]\n\nnode_map = {n.type: {w: n for w in n.support} for n in node_list}\n\n\ndef get_node(node_type, workflow_model):\n    return node_map.get(node_type).get(workflow_model)\n"
  },
  {
    "path": "apps/application/flow/step_node/ai_chat_step_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:29\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_chat_node.py\n    @date：2024/6/4 13:58\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass ChatNodeSerializer(serializers.Serializer):\n    model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_(\"Model id\"))\n    model_id_type = serializers.CharField(required=False, default='custom', label=_(\"Model id type\"))\n    model_id_reference = serializers.ListField(required=False, child=serializers.CharField(), allow_empty=True,\n                                               label=_(\"Reference Field\"))\n    system = serializers.CharField(required=False, allow_blank=True, allow_null=True,\n                                   label=_(\"Role Setting\"))\n    prompt = serializers.CharField(required=True, label=_(\"Prompt word\"))\n    # 多轮对话数量\n    dialogue_number = serializers.IntegerField(required=True, label=_(\"Number of multi-round conversations\"))\n\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    model_params_setting = serializers.DictField(required=False,\n                                                 label=_(\"Model parameter settings\"))\n    model_setting = serializers.DictField(required=False,\n                                          label='Model settings')\n    dialogue_type = serializers.CharField(required=False, allow_blank=True, allow_null=True,\n                                          label=_(\"Context Type\"))\n    mcp_servers = serializers.JSONField(required=False, label=_(\"MCP Server\"))\n    mcp_tool_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_(\"MCP Tool ID\"))\n    mcp_tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True,\n                                         label=_(\"MCP Tool IDs\"), )\n    mcp_source = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_(\"MCP Source\"))\n\n    tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True,\n                                     label=_(\"Tool IDs\"), )\n    application_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True,\n                                            label=_(\"App IDs\"), )\n    skill_tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True,\n                                           label=_(\"Skill IDs\"), )\n    mcp_output_enable = serializers.BooleanField(required=False, default=True, label=_(\"Whether to enable MCP output\"))\n\n\nclass IChatNode(INode):\n    type = 'ai-chat-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP,\n               WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return ChatNodeSerializer\n\n    def _run(self):\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL,\n            WorkflowMode.TOOL_LOOP].__contains__(\n            self.workflow_manage.flow.workflow_mode):\n            return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,\n                                **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None})\n        else:\n            return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id,\n                chat_record_id,\n                model_params_setting=None,\n                model_id_type=None,\n                model_id_reference=None,\n                dialogue_type=None,\n                model_setting=None,\n                mcp_servers=None,\n                mcp_tool_id=None,\n                mcp_tool_ids=None,\n                mcp_source=None,\n                tool_ids=None,\n                application_ids=None,\n                skill_tool_ids=None,\n                mcp_output_enable=True,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/ai_chat_step_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:34\n    @desc:\n\"\"\"\nfrom .base_chat_node import BaseChatNode\n"
  },
  {
    "path": "apps/application/flow/step_node/ai_chat_step_node/impl/base_chat_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_question_node.py\n    @date：2024/6/4 14:30\n    @desc:\n\"\"\"\nimport json\nimport re\nimport time\nfrom functools import reduce\nfrom typing import List, Dict\n\nfrom application.flow.i_step_node import NodeResult, INode\nfrom application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode\nfrom application.flow.tools import Reasoning, mcp_response_generator\nfrom application.models import Application, ApplicationApiKey, ApplicationAccessToken\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.rsa_util import rsa_long_decrypt\nfrom common.utils.shared_resource_auth import filter_authorized_ids\nfrom common.utils.tool_code import ToolExecutor\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext as _\nfrom langchain_core.messages import BaseMessage, AIMessage, HumanMessage, SystemMessage\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model_credential, get_model_instance_by_model_workspace_id\nfrom tools.models import Tool\n\n\ndef _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str,\n                   reasoning_content: str):\n    chat_model = node_variable.get('chat_model')\n    message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))\n    answer_tokens = chat_model.get_num_tokens(answer)\n    node.context['message_tokens'] = message_tokens\n    node.context['answer_tokens'] = answer_tokens\n    node.context['answer'] = answer\n    node.context['question'] = node_variable['question']\n    node.context['run_time'] = time.time() - node.context['start_time']\n    node.context['reasoning_content'] = reasoning_content\n    if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):\n        node.answer_text = answer\n\n\ndef write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据 (流式)\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result')\n    answer = ''\n    reasoning_content = ''\n    model_setting = node.context.get('model_setting',\n                                     {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',\n                                      'reasoning_content_start': '<think>'})\n    reasoning = Reasoning(model_setting.get('reasoning_content_start', '<think>'),\n                          model_setting.get('reasoning_content_end', '</think>'))\n    response_reasoning_content = False\n\n    for chunk in response:\n        if workflow.is_the_task_interrupted():\n            break\n        reasoning_chunk = reasoning.get_reasoning_content(chunk)\n        content_chunk = reasoning_chunk.get('content')\n        if 'reasoning_content' in chunk.additional_kwargs:\n            response_reasoning_content = True\n            reasoning_content_chunk = chunk.additional_kwargs.get('reasoning_content', '')\n        else:\n            reasoning_content_chunk = reasoning_chunk.get('reasoning_content')\n        answer += content_chunk\n        if reasoning_content_chunk is None:\n            reasoning_content_chunk = ''\n        reasoning_content += reasoning_content_chunk\n        yield {'content': content_chunk,\n               'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable',\n                                                                                 False) else ''}\n\n    reasoning_chunk = reasoning.get_end_reasoning_content()\n    answer += reasoning_chunk.get('content')\n    reasoning_content_chunk = \"\"\n    if not response_reasoning_content:\n        reasoning_content_chunk = reasoning_chunk.get(\n            'reasoning_content')\n    yield {'content': reasoning_chunk.get('content'),\n           'reasoning_content': reasoning_content_chunk if model_setting.get('reasoning_content_enable',\n                                                                             False) else ''}\n    _write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content)\n\n\ndef write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点实例对象\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result')\n    model_setting = node.context.get('model_setting',\n                                     {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',\n                                      'reasoning_content_start': '<think>'})\n    reasoning = Reasoning(model_setting.get('reasoning_content_start'), model_setting.get('reasoning_content_end'))\n    reasoning_result = reasoning.get_reasoning_content(response)\n    reasoning_result_end = reasoning.get_end_reasoning_content()\n    content = reasoning_result.get('content') + reasoning_result_end.get('content')\n    meta = {**response.response_metadata, **response.additional_kwargs}\n    if 'reasoning_content' in meta:\n        reasoning_content = (meta.get('reasoning_content', '') or '')\n    else:\n        reasoning_content = (reasoning_result.get('reasoning_content') or '') + (\n                reasoning_result_end.get('reasoning_content') or '')\n    _write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content)\n\n\ndef get_default_model_params_setting(model_id):\n    model = QuerySet(Model).filter(id=model_id).first()\n    credential = get_model_credential(model.provider, model.model_type, model.model_name)\n    model_params_setting = credential.get_model_params_setting_form(\n        model.model_name).get_default_form_data()\n    return model_params_setting\n\n\ndef get_node_message(chat_record, runtime_node_id):\n    node_details = chat_record.get_node_details_runtime_node_id(runtime_node_id)\n    if node_details is None:\n        return []\n    return [HumanMessage(node_details.get('question')), AIMessage(node_details.get('answer'))]\n\n\ndef get_workflow_message(chat_record):\n    return [chat_record.get_human_message(), chat_record.get_ai_message()]\n\n\ndef get_message(chat_record, dialogue_type, runtime_node_id):\n    return get_node_message(chat_record, runtime_node_id) if dialogue_type == 'NODE' else get_workflow_message(\n        chat_record)\n\n\nclass BaseChatNode(IChatNode):\n    def save_context(self, details, workflow_manage):\n        self.context['answer'] = details.get('answer')\n        self.context['question'] = details.get('question')\n        self.context['reasoning_content'] = details.get('reasoning_content')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n\n    def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id,\n                model_params_setting=None,\n                model_id_type=None,\n                model_id_reference=None,\n                dialogue_type=None,\n                model_setting=None,\n                mcp_servers=None,\n                mcp_tool_id=None,\n                mcp_tool_ids=None,\n                mcp_source=None,\n                tool_ids=None,\n                application_ids=None,\n                skill_tool_ids=None,\n                mcp_output_enable=True,\n                **kwargs) -> NodeResult:\n        if dialogue_type is None:\n            dialogue_type = 'WORKFLOW'\n\n        if model_id_type == 'reference' and model_id_reference:\n\n            reference_data = self.workflow_manage.get_reference_field(\n                model_id_reference[0],\n                model_id_reference[1:],\n            )\n\n            if reference_data and isinstance(reference_data, dict):\n                model_id = reference_data.get('model_id', model_id)\n                model_params_setting = reference_data.get('model_params_setting')\n\n        if  model_params_setting is None and model_id:\n            model_params_setting = get_default_model_params_setting(model_id)\n\n        if model_setting is None:\n            model_setting = {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',\n                             'reasoning_content_start': '<think>'}\n        self.context['model_setting'] = model_setting\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,\n                                                              **model_params_setting)\n        history_message = self.get_history_message(history_chat_record, dialogue_number, dialogue_type,\n                                                   self.runtime_node_id)\n        self.context['history_message'] = [{'content': message.content, 'role': message.type} for message in\n                                           (history_message if history_message is not None else [])]\n        question = self.generate_prompt_question(prompt)\n        self.context['question'] = question.content\n        system = self.workflow_manage.generate_prompt(system)\n        self.context['system'] = system\n        message_list = self.generate_message_list(system, prompt, history_message)\n        self.context['message_list'] = message_list\n\n        # 过滤tool_id\n        all_tool_ids = list(set(\n            (mcp_tool_ids or []) +\n            (tool_ids or []) +\n            (skill_tool_ids or []) +\n            ([mcp_tool_id] if mcp_tool_id else [])\n        ))\n        authorized_set = set(filter_authorized_ids('tool', all_tool_ids, workspace_id))\n\n        mcp_tool_ids = [i for i in (mcp_tool_ids or []) if i in authorized_set]\n        tool_ids = [i for i in (tool_ids or []) if i in authorized_set]\n        skill_tool_ids = [i for i in (skill_tool_ids or []) if i in authorized_set]\n        mcp_tool_id = mcp_tool_id if (mcp_tool_id and mcp_tool_id in authorized_set) else None\n        # 处理 MCP 请求\n        mcp_result = self._handle_mcp_request(\n            mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids,\n            application_ids, skill_tool_ids, mcp_output_enable,\n            chat_model, message_list, history_message, question, chat_id\n        )\n        if mcp_result:\n            return mcp_result\n\n        if stream:\n            r = chat_model.stream(message_list)\n            return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,\n                               'question': question.content}, {},\n                              _write_context=write_context_stream)\n        else:\n            r = chat_model.invoke(message_list)\n            return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,\n                               'history_message': [{'content': message.content, 'role': message.type} for message in\n                                                   (history_message if history_message is not None else [])],\n                               'question': question.content}, {},\n                              _write_context=write_context)\n\n    def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids,\n                            application_ids, skill_tool_ids,\n                            mcp_output_enable, chat_model, message_list, history_message, question, chat_id):\n\n        mcp_servers_config = {}\n\n        # 迁移过来mcp_source是None\n        if mcp_source is None:\n            mcp_source = 'custom'\n        # 兼容老数据\n        if not mcp_tool_ids:\n            mcp_tool_ids = []\n        if mcp_tool_id:\n            mcp_tool_ids = list(set(mcp_tool_ids + [mcp_tool_id]))\n        if mcp_source == 'custom' and mcp_servers and '\"stdio\"' not in mcp_servers:\n            mcp_servers_config = json.loads(mcp_servers)\n            mcp_servers_config = self.handle_variables(mcp_servers_config)\n        elif mcp_tool_ids:\n            mcp_tools = QuerySet(Tool).filter(id__in=mcp_tool_ids).values()\n            for mcp_tool in mcp_tools:\n                if mcp_tool and mcp_tool['is_active']:\n                    mcp_servers_config = {**mcp_servers_config, **json.loads(mcp_tool['code'])}\n                    mcp_servers_config = self.handle_variables(mcp_servers_config)\n        tool_init_params = {}\n        if tool_ids and len(tool_ids) > 0:  # 如果有工具ID，则将其转换为MCP\n            self.context['tool_ids'] = tool_ids\n            for tool_id in tool_ids:\n                tool = QuerySet(Tool).filter(id=tool_id).first()\n                if not tool.is_active:\n                    continue\n                executor = ToolExecutor()\n                if tool.init_params is not None:\n                    params = json.loads(rsa_long_decrypt(tool.init_params))\n                    tool_init_params = json.loads(rsa_long_decrypt(tool.init_params))\n                else:\n                    params = {}\n                tool_config = executor.get_tool_mcp_config(tool, params)\n\n                mcp_servers_config[str(tool.id)] = tool_config\n\n        if application_ids and len(application_ids) > 0:\n            self.context['application_ids'] = application_ids\n            for application_id in application_ids:\n                app = QuerySet(Application).filter(id=application_id, is_publish=True).first()\n                if app is None:\n                    continue\n                app_key = QuerySet(ApplicationApiKey).filter(application_id=application_id, is_active=True).first()\n                if app_key is not None:\n                    api_key = app_key.secret_key\n                    application_access_token = QuerySet(ApplicationAccessToken).filter(\n                        application_id=app_key.application_id\n                    ).first()\n                    if application_access_token is not None and application_access_token.authentication:\n                        raise AppApiException(\n                            500,\n                            _('Agent 【{name}】 access token authentication is not supported for agent tool').format(\n                                name=app.name)\n                        )\n                else:\n                    raise AppApiException(\n                        500,\n                        _('Agent Key is required for agent tool 【{name}】').format(name=app.name)\n                    )\n                executor = ToolExecutor()\n                app_config = executor.get_app_mcp_config(api_key)\n                mcp_servers_config[app.name] = app_config\n\n        if skill_tool_ids and len(skill_tool_ids) > 0:\n            self.context['skill_tool_ids'] = skill_tool_ids\n            skill_file_items = []\n\n            for tool_id in skill_tool_ids:\n                tool = QuerySet(Tool).filter(id=tool_id, is_active=True).first()\n                if tool is None or tool.is_active is False:\n                    continue\n                init_params_default_value = {i[\"field\"]: i.get('default_value') for i in tool.init_field_list}\n                if tool.init_params is not None:\n                    params = init_params_default_value | json.loads(rsa_long_decrypt(tool.init_params))\n                else:\n                    params = init_params_default_value\n\n                skill_file_items.append({\n                    'tool_id': str(tool.id),\n                    'file_id': tool.code,\n                    'params': params\n                })\n            mcp_servers_config['skills'] = skill_file_items\n\n        if len(mcp_servers_config) > 0:\n            # 安全获取 application\n            application_id = None\n            if (self.workflow_manage and\n                    self.workflow_manage.work_flow_post_handler and\n                    self.workflow_manage.work_flow_post_handler.chat_info):\n                application_id = self.workflow_manage.work_flow_post_handler.chat_info.application.id\n            knowledge_id = self.workflow_params.get('knowledge_id')\n            source_id = application_id or knowledge_id\n            source_type = 'APPLICATION' if application_id else 'KNOWLEDGE'\n            r = mcp_response_generator(chat_model, message_list, json.dumps(mcp_servers_config), mcp_output_enable,\n                                       tool_init_params, source_id, source_type, chat_id)\n            return NodeResult(\n                {'result': r, 'chat_model': chat_model, 'message_list': message_list,\n                 'history_message': [{'content': message.content, 'role': message.type} for message in\n                                     (history_message if history_message is not None else [])],\n                 'question': question.content}, {},\n                _write_context=write_context_stream)\n\n        return None\n\n    def handle_variables(self, tool_params):\n        # 处理参数中的变量\n        for k, v in tool_params.items():\n            if type(v) == str:\n                tool_params[k] = self.workflow_manage.generate_prompt(tool_params[k])\n            if type(v) == dict:\n                self.handle_variables(v)\n            if (type(v) == list) and (type(v[0]) == str):\n                tool_params[k] = self.get_reference_content(v)\n        return tool_params\n\n    def get_reference_content(self, fields: List[str]):\n        return str(self.workflow_manage.get_reference_field(\n            fields[0],\n            fields[1:]))\n\n    @staticmethod\n    def get_history_message(history_chat_record, dialogue_number, dialogue_type, runtime_node_id):\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = reduce(lambda x, y: [*x, *y], [\n            get_message(history_chat_record[index], dialogue_type, runtime_node_id)\n            for index in\n            range(start_index if start_index > 0 else 0, len(history_chat_record))], [])\n        for message in history_message:\n            if isinstance(message.content, str):\n                message.content = re.sub('<form_rander>[\\d\\D]*?<\\/form_rander>', '', message.content)\n        return history_message\n\n    def generate_prompt_question(self, prompt):\n        return HumanMessage(self.workflow_manage.generate_prompt(prompt))\n\n    def generate_message_list(self, system: str, prompt: str, history_message):\n        if system is not None and len(system) > 0:\n            return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message,\n                    HumanMessage(self.workflow_manage.generate_prompt(prompt))]\n        else:\n            return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))]\n\n    @staticmethod\n    def reset_message_list(message_list: List[BaseMessage], answer_text):\n        result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for\n                  message\n                  in\n                  message_list]\n        result.append({'role': 'ai', 'content': answer_text})\n        return result\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'system': self.context.get('system'),\n            'history_message': self.context.get('history_message'),\n            'question': self.context.get('question'),\n            'answer': self.context.get('answer'),\n            'reasoning_content': self.context.get('reasoning_content'),\n            'enableException': self.node.properties.get('enableException'),\n            'type': self.node.type,\n            'message_tokens': self.context.get('message_tokens'),\n            'answer_tokens': self.context.get('answer_tokens'),\n            'status': self.status,\n            'err_message': self.err_message\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/application_node/__init__.py",
    "content": "# coding=utf-8\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/application_node/i_application_node.py",
    "content": "# coding=utf-8\nfrom typing import Type\n\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\nfrom django.utils.translation import gettext_lazy as _\n\nfrom application.models import ChatSourceChoices\n\n\nclass ApplicationNodeSerializer(serializers.Serializer):\n    application_id = serializers.CharField(required=True, label=_(\"Application ID\"))\n    question_reference_address = serializers.ListField(required=True,\n                                                       label=_(\"User Questions\"))\n    api_input_field_list = serializers.ListField(required=False, label=_(\"API Input Fields\"))\n    user_input_field_list = serializers.ListField(required=False,\n                                                  label=_(\"User Input Fields\"))\n    image_list = serializers.ListField(required=False, label=_(\"picture\"))\n    document_list = serializers.ListField(required=False, label=_(\"document\"))\n    audio_list = serializers.ListField(required=False, label=_(\"Audio\"))\n    video_list = serializers.ListField(required=False, label=_(\"Video\"))\n    child_node = serializers.DictField(required=False, allow_null=True,\n                                       label=_(\"Child Nodes\"))\n    node_data = serializers.DictField(required=False, allow_null=True, label=_(\"Form Data\"))\n\n\nclass IApplicationNode(INode):\n    type = 'application-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return ApplicationNodeSerializer\n\n    def _run(self):\n        question = self.workflow_manage.get_reference_field(\n            self.node_params_serializer.data.get('question_reference_address')[0],\n            self.node_params_serializer.data.get('question_reference_address')[1:])\n        kwargs = {}\n        for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []):\n            value = api_input_field.get('value', [''])[0] if api_input_field.get('value') else ''\n            kwargs[api_input_field['variable']] = self.workflow_manage.get_reference_field(value,\n                                                                                           api_input_field['value'][\n                                                                                           1:]) if value != '' else ''\n\n        for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []):\n            value = user_input_field.get('value', [''])[0] if user_input_field.get('value') else ''\n            kwargs[user_input_field['field']] = self.workflow_manage.get_reference_field(value,\n                                                                                         user_input_field['value'][\n                                                                                         1:]) if value != '' else ''\n        # 判断是否包含这个属性\n        app_document_list = self.node_params_serializer.data.get('document_list', [])\n        if app_document_list and len(app_document_list) > 0:\n            app_document_list = self.workflow_manage.get_reference_field(\n                app_document_list[0],\n                app_document_list[1:])\n            for document in app_document_list:\n                if 'file_id' not in document:\n                    raise ValueError(\n                        _(\"Parameter value error: The uploaded document lacks file_id, and the document upload fails\"))\n        app_image_list = self.node_params_serializer.data.get('image_list', [])\n        if app_image_list and len(app_image_list) > 0:\n            app_image_list = self.workflow_manage.get_reference_field(\n                app_image_list[0],\n                app_image_list[1:])\n            for image in app_image_list:\n                if 'file_id' not in image:\n                    raise ValueError(\n                        _(\"Parameter value error: The uploaded image lacks file_id, and the image upload fails\"))\n\n        app_audio_list = self.node_params_serializer.data.get('audio_list', [])\n        if app_audio_list and len(app_audio_list) > 0:\n            app_audio_list = self.workflow_manage.get_reference_field(\n                app_audio_list[0],\n                app_audio_list[1:])\n            for audio in app_audio_list:\n                if 'file_id' not in audio:\n                    raise ValueError(\n                        _(\"Parameter value error: The uploaded audio lacks file_id, and the audio upload fails.\"))\n        app_video_list = self.node_params_serializer.data.get('video_list', [])\n        if app_video_list and len(app_video_list) > 0:\n            app_video_list = self.workflow_manage.get_reference_field(\n                app_video_list[0],\n                app_video_list[1:]\n            )\n            for video in app_video_list:\n                if 'file_id' not in video:\n                    raise ValueError(\n                        _(\"Parameter value error: The uploaded video lacks file_id, and the video upload fails.\"))\n        return self.execute(**{**self.flow_params_serializer.data, **self.node_params_serializer.data},\n                            app_document_list=app_document_list, app_image_list=app_image_list,\n                            app_audio_list=app_audio_list,\n                            app_video_list=app_video_list,\n                            ip_address=self.workflow_params.get('ip_address') or '-',\n                            source=self.workflow_params.get('source') or {\"type\": ChatSourceChoices.ONLINE.value},\n                            message=str(question), **kwargs)\n\n    def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat, client_id, client_type,\n                app_document_list=None, app_image_list=None, app_audio_list=None, app_video_list=None, child_node=None,\n                node_data=None,\n                ip_address=None,\n                source=None,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/application_node/impl/__init__.py",
    "content": "# coding=utf-8\nfrom .base_application_node import BaseApplicationNode\n"
  },
  {
    "path": "apps/application/flow/step_node/application_node/impl/base_application_node.py",
    "content": "# coding=utf-8\nimport json\nimport re\nimport time\nimport uuid\nfrom typing import Dict, List\nfrom django.utils.translation import gettext as _\nfrom application.flow.common import Answer\nfrom application.flow.i_step_node import NodeResult, INode\nfrom application.flow.step_node.application_node.i_application_node import IApplicationNode\nfrom application.models import Chat, ChatSourceChoices\n\n\ndef string_to_uuid(input_str):\n    return str(uuid.uuid5(uuid.NAMESPACE_DNS, input_str))\n\n\ndef _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict):\n    return node_variable.get('is_interrupt_exec', False)\n\n\ndef _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str,\n                   reasoning_content: str):\n    result = node_variable.get('result')\n    node.context['application_node_dict'] = node_variable.get('application_node_dict')\n    node.context['node_dict'] = node_variable.get('node_dict', {})\n    node.context['is_interrupt_exec'] = node_variable.get('is_interrupt_exec')\n    node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0)\n    node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0)\n    node.context['answer'] = answer\n    node.context['result'] = answer\n    node.context['reasoning_content'] = reasoning_content\n    node.context['question'] = node_variable['question']\n    node.context['run_time'] = time.time() - node.context['start_time']\n    if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):\n        node.answer_text = answer\n\n\ndef write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据 (流式)\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result')\n    answer = ''\n    reasoning_content = ''\n    usage = {}\n    node_child_node = {}\n    application_node_dict = node.context.get('application_node_dict', {})\n    is_interrupt_exec = False\n    for chunk in response:\n        # 先把流转成字符串\n        response_content = chunk.decode('utf-8')[6:]\n        response_content = json.loads(response_content)\n        content = (response_content.get('content', '') or '')\n        runtime_node_id = response_content.get('runtime_node_id', '')\n        chat_record_id = response_content.get('chat_record_id', '')\n        child_node = response_content.get('child_node')\n        view_type = response_content.get('view_type')\n        node_type = response_content.get('node_type')\n        real_node_id = response_content.get('real_node_id')\n        node_is_end = response_content.get('node_is_end', False)\n        _reasoning_content = (response_content.get('reasoning_content', '') or '')\n        if node_type == 'form-node':\n            is_interrupt_exec = True\n        answer += content\n        reasoning_content += _reasoning_content\n        node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,\n                           'child_node': child_node}\n\n        if real_node_id is not None:\n            application_node = application_node_dict.get(real_node_id, None)\n            if application_node is None:\n\n                application_node_dict[real_node_id] = {'content': content,\n                                                       'runtime_node_id': runtime_node_id,\n                                                       'chat_record_id': chat_record_id,\n                                                       'child_node': child_node,\n                                                       'index': len(application_node_dict),\n                                                       'view_type': view_type,\n                                                       'reasoning_content': _reasoning_content}\n            else:\n                application_node['content'] += content\n                application_node['reasoning_content'] += _reasoning_content\n\n        yield {'content': content,\n               'node_type': node_type,\n               'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,\n               'reasoning_content': _reasoning_content,\n               'child_node': child_node,\n               'real_node_id': real_node_id,\n               'node_is_end': node_is_end,\n               'view_type': view_type}\n        usage = response_content.get('usage', {})\n    node_variable['result'] = {'usage': usage}\n    node_variable['is_interrupt_exec'] = is_interrupt_exec\n    node_variable['child_node'] = node_child_node\n    node_variable['application_node_dict'] = application_node_dict\n    _write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content)\n\n\ndef write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点实例对象\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result', {}).get('data', {})\n    node_variable['result'] = {'usage': {'completion_tokens': response.get('completion_tokens'),\n                                         'prompt_tokens': response.get('prompt_tokens')}}\n    answer = response.get('content', '') or \"抱歉，没有查找到相关内容，请重新描述您的问题或提供更多信息。\"\n    reasoning_content = response.get('reasoning_content', '')\n    answer_list = response.get('answer_list', [])\n    node_variable['application_node_dict'] = {answer.get('real_node_id'): {**answer, 'index': index} for answer, index\n                                              in\n                                              zip(answer_list, range(len(answer_list)))}\n    _write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content)\n\n\ndef reset_application_node_dict(application_node_dict, runtime_node_id, node_data):\n    try:\n        if application_node_dict is None:\n            return\n        for key in application_node_dict:\n            application_node = application_node_dict[key]\n            if application_node.get('runtime_node_id') == runtime_node_id:\n                content: str = application_node.get('content')\n                match = re.search('<form_rander>.*?</form_rander>', content)\n                if match:\n                    form_setting_str = match.group().replace('<form_rander>', '').replace('</form_rander>', '')\n                    form_setting = json.loads(form_setting_str)\n                    form_setting['is_submit'] = True\n                    form_setting['form_data'] = node_data\n                    value = f'<form_rander>{json.dumps(form_setting)}</form_rander>'\n                    res = re.sub('<form_rander>.*?</form_rander>',\n                                 '${value}', content)\n                    application_node['content'] = res.replace('${value}', value)\n    except Exception as e:\n        pass\n\n\nclass BaseApplicationNode(IApplicationNode):\n    def get_answer_list(self) -> List[Answer] | None:\n        if self.answer_text is None:\n            return None\n        application_node_dict = self.context.get('application_node_dict')\n        if application_node_dict is None or len(application_node_dict) == 0:\n            return [\n                Answer(self.answer_text, self.view_type, self.runtime_node_id, self.workflow_params['chat_record_id'],\n                       self.context.get('child_node'), self.runtime_node_id, '')]\n        else:\n            return [Answer(n.get('content'), n.get('view_type'), self.runtime_node_id,\n                           self.workflow_params['chat_record_id'], {'runtime_node_id': n.get('runtime_node_id'),\n                                                                    'chat_record_id': n.get('chat_record_id')\n                               , 'child_node': n.get('child_node')}, n.get('real_node_id'),\n                           n.get('reasoning_content', ''))\n                    for n in\n                    sorted(application_node_dict.values(), key=lambda item: item.get('index'))]\n\n    def save_context(self, details, workflow_manage):\n        self.context['answer'] = details.get('answer')\n        self.context['result'] = details.get('answer')\n        self.context['question'] = details.get('question')\n        self.context['type'] = details.get('type')\n        self.context['reasoning_content'] = details.get('reasoning_content')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n\n    def get_chat_asker(self, kwargs):\n        asker = kwargs.get('asker')\n        if asker:\n            if isinstance(asker, dict):\n                return asker\n            return {'username': asker}\n        return self.workflow_manage.work_flow_post_handler.chat_info.get_chat_user()\n\n    def execute(self, application_id, message, chat_id, chat_record_id, stream, re_chat,\n                chat_user_id,\n                chat_user_type,\n                app_document_list=None, app_image_list=None, app_audio_list=None, app_video_list=None, child_node=None,\n                node_data=None,\n                ip_address=None,\n                source=None,\n                **kwargs) -> NodeResult:\n        from chat.serializers.chat import ChatSerializers\n        if application_id == self.workflow_manage.get_body().get('application_id'):\n            raise Exception(_(\"The sub application cannot use the current node\"))\n        # 生成嵌入应用的chat_id\n        current_chat_id = string_to_uuid(chat_id + application_id)\n        Chat.objects.get_or_create(id=current_chat_id, defaults={\n            'application_id': application_id,\n            'abstract': message[0:1024],\n            'chat_user_id': chat_user_id,\n            'chat_user_type': chat_user_type,\n            'ip_address': ip_address,\n            'source': source,\n            'asker': self.get_chat_asker(kwargs)\n        })\n        if app_document_list is None:\n            app_document_list = []\n        if app_image_list is None:\n            app_image_list = []\n        if app_audio_list is None:\n            app_audio_list = []\n        if app_video_list is None:\n            app_video_list = []\n        runtime_node_id = None\n        record_id = None\n        child_node_value = None\n        if child_node is not None:\n            runtime_node_id = child_node.get('runtime_node_id')\n            record_id = child_node.get('chat_record_id')\n            child_node_value = child_node.get('child_node')\n            application_node_dict = self.context.get('application_node_dict')\n            reset_application_node_dict(application_node_dict, runtime_node_id, node_data)\n        response = ChatSerializers(data={\n            \"chat_id\": current_chat_id,\n            \"chat_user_id\": chat_user_id,\n            'chat_user_type': chat_user_type,\n            'application_id': application_id,\n            'ip_address': ip_address,\n            'source': source,\n            'debug': False\n        }).chat(instance=\n                {'message': message,\n                 're_chat': re_chat,\n                 'stream': stream,\n                 'document_list': [*app_document_list],\n                 'image_list': [*app_image_list],\n                 'audio_list': [*app_audio_list],\n                 'video_list': [*app_video_list],\n                 'runtime_node_id': runtime_node_id,\n                 'chat_record_id': record_id,\n                 'child_node': child_node_value,\n                 'node_data': node_data,\n                 'form_data': kwargs}\n                )\n\n        if response.status_code == 200:\n            if stream:\n                content_generator = response.streaming_content\n                return NodeResult({'result': content_generator, 'question': message}, {},\n                                  _write_context=write_context_stream, _is_interrupt=_is_interrupt_exec)\n            else:\n                data = json.loads(response.content)\n                return NodeResult({'result': data, 'question': message}, {},\n                                  _write_context=write_context, _is_interrupt=_is_interrupt_exec)\n\n    def get_details(self, index: int, **kwargs):\n        global_fields = []\n        for api_input_field in self.node_params_serializer.data.get('api_input_field_list', []):\n            value = api_input_field.get('value', [''])[0] if api_input_field.get('value') else ''\n            global_fields.append({\n                'label': api_input_field['variable'],\n                'key': api_input_field['variable'],\n                'value': self.workflow_manage.get_reference_field(\n                    value,\n                    api_input_field['value'][1:]\n                ) if value != '' else ''\n            })\n\n        for user_input_field in self.node_params_serializer.data.get('user_input_field_list', []):\n            value = user_input_field.get('value', [''])[0] if user_input_field.get('value') else ''\n            global_fields.append({\n                'label': user_input_field['label'],\n                'key': user_input_field['field'],\n                'value': self.workflow_manage.get_reference_field(\n                    value,\n                    user_input_field['value'][1:]\n                ) if value != '' else ''\n            })\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            \"info\": self.node.properties.get('node_data'),\n            'run_time': self.context.get('run_time'),\n            'question': self.context.get('question'),\n            'answer': self.context.get('answer'),\n            'reasoning_content': self.context.get('reasoning_content'),\n            'type': self.node.type,\n            'message_tokens': self.context.get('message_tokens'),\n            'answer_tokens': self.context.get('answer_tokens'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'global_fields': global_fields,\n            'document_list': self.workflow_manage.document_list,\n            'image_list': self.workflow_manage.image_list,\n            'audio_list': self.workflow_manage.audio_list,\n            'video_list': self.workflow_manage.video_list,\n            'application_node_dict': self.context.get('application_node_dict'),\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/condition_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/6/7 14:43\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/condition_node/i_condition_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_condition_node.py\n    @date：2024/6/7 9:54\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode\n\n\nclass ConditionSerializer(serializers.Serializer):\n    compare = serializers.CharField(required=True, label=_(\"Comparator\"))\n    value = serializers.CharField(required=True, label=_(\"value\"))\n    field = serializers.ListField(required=True, label=_(\"Fields\"))\n\n\nclass ConditionBranchSerializer(serializers.Serializer):\n    id = serializers.CharField(required=True, label=_(\"Branch id\"))\n    type = serializers.CharField(required=True, label=_(\"Branch Type\"))\n    condition = serializers.CharField(required=True, label=_(\"Condition or|and\"))\n    conditions = ConditionSerializer(many=True)\n\n\nclass ConditionNodeParamsSerializer(serializers.Serializer):\n    branch = ConditionBranchSerializer(many=True)\n\n\nclass IConditionNode(INode):\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return ConditionNodeParamsSerializer\n\n    type = 'condition-node'\n\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n"
  },
  {
    "path": "apps/application/flow/step_node/condition_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:35\n    @desc:\n\"\"\"\nfrom .base_condition_node import BaseConditionNode\n"
  },
  {
    "path": "apps/application/flow/step_node/condition_node/impl/base_condition_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_condition_node.py\n    @date：2024/6/7 11:29\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.compare import compare_handle_list\nfrom application.flow.step_node.condition_node.i_condition_node import IConditionNode\n\n\nclass BaseConditionNode(IConditionNode):\n    def save_context(self, details, workflow_manage):\n        self.context['branch_id'] = details.get('branch_id')\n        self.context['branch_name'] = details.get('branch_name')\n        self.context['exception_message'] = details.get('err_message')\n\n    def execute(self, **kwargs) -> NodeResult:\n        branch_list = self.node_params_serializer.data['branch']\n        branch = self._execute(branch_list)\n        r = NodeResult({'branch_id': branch.get('id'), 'branch_name': branch.get('type')}, {})\n        return r\n\n    def _execute(self, branch_list: List):\n        for branch in branch_list:\n            if self.branch_assertion(branch):\n                return branch\n\n    def branch_assertion(self, branch):\n        condition_list = [self.assertion(row.get('field'), row.get('compare'), row.get('value')) for row in\n                          branch.get('conditions')]\n        condition = branch.get('condition')\n        return all(condition_list) if condition == 'and' else any(condition_list)\n\n    def assertion(self, field_list: List[str], compare: str, value):\n        try:\n            value = self.workflow_manage.generate_prompt(value)\n        except Exception as e:\n            pass\n        field_value = None\n        try:\n            field_value = self.workflow_manage.get_reference_field(field_list[0], field_list[1:])\n        except  Exception as e:\n            pass\n        for compare_handler in compare_handle_list:\n            if compare_handler.support(field_list[0], field_list[1:], field_value, compare, value):\n                return compare_handler.compare(field_value, compare, value)\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'branch_id': self.context.get('branch_id'),\n            'branch_name': self.context.get('branch_name'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/data_source_local_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/11/11 10:06\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/flow/step_node/data_source_local_node/i_data_source_local_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： i_data_source_local_node.py\n    @date：2025/11/11 10:06\n    @desc:\n\"\"\"\nfrom abc import abstractmethod\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass DataSourceLocalNodeParamsSerializer(serializers.Serializer):\n    file_type_list = serializers.ListField(child=serializers.CharField(label=('')), label='')\n    file_size_limit = serializers.IntegerField(required=True, label=_(\"Number of uploaded files\"))\n    file_count_limit = serializers.IntegerField(required=True, label=_(\"Upload file size\"))\n\n\nclass IDataSourceLocalNode(INode):\n    type = 'data-source-local-node'\n\n    @staticmethod\n    @abstractmethod\n    def get_form_list(node):\n        pass\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return DataSourceLocalNodeParamsSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, file_type_list, file_size_limit, file_count_limit, **kwargs) -> NodeResult:\n        pass\n\n    support = [WorkflowMode.KNOWLEDGE]\n"
  },
  {
    "path": "apps/application/flow/step_node/data_source_local_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/11/11 10:08\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/flow/step_node/data_source_local_node/impl/base_data_source_local_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： base_data_source_local_node.py\n    @date：2025/11/11 10:30\n    @desc:\n\"\"\"\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.data_source_local_node.i_data_source_local_node import IDataSourceLocalNode\nfrom common import forms\nfrom common.forms import BaseForm\n\n\nclass BaseDataSourceLocalNodeForm(BaseForm):\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n\nclass BaseDataSourceLocalNode(IDataSourceLocalNode):\n    def save_context(self, details, workflow_manage):\n        self.context['exception_message'] = details.get('err_message')\n\n    @staticmethod\n    def get_form_list(node):\n        node_data = node.get('properties').get('node_data')\n        return [{\n            'field': 'file_list',\n            'input_type': 'LocalFileUpload',\n            'attrs': {\n                'file_count_limit': node_data.get('file_count_limit') or 10,\n                'file_size_limit': node_data.get('file_size_limit') or 100,\n                'file_type_list': node_data.get('file_type_list'),\n            },\n            'label': '',\n        }]\n\n    def execute(self, file_type_list, file_size_limit, file_count_limit, **kwargs) -> NodeResult:\n        return NodeResult({'file_list': self.workflow_manage.params.get('data_source', {}).get('file_list')},\n                          self.workflow_manage.params.get('knowledge_base') or {})\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'file_list': self.context.get('file_list'),\n            'knowledge_base': self.workflow_params.get('knowledge_base'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/data_source_web_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2025/11/12 13:43\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/flow/step_node/data_source_web_node/i_data_source_web_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： i_data_source_web_node.py\n    @date：2025/11/12 13:47\n    @desc:\n\"\"\"\nfrom abc import abstractmethod\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass IDataSourceWebNode(INode):\n    type = 'data-source-web-node'\n    support = [WorkflowMode.KNOWLEDGE]\n\n    @staticmethod\n    @abstractmethod\n    def get_form_list(node):\n        pass\n\n    def _run(self):\n        return self.execute(**self.flow_params_serializer.data)\n\n    def execute(self, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/data_source_web_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py\n    @date：2025/11/12 13:44\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/flow/step_node/data_source_web_node/impl/base_data_source_web_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： base_data_source_web_node.py\n    @date：2025/11/12 13:47\n    @desc:\n\"\"\"\nimport traceback\n\nfrom django.utils.translation import gettext_lazy as _\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.data_source_web_node.i_data_source_web_node import IDataSourceWebNode\nfrom common import forms\nfrom common.forms import BaseForm\nfrom common.utils.fork import ForkManage, Fork, ChildLink\nfrom common.utils.logger import maxkb_logger\n\n\nclass BaseDataSourceWebNodeForm(BaseForm):\n    source_url = forms.TextInputField(_('Web source url'), required=True, attrs={\n        'placeholder': _('Please enter the Web root address')})\n    selector = forms.TextInputField(_('Web knowledge selector'), required=False, attrs={\n        'placeholder': _('The default is body, you can enter .classname/#idname/tagname')})\n\n\nclass InterruptedTaskException(Exception):\n    def __init__(self, *args, **kwargs):  # real signature unknown\n        pass\n\n\ndef get_collect_handler(workflow_manage):\n    results = []\n\n    def handler(child_link: ChildLink, response: Fork.Response):\n        if response.status == 200:\n            try:\n                document_name = child_link.tag.text if child_link.tag is not None and len(\n                    child_link.tag.text.strip()) > 0 else child_link.url\n                results.append({\n                    \"name\": document_name.strip(),\n                    \"content\": response.content,\n                })\n\n            except Exception as e:\n                maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}')\n        if workflow_manage.is_the_task_interrupted():\n            raise InterruptedTaskException('Task interrupted')\n\n    return handler, results\n\n\nclass BaseDataSourceWebNode(IDataSourceWebNode):\n    def save_context(self, details, workflow_manage):\n        self.context['exception_message'] = details.get('err_message')\n\n    @staticmethod\n    def get_form_list(node):\n        return BaseDataSourceWebNodeForm().to_form_list()\n\n    def execute(self, **kwargs) -> NodeResult:\n        BaseDataSourceWebNodeForm().valid_form(self.workflow_params.get(\"data_source\"))\n\n        data_source = self.workflow_params.get(\"data_source\")\n\n        node_id = data_source.get(\"node_id\")\n        source_url = data_source.get(\"source_url\")\n        selector = data_source.get(\"selector\") or \"body\"\n\n        collect_handler, document_list = get_collect_handler(self.workflow_manage)\n\n        try:\n            ForkManage(source_url, selector.split(\" \") if selector is not None else []).fork(3, set(), collect_handler)\n\n            return NodeResult({'document_list': document_list, 'source_url': source_url, 'selector': selector},\n                              self.workflow_manage.params.get('knowledge_base') or {})\n\n        except Exception as e:\n            if isinstance(e, InterruptedTaskException):\n                return NodeResult({'document_list': document_list, 'source_url': source_url, 'selector': selector},\n                                  self.workflow_manage.params.get('knowledge_base') or {})\n            maxkb_logger.error(_('data source web node:{node_id} error{error}{traceback}').format(\n                knowledge_id=node_id, error=str(e), traceback=traceback.format_exc()))\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'input_params': {\"source_url\": self.context.get(\"source_url\"), \"selector\": self.context.get('selector')},\n            'output_params': self.context.get('document_list'),\n            'knowledge_base': self.workflow_params.get('knowledge_base'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/direct_reply_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 17:50\n    @desc:\n\"\"\"\nfrom .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/direct_reply_node/i_reply_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_reply_node.py\n    @date：2024/6/11 16:25\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\nfrom common.exception.app_exception import AppApiException\n\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass ReplyNodeParamsSerializer(serializers.Serializer):\n    reply_type = serializers.CharField(required=True, label=_(\"Response Type\"))\n    fields = serializers.ListField(required=False, label=_(\"Reference Field\"))\n    content = serializers.CharField(required=False, allow_blank=True, allow_null=True,\n                                    label=_(\"Direct answer content\"))\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        if self.data.get('reply_type') == 'referencing':\n            if 'fields' not in self.data:\n                raise AppApiException(500, _(\"Reference field cannot be empty\"))\n            if len(self.data.get('fields')) < 2:\n                raise AppApiException(500, _(\"Reference field error\"))\n        else:\n            if 'content' not in self.data or self.data.get('content') is None:\n                raise AppApiException(500, _(\"Content cannot be empty\"))\n\n\nclass IReplyNode(INode):\n    type = 'reply-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP,\n               WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return ReplyNodeParamsSerializer\n\n    def _run(self):\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL,\n            WorkflowMode.TOOL_LOOP].__contains__(\n                self.workflow_manage.flow.workflow_mode):\n            return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,\n                                **{'stream': True})\n        else:\n            return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, reply_type, stream, fields=None, content=None, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/direct_reply_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 17:49\n    @desc:\n\"\"\"\nfrom .base_reply_node import *"
  },
  {
    "path": "apps/application/flow/step_node/direct_reply_node/impl/base_reply_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_reply_node.py\n    @date：2024/6/11 17:25\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.direct_reply_node.i_reply_node import IReplyNode\n\n\nclass BaseReplyNode(IReplyNode):\n    def save_context(self, details, workflow_manage):\n        self.context['answer'] = details.get('answer')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n\n    def execute(self, reply_type, stream, fields=None, content=None, **kwargs) -> NodeResult:\n        if reply_type == 'referencing':\n            result = self.get_reference_content(fields)\n        else:\n            result = self.generate_reply_content(content)\n        return NodeResult({'answer': result}, {})\n\n    def generate_reply_content(self, prompt):\n        return self.workflow_manage.generate_prompt(prompt)\n\n    def get_reference_content(self, fields: List[str]):\n        return str(self.workflow_manage.get_reference_field(\n            fields[0],\n            fields[1:]))\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'answer': self.context.get('answer'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/document_extract_node/__init__.py",
    "content": "from .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/document_extract_node/i_document_extract_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass DocumentExtractNodeSerializer(serializers.Serializer):\n    document_list = serializers.ListField(required=False, label=_(\"document\"))\n\n\nclass IDocumentExtractNode(INode):\n    type = 'document-extract-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP,\n               WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return DocumentExtractNodeSerializer\n\n    def _run(self):\n        res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('document_list')[0],\n                                                       self.node_params_serializer.data.get('document_list')[1:])\n        return self.execute(document=res, **self.flow_params_serializer.data)\n\n    def execute(self, document, chat_id=None, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/document_extract_node/impl/__init__.py",
    "content": "from .base_document_extract_node import BaseDocumentExtractNode\n"
  },
  {
    "path": "apps/application/flow/step_node/document_extract_node/impl/base_document_extract_node.py",
    "content": "# coding=utf-8\nimport ast\nimport io\n\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode\nfrom knowledge.models import File, FileSourceType\nfrom knowledge.serializers.document import split_handles, parse_table_handle_list, FileBufferHandle\n\nsplitter = '\\n`-----------------------------------`\\n'\n\n\nclass BaseDocumentExtractNode(IDocumentExtractNode):\n    def save_context(self, details, workflow_manage):\n        self.context['content'] = details.get('content')\n        self.context['exception_message'] = details.get('err_message')\n\n    def execute(self, document, chat_id=None, **kwargs):\n        get_buffer = FileBufferHandle().get_buffer\n\n        self.context['document_list'] = document\n        content = []\n        if document is None or not isinstance(document, list):\n            return NodeResult({'content': '', 'document_list': []}, {})\n\n        # 安全获取 application\n        application_id = None\n        if (self.workflow_manage and\n                self.workflow_manage.work_flow_post_handler and\n                self.workflow_manage.work_flow_post_handler.chat_info):\n            application_id = self.workflow_manage.work_flow_post_handler.chat_info.application.id\n        knowledge_id = self.workflow_params.get('knowledge_id')\n\n        # doc文件中的图片保存\n        def save_image(image_list):\n            for image in image_list:\n                meta = {\n                    'debug': False if (application_id or knowledge_id) else True,\n                    'chat_id': chat_id,\n                    'application_id': str(application_id) if application_id else None,\n                    'knowledge_id': str(knowledge_id) if knowledge_id else None,\n                    'file_id': str(image.id)\n                }\n                file_bytes = image.meta.pop('content')\n                new_file = File(\n                    id=meta['file_id'],\n                    file_name=image.file_name,\n                    file_size=len(file_bytes),\n                    source_type=FileSourceType.APPLICATION.value if meta[\n                        'application_id'] else FileSourceType.KNOWLEDGE.value,\n                    source_id=meta['application_id'] if meta['application_id'] else meta['knowledge_id'],\n                    meta=meta\n                )\n                if not QuerySet(File).filter(id=new_file.id).exists():\n                    new_file.save(file_bytes)\n\n        document_list = []\n        for doc in document:\n            file = QuerySet(File).filter(id=doc['file_id']).first()\n            buffer = io.BytesIO(file.get_bytes())\n            buffer.name = doc['name']  # this is the important line\n\n            for split_handle in (parse_table_handle_list + split_handles):\n                if split_handle.support(buffer, get_buffer):\n                    # 回到文件头\n                    buffer.seek(0)\n                    file_content = split_handle.get_content(buffer, save_image)\n                    content.append('### ' + doc['name'] + '\\n' + file_content)\n                    document_list.append({'id': str(file.id), 'name': doc['name'], 'content': file_content})\n                    break\n\n        return NodeResult({'content': splitter.join(content), 'document_list': document_list}, {})\n\n    def get_details(self, index: int, **kwargs):\n        content = self.context.get('content', '').split(splitter)\n        # 不保存content全部内容，因为content内容可能会很大\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'content': [file_content[:500] for file_content in content],\n            'status': self.status,\n            'err_message': self.err_message,\n            'document_list': self.context.get('document_list'),\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/document_split_node/__init__.py",
    "content": "from .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/document_split_node/i_document_split_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass DocumentSplitNodeSerializer(serializers.Serializer):\n    document_list = serializers.ListField(required=False, label=_(\"document list\"))\n    split_strategy = serializers.ChoiceField(\n        choices=['auto', 'custom', 'qa'], required=False, label=_(\"split strategy\"), default='auto'\n    )\n    paragraph_title_relate_problem_type = serializers.ChoiceField(\n        choices=['custom', 'referencing'], required=False, label=_(\"paragraph title relate problem type\"),\n        default='custom'\n    )\n    paragraph_title_relate_problem = serializers.BooleanField(\n        required=False, label=_(\"paragraph title relate problem\"), default=False\n    )\n    paragraph_title_relate_problem_reference = serializers.ListField(\n        required=False, label=_(\"paragraph title relate problem reference\"), child=serializers.CharField(), default=[]\n    )\n    document_name_relate_problem_type = serializers.ChoiceField(\n        choices=['custom', 'referencing'], required=False, label=_(\"document name relate problem type\"),\n        default='custom'\n    )\n    document_name_relate_problem = serializers.BooleanField(\n        required=False, label=_(\"document name relate problem\"), default=False\n    )\n    document_name_relate_problem_reference = serializers.ListField(\n        required=False, label=_(\"document name relate problem reference\"), child=serializers.CharField(), default=[]\n    )\n    limit = serializers.IntegerField(required=False, label=_(\"limit\"), default=4096)\n    limit_type = serializers.ChoiceField(\n        choices=['custom', 'referencing'], required=False, label=_(\"document name relate problem type\"),\n        default='custom'\n    )\n    limit_reference = serializers.ListField(\n        required=False, label=_(\"limit reference\"), child=serializers.CharField(), default=[]\n    )\n    chunk_size = serializers.IntegerField(required=False, label=_(\"chunk size\"), default=256)\n    chunk_size_type = serializers.ChoiceField(\n        choices=['custom', 'referencing'], required=False, label=_(\"chunk size type\"), default='custom'\n    )\n    chunk_size_reference = serializers.ListField(\n        required=False, label=_(\"chunk size reference\"), child=serializers.CharField(), default=[]\n    )\n    patterns = serializers.ListField(\n        required=False, label=_(\"patterns\"), child=serializers.CharField(), default=[]\n    )\n    patterns_type = serializers.ChoiceField(\n        choices=['custom', 'referencing'], required=False, label=_(\"patterns type\"), default='custom'\n    )\n    patterns_reference = serializers.ListField(\n        required=False, label=_(\"patterns reference\"), child=serializers.CharField(), default=[]\n    )\n    with_filter = serializers.BooleanField(\n        required=False, label=_(\"with filter\"), default=False\n    )\n    with_filter_type = serializers.ChoiceField(\n        choices=['custom', 'referencing'], required=False, label=_(\"with filter type\"), default='custom'\n    )\n    with_filter_reference = serializers.ListField(\n        required=False, label=_(\"with filter reference\"), child=serializers.CharField(), default=[]\n    )\n\n\nclass IDocumentSplitNode(INode):\n    type = 'document-split-node'\n    support = [\n        WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.KNOWLEDGE,\n        WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP\n    ]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return DocumentSplitNodeSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, document_list, knowledge_id, split_strategy, paragraph_title_relate_problem_type,\n                paragraph_title_relate_problem, paragraph_title_relate_problem_reference,\n                document_name_relate_problem_type, document_name_relate_problem,\n                document_name_relate_problem_reference, limit, limit_type, limit_reference, chunk_size, chunk_size_type,\n                chunk_size_reference, patterns, patterns_type, patterns_reference, with_filter, with_filter_type,\n                with_filter_reference, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/document_split_node/impl/__init__.py",
    "content": "from .base_document_split_node import BaseDocumentSplitNode\n"
  },
  {
    "path": "apps/application/flow/step_node/document_split_node/impl/base_document_split_node.py",
    "content": "# coding=utf-8\nimport io\nimport mimetypes\nfrom typing import List\n\nfrom django.core.files.uploadedfile import InMemoryUploadedFile\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.document_split_node.i_document_split_node import IDocumentSplitNode\nfrom common.chunk import text_to_chunk\nfrom knowledge.serializers.document import default_split_handle, FileBufferHandle, md_qa_split_handle\n\n\ndef bytes_to_uploaded_file(file_bytes, file_name=\"file.txt\"):\n    if file_name.startswith(\"http\"):\n        file_name = \"file.txt\"\n    content_type, _ = mimetypes.guess_type(file_name)\n    if content_type is None:\n        # 如果未能识别，设置为默认的二进制文件类型\n        content_type = \"application/octet-stream\"\n    # 创建一个内存中的字节流对象\n    file_stream = io.BytesIO(file_bytes)\n\n    # 获取文件大小\n    file_size = len(file_bytes)\n\n    # 创建 InMemoryUploadedFile 对象\n    uploaded_file = InMemoryUploadedFile(\n        file=file_stream,\n        field_name=None,\n        name=file_name,\n        content_type=content_type,\n        size=file_size,\n        charset=None,\n    )\n    return uploaded_file\n\n\nclass BaseDocumentSplitNode(IDocumentSplitNode):\n    def save_context(self, details, workflow_manage):\n        self.context['content'] = details.get('content')\n        self.context['exception_message'] = details.get('err_message')\n\n    def get_reference_content(self, fields: List[str]):\n        return self.workflow_manage.get_reference_field(fields[0], fields[1:])\n\n    def execute(self, document_list, knowledge_id, split_strategy, paragraph_title_relate_problem_type,\n                paragraph_title_relate_problem, paragraph_title_relate_problem_reference,\n                document_name_relate_problem_type, document_name_relate_problem,\n                document_name_relate_problem_reference, limit, limit_type, limit_reference, chunk_size, chunk_size_type,\n                chunk_size_reference, patterns, patterns_type, patterns_reference, with_filter, with_filter_type,\n                with_filter_reference, **kwargs) -> NodeResult:\n        self.context['knowledge_id'] = knowledge_id\n        file_list = self.get_reference_content(document_list)\n\n        # 处理引用类型的参数\n        if patterns_type == 'referencing':\n            patterns = self.get_reference_content(patterns_reference)\n        if limit_type == 'referencing':\n            limit = self.get_reference_content(limit_reference)\n        if chunk_size_type == 'referencing':\n            chunk_size = self.get_reference_content(chunk_size_reference)\n        if with_filter_type == 'referencing':\n            with_filter = self.get_reference_content(with_filter_reference)\n\n        paragraph_list = []\n        for doc in file_list:\n            get_buffer = FileBufferHandle().get_buffer\n\n            file_mem = bytes_to_uploaded_file(doc['content'].encode('utf-8'), doc['name'])\n            if split_strategy == 'qa':\n                result = md_qa_split_handle.handle(file_mem, get_buffer, self._save_image)\n            else:\n                result = default_split_handle.handle(file_mem, patterns, with_filter, limit, get_buffer,\n                                                     self._save_image)\n            # 统一处理结果为列表\n            results = result if isinstance(result, list) else [result]\n\n            for item in results:\n                self._process_split_result(\n                    item, knowledge_id, doc.get('id'), doc.get('name'),\n                    split_strategy, paragraph_title_relate_problem_type,\n                    paragraph_title_relate_problem, paragraph_title_relate_problem_reference,\n                    document_name_relate_problem_type, document_name_relate_problem,\n                    document_name_relate_problem_reference, chunk_size\n                )\n\n            paragraph_list += results\n\n        self.context['paragraph_list'] = paragraph_list\n        self.context['document_list'] = file_list\n        self.context['limit'] = limit\n        self.context['chunk_size'] = chunk_size\n        self.context['with_filter'] = with_filter\n        self.context['patterns'] = patterns\n        self.context['split_strategy'] = split_strategy\n\n        return NodeResult({'paragraph_list': paragraph_list}, {})\n\n    def _save_image(self, image_list):\n        pass\n\n    def _process_split_result(\n            self, item, knowledge_id, source_file_id, file_name,\n            split_strategy, paragraph_title_relate_problem_type,\n            paragraph_title_relate_problem, paragraph_title_relate_problem_reference,\n            document_name_relate_problem_type, document_name_relate_problem,\n            document_name_relate_problem_reference, chunk_size\n    ):\n        \"\"\"处理文档分割结果\"\"\"\n        item['meta'] = {\n            'knowledge_id': knowledge_id,\n            'source_file_id': source_file_id,\n            'source_url': file_name,\n        }\n        if item.get('name', 'file.txt') == 'file.txt':\n            item['name'] = file_name\n        item['source_file_id'] = source_file_id\n        item['paragraphs'] = item.pop('content', item.get('paragraphs', []))\n\n        for paragraph in item['paragraphs']:\n            paragraph['problem_list'] = self._generate_problem_list(\n                paragraph, file_name,\n                split_strategy, paragraph_title_relate_problem_type,\n                paragraph_title_relate_problem, paragraph_title_relate_problem_reference,\n                document_name_relate_problem_type, document_name_relate_problem,\n                document_name_relate_problem_reference\n            )\n            paragraph['is_active'] = True\n            paragraph['chunks'] = text_to_chunk(paragraph['content'], chunk_size)\n\n    def _generate_problem_list(\n            self, paragraph, document_name, split_strategy, paragraph_title_relate_problem_type,\n            paragraph_title_relate_problem, paragraph_title_relate_problem_reference,\n            document_name_relate_problem_type, document_name_relate_problem,\n            document_name_relate_problem_reference\n    ):\n        if paragraph_title_relate_problem_type == 'referencing':\n            paragraph_title_relate_problem = self.get_reference_content(paragraph_title_relate_problem_reference)\n        if document_name_relate_problem_type == 'referencing':\n            document_name_relate_problem = self.get_reference_content(document_name_relate_problem_reference)\n\n        problem_list = [\n            item for p in paragraph.get('problem_list', []) for item in p.get('content', '').split('<br>')\n            if item.strip()\n        ]\n\n        if split_strategy == 'auto':\n            if paragraph_title_relate_problem and paragraph.get('title'):\n                problem_list.append(paragraph.get('title'))\n            if document_name_relate_problem and document_name:\n                problem_list.append(document_name)\n        elif split_strategy == 'custom':\n            if paragraph_title_relate_problem and paragraph.get('title'):\n                problem_list.append(paragraph.get('title'))\n            if document_name_relate_problem and document_name:\n                problem_list.append(document_name)\n        elif split_strategy == 'qa':\n            if document_name_relate_problem and document_name:\n                problem_list.append(document_name)\n\n        return list(set(problem_list))\n\n    def get_details(self, index: int, **kwargs):\n        paragraph_list = self.context.get('paragraph_list', [])\n        # 每个文档保留前5个分段\n        limited_paragraph_list = []\n        for doc in paragraph_list:\n            if doc.get('paragraphs'):\n                doc_copy = doc.copy()\n                doc_copy['paragraphs'] = doc['paragraphs'][:5]\n                limited_paragraph_list.append(doc_copy)\n            else:\n                limited_paragraph_list.append(doc)\n        paragraph_list = limited_paragraph_list\n\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'paragraph_list': paragraph_list,\n            'limit': self.context.get('limit'),\n            'chunk_size': self.context.get('chunk_size'),\n            'with_filter': self.context.get('with_filter'),\n            'patterns': self.context.get('patterns'),\n            'split_strategy': self.context.get('split_strategy'),\n            'enableException': self.node.properties.get('enableException'),\n            # 'document_list': self.context.get('document_list', []),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/form_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/11/4 14:48\n    @desc:\n\"\"\"\nfrom .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/form_node/i_form_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： i_form_node.py\n    @date：2024/11/4 14:48\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass FormNodeParamsSerializer(serializers.Serializer):\n    form_field_list = serializers.ListField(required=True, label=_(\"Form Configuration\"))\n    form_content_format = serializers.CharField(required=True, label=_('Form output content'))\n    form_data = serializers.DictField(required=False, allow_null=True, label=_(\"Form Data\"))\n\n\nclass IFormNode(INode):\n    type = 'form-node'\n    view_type = 'single_view'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return FormNodeParamsSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, form_field_list, form_content_format, form_data, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/form_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/11/4 14:49\n    @desc:\n\"\"\"\nfrom .base_form_node import BaseFormNode\n"
  },
  {
    "path": "apps/application/flow/step_node/form_node/impl/base_form_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： base_form_node.py\n    @date：2024/11/4 14:52\n    @desc:\n\"\"\"\nimport json\nimport time\nfrom typing import Dict, List\n\nfrom langchain_core.prompts import PromptTemplate\n\nfrom application.flow.common import Answer\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.form_node.i_form_node import IFormNode\n\nmulti_select_list = [\n    'MultiSelect',\n    'MultiRow'\n]\n\n\ndef get_default_option(option_list, _type, value_field):\n    try:\n        if option_list is not None and isinstance(option_list, list) and len(option_list) > 0:\n            default_value_list = [o.get(value_field) for o in option_list if o.get('default')]\n            if len(default_value_list) == 0:\n                return [option_list[0].get(\n                    value_field)] if multi_select_list.__contains__(_type) else option_list[0].get(\n                    value_field)\n            else:\n                if multi_select_list.__contains__(_type):\n                    return default_value_list\n                else:\n                    return default_value_list[0]\n    except Exception as _:\n        pass\n    return []\n\n\ndef write_context(step_variable: Dict, global_variable: Dict, node, workflow):\n    if step_variable is not None:\n        for key in step_variable:\n            node.context[key] = step_variable[key]\n        if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'result' in step_variable:\n            result = step_variable['result']\n            yield result\n            node.answer_text = result\n    node.context['run_time'] = time.time() - node.context['start_time']\n\n\ndef generate_prompt(workflow_manage, _value):\n    try:\n        return workflow_manage.generate_prompt(_value)\n    except Exception as e:\n        return _value\n\n\nclass BaseFormNode(IFormNode):\n    def save_context(self, details, workflow_manage):\n        form_data = details.get('form_data', None)\n        self.context['result'] = details.get('result')\n        self.context['form_content_format'] = details.get('form_content_format')\n        self.context['form_field_list'] = details.get('form_field_list')\n        self.context['run_time'] = details.get('run_time')\n        self.context['start_time'] = details.get('start_time')\n        self.context['form_data'] = form_data\n        self.context['is_submit'] = details.get('is_submit')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('result')\n        if form_data is not None:\n            for key in form_data:\n                self.context[key] = form_data[key]\n\n    def reset_field(self, field):\n        reset_field = ['field', 'label', 'default_value']\n        for f in reset_field:\n            _value = field[f]\n            if _value is None:\n                continue\n            if isinstance(_value, str):\n                field[f] = generate_prompt(self.workflow_manage, _value)\n            elif f == 'label':\n                _label_value = _value.get('label')\n                _value['label'] = generate_prompt(self.workflow_manage, _label_value)\n                tooltip = _value.get('attrs').get('tooltip')\n                if tooltip is not None:\n                    _value.get('attrs')['tooltip'] = generate_prompt(self.workflow_manage, tooltip)\n\n        if ['SingleSelect', 'MultiSelect', 'RadioCard', 'RadioRow', 'MultiRow'].__contains__(field.get('input_type')):\n            if field.get('assignment_method') == 'ref_variables':\n                option_list = self.workflow_manage.get_reference_field(field.get('option_list')[0],\n                                                                       field.get('option_list')[1:])\n                option_list = option_list if isinstance(option_list, list) else []\n                field['option_list'] = option_list\n                field['default_value'] = get_default_option(option_list, field.get('input_type'),\n                                                            field.get('value_field'))\n\n        if ['JsonInput'].__contains__(field.get('input_type')):\n            if field.get('default_value_assignment_method') == 'ref_variables':\n                field['default_value'] = self.workflow_manage.get_reference_field(field.get('default_value')[0],\n                                                                                  field.get('default_value')[1:])\n\n        return field\n\n    def execute(self, form_field_list, form_content_format, form_data, **kwargs) -> NodeResult:\n        if form_data is not None:\n            self.context['is_submit'] = True\n            self.context['form_data'] = form_data\n            for key in form_data:\n                self.context[key] = form_data.get(key)\n        else:\n            self.context['is_submit'] = False\n        form_field_list = [self.reset_field(field) for field in form_field_list]\n        form_setting = {\"form_field_list\": form_field_list, \"runtime_node_id\": self.runtime_node_id,\n                        \"chat_record_id\": self.flow_params_serializer.data.get(\"chat_record_id\"),\n                        \"is_submit\": self.context.get(\"is_submit\", False)}\n        form = f'<form_rander>{json.dumps(form_setting, ensure_ascii=False)}</form_rander>'\n        context = self.workflow_manage.get_workflow_content()\n        form_content_format = self.workflow_manage.reset_prompt(form_content_format)\n        prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')\n        value = prompt_template.format(form=form, context=context, runtime_node_id=self.runtime_node_id,\n                                       chat_record_id=self.flow_params_serializer.data.get(\"chat_record_id\"),\n                                       form_field_list=form_field_list)\n\n        return NodeResult(\n            {'result': value, 'form_field_list': form_field_list, 'form_content_format': form_content_format}, {},\n            _write_context=write_context)\n\n    def get_answer_list(self) -> List[Answer] | None:\n        form_content_format = self.context.get('form_content_format')\n        form_field_list = self.context.get('form_field_list')\n        form_setting = {\"form_field_list\": form_field_list, \"runtime_node_id\": self.runtime_node_id,\n                        \"chat_record_id\": self.flow_params_serializer.data.get(\"chat_record_id\"),\n                        'form_data': self.context.get('form_data', {}),\n                        \"is_submit\": self.context.get(\"is_submit\", False)}\n        form = f'<form_rander>{json.dumps(form_setting, ensure_ascii=False)}</form_rander>'\n        context = self.workflow_manage.get_workflow_content()\n        form_content_format = self.workflow_manage.reset_prompt(form_content_format)\n        prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')\n        value = prompt_template.format(form=form, context=context, runtime_node_id=self.runtime_node_id,\n                                       chat_record_id=self.flow_params_serializer.data.get(\"chat_record_id\"),\n                                       form_field_list=form_field_list)\n        return [\n            Answer(value, self.view_type, self.runtime_node_id, self.workflow_params.get('chat_record_id') or '', None,\n                   self.runtime_node_id, '')]\n\n    def get_details(self, index: int, **kwargs):\n        form_content_format = self.context.get('form_content_format')\n        form_field_list = self.context.get('form_field_list')\n        form_setting = {\"form_field_list\": form_field_list, \"runtime_node_id\": self.runtime_node_id,\n                        \"chat_record_id\": self.flow_params_serializer.data.get(\"chat_record_id\"),\n                        'form_data': self.context.get('form_data', {}),\n                        \"is_submit\": self.context.get(\"is_submit\", False)}\n        form = f'<form_rander>{json.dumps(form_setting, ensure_ascii=False)}</form_rander>'\n        context = self.workflow_manage.get_workflow_content()\n        form_content_format = self.workflow_manage.reset_prompt(form_content_format)\n        prompt_template = PromptTemplate.from_template(form_content_format, template_format='jinja2')\n        value = prompt_template.format(form=form, context=context, runtime_node_id=self.runtime_node_id,\n                                       chat_record_id=self.flow_params_serializer.data.get(\"chat_record_id\"),\n                                       form_field_list=form_field_list)\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            \"result\": value,\n            \"form_content_format\": self.context.get('form_content_format'),\n            \"form_field_list\": self.context.get('form_field_list'),\n            'form_data': self.context.get('form_data'),\n            'start_time': self.context.get('start_time'),\n            'is_submit': self.context.get('is_submit'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/image_generate_step_node/__init__.py",
    "content": "# coding=utf-8\n\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass ImageGenerateNodeSerializer(serializers.Serializer):\n    model_id = serializers.CharField(required=True, label=_(\"Model id\"))\n\n    prompt = serializers.CharField(required=True, label=_(\"Prompt word (positive)\"))\n\n    negative_prompt = serializers.CharField(required=False, label=_(\"Prompt word (negative)\"),\n                                            allow_null=True, allow_blank=True, )\n    # 多轮对话数量\n    dialogue_number = serializers.IntegerField(required=False, default=0,\n                                               label=_(\"Number of multi-round conversations\"))\n\n    dialogue_type = serializers.CharField(required=False, default='NODE',\n                                          label=_(\"Conversation storage type\"))\n\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    model_params_setting = serializers.JSONField(required=False, default=dict,\n                                                 label=_(\"Model parameter settings\"))\n\n\nclass IImageGenerateNode(INode):\n    type = 'image-generate-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return ImageGenerateNodeSerializer\n\n    def _run(self):\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL,\n            WorkflowMode.TOOL_LOOP].__contains__(\n            self.workflow_manage.flow.workflow_mode):\n            return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,\n                                **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None})\n        else:\n            return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record,\n                model_params_setting,\n                chat_record_id,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/image_generate_step_node/impl/__init__.py",
    "content": "# coding=utf-8\n\nfrom .base_image_generate_node import BaseImageGenerateNode\n"
  },
  {
    "path": "apps/application/flow/step_node/image_generate_step_node/impl/base_image_generate_node.py",
    "content": "# coding=utf-8\nfrom functools import reduce\nfrom typing import List\n\nimport requests\nfrom langchain_core.messages import BaseMessage, HumanMessage, AIMessage\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.image_generate_step_node.i_image_generate_node import IImageGenerateNode\nfrom common.utils.common import bytes_to_uploaded_file\nfrom knowledge.models import FileSourceType\nfrom oss.serializers.file import FileSerializer\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\n\n\nclass BaseImageGenerateNode(IImageGenerateNode):\n    def save_context(self, details, workflow_manage):\n        self.context['answer'] = details.get('answer')\n        self.context['question'] = details.get('question')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n\n    def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record,\n                model_params_setting,\n                chat_record_id,\n                **kwargs) -> NodeResult:\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        tti_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,\n                                                             **model_params_setting)\n        history_message = self.get_history_message(history_chat_record, dialogue_number)\n        self.context['history_message'] = history_message\n        question = self.generate_prompt_question(prompt)\n        self.context['question'] = question\n        message_list = self.generate_message_list(question, history_message)\n        self.context['message_list'] = message_list\n        self.context['dialogue_type'] = dialogue_type\n        self.context['negative_prompt'] = self.generate_prompt_question(negative_prompt)\n        image_urls = tti_model.generate_image(question, negative_prompt)\n        # 保存图片\n        file_urls = []\n        for image_url in image_urls:\n            file_name = 'generated_image.png'\n            if isinstance(image_url, str):\n                if image_url.startswith('http'):\n                    # HTTP URL 情况\n                    image_url = requests.get(image_url).content\n                elif image_url.startswith('data:image'):\n                    # Data URL 格式 (data:image/png;base64,...)\n                    import base64\n                    header, encoded = image_url.split(',', 1)\n                    image_url = base64.b64decode(encoded)\n                else:\n                    import base64\n                    image_url = base64.b64decode(image_url)\n            file = bytes_to_uploaded_file(image_url, file_name)\n            file_url = self.upload_file(file)\n            file_urls.append(file_url)\n        self.context['image_list'] = [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls]\n        answer = ' '.join([f\"![Image]({path})\" for path in file_urls])\n        return NodeResult({'answer': answer, 'chat_model': tti_model, 'message_list': message_list,\n                           'image': [{'file_id': path.split('/')[-1], 'url': path} for path in file_urls],\n                           'history_message': history_message, 'question': question}, {})\n\n    def generate_history_ai_message(self, chat_record):\n        for val in chat_record.details.values():\n            if self.node.id == val['node_id'] and 'image_list' in val:\n                if val['dialogue_type'] == 'WORKFLOW':\n                    return chat_record.get_ai_message()\n                image_list = val['image_list']\n                return AIMessage(content=[\n                    *[{'type': 'image_url', 'image_url': {'url': f'{file_url}'}} for file_url in image_list]\n                ])\n        return chat_record.get_ai_message()\n\n    def get_history_message(self, history_chat_record, dialogue_number):\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = reduce(lambda x, y: [*x, *y], [\n            [self.generate_history_human_message(history_chat_record[index]),\n             self.generate_history_ai_message(history_chat_record[index])]\n            for index in\n            range(start_index if start_index > 0 else 0, len(history_chat_record))], [])\n        return history_message\n\n    def generate_history_human_message(self, chat_record):\n\n        for data in chat_record.details.values():\n            if self.node.id == data['node_id'] and 'image_list' in data:\n                image_list = data['image_list']\n                if len(image_list) == 0 or data['dialogue_type'] == 'WORKFLOW':\n                    return HumanMessage(content=chat_record.problem_text)\n                return HumanMessage(content=data['question'])\n        return HumanMessage(content=chat_record.problem_text)\n\n    def generate_prompt_question(self, prompt):\n        return self.workflow_manage.generate_prompt(prompt)\n\n    def generate_message_list(self, question: str, history_message):\n        return [\n            *history_message,\n            question\n        ]\n\n    def upload_file(self, file):\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__(\n                self.workflow_manage.flow.workflow_mode):\n            return self.upload_knowledge_file(file)\n        return self.upload_application_file(file)\n\n    def upload_knowledge_file(self, file):\n        knowledge_id = self.workflow_params.get('knowledge_id')\n        meta = {\n            'debug': False,\n            'knowledge_id': knowledge_id,\n        }\n        file_url = FileSerializer(data={\n            'file': file,\n            'meta': meta,\n            'source_id': knowledge_id,\n            'source_type': FileSourceType.KNOWLEDGE.value\n        }).upload()\n        return file_url\n\n    def upload_application_file(self, file):\n        application = self.workflow_manage.work_flow_post_handler.chat_info.application\n        chat_id = self.workflow_params.get('chat_id')\n        meta = {\n            'debug': False if application.id else True,\n            'chat_id': chat_id,\n            'application_id': str(application.id) if application.id else None,\n        }\n        file_url = FileSerializer(data={\n            'file': file,\n            'meta': meta,\n            'source_id': meta['application_id'],\n            'source_type': FileSourceType.APPLICATION.value\n        }).upload()\n        return file_url\n\n    @staticmethod\n    def reset_message_list(message_list: List[BaseMessage], answer_text):\n        result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for\n                  message\n                  in\n                  message_list]\n        result.append({'role': 'ai', 'content': answer_text})\n        return result\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'history_message': [{'content': message.content, 'role': message.type} for message in\n                                (self.context.get('history_message') if self.context.get(\n                                    'history_message') is not None else [])],\n            'question': self.context.get('question'),\n            'answer': self.context.get('answer'),\n            'type': self.node.type,\n            'message_tokens': self.context.get('message_tokens'),\n            'answer_tokens': self.context.get('answer_tokens'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'image_list': self.context.get('image_list'),\n            'dialogue_type': self.context.get('dialogue_type'),\n            'negative_prompt': self.context.get('negative_prompt'),\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/image_to_video_step_node/__init__.py",
    "content": "# coding=utf-8\n\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/image_to_video_step_node/i_image_to_video_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass ImageToVideoNodeSerializer(serializers.Serializer):\n    model_id = serializers.CharField(required=True, label=_(\"Model id\"))\n\n    prompt = serializers.CharField(required=True, label=_(\"Prompt word (positive)\"))\n\n    negative_prompt = serializers.CharField(required=False, label=_(\"Prompt word (negative)\"),\n                                            allow_null=True, allow_blank=True, )\n    # 多轮对话数量\n    dialogue_number = serializers.IntegerField(required=False, default=0,\n                                               label=_(\"Number of multi-round conversations\"))\n\n    dialogue_type = serializers.CharField(required=False, default='NODE',\n                                          label=_(\"Conversation storage type\"))\n\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    model_params_setting = serializers.JSONField(required=False, default=dict,\n                                                 label=_(\"Model parameter settings\"))\n\n    first_frame_url = serializers.ListField(required=True, label=_(\"First frame url\"))\n    last_frame_url = serializers.ListField(required=False, label=_(\"Last frame url\"))\n\n\nclass IImageToVideoNode(INode):\n    type = 'image-to-video-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return ImageToVideoNodeSerializer\n\n    def _run(self):\n        first_frame_url = self.workflow_manage.get_reference_field(\n            self.node_params_serializer.data.get('first_frame_url')[0],\n            self.node_params_serializer.data.get('first_frame_url')[1:])\n        if first_frame_url is []:\n            raise ValueError(\n                _(\"First frame url cannot be empty\"))\n        last_frame_url = None\n        if self.node_params_serializer.data.get('last_frame_url') is not None and self.node_params_serializer.data.get(\n                'last_frame_url') != []:\n            last_frame_url = self.workflow_manage.get_reference_field(\n                self.node_params_serializer.data.get('last_frame_url')[0],\n                self.node_params_serializer.data.get('last_frame_url')[1:])\n        node_params_data = {k: v for k, v in self.node_params_serializer.data.items()\n                            if k not in ['first_frame_url', 'last_frame_url']}\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__(\n                self.workflow_manage.flow.workflow_mode):\n            return self.execute(first_frame_url=first_frame_url, last_frame_url=last_frame_url, **node_params_data,\n                                **self.flow_params_serializer.data,\n                                **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None})\n        else:\n            return self.execute(first_frame_url=first_frame_url, last_frame_url=last_frame_url,\n                                **node_params_data, **self.flow_params_serializer.data)\n\n    def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record,\n                model_params_setting,\n                chat_record_id,\n                first_frame_url, last_frame_url,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/image_to_video_step_node/impl/__init__.py",
    "content": "# coding=utf-8\n\nfrom .base_image_to_video_node import BaseImageToVideoNode\n"
  },
  {
    "path": "apps/application/flow/step_node/image_to_video_step_node/impl/base_image_to_video_node.py",
    "content": "# coding=utf-8\nimport base64\nfrom functools import reduce\nfrom typing import List\n\nimport requests\nfrom django.db.models import QuerySet\nfrom langchain_core.messages import BaseMessage, HumanMessage, AIMessage\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.image_to_video_step_node.i_image_to_video_node import IImageToVideoNode\nfrom common.utils.common import bytes_to_uploaded_file\nfrom knowledge.models import FileSourceType, File\nfrom oss.serializers.file import FileSerializer, mime_types\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\nfrom django.utils.translation import gettext\n\n\nclass BaseImageToVideoNode(IImageToVideoNode):\n    def save_context(self, details, workflow_manage):\n        self.context['answer'] = details.get('answer')\n        self.context['question'] = details.get('question')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n\n    def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record,\n                model_params_setting,\n                chat_record_id,\n                first_frame_url, last_frame_url=None,\n                **kwargs) -> NodeResult:\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        ttv_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,\n                                                             **model_params_setting)\n        history_message = self.get_history_message(history_chat_record, dialogue_number)\n        self.context['history_message'] = history_message\n        question = self.generate_prompt_question(prompt)\n        self.context['question'] = question\n        message_list = self.generate_message_list(question, history_message)\n        self.context['message_list'] = message_list\n        self.context['dialogue_type'] = dialogue_type\n        self.context['negative_prompt'] = self.generate_prompt_question(negative_prompt)\n        self.context['first_frame_url'] = first_frame_url\n        self.context['last_frame_url'] = last_frame_url\n        # 处理首尾帧图片 这块可以是url 也可以是file_id 如果是url 可以直接传递给模型  如果是file_id 需要传base64\n        # 判断是不是 url\n        first_frame_url = self.get_file_base64(first_frame_url)\n        last_frame_url = self.get_file_base64(last_frame_url)\n        video_urls = ttv_model.generate_video(question, negative_prompt, first_frame_url, last_frame_url)\n        # 保存图片\n        if video_urls is None or video_urls == '':\n            return NodeResult({'answer': gettext('Failed to generate video')}, {})\n        file_name = 'generated_video.mp4'\n        if isinstance(video_urls, str) and video_urls.startswith('http'):\n            video_urls = requests.get(video_urls).content\n        file = bytes_to_uploaded_file(video_urls, file_name)\n        file_url = self.upload_file(file)\n        video_label = f'<video src=\"{file_url}\" controls style=\"max-width: 100%; width: 100%; height: auto; max-height: 60vh;\"></video>'\n        video_list = [{'file_id': file_url.split('/')[-1], 'file_name': file_name, 'url': file_url}]\n        return NodeResult({'answer': video_label, 'chat_model': ttv_model, 'message_list': message_list,\n                           'video': video_list,\n                           'history_message': history_message, 'question': question}, {})\n\n    def get_file_base64(self, image_url):\n        try:\n            if isinstance(image_url, list):\n                image_url = image_url[0].get('file_id') if 'file_id' in image_url[0] else image_url[0].get('url')\n            if isinstance(image_url, str) and not image_url.startswith('http'):\n                file = QuerySet(File).filter(id=image_url).first()\n                file_bytes = file.get_bytes()\n                # 如果我不知道content_type 可以用 magic 库去检测\n                file_type = file.file_name.split(\".\")[-1].lower()\n                content_type = mime_types.get(file_type, 'application/octet-stream')\n                encoded_bytes = base64.b64encode(file_bytes)\n                return f'data:{content_type};base64,{encoded_bytes.decode()}'\n            return image_url\n        except Exception as e:\n            raise ValueError(\n                gettext(\"Failed to obtain the image\"))\n\n    def upload_file(self, file):\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__(\n                self.workflow_manage.flow.workflow_mode):\n            return self.upload_knowledge_file(file)\n        return self.upload_application_file(file)\n\n    def upload_knowledge_file(self, file):\n        knowledge_id = self.workflow_params.get('knowledge_id')\n        meta = {\n            'debug': False,\n            'knowledge_id': knowledge_id\n        }\n        file_url = FileSerializer(data={\n            'file': file,\n            'meta': meta,\n            'source_id': knowledge_id,\n            'source_type': FileSourceType.KNOWLEDGE.value\n        }).upload()\n        return file_url\n\n    def upload_application_file(self, file):\n        application = self.workflow_manage.work_flow_post_handler.chat_info.application\n        chat_id = self.workflow_params.get('chat_id')\n        meta = {\n            'debug': False if application.id else True,\n            'chat_id': chat_id,\n            'application_id': str(application.id) if application.id else None,\n        }\n        file_url = FileSerializer(data={\n            'file': file,\n            'meta': meta,\n            'source_id': meta['application_id'],\n            'source_type': FileSourceType.APPLICATION.value\n        }).upload()\n        return file_url\n\n    def generate_history_ai_message(self, chat_record):\n        for val in chat_record.details.values():\n            if self.node.id == val['node_id'] and 'image_list' in val:\n                if val['dialogue_type'] == 'WORKFLOW':\n                    return chat_record.get_ai_message()\n                image_list = val['image_list']\n                return AIMessage(content=[\n                    *[{'type': 'image_url', 'image_url': {'url': f'{file_url}'}} for file_url in image_list]\n                ])\n        return chat_record.get_ai_message()\n\n    def get_history_message(self, history_chat_record, dialogue_number):\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = reduce(lambda x, y: [*x, *y], [\n            [self.generate_history_human_message(history_chat_record[index]),\n             self.generate_history_ai_message(history_chat_record[index])]\n            for index in\n            range(start_index if start_index > 0 else 0, len(history_chat_record))], [])\n        return history_message\n\n    def generate_history_human_message(self, chat_record):\n\n        for data in chat_record.details.values():\n            if self.node.id == data['node_id'] and 'image_list' in data:\n                image_list = data['image_list']\n                if len(image_list) == 0 or data['dialogue_type'] == 'WORKFLOW':\n                    return HumanMessage(content=chat_record.problem_text)\n                return HumanMessage(content=data['question'])\n        return HumanMessage(content=chat_record.problem_text)\n\n    def generate_prompt_question(self, prompt):\n        return self.workflow_manage.generate_prompt(prompt)\n\n    def generate_message_list(self, question: str, history_message):\n        return [\n            *history_message,\n            question\n        ]\n\n    @staticmethod\n    def reset_message_list(message_list: List[BaseMessage], answer_text):\n        result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for\n                  message\n                  in\n                  message_list]\n        result.append({'role': 'ai', 'content': answer_text})\n        return result\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'history_message': [{'content': message.content, 'role': message.type} for message in\n                                (self.context.get('history_message') if self.context.get(\n                                    'history_message') is not None else [])],\n            'question': self.context.get('question'),\n            'answer': self.context.get('answer'),\n            'type': self.node.type,\n            'message_tokens': self.context.get('message_tokens'),\n            'answer_tokens': self.context.get('answer_tokens'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'first_frame_url': self.context.get('first_frame_url'),\n            'last_frame_url': self.context.get('last_frame_url'),\n            'dialogue_type': self.context.get('dialogue_type'),\n            'negative_prompt': self.context.get('negative_prompt'),\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/image_understand_step_node/__init__.py",
    "content": "# coding=utf-8\n\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass ImageUnderstandNodeSerializer(serializers.Serializer):\n    model_id = serializers.CharField(required=True, label=_(\"Model id\"))\n    system = serializers.CharField(required=False, allow_blank=True, allow_null=True,\n                                   label=_(\"Role Setting\"))\n    prompt = serializers.CharField(required=True, label=_(\"Prompt word\"))\n    # 多轮对话数量\n    dialogue_number = serializers.IntegerField(required=True, label=_(\"Number of multi-round conversations\"))\n\n    dialogue_type = serializers.CharField(required=True, label=_(\"Conversation storage type\"))\n\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    image_list = serializers.ListField(required=False, label=_(\"picture\"))\n\n    model_params_setting = serializers.JSONField(required=False, default=dict,\n                                                 label=_(\"Model parameter settings\"))\n\n\nclass IImageUnderstandNode(INode):\n    type = 'image-understand-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return ImageUnderstandNodeSerializer\n\n    def _run(self):\n        res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('image_list')[0],\n                                                       self.node_params_serializer.data.get('image_list')[1:])\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL,\n            WorkflowMode.TOOL_LOOP].__contains__(\n            self.workflow_manage.flow.workflow_mode):\n            return self.execute(image=res, **self.node_params_serializer.data, **self.flow_params_serializer.data,\n                                **{'history_chat_record': [], 'stream': True, 'chat_record_id': None})\n        else:\n            return self.execute(image=res, **self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream,\n                model_params_setting,\n                chat_record_id,\n                image,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/image_understand_step_node/impl/__init__.py",
    "content": "# coding=utf-8\n\nfrom .base_image_understand_node import BaseImageUnderstandNode\n"
  },
  {
    "path": "apps/application/flow/step_node/image_understand_step_node/impl/base_image_understand_node.py",
    "content": "# coding=utf-8\nimport base64\nimport time\nfrom functools import reduce\nfrom imghdr import what\nfrom typing import List, Dict\n\nfrom django.db.models import QuerySet\nfrom langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage\n\nfrom application.flow.i_step_node import NodeResult, INode\nfrom application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode\nfrom knowledge.models import File\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\n\n\ndef _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):\n    chat_model = node_variable.get('chat_model')\n    message_tokens = node_variable['usage_metadata']['output_tokens'] if 'usage_metadata' in node_variable else 0\n    answer_tokens = chat_model.get_num_tokens(answer)\n    node.context['message_tokens'] = message_tokens\n    node.context['answer_tokens'] = answer_tokens\n    node.context['answer'] = answer\n    node.context['history_message'] = node_variable['history_message']\n    node.context['question'] = node_variable['question']\n    node.context['run_time'] = time.time() - node.context['start_time']\n    if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):\n        node.answer_text = answer\n\n\ndef write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据 (流式)\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result')\n    answer = ''\n    for chunk in response:\n        if isinstance(chunk.content, list):\n            for chunk_item in chunk.content:\n                text = chunk_item.get(\"text\", \"\")\n                answer += text\n                yield text\n        else:\n            text = chunk.content or \"\"\n            answer += text\n            yield text\n    _write_context(node_variable, workflow_variable, node, workflow, answer)\n\n\ndef write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点实例对象\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result')\n    answer = response.content\n    _write_context(node_variable, workflow_variable, node, workflow, answer)\n\n\ndef file_id_to_base64(file_id: str):\n    file = QuerySet(File).filter(id=file_id).first()\n    file_bytes = file.get_bytes()\n    base64_image = base64.b64encode(file_bytes).decode(\"utf-8\")\n    return [base64_image, what(None, file_bytes)]\n\n\nclass BaseImageUnderstandNode(IImageUnderstandNode):\n    def save_context(self, details, workflow_manage):\n        self.context['answer'] = details.get('answer')\n        self.context['question'] = details.get('question')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n\n    def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream,\n                model_params_setting,\n                chat_record_id,\n                image,\n                **kwargs) -> NodeResult:\n        # 处理不正确的参数\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        image_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,\n                                                               **model_params_setting)\n        # 执行详情中的历史消息不需要图片内容\n        history_message = self.get_history_message_for_details(history_chat_record, dialogue_number)\n        self.context['history_message'] = history_message\n        question = self.generate_prompt_question(prompt)\n        self.context['question'] = question.content\n        # 生成消息列表, 真实的history_message\n        message_list = self.generate_message_list(image_model, system, prompt,\n                                                  self.get_history_message(history_chat_record, dialogue_number), image)\n        self.context['message_list'] = message_list\n        self.generate_context_image(image)\n        self.context['dialogue_type'] = dialogue_type\n        if stream:\n            r = image_model.stream(message_list)\n            return NodeResult({'result': r, 'chat_model': image_model, 'message_list': message_list,\n                               'history_message': history_message, 'question': question.content}, {},\n                              _write_context=write_context_stream)\n        else:\n            r = image_model.invoke(message_list)\n            return NodeResult({'result': r, 'chat_model': image_model, 'message_list': message_list,\n                               'history_message': history_message, 'question': question.content}, {},\n                              _write_context=write_context)\n\n    def generate_context_image(self, image):\n        if isinstance(image, str) and image.startswith('http'):\n            self.context['image_list'] = [{'url': image}]\n        elif image is not None and len(image) > 0:\n            self.context['image_list'] = image\n\n    def get_history_message_for_details(self, history_chat_record, dialogue_number):\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = reduce(lambda x, y: [*x, *y], [\n            [self.generate_history_human_message_for_details(history_chat_record[index]),\n             self.generate_history_ai_message(history_chat_record[index])]\n            for index in\n            range(start_index if start_index > 0 else 0, len(history_chat_record))], [])\n        return history_message\n\n    def generate_history_ai_message(self, chat_record):\n        for val in chat_record.details.values():\n            if self.node.id == val['node_id'] and 'image_list' in val:\n                if val['dialogue_type'] == 'WORKFLOW':\n                    return chat_record.get_ai_message()\n                return AIMessage(content=val['answer'])\n        return chat_record.get_ai_message()\n\n    def generate_history_human_message_for_details(self, chat_record):\n        for data in chat_record.details.values():\n            if self.node.id == data['node_id'] and 'image_list' in data:\n                image_list = data['image_list']\n                if len(image_list) == 0 or data['dialogue_type'] == 'WORKFLOW':\n                    return HumanMessage(content=chat_record.problem_text)\n\n                file_id_list = []\n                url_list = []\n                for image in image_list:\n                    if 'file_id' in image:\n                        file_id_list.append(image.get('file_id'))\n                    elif 'url' in image:\n                        url_list.append(image.get('url'))\n                return HumanMessage(content=[\n                    {'type': 'text', 'text': data['question']},\n                    *[{'type': 'image_url', 'image_url': {'url': f'./oss/file/{file_id}'}} for file_id in file_id_list],\n                    *[{'type': 'image_url', 'image_url': {'url': url}} for url in url_list]\n                ])\n        return HumanMessage(content=chat_record.problem_text)\n\n    def get_history_message(self, history_chat_record, dialogue_number):\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = reduce(lambda x, y: [*x, *y], [\n            [self.generate_history_human_message(history_chat_record[index]),\n             self.generate_history_ai_message(history_chat_record[index])]\n            for index in\n            range(start_index if start_index > 0 else 0, len(history_chat_record))], [])\n        return history_message\n\n    def generate_history_human_message(self, chat_record):\n\n        for data in chat_record.details.values():\n            if self.node.id == data['node_id'] and 'image_list' in data:\n                image_list = data['image_list']\n                if len(image_list) == 0 or data['dialogue_type'] == 'WORKFLOW':\n                    return HumanMessage(content=chat_record.problem_text)\n                file_id_list = []\n                url_list = []\n                for image in image_list:\n                    if 'file_id' in image:\n                        file_id_list.append(image.get('file_id'))\n                    elif 'url' in image:\n                        url_list.append(image.get('url'))\n                image_base64_list = [file_id_to_base64(file_id) for file_id in file_id_list]\n\n                return HumanMessage(\n                    content=[\n                        {'type': 'text', 'text': data['question']},\n                        *[{'type': 'image_url',\n                           'image_url': {'url': f'data:image/{base64_image[1]};base64,{base64_image[0]}'}} for\n                          base64_image in image_base64_list],\n                        *[{'type': 'image_url', 'image_url': url} for url in url_list]\n                    ])\n        return HumanMessage(content=chat_record.problem_text)\n\n    def generate_prompt_question(self, prompt):\n        return HumanMessage(self.workflow_manage.generate_prompt(prompt))\n\n    def _process_images(self, image):\n        \"\"\"\n        处理图像数据，转换为模型可识别的格式\n        \"\"\"\n        images = []\n        if isinstance(image, str) and image.startswith('http'):\n            images.append({'type': 'image_url', 'image_url': {'url': image}})\n        elif image is not None and len(image) > 0:\n            for img in image:\n                if 'file_id' in img:\n                    file_id = img['file_id']\n                    file = QuerySet(File).filter(id=file_id).first()\n                    image_bytes = file.get_bytes()\n                    base64_image = base64.b64encode(image_bytes).decode(\"utf-8\")\n                    image_format = what(None, image_bytes)\n                    images.append(\n                        {'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}})\n                elif 'url' in img and img['url'].startswith('http'):\n                    images.append(\n                        {'type': 'image_url', 'image_url': {'url': img[\"url\"]}})\n        return images\n\n    def generate_message_list(self, image_model, system: str, prompt: str, history_message, image):\n        prompt_text = self.workflow_manage.generate_prompt(prompt)\n        images = self._process_images(image)\n\n        if images:\n            messages = [HumanMessage(content=[{'type': 'text', 'text': prompt_text}, *images])]\n        else:\n            messages = [HumanMessage(prompt_text)]\n\n        if system is not None and len(system) > 0:\n            return [\n                SystemMessage(self.workflow_manage.generate_prompt(system)),\n                *history_message,\n                *messages\n            ]\n        else:\n            return [\n                *history_message,\n                *messages\n            ]\n\n    @staticmethod\n    def reset_message_list(message_list: List[BaseMessage], answer_text):\n        result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for\n                  message\n                  in\n                  message_list]\n        result.append({'role': 'ai', 'content': answer_text})\n        return result\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'system': self.node_params.get('system'),\n            'history_message': [{'content': message.content, 'role': message.type} for message in\n                                (self.context.get('history_message') if self.context.get(\n                                    'history_message') is not None else [])],\n            'question': self.context.get('question'),\n            'answer': self.context.get('answer'),\n            'type': self.node.type,\n            'message_tokens': self.context.get('message_tokens'),\n            'answer_tokens': self.context.get('answer_tokens'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'image_list': self.context.get('image_list'),\n            'dialogue_type': self.context.get('dialogue_type'),\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/intent_node/__init__.py",
    "content": "# coding=utf-8\n\n\n\n\nfrom .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/intent_node/i_intent_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass IntentBranchSerializer(serializers.Serializer):\n    id = serializers.CharField(required=True, label=_(\"Branch id\"))\n    content = serializers.CharField(required=True, label=_(\"content\"))\n    isOther = serializers.BooleanField(required=True, label=_(\"Branch Type\"))\n\n\nclass IntentNodeSerializer(serializers.Serializer):\n    model_id = serializers.CharField(required=True, label=_(\"Model id\"))\n    content_list = serializers.ListField(required=True, label=_(\"Text content\"))\n    dialogue_number = serializers.IntegerField(required=True, label=\n    _(\"Number of multi-round conversations\"))\n    model_params_setting = serializers.DictField(required=False,\n                                                 label=_(\"Model parameter settings\"))\n    branch = IntentBranchSerializer(many=True)\n\n\nclass IIntentNode(INode):\n    type = 'intent-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def save_context(self, details, workflow_manage):\n        pass\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return IntentNodeSerializer\n\n    def _run(self):\n        question = self.workflow_manage.get_reference_field(\n            self.node_params_serializer.data.get('content_list')[0],\n            self.node_params_serializer.data.get('content_list')[1:],\n        )\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL,\n            WorkflowMode.TOOL_LOOP].__contains__(\n            self.workflow_manage.flow.workflow_mode):\n            return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,\n                                **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None,\n                                   'user_input': str(question)})\n        else:\n            return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,\n                                user_input=str(question))\n\n    def execute(self, model_id, dialogue_number, history_chat_record, user_input, branch,\n                model_params_setting=None, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/intent_node/impl/__init__.py",
    "content": "\n\nfrom .base_intent_node import BaseIntentNode"
  },
  {
    "path": "apps/application/flow/step_node/intent_node/impl/base_intent_node.py",
    "content": "# coding=utf-8\nimport json\nimport re\nimport time\nfrom typing import List, Dict, Any\nfrom functools import reduce\n\nfrom django.db.models import QuerySet\nfrom langchain_core.messages import HumanMessage, SystemMessage\n\nfrom application.flow.i_step_node import INode, NodeResult\nfrom application.flow.step_node.intent_node.i_intent_node import IIntentNode\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential\nfrom .prompt_template import PROMPT_TEMPLATE\n\n\ndef get_default_model_params_setting(model_id):\n    model = QuerySet(Model).filter(id=model_id).first()\n    credential = get_model_credential(model.provider, model.model_type, model.model_name)\n    model_params_setting = credential.get_model_params_setting_form(\n        model.model_name).get_default_form_data()\n    return model_params_setting\n\n\ndef _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):\n    chat_model = node_variable.get('chat_model')\n    message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))\n    answer_tokens = chat_model.get_num_tokens(answer)\n\n    node.context['message_tokens'] = message_tokens\n    node.context['answer_tokens'] = answer_tokens\n    node.context['answer'] = answer\n    node.context['history_message'] = node_variable['history_message']\n    node.context['user_input'] = node_variable['user_input']\n    node.context['branch_id'] = node_variable.get('branch_id')\n    node.context['reason'] = node_variable.get('reason')\n    node.context['category'] = node_variable.get('category')\n    node.context['run_time'] = time.time() - node.context['start_time']\n\n\ndef write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    response = node_variable.get('result')\n    answer = response.content\n    _write_context(node_variable, workflow_variable, node, workflow, answer)\n\n\nclass BaseIntentNode(IIntentNode):\n\n    def save_context(self, details, workflow_manage):\n        self.context['exception_message'] = details.get('err_message')\n        self.context['branch_id'] = details.get('branch_id')\n        self.context['category'] = details.get('category')\n\n    def execute(self, model_id, dialogue_number, history_chat_record, user_input, branch,\n                model_params_setting=None, **kwargs) -> NodeResult:\n\n        # 设置默认模型参数\n        if model_params_setting is None:\n            model_params_setting = get_default_model_params_setting(model_id)\n\n        # 获取模型实例\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        chat_model = get_model_instance_by_model_workspace_id(\n            model_id, workspace_id, **model_params_setting\n        )\n\n        # 获取历史对话\n        history_message = self.get_history_message(history_chat_record, dialogue_number)\n        self.context['history_message'] = history_message\n\n        # 保存问题到上下文\n        self.context['user_input'] = user_input\n\n        # 构建分类提示词\n        prompt = self.build_classification_prompt(user_input, branch)\n\n        # 生成消息列表\n        system = self.build_system_prompt()\n        message_list = self.generate_message_list(system, prompt, history_message)\n        self.context['message_list'] = message_list\n\n        # 调用模型进行分类\n        try:\n            r = chat_model.invoke(message_list)\n            classification_result = r.content.strip()\n            # 解析分类结果获取分支信息\n            matched_branch = self.parse_classification_result(classification_result, branch)\n\n            #  返回结果\n            return NodeResult({\n                'result': r,\n                'chat_model': chat_model,\n                'message_list': message_list,\n                'history_message': history_message,\n                'user_input': user_input,\n                'branch_id': matched_branch['id'],\n                'reason': self.parse_result_reason(r.content),\n                'category': matched_branch.get('content', matched_branch['id'])\n            }, {}, _write_context=write_context)\n\n        except Exception as e:\n            # 错误处理：返回\"其他\"分支\n            other_branch = self.find_other_branch(branch)\n            if other_branch:\n                return NodeResult({\n                    'branch_id': other_branch['id'],\n                    'category': other_branch.get('content', other_branch['id']),\n                    'error': str(e)\n                }, {})\n            else:\n                raise Exception(f\"error: {str(e)}\")\n\n    @staticmethod\n    def get_history_message(history_chat_record, dialogue_number):\n        \"\"\"获取历史消息\"\"\"\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = reduce(lambda x, y: [*x, *y], [\n            [history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()]\n            for index in\n            range(start_index if start_index > 0 else 0, len(history_chat_record))], [])\n\n        for message in history_message:\n            if isinstance(message.content, str):\n                message.content = re.sub('<form_rander>[\\d\\D]*?<\\/form_rander>', '', message.content)\n        return history_message\n\n    def build_system_prompt(self) -> str:\n        \"\"\"构建系统提示词\"\"\"\n        return \"你是一个专业的意图识别助手，请根据用户输入和意图选项，准确识别用户的真实意图。\"\n\n    def build_classification_prompt(self, user_input: str, branch: List[Dict]) -> str:\n        \"\"\"构建分类提示词\"\"\"\n\n        classification_list = []\n\n        other_branch = self.find_other_branch(branch)\n        # 添加其他分支\n        if other_branch:\n            classification_list.append({\n                \"classificationId\": 0,\n                \"content\": other_branch.get('content')\n            })\n        # 添加正常分支\n        classification_id = 1\n        for b in branch:\n            if not b.get('isOther'):\n                classification_list.append({\n                    \"classificationId\": classification_id,\n                    \"content\": b['content']\n                })\n                classification_id += 1\n\n        return PROMPT_TEMPLATE.format(\n            classification_list=classification_list,\n            user_input=user_input\n        )\n\n    def generate_message_list(self, system: str, prompt: str, history_message):\n        \"\"\"生成消息列表\"\"\"\n        if system is None or len(system) == 0:\n            return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))]\n        else:\n            return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message,\n                    HumanMessage(self.workflow_manage.generate_prompt(prompt))]\n\n    def parse_classification_result(self, result: str, branch: List[Dict]) -> Dict[str, Any]:\n        \"\"\"解析分类结果\"\"\"\n\n        other_branch = self.find_other_branch(branch)\n        normal_intents = [\n            b\n            for b in branch\n            if not b.get('isOther')\n        ]\n\n        def get_branch_by_id(category_id: int):\n            if category_id == 0:\n                return other_branch\n            elif 1 <= category_id <= len(normal_intents):\n                return normal_intents[category_id - 1]\n            return None\n\n        try:\n            result_json = json.loads(result)\n            classification_id = result_json.get('classificationId')\n            # 如果是 0 ，返回其他分支\n            matched_branch = get_branch_by_id(classification_id)\n            if matched_branch:\n                return matched_branch\n\n        except Exception as e:\n            # json 解析失败，re 提取\n            numbers = re.findall(r'\"classificationId\":\\s*(\\d+)', result)\n            if numbers:\n                classification_id = int(numbers[0])\n\n                matched_branch = get_branch_by_id(classification_id)\n                if matched_branch:\n                    return matched_branch\n\n        # 如果都解析失败，返回“other”\n        return other_branch or (normal_intents[0] if normal_intents else {'id': 'unknown', 'content': 'unknown'})\n\n    def parse_result_reason(self, result: str):\n        \"\"\"解析分类的原因\"\"\"\n        try:\n            result_json = json.loads(result)\n            return result_json.get('reason', '')\n        except Exception as e:\n            reason_patterns = [\n                r'\"reason\":\\s*\"([^\"]*)\"',  # 标准格式\n                r'\"reason\":\\s*\"([^\"]*)',  # 缺少结束引号\n                r'\"reason\":\\s*([^,}\\n]*)',  # 没有引号包围的内容\n            ]\n            for pattern in reason_patterns:\n                match = re.search(pattern, result, re.DOTALL)\n                if match:\n                    reason = match.group(1).strip()\n                    # 清理可能的尾部字符\n                    reason = re.sub(r'[\"\\s]*$', '', reason)\n                    return reason\n\n            return ''\n\n    def find_other_branch(self, branch: List[Dict]) -> Dict[str, Any] | None:\n        \"\"\"查找其他分支\"\"\"\n        for b in branch:\n            if b.get('isOther'):\n                return b\n        return None\n\n    def get_details(self, index: int, **kwargs):\n        \"\"\"获取节点执行详情\"\"\"\n        return {\n            'name': self.node.properties.get('stepName'),\n            'index': index,\n            'run_time': self.context.get('run_time'),\n            'system': self.context.get('system'),\n            'history_message': [\n                {'content': message.content, 'role': message.type}\n                for message in (self.context.get('history_message') or [])\n            ],\n            'user_input': self.context.get('user_input'),\n            'answer': self.context.get('answer'),\n            'branch_id': self.context.get('branch_id'),\n            'category': self.context.get('category'),\n            'type': self.node.type,\n            'message_tokens': self.context.get('message_tokens'),\n            'answer_tokens': self.context.get('answer_tokens'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/intent_node/impl/prompt_template.py",
    "content": "\n\n\n\nPROMPT_TEMPLATE = \"\"\"\n    # Role\n    You are an intention classification expert, good at being able to judge which classification the user's input belongs to.\n\n    ## Skills\n    Skill 1: Clearly determine which of the following intention classifications the user's input belongs to.\n    Intention classification list:\n    {classification_list}\n\n    Note:\n    - Please determine the match only between the user's input content and the Intention classification list content, without judging or categorizing the match with the classification ID.\n    - **When classifying, you must give higher weight to the context and intent continuity shown in the historical conversation. Do not rely solely on the literal meaning of the current input; instead, prioritize the most consistent classification with the previous dialogue flow.**\n\n    ## User Input\n    {user_input}\n\n    ## Reply requirements\n    - The answer must be returned in JSON format.\n    - Strictly ensure that the output is in a valid JSON format.\n    - Do not add prefix ```json or suffix ```\n    - The answer needs to include the following fields such as:\n    {{\n    \"classificationId\": 0,\n    \"reason\": \"\"\n    }}\n\n    ## Limit\n    - Please do not reply in text.\"\"\"\n"
  },
  {
    "path": "apps/application/flow/step_node/knowledge_write_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2025/11/13 11:17\n    @desc:\n\"\"\""
  },
  {
    "path": "apps/application/flow/step_node/knowledge_write_node/i_knowledge_write_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： i_knowledge_write_node.py\n    @date：2025/11/13 11:19\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass KnowledgeWriteNodeParamSerializer(serializers.Serializer):\n    document_list = serializers.ListField(required=True, child=serializers.CharField(required=True), allow_null=True,\n                                          label=_('document list'))\n\n\nclass IKnowledgeWriteNode(INode):\n\n    def save_context(self, details, workflow_manage):\n        pass\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return KnowledgeWriteNodeParamSerializer\n\n    def _run(self):\n        documents = self.workflow_manage.get_reference_field(\n            self.node_params_serializer.data.get('document_list')[0],\n            self.node_params_serializer.data.get('document_list')[1:],\n        )\n\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data, documents=documents)\n\n    def execute(self, documents, **kwargs) -> NodeResult:\n        pass\n\n    type = 'knowledge-write-node'\n    support = [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP]\n"
  },
  {
    "path": "apps/application/flow/step_node/knowledge_write_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2025/11/13 11:18\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/application/flow/step_node/knowledge_write_node/impl/base_knowledge_write_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： base_knowledge_write_node.py\n    @date：2025/11/13 11:19\n    @desc:\n\"\"\"\nfrom functools import reduce\nfrom typing import Dict, List, Any\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\nfrom django.db.models.aggregates import Max\n\nfrom rest_framework import serializers\nfrom django.utils.translation import gettext_lazy as _\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.knowledge_write_node.i_knowledge_write_node import IKnowledgeWriteNode\nfrom common.chunk import text_to_chunk\nfrom common.utils.common import bulk_create_in_batches, filter_special_character\nfrom knowledge.models import Document, KnowledgeType, Paragraph, File, FileSourceType, Problem, ProblemParagraphMapping, \\\n    Tag, DocumentTag\nfrom knowledge.serializers.common import ProblemParagraphObject, ProblemParagraphManage\nfrom knowledge.serializers.document import DocumentSerializers\n\n\nclass ParagraphInstanceSerializer(serializers.Serializer):\n    content = serializers.CharField(required=True, label=_('content'), max_length=102400, min_length=1, allow_null=True,\n                                    allow_blank=True)\n    title = serializers.CharField(required=False, max_length=256, label=_('section title'), allow_null=True,\n                                  allow_blank=True)\n    problem_list = serializers.ListField(required=False, child=serializers.CharField(required=False, allow_blank=True))\n    is_active = serializers.BooleanField(required=False, label=_('Is active'))\n    chunks = serializers.ListField(required=False, child=serializers.CharField(required=True))\n\n\nclass TagInstanceSerializer(serializers.Serializer):\n    key = serializers.CharField(required=True, max_length=64, label=_('Tag Key'))\n    value = serializers.CharField(required=True, max_length=128, label=_('Tag Value'))\n\n\nclass KnowledgeWriteParamSerializer(serializers.Serializer):\n    name = serializers.CharField(required=True, label=_('document name'), max_length=128, min_length=1,\n                                 source=_('document name'))\n    meta = serializers.DictField(required=False)\n    tags = serializers.ListField(required=False, label=_('Tags'), child=TagInstanceSerializer())\n    paragraphs = ParagraphInstanceSerializer(required=False, many=True, allow_null=True)\n    source_file_id = serializers.UUIDField(required=False, allow_null=True)\n\n\ndef convert_uuid_to_str(obj):\n    if isinstance(obj, dict):\n        return {k: convert_uuid_to_str(v) for k, v in obj.items()}\n    elif isinstance(obj, list):\n        return [convert_uuid_to_str(i) for i in obj]\n    elif isinstance(obj, uuid.UUID):\n        return str(obj)\n    else:\n        return obj\n\n\ndef link_file(source_file_id, document_id):\n    if source_file_id is None:\n        return\n    source_file = QuerySet(File).filter(id=source_file_id).first()\n    if source_file:\n        file_content = source_file.get_bytes()\n\n        new_file = File(\n            id=uuid.uuid7(),\n            file_name=source_file.file_name,\n            file_size=source_file.file_size,\n            source_type=FileSourceType.DOCUMENT,\n            source_id=document_id,  # 更新为当前知识库ID\n            meta=source_file.meta.copy() if source_file.meta else {}\n        )\n\n        # 保存文件内容和元数据\n        new_file.save(file_content)\n\n\ndef get_paragraph_problem_model(knowledge_id: str, document_id: str, instance: Dict):\n    paragraph = Paragraph(\n        id=uuid.uuid7(),\n        document_id=document_id,\n        content=filter_special_character(instance.get(\"content\")),\n        knowledge_id=knowledge_id,\n        title=instance.get(\"title\") if 'title' in instance else '',\n        chunks=[filter_special_character(c) for c in (instance.get('chunks') if 'chunks' in instance else text_to_chunk(\n            instance.get(\"content\")))],\n    )\n\n    problem_paragraph_object_list = [ProblemParagraphObject(\n        knowledge_id, document_id, str(paragraph.id), problem\n    ) for problem in (instance.get('problem_list') if 'problem_list' in instance else [])]\n\n    return {\n        'paragraph': paragraph,\n        'problem_paragraph_object_list': problem_paragraph_object_list,\n    }\n\n\ndef get_paragraph_model(document_model, paragraph_list: List):\n    knowledge_id = document_model.knowledge_id\n    paragraph_model_dict_list = [\n        get_paragraph_problem_model(knowledge_id, document_model.id, paragraph)\n        for paragraph in paragraph_list\n    ]\n\n    paragraph_model_list = []\n    problem_paragraph_object_list = []\n    for paragraphs in paragraph_model_dict_list:\n        paragraph = paragraphs.get('paragraph')\n        for problem_model in paragraphs.get('problem_paragraph_object_list'):\n            problem_paragraph_object_list.append(problem_model)\n        paragraph_model_list.append(paragraph)\n\n    return {\n        'document': document_model,\n        'paragraph_model_list': paragraph_model_list,\n        'problem_paragraph_object_list': problem_paragraph_object_list,\n    }\n\n\ndef get_document_paragraph_model(knowledge_id: str, instance: Dict):\n    source_meta = {'source_file_id': instance.get(\"source_file_id\")} if instance.get(\"source_file_id\") else {}\n    meta = {**instance.get('meta'), **source_meta} if instance.get('meta') is not None else source_meta\n    meta = {**convert_uuid_to_str(meta), 'allow_download': True}\n\n    document_model = Document(\n        **{\n            'knowledge_id': knowledge_id,\n            'id': uuid.uuid7(),\n            'name': instance.get('name'),\n            'char_length': reduce(\n                lambda x, y: x + y,\n                [len(p.get('content')) for p in instance.get('paragraphs', [])],\n                0),\n            'meta': meta,\n            'type': instance.get('type') if instance.get('type') is not None else KnowledgeType.WORKFLOW\n        }\n    )\n\n    return get_paragraph_model(\n        document_model,\n        instance.get('paragraphs') if 'paragraphs' in instance else []\n    )\n\n\ndef save_knowledge_tags(knowledge_id: str, tags: List[Dict[str, Any]]):\n    existed_tags_dict = {\n        (key, value): str(tag_id)\n        for key, value, tag_id in QuerySet(Tag).filter(knowledge_id=knowledge_id).values_list(\"key\", \"value\", \"id\")\n    }\n\n    tag_model_list = []\n    new_tag_dict = {}\n    for tag in tags:\n        key = tag.get(\"key\")\n        value = tag.get(\"value\")\n\n        if (key, value) not in existed_tags_dict:\n            tag_model = Tag(\n                id=uuid.uuid7(),\n                knowledge_id=knowledge_id,\n                key=key,\n                value=value\n            )\n            tag_model_list.append(tag_model)\n            new_tag_dict[(key, value)] = str(tag_model.id)\n\n    if tag_model_list:\n        Tag.objects.bulk_create(tag_model_list)\n\n    all_tag_dict = {**existed_tags_dict, **new_tag_dict}\n\n    return all_tag_dict, new_tag_dict\n\n\ndef batch_add_document_tag(document_tag_map: Dict[str, List[str]]):\n    \"\"\"\n    批量添加文档-标签关联\n    document_tag_map: {document_id: [tag_id1, tag_id2, ...]}\n    \"\"\"\n    all_document_ids = list(document_tag_map.keys())\n    all_tag_ids = list(set(tag_id for tag_ids in document_tag_map.values() for tag_id in tag_ids))\n\n    # 查询已存在的文档-标签关联\n    existed_relations = set(\n        QuerySet(DocumentTag).filter(\n            document_id__in=all_document_ids,\n            tag_id__in=all_tag_ids\n        ).values_list('document_id', 'tag_id')\n    )\n\n    new_relations = [\n        DocumentTag(\n            id=uuid.uuid7(),\n            document_id=doc_id,\n            tag_id=tag_id,\n        )\n        for doc_id, tag_ids in document_tag_map.items()\n        for tag_id in tag_ids\n        if (doc_id, tag_id) not in existed_relations\n    ]\n\n    if new_relations:\n        QuerySet(DocumentTag).bulk_create(new_relations)\n\n\nclass BaseKnowledgeWriteNode(IKnowledgeWriteNode):\n\n    def save_context(self, details, workflow_manage):\n        self.context['exception_message'] = details.get('err_message')\n\n    def save(self, document_list):\n        serializer = KnowledgeWriteParamSerializer(data=document_list, many=True)\n        serializer.is_valid(raise_exception=True)\n        document_list = serializer.data\n\n        knowledge_id = self.workflow_params.get(\"knowledge_id\")\n        workspace_id = self.workflow_params.get(\"workspace_id\")\n\n        document_model_list = []\n        paragraph_model_list = []\n        problem_paragraph_object_list = []\n        # 所有标签\n        knowledge_tag_list = []\n        # 文档标签映射关系\n        document_tags_map = {}\n        knowledge_tag_dict = {}\n\n        for document in document_list:\n            document_paragraph_dict_model = get_document_paragraph_model(\n                knowledge_id,\n                document\n            )\n            document_instance = document_paragraph_dict_model.get('document')\n            link_file(document.get(\"source_file_id\"), document_instance.id)\n            document_model_list.append(document_instance)\n            # 收集标签\n            single_document_tag_list = document.get(\"tags\", [])\n            # 去重传入的标签\n            for tag in single_document_tag_list:\n                tag_key = (tag['key'], tag['value'])\n                if tag_key not in knowledge_tag_dict:\n                    knowledge_tag_dict[tag_key] = tag\n\n            if single_document_tag_list:\n                document_tags_map[str(document_instance.id)] = single_document_tag_list\n\n            for paragraph in document_paragraph_dict_model.get(\"paragraph_model_list\"):\n                paragraph_model_list.append(paragraph)\n            for problem_paragraph_object in document_paragraph_dict_model.get(\"problem_paragraph_object_list\"):\n                problem_paragraph_object_list.append(problem_paragraph_object)\n        knowledge_tag_list = list(knowledge_tag_dict.values())\n        # 保存所有文档中含有的标签到知识库\n        if knowledge_tag_list:\n            all_tag_dict, new_tag_dict = save_knowledge_tags(knowledge_id, knowledge_tag_list)\n            # 构建文档-标签ID映射\n            document_tag_id_map = {}\n            # 为每个文档添加其对应的标签\n            for doc_id, doc_tags in document_tags_map.items():\n                doc_tag_ids = [\n                    all_tag_dict[(tag.get(\"key\"), tag.get(\"value\"))]\n                    for tag in doc_tags\n                    if (tag.get(\"key\"), tag.get(\"value\")) in all_tag_dict\n                ]\n                if doc_tag_ids:\n                    document_tag_id_map[doc_id] = doc_tag_ids\n            if document_tag_id_map:\n                batch_add_document_tag(document_tag_id_map)\n\n        problem_model_list, problem_paragraph_mapping_list = (\n            ProblemParagraphManage(problem_paragraph_object_list, knowledge_id).to_problem_model_list()\n        )\n\n        QuerySet(Document).bulk_create(document_model_list) if len(document_model_list) > 0 else None\n\n        if len(paragraph_model_list) > 0:\n            for document in document_model_list:\n                max_position = Paragraph.objects.filter(document_id=document.id).aggregate(\n                    max_position=Max('position')\n                )['max_position'] or 0\n                sub_list = [p for p in paragraph_model_list if p.document_id == document.id]\n                for i, paragraph in enumerate(sub_list):\n                    paragraph.position = max_position + i + 1\n                QuerySet(Paragraph).bulk_create(sub_list if len(sub_list) > 0 else [])\n\n        bulk_create_in_batches(Problem, problem_model_list, batch_size=1000)\n\n        bulk_create_in_batches(ProblemParagraphMapping, problem_paragraph_mapping_list, batch_size=1000)\n\n        return document_model_list, knowledge_id, workspace_id\n\n    @staticmethod\n    def post_embedding(document_model_list, knowledge_id, workspace_id):\n        for document in document_model_list:\n            DocumentSerializers.Operate(data={\n                'knowledge_id': knowledge_id,\n                'document_id': document.id,\n                'workspace_id': workspace_id\n            }).refresh()\n\n    def execute(self, documents, **kwargs) -> NodeResult:\n\n        document_model_list, knowledge_id, workspace_id = self.save(documents)\n        self.post_embedding(document_model_list, knowledge_id, workspace_id)\n\n        write_content_list = [{\n            \"name\": document.get(\"name\"),\n            \"paragraphs\": [{\n                \"title\": p.get(\"title\"),\n                \"content\": p.get(\"content\"),\n            } for p in document.get(\"paragraphs\")[0:5]]\n        } for document in documents]\n\n        return NodeResult({'write_content': write_content_list}, {})\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'write_content': self.context.get(\"write_content\"),\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_break_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/9/15 12:08\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_break_node/i_loop_break_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： i_loop_break_node.py\n    @date：2025/9/15 12:14\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode\nfrom application.flow.i_step_node import NodeResult\n\n\nclass ConditionSerializer(serializers.Serializer):\n    compare = serializers.CharField(required=True, label=_(\"Comparator\"))\n    value = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"value\"))\n    field = serializers.ListField(required=True, label=_(\"Fields\"))\n\n\nclass LoopBreakNodeSerializer(serializers.Serializer):\n    condition = serializers.CharField(required=True, label=_(\"Condition or|and\"))\n    condition_list = ConditionSerializer(many=True)\n\n\nclass ILoopBreakNode(INode):\n    type = 'loop-break-node'\n    support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return LoopBreakNodeSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data)\n\n    def execute(self, condition, condition_list, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_break_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/9/15 12:16\n    @desc:\n\"\"\"\nfrom .base_loop_break_node import BaseLoopBreakNode\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_break_node/impl/base_loop_break_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： base_loop_break_node.py\n    @date：2025/9/15 12:17\n    @desc:\n\"\"\"\nimport time\nfrom typing import List, Dict\n\nfrom application.flow.compare import compare_handle_list\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.loop_break_node.i_loop_break_node import ILoopBreakNode\n\n\ndef _write_context(step_variable: Dict, global_variable: Dict, node, workflow):\n    if step_variable.get(\"is_break\"):\n        yield \"BREAK\"\n\n    node.context['run_time'] = time.time() - node.context['start_time']\n\n\nclass BaseLoopBreakNode(ILoopBreakNode):\n    def save_context(self, details, workflow_manage):\n        self.context['exception_message'] = details.get('err_message')\n\n    def execute(self, condition, condition_list, **kwargs) -> NodeResult:\n        r = [self.assertion(row.get('field'), row.get('compare'), row.get('value')) for row in\n             condition_list]\n        is_break = all(r) if condition == 'and' else any(r)\n        if is_break:\n            self.node_params['is_result'] = True\n        self.context['is_break'] = is_break\n        return NodeResult({'is_break': is_break}, {},\n                          _write_context=_write_context,\n                          _is_interrupt=lambda n, v, w: is_break)\n\n    def assertion(self, field_list: List[str], compare: str, value):\n        try:\n            value = self.workflow_manage.generate_prompt(value)\n        except Exception as e:\n            pass\n        field_value = None\n        try:\n            field_value = self.workflow_manage.get_reference_field(field_list[0], field_list[1:])\n        except  Exception as e:\n            pass\n        for compare_handler in compare_handle_list:\n            if compare_handler.support(field_list[0], field_list[1:], field_value, compare, value):\n                return compare_handler.compare(field_value, compare, value)\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'is_break': self.context.get('is_break'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_continue_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/9/15 12:08\n    @desc:\n\"\"\"\nfrom .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/loop_continue_node/i_loop_continue_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： i_loop_continue_node.py\n    @date：2025/9/15 12:13\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass ConditionSerializer(serializers.Serializer):\n    compare = serializers.CharField(required=True, label=_(\"Comparator\"))\n    value = serializers.CharField(required=True, label=_(\"value\"))\n    field = serializers.ListField(required=True, label=_(\"Fields\"))\n\n\nclass LoopContinueNodeSerializer(serializers.Serializer):\n    condition = serializers.CharField(required=True, label=_(\"Condition or|and\"))\n    condition_list = ConditionSerializer(many=True)\n\n\nclass ILoopContinueNode(INode):\n    type = 'loop-continue-node'\n    support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return LoopContinueNodeSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data)\n\n    def execute(self, condition, condition_list, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_continue_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/9/15 12:13\n    @desc:\n\"\"\"\nfrom .base_loop_continue_node import BaseLoopContinueNode"
  },
  {
    "path": "apps/application/flow/step_node/loop_continue_node/impl/base_loop_continue_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： base_loop_continue_node.py\n    @date：2025/9/15 12:13\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom application.flow.compare import compare_handle_list\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.loop_continue_node.i_loop_continue_node import ILoopContinueNode\n\n\nclass BaseLoopContinueNode(ILoopContinueNode):\n    def save_context(self, details, workflow_manage):\n        self.context['exception_message'] = details.get('err_message')\n\n    def execute(self, condition, condition_list, **kwargs) -> NodeResult:\n        condition_list = [self.assertion(row.get('field'), row.get('compare'), row.get('value')) for row in\n                          condition_list]\n        is_continue = all(condition_list) if condition == 'and' else any(condition_list)\n        self.context['is_continue'] = is_continue\n        if is_continue:\n            return NodeResult({'is_continue': is_continue, 'branch_id': 'continue'}, {})\n        return NodeResult({'is_continue': is_continue}, {})\n\n    def assertion(self, field_list: List[str], compare: str, value):\n        try:\n            value = self.workflow_manage.generate_prompt(value)\n        except Exception as e:\n            pass\n        field_value = None\n        try:\n            field_value = self.workflow_manage.get_reference_field(field_list[0], field_list[1:])\n        except  Exception as e:\n            pass\n        for compare_handler in compare_handle_list:\n            if compare_handler.support(field_list[0], field_list[1:], field_value, compare, value):\n                return compare_handler.compare(field_value, compare, value)\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            \"is_continue\": self.context.get('is_continue'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py\n    @date：2025/3/11 18:24\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_node/i_loop_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： i_loop_node.py\n    @date：2025/3/11 18:19\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\nfrom common.exception.app_exception import AppApiException\n\n\nclass ILoopNodeSerializer(serializers.Serializer):\n    loop_type = serializers.CharField(required=True, label=_(\"loop_type\"))\n    array = serializers.ListField(required=False, allow_null=True,\n                                  label=_(\"array\"))\n    number = serializers.IntegerField(required=False, allow_null=True,\n                                      label=_(\"number\"))\n    loop_body = serializers.DictField(required=True, label=\"循环体\")\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        loop_type = self.data.get('loop_type')\n        if loop_type == 'ARRAY':\n            array = self.data.get('array')\n            if array is None or len(array) == 0:\n                message = _('{field}, this field is required.', field='array')\n                raise AppApiException(500, message)\n        elif loop_type == 'NUMBER':\n            number = self.data.get('number')\n            if number is None:\n                message = _('{field}, this field is required.', field='number')\n                raise AppApiException(500, message)\n\n\nclass ILoopNode(INode):\n    type = 'loop-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.KNOWLEDGE, WorkflowMode.TOOL]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return ILoopNodeSerializer\n\n    def _run(self):\n        array = self.node_params_serializer.data.get('array')\n        if self.node_params_serializer.data.get('loop_type') == 'ARRAY':\n            array = self.workflow_manage.get_reference_field(\n                array[0],\n                array[1:])\n        return self.execute(**{**self.node_params_serializer.data, \"array\": array}, **self.flow_params_serializer.data)\n\n    def execute(self, loop_type, array, number, loop_body, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py.py\n    @date：2025/3/11 18:24\n    @desc:\n\"\"\"\nfrom .base_loop_node import BaseLoopNode\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_node/impl/base_loop_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： base_loop_node.py\n    @date：2025/3/11 18:24\n    @desc:\n\"\"\"\nimport time\nfrom typing import Dict, List\n\nfrom django.utils.translation import gettext as _\n\nfrom application.flow.common import Answer, WorkflowMode\nfrom application.flow.i_step_node import NodeResult, WorkFlowPostHandler, INode\nfrom application.flow.step_node.loop_node.i_loop_node import ILoopNode\nfrom application.flow.tools import Reasoning\nfrom application.models import ChatRecord\nfrom common.handle.impl.response.loop_to_response import LoopToResponse\nfrom maxkb.const import CONFIG\n\nmax_loop_count = int(CONFIG.get(\"WORKFLOW_LOOP_NODE_MAX_LOOP_COUNT\", 500))\n\n\ndef _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict):\n    return node.context.get('is_interrupt_exec', False)\n\n\ndef _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str,\n                   reasoning_content: str):\n    node.context['answer'] = answer\n    node.context['run_time'] = time.time() - node.context['start_time']\n    node.context['reasoning_content'] = reasoning_content\n    if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):\n        node.answer_text = answer\n\n\ndef write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据 (流式)\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点\n    @param workflow:           工作流管理器\n    \"\"\"\n\n    response = node_variable.get('result')\n    workflow_manage = node_variable.get('workflow_manage')\n    answer = ''\n    reasoning_content = ''\n    for chunk in response:\n        content_chunk = chunk.get('content', '')\n        reasoning_content_chunk = chunk.get('reasoning_content', '')\n        reasoning_content += reasoning_content_chunk\n        answer += content_chunk\n        yield {'content': content_chunk,\n               'reasoning_content': reasoning_content_chunk}\n    runtime_details = workflow_manage.get_runtime_details()\n    _write_context(node_variable, workflow_variable, node, workflow, answer, reasoning_content)\n\n\ndef write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点实例对象\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result')\n    model_setting = node.context.get('model_setting',\n                                     {'reasoning_content_enable': False, 'reasoning_content_end': '</think>',\n                                      'reasoning_content_start': '<think>'})\n    reasoning = Reasoning(model_setting.get('reasoning_content_start'), model_setting.get('reasoning_content_end'))\n    reasoning_result = reasoning.get_reasoning_content(response)\n    reasoning_result_end = reasoning.get_end_reasoning_content()\n    content = reasoning_result.get('content') + reasoning_result_end.get('content')\n    if 'reasoning_content' in response.response_metadata:\n        reasoning_content = response.response_metadata.get('reasoning_content', '')\n    else:\n        reasoning_content = reasoning_result.get('reasoning_content') + reasoning_result_end.get('reasoning_content')\n    _write_context(node_variable, workflow_variable, node, workflow, content, reasoning_content)\n\n\ndef get_answer_list(instance, child_node_node_dict, runtime_node_id):\n    answer_list = instance.get_record_answer_list()\n    for a in answer_list:\n        _v = child_node_node_dict.get(a.get('runtime_node_id'))\n        if _v:\n            a['runtime_node_id'] = runtime_node_id\n            a['child_node'] = _v\n    return answer_list\n\n\ndef insert_or_replace(arr, index, value):\n    if index < len(arr):\n        arr[index] = value  # 替换\n    else:\n        # 在末尾插入足够多的None，然后替换最后一个\n        arr.extend([None] * (index - len(arr) + 1))\n        arr[index] = value\n    return arr\n\n\ndef generate_loop_number(number: int):\n    def i(current_index: int):\n        return iter([(index, index) for index in range(current_index, number)])\n\n    return i\n\n\ndef generate_loop_array(array):\n    def i(current_index: int):\n        return iter([(array[index], index) for index in range(current_index, len(array))])\n\n    return i\n\n\ndef generate_while_loop(current_index: int):\n    index = current_index\n    while True:\n        yield index, index\n        index += 1\n\n\ndef loop(workflow_manage_new_instance, node: INode, generate_loop):\n    loop_global_data = {}\n    break_outer = False\n    is_interrupt_exec = False\n    loop_node_data = node.context.get('loop_node_data') or []\n    loop_answer_data = node.context.get(\"loop_answer_data\") or []\n    start_index = node.context.get(\"current_index\") or 0\n    current_index = start_index\n    node_params = node.node_params\n    start_node_id = node_params.get('child_node', {}).get('runtime_node_id')\n    loop_type = node_params.get('loop_type')\n    start_node_data = None\n    chat_record = None\n    child_node = None\n    if start_node_id:\n        chat_record_id = node_params.get('child_node', {}).get('chat_record_id')\n        child_node = node_params.get('child_node', {}).get('child_node')\n        start_node_data = node_params.get('node_data')\n        chat_record = ChatRecord(id=chat_record_id, answer_text_list=[], answer_text='',\n                                 details=loop_node_data[current_index])\n\n    for item, index in generate_loop(current_index):\n        if 0 < max_loop_count <= index - start_index and loop_type == 'LOOP':\n            raise Exception(_('Exceeding the maximum number of cycles'))\n        \"\"\"\n        指定次数循环\n        @return:\n        \"\"\"\n        instance = workflow_manage_new_instance({'index': index, 'item': item}, loop_global_data, start_node_id,\n                                                start_node_data, chat_record, child_node)\n        response = instance.stream()\n        answer = ''\n        current_index = index\n        reasoning_content = ''\n        child_node_node_dict = {}\n        for chunk in response:\n            if chunk.get('node_type') == 'loop-break-node' and chunk.get('content', '') == 'BREAK':\n                break_outer = True\n                continue\n            child_node = chunk.get('child_node')\n            runtime_node_id = chunk.get('runtime_node_id', '')\n            chat_record_id = chunk.get('chat_record_id', '')\n            child_node_node_dict[runtime_node_id] = {\n                'runtime_node_id': runtime_node_id,\n                'chat_record_id': chat_record_id,\n                'child_node': child_node}\n            content_chunk = (chunk.get('content', '') or '')\n            reasoning_content_chunk = (chunk.get('reasoning_content', '') or '')\n            if chunk.get('real_node_id'):\n                chunk['real_node_id'] = chunk['real_node_id'] + '__' + str(index)\n            reasoning_content += reasoning_content_chunk\n            answer += content_chunk\n            yield chunk\n            if chunk.get('node_status', \"SUCCESS\") == 'ERROR':\n                insert_or_replace(loop_node_data, index, instance.get_runtime_details())\n                insert_or_replace(loop_answer_data, index,\n                                  get_answer_list(instance, child_node_node_dict, node.runtime_node_id))\n                node.context['is_interrupt_exec'] = is_interrupt_exec\n                node.context['loop_node_data'] = loop_node_data\n                node.context['loop_answer_data'] = loop_answer_data\n                node.context[\"index\"] = current_index\n                node.context[\"item\"] = current_index\n                node.status = 500\n                node.err_message = chunk.get('content')\n                return\n            node_type = chunk.get('node_type')\n            if node_type == 'form-node':\n                break_outer = True\n                is_interrupt_exec = True\n        start_node_id = None\n        start_node_data = None\n        chat_record = None\n        child_node = None\n        insert_or_replace(loop_node_data, index, instance.get_runtime_details())\n        insert_or_replace(loop_answer_data, index,\n                          get_answer_list(instance, child_node_node_dict, node.runtime_node_id))\n        instance._cleanup()\n        if break_outer:\n            break\n        if instance.is_the_task_interrupted():\n            break\n    node.context['is_interrupt_exec'] = is_interrupt_exec\n    node.context['loop_node_data'] = loop_node_data\n    node.context['loop_answer_data'] = loop_answer_data\n    node.context[\"index\"] = current_index\n    node.context[\"item\"] = current_index\n    node.context['run_time'] = time.time() - node.context.get(\"start_time\")\n\n\ndef get_tokens(loop_node_data):\n    message_tokens = 0\n    answer_tokens = 0\n    for details in loop_node_data:\n        message_tokens += sum([row.get('message_tokens') for row in details.values() if\n                               'message_tokens' in row and row.get('message_tokens') is not None])\n        answer_tokens += sum([row.get('answer_tokens') for row in details.values() if\n                              'answer_tokens' in row and row.get('answer_tokens') is not None])\n    return {'message_tokens': message_tokens, 'answer_tokens': answer_tokens}\n\n\ndef get_write_context(loop_type, array, number, loop_body):\n    def inner_write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n        if loop_type == 'ARRAY':\n            return loop(node_variable['workflow_manage_new_instance'], node, generate_loop_array(array))\n        if loop_type == 'LOOP':\n            return loop(node_variable['workflow_manage_new_instance'], node, generate_while_loop)\n        return loop(node_variable['workflow_manage_new_instance'], node, generate_loop_number(number))\n\n    return inner_write_context\n\n\nclass LoopWorkFlowPostHandler(WorkFlowPostHandler):\n    def handler(self, workflow):\n        pass\n\n\nclass BaseLoopNode(ILoopNode):\n    def save_context(self, details, workflow_manage):\n        self.context['loop_context_data'] = details.get('loop_context_data')\n        self.context['loop_answer_data'] = details.get('loop_answer_data')\n        self.context['loop_node_data'] = details.get('loop_node_data')\n        self.context['result'] = details.get('result')\n        self.context['params'] = details.get('params')\n        self.context['run_time'] = details.get('run_time')\n        self.context['index'] = details.get('current_index')\n        self.context['item'] = details.get('current_item')\n        for key, value in (details.get('loop_context_data') or {}).items():\n            self.context[key] = value\n        self.answer_text = \"\"\n\n    def get_answer_list(self) -> List[Answer] | None:\n        result = []\n        for answer_list in (self.context.get(\"loop_answer_data\") or []):\n            for a in answer_list:\n                if isinstance(a, dict):\n                    result.append(Answer(**a))\n\n        return result\n\n    def get_loop_context(self):\n        return self.context\n\n    def execute(self, loop_type, array, number, loop_body, **kwargs) -> NodeResult:\n        from application.flow.loop_workflow_manage import LoopWorkflowManage, Workflow\n        from application.flow.knowledge_loop_workflow_manage import KnowledgeLoopWorkflowManage\n        from application.flow.tool_loop_workflow_manage import ToolLoopWorkflowManage\n        def workflow_manage_new_instance(loop_data, global_data, start_node_id=None,\n                                         start_node_data=None, chat_record=None, child_node=None):\n            workflow_mode = {WorkflowMode.APPLICATION: WorkflowMode.APPLICATION_LOOP,\n                             WorkflowMode.KNOWLEDGE: WorkflowMode.KNOWLEDGE_LOOP,\n                             WorkflowMode.TOOL: WorkflowMode.TOOL_LOOP}.get(\n                self.workflow_manage.flow.workflow_mode) or WorkflowMode.APPLICATION\n            c = {WorkflowMode.APPLICATION_LOOP: LoopWorkflowManage,\n                 WorkflowMode.KNOWLEDGE_LOOP: KnowledgeLoopWorkflowManage,\n                 WorkflowMode.TOOL_LOOP: ToolLoopWorkflowManage}.get(workflow_mode) or LoopWorkflowManage\n            workflow_manage = c(Workflow.new_instance(loop_body, workflow_mode),\n                                self.workflow_manage.params,\n                                LoopWorkFlowPostHandler(\n                                    self.workflow_manage.work_flow_post_handler.chat_info),\n                                self.workflow_manage,\n                                loop_data,\n                                self.get_loop_context,\n                                base_to_response=LoopToResponse(),\n                                start_node_id=start_node_id,\n                                start_node_data=start_node_data,\n                                chat_record=chat_record,\n                                child_node=child_node,\n                                is_the_task_interrupted=self.workflow_manage.is_the_task_interrupted\n                                )\n\n            return workflow_manage\n\n        return NodeResult({'workflow_manage_new_instance': workflow_manage_new_instance}, {},\n                          _write_context=get_write_context(loop_type, array, number, loop_body),\n                          _is_interrupt=_is_interrupt_exec)\n\n    def get_loop_context_data(self):\n        fields = self.node.properties.get('config', []).get('fields', []) or []\n        return {f.get('value'): self.context.get(f.get('value')) for f in fields if\n                self.context.get(f.get('value')) is not None}\n\n    def get_details(self, index: int, **kwargs):\n        tokens = get_tokens(self.context.get(\"loop_node_data\"))\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            \"result\": self.context.get('result'),\n            'array': self.node_params_serializer.data.get('array'),\n            'number': self.node_params_serializer.data.get('number'),\n            \"params\": self.context.get('params'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'current_index': self.context.get(\"index\"),\n            \"current_item\": self.context.get(\"item\"),\n            'loop_type': self.node_params_serializer.data.get('loop_type'),\n            'status': self.status,\n            'loop_context_data': self.get_loop_context_data(),\n            'loop_node_data': self.context.get(\"loop_node_data\"),\n            'loop_answer_data': self.context.get(\"loop_answer_data\"),\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n            'message_tokens': tokens.get('message_tokens') or 0,\n            'answer_tokens': tokens.get('answer_tokens') or 0,\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_start_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:30\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_start_node/i_loop_start_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_start_node.py\n    @date：2024/6/3 16:54\n    @desc:\n\"\"\"\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass ILoopStarNode(INode):\n    type = 'loop-start-node'\n    support = [WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL_LOOP]\n\n    def _run(self):\n        return self.execute(**self.flow_params_serializer.data)\n\n    def execute(self, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_start_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:36\n    @desc:\n\"\"\"\nfrom .base_start_node import BaseLoopStartStepNode\n"
  },
  {
    "path": "apps/application/flow/step_node/loop_start_node/impl/base_start_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_start_node.py\n    @date：2024/6/3 17:17\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.loop_start_node.i_loop_start_node import ILoopStarNode\n\n\nclass BaseLoopStartStepNode(ILoopStarNode):\n    def save_context(self, details, workflow_manage):\n        self.context['index'] = details.get('current_index')\n        self.context['item'] = details.get('current_item')\n        self.context['exception_message'] = details.get('err_message')\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        pass\n\n    def execute(self, **kwargs) -> NodeResult:\n        \"\"\"\n        开始节点 初始化全局变量\n        \"\"\"\n        loop_params = self.workflow_manage.loop_params\n        node_variable = {\n            'index': loop_params.get(\"index\"),\n            'item': loop_params.get(\"item\")\n        }\n        if WorkflowMode.APPLICATION_LOOP == self.workflow_manage.flow.workflow_mode:\n            self.workflow_manage.chat_context = self.workflow_manage.get_chat_info().get_chat_variable()\n        return NodeResult(node_variable, {})\n\n    def get_details(self, index: int, **kwargs):\n        global_fields = []\n        for field in self.node.properties.get('config')['globalFields']:\n            key = field['value']\n            global_fields.append({\n                'label': field['label'],\n                'key': key,\n                'value': self.workflow_manage.context[key] if key in self.workflow_manage.context else ''\n            })\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            \"current_index\": self.context.get('index'),\n            \"current_item\": self.context.get('item'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/mcp_node/__init__.py",
    "content": "# coding=utf-8\n\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/mcp_node/i_mcp_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass McpNodeSerializer(serializers.Serializer):\n    mcp_servers = serializers.JSONField(required=True, label=_(\"Mcp servers\"))\n    mcp_server = serializers.CharField(required=True, label=_(\"Mcp server\"))\n    mcp_tool = serializers.CharField(required=True, label=_(\"Mcp tool\"))\n    mcp_tool_id = serializers.CharField(required=False, label=_(\"Mcp tool\"), allow_null=True, allow_blank=True)\n    mcp_source = serializers.CharField(required=False, label=_(\"Mcp source\"), allow_blank=True, allow_null=True)\n    tool_params = serializers.DictField(required=True, label=_(\"Tool parameters\"))\n\n\nclass IMcpNode(INode):\n    type = 'mcp-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return McpNodeSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, mcp_servers, mcp_server, mcp_tool, mcp_tool_id, mcp_source, tool_params, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/mcp_node/impl/__init__.py",
    "content": "# coding=utf-8\n\nfrom .base_mcp_node import BaseMcpNode\n"
  },
  {
    "path": "apps/application/flow/step_node/mcp_node/impl/base_mcp_node.py",
    "content": "# coding=utf-8\nimport asyncio\nimport json\nfrom typing import List\n\nfrom django.db.models import QuerySet\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.mcp_node.i_mcp_node import IMcpNode\nfrom tools.models import Tool\n\n\nclass BaseMcpNode(IMcpNode):\n    def save_context(self, details, workflow_manage):\n        self.context['result'] = details.get('result')\n        self.context['tool_params'] = details.get('tool_params')\n        self.context['mcp_tool'] = details.get('mcp_tool')\n        self.context['exception_message'] = details.get('err_message')\n\n    def execute(self, mcp_servers, mcp_server, mcp_tool, mcp_tool_id, mcp_source, tool_params, **kwargs) -> NodeResult:\n        if mcp_source == 'referencing':\n            if not mcp_tool_id:\n                raise ValueError(\"MCP tool ID is required when mcp_source is 'referencing'.\")\n            tool = QuerySet(Tool).filter(id=mcp_tool_id).first()\n            if not tool:\n                raise ValueError(f\"Tool with ID {mcp_tool_id} not found.\")\n            if not tool.is_active:\n                raise ValueError(f\"Tool with ID {mcp_tool_id} is inactive.\")\n            servers = json.loads(tool.code)\n            servers = self.handle_variables(servers)  # 处理servers中的变量\n            params = json.loads(json.dumps(tool_params))\n            params = self.handle_variables(params)\n        else:\n            servers = json.loads(mcp_servers)\n            servers = self.handle_variables(servers)  # 处理servers中的变量\n            params = json.loads(json.dumps(tool_params))\n            params = self.handle_variables(params)\n\n        async def call_tool(t, a):\n            client = MultiServerMCPClient(servers)\n            async with client.session(mcp_server) as s:\n                return await s.call_tool(t, a)\n\n        res = asyncio.run(call_tool(mcp_tool, params))\n        return NodeResult(\n            {'result': [content.text for content in res.content], 'tool_params': params, 'mcp_tool': mcp_tool}, {})\n\n    def handle_variables(self, tool_params):\n        # 处理参数中的变量\n        for k, v in tool_params.items():\n            if type(v) == str:\n                tool_params[k] = self.workflow_manage.generate_prompt(tool_params[k])\n            if type(v) == dict:\n                self.handle_variables(v)\n            if (type(v) == list) and (type(v[0]) == str):\n                tool_params[k] = self.get_reference_content(v)\n        return tool_params\n\n    def get_reference_content(self, fields: List[str]):\n        return self.workflow_manage.get_reference_field(\n            fields[0],\n            fields[1:])\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'type': self.node.type,\n            'mcp_tool': self.context.get('mcp_tool'),\n            'tool_params': self.context.get('tool_params'),\n            'result': self.context.get('result'),\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/parameter_extraction_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/10/13 14:56\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/parameter_extraction_node/i_parameter_extraction_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass VariableSplittingNodeParamsSerializer(serializers.Serializer):\n    input_variable = serializers.ListField(required=True,\n                                           label=_(\"input variable\"))\n\n    variable_list = serializers.ListField(required=True,\n                                          label=_(\"Split variables\"))\n\n    model_params_setting = serializers.DictField(required=False,\n                                                 label=_(\"Model parameter settings\"))\n\n    model_id = serializers.CharField(required=True, label=_(\"Model id\"))\n\n\nclass IParameterExtractionNode(INode):\n    type = 'parameter-extraction-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return VariableSplittingNodeParamsSerializer\n\n    def _run(self):\n        input_variable = self.workflow_manage.get_reference_field(\n            self.node_params_serializer.data.get('input_variable')[0],\n            self.node_params_serializer.data.get('input_variable')[1:])\n        return self.execute(input_variable, self.node_params_serializer.data['variable_list'],\n                            self.node_params_serializer.data['model_params_setting'],\n                            self.node_params_serializer.data['model_id'])\n\n    def execute(self, input_variable, variable_list, model_params_setting, model_id, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/parameter_extraction_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/10/13 15:01\n    @desc:\n\"\"\"\nfrom .base_parameter_extraction_node import *\n"
  },
  {
    "path": "apps/application/flow/step_node/parameter_extraction_node/impl/base_parameter_extraction_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： base_variable_splitting_node.py\n    @date：2025/10/13 15:02\n    @desc:\n\"\"\"\nimport json\nimport re\n\nfrom django.db.models import QuerySet\nfrom langchain_core.messages import HumanMessage\nfrom langchain_core.prompts import PromptTemplate\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.parameter_extraction_node.i_parameter_extraction_node import IParameterExtractionNode\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential\n\nprompt = \"\"\"\nPlease strictly process the text according to the following requirements:\n**Task**: \nExtract specified field information from given text\n\n**Enter text**: \n{{question}}\n\n**Extract configuration**: \n{{properties}}\n\n**Rule**:\n- Strictly follow the data and field of Extract configuration\n- If not found, use null value\n- Only return pure JSON without additional text\n- Keep the string format neat\n\"\"\"\n\n\ndef get_default_model_params_setting(model_id):\n    model = QuerySet(Model).filter(id=model_id).first()\n    credential = get_model_credential(model.provider, model.model_type, model.model_name)\n    model_params_setting = credential.get_model_params_setting_form(\n        model.model_name).get_default_form_data()\n    return model_params_setting\n\n\ndef generate_properties(variable_list):\n    return {variable['field']: {'type': variable['parameter_type'], 'description': (variable.get('desc') or \"\"),\n                                'title': variable['label']} for variable in\n            variable_list}\n\n\ndef generate_example(variable_list):\n    return {variable['field']: None for variable in variable_list}\n\n\ndef generate_content(input_variable, variable_list):\n    properties = generate_properties(variable_list)\n    prompt_template = PromptTemplate.from_template(prompt, template_format='jinja2')\n    value = prompt_template.format(properties=properties, question=input_variable)\n    return value\n\n\ndef json_loads(response, expected_fields):\n    if not response or not isinstance(response, str):\n        return {field: None for field in expected_fields}\n\n    cleaned = response.strip()\n\n    extraction_strategies = [\n        lambda: json.loads(cleaned),\n        lambda: json.loads(re.search(r'```(?:json)?\\s*(\\{.*?\\})\\s*```', cleaned, re.DOTALL).group(1)),\n        lambda: json.loads(re.search(r'(\\{[\\s\\S]*\\})', cleaned).group(1)),\n    ]\n    for strategy in extraction_strategies:\n        try:\n            result = strategy()\n            return result\n        except:\n            continue\n    return generate_example(expected_fields)\n\n\nclass BaseParameterExtractionNode(IParameterExtractionNode):\n\n    def save_context(self, details, workflow_manage):\n        for key, value in details.get('result').items():\n            self.context[key] = value\n        self.context['result'] = details.get('result')\n        self.context['request'] = details.get('request')\n        self.context['exception_message'] = details.get('err_message')\n\n    def execute(self, input_variable, variable_list, model_params_setting, model_id, **kwargs) -> NodeResult:\n        input_variable = str(input_variable)\n        self.context['request'] = input_variable\n        if model_params_setting is None:\n            model_params_setting = get_default_model_params_setting(model_id)\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,\n                                                              **model_params_setting)\n        content = generate_content(input_variable, variable_list)\n        response = chat_model.invoke([HumanMessage(content=content)])\n        result = json_loads(response.content, variable_list)\n        return NodeResult({'result': result, **result}, {})\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'request': self.context.get('request'),\n            'result': self.context.get('result'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/question_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:30\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/question_node/i_question_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_chat_node.py\n    @date：2024/6/4 13:58\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass QuestionNodeSerializer(serializers.Serializer):\n    model_id = serializers.CharField(required=True, label=_(\"Model id\"))\n    system = serializers.CharField(required=False, allow_blank=True, allow_null=True,\n                                   label=_(\"Role Setting\"))\n    prompt = serializers.CharField(required=True, label=_(\"Prompt word\"))\n    # 多轮对话数量\n    dialogue_number = serializers.IntegerField(required=True, label=\n    _(\"Number of multi-round conversations\"))\n\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n    model_params_setting = serializers.DictField(required=False,\n                                                 label=_(\"Model parameter settings\"))\n\n\nclass IQuestionNode(INode):\n    type = 'question-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return QuestionNodeSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id,\n                model_params_setting=None,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/question_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:35\n    @desc:\n\"\"\"\nfrom .base_question_node import BaseQuestionNode\n"
  },
  {
    "path": "apps/application/flow/step_node/question_node/impl/base_question_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_question_node.py\n    @date：2024/6/4 14:30\n    @desc:\n\"\"\"\nimport re\nimport time\nfrom functools import reduce\nfrom typing import List, Dict\n\nfrom django.db.models import QuerySet\nfrom langchain_core.messages import BaseMessage, HumanMessage, SystemMessage\n\nfrom application.flow.i_step_node import NodeResult, INode\nfrom application.flow.step_node.question_node.i_question_node import IQuestionNode\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model_instance_by_model_workspace_id, get_model_credential\n\n\ndef _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):\n    chat_model = node_variable.get('chat_model')\n    message_tokens = chat_model.get_num_tokens_from_messages(node_variable.get('message_list'))\n    answer_tokens = chat_model.get_num_tokens(answer)\n    node.context['message_tokens'] = message_tokens\n    node.context['answer_tokens'] = answer_tokens\n    node.context['answer'] = answer\n    node.context['history_message'] = node_variable['history_message']\n    node.context['question'] = node_variable['question']\n    node.context['run_time'] = time.time() - node.context['start_time']\n    if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):\n        node.answer_text = answer\n\n\ndef write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据 (流式)\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result')\n    answer = ''\n    for chunk in response:\n        answer += chunk.content\n        yield chunk.content\n    _write_context(node_variable, workflow_variable, node, workflow, answer)\n\n\ndef write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点实例对象\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result')\n    answer = response.content\n    _write_context(node_variable, workflow_variable, node, workflow, answer)\n\n\ndef get_default_model_params_setting(model_id):\n    model = QuerySet(Model).filter(id=model_id).first()\n    credential = get_model_credential(model.provider, model.model_type, model.model_name)\n    model_params_setting = credential.get_model_params_setting_form(\n        model.model_name).get_default_form_data()\n    return model_params_setting\n\n\nclass BaseQuestionNode(IQuestionNode):\n    def save_context(self, details, workflow_manage):\n        self.context['run_time'] = details.get('run_time')\n        self.context['question'] = details.get('question')\n        self.context['answer'] = details.get('answer')\n        self.context['message_tokens'] = details.get('message_tokens')\n        self.context['answer_tokens'] = details.get('answer_tokens')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n\n    def execute(self, model_id, system, prompt, dialogue_number, history_chat_record, stream, chat_id, chat_record_id,\n                model_params_setting=None,\n                **kwargs) -> NodeResult:\n        if model_params_setting is None:\n            model_params_setting = get_default_model_params_setting(model_id)\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,\n                                                              **model_params_setting)\n        history_message = self.get_history_message(history_chat_record, dialogue_number)\n        self.context['history_message'] = history_message\n        question = self.generate_prompt_question(prompt)\n        self.context['question'] = question.content\n        system = self.workflow_manage.generate_prompt(system)\n        self.context['system'] = system\n        message_list = self.generate_message_list(system, prompt, history_message)\n        self.context['message_list'] = message_list\n        if stream:\n            r = chat_model.stream(message_list)\n            return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,\n                               'history_message': history_message, 'question': question.content}, {},\n                              _write_context=write_context_stream)\n        else:\n            r = chat_model.invoke(message_list)\n            return NodeResult({'result': r, 'chat_model': chat_model, 'message_list': message_list,\n                               'history_message': history_message, 'question': question.content}, {},\n                              _write_context=write_context)\n\n    @staticmethod\n    def get_history_message(history_chat_record, dialogue_number):\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = reduce(lambda x, y: [*x, *y], [\n            [history_chat_record[index].get_human_message(), history_chat_record[index].get_ai_message()]\n            for index in\n            range(start_index if start_index > 0 else 0, len(history_chat_record))], [])\n        for message in history_message:\n            if isinstance(message.content, str):\n                message.content = re.sub('<form_rander>[\\d\\D]*?<\\/form_rander>', '', message.content)\n        return history_message\n\n    def generate_prompt_question(self, prompt):\n        return HumanMessage(self.workflow_manage.generate_prompt(prompt))\n\n    def generate_message_list(self, system: str, prompt: str, history_message):\n        if system is not None and len(system) > 0:\n            return [SystemMessage(self.workflow_manage.generate_prompt(system)), *history_message,\n                    HumanMessage(self.workflow_manage.generate_prompt(prompt))]\n        else:\n            return [*history_message, HumanMessage(self.workflow_manage.generate_prompt(prompt))]\n\n    @staticmethod\n    def reset_message_list(message_list: List[BaseMessage], answer_text):\n        result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for\n                  message\n                  in\n                  message_list]\n        result.append({'role': 'ai', 'content': answer_text})\n        return result\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'system': self.context.get('system'),\n            'history_message': [{'content': message.content, 'role': message.type} for message in\n                                (self.context.get('history_message') if self.context.get(\n                                    'history_message') is not None else [])],\n            'question': self.context.get('question'),\n            'answer': self.context.get('answer'),\n            'type': self.node.type,\n            'message_tokens': self.context.get('message_tokens'),\n            'answer_tokens': self.context.get('answer_tokens'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/reranker_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py\n    @date：2024/9/4 11:37\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/reranker_node/i_reranker_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： i_reranker_node.py\n    @date：2024/9/4 10:40\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass RerankerSettingSerializer(serializers.Serializer):\n    # 需要查询的条数\n    top_n = serializers.IntegerField(required=True,\n                                     label=_(\"Reference segment number\"))\n    # 相似度 0-1之间\n    similarity = serializers.FloatField(required=True, max_value=2, min_value=0,\n                                        label=_(\"Reference segment number\"))\n    max_paragraph_char_number = serializers.IntegerField(required=True,\n                                                         label=_(\"Maximum number of words in a quoted segment\"))\n\n\nclass RerankerStepNodeSerializer(serializers.Serializer):\n    reranker_setting = RerankerSettingSerializer(required=True)\n\n    question_reference_address = serializers.ListField(required=True)\n    reranker_model_id = serializers.UUIDField(required=True)\n    reranker_reference_list = serializers.ListField(required=True, child=serializers.ListField(required=True))\n    show_knowledge = serializers.BooleanField(required=True,\n                                              label=_(\"The results are displayed in the knowledge sources\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n\n\nclass IRerankerNode(INode):\n    type = 'reranker-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return RerankerStepNodeSerializer\n\n    def _run(self):\n        question = self.workflow_manage.get_reference_field(\n            self.node_params_serializer.data.get('question_reference_address')[0],\n            self.node_params_serializer.data.get('question_reference_address')[1:])\n        reranker_list = [self.workflow_manage.get_reference_field(\n            reference[0],\n            reference[1:]) for reference in\n            self.node_params_serializer.data.get('reranker_reference_list')]\n        return self.execute(**self.node_params_serializer.data, question=str(question),\n\n                            reranker_list=reranker_list)\n\n    def execute(self, question, reranker_setting, reranker_list, reranker_model_id, show_knowledge,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/reranker_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py\n    @date：2024/9/4 11:39\n    @desc:\n\"\"\"\nfrom .base_reranker_node import *\n"
  },
  {
    "path": "apps/application/flow/step_node/reranker_node/impl/base_reranker_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： base_reranker_node.py\n    @date：2024/9/4 11:41\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom langchain_core.documents import Document\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.reranker_node.i_reranker_node import IRerankerNode\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\n\n\ndef merge_reranker_list(reranker_list, result=None):\n    if result is None:\n        result = []\n    for document in reranker_list:\n        if isinstance(document, list):\n            merge_reranker_list(document, result)\n        elif isinstance(document, dict):\n            content = document.get('title', '') + document.get('content', '')\n            title = document.get(\"title\")\n            result.append(\n                Document(page_content=str(document) if len(content) == 0 else content,\n                         metadata={'title': title, **document}))\n        else:\n            result.append(Document(page_content=str(document), metadata={}))\n    return result\n\n\ndef filter_result(document_list: List[Document], max_paragraph_char_number, top_n, similarity):\n    use_len = 0\n    result = []\n    for index in range(len(document_list)):\n        document = document_list[index]\n        if use_len >= max_paragraph_char_number or index >= top_n or document.metadata.get(\n                'relevance_score') < similarity:\n            break\n        content = document.page_content[0:max_paragraph_char_number - use_len]\n        use_len = use_len + len(content)\n        result.append({'page_content': content, 'metadata': document.metadata})\n    return result\n\n\ndef reset_result_list(result_list: List[Document], document_list: List[Document]):\n    r = []\n    document_list = document_list.copy()\n    for result in result_list:\n        filter_result_list = [document for document in document_list if document.page_content == result.page_content]\n        if len(filter_result_list) > 0:\n            item = filter_result_list[0]\n            document_list.remove(item)\n            r.append(Document(page_content=item.page_content,\n                              metadata={**item.metadata, 'relevance_score': result.metadata.get('relevance_score')}))\n        else:\n            r.append(result)\n    return r\n\n\ndef get_none_result(question):\n    return NodeResult(\n        {'document_list': [], 'question': question,\n         'result_list': [], 'result': ''}, {})\n\n\ndef reset_metadata(metadata):\n    meta = metadata.get('meta')\n    if isinstance(metadata.get('meta'), dict):\n        if not meta.get('allow_download', False):\n            metadata['meta'] = {'allow_download': False}\n    return metadata\n\n\nclass BaseRerankerNode(IRerankerNode):\n    def save_context(self, details, workflow_manage):\n        self.context['document_list'] = details.get('document_list', [])\n        self.context['question'] = details.get('question')\n        self.context['run_time'] = details.get('run_time')\n        self.context['result_list'] = details.get('result_list')\n        self.context['result'] = details.get('result')\n        self.context['exception_message'] = details.get('err_message')\n\n    def execute(self, question, reranker_setting, reranker_list, reranker_model_id, show_knowledge,\n                **kwargs) -> NodeResult:\n        self.context['show_knowledge'] = show_knowledge\n        documents = merge_reranker_list(reranker_list)\n        documents = [d for d in documents if d.page_content and len(d.page_content) > 0]\n        if len(documents) == 0:\n            return get_none_result(question)\n        top_n = reranker_setting.get('top_n', 3)\n        self.context['document_list'] = [\n            {'page_content': document.page_content, 'metadata': reset_metadata(document.metadata)} for\n            document in documents]\n        self.context['question'] = question\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        reranker_model = get_model_instance_by_model_workspace_id(reranker_model_id,\n                                                                  workspace_id,\n                                                                  top_n=top_n)\n        result = reranker_model.compress_documents(\n            documents,\n            question)\n        similarity = reranker_setting.get('similarity', 0.6)\n        max_paragraph_char_number = reranker_setting.get('max_paragraph_char_number', 5000)\n        result = reset_result_list(result, documents)\n        r = filter_result(result, max_paragraph_char_number, top_n, similarity)\n        return NodeResult({'result_list': r, 'result': ''.join([item.get('page_content') for item in r])}, {})\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'show_knowledge': self.context.get('show_knowledge'),\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'document_list': self.context.get('document_list'),\n            \"question\": self.context.get('question'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'reranker_setting': self.node_params_serializer.data.get('reranker_setting'),\n            'result_list': self.context.get('result_list'),\n            'result': self.context.get('result'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/search_document_node/__init__.py",
    "content": "from .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/search_document_node/i_search_document_node.py",
    "content": "# coding=utf-8\nfrom typing import Type, List\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass SearchDocumentStepNodeSerializer(serializers.Serializer):\n    knowledge_id_list = serializers.ListField(\n        required=False, child=serializers.UUIDField(required=True),\n        label=_(\"knowledge id list\"), default=list\n    )\n    search_mode = serializers.ChoiceField(\n        required=False, choices=['auto', 'custom'], label=_(\"search mode\"), default='auto'\n    )\n    search_scope_type = serializers.ChoiceField(\n        required=False, choices=['custom', 'referencing'], label=_(\"search scope type\"),\n        allow_null=True, default='custom'\n    )\n    search_scope_source = serializers.ChoiceField(\n        required=False, choices=['document', 'knowledge'],\n        label=_(\"search scope variable type\"), default='knowledge'\n    )\n    search_scope_reference = serializers.ListField(\n        required=False, label=_(\"search scope variable\"), default=list\n    )\n    question_reference = serializers.ListField(\n        required=False, label=_(\"question reference address\"), default=list\n    )\n    search_condition_type = serializers.ChoiceField(\n        required=False, choices=['AND', 'OR'], label=_(\"search condition type\"), default='AND'\n    )\n    search_condition_list = serializers.ListField(\n        required=False, label=_(\"search condition list\"), default=list\n    )\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n\n\nclass ISearchDocumentStepNode(INode):\n    type = 'search-document-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return SearchDocumentStepNodeSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, knowledge_id_list: List, search_mode: str, search_scope_type: str, search_scope_source: str,\n                search_scope_reference: List, question_reference: List, search_condition_type: str,\n                search_condition_list: List,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/search_document_node/impl/__init__.py",
    "content": "from .base_search_document_node import BaseSearchDocumentNode\n"
  },
  {
    "path": "apps/application/flow/step_node/search_document_node/impl/base_search_document_node.py",
    "content": "# coding=utf-8\nfrom typing import List\n\nimport jieba\nfrom django.db.models import Q\nfrom django.db.models import QuerySet\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.search_document_node.i_search_document_node import ISearchDocumentStepNode\nfrom common.constants.permission_constants import RoleConstants\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.utils.shared_resource_auth import filter_authorized_ids\nfrom knowledge.models import Document, DocumentTag, Knowledge\n\n\nclass BaseSearchDocumentNode(ISearchDocumentStepNode):\n    def save_context(self, details, workflow_manage):\n        self.context['document_list'] = details.get('document_list')\n        self.context['knowledge_list'] = details.get('knowledge_list')\n        self.context['document_items'] = details.get('document_items')\n        self.context['knowledge_items'] = details.get('knowledge_items')\n        self.context['question'] = details.get('question')\n        self.context['run_time'] = details.get('run_time')\n        self.context['exception_message'] = details.get('err_message')\n\n    def get_reference_content(self, fields: List[str]):\n        return self.workflow_manage.get_reference_field(fields[0], fields[1:])\n\n    def execute(self, knowledge_id_list: List, search_mode: str, search_scope_type: str, search_scope_source: str,\n                search_scope_reference: List, question_reference: List, search_condition_type: str,\n                search_condition_list: List,\n                **kwargs) -> NodeResult:\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n\n        if search_scope_type == 'custom':  # 手动选择知识库\n            knowledge_id_list = filter_authorized_ids('knowledge', knowledge_id_list, workspace_id)\n            document_id_list = QuerySet(Document).filter(\n                knowledge_id__in=knowledge_id_list\n            ).values_list('id', flat=True)\n        else:  # 引用上一步知识库/文档\n            if search_scope_source == 'document':  # 文档\n                document_id_list = self.get_reference_content(search_scope_reference)\n            else:  # 知识库\n                ref_knowledge_ids = filter_authorized_ids('knowledge',\n                                                          self.get_reference_content(search_scope_reference),\n                                                          workspace_id)\n                document_id_list = QuerySet(Document).filter(\n                    knowledge_id__in=ref_knowledge_ids\n                ).values_list('id', flat=True)\n\n        # 权限过滤\n        get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized')\n        chat_user_type = self.workflow_manage.get_body().get('chat_user_type')\n\n        if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type:\n            actual_knowledge_ids = list(\n                QuerySet(Document).filter(id__in=document_id_list)\n                .values_list('knowledge_id', flat=True).distinct()\n            )\n            authorized_knowledge_ids = get_knowledge_list_of_authorized(\n                self.workflow_manage.get_body().get('chat_user_id'),\n                actual_knowledge_ids\n            )\n            document_id_list = QuerySet(Document).filter(\n                id__in=document_id_list,\n                knowledge_id__in=authorized_knowledge_ids\n            ).values_list('id', flat=True)\n\n        if search_mode == 'auto':  # 通过问题自动检索\n            matched_doc_ids = self.handle_auto_tags(document_id_list, question_reference)\n\n            final_document_ids = list(matched_doc_ids)\n        else:  # 自定义检索条件\n            matched_document_ids = self.handle_custom_tags(\n                document_id_list, search_condition_list, search_condition_type\n            )\n\n            final_document_ids = list(matched_document_ids)\n\n        # UUID to str\n        final_document_ids = [str(doc_id) for doc_id in final_document_ids]\n        document_items = QuerySet(Document).filter(id__in=final_document_ids).values()\n        final_knowledge_ids = list(set(str(doc['knowledge_id']) for doc in document_items))\n        knowledge_items = QuerySet(Knowledge).filter(id__in=final_knowledge_ids).values()\n\n        return NodeResult({\n            'document_list': final_document_ids,\n            'document_items': list(document_items),\n            'knowledge_list': final_knowledge_ids,\n            'knowledge_items': list(knowledge_items)\n        }, {})\n\n    def handle_auto_tags(self, document_id_list: list, question_reference: list):\n        question = self.get_reference_content(question_reference)\n\n        # 使用jieba分词\n        keywords = jieba.lcut(question)\n        if not keywords:\n            return set()\n\n        # 构建OR查询,一次性获取所有匹配的文档\n        q_objects = Q()\n        for keyword in keywords:\n            q_objects |= Q(tag__value__icontains=keyword)\n\n        # 单次数据库查询\n        matched_doc_ids = set(\n            QuerySet(DocumentTag)\n            .filter(document_id__in=document_id_list)\n            .filter(q_objects)\n            .values_list('document_id', flat=True)\n            .distinct()\n        )\n\n        return matched_doc_ids\n\n    def handle_custom_tags(self, document_id_list: List, search_condition_list: list, search_condition_type: str):\n\n        if not search_condition_list:\n            return set(document_id_list)\n\n        if search_condition_type == 'AND':\n            # AND逻辑:使用子查询和聚合\n            matched_doc_ids = set(document_id_list)\n\n            for condition in search_condition_list:\n                tag_key = condition['key']\n                field_value = self.workflow_manage.generate_prompt(condition['value'])\n                compare_type = condition['compare']\n\n                if not field_value or field_value == 'None' or len(field_value) == 0:\n                    continue\n\n                # 构建查询条件\n                if compare_type == 'not_contain':\n                    # 反向查询:找出包含该标签的文档,然后排除\n                    exclude_docs = set(QuerySet(DocumentTag).filter(\n                        document_id__in=matched_doc_ids,\n                        tag__key=tag_key,\n                        tag__value__icontains=field_value\n                    ).values_list('document_id', flat=True).distinct())\n\n                    matched_doc_ids = matched_doc_ids - exclude_docs\n                else:\n                    if compare_type == 'contain':\n                        q_filter = Q(tag__key=tag_key, tag__value__icontains=field_value)\n                    elif compare_type == 'eq':\n                        q_filter = Q(tag__key=tag_key, tag__value=field_value)\n                    else:\n                        continue\n\n                    # 单次查询获取符合条件的文档\n                    tag_docs = set(QuerySet(DocumentTag).filter(\n                        document_id__in=matched_doc_ids\n                    ).filter(q_filter).values_list('document_id', flat=True).distinct())\n\n                    matched_doc_ids = matched_doc_ids.intersection(tag_docs)\n\n            return matched_doc_ids\n\n        else:\n            # OR逻辑\n            matched_docs = set()\n\n            for condition in search_condition_list:\n                tag_key = condition['key']\n                field_value = self.workflow_manage.generate_prompt(condition['value'])\n                compare_type = condition['compare']\n\n                if not field_value or field_value == 'None' or len(field_value) == 0:\n                    continue\n\n                if compare_type == 'not_contain':\n                    # 反向查询:找出包含该标签的文档,然后用全集减去\n                    exclude_docs = set(QuerySet(DocumentTag).filter(\n                        document_id__in=document_id_list,\n                        tag__key=tag_key,\n                        tag__value__icontains=field_value\n                    ).values_list('document_id', flat=True).distinct())\n\n                    matched_docs = matched_docs.union(set(document_id_list) - exclude_docs)\n                else:\n                    if compare_type == 'contain':\n                        q_filter = Q(tag__key=tag_key, tag__value__icontains=field_value)\n                    elif compare_type == 'eq':\n                        q_filter = Q(tag__key=tag_key, tag__value=field_value)\n                    else:\n                        continue\n\n                    docs = set(QuerySet(DocumentTag).filter(\n                        document_id__in=document_id_list\n                    ).filter(q_filter).values_list('document_id', flat=True).distinct())\n\n                    matched_docs = matched_docs.union(docs)\n\n            return matched_docs\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            'question': self.context.get('question'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'document_list': self.context.get('document_list'),\n            'knowledge_list': self.context.get('knowledge_list'),\n            'document_items': self.context.get('document_items'),\n            'knowledge_items': self.context.get('knowledge_items'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/search_knowledge_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:30\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/search_knowledge_node/i_search_knowledge_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_search_dataset_node.py\n    @date：2024/6/3 17:52\n    @desc:\n\"\"\"\nimport re\nfrom typing import Type\n\nfrom django.core import validators\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\nfrom common.utils.common import flat_map\n\n\nclass DatasetSettingSerializer(serializers.Serializer):\n    # 需要查询的条数\n    top_n = serializers.IntegerField(required=True,\n                                     label=_(\"Reference segment number\"))\n    # 相似度 0-1之间\n    similarity = serializers.FloatField(required=True, max_value=2, min_value=0,\n                                        label=_('similarity'))\n    search_mode = serializers.CharField(required=True, validators=[\n        validators.RegexValidator(regex=re.compile(\"^embedding|keywords|blend$\"),\n                                  message=_(\"The type only supports embedding|keywords|blend\"), code=500)\n    ], label=_(\"Retrieval Mode\"))\n    max_paragraph_char_number = serializers.IntegerField(required=True,\n                                                         label=_(\"Maximum number of words in a quoted segment\"))\n\n\nclass SearchDatasetStepNodeSerializer(serializers.Serializer):\n    # 需要查询的数据集id列表\n    knowledge_id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True),\n                                              label=_(\"Dataset id list\"))\n    knowledge_setting = DatasetSettingSerializer(required=True)\n\n    question_reference_address = serializers.ListField(required=True)\n\n    show_knowledge = serializers.BooleanField(required=True,\n                                              label=_(\"The results are displayed in the knowledge sources\"))\n    search_scope_type = serializers.ChoiceField(\n        required=False, choices=['custom', 'referencing'], label=_(\"search scope type\"),\n        allow_null=True, default='custom'\n    )\n    search_scope_source = serializers.ChoiceField(\n        required=False, choices=['document', 'knowledge'],\n        label=_(\"search scope variable type\"), default='knowledge'\n    )\n    search_scope_reference = serializers.ListField(\n        required=False, label=_(\"search scope variable\"), default=list\n    )\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n\n\ndef get_paragraph_list(chat_record, node_id):\n    return flat_map([chat_record.details[key].get('paragraph_list', []) for key in chat_record.details if\n                     (chat_record.details[\n                          key].get('type', '') == 'search-dataset-node') and chat_record.details[key].get(\n                         'paragraph_list', []) is not None and key == node_id])\n\n\nclass ISearchKnowledgeStepNode(INode):\n    type = 'search-knowledge-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return SearchDatasetStepNodeSerializer\n\n    def _run(self):\n        question = self.workflow_manage.get_reference_field(\n            self.node_params_serializer.data.get('question_reference_address')[0],\n            self.node_params_serializer.data.get('question_reference_address')[1:])\n        exclude_paragraph_id_list = []\n        if self.flow_params_serializer.data.get('re_chat', False):\n            history_chat_record = self.flow_params_serializer.data.get('history_chat_record', [])\n            paragraph_id_list = [p.get('id') for p in flat_map(\n                [get_paragraph_list(chat_record, self.runtime_node_id) for chat_record in history_chat_record if\n                 chat_record.problem_text == question])]\n            exclude_paragraph_id_list = list(set(paragraph_id_list))\n\n        return self.execute(**self.node_params_serializer.data, question=str(question),\n                            exclude_paragraph_id_list=exclude_paragraph_id_list)\n\n    def execute(self, dataset_id_list, dataset_setting, question, show_knowledge, search_scope_type,\n                search_scope_source,\n                search_scope_reference,\n                exclude_paragraph_id_list=None,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/search_knowledge_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:35\n    @desc:\n\"\"\"\nfrom .base_search_knowledge_node import BaseSearchKnowledgeNode\n"
  },
  {
    "path": "apps/application/flow/step_node/search_knowledge_node/impl/base_search_knowledge_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_search_dataset_node.py\n    @date：2024/6/4 11:56\n    @desc:\n\"\"\"\nimport os\nfrom typing import List, Dict\n\nfrom django.db import connection\nfrom django.db.models import QuerySet\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.search_knowledge_node.i_search_knowledge_node import ISearchKnowledgeStepNode\nfrom common.config.embedding_config import VectorStore\nfrom common.constants.permission_constants import RoleConstants\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.db.search import native_search\nfrom common.utils.common import get_file_content\nfrom common.utils.shared_resource_auth import filter_authorized_ids\nfrom knowledge.models import Document, Paragraph, Knowledge, SearchMode\nfrom maxkb.conf import PROJECT_DIR\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\n\n\ndef get_embedding_id(dataset_id_list):\n    dataset_list = QuerySet(Knowledge).filter(id__in=dataset_id_list)\n    if len(set([dataset.embedding_model_id for dataset in dataset_list])) > 1:\n        raise Exception(\"关联知识库的向量模型不一致，无法召回分段。\")\n    if len(dataset_list) == 0:\n        raise Exception(\"知识库设置错误,请重新设置知识库\")\n    return dataset_list[0].embedding_model_id\n\n\ndef get_none_result(question):\n    return NodeResult(\n        {'paragraph_list': [], 'is_hit_handling_method': [], 'question': question, 'data': '',\n         'directly_return': ''}, {})\n\n\ndef reset_title(title):\n    if title is None or len(title.strip()) == 0:\n        return \"\"\n    else:\n        return f\"#### {title}\\n\"\n\n\ndef reset_meta(meta):\n    if not meta.get('allow_download', False):\n        return {'allow_download': False}\n    return meta\n\n\nclass BaseSearchKnowledgeNode(ISearchKnowledgeStepNode):\n    def save_context(self, details, workflow_manage):\n        result = details.get('paragraph_list', [])\n        knowledge_setting = self.node_params_serializer.data.get('knowledge_setting')\n        directly_return = '\\n'.join(\n            [f\"{paragraph.get('title', '')}:{paragraph.get('content')}\" for paragraph in result if\n             paragraph.get('is_hit_handling_method')])\n        self.context['paragraph_list'] = result\n        self.context['question'] = details.get('question')\n        self.context['run_time'] = details.get('run_time')\n        self.context['is_hit_handling_method_list'] = [row for row in result if row.get('is_hit_handling_method')]\n        self.context['data'] = '\\n'.join(\n            [f\"{paragraph.get('title', '')}:{paragraph.get('content')}\" for paragraph in\n             result])[0:knowledge_setting.get('max_paragraph_char_number', 5000)]\n        self.context['directly_return'] = directly_return\n        self.context['exception_message'] = details.get('err_message')\n\n    def get_reference_content(self, fields: List[str]):\n        return self.workflow_manage.get_reference_field(fields[0], fields[1:])\n\n    def execute(self, knowledge_id_list, knowledge_setting, question, show_knowledge, search_scope_type,\n                search_scope_source,\n                search_scope_reference,\n                exclude_paragraph_id_list=None,\n                **kwargs) -> NodeResult:\n        self.context['question'] = question\n        self.context['show_knowledge'] = show_knowledge\n\n        document_id_list = None\n        if search_scope_type == 'referencing':  # 引用上一步知识库/文档\n            if search_scope_source == 'knowledge':  # 知识库\n                knowledge_id_list = self.get_reference_content(search_scope_reference)\n            else:  # 文档\n                document_id_list = self.get_reference_content(search_scope_reference)\n                knowledge_id_list = [str(k) for k in QuerySet(Document).filter(\n                    id__in=document_id_list\n                ).values_list(\n                    'knowledge_id', flat=True\n                ).distinct()]\n\n        get_knowledge_list_of_authorized = DatabaseModelManage.get_model('get_knowledge_list_of_authorized')\n        chat_user_type = self.workflow_manage.get_body().get('chat_user_type')\n        if get_knowledge_list_of_authorized is not None and RoleConstants.CHAT_USER.value.name == chat_user_type:\n            knowledge_id_list = get_knowledge_list_of_authorized(self.workflow_manage.get_body().get('chat_user_id'),\n                                                                 knowledge_id_list)\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        knowledge_id_list = filter_authorized_ids('knowledge', knowledge_id_list, workspace_id)\n        if len(knowledge_id_list) == 0:\n            return get_none_result(question)\n        model_id = get_embedding_id(knowledge_id_list)\n        embedding_model = get_model_instance_by_model_workspace_id(model_id, workspace_id)\n        embedding_value = embedding_model.embed_query(question)\n        vector = VectorStore.get_embedding_vector()\n        exclude_document_id_list = [str(document.id) for document in\n                                    QuerySet(Document).filter(\n                                        knowledge_id__in=knowledge_id_list,\n                                        is_active=False)]\n        embedding_list = vector.query(question, embedding_value, knowledge_id_list, document_id_list,\n                                      exclude_document_id_list,\n                                      exclude_paragraph_id_list, True, knowledge_setting.get('top_n'),\n                                      knowledge_setting.get('similarity'),\n                                      SearchMode(knowledge_setting.get('search_mode')))\n        # 手动关闭数据库连接\n        connection.close()\n        if embedding_list is None:\n            return get_none_result(question)\n        paragraph_list = self.list_paragraph(embedding_list, vector)\n        result = [self.reset_paragraph(paragraph, embedding_list) for paragraph in paragraph_list]\n        result = sorted(result, key=lambda p: p.get('similarity'), reverse=True)\n        return NodeResult({'paragraph_list': result,\n                           'is_hit_handling_method_list': [row for row in result if row.get('is_hit_handling_method')],\n                           'data': '\\n'.join(\n                               [f\"{reset_title(paragraph.get('title', ''))}{paragraph.get('content')}\" for paragraph in\n                                result])[0:knowledge_setting.get('max_paragraph_char_number', 5000)],\n                           'directly_return': '\\n'.join(\n                               [paragraph.get('content') for paragraph in\n                                result if\n                                paragraph.get('is_hit_handling_method')]),\n                           'question': question},\n\n                          {})\n\n    @staticmethod\n    def reset_paragraph(paragraph: Dict, embedding_list: List):\n        filter_embedding_list = [embedding for embedding in embedding_list if\n                                 str(embedding.get('paragraph_id')) == str(paragraph.get('id'))]\n        if filter_embedding_list is not None and len(filter_embedding_list) > 0:\n            find_embedding = filter_embedding_list[-1]\n            return {\n                **paragraph,\n                'similarity': find_embedding.get('similarity'),\n                'is_hit_handling_method': find_embedding.get('similarity') > paragraph.get(\n                    'directly_return_similarity') and paragraph.get('hit_handling_method') == 'directly_return',\n                'update_time': paragraph.get('update_time').strftime(\"%Y-%m-%d %H:%M:%S\"),\n                'create_time': paragraph.get('create_time').strftime(\"%Y-%m-%d %H:%M:%S\"),\n                'id': str(paragraph.get('id')),\n                'knowledge_id': str(paragraph.get('knowledge_id')),\n                'document_id': str(paragraph.get('document_id')),\n                'meta': reset_meta(paragraph.get('meta'))\n            }\n\n    @staticmethod\n    def list_paragraph(embedding_list: List, vector):\n        paragraph_id_list = [row.get('paragraph_id') for row in embedding_list]\n        if paragraph_id_list is None or len(paragraph_id_list) == 0:\n            return []\n        paragraph_list = native_search(QuerySet(Paragraph).filter(id__in=paragraph_id_list),\n                                       get_file_content(\n                                           os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql',\n                                                        'list_knowledge_paragraph_by_paragraph_id.sql')),\n                                       with_table_name=True)\n        # 如果向量库中存在脏数据 直接删除\n        if len(paragraph_list) != len(paragraph_id_list):\n            exist_paragraph_list = [row.get('id') for row in paragraph_list]\n            for paragraph_id in paragraph_id_list:\n                if not exist_paragraph_list.__contains__(paragraph_id):\n                    vector.delete_by_paragraph_id(paragraph_id)\n        return paragraph_list\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            'show_knowledge': self.context.get('show_knowledge'),\n            'question': self.context.get('question'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'paragraph_list': self.context.get('paragraph_list'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/speech_to_text_step_node/__init__.py",
    "content": "# coding=utf-8\n\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass SpeechToTextNodeSerializer(serializers.Serializer):\n    stt_model_id = serializers.CharField(required=True, label=_(\"Model id\"))\n\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    audio_list = serializers.ListField(required=True,\n                                       label=_(\"The audio file cannot be empty\"))\n    model_params_setting = serializers.DictField(required=False,\n                                                 label=_(\"Model parameter settings\"))\n\n\nclass ISpeechToTextNode(INode):\n    type = 'speech-to-text-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP,WorkflowMode.TOOL,WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return SpeechToTextNodeSerializer\n\n    def _run(self):\n        res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('audio_list')[0],\n                                                       self.node_params_serializer.data.get('audio_list')[1:])\n        for audio in res:\n            if 'file_id' not in audio:\n                raise ValueError(\n                    _(\"Parameter value error: The uploaded audio lacks file_id, and the audio upload fails\"))\n\n        return self.execute(audio=res, **self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, stt_model_id,\n                audio, model_params_setting=None,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/speech_to_text_step_node/impl/__init__.py",
    "content": "# coding=utf-8\n\nfrom .base_speech_to_text_node import BaseSpeechToTextNode\n"
  },
  {
    "path": "apps/application/flow/step_node/speech_to_text_step_node/impl/base_speech_to_text_node.py",
    "content": "# coding=utf-8\nimport os\nimport tempfile\nfrom concurrent.futures import ThreadPoolExecutor\n\nfrom django.db.models import QuerySet\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.speech_to_text_step_node.i_speech_to_text_node import ISpeechToTextNode\nfrom common.utils.common import split_and_transcribe, any_to_mp3\nfrom knowledge.models import File\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\n\n\nclass BaseSpeechToTextNode(ISpeechToTextNode):\n\n    def save_context(self, details, workflow_manage):\n        self.context['answer'] = details.get('answer')\n        self.context['result'] = details.get('answer')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n        self.context['exception_message'] = details.get('err_message')\n\n    def execute(self, stt_model_id, audio, model_params_setting=None, **kwargs) -> NodeResult:\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        stt_model = get_model_instance_by_model_workspace_id(stt_model_id, workspace_id, **(model_params_setting or {}))\n        audio_list = audio\n        self.context['audio_list'] = audio\n\n        def process_audio_item(audio_item, model):\n            file = QuerySet(File).filter(id=audio_item['file_id']).first()\n            # 根据file_name 吧文件转成mp3格式\n            file_format = file.file_name.split('.')[-1]\n            with tempfile.NamedTemporaryFile(delete=False, suffix=f'.{file_format}') as temp_file:\n                temp_file.write(file.get_bytes())\n                temp_file_path = temp_file.name\n            with tempfile.NamedTemporaryFile(delete=False, suffix='.mp3') as temp_amr_file:\n                temp_mp3_path = temp_amr_file.name\n            any_to_mp3(temp_file_path, temp_mp3_path)\n            try:\n                transcription = split_and_transcribe(temp_mp3_path, model)\n                return {file.file_name: transcription}\n            finally:\n                os.remove(temp_file_path)\n                os.remove(temp_mp3_path)\n\n        def process_audio_items(audio_list, model):\n            with ThreadPoolExecutor(max_workers=5) as executor:\n                results = list(executor.map(lambda item: process_audio_item(item, model), audio_list))\n            return results\n\n        result = process_audio_items(audio_list, stt_model)\n        content = []\n        result_content = []\n        for item in result:\n            for key, value in item.items():\n                content.append(f'### {key}\\n{value}')\n                result_content.append(value)\n        return NodeResult({'answer': '\\n'.join(result_content), 'result': '\\n'.join(result_content),\n                           'content': content}, {})\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'answer': self.context.get('answer'),\n            'content': self.context.get('content'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'audio_list': self.context.get('audio_list'),\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/start_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:30\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/start_node/i_start_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_start_node.py\n    @date：2024/6/3 16:54\n    @desc:\n\"\"\"\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass IStarNode(INode):\n    type = 'start-node'\n    support = [WorkflowMode.APPLICATION]\n\n    def _run(self):\n        return self.execute(**self.flow_params_serializer.data)\n\n    def execute(self, question, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/start_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:36\n    @desc:\n\"\"\"\nfrom .base_start_node import BaseStartStepNode\n"
  },
  {
    "path": "apps/application/flow/step_node/start_node/impl/base_start_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_start_node.py\n    @date：2024/6/3 17:17\n    @desc:\n\"\"\"\nimport time\nfrom datetime import datetime\nfrom typing import List, Type\nfrom django.utils import timezone\nfrom rest_framework import serializers\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.start_node.i_start_node import IStarNode\n\n\ndef get_default_global_variable(input_field_list: List):\n    return {\n        item.get('variable') or item.get('field'): item.get('default_value')\n        for item in input_field_list\n        if item.get('default_value', None) is not None\n    }\n\n\ndef get_global_variable(node):\n    body = node.workflow_manage.get_body()\n    history_chat_record = node.flow_params_serializer.data.get('history_chat_record', [])\n    history_context = [{'question': chat_record.problem_text, 'answer': chat_record.answer_text} for chat_record in\n                       history_chat_record]\n    chat_id = node.flow_params_serializer.data.get('chat_id')\n    return {'time': timezone.localtime(timezone.now()).strftime('%Y-%m-%d %H:%M:%S'), 'start_time': time.time(),\n            'history_context': history_context, 'chat_id': str(chat_id), **node.workflow_manage.form_data,\n            'chat_user_id': body.get('chat_user_id'),\n            'chat_user_type': body.get('chat_user_type'),\n            'chat_user': body.get('chat_user'),\n            'chat_user_group': body.get('chat_user_group')\n            }\n\n\nclass BaseStartStepNode(IStarNode):\n    def save_context(self, details, workflow_manage):\n        base_node = self.workflow_manage.get_base_node()\n        default_global_variable = get_default_global_variable(base_node.properties.get('user_input_field_list', []))\n        default_api_global_variable = get_default_global_variable(base_node.properties.get('api_input_field_list', []))\n        workflow_variable = {**default_global_variable, **default_api_global_variable, **get_global_variable(self)}\n        self.context['question'] = details.get('question')\n        self.context['run_time'] = details.get('run_time')\n        self.context['document'] = details.get('document_list')\n        self.context['image'] = details.get('image_list')\n        self.context['audio'] = details.get('audio_list')\n        self.context['video'] = details.get('video_list')\n        self.context['other'] = details.get('other_list')\n        self.context['exception_message'] = details.get('err_message')\n        self.status = details.get('status')\n        self.err_message = details.get('err_message')\n        for key, value in workflow_variable.items():\n            workflow_manage.context[key] = value\n        for item in details.get('global_fields', []):\n            workflow_manage.context[item.get('key')] = item.get('value')\n        self.workflow_manage.chat_context = self.workflow_manage.get_chat_info().get_chat_variable()\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        pass\n\n    def execute(self, question, **kwargs) -> NodeResult:\n        base_node = self.workflow_manage.get_base_node()\n        default_global_variable = get_default_global_variable(base_node.properties.get('user_input_field_list', []))\n        default_api_global_variable = get_default_global_variable(base_node.properties.get('api_input_field_list', []))\n        workflow_variable = {**default_global_variable, **default_api_global_variable, **get_global_variable(self)}\n        \"\"\"\n        开始节点 初始化全局变量\n        \"\"\"\n        node_variable = {\n            'question': question,\n            'image': self.workflow_manage.image_list,\n            'document': self.workflow_manage.document_list,\n            'audio': self.workflow_manage.audio_list,\n            'video': self.workflow_manage.video_list,\n            'other': self.workflow_manage.other_list,\n\n        }\n        self.workflow_manage.chat_context = self.workflow_manage.get_chat_info().get_chat_variable()\n        return NodeResult(node_variable, workflow_variable)\n\n    def get_details(self, index: int, **kwargs):\n        global_fields = []\n        for field in self.node.properties.get('config')['globalFields']:\n            key = field['value']\n            global_fields.append({\n                'label': field['label'],\n                'key': key,\n                'value': self.workflow_manage.context[key] if key in self.workflow_manage.context else ''\n            })\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            \"question\": self.context.get('question'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'image_list': self.context.get('image'),\n            'video_list': self.context.get('video'),\n            'document_list': self.context.get('document'),\n            'audio_list': self.context.get('audio'),\n            'other_list': self.context.get('other'),\n            'global_fields': global_fields,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/text_to_speech_step_node/__init__.py",
    "content": "# coding=utf-8\n\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass TextToSpeechNodeSerializer(serializers.Serializer):\n    tts_model_id = serializers.CharField(required=True, label=_(\"Model id\"))\n\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    content_list = serializers.ListField(required=True, label=_(\"Text content\"))\n    model_params_setting = serializers.DictField(required=False,\n                                                 label=_(\"Model parameter settings\"))\n\n\nclass ITextToSpeechNode(INode):\n    type = 'text-to-speech-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return TextToSpeechNodeSerializer\n\n    def _run(self):\n        content = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('content_list')[0],\n                                                           self.node_params_serializer.data.get('content_list')[1:])\n        return self.execute(content=content, **self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, tts_model_id,\n                content, model_params_setting=None,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/text_to_speech_step_node/impl/__init__.py",
    "content": "# coding=utf-8\n\nfrom .base_text_to_speech_node import BaseTextToSpeechNode\n"
  },
  {
    "path": "apps/application/flow/step_node/text_to_speech_step_node/impl/base_text_to_speech_node.py",
    "content": "# coding=utf-8\nimport io\nimport mimetypes\n\nfrom django.core.files.uploadedfile import InMemoryUploadedFile\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.text_to_speech_step_node.i_text_to_speech_node import ITextToSpeechNode\nfrom common.utils.common import _remove_empty_lines\nfrom knowledge.models import FileSourceType\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\nfrom oss.serializers.file import FileSerializer\nfrom pydub import AudioSegment\n\n\ndef bytes_to_uploaded_file(file_bytes, file_name=\"generated_audio.mp3\"):\n    content_type, _ = mimetypes.guess_type(file_name)\n    if content_type is None:\n        # 如果未能识别，设置为默认的二进制文件类型\n        content_type = \"application/octet-stream\"\n    # 创建一个内存中的字节流对象\n    file_stream = io.BytesIO(file_bytes)\n\n    # 获取文件大小\n    file_size = len(file_bytes)\n\n    uploaded_file = InMemoryUploadedFile(\n        file=file_stream,\n        field_name=None,\n        name=file_name,\n        content_type=content_type,\n        size=file_size,\n        charset=None,\n    )\n    return uploaded_file\n\n\nclass BaseTextToSpeechNode(ITextToSpeechNode):\n    def save_context(self, details, workflow_manage):\n        self.context['answer'] = details.get('answer')\n        self.context['result'] = details.get('result')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n\n    def execute(self, tts_model_id,\n                content, model_params_setting=None,\n                max_length=1024, **kwargs) -> NodeResult:\n        # 分割文本为合理片段\n        content = _remove_empty_lines(content)\n        content_chunks = [content[i:i + max_length]\n                          for i in range(0, len(content), max_length)]\n\n        # 生成并收集所有音频片段\n        audio_segments = []\n        temp_files = []\n\n        for i, chunk in enumerate(content_chunks):\n            self.context['content'] = chunk\n            workspace_id = self.workflow_manage.get_body().get('workspace_id')\n            model = get_model_instance_by_model_workspace_id(\n                tts_model_id, workspace_id, **model_params_setting)\n\n            audio_byte = model.text_to_speech(chunk)\n\n            # 保存为临时音频文件用于合并\n            temp_file = io.BytesIO(audio_byte)\n            audio_segment = AudioSegment.from_file(temp_file)\n            audio_segments.append(audio_segment)\n            temp_files.append(temp_file)\n\n        # 合并所有音频片段\n        combined_audio = AudioSegment.empty()\n        for segment in audio_segments:\n            combined_audio += segment\n\n        # 将合并后的音频转为字节流\n        output_buffer = io.BytesIO()\n        combined_audio.export(output_buffer, format=\"mp3\")\n        combined_bytes = output_buffer.getvalue()\n        file_name = 'combined_audio.mp3'\n        file = bytes_to_uploaded_file(combined_bytes, file_name)\n        # 存储合并后的音频文件\n        file_url = self.upload_file(file)\n        # 生成音频标签\n        audio_label = f'<audio src=\"{file_url}\" controls style=\"width: 300px; height: 43px\"></audio>'\n        file_id = file_url.split('/')[-1]\n        audio_list = [{'file_id': file_id, 'file_name': file_name, 'url': file_url}]\n\n        # 关闭所有临时文件\n        for temp_file in temp_files:\n            temp_file.close()\n        output_buffer.close()\n\n        return NodeResult({\n            'answer': audio_label,\n            'result': audio_list\n        }, {})\n\n    def upload_file(self, file):\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__(\n                self.workflow_manage.flow.workflow_mode):\n            return self.upload_knowledge_file(file)\n        return self.upload_application_file(file)\n\n    def upload_knowledge_file(self, file):\n        knowledge_id = self.workflow_params.get('knowledge_id')\n        meta = {\n            'debug': False,\n            'knowledge_id': knowledge_id,\n        }\n        file_url = FileSerializer(data={\n            'file': file,\n            'meta': meta,\n            'source_id': knowledge_id,\n            'source_type': FileSourceType.KNOWLEDGE.value\n        }).upload()\n        return file_url\n\n    def upload_application_file(self, file):\n        application = self.workflow_manage.work_flow_post_handler.chat_info.application\n        chat_id = self.workflow_params.get('chat_id')\n        meta = {\n            'debug': False if application.id else True,\n            'chat_id': chat_id,\n            'application_id': str(application.id) if application.id else None,\n        }\n        file_url = FileSerializer(data={\n            'file': file,\n            'meta': meta,\n            'source_id': meta['application_id'],\n            'source_type': FileSourceType.APPLICATION.value\n        }).upload()\n        return file_url\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'content': self.context.get('content'),\n            'err_message': self.err_message,\n            'answer': self.context.get('answer'),\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/text_to_video_step_node/__init__.py",
    "content": "# coding=utf-8\n\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/text_to_video_step_node/i_text_to_video_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass TextToVideoNodeSerializer(serializers.Serializer):\n    model_id = serializers.CharField(required=True, label=_(\"Model id\"))\n\n    prompt = serializers.CharField(required=True, label=_(\"Prompt word (positive)\"))\n\n    negative_prompt = serializers.CharField(required=False, label=_(\"Prompt word (negative)\"),\n                                            allow_null=True, allow_blank=True, )\n    # 多轮对话数量\n    dialogue_number = serializers.IntegerField(required=False, default=0,\n                                               label=_(\"Number of multi-round conversations\"))\n\n    dialogue_type = serializers.CharField(required=False, default='NODE',\n                                          label=_(\"Conversation storage type\"))\n\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    model_params_setting = serializers.JSONField(required=False, default=dict,\n                                                 label=_(\"Model parameter settings\"))\n\n\nclass ITextToVideoNode(INode):\n    type = 'text-to-video-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return TextToVideoNodeSerializer\n\n    def _run(self):\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL,\n            WorkflowMode.TOOL_LOOP].__contains__(\n            self.workflow_manage.flow.workflow_mode):\n            return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data,\n                                **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None})\n        else:\n            return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record,\n                model_params_setting,\n                chat_record_id,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/text_to_video_step_node/impl/__init__.py",
    "content": "# coding=utf-8\n\nfrom .base_text_to_video_node import BaseTextToVideoNode\n"
  },
  {
    "path": "apps/application/flow/step_node/text_to_video_step_node/impl/base_text_to_video_node.py",
    "content": "# coding=utf-8\nfrom functools import reduce\nfrom typing import List\n\nimport requests\nfrom langchain_core.messages import BaseMessage, HumanMessage, AIMessage\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.text_to_video_step_node.i_text_to_video_node import ITextToVideoNode\nfrom common.utils.common import bytes_to_uploaded_file\nfrom knowledge.models import FileSourceType\nfrom oss.serializers.file import FileSerializer\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\nfrom django.utils.translation import gettext\n\n\nclass BaseTextToVideoNode(ITextToVideoNode):\n    def save_context(self, details, workflow_manage):\n        self.context['answer'] = details.get('answer')\n        self.context['exception_message'] = details.get('err_message')\n        self.context['question'] = details.get('question')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n\n    def execute(self, model_id, prompt, negative_prompt, dialogue_number, dialogue_type, history_chat_record,\n                model_params_setting,\n                chat_record_id,\n                **kwargs) -> NodeResult:\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        ttv_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,\n                                                             **model_params_setting)\n        history_message = self.get_history_message(history_chat_record, dialogue_number)\n        self.context['history_message'] = history_message\n        question = self.generate_prompt_question(prompt)\n        self.context['question'] = question\n        message_list = self.generate_message_list(question, history_message)\n        self.context['message_list'] = message_list\n        self.context['dialogue_type'] = dialogue_type\n        self.context['negative_prompt'] = self.generate_prompt_question(negative_prompt)\n        video_urls = ttv_model.generate_video(question, negative_prompt)\n        # 保存图片\n        if video_urls is None:\n            return NodeResult({'answer': gettext('Failed to generate video')}, {})\n        file_name = 'generated_video.mp4'\n        if isinstance(video_urls, str) and video_urls.startswith('http'):\n            video_urls = requests.get(video_urls).content\n        file = bytes_to_uploaded_file(video_urls, file_name)\n        file_url = self.upload_file(file)\n        video_label = f'<video src=\"{file_url}\" controls style=\"max-width: 100%; width: 100%; height: auto;\"></video>'\n        video_list = [{'file_id': file_url.split('/')[-1], 'file_name': file_name, 'url': file_url}]\n        return NodeResult({'answer': video_label, 'chat_model': ttv_model, 'message_list': message_list,\n                           'video': video_list,\n                           'history_message': history_message, 'question': question}, {})\n\n    def upload_file(self, file):\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP].__contains__(\n                self.workflow_manage.flow.workflow_mode):\n            return self.upload_knowledge_file(file)\n        return self.upload_application_file(file)\n\n    def upload_knowledge_file(self, file):\n        knowledge_id = self.workflow_params.get('knowledge_id')\n        meta = {\n            'debug': False,\n            'knowledge_id': knowledge_id\n        }\n        file_url = FileSerializer(data={\n            'file': file,\n            'meta': meta,\n            'source_id': knowledge_id,\n            'source_type': FileSourceType.KNOWLEDGE.value\n        }).upload()\n        return file_url\n\n    def upload_application_file(self, file):\n        application = self.workflow_manage.work_flow_post_handler.chat_info.application\n        chat_id = self.workflow_params.get('chat_id')\n        meta = {\n            'debug': False if application.id else True,\n            'chat_id': chat_id,\n            'application_id': str(application.id) if application.id else None,\n        }\n        file_url = FileSerializer(data={\n            'file': file,\n            'meta': meta,\n            'source_id': meta['application_id'],\n            'source_type': FileSourceType.APPLICATION.value\n        }).upload()\n        return file_url\n\n    def generate_history_ai_message(self, chat_record):\n        for val in chat_record.details.values():\n            if self.node.id == val['node_id'] and 'image_list' in val:\n                if val['dialogue_type'] == 'WORKFLOW':\n                    return chat_record.get_ai_message()\n                image_list = val['image_list']\n                return AIMessage(content=[\n                    *[{'type': 'image_url', 'image_url': {'url': f'{file_url}'}} for file_url in image_list]\n                ])\n        return chat_record.get_ai_message()\n\n    def get_history_message(self, history_chat_record, dialogue_number):\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = reduce(lambda x, y: [*x, *y], [\n            [self.generate_history_human_message(history_chat_record[index]),\n             self.generate_history_ai_message(history_chat_record[index])]\n            for index in\n            range(start_index if start_index > 0 else 0, len(history_chat_record))], [])\n        return history_message\n\n    def generate_history_human_message(self, chat_record):\n\n        for data in chat_record.details.values():\n            if self.node.id == data['node_id'] and 'image_list' in data:\n                image_list = data['image_list']\n                if len(image_list) == 0 or data['dialogue_type'] == 'WORKFLOW':\n                    return HumanMessage(content=chat_record.problem_text)\n                return HumanMessage(content=data['question'])\n        return HumanMessage(content=chat_record.problem_text)\n\n    def generate_prompt_question(self, prompt):\n        return self.workflow_manage.generate_prompt(prompt)\n\n    def generate_message_list(self, question: str, history_message):\n        return [\n            *history_message,\n            question\n        ]\n\n    @staticmethod\n    def reset_message_list(message_list: List[BaseMessage], answer_text):\n        result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for\n                  message\n                  in\n                  message_list]\n        result.append({'role': 'ai', 'content': answer_text})\n        return result\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'history_message': [{'content': message.content, 'role': message.type} for message in\n                                (self.context.get('history_message') if self.context.get(\n                                    'history_message') is not None else [])],\n            'question': self.context.get('question'),\n            'answer': self.context.get('answer'),\n            'type': self.node.type,\n            'message_tokens': self.context.get('message_tokens'),\n            'answer_tokens': self.context.get('answer_tokens'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'image_list': self.context.get('image_list'),\n            'dialogue_type': self.context.get('dialogue_type'),\n            'negative_prompt': self.context.get('negative_prompt'),\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_lib_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py\n    @date：2024/8/8 17:45\n    @desc:\n\"\"\"\nfrom .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/tool_lib_node/i_tool_lib_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： i_function_lib_node.py\n    @date：2024/8/8 16:21\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom django.db import connection\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\nfrom common.field.common import ObjectField\nfrom tools.models.tool import Tool\n\n\nclass InputField(serializers.Serializer):\n    name = serializers.CharField(required=True, label=_('Variable Name'))\n    value = ObjectField(required=True, label=_(\"Variable Value\"), model_type_list=[str, list])\n\n\nclass FunctionLibNodeParamsSerializer(serializers.Serializer):\n    tool_lib_id = serializers.UUIDField(required=True, label=_('Library ID'))\n    input_field_list = InputField(required=True, many=True)\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        f_lib = QuerySet(Tool).filter(id=self.data.get('tool_lib_id')).first()\n        # 归还链接到连接池\n        connection.close()\n        if f_lib is None:\n            raise Exception(_('The function has been deleted'))\n\n\nclass IToolLibNode(INode):\n    type = 'tool-lib-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return FunctionLibNodeParamsSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_lib_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py\n    @date：2024/8/8 17:48\n    @desc:\n\"\"\"\nfrom .base_tool_lib_node import BaseToolLibNodeNode\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_lib_node/impl/base_tool_lib_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： base_function_lib_node.py\n    @date：2024/8/8 17:49\n    @desc:\n\"\"\"\n\nimport base64\nimport io\nimport json\nimport mimetypes\nimport time\nimport traceback\nfrom typing import Dict\n\nimport uuid_utils.compat as uuid\nfrom django.core.files.uploadedfile import InMemoryUploadedFile\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext as _\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.tool_lib_node.i_tool_lib_node import IToolLibNode\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.rsa_util import rsa_long_decrypt\nfrom common.utils.tool_code import ToolExecutor\nfrom knowledge.models import FileSourceType\nfrom knowledge.models.knowledge_action import State\nfrom oss.serializers.file import FileSerializer\nfrom tools.models import Tool, ToolRecord, ToolTaskTypeChoices\n\nfunction_executor = ToolExecutor()\n\n\ndef write_context(step_variable: Dict, global_variable: Dict, node, workflow):\n    if step_variable is not None:\n        for key in step_variable:\n            node.context[key] = step_variable[key]\n        if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'result' in step_variable:\n            result = str(step_variable['result']) + '\\n'\n            yield result\n            node.answer_text = result\n    node.context['run_time'] = time.time() - node.context['start_time']\n\n\ndef get_field_value(debug_field_list, name, is_required):\n    result = [field for field in debug_field_list if field.get('name') == name]\n    if len(result) > 0:\n        return result[-1]['value']\n    if is_required:\n        raise AppApiException(500, _('Field: {name} No value set').format(name=name))\n    return None\n\n\ndef valid_reference_value(_type, value, name):\n    try:\n        if _type == 'int':\n            instance_type = int | float\n        elif _type == 'boolean':\n            instance_type = bool\n        elif _type == 'float':\n            instance_type = float | int\n        elif _type == 'dict':\n            value = json.loads(value) if isinstance(value, str) else value\n            instance_type = dict\n        elif _type == 'array':\n            value = json.loads(value) if isinstance(value, str) else value\n            instance_type = list\n        elif _type == 'string':\n            instance_type = str\n        else:\n            raise Exception(_(\n                'Field: {name} Type: {_type} Value: {value} Unsupported types'\n            ).format(name=name, _type=_type))\n    except:\n        return value\n    if not isinstance(value, instance_type):\n        raise Exception(_(\n            'Field: {name} Type: {_type} Value: {value} Type error'\n        ).format(name=name, _type=_type, value=value))\n    return value\n\n\ndef convert_value(name: str, value, _type, is_required, source, node):\n    if not is_required and (value is None or ((isinstance(value, str) or isinstance(value, list)) and len(value) == 0)):\n        return None\n    if source == 'reference':\n        value = node.workflow_manage.get_reference_field(\n            value[0],\n            value[1:])\n        if value is None:\n            if not is_required:\n                return None\n            else:\n                raise Exception(_(\n                    'Field: {name} Type: {_type} is required'\n                ).format(name=name, _type=_type))\n        value = valid_reference_value(_type, value, name)\n        if _type == 'int':\n            return int(value)\n        if _type == 'float':\n            return float(value)\n        return value\n    try:\n        value = node.workflow_manage.generate_prompt(value)\n        if _type == 'int':\n            return int(value)\n        if _type == 'boolean':\n            value = 0 if ['0', '[]'].__contains__(value) else value\n            return bool(value)\n        if _type == 'float':\n            return float(value)\n        if _type == 'dict':\n            v = json.loads(value)\n            if isinstance(v, dict):\n                return v\n            raise Exception(_('type error'))\n        if _type == 'array':\n            v = json.loads(value)\n            if isinstance(v, list):\n                return v\n            raise Exception(_('type error'))\n        return value\n    except Exception as e:\n        raise Exception(\n            _('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type,\n                                                                              value=value))\n\n\ndef valid_function(tool_lib, workspace_id):\n    if tool_lib is None:\n        raise Exception(_('Tool does not exist'))\n    get_authorized_tool = DatabaseModelManage.get_model(\"get_authorized_tool\")\n    if tool_lib and tool_lib.workspace_id != workspace_id and get_authorized_tool is not None:\n        tool_lib = get_authorized_tool(QuerySet(Tool).filter(id=tool_lib.id), workspace_id).first()\n    if tool_lib is None:\n        raise Exception(_(\"Tool does not exist\"))\n    if not tool_lib.is_active:\n        raise Exception(_(\"Tool is not active\"))\n\n\ndef _filter_file_bytes(data):\n    \"\"\"递归过滤掉所有层级的 file_bytes\"\"\"\n    if isinstance(data, dict):\n        return {k: _filter_file_bytes(v) for k, v in data.items() if k != 'file_bytes'}\n    elif isinstance(data, list):\n        return [_filter_file_bytes(item) for item in data]\n    else:\n        return data\n\n\ndef bytes_to_uploaded_file(file_bytes, file_name=\"unknown\"):\n    content_type, _ = mimetypes.guess_type(file_name)\n    if content_type is None:\n        # 如果未能识别，设置为默认的二进制文件类型\n        content_type = \"application/octet-stream\"\n    # 创建一个内存中的字节流对象\n    file_stream = io.BytesIO(file_bytes)\n\n    # 获取文件大小\n    file_size = len(file_bytes)\n\n    uploaded_file = InMemoryUploadedFile(\n        file=file_stream,\n        field_name=None,\n        name=file_name,\n        content_type=content_type,\n        size=file_size,\n        charset=None,\n    )\n    return uploaded_file\n\n\ndef _get_result_detail(result):\n    if isinstance(result, dict):\n        result_dict = {k: (str(v)[:500] if len(str(v)) > 500 else v) for k, v in result.items()}\n    elif isinstance(result, list):\n        result_dict = [str(item)[:500] if len(str(item)) > 500 else item for item in result]\n    elif isinstance(result, str):\n        result_dict = result[:500] if len(result) > 500 else result\n    else:\n        result_dict = result\n    return result_dict\n\n\nclass BaseToolLibNodeNode(IToolLibNode):\n    def save_context(self, details, workflow_manage):\n        self.context['result'] = details.get('result')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result'):\n            self.answer_text = str(details.get('result'))\n\n    def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult:\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        tool_lib = QuerySet(Tool).filter(id=tool_lib_id).first()\n        valid_function(tool_lib, workspace_id)\n        params = {\n            field.get('name'): convert_value(\n                field.get('name'), field.get('value'), field.get('type'),\n                field.get('is_required'),\n                field.get('source'), self\n            )\n            for field in [\n                {\n                    'value': get_field_value(input_field_list, field.get('name'), field.get('is_required'), ), **field\n                } for field in tool_lib.input_field_list\n            ]\n        }\n\n        self.context['params'] = params\n        # 合并初始化参数\n        init_params_default_value = {i[\"field\"]: i.get('default_value') for i in tool_lib.init_field_list}\n        if tool_lib.init_params is not None:\n            all_params = init_params_default_value | json.loads(rsa_long_decrypt(tool_lib.init_params)) | params\n        else:\n            all_params = init_params_default_value | params\n        if self.node.properties.get('kind') == 'data-source':\n            exist = function_executor.exec_code(\n                f'{tool_lib.code}\\ndef function_exist(function_name): return callable(globals().get(function_name))',\n                {'function_name': 'get_download_file_list'})\n            all_params = {**all_params, **self.workflow_params.get('data_source')}\n            if exist:\n                download_file_list = []\n                download_list = function_executor.exec_code(tool_lib.code,\n                                                            all_params,\n                                                            function_name='get_download_file_list')\n                for item in download_list:\n                    result = function_executor.exec_code(tool_lib.code,\n                                                         {**all_params, 'download_item': item},\n                                                         function_name='download')\n                    file_bytes = result.get('file_bytes', [])\n                    chunks = []\n                    for chunk in file_bytes:\n                        chunks.append(base64.b64decode(chunk))\n                    file = bytes_to_uploaded_file(b''.join(chunks), result.get('name'))\n                    file_url = self.upload_knowledge_file(file)\n                    download_file_list.append({'file_id': file_url.split('/')[-1], 'name': result.get('name')})\n                result = download_file_list\n            else:\n                result = function_executor.exec_code(tool_lib.code, all_params)\n        else:\n            result = self.tool_exec_record(tool_lib, all_params)\n        return NodeResult({'result': result},\n                          (self.workflow_manage.params.get('knowledge_base') or {}) if self.node.properties.get(\n                              'kind') == 'data-source' else {}, _write_context=write_context)\n\n    def tool_exec_record(self, tool_lib, all_params):\n        task_record_id = uuid.uuid7()\n        start_time = time.time()\n        try:\n            # 过滤掉 tool_init_params 中的参数\n            tool_init_params = json.loads(rsa_long_decrypt(tool_lib.init_params)) if tool_lib.init_params else {}\n            if tool_init_params:\n                filtered_args = {\n                    k: v for k, v in all_params.items()\n                    if k not in tool_init_params\n                }\n            else:\n                filtered_args = all_params\n            ToolRecord(\n                id=task_record_id,\n                workspace_id=tool_lib.workspace_id,\n                tool_id=tool_lib.id,\n                source_type=ToolTaskTypeChoices.KNOWLEDGE.value if self.workflow_manage.params.get(\n                    'knowledge_id') else ToolTaskTypeChoices.APPLICATION.value,\n                source_id=self.workflow_manage.params.get('knowledge_id') or self.workflow_manage.params.get(\n                    'application_id'),\n                meta={'input': filtered_args, 'output': {}},\n                state=State.STARTED\n            ).save()\n\n            result = function_executor.exec_code(tool_lib.code, all_params)\n            result_dict = _get_result_detail(result)\n            QuerySet(ToolRecord).filter(id=task_record_id).update(\n                state=State.SUCCESS,\n                run_time=time.time() - start_time,\n                meta={'input': filtered_args, 'output': result_dict}\n            )\n\n            return result\n        except Exception as e:\n            maxkb_logger.error(f\"Tool execution error: {traceback.format_exc()}\")\n            QuerySet(ToolRecord).filter(id=task_record_id).update(\n                state=State.FAILURE,\n                run_time=time.time() - start_time,\n                meta={'input': filtered_args, 'output': 'Error: ' + str(e)}\n            )\n\n    def upload_knowledge_file(self, file):\n        knowledge_id = self.workflow_params.get('knowledge_id')\n        meta = {\n            'debug': False,\n            'knowledge_id': knowledge_id,\n        }\n        file_url = FileSerializer(data={\n            'file': file,\n            'meta': meta,\n            'source_id': knowledge_id,\n            'source_type': FileSourceType.KNOWLEDGE.value\n        }).upload().replace(\"./oss/file/\", '')\n        file.close()\n        return file_url\n\n    def get_details(self, index: int, **kwargs):\n        result = _filter_file_bytes(self.context.get('result'))\n\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            \"result\": result,\n            \"params\": self.context.get('params'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/8/13 10:43\n    @desc:\n\"\"\"\nfrom .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/tool_node/i_tool_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： i_function_lib_node.py\n    @date：2024/8/8 16:21\n    @desc:\n\"\"\"\nimport re\nfrom typing import Type\n\nfrom django.core import validators\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\nfrom rest_framework.utils.formatting import lazy_format\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\nfrom common.exception.app_exception import AppApiException\nfrom common.field.common import ObjectField\n\n\nclass InputField(serializers.Serializer):\n    name = serializers.CharField(required=True, label=_('Variable Name'))\n    is_required = serializers.BooleanField(required=True, label=_(\"Is this field required\"))\n    type = serializers.CharField(required=True, label=_(\"type\"), validators=[\n        validators.RegexValidator(regex=re.compile(\"^string|int|dict|array|float|boolean$\"),\n                                  message=_(\"The field only supports string|int|dict|array|float\"), code=500)\n    ])\n    source = serializers.CharField(required=True, label=_(\"source\"), validators=[\n        validators.RegexValidator(regex=re.compile(\"^custom|reference$\"),\n                                  message=_(\"The field only supports custom|reference\"), code=500)\n    ])\n    value = ObjectField(required=True, label=_(\"Variable Value\"), model_type_list=[str, list])\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        is_required = self.data.get('is_required')\n        if is_required and self.data.get('value') is None:\n            message = lazy_format(_('{field}, this field is required.'), field=self.data.get(\"name\"))\n            raise AppApiException(500, message)\n\n\nclass FunctionNodeParamsSerializer(serializers.Serializer):\n    input_field_list = InputField(required=True, many=True)\n    code = serializers.CharField(required=True, label=_(\"function\"))\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n\n\nclass IToolNode(INode):\n    type = 'tool-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return FunctionNodeParamsSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, input_field_list, code, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/8/13 11:19\n    @desc:\n\"\"\"\nfrom .base_tool_node import BaseToolNodeNode\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_node/impl/base_tool_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： base_function_lib_node.py\n    @date：2024/8/8 17:49\n    @desc:\n\"\"\"\nimport json\nimport time\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.tool_node.i_tool_node import IToolNode\nfrom common.utils.tool_code import ToolExecutor\nfrom maxkb.const import CONFIG\n\nfunction_executor = ToolExecutor()\n\n\ndef write_context(step_variable: Dict, global_variable: Dict, node, workflow):\n    if step_variable is not None:\n        for key in step_variable:\n            node.context[key] = step_variable[key]\n        if workflow.is_result(node, NodeResult(step_variable, global_variable)) and 'result' in step_variable:\n            result = str(step_variable['result']) + '\\n'\n            yield result\n            node.answer_text = result\n    node.context['run_time'] = time.time() - node.context['start_time']\n\n\ndef valid_reference_value(_type, value, name):\n    try:\n        if _type == 'int':\n            instance_type = int | float\n        elif _type == 'boolean':\n            instance_type = bool\n        elif _type == 'float':\n            instance_type = float | int\n        elif _type == 'dict':\n            value = json.loads(value) if isinstance(value, str) else value\n            instance_type = dict\n        elif _type == 'array':\n            value = json.loads(value) if isinstance(value, str) else value\n            instance_type = list\n        elif _type == 'string':\n            instance_type = str\n        else:\n            raise Exception(_(\n                'Field: {name} Type: {_type} Value: {value} Unsupported types'\n            ).format(name=name, _type=_type))\n    except:\n        return value\n    if not isinstance(value, instance_type):\n        raise Exception(_(\n            'Field: {name} Type: {_type} Value: {value} Type error'\n        ).format(name=name, _type=_type, value=value))\n    return value\n\n\ndef convert_value(name: str, value, _type, is_required, source, node):\n    if not is_required and (value is None or ((isinstance(value, str) or isinstance(value, list)) and len(value) == 0)):\n        return None\n    if source == 'reference':\n        value = node.workflow_manage.get_reference_field(\n            value[0],\n            value[1:])\n        if value is None:\n            if not is_required:\n                return None\n            else:\n                raise Exception(_(\n                    'Field: {name} Type: {_type} is required'\n                ).format(name=name, _type=_type))\n        value = valid_reference_value(_type, value, name)\n        if _type == 'int':\n            return int(value)\n        if _type == 'float':\n            return float(value)\n        return value\n    try:\n        value = node.workflow_manage.generate_prompt(value)\n        if _type == 'int':\n            return int(value)\n        if _type == 'boolean':\n            value = 0 if ['0', '[]'].__contains__(value) else value\n            return bool(value)\n        if _type == 'float':\n            return float(value)\n        if _type == 'dict':\n            v = json.loads(value)\n            if isinstance(v, dict):\n                return v\n            raise Exception(_('type error'))\n        if _type == 'array':\n            v = json.loads(value)\n            if isinstance(v, list):\n                return v\n            raise Exception(_('type error'))\n        return value\n    except Exception as e:\n        raise Exception(\n            _('Field: {name} Type: {_type} Value: {value} Type error').format(name=name, _type=_type,\n                                                                              value=value))\n\n\nclass BaseToolNodeNode(IToolNode):\n    def save_context(self, details, workflow_manage):\n        self.context['result'] = details.get('result')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = str(details.get('result'))\n\n    def execute(self, input_field_list, code, **kwargs) -> NodeResult:\n        params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'),\n                                                   field.get('is_required'), field.get('source'), self)\n                  for field in input_field_list}\n        result = function_executor.exec_code(code, params)\n        self.context['params'] = params\n        return NodeResult({'result': result}, {}, _write_context=write_context)\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            \"result\": self.context.get('result'),\n            \"params\": self.context.get('params'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_start_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:30\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_start_node/i_tool_start_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： i_start_node.py\n    @date：2024/6/3 16:54\n    @desc:\n\"\"\"\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass IToolStartNode(INode):\n    type = 'tool-start-node'\n    support = [WorkflowMode.TOOL]\n\n    def _run(self):\n        return self.execute(**self.flow_params_serializer.data)\n\n    def execute(self, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_start_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 15:36\n    @desc:\n\"\"\"\nfrom .base_tool_start_node import BaseToolStartStepNode\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_start_node/impl/base_tool_start_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_start_node.py\n    @date：2024/6/3 17:17\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom rest_framework import serializers\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.tool_start_node.i_tool_start_node import IToolStartNode\n\n\nclass BaseToolStartStepNode(IToolStartNode):\n    def save_context(self, details, workflow_manage):\n        base_node = self.workflow_manage.get_base_node()\n        workflow_variable = {}\n        self.context['exception_message'] = details.get('err_message')\n        self.status = details.get('status')\n        self.err_message = details.get('err_message')\n        for key, value in workflow_variable.items():\n            workflow_manage.context[key] = value\n        for item in details.get('global_fields', []):\n            workflow_manage.context[item.get('key')] = item.get('value')\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        pass\n\n    def execute(self, **kwargs) -> NodeResult:\n        base_node = self.workflow_manage.get_base_node()\n        global_value = {}\n        params = self.workflow_manage.get_body()\n        for item in base_node.properties.get('user_input_field_list', []):\n            global_value[item.get('field')] = params[item.get('field')]\n\n        self.workflow_manage.out_context = {\n            item.get('field'): None\n            for item in base_node.properties.get('user_output_field_list', [])\n            if item.get('default_value', None) is not None\n        }\n        return NodeResult({}, global_value)\n\n    def get_details(self, index: int, **kwargs):\n        global_fields = []\n        for field in self.node.properties.get('config')['globalFields']:\n            key = field['value']\n            global_fields.append({\n                'label': field['label'],\n                'key': key,\n                'value': self.workflow_manage.context[key] if key in self.workflow_manage.context else ''\n            })\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            \"question\": self.context.get('question'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'err_message': self.err_message,\n            'global_fields': global_fields,\n            '': '',\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_workflow_lib_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2026/3/16 13:53\n    @desc:\n\"\"\"\nfrom .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/tool_workflow_lib_node/i_tool_workflow_lib_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： i_function_lib_node.py\n    @date：2024/8/8 16:21\n    @desc:\n\"\"\"\nfrom typing import Type\n\nfrom django.db import connection\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\nfrom common.field.common import ObjectField\nfrom tools.models.tool import Tool, ToolType\n\n\nclass InputField(serializers.Serializer):\n    field = serializers.CharField(required=True, label=_('Variable Name'))\n    label = serializers.CharField(required=True, label=_('Variable Label'))\n    source = serializers.CharField(required=True, label=_('Variable Source'))\n    type = serializers.CharField(required=True, label=_('Variable Type'))\n    value = ObjectField(required=True, label=_(\"Variable Value\"), model_type_list=[str, list, bool, dict, int, float])\n\n\nclass FunctionLibNodeParamsSerializer(serializers.Serializer):\n    tool_lib_id = serializers.UUIDField(required=True, label=_('Library ID'))\n    input_field_list = InputField(required=True, many=True)\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        f_lib = QuerySet(Tool).filter(id=self.data.get('tool_lib_id'), tool_type=ToolType.WORKFLOW).first()\n        # 归还链接到连接池\n        connection.close()\n        if f_lib is None:\n            raise Exception(_('The function has been deleted'))\n\n\nclass IToolWorkflowLibNode(INode):\n    type = 'tool-workflow-lib-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return FunctionLibNodeParamsSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_workflow_lib_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2026/3/16 13:53\n    @desc:\n\"\"\"\nfrom .base_tool_workflow_lib_node import *\n"
  },
  {
    "path": "apps/application/flow/step_node/tool_workflow_lib_node/impl/base_tool_workflow_lib_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： base_tool_workflow_lib_node.py.py\n    @date：2026/3/16 13:55\n    @desc:\n\"\"\"\n\nimport time\nfrom typing import Dict\n\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\n\nfrom application.flow.common import WorkflowMode, Workflow\nfrom application.flow.i_step_node import NodeResult, ToolWorkflowPostHandler, INode\nfrom application.flow.step_node.tool_workflow_lib_node.i_tool_workflow_lib_node import IToolWorkflowLibNode\nfrom application.models import ChatRecord\nfrom application.serializers.common import ToolExecute\nfrom common.exception.app_exception import ChatException\nfrom common.handle.impl.response.loop_to_response import LoopToResponse\nfrom tools.models import ToolWorkflowVersion\n\n\ndef _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str,\n                   reasoning_content: str):\n    result = node_variable.get('result')\n    node.context['application_node_dict'] = node_variable.get('application_node_dict')\n    node.context['node_dict'] = node_variable.get('node_dict', {})\n    node.context['is_interrupt_exec'] = node_variable.get('is_interrupt_exec')\n    node.context['message_tokens'] = result.get('usage', {}).get('prompt_tokens', 0)\n    node.context['answer_tokens'] = result.get('usage', {}).get('completion_tokens', 0)\n    node.context['answer'] = answer\n    node.context['result'] = answer\n    node.context['reasoning_content'] = reasoning_content\n    node.context['run_time'] = time.time() - node.context['start_time']\n    if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):\n        node.answer_text = answer\n\n\ndef get_answer_list(instance, child_node_node_dict, runtime_node_id):\n    answer_list = instance.get_record_answer_list()\n    for a in answer_list:\n        _v = child_node_node_dict.get(a.get('runtime_node_id'))\n        if _v:\n            a['runtime_node_id'] = runtime_node_id\n            a['child_node'] = _v\n    return answer_list\n\n\ndef write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据 (流式)\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点\n    @param workflow:           工作流管理器\n    \"\"\"\n    workflow_manage_new_instance = node_variable.get('workflow_manage_new_instance')\n    node_params = node.node_params\n    start_node_id = node_params.get('child_node', {}).get('runtime_node_id')\n    child_node_data = node.context.get('child_node_data') or []\n    start_node_data = None\n    chat_record = None\n    child_node = None\n    if start_node_id:\n        chat_record_id = node_params.get('child_node', {}).get('chat_record_id')\n        child_node = node_params.get('child_node', {}).get('child_node')\n        start_node_data = node_params.get('node_data')\n        chat_record = ChatRecord(id=chat_record_id, answer_text_list=[], answer_text='',\n                                 details=child_node_data)\n    instance = workflow_manage_new_instance(start_node_id,\n                                            start_node_data, chat_record, child_node)\n    answer = ''\n    reasoning_content = ''\n    usage = {}\n    node_child_node = {}\n    is_interrupt_exec = False\n    response = instance.stream()\n    child_node_node_dict = {}\n    for chunk in response:\n        response_content = chunk\n        content = (response_content.get('content', '') or '')\n        runtime_node_id = response_content.get('runtime_node_id', '')\n        chat_record_id = response_content.get('chat_record_id', '')\n        child_node = response_content.get('child_node')\n        node_type = response_content.get('node_type')\n        _reasoning_content = (response_content.get('reasoning_content', '') or '')\n        if node_type == 'form-node':\n            is_interrupt_exec = True\n        answer += content\n        reasoning_content += _reasoning_content\n        node_child_node = {'runtime_node_id': runtime_node_id, 'chat_record_id': chat_record_id,\n                           'child_node': child_node}\n\n        child_node = chunk.get('child_node')\n        runtime_node_id = chunk.get('runtime_node_id', '')\n        chat_record_id = chunk.get('chat_record_id', '')\n        child_node_node_dict[runtime_node_id] = {\n            'runtime_node_id': runtime_node_id,\n            'chat_record_id': chat_record_id,\n            'child_node': child_node}\n        content_chunk = (chunk.get('content', '') or '')\n        reasoning_content_chunk = (chunk.get('reasoning_content', '') or '')\n        reasoning_content += reasoning_content_chunk\n        answer += content_chunk\n        yield chunk\n        if chunk.get('node_status', \"SUCCESS\") == 'ERROR':\n            is_interrupt_exec = True\n            node.status = 500\n            node.err_message = chunk.get('content')\n        usage = response_content.get('usage', {})\n    child_answer_data = get_answer_list(instance, child_node_node_dict, node.runtime_node_id)\n    node.context['usage'] = {'usage': usage}\n    node.context['child_node'] = node_child_node\n    node.context['child_node_data'] = instance.get_runtime_details()\n    node.context['is_interrupt_exec'] = is_interrupt_exec\n    node.context['child_node_data'] = instance.get_runtime_details()\n    node.context['child_answer_data'] = child_answer_data\n    node.context['run_time'] = time.time() - node.context.get(\"start_time\")\n    for key, value in instance.out_context.items():\n        node.context[key] = value\n\n\ndef _is_interrupt_exec(node, node_variable: Dict, workflow_variable: Dict):\n    return node.context.get('is_interrupt_exec', False)\n\n\nclass BaseToolWorkflowLibNodeNode(IToolWorkflowLibNode):\n    def get_parameters(self, input_field_list):\n        result = {}\n        for input in input_field_list:\n            source = input.get('source')\n            value = input.get('value')\n            if source == 'reference':\n                value = self.workflow_manage.get_reference_field(\n                    value[0],\n                    value[1:])\n            result[input.get('field')] = value\n\n        return result\n\n    def save_context(self, details, workflow_manage):\n        self.context['child_answer_data'] = details.get('child_answer_data')\n        self.context['child_node_data'] = details.get('child_node_data')\n        self.context['result'] = details.get('result')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result'):\n            self.answer_text = str(details.get('result'))\n\n    @staticmethod\n    def to_chat_record(record):\n        if record is None:\n            return None\n        return ChatRecord(\n            answer_text_list=record.meta.get('answer_text_list'),\n            details=record.meta.get('details'),\n            answer_text='',\n        )\n\n    def execute(self, tool_lib_id, input_field_list, **kwargs) -> NodeResult:\n        from application.flow.tool_workflow_manage import ToolWorkflowManage\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        tool_workflow_version = QuerySet(ToolWorkflowVersion).filter(tool_id=tool_lib_id).order_by(\n            '-create_time')[0:1].first()\n        if tool_workflow_version is None:\n            raise ChatException(500, _(\"The tool has not been published. Please use it after publishing.\"))\n        parameters = self.get_parameters(input_field_list)\n        tool_record_id = (self.node_params.get('child_node') or {}).get('chat_record_id') or str(uuid.uuid7())\n        took_execute = ToolExecute(tool_lib_id, tool_record_id,\n                                   workspace_id,\n                                   self.workflow_manage.get_source_type(),\n                                   self.workflow_manage.get_source_id(),\n                                   False)\n\n        def workflow_manage_new_instance(start_node_id=None,\n                                         start_node_data=None, chat_record=None, child_node=None):\n            work_flow_manage = ToolWorkflowManage(\n                Workflow.new_instance(tool_workflow_version.work_flow, WorkflowMode.TOOL),\n                {\n                    'chat_record_id': tool_record_id,\n                    'tool_id': tool_lib_id,\n                    'stream': True,\n                    'workspace_id': workspace_id,\n                    **parameters},\n                ToolWorkflowPostHandler(took_execute, tool_lib_id),\n                base_to_response=LoopToResponse(),\n                start_node_id=start_node_id,\n                start_node_data=start_node_data,\n                child_node=child_node,\n                chat_record=self.to_chat_record(took_execute.get_record()),\n                is_the_task_interrupted=lambda: False)\n\n            return work_flow_manage\n\n        return NodeResult({'workflow_manage_new_instance': workflow_manage_new_instance},\n                          {}, _write_context=write_context_stream,\n                          _is_interrupt=_is_interrupt_exec)\n\n    def get_details(self, index: int, **kwargs):\n        result = self.context.get('result')\n\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            \"result\": result,\n            \"params\": self.context.get('params'),\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'status': self.status,\n            'child_node_data': self.context.get(\"child_node_data\"),\n            'child_answer_data': self.context.get(\"child_answer_data\"),\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/variable_aggregation_node/__init__.py",
    "content": ""
  },
  {
    "path": "apps/application/flow/step_node/variable_aggregation_node/i_variable_aggregation_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass VariableListSerializer(serializers.Serializer):\n    v_id = serializers.CharField(required=True, label=_(\"Variable id\"))\n    variable = serializers.ListField(required=True, label=_(\"Variable\"))\n\n\nclass VariableGroupSerializer(serializers.Serializer):\n    id = serializers.CharField(required=True, label=_(\"Group id\"))\n    field = serializers.CharField(required=True, label=_(\"group_name\"))\n    label = serializers.CharField(required=True)\n    variable_list = VariableListSerializer(many=True)\n\n\nclass VariableAggregationNodeSerializer(serializers.Serializer):\n    strategy = serializers.CharField(required=True, label=_(\"Strategy\"))\n    group_list = VariableGroupSerializer(many=True)\n\n\nclass IVariableAggregation(INode):\n    type = 'variable-aggregation-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return VariableAggregationNodeSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, strategy, group_list, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/variable_aggregation_node/impl/__init__.py",
    "content": ""
  },
  {
    "path": "apps/application/flow/step_node/variable_aggregation_node/impl/base_variable_aggregation_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎²\n    @file： base_variable_aggregation_node.py\n    @date：2025/10/23 17:42\n    @desc:\n\"\"\"\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.variable_aggregation_node.i_variable_aggregation_node import IVariableAggregation\n\n\ndef _filter_file_bytes(data):\n    \"\"\"递归过滤掉所有层级的 file_bytes\"\"\"\n    if isinstance(data, dict):\n        return {k: _filter_file_bytes(v) for k, v in data.items() if k != 'file_bytes'}\n    elif isinstance(data, list):\n        return [_filter_file_bytes(item) for item in data]\n    else:\n        return data\n\n\nclass BaseVariableAggregationNode(IVariableAggregation):\n\n    def save_context(self, details, workflow_manage):\n        for key, value in details.get('result').items():\n            self.context['key'] = value\n        self.context['result'] = details.get('result')\n        self.context['strategy'] = details.get('strategy')\n        self.context['group_list'] = details.get('group_list')\n        self.context['exception_message'] = details.get('err_message')\n\n    def get_first_non_null(self, variable_list):\n\n        for variable in variable_list:\n            v = self.workflow_manage.get_reference_field(\n                variable.get('variable')[0],\n                variable.get('variable')[1:])\n            if v is not None and not (isinstance(v, (str, list, dict)) and len(v) == 0):\n                return v\n        return None\n\n    def set_variable_to_json(self, variable_list):\n\n        return [self.workflow_manage.get_reference_field(\n            variable.get('variable')[0],\n            variable.get('variable')[1:]) for variable in variable_list]\n\n    def reset_variable(self, variable):\n        value = self.workflow_manage.get_reference_field(\n            variable.get('variable')[0],\n            variable.get('variable')[1:])\n        node_id = variable.get('variable')[0]\n        node = self.workflow_manage.flow.get_node(node_id)\n        return {\"value\": value, 'node_name': node.properties.get('stepName') if node is not None else node_id,\n                'field': variable.get('variable')[1]}\n\n    def reset_group_list(self, group_list):\n        result = []\n        for g in group_list:\n            b = {'label': g.get('label'),\n                 'variable_list': [self.reset_variable(variable) for variable in g.get('variable_list')]}\n            result.append(b)\n        return result\n\n    def execute(self, strategy, group_list, **kwargs) -> NodeResult:\n        strategy_map = {'first_non_null': self.get_first_non_null,\n                        'variable_to_json': self.set_variable_to_json,\n                        }\n\n        result = {item.get('field'): strategy_map[strategy](item.get('variable_list')) for item in group_list}\n\n        return NodeResult(\n            {'result': result, 'strategy': strategy, 'group_list': self.reset_group_list(group_list), **result}, {})\n\n    def get_details(self, index: int, **kwargs):\n        result = _filter_file_bytes(self.context.get('result'))\n        group_list = _filter_file_bytes(self.context.get('group_list'))\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'result': result,\n            'strategy': self.context.get('strategy'),\n            'group_list': group_list,\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/variable_assign_node/__init__.py",
    "content": "# coding=utf-8\n\nfrom .impl import *"
  },
  {
    "path": "apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass VariableAssignNodeParamsSerializer(serializers.Serializer):\n    variable_list = serializers.ListField(required=True,\n                                          label=_(\"Reference Field\"))\n\n\nclass IVariableAssignNode(INode):\n    type = 'variable-assign-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return VariableAssignNodeParamsSerializer\n\n    def _run(self):\n        return self.execute(**self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, variable_list, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/variable_assign_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/6/11 17:49\n    @desc:\n\"\"\"\nfrom .base_variable_assign_node import *"
  },
  {
    "path": "apps/application/flow/step_node/variable_assign_node/impl/base_variable_assign_node.py",
    "content": "# coding=utf-8\nimport json\nfrom typing import List\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.variable_assign_node.i_variable_assign_node import IVariableAssignNode\n\n\nclass BaseVariableAssignNode(IVariableAssignNode):\n    def save_context(self, details, workflow_manage):\n        self.context['variable_list'] = details.get('variable_list')\n        self.context['result_list'] = details.get('result_list')\n        self.context['exception_message'] = details.get('err_message')\n\n    def global_evaluation(self, variable, value):\n        from application.flow.loop_workflow_manage import LoopWorkflowManage\n        if isinstance(self.workflow_manage, LoopWorkflowManage):\n            self.workflow_manage.parentWorkflowManage.context[variable['fields'][1]] = value\n        else:\n            self.workflow_manage.context[variable['fields'][1]] = value\n\n    def loop_evaluation(self, variable, value):\n        from application.flow.loop_workflow_manage import LoopWorkflowManage\n        if isinstance(self.workflow_manage, LoopWorkflowManage):\n            self.workflow_manage.get_loop_context()[variable['fields'][1]] = value\n\n    def chat_evaluation(self, variable, value):\n        from application.flow.loop_workflow_manage import LoopWorkflowManage\n        if isinstance(self.workflow_manage, LoopWorkflowManage):\n            self.workflow_manage.parentWorkflowManage.chat_context[variable['fields'][1]] = value\n        else:\n            self.workflow_manage.chat_context[variable['fields'][1]] = value\n\n    def out_evaluation(self, variable, value):\n        from application.flow.loop_workflow_manage import LoopWorkflowManage\n        if isinstance(self.workflow_manage, LoopWorkflowManage):\n            self.workflow_manage.parentWorkflowManage.out_context[variable['fields'][1]] = value\n        else:\n            self.workflow_manage.out_context[variable['fields'][1]] = value\n\n    def handle(self, variable, evaluation):\n        result = {\n            'name': variable['name'],\n            'input_value': self.get_reference_content(variable['fields']),\n        }\n        if variable['source'] == 'custom':\n            if variable['type'] == 'json':\n                if isinstance(variable['value'], dict) or isinstance(variable['value'], list):\n                    val = variable['value']\n                else:\n                    val = json.loads(variable['value'])\n                evaluation(variable, val)\n                result['output_value'] = variable['value'] = val\n            elif variable['type'] == 'string':\n                # 变量解析 例如：{{global.xxx}}\n                val = self.workflow_manage.generate_prompt(variable['value'])\n                evaluation(variable, val)\n                result['output_value'] = val\n            else:\n                val = variable['value']\n                evaluation(variable, val)\n                result['output_value'] = val\n        else:\n            reference = self.get_reference_content(variable['reference'])\n            evaluation(variable, reference)\n            result['output_value'] = reference\n        return result\n\n    def execute(self, variable_list, **kwargs) -> NodeResult:\n        #\n        result_list = []\n        is_chat = False\n        for variable in variable_list:\n            if 'fields' not in variable:\n                continue\n            if 'global' == variable['fields'][0]:\n                result = self.handle(variable, self.global_evaluation)\n                result_list.append(result)\n            if 'chat' == variable['fields'][0]:\n                result = self.handle(variable, self.chat_evaluation)\n                result_list.append(result)\n                is_chat = True\n            if 'loop' == variable['fields'][0]:\n                result = self.handle(variable, self.loop_evaluation)\n                result_list.append(result)\n            if 'output' == variable['fields'][0]:\n                result = self.handle(variable, self.out_evaluation)\n                result_list.append(result)\n        if is_chat:\n            from application.flow.loop_workflow_manage import LoopWorkflowManage\n            if isinstance(self.workflow_manage, LoopWorkflowManage):\n                self.workflow_manage.parentWorkflowManage.get_chat_info().set_chat_variable(\n                    self.workflow_manage.parentWorkflowManage.chat_context)\n            else:\n                self.workflow_manage.get_chat_info().set_chat_variable(self.workflow_manage.chat_context)\n        return NodeResult({'variable_list': variable_list, 'result_list': result_list}, {})\n\n    def get_reference_content(self, fields: List[str]):\n        return self.workflow_manage.get_reference_field(\n            fields[0],\n            fields[1:])\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'variable_list': self.context.get('variable_list'),\n            'result_list': self.context.get('result_list'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/variable_splitting_node/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/10/13 14:56\n    @desc:\n\"\"\"\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/variable_splitting_node/i_variable_splitting_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass VariableSplittingNodeParamsSerializer(serializers.Serializer):\n    input_variable = serializers.ListField(required=True,\n                                           label=_(\"input variable\"))\n\n    variable_list = serializers.ListField(required=True,\n                                          label=_(\"Split variables\"))\n\n\nclass IVariableSplittingNode(INode):\n    type = 'variable-splitting-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return VariableSplittingNodeParamsSerializer\n\n    def _run(self):\n        input_variable = self.workflow_manage.get_reference_field(\n            self.node_params_serializer.data.get('input_variable')[0],\n            self.node_params_serializer.data.get('input_variable')[1:])\n        return self.execute(input_variable, self.node_params_serializer.data['variable_list'])\n\n    def execute(self, input_variable, variable_list, **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/variable_splitting_node/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/10/13 15:01\n    @desc:\n\"\"\"\nfrom .base_variable_splitting_node import *"
  },
  {
    "path": "apps/application/flow/step_node/variable_splitting_node/impl/base_variable_splitting_node.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： base_variable_splitting_node.py\n    @date：2025/10/13 15:02\n    @desc:\n\"\"\"\nimport json\nfrom jsonpath_ng import parse\n\nfrom application.flow.i_step_node import NodeResult\nfrom application.flow.step_node.variable_splitting_node.i_variable_splitting_node import IVariableSplittingNode\n\n\ndef smart_jsonpath_search(data: dict, path: str):\n    \"\"\"\n    智能JSON Path搜索\n    返回:\n    - 单个匹配: 直接返回值\n    - 多个匹配: 返回值的列表\n    - 无匹配: 返回None\n    \"\"\"\n    jsonpath_expr = parse(path)\n    matches = jsonpath_expr.find(data)\n\n    if not matches:\n        return None\n    elif len(matches) == 1:\n        return matches[0].value\n    else:\n        return [match.value for match in matches]\n\n\nclass BaseVariableSplittingNode(IVariableSplittingNode):\n    def save_context(self, details, workflow_manage):\n        for key, value in details.get('result').items():\n            self.context[key] = value\n        self.context['result'] = details.get('result')\n        self.context['request'] = details.get('request')\n        self.context['exception_message'] = details.get('err_message')\n\n    def execute(self, input_variable, variable_list, **kwargs) -> NodeResult:\n        if type(input_variable).__name__ == \"str\":\n            try:\n                input_variable = json.loads(input_variable)\n            except Exception:\n                pass\n\n        self.context['request'] = input_variable\n        response = {v['field']: smart_jsonpath_search(input_variable, v['expression']) for v in variable_list}\n        return NodeResult({'result': response, **response}, {})\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'type': self.node.type,\n            'request': self.context.get('request'),\n            'result': self.context.get('result'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/step_node/video_understand_step_node/__init__.py",
    "content": "# coding=utf-8\n\nfrom .impl import *\n"
  },
  {
    "path": "apps/application/flow/step_node/video_understand_step_node/i_video_understand_node.py",
    "content": "# coding=utf-8\n\nfrom typing import Type\n\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.common import WorkflowMode\nfrom application.flow.i_step_node import INode, NodeResult\n\n\nclass VideoUnderstandNodeSerializer(serializers.Serializer):\n    model_id = serializers.CharField(required=True, label=_(\"Model id\"))\n    system = serializers.CharField(required=False, allow_blank=True, allow_null=True,\n                                   label=_(\"Role Setting\"))\n    prompt = serializers.CharField(required=True, label=_(\"Prompt word\"))\n    # 多轮对话数量\n    dialogue_number = serializers.IntegerField(required=True, label=_(\"Number of multi-round conversations\"))\n\n    dialogue_type = serializers.CharField(required=True, label=_(\"Conversation storage type\"))\n\n    is_result = serializers.BooleanField(required=False,\n                                         label=_('Whether to return content'))\n\n    video_list = serializers.ListField(required=False, label=_(\"video\"))\n\n    model_params_setting = serializers.JSONField(required=False, default=dict,\n                                                 label=_(\"Model parameter settings\"))\n\n\nclass IVideoUnderstandNode(INode):\n    type = 'video-understand-node'\n    support = [WorkflowMode.APPLICATION, WorkflowMode.APPLICATION_LOOP, WorkflowMode.KNOWLEDGE,\n               WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL, WorkflowMode.TOOL_LOOP]\n\n    def get_node_params_serializer_class(self) -> Type[serializers.Serializer]:\n        return VideoUnderstandNodeSerializer\n\n    def _run(self):\n        res = self.workflow_manage.get_reference_field(self.node_params_serializer.data.get('video_list')[0],\n                                                       self.node_params_serializer.data.get('video_list')[1:])\n\n        if [WorkflowMode.KNOWLEDGE, WorkflowMode.KNOWLEDGE_LOOP, WorkflowMode.TOOL,\n            WorkflowMode.TOOL_LOOP].__contains__(\n                self.workflow_manage.flow.workflow_mode):\n            return self.execute(video=res, **self.node_params_serializer.data, **self.flow_params_serializer.data,\n                                **{'history_chat_record': [], 'stream': True, 'chat_id': None, 'chat_record_id': None})\n        else:\n            return self.execute(video=res, **self.node_params_serializer.data, **self.flow_params_serializer.data)\n\n    def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream,\n                model_params_setting,\n                chat_record_id,\n                video,\n                **kwargs) -> NodeResult:\n        pass\n"
  },
  {
    "path": "apps/application/flow/step_node/video_understand_step_node/impl/__init__.py",
    "content": "# coding=utf-8\n\nfrom .base_video_understand_node import BaseVideoUnderstandNode\n"
  },
  {
    "path": "apps/application/flow/step_node/video_understand_step_node/impl/base_video_understand_node.py",
    "content": "# coding=utf-8\n\nimport time\nfrom functools import reduce\nfrom typing import List, Dict\n\nfrom django.db.models import QuerySet\nfrom langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage\n\nfrom application.flow.i_step_node import NodeResult, INode\nfrom application.flow.step_node.video_understand_step_node.i_video_understand_node import IVideoUnderstandNode\nfrom knowledge.models import File\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\n\n\ndef _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):\n    chat_model = node_variable.get('chat_model')\n    message_tokens = node_variable['usage_metadata']['output_tokens'] if 'usage_metadata' in node_variable else 0\n    answer_tokens = chat_model.get_num_tokens(answer)\n    node.context['message_tokens'] = message_tokens\n    node.context['answer_tokens'] = answer_tokens\n    node.context['answer'] = answer\n    node.context['history_message'] = node_variable['history_message']\n    node.context['question'] = node_variable['question']\n    node.context['run_time'] = time.time() - node.context['start_time']\n    if workflow.is_result(node, NodeResult(node_variable, workflow_variable)):\n        node.answer_text = answer\n\n\ndef write_context_stream(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据 (流式)\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result')\n    answer = ''\n    for chunk in response:\n        answer += chunk.content\n        yield chunk.content\n    _write_context(node_variable, workflow_variable, node, workflow, answer)\n\n\ndef write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow):\n    \"\"\"\n    写入上下文数据\n    @param node_variable:      节点数据\n    @param workflow_variable:  全局数据\n    @param node:               节点实例对象\n    @param workflow:           工作流管理器\n    \"\"\"\n    response = node_variable.get('result')\n    answer = response.content\n    _write_context(node_variable, workflow_variable, node, workflow, answer)\n\n\ndef file_id_to_base64(file_id: str, video_model):\n    file = QuerySet(File).filter(id=file_id).first()\n    file_bytes = file.get_bytes()\n    url = video_model.upload_file_and_get_url(file_bytes, file.file_name)\n    return url\n\n\nclass BaseVideoUnderstandNode(IVideoUnderstandNode):\n    def save_context(self, details, workflow_manage):\n        self.context['answer'] = details.get('answer')\n        self.context['question'] = details.get('question')\n        self.context['exception_message'] = details.get('err_message')\n        if self.node_params.get('is_result', False):\n            self.answer_text = details.get('answer')\n\n    def execute(self, model_id, system, prompt, dialogue_number, dialogue_type, history_chat_record, stream,\n                model_params_setting,\n                chat_record_id,\n                video,\n                **kwargs) -> NodeResult:\n        workspace_id = self.workflow_manage.get_body().get('workspace_id')\n        video_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,\n                                                               **model_params_setting)\n        # 执行详情中的历史消息不需要图片内容\n        history_message = self.get_history_message_for_details(history_chat_record, dialogue_number)\n        self.context['history_message'] = history_message\n        question = self.generate_prompt_question(prompt)\n        self.context['question'] = question.content\n        # 生成消息列表, 真实的history_message\n        message_list = self.generate_message_list(video_model, system, prompt,\n                                                  self.get_history_message(history_chat_record, dialogue_number,\n                                                                           video_model), video)\n        self.context['message_list'] = message_list\n        self.generate_context_video(video)\n        self.context['dialogue_type'] = dialogue_type\n        if stream:\n            r = video_model.stream(message_list)\n            return NodeResult({'result': r, 'chat_model': video_model, 'message_list': message_list,\n                               'history_message': history_message, 'question': question.content}, {},\n                              _write_context=write_context_stream)\n        else:\n            r = video_model.invoke(message_list)\n            return NodeResult({'result': r, 'chat_model': video_model, 'message_list': message_list,\n                               'history_message': history_message, 'question': question.content}, {},\n                              _write_context=write_context)\n\n    def generate_context_video(self, video):\n        if isinstance(video, str) and video.startswith('http'):\n            self.context['video_list'] = [{'url': video}]\n        elif video is not None and len(video) > 0:\n            self.context['video_list'] = video\n\n    def get_history_message_for_details(self, history_chat_record, dialogue_number):\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = reduce(lambda x, y: [*x, *y], [\n            [self.generate_history_human_message_for_details(history_chat_record[index]),\n             self.generate_history_ai_message(history_chat_record[index])]\n            for index in\n            range(start_index if start_index > 0 else 0, len(history_chat_record))], [])\n        return history_message\n\n    def generate_history_ai_message(self, chat_record):\n        for val in chat_record.details.values():\n            if self.node.id == val['node_id'] and 'video_list' in val:\n                if val['dialogue_type'] == 'WORKFLOW':\n                    return chat_record.get_ai_message()\n                return AIMessage(content=val.get('answer') or val.get('err_message') or '')\n        return chat_record.get_ai_message()\n\n    def generate_history_human_message_for_details(self, chat_record):\n        for data in chat_record.details.values():\n            if self.node.id == data['node_id'] and 'video_list' in data:\n                video_list = data['video_list']\n                # 增加对 None 和空列表的检查\n                if not video_list or len(video_list) == 0 or data['dialogue_type'] == 'WORKFLOW':\n                    return HumanMessage(content=chat_record.problem_text)\n                file_id_list = []\n                url_list = []\n                for image in video_list:\n                    if 'file_id' in image:\n                        file_id_list.append(image.get('file_id'))\n                    elif 'url' in image:\n                        url_list.append(image.get('url'))\n                return HumanMessage(content=[\n                    {'type': 'text', 'text': data['question']},\n                    *[{'type': 'video_url', 'video_url': {'url': f'./oss/file/{file_id}'}} for file_id in file_id_list],\n                    *[{'type': 'video_url', 'video_url': {'url': url}} for url in url_list],\n                ])\n        return HumanMessage(content=chat_record.problem_text)\n\n    def get_history_message(self, history_chat_record, dialogue_number, video_model):\n        start_index = len(history_chat_record) - dialogue_number\n        history_message = reduce(lambda x, y: [*x, *y], [\n            [self.generate_history_human_message(history_chat_record[index], video_model),\n             self.generate_history_ai_message(history_chat_record[index])]\n            for index in\n            range(start_index if start_index > 0 else 0, len(history_chat_record))], [])\n        return history_message\n\n    def generate_history_human_message(self, chat_record, video_model):\n\n        for data in chat_record.details.values():\n            if self.node.id == data['node_id'] and 'video_list' in data:\n                video_list = data['video_list']\n                if video_list is None or len(video_list) == 0 or data['dialogue_type'] == 'WORKFLOW':\n                    return HumanMessage(content=chat_record.problem_text)\n                file_id_list = []\n                url_list = []\n                for image in video_list:\n                    if 'file_id' in image:\n                        file_id_list.append(image.get('file_id'))\n                    elif 'url' in image:\n                        url_list.append(image.get('url'))\n                video_base64_list = [file_id_to_base64(video.get('file_id'), video_model) for video in video_list]\n                return HumanMessage(\n                    content=[\n                        {'type': 'text', 'text': data['question']},\n                        *[{'type': 'video_url',\n                           'video_url': {'url': f'{base64_video}'}} for\n                          base64_video in video_base64_list]\n                    ])\n        return HumanMessage(content=chat_record.problem_text)\n\n    def generate_prompt_question(self, prompt):\n        return HumanMessage(self.workflow_manage.generate_prompt(prompt))\n\n    def _process_videos(self, image, video_model):\n        videos = []\n        if isinstance(image, str) and image.startswith('http'):\n            videos.append({'type': 'video_url', 'video_url': {'url': image}})\n        elif image is not None and len(image) > 0:\n            for img in image:\n                if 'file_id' in img:\n                    file_id = img['file_id']\n                    file = QuerySet(File).filter(id=file_id).first()\n                    url = video_model.upload_file_and_get_url(file.get_bytes(), file.file_name)\n                    videos.append(\n                        {'type': 'video_url', 'video_url': {'url': url}})\n                elif 'url' in img and img['url'].startswith('http'):\n                    videos.append(\n                        {'type': 'video_url', 'video_url': {'url': img['url']}})\n        return videos\n\n    def generate_message_list(self, video_model, system: str, prompt: str, history_message, video):\n        prompt_text = self.workflow_manage.generate_prompt(prompt)\n        videos = self._process_videos(video, video_model)\n\n        if videos:\n            messages = [HumanMessage(content=[{'type': 'text', 'text': prompt_text}, *videos])]\n        else:\n            messages = [HumanMessage(prompt_text)]\n\n        if system is not None and len(system) > 0:\n            return [\n                SystemMessage(self.workflow_manage.generate_prompt(system)),\n                *history_message,\n                *messages\n            ]\n        else:\n            return [\n                *history_message,\n                *messages\n            ]\n\n    @staticmethod\n    def reset_message_list(message_list: List[BaseMessage], answer_text):\n        result = [{'role': 'user' if isinstance(message, HumanMessage) else 'ai', 'content': message.content} for\n                  message\n                  in\n                  message_list]\n        result.append({'role': 'ai', 'content': answer_text})\n        return result\n\n    def get_details(self, index: int, **kwargs):\n        return {\n            'name': self.node.properties.get('stepName'),\n            \"index\": index,\n            'run_time': self.context.get('run_time'),\n            'system': self.node_params.get('system'),\n            'history_message': [{'content': message.content, 'role': message.type} for message in\n                                (self.context.get('history_message') if self.context.get(\n                                    'history_message') is not None else [])],\n            'question': self.context.get('question'),\n            'answer': self.context.get('answer'),\n            'type': self.node.type,\n            'message_tokens': self.context.get('message_tokens'),\n            'answer_tokens': self.context.get('answer_tokens'),\n            'status': self.status,\n            'err_message': self.err_message,\n            'video_list': self.context.get('video_list'),\n            'dialogue_type': self.context.get('dialogue_type'),\n            'enableException': self.node.properties.get('enableException'),\n        }\n"
  },
  {
    "path": "apps/application/flow/tool_loop_workflow_manage.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： workflow_manage.py\n    @date：2024/1/9 17:40\n    @desc:\n\"\"\"\nfrom application.flow.i_step_node import ToolFlowParamsSerializer\nfrom application.flow.loop_workflow_manage import LoopWorkflowManage\n\n\nclass ToolLoopWorkflowManage(LoopWorkflowManage):\n    def get_params_serializer_class(self):\n        return ToolFlowParamsSerializer\n\n    def get_source_type(self):\n        return \"TOOL\"\n\n    def get_source_id(self):\n        return self.params.get('tool_id')\n"
  },
  {
    "path": "apps/application/flow/tool_workflow_manage.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： tool_workflow_manage.py\n    @date：2026/3/12 15:17\n    @desc:\n\"\"\"\nfrom concurrent.futures import ThreadPoolExecutor\n\nfrom django.db import close_old_connections\nfrom django.utils.translation import get_language\n\nfrom application.flow.common import Workflow\nfrom application.flow.i_step_node import WorkFlowPostHandler, ToolFlowParamsSerializer\nfrom application.flow.workflow_manage import WorkflowManage\nfrom common.handle.base_to_response import BaseToResponse\nfrom common.handle.impl.response.system_to_response import SystemToResponse\n\nexecutor = ThreadPoolExecutor(max_workers=200)\n\n\nclass ToolWorkflowManage(WorkflowManage):\n    def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostHandler,\n                 base_to_response: BaseToResponse = SystemToResponse(), form_data=None,\n                 start_node_id=None,\n                 start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False):\n        super().__init__(flow, params, work_flow_post_handler, base_to_response, form_data, None, None, None,\n                         None, None, start_node_id, start_node_data, chat_record, child_node, is_the_task_interrupted)\n        self.out_context = {}\n\n    def get_params_serializer_class(self):\n        return ToolFlowParamsSerializer\n\n    def stream(self):\n        close_old_connections()\n        language = get_language()\n        self.run_chain_async(self.start_node, None, language)\n        return self.await_result(is_cleanup=False)\n\n    def get_start_node(self):\n        return self.flow.get_node('tool-start-node')\n\n    def get_base_node(self):\n        \"\"\"\n        获取基础节点\n        @return:\n        \"\"\"\n        return self.flow.get_node('tool-base-node')\n\n    def get_source_type(self):\n        return \"TOOL\"\n\n    def get_source_id(self):\n        return self.params.get('tool_id')\n"
  },
  {
    "path": "apps/application/flow/tools.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： utils.py\n    @date：2024/6/6 15:15\n    @desc:\n\"\"\"\nfrom tools.models import ToolRecord, Tool\nfrom maxkb.const import CONFIG\nfrom knowledge.models.knowledge_action import State\nfrom knowledge.models import File\nfrom common.utils.logger import maxkb_logger\nfrom common.result import result\nfrom application.flow.i_step_node import WorkFlowPostHandler\nfrom application.flow.backend.sandbox_shell import SandboxShellBackend\nimport asyncio\nimport io\nimport json\nimport os\nimport queue\nimport re\nimport shutil\nimport threading\nimport zipfile\nfrom functools import reduce\nfrom typing import Iterator\n\nimport uuid_utils.compat as uuid\nfrom asgiref.sync import sync_to_async\nfrom deepagents import create_deep_agent\nfrom django.db.models import QuerySet\nfrom django.http import StreamingHttpResponse\nfrom langchain_core.messages import BaseMessageChunk, BaseMessage, ToolMessage, AIMessageChunk\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom langgraph.checkpoint.memory import MemorySaver\n\n# ---------------------------------------------------------------------------\n# Fix: qwen's OpenAI-compatible streaming sends id='' (empty string) for\n# intermediate tool_call_chunks while only the first chunk carries the real\n# id ('call_xxx...'). langchain-core's merge_lists treats '' != 'call_xxx' as\n# an ID conflict and _appends_ instead of merging → the accumulated AIMessage\n# ends up with two separate tool_calls (one with empty args, one with empty\n# id) instead of one correct entry. This causes the Qwen API to reject the\n# next request with \"function.arguments must be in JSON format\".\n#\n# Patch: normalise id='' → None for items that have an 'index' key\n# (i.e. tool_call_chunk dicts). merge_lists treats None as \"no id\" and will\n# merge with any existing entry, keeping the real id from the first chunk.\n# ---------------------------------------------------------------------------\nimport langchain_core.messages.ai as _lc_ai_module\nfrom langchain_core.utils._merge import merge_lists as _original_merge_lists\n\n\ndef _merge_lists_normalize_empty_tool_chunk_ids(left, *others):\n    \"\"\"Wrapper around merge_lists that normalises empty-string IDs to None in\n    tool_call_chunk items (those with an 'index' key) so that qwen streaming\n    chunks with id='' are merged correctly by index.\"\"\"\n    def _norm(lst):\n        if lst is None:\n            return lst\n        result = []\n        for item in lst:\n            if isinstance(item, dict) and 'index' in item and item.get('id') == '':\n                item = {**item, 'id': None}\n            result.append(item)\n        return result\n\n    return _original_merge_lists(\n        _norm(left),\n        *[_norm(o) for o in others],\n    )\n\n\n# Replace the module-level reference used by add_ai_message_chunks in ai.py\n_lc_ai_module.merge_lists = _merge_lists_normalize_empty_tool_chunk_ids\n\n\nclass Reasoning:\n    def __init__(self, reasoning_content_start, reasoning_content_end):\n        self.content = \"\"\n        self.reasoning_content = \"\"\n        self.all_content = \"\"\n        self.reasoning_content_start_tag = reasoning_content_start\n        self.reasoning_content_end_tag = reasoning_content_end\n        self.reasoning_content_start_tag_len = len(\n            reasoning_content_start) if reasoning_content_start is not None else 0\n        self.reasoning_content_end_tag_len = len(\n            reasoning_content_end) if reasoning_content_end is not None else 0\n        self.reasoning_content_end_tag_prefix = reasoning_content_end[\n            0] if self.reasoning_content_end_tag_len > 0 else ''\n        self.reasoning_content_is_start = False\n        self.reasoning_content_is_end = False\n        self.reasoning_content_chunk = \"\"\n\n    def get_end_reasoning_content(self):\n        if not self.reasoning_content_is_start and not self.reasoning_content_is_end:\n            r = {'content': self.all_content, 'reasoning_content': ''}\n            self.reasoning_content_chunk = \"\"\n            return r\n        if self.reasoning_content_is_start and not self.reasoning_content_is_end:\n            r = {'content': '', 'reasoning_content': self.reasoning_content_chunk}\n            self.reasoning_content_chunk = \"\"\n            return r\n        return {'content': '', 'reasoning_content': ''}\n\n    def _normalize_content(self, content):\n        \"\"\"将不同类型的内容统一转换为字符串\"\"\"\n        if isinstance(content, str):\n            return content\n        elif isinstance(content, list):\n            # 处理包含多种内容类型的列表\n            normalized_parts = []\n            for item in content:\n                if isinstance(item, dict):\n                    if item.get('type') == 'text':\n                        normalized_parts.append(item.get('text', ''))\n            return ''.join(normalized_parts)\n        else:\n            return str(content)\n\n    def get_reasoning_content(self, chunk):\n        # 如果没有开始思考过程标签那么就全是结果\n        if self.reasoning_content_start_tag is None or len(self.reasoning_content_start_tag) == 0:\n            self.content += chunk.content\n            return {'content': chunk.content, 'reasoning_content': ''}\n        # 如果没有结束思考过程标签那么就全部是思考过程\n        if self.reasoning_content_end_tag is None or len(self.reasoning_content_end_tag) == 0:\n            return {'content': '', 'reasoning_content': chunk.content}\n        chunk.content = self._normalize_content(chunk.content)\n        self.all_content += chunk.content\n        if not self.reasoning_content_is_start and len(self.all_content) >= self.reasoning_content_start_tag_len:\n            if self.all_content.startswith(self.reasoning_content_start_tag):\n                self.reasoning_content_is_start = True\n                self.reasoning_content_chunk = self.all_content[self.reasoning_content_start_tag_len:]\n            else:\n                if not self.reasoning_content_is_end:\n                    self.reasoning_content_is_end = True\n                    self.content += self.all_content\n                    return {'content': self.all_content,\n                            'reasoning_content': chunk.additional_kwargs.get('reasoning_content',\n                                                                             '') if chunk.additional_kwargs else ''\n                            }\n        else:\n            if self.reasoning_content_is_start:\n                self.reasoning_content_chunk += chunk.content\n        reasoning_content_end_tag_prefix_index = self.reasoning_content_chunk.find(\n            self.reasoning_content_end_tag_prefix)\n        if self.reasoning_content_is_end:\n            self.content += chunk.content\n            return {'content': chunk.content, 'reasoning_content': chunk.additional_kwargs.get('reasoning_content',\n                                                                                               '') if chunk.additional_kwargs else ''\n                    }\n        # 是否包含结束\n        if reasoning_content_end_tag_prefix_index > -1:\n            if len(self.reasoning_content_chunk) - reasoning_content_end_tag_prefix_index >= self.reasoning_content_end_tag_len:\n                reasoning_content_end_tag_index = self.reasoning_content_chunk.find(\n                    self.reasoning_content_end_tag)\n                if reasoning_content_end_tag_index > -1:\n                    reasoning_content_chunk = self.reasoning_content_chunk[\n                        0:reasoning_content_end_tag_index]\n                    content_chunk = self.reasoning_content_chunk[\n                        reasoning_content_end_tag_index + self.reasoning_content_end_tag_len:]\n                    self.reasoning_content += reasoning_content_chunk\n                    self.content += content_chunk\n                    self.reasoning_content_chunk = \"\"\n                    self.reasoning_content_is_end = True\n                    return {'content': content_chunk, 'reasoning_content': reasoning_content_chunk}\n                else:\n                    reasoning_content_chunk = self.reasoning_content_chunk[\n                        0:reasoning_content_end_tag_prefix_index + 1]\n                    self.reasoning_content_chunk = self.reasoning_content_chunk.replace(\n                        reasoning_content_chunk, '')\n                    self.reasoning_content += reasoning_content_chunk\n                    return {'content': '', 'reasoning_content': reasoning_content_chunk}\n            else:\n                return {'content': '', 'reasoning_content': ''}\n\n        else:\n            if self.reasoning_content_is_end:\n                self.content += chunk.content\n                return {'content': chunk.content, 'reasoning_content': chunk.additional_kwargs.get('reasoning_content',\n                                                                                                   '') if chunk.additional_kwargs else ''\n                        }\n            else:\n                # aaa\n                result = {'content': '',\n                          'reasoning_content': self.reasoning_content_chunk}\n                self.reasoning_content += self.reasoning_content_chunk\n                self.reasoning_content_chunk = \"\"\n                return result\n\n\ndef event_content(chat_id, chat_record_id, response, workflow,\n                  write_context,\n                  post_handler: WorkFlowPostHandler):\n    \"\"\"\n    用于处理流式输出\n    @param chat_id:         会话id\n    @param chat_record_id:  对话记录id\n    @param response:        响应数据\n    @param workflow:        工作流管理器\n    @param write_context    写入节点上下文\n    @param post_handler:    后置处理器\n    \"\"\"\n    answer = ''\n    try:\n        for chunk in response:\n            answer += chunk.content\n            yield 'data: ' + json.dumps({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True,\n                                         'content': chunk.content, 'is_end': False}, ensure_ascii=False) + \"\\n\\n\"\n        write_context(answer, 200)\n        post_handler.handler(chat_id, chat_record_id, answer, workflow)\n        yield 'data: ' + json.dumps({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True,\n                                     'content': '', 'is_end': True}, ensure_ascii=False) + \"\\n\\n\"\n    except Exception as e:\n        answer = str(e)\n        write_context(answer, 500)\n        post_handler.handler(chat_id, chat_record_id, answer, workflow)\n        yield 'data: ' + json.dumps({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True,\n                                     'content': answer, 'is_end': True}, ensure_ascii=False) + \"\\n\\n\"\n\n\ndef to_stream_response(chat_id, chat_record_id, response: Iterator[BaseMessageChunk], workflow, write_context,\n                       post_handler):\n    \"\"\"\n    将结果转换为服务流输出\n    @param chat_id:        会话id\n    @param chat_record_id: 对话记录id\n    @param response:       响应数据\n    @param workflow:       工作流管理器\n    @param write_context   写入节点上下文\n    @param post_handler:   后置处理器\n    @return: 响应\n    \"\"\"\n    r = StreamingHttpResponse(\n        streaming_content=event_content(\n            chat_id, chat_record_id, response, workflow, write_context, post_handler),\n        content_type='text/event-stream;charset=utf-8',\n        charset='utf-8')\n\n    r['Cache-Control'] = 'no-cache'\n    return r\n\n\ndef to_response(chat_id, chat_record_id, response: BaseMessage, workflow, write_context,\n                post_handler: WorkFlowPostHandler):\n    \"\"\"\n    将结果转换为服务输出\n\n    @param chat_id:        会话id\n    @param chat_record_id: 对话记录id\n    @param response:       响应数据\n    @param workflow:       工作流管理器\n    @param write_context   写入节点上下文\n    @param post_handler:   后置处理器\n    @return: 响应\n    \"\"\"\n    answer = response.content\n    write_context(answer)\n    post_handler.handler(chat_id, chat_record_id, answer, workflow)\n    return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True,\n                           'content': answer, 'is_end': True})\n\n\ndef to_response_simple(chat_id, chat_record_id, response: BaseMessage, workflow,\n                       post_handler: WorkFlowPostHandler):\n    answer = response.content\n    post_handler.handler(chat_id, chat_record_id, answer, workflow)\n    return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True,\n                           'content': answer, 'is_end': True})\n\n\ndef to_stream_response_simple(stream_event):\n    r = StreamingHttpResponse(\n        streaming_content=stream_event,\n        content_type='text/event-stream;charset=utf-8',\n        charset='utf-8')\n\n    r['Cache-Control'] = 'no-cache'\n    return r\n\n\ndef generate_tool_message_complete(icon, name, input_content, output_content):\n    \"\"\"生成包含输入和输出的工具消息模版\"\"\"\n    # 确保输入内容是字符串，如果不是则尝试转换为 JSON 字符串\n    if not isinstance(input_content, str):\n        input_content = json.dumps(input_content, ensure_ascii=False)\n    # 格式化输出\n    if not isinstance(output_content, str):\n        output_content = json.dumps(output_content, ensure_ascii=False)\n    content = {\n        \"icon\": icon,\n        \"title\": name,\n        \"type\": \"simple-tool-calls\",\n        \"content\": {\n            \"input\": input_content,\n            \"output\": output_content\n        }\n    }\n    return f'<tool_calls_render>{json.dumps(content)}</tool_calls_render>'\n\n\n# 全局单例事件循环\n_global_loop = None\n_loop_thread = None\n_loop_lock = threading.Lock()\n\n\ndef get_global_loop():\n    \"\"\"获取全局共享的事件循环\"\"\"\n    global _global_loop, _loop_thread\n\n    with _loop_lock:\n        if _global_loop is None:\n            _global_loop = asyncio.new_event_loop()\n\n            def run_forever():\n                asyncio.set_event_loop(_global_loop)\n                _global_loop.run_forever()\n\n            _loop_thread = threading.Thread(\n                target=run_forever, daemon=True, name=\"GlobalAsyncLoop\")\n            _loop_thread.start()\n\n    return _global_loop\n\n\ndef _extract_tool_id(raw_id):\n    \"\"\"从 raw_id 中提取最后一个符合 call_... 模式的 id，若无匹配则返回原值或 None\"\"\"\n    if not raw_id:\n        return None\n    if not isinstance(raw_id, str):\n        raw_id = str(raw_id)\n\n    s = raw_id\n    prefix = 'call_'\n    positions = [m.start() for m in re.finditer(re.escape(prefix), s)]\n    if not positions:\n        return raw_id\n\n    # 取最后一个前缀位置，截到下一个前缀或结尾\n    start = positions[-1]\n    end = len(s)\n    for pos in positions:\n        if pos > start:\n            end = pos\n            break\n\n    tool_id = s[start:end]\n    return tool_id or raw_id\n\n\nasync def _initialize_skills(mcp_servers, temp_dir):\n    skills_dir = os.path.join(temp_dir, 'skills')\n    mcp_config = json.loads(mcp_servers)\n    if \"skills\" in mcp_config:\n        skill_file_items = mcp_config.pop('skills')\n        for skill_file in skill_file_items:\n            # 使用 sync_to_async 包装 ORM 查询\n            file = await sync_to_async(lambda: QuerySet(File).filter(id=skill_file['file_id']).first())()\n            if not file:\n                continue\n            # get_bytes 可能也涉及 IO，也用 sync_to_async 包装\n            file_bytes = await sync_to_async(file.get_bytes)()\n            params = skill_file.get('params', {})\n            with zipfile.ZipFile(io.BytesIO(file_bytes), 'r') as zip_ref:\n                members = [\n                    m for m in zip_ref.namelist()\n                    if not m.startswith('__MACOSX/') and '__MACOSX' not in m\n                ]\n                for member in members:\n                    if \"..\" in member or member.startswith(\"/\"):\n                        raise ValueError(f\"非法路径: {member}\")\n                zip_ref.extractall(skills_dir, members=members)\n\n                # 获取技能解压后的顶级目录名\n                top_level_dirs = set()\n                for member in members:\n                    parts = member.split('/')\n                    if parts[0]:\n                        top_level_dirs.add(parts[0])\n\n                # 将 params 写入每个顶级目录下的 .env 文件\n                if params:\n                    env_lines = []\n                    for key, value in params.items():\n                        # 对含空格或特殊字符的值加引号\n                        env_lines.append(f'{key}={value}')\n                    env_content = '\\n'.join(env_lines) + '\\n'\n                    for top_dir in top_level_dirs:\n                        env_path = os.path.join(skills_dir, top_dir, '.env')\n                        with open(env_path, 'w', encoding='utf-8') as f:\n                            f.write(env_content)\n\n        os.system(\"chmod -R g+rx \" + temp_dir)  # 确保技能目录可访问\n\n    client = MultiServerMCPClient(mcp_config)\n\n    return client\n\n\nasync def _yield_mcp_response(chat_model, message_list, mcp_servers, mcp_output_enable=True, tool_init_params={},\n                              source_id=None, source_type=None, temp_dir=None, chat_id=None):\n    try:\n        checkpointer = MemorySaver()\n        client = await _initialize_skills(mcp_servers, temp_dir)\n        tools = await client.get_tools()\n        agent = create_deep_agent(\n            model=chat_model,\n            backend=SandboxShellBackend(root_dir=temp_dir, virtual_mode=True),\n            skills=['/skills'],\n            tools=tools,\n            interrupt_on={\n                \"write_file\": False,\n                \"read_file\": False,\n                \"edit_file\": False\n            },\n            checkpointer=checkpointer,\n        )\n        recursion_limit = int(CONFIG.get(\n            \"LANGCHAIN_GRAPH_RECURSION_LIMIT\", '100'))\n        response = agent.astream(\n            {\"messages\": message_list},\n            config={\"recursion_limit\": recursion_limit,\n                    \"configurable\": {\"thread_id\": chat_id}},\n            stream_mode='messages'\n        )\n\n        tool_calls_info = {}  # tool_id -> {'name': ..., 'input': ...}\n        # key(index/id) -> {'id': ..., 'name': ..., 'arguments': ...}\n        _tool_fragments = {}\n\n        def _merge_arguments(entry, part_args):\n            if not isinstance(part_args, str):\n                try:\n                    part_args = json.dumps(part_args, ensure_ascii=False)\n                except Exception:\n                    part_args = str(part_args) if part_args else ''\n            if not part_args:\n                return\n\n            # Some providers first emit placeholder args like \"{}\" and then\n            # stream the real JSON fragments via later chunks. Prefer fragments.\n            if entry['arguments'] in ('{}', '[]') and part_args.startswith('{'):\n                entry['arguments'] = part_args\n                return\n\n            if entry['arguments']:\n                try:\n                    existing_obj = json.loads(entry['arguments'])\n                    new_obj = json.loads(part_args)\n                    if isinstance(existing_obj, dict) and isinstance(new_obj, dict):\n                        merged = {**existing_obj, **new_obj}\n                        entry['arguments'] = json.dumps(\n                            merged, ensure_ascii=False)\n                    else:\n                        entry['arguments'] += part_args\n                except (json.JSONDecodeError, ValueError):\n                    entry['arguments'] += part_args\n            else:\n                entry['arguments'] = part_args\n\n        def _get_fragment_key(idx, raw_id):\n            if idx is not None:\n                return f'idx:{idx}'\n            if raw_id and str(raw_id).strip():\n                return f\"id:{_extract_tool_id(str(raw_id).strip())}\"\n            return None\n\n        def _upsert_fragment(key, raw_id, func_name, part_args):\n            if key is None:\n                return\n            entry = _tool_fragments.setdefault(\n                key, {'id': '', 'name': '', 'arguments': ''})\n\n            if raw_id and str(raw_id).strip():\n                new_id = str(raw_id).strip()\n                if entry.get('completed') and entry.get('id') and entry['id'] != new_id:\n                    maxkb_logger.debug(\n                        f\"Resetting completed fragment {key}: old ID {entry['id']} -> new ID {new_id}\")\n                    entry.clear()\n                    entry.update({'id': '', 'name': '', 'arguments': ''})\n                entry['id'] = new_id\n\n            if func_name:\n                entry['name'] = func_name\n\n            _merge_arguments(entry, part_args)\n\n        async for chunk in response:\n            # print(chunk)\n            if isinstance(chunk[0], AIMessageChunk):\n                # ----------------------------------------------------------------\n                # 1. 从 tool_call_chunks 中聚合工具调用片段\n                #    (qwen/OpenAI streaming 通过 tool_call_chunks 传递，\n                #     additional_kwargs['tool_calls'] 在流式时通常为空)\n                # ----------------------------------------------------------------\n                for tc_chunk in (chunk[0].tool_call_chunks or []):\n                    raw_id = tc_chunk.get('id')\n                    key = _get_fragment_key(tc_chunk.get('index'), raw_id)\n                    _upsert_fragment(\n                        key,\n                        raw_id,\n                        tc_chunk.get('name'),\n                        tc_chunk.get('args', '')\n                    )\n\n                # ----------------------------------------------------------------\n                # 1.1 兼容部分模型将工具调用放在 chunk.tool_calls，且 tool_call_chunks\n                #     的 index 为空（例如 ollama/qwen）\n                # ----------------------------------------------------------------\n                has_tool_call_chunks = bool(chunk[0].tool_call_chunks)\n                for tool_call in (chunk[0].tool_calls or []):\n                    raw_id = tool_call.get('id')\n                    part_args = tool_call.get('args', '')\n                    # qwen-plus often emits {} here as a placeholder while\n                    # the real args are split in tool_call_chunks/invalid_tool_calls.\n                    if has_tool_call_chunks and (\n                        part_args == '' or part_args == {} or part_args == []\n                    ):\n                        part_args = ''\n                    key = _get_fragment_key(tool_call.get('index'), raw_id)\n                    _upsert_fragment(\n                        key,\n                        raw_id,\n                        tool_call.get('name'),\n                        part_args\n                    )\n\n                # ----------------------------------------------------------------\n                # 1.2 兼容 invalid_tool_calls 分片（部分模型会把中间 JSON 片段放这里）\n                # ----------------------------------------------------------------\n                for invalid_tool_call in (chunk[0].invalid_tool_calls or []):\n                    raw_id = invalid_tool_call.get('id')\n                    key = _get_fragment_key(\n                        invalid_tool_call.get('index'), raw_id)\n                    _upsert_fragment(\n                        key,\n                        raw_id,\n                        invalid_tool_call.get('name'),\n                        invalid_tool_call.get('args', '')\n                    )\n\n                # ----------------------------------------------------------------\n                # 2. 兼容 additional_kwargs['tool_calls'] 方式（旧格式/非流式情况）\n                # ----------------------------------------------------------------\n                legacy_tool_calls = chunk[0].additional_kwargs.get(\n                    'tool_calls', [])\n                for tool_call in legacy_tool_calls:\n                    raw_id = tool_call.get('id')\n                    func = tool_call.get('function', {})\n                    if isinstance(func, dict):\n                        func_name = func.get('name')\n                        part_args = func.get('arguments', '')\n                    else:\n                        func_name = tool_call.get('name')\n                        part_args = tool_call.get('arguments', '')\n                    key = _get_fragment_key(tool_call.get('index'), raw_id)\n                    _upsert_fragment(key, raw_id, func_name, part_args)\n\n                # ----------------------------------------------------------------\n                # 3. 检测工具调用结束，更新 tool_calls_info\n                # ----------------------------------------------------------------\n                is_finish_chunk = (\n                    chunk[0].response_metadata.get(\n                        'finish_reason') == 'tool_calls'\n                    or chunk[0].chunk_position == 'last'\n                )\n\n                if is_finish_chunk:\n                    # 在 finish chunk 时，将所有未完成的 fragment 标记完成并更新 tool_calls_info\n                    maxkb_logger.debug(\n                        f\"Processing finish chunk. Tool fragments: {_tool_fragments}\")\n                    for idx, entry in _tool_fragments.items():\n                        if entry.get('completed'):\n                            maxkb_logger.debug(\n                                f\"Skipping fragment {idx}: already completed\")\n                            continue\n                        if not entry.get('id'):\n                            maxkb_logger.debug(\n                                f\"Skipping fragment {idx}: missing id. Fragment: {entry}\")\n                            continue\n                        if not entry.get('arguments'):\n                            maxkb_logger.debug(\n                                f\"Skipping fragment {idx}: missing arguments. Fragment: {entry}\")\n                            continue\n\n                        if not entry.get('completed') and entry.get('id') and entry.get('arguments'):\n                            try:\n                                parsed_args = json.loads(entry['arguments'])\n                                filtered_args = {\n                                    k: v for k, v in parsed_args.items()\n                                    if k not in tool_init_params\n                                } if tool_init_params else parsed_args\n                                normalized_id = _extract_tool_id(entry['id'])\n                                info = {\n                                    'name': entry['name'],\n                                    'input': json.dumps(filtered_args, ensure_ascii=False)\n                                }\n                                tool_calls_info[entry['id']] = info\n                                if normalized_id and normalized_id != entry['id']:\n                                    tool_calls_info[normalized_id] = info\n                                entry['completed'] = True\n                                maxkb_logger.debug(\n                                    f\"Added tool call {entry['id']} to tool_calls_info\")\n                            except (json.JSONDecodeError, ValueError) as e:\n                                # JSON parsing failed, but still add to tool_calls_info with raw arguments\n                                # to prevent \"Tool ID not found\" errors when ToolMessage arrives\n                                maxkb_logger.warning(\n                                    f\"Failed to parse tool arguments at finish for tool {entry.get('id', 'unknown')}: \"\n                                    f\"{entry['arguments']}, error: {e}. Using raw arguments.\")\n                                normalized_id = _extract_tool_id(entry['id'])\n                                info = {\n                                    'name': entry['name'],\n                                    # Use raw arguments\n                                    'input': entry['arguments']\n                                }\n                                tool_calls_info[entry['id']] = info\n                                if normalized_id and normalized_id != entry['id']:\n                                    tool_calls_info[normalized_id] = info\n                                entry['completed'] = True\n\n                # ----------------------------------------------------------------\n                # 4. 修复 tool_call_chunks 中的空 id（回填已知 id）\n                # ----------------------------------------------------------------\n                if chunk[0].tool_call_chunks:\n                    for tc_chunk in chunk[0].tool_call_chunks:\n                        key = _get_fragment_key(\n                            tc_chunk.get('index'), tc_chunk.get('id'))\n                        if key is not None:\n                            frag = _tool_fragments.get(key)\n                            if frag and frag.get('id') and not tc_chunk.get('id'):\n                                tc_chunk['id'] = frag['id']\n\n                # ----------------------------------------------------------------\n                # 5. 修复 additional_kwargs['tool_calls']（兼容旧格式）\n                #    仅在 finish chunk 时写入完整参数，避免污染中间 chunk 的\n                #    additional_kwargs（中间 chunk 会被 ainvoke 累积，如果写入\n                #    不完整 JSON 会导致下一轮 API 调用出现 arguments 非 JSON 格式错误）\n                # ----------------------------------------------------------------\n                if legacy_tool_calls and is_finish_chunk:\n                    fixed_tool_calls = []\n                    for tool_call in legacy_tool_calls:\n                        key = _get_fragment_key(\n                            tool_call.get('index'), tool_call.get('id'))\n                        frag = _tool_fragments.get(\n                            key) if key is not None else None\n                        tc = dict(tool_call)\n                        if frag and frag.get('id') and not tc.get('id'):\n                            tc['id'] = frag['id']\n                        if frag and isinstance(tc.get('function'), dict):\n                            tc['function'] = dict(tc['function'])\n                            if frag.get('completed'):\n                                tc['function']['arguments'] = frag['arguments']\n                        fixed_tool_calls.append(tc)\n                    chunk[0].additional_kwargs['tool_calls'] = fixed_tool_calls\n\n                yield chunk[0]\n\n            if mcp_output_enable and isinstance(chunk[0], ToolMessage):\n                tool_id = chunk[0].tool_call_id\n                normalized_tool_id = _extract_tool_id(tool_id)\n                tool_info = tool_calls_info.get(tool_id) or tool_calls_info.get(\n                    normalized_tool_id)\n\n                if tool_info:\n                    try:\n                        if isinstance(chunk[0].content, str):\n                            tool_result = json.loads(chunk[0].content)\n                        elif isinstance(chunk[0].content, dict):\n                            tool_result = chunk[0].content\n                        elif isinstance(chunk[0].content, list):\n                            tool_result = chunk[0].content[0] if len(\n                                chunk[0].content) > 0 else {}\n                        else:\n                            tool_result = {}\n                        text = tool_result.get('text') if 'text' in tool_result else None\n                        text_result = json.loads(text) if text else tool_result\n                        if text:\n                            tool_lib_id = text_result.pop('tool_id') if 'tool_id' in text_result else None\n                        else:\n                            tool_lib_id = tool_result.pop('tool_id') if 'tool_id' in tool_result else None\n                        if tool_lib_id:\n                            await save_tool_record(tool_lib_id, tool_info, tool_result, source_id, source_type)\n                        tool_result = json.dumps(text_result, ensure_ascii=False)\n                    except Exception as e:\n                        tool_result = chunk[0].content\n                    content = generate_tool_message_complete(\n                        tool_info.get('icon', ''),\n                        tool_info['name'],\n                        tool_info['input'],\n                        tool_result\n                    )\n                    chunk[0].content = content\n                else:\n                    maxkb_logger.warning(\n                        f\"Tool ID {tool_id} not found in tool_calls_info. \"\n                        f\"Normalized Tool ID: {normalized_tool_id}. \"\n                        f\"Available IDs: {list(tool_calls_info.keys())}. \"\n                        f\"Tool fragments at this point: {_tool_fragments}\"\n                    )\n\n                yield chunk[0]\n\n    except ExceptionGroup as eg:\n        def get_real_error(exc):\n            if isinstance(exc, ExceptionGroup):\n                return get_real_error(exc.exceptions[0])\n            return exc\n\n        real_error = get_real_error(eg)\n        error_msg = f\"{type(real_error).__name__}: {str(real_error)}\"\n        raise RuntimeError(error_msg) from None\n\n    except Exception as e:\n        error_msg = f\"{type(e).__name__}: {str(e)}\"\n        raise RuntimeError(error_msg) from None\n\n\nasync def save_tool_record(tool_id, tool_info, tool_result, source_id, source_type):\n    tool = await sync_to_async(lambda: QuerySet(Tool).filter(id=tool_id).first())()\n    tool_info['icon'] = tool.icon\n    tool_record = ToolRecord(\n        id=uuid.uuid7(),\n        workspace_id=tool.workspace_id,\n        tool_id=tool_id,\n        source_type=source_type,\n        source_id=source_id,\n        meta={'input': tool_info['input'], 'output': tool_result},\n        state=State.SUCCESS\n    )\n    await sync_to_async(tool_record.save)()\n\n\ndef mcp_response_generator(chat_model, message_list, mcp_servers, mcp_output_enable=True, tool_init_params={},\n                           source_id=None, source_type=None, chat_id=None):\n    \"\"\"使用全局事件循环，不创建新实例\"\"\"\n    result_queue = queue.Queue()\n    loop = get_global_loop()  # 使用共享循环\n    # 创建临时文件夹\n    if chat_id:\n        temp_dir = os.path.join('/tmp', chat_id[:8])\n    else:\n        temp_dir = os.path.join('/tmp', uuid.uuid7().hex[:8])\n    skills_dir = os.path.join(temp_dir, 'skills')\n    os.makedirs(skills_dir, exist_ok=True)\n\n    # print(f\"Initializing skills in temporary directory: {skills_dir}\")\n\n    async def _run():\n        try:\n            async_gen = _yield_mcp_response(chat_model, message_list, mcp_servers, mcp_output_enable, tool_init_params,\n                                            source_id, source_type, temp_dir, chat_id)\n            async for chunk in async_gen:\n                result_queue.put(('data', chunk))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            result_queue.put(('error', e))\n        finally:\n            result_queue.put(('done', None))\n\n    # 在全局循环中调度任务\n    asyncio.run_coroutine_threadsafe(_run(), loop)\n\n    while True:\n        msg_type, data = result_queue.get()\n        if msg_type == 'done':\n            # 清理临时文件夹\n            shutil.rmtree(temp_dir, ignore_errors=True)\n            break\n        if msg_type == 'error':\n            # 清理临时文件夹\n            shutil.rmtree(temp_dir, ignore_errors=True)\n            raise data\n        yield data\n\n\nasync def anext_async(agen):\n    return await agen.__anext__()\n\n\ntarget_source_node_mapping = {\n    'TOOL': {'tool-lib-node': lambda n: [n.get('properties').get('node_data').get('tool_lib_id')],\n             'ai-chat-node': lambda n: [*(n.get('properties').get('node_data').get('mcp_tool_ids') or []),\n                                        *(n.get('properties').get('node_data').get('tool_ids') or []),\n                                        *(n.get('properties').get('node_data').get('skill_tool_ids') or [])],\n             'mcp-node': lambda n: [n.get('properties').get('node_data').get('mcp_tool_id')]\n             },\n    'MODEL': {'ai-chat-node': lambda n: [n.get('properties').get('node_data').get('model_id')],\n              'question-node': lambda n: [n.get('properties').get('node_data').get('model_id')],\n              'speech-to-text-node': lambda n: [n.get('properties').get('node_data').get('stt_model_id')],\n              'text-to-speech-node': lambda n: [n.get('properties').get('node_data').get('tts_model_id')],\n              'image-to-video-node': lambda n: [n.get('properties').get('node_data').get('model_id')],\n              'image-generate-node': lambda n: [n.get('properties').get('node_data').get('model_id')],\n              'intent-node': lambda n: [n.get('properties').get('node_data').get('model_id')],\n              'image-understand-node': lambda n: [n.get('properties').get('node_data').get('model_id')],\n              'parameter-extraction-node': lambda n: [n.get('properties').get('node_data').get('model_id')],\n              'video-understand-node': lambda n: [n.get('properties').get('node_data').get('model_id')],\n              },\n    'KNOWLEDGE': {'search-knowledge-node': lambda n: n.get('properties').get('node_data').get('knowledge_id_list')},\n    'APPLICATION': {\n        'application-node': lambda n: [n.get('properties').get('node_data').get('application_id')]\n    }\n}\n\n\ndef get_node_handle_callback(source_type, source_id):\n    def node_handle_callback(node):\n        from system_manage.models.resource_mapping import ResourceMapping\n        response = []\n        for key, value in target_source_node_mapping.items():\n            if node.get('type') in value:\n                call = value.get(node.get('type'))\n                target_source_id_list = call(node)\n                for target_source_id in target_source_id_list:\n                    if target_source_id:\n                        response.append(ResourceMapping(source_type=source_type, target_type=key, source_id=source_id,\n                                                        target_id=target_source_id))\n        return response\n\n    return node_handle_callback\n\n\ndef get_workflow_resource(workflow, node_handle):\n    response = []\n    if 'nodes' in workflow:\n        for node in workflow.get('nodes'):\n            rs = node_handle(node)\n            if rs:\n                for r in rs:\n                    response.append(r)\n            if node.get('type') == 'loop-node':\n                r = get_workflow_resource(node.get('properties', {}).get(\n                    'node_data', {}).get('loop_body'), node_handle)\n                for rn in r:\n                    response.append(rn)\n        return list({(str(item.target_type) + str(item.target_id)): item for item in response}.values())\n    return []\n\n\napplication_instance_field_call_dict = {\n    'TOOL': [\n        lambda instance: instance.mcp_tool_ids or [],\n        lambda instance: instance.skill_tool_ids or [],\n        lambda instance: instance.tool_ids or []\n    ],\n    'MODEL': [\n        lambda instance: [instance.model_id] if instance.model_id else [],\n        lambda instance: [\n            instance.tts_model_id] if instance.tts_model_id else [],\n        lambda instance: [\n            instance.stt_model_id] if instance.stt_model_id else []\n    ]\n}\nknowledge_instance_field_call_dict = {\n    'MODEL': [lambda instance: [instance.embedding_model_id] if instance.embedding_model_id else []],\n}\n\n\ndef get_instance_resource(instance, source_type, source_id, instance_field_call_dict):\n    response = []\n    from system_manage.models.resource_mapping import ResourceMapping\n    for target_type, call_list in instance_field_call_dict.items():\n        target_id_list = reduce(\n            lambda x, y: [*x, *y], [call(instance) for call in call_list], [])\n        if target_id_list:\n            for target_id in target_id_list:\n                response.append(ResourceMapping(source_type=source_type, target_type=target_type, source_id=source_id,\n                                                target_id=target_id))\n    return response\n\n\ndef save_workflow_mapping(workflow, source_type, source_id, other_resource_mapping=None):\n    if not other_resource_mapping:\n        other_resource_mapping = []\n    from system_manage.models.resource_mapping import ResourceMapping\n    from django.db.models import QuerySet\n    QuerySet(ResourceMapping).filter(\n        source_type=source_type, source_id=source_id).delete()\n    resource_mapping_list = get_workflow_resource(workflow,\n                                                  get_node_handle_callback(source_type,\n                                                                           source_id))\n    resource_mapping_list += other_resource_mapping\n    if resource_mapping_list:\n        QuerySet(ResourceMapping).bulk_create(\n            {(str(item.target_type) + str(item.target_id)): item for item in resource_mapping_list}.values())\n\n\ndef get_tool_id_list(workflow):\n    _result = []\n    for node in workflow.get('nodes', []):\n        if node.get('type') == 'tool-lib-node':\n            tool_id = node.get('properties', {}).get(\n                'node_data', {}).get('tool_lib_id')\n            if tool_id:\n                _result.append(tool_id)\n        elif node.get('type') == 'loop-node':\n            r = get_tool_id_list(node.get('properties', {}).get(\n                'node_data', {}).get('loop_body', {}))\n            for item in r:\n                _result.append(item)\n        elif node.get('type') == 'ai-chat-node':\n            node_data = node.get('properties', {}).get('node_data', {})\n            mcp_tool_ids = node_data.get('mcp_tool_ids') or []\n            skill_tool_ids = node_data.get('skill_tool_ids') or []\n            tool_ids = node_data.get('tool_ids') or []\n            for _id in mcp_tool_ids + tool_ids + skill_tool_ids:\n                _result.append(_id)\n        elif node.get('type') == 'mcp-node':\n            mcp_tool_id = node.get('properties', {}).get(\n                'node_data', {}).get('mcp_tool_id')\n            if mcp_tool_id:\n                _result.append(mcp_tool_id)\n    return _result\n"
  },
  {
    "path": "apps/application/flow/workflow_manage.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： workflow_manage.py\n    @date：2024/1/9 17:40\n    @desc:\n\"\"\"\nimport concurrent\nimport json\nimport threading\nfrom concurrent.futures import ThreadPoolExecutor\nfrom functools import reduce\nfrom typing import List, Dict\n\nfrom django.db import close_old_connections, connection\nfrom django.utils import translation\nfrom django.utils.translation import get_language\nfrom langchain_core.prompts import PromptTemplate\nfrom rest_framework import status\n\nfrom application.flow import tools\nfrom application.flow.common import Workflow\nfrom application.flow.i_step_node import INode, WorkFlowPostHandler, NodeResult, FlowParamsSerializer\nfrom application.flow.step_node import get_node\nfrom common.handle.base_to_response import BaseToResponse\nfrom common.handle.impl.response.system_to_response import SystemToResponse\nfrom common.utils.logger import maxkb_logger\n\nexecutor = ThreadPoolExecutor(max_workers=200)\n\n\nclass NodeResultFuture:\n    def __init__(self, r, e, status=200):\n        self.r = r\n        self.e = e\n        self.status = status\n\n    def result(self):\n        if self.status == 200:\n            return self.r\n        else:\n            raise self.e\n\n\ndef await_result(result, timeout=1):\n    try:\n        result.result(timeout)\n        return False\n    except Exception as e:\n        return True\n\n\nclass NodeChunkManage:\n\n    def __init__(self, work_flow):\n        self.node_chunk_list = []\n        self.current_node_chunk = None\n        self.work_flow = work_flow\n\n    def add_node_chunk(self, node_chunk):\n        self.node_chunk_list.append(node_chunk)\n\n    def contains(self, node_chunk):\n        return self.node_chunk_list.__contains__(node_chunk)\n\n    def pop(self):\n        if self.current_node_chunk is None:\n            try:\n                current_node_chunk = self.node_chunk_list.pop(0)\n                self.current_node_chunk = current_node_chunk\n            except IndexError as e:\n                pass\n        if self.current_node_chunk is not None:\n            try:\n                chunk = self.current_node_chunk.chunk_list.pop(0)\n                return chunk\n            except IndexError as e:\n                if self.current_node_chunk.is_end():\n                    self.current_node_chunk = None\n                    if self.work_flow.answer_is_not_empty():\n                        chunk = self.work_flow.base_to_response.to_stream_chunk_response(\n                            self.work_flow.params['chat_id'],\n                            self.work_flow.params['chat_record_id'],\n                            '\\n\\n', False, 0, 0)\n                        self.work_flow.append_answer('\\n\\n')\n                        return chunk\n                    return self.pop()\n        return None\n\n\nclass WorkflowManage:\n    def __init__(self, flow: Workflow, params, work_flow_post_handler: WorkFlowPostHandler,\n                 base_to_response: BaseToResponse = SystemToResponse(), form_data=None, image_list=None,\n                 document_list=None,\n                 audio_list=None,\n                 video_list=None,\n                 other_list=None,\n                 start_node_id=None,\n                 start_node_data=None, chat_record=None, child_node=None, is_the_task_interrupted=lambda: False):\n        if form_data is None:\n            form_data = {}\n        if image_list is None:\n            image_list = []\n        if document_list is None:\n            document_list = []\n        if audio_list is None:\n            audio_list = []\n        if video_list is None:\n            video_list = []\n        if other_list is None:\n            other_list = []\n        self.start_node_id = start_node_id\n        self.start_node = None\n        self.form_data = form_data\n        self.image_list = image_list\n        self.video_list = video_list\n        self.document_list = document_list\n        self.audio_list = audio_list\n        self.other_list = other_list\n        self.params = params\n        self.flow = flow\n        self.context = {}\n        self.chat_context = {}\n        self.node_chunk_manage = NodeChunkManage(self)\n        self.work_flow_post_handler = work_flow_post_handler\n        self.current_node = None\n        self.current_result = None\n        self.answer = \"\"\n        self.answer_list = ['']\n        self.status = 200\n        self.base_to_response = base_to_response\n        self.chat_record = chat_record\n        self.child_node = child_node\n        self.future_list = []\n        self.lock = threading.Lock()\n        self.field_list = []\n        self.global_field_list = []\n        self.chat_field_list = []\n        self.init_fields()\n        self.is_the_task_interrupted = is_the_task_interrupted\n        if start_node_id is not None:\n            self.load_node(chat_record, start_node_id, start_node_data)\n        else:\n            self.node_context = []\n\n    def init_fields(self):\n        field_list = []\n        global_field_list = []\n        chat_field_list = []\n        for node in self.flow.nodes:\n            properties = node.properties\n            node_name = properties.get('stepName')\n            node_id = node.id\n            node_config = properties.get('config')\n            field_list.append(\n                {'label': '异常信息', 'value': 'exception_message', 'node_id': node_id, 'node_name': node_name})\n            if node_config is not None:\n                fields = node_config.get('fields')\n                if fields is not None:\n                    for field in fields:\n                        field_list.append({**field, 'node_id': node_id, 'node_name': node_name})\n                global_fields = node_config.get('globalFields')\n                if global_fields is not None:\n                    for global_field in global_fields:\n                        global_field_list.append({**global_field, 'node_id': node_id, 'node_name': node_name})\n                chat_fields = node_config.get('chatFields')\n                if chat_fields is not None:\n                    for chat_field in chat_fields:\n                        chat_field_list.append({**chat_field, 'node_id': node_id, 'node_name': node_name})\n        field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)\n        global_field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)\n        chat_field_list.sort(key=lambda f: len(f.get('node_name') + f.get('value')), reverse=True)\n        self.field_list = field_list\n        self.global_field_list = global_field_list\n        self.chat_field_list = chat_field_list\n\n    def append_answer(self, content):\n        self.answer += content\n        self.answer_list[-1] += content\n\n    def answer_is_not_empty(self):\n        return len(self.answer_list[-1]) > 0\n\n    def load_node(self, chat_record, start_node_id, start_node_data):\n        self.node_context = []\n        self.answer = chat_record.answer_text\n        self.answer_list = chat_record.answer_text_list\n        self.answer_list.append('')\n        for node_details in sorted(chat_record.details.values(), key=lambda d: d.get('index')):\n            node_id = node_details.get('node_id')\n            if node_details.get('runtime_node_id') == start_node_id:\n                def get_node_params(n):\n                    is_result = False\n                    if ['application-node', 'loop-node', 'tool-workflow-lib-node'].__contains__(n.type):\n                        is_result = True\n                    return {**n.properties.get('node_data'), 'form_data': start_node_data, 'node_data': start_node_data,\n                            'child_node': self.child_node, 'is_result': is_result}\n\n                self.start_node = self.get_node_cls_by_id(node_id, node_details.get('up_node_id_list'),\n                                                          get_node_params=get_node_params)\n                self.start_node.valid_args(\n                    {**self.start_node.node_params, 'form_data': start_node_data}, self.start_node.workflow_params)\n                if self.start_node.type == 'loop-node':\n                    loop_node_data = node_details.get('loop_node_data', {})\n                    for k, v in node_details.get('loop_context_data').items():\n                        if v is not None:\n                            self.start_node.context[k] = v\n                    self.start_node.context['loop_node_data'] = loop_node_data\n                    self.start_node.context['current_index'] = node_details.get('current_index')\n                    self.start_node.context['current_item'] = node_details.get('current_item')\n                    self.start_node.context['loop_answer_data'] = node_details.get('loop_answer_data', {})\n                if self.start_node.type == 'application-node':\n                    application_node_dict = node_details.get('application_node_dict', {})\n                    self.start_node.context['application_node_dict'] = application_node_dict\n                self.node_context.append(self.start_node)\n                continue\n\n            node_id = node_details.get('node_id')\n            node = self.get_node_cls_by_id(node_id, node_details.get('up_node_id_list'))\n            node.valid_args(node.node_params, node.workflow_params)\n            node.save_context(node_details, self)\n            node.node_chunk.end()\n            self.node_context.append(node)\n\n    def run(self):\n        close_old_connections()\n        language = get_language()\n        if self.params.get('stream'):\n            return self.run_stream(self.start_node, None, language)\n        return self.run_block(language)\n\n    def run_block(self, language='zh'):\n        \"\"\"\n        非流式响应\n        @return: 结果\n        \"\"\"\n        try:\n            self.run_chain_async(None, None, language)\n            while self.is_run():\n                pass\n            details = self.get_runtime_details()\n            message_tokens = sum([row.get('message_tokens') for row in details.values() if\n                                  'message_tokens' in row and row.get('message_tokens') is not None])\n            answer_tokens = sum([row.get('answer_tokens') for row in details.values() if\n                                 'answer_tokens' in row and row.get('answer_tokens') is not None])\n            answer_text_list = self.get_answer_text_list()\n            answer_text = '\\n\\n'.join(\n                '\\n\\n'.join([a.get('content') for a in answer]) for answer in\n                answer_text_list)\n            answer_list = reduce(lambda pre, _n: [*pre, *_n], answer_text_list, [])\n            self.work_flow_post_handler.handler(self)\n\n            res = self.base_to_response.to_block_response(self.params['chat_id'],\n                                                          self.params['chat_record_id'], answer_text, True\n                                                          , message_tokens, answer_tokens,\n                                                          _status=status.HTTP_200_OK if self.status == 200 else status.HTTP_500_INTERNAL_SERVER_ERROR,\n                                                          other_params={'answer_list': answer_list})\n        finally:\n            self._cleanup()\n        return res\n\n    def _cleanup(self):\n        \"\"\"清理所有对象引用\"\"\"\n        # 清理列表\n        self.future_list.clear()\n        self.field_list.clear()\n        self.global_field_list.clear()\n        self.chat_field_list.clear()\n        self.image_list.clear()\n        self.video_list.clear()\n        self.document_list.clear()\n        self.audio_list.clear()\n        self.other_list.clear()\n        if hasattr(self, 'node_context'):\n            self.node_context.clear()\n\n        # 清理字典\n        self.context.clear()\n        self.chat_context.clear()\n        self.form_data.clear()\n\n        # 清理对象引用\n        self.node_chunk_manage = None\n        self.work_flow_post_handler = None\n        self.flow = None\n        self.start_node = None\n        self.current_node = None\n        self.current_result = None\n        self.chat_record = None\n        self.base_to_response = None\n        self.params = None\n        self.lock = None\n\n    def run_stream(self, current_node, node_result_future, language='zh'):\n        \"\"\"\n        流式响应\n        @return:\n        \"\"\"\n        self.run_chain_async(current_node, node_result_future, language)\n        return tools.to_stream_response_simple(self.await_result())\n\n    def get_body(self):\n        return self.params\n\n    def is_run(self, timeout=0.5):\n        future_list_len = len(self.future_list)\n        try:\n            r = concurrent.futures.wait(self.future_list, timeout)\n            if len(r.not_done) > 0:\n                return True\n            else:\n                if future_list_len == len(self.future_list):\n                    return False\n                else:\n                    return True\n        except Exception as e:\n            return True\n\n    def await_result(self, is_cleanup=True):\n        try:\n            while self.is_run():\n                while True:\n                    chunk = self.node_chunk_manage.pop()\n                    if chunk is not None:\n                        yield chunk\n                    else:\n                        break\n            while True:\n                chunk = self.node_chunk_manage.pop()\n                if chunk is None:\n                    break\n                yield chunk\n        finally:\n            while self.is_run():\n                pass\n            details = self.get_runtime_details()\n            message_tokens = sum([row.get('message_tokens') for row in details.values() if\n                                  'message_tokens' in row and row.get('message_tokens') is not None])\n            answer_tokens = sum([row.get('answer_tokens') for row in details.values() if\n                                 'answer_tokens' in row and row.get('answer_tokens') is not None])\n            self.work_flow_post_handler.handler(self)\n            yield self.base_to_response.to_stream_chunk_response(self.params.get('chat_id'),\n                                                                 self.params.get('chat_record_id'),\n                                                                 '',\n                                                                 [],\n                                                                 '', True, message_tokens, answer_tokens, {})\n            if is_cleanup:\n                self._cleanup()\n\n    def run_chain_async(self, current_node, node_result_future, language='zh'):\n        future = executor.submit(self.run_chain_manage, current_node, node_result_future, language)\n        self.future_list.append(future)\n\n    def run_chain_manage(self, current_node, node_result_future, language='zh'):\n        translation.activate(language)\n        if current_node is None:\n            start_node = self.get_start_node()\n            current_node = get_node(start_node.type, self.flow.workflow_mode)(start_node, self.params, self)\n        self.node_chunk_manage.add_node_chunk(current_node.node_chunk)\n        # 添加节点\n        self.append_node(current_node)\n        result = self.run_chain(current_node, node_result_future)\n        if result is None:\n            return\n        node_list = self.get_next_node_list(current_node, result)\n        if len(node_list) == 1:\n            self.run_chain_manage(node_list[0], None, language)\n        elif len(node_list) > 1:\n            sorted_node_run_list = sorted(node_list, key=lambda n: n.node.y)\n            # 获取到可执行的子节点\n            result_list = [{'node': node, 'future': executor.submit(self.run_chain_manage, node, None, language)} for\n                           node in\n                           sorted_node_run_list]\n            for r in result_list:\n                self.future_list.append(r.get('future'))\n\n    def run_chain(self, current_node, node_result_future=None):\n        if node_result_future is None:\n            node_result_future = self.run_node_future(current_node)\n        try:\n            is_stream = self.params.get('stream', True)\n            result = self.hand_event_node_result(current_node,\n                                                 node_result_future) if is_stream else self.hand_node_result(\n                current_node, node_result_future)\n            return result\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n        return None\n\n    def hand_node_result(self, current_node, node_result_future):\n        try:\n            current_result = node_result_future.result()\n            result = current_result.write_context(current_node, self)\n            if result is not None:\n                # 阻塞获取结果\n                list(result)\n            return current_result\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            self.status = 500\n            current_node.get_write_error_context(e)\n            self.answer += str(e)\n        finally:\n            current_node.node_chunk.end()\n\n    def append_node(self, current_node):\n        for index in range(len(self.node_context)):\n            n = self.node_context[index]\n            if current_node.id == n.node.id and current_node.runtime_node_id == n.runtime_node_id:\n                self.node_context[index] = current_node\n                return\n        self.node_context.append(current_node)\n\n    def hand_event_node_result(self, current_node, node_result_future):\n        runtime_node_id = current_node.runtime_node_id\n        real_node_id = current_node.runtime_node_id\n        child_node = {}\n        view_type = current_node.view_type\n        try:\n            current_result = node_result_future.result()\n            result = current_result.write_context(current_node, self)\n            if result is not None:\n                if self.is_result(current_node, current_result):\n                    for r in result:\n                        reasoning_content = ''\n                        content = r\n                        child_node = {}\n                        node_is_end = False\n                        view_type = current_node.view_type\n                        node_type = current_node.type\n                        if isinstance(r, dict):\n                            content = r.get('content')\n                            child_node = {'runtime_node_id': r.get('runtime_node_id'),\n                                          'chat_record_id': r.get('chat_record_id')\n                                , 'child_node': r.get('child_node')}\n                            if r.__contains__('real_node_id'):\n                                real_node_id = r.get('real_node_id')\n                            if r.__contains__('node_is_end'):\n                                node_is_end = r.get('node_is_end')\n                            if r.__contains__('node_type'):\n                                node_type = r.get(\"node_type\")\n                            view_type = r.get('view_type')\n                            reasoning_content = r.get('reasoning_content')\n                        chunk = self.base_to_response.to_stream_chunk_response(self.params.get('chat_id'),\n                                                                               self.params.get('chat_record_id'),\n                                                                               current_node.id,\n                                                                               current_node.up_node_id_list,\n                                                                               content, False, 0, 0,\n                                                                               {'node_type': node_type,\n                                                                                'runtime_node_id': runtime_node_id,\n                                                                                'view_type': view_type,\n                                                                                'child_node': child_node,\n                                                                                'node_is_end': node_is_end,\n                                                                                'real_node_id': real_node_id,\n                                                                                'reasoning_content': reasoning_content,\n                                                                                'node_status': \"SUCCESS\"})\n                        current_node.node_chunk.add_chunk(chunk)\n                    chunk = (self.base_to_response\n                             .to_stream_chunk_response(self.params.get('chat_id'),\n                                                       self.params.get('chat_record_id'),\n                                                       current_node.id,\n                                                       current_node.up_node_id_list,\n                                                       '', False, 0, 0, {'node_is_end': True,\n                                                                         'runtime_node_id': runtime_node_id,\n                                                                         'node_type': current_node.type,\n                                                                         'view_type': view_type,\n                                                                         'child_node': child_node,\n                                                                         'real_node_id': real_node_id,\n                                                                         'reasoning_content': '',\n                                                                         'node_status': \"SUCCESS\"}))\n                    current_node.node_chunk.add_chunk(chunk)\n                else:\n                    list(result)\n            if current_node.status == 500:\n                enableException = current_node.node.properties.get('enableException')\n                if not enableException:\n                    return None\n                current_node.context['exception_message'] = current_node.err_message\n                current_node.context['branch_id'] = 'exception'\n                r = NodeResult({'branch_id': 'exception', 'exception': current_node.err_message}, {},\n                               _is_interrupt=lambda node, step_variable, global_variable: False)\n                r.write_context(current_node, self)\n                return r\n            if self.is_the_task_interrupted():\n                current_node.status = 201\n                return None\n            return current_result\n        except Exception as e:\n            # 添加节点\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            enableException = current_node.node.properties.get('enableException')\n            current_node.get_write_error_context(e)\n            self.status = 500\n            if self.is_the_task_interrupted():\n                current_node.status = 201\n                return None\n            if not enableException:\n                chunk = self.base_to_response.to_stream_chunk_response(self.params.get('chat_id'),\n                                                                       self.params.get('chat_id'),\n                                                                       current_node.id,\n                                                                       current_node.up_node_id_list,\n                                                                       'Exception:' + str(e), False, 0, 0,\n                                                                       {'node_is_end': True,\n                                                                        'runtime_node_id': current_node.runtime_node_id,\n                                                                        'node_type': current_node.type,\n                                                                        'view_type': current_node.view_type,\n                                                                        'child_node': {},\n                                                                        'real_node_id': real_node_id,\n                                                                        'node_status': 'ERROR'})\n                current_node.node_chunk.add_chunk(chunk)\n                return None\n            else:\n                current_node.context['exception_message'] = current_node.err_message\n                current_node.context['branch_id'] = 'exception'\n                return NodeResult({'branch_id': 'exception', 'exception': current_node.err_message}, {},\n                                  _is_interrupt=lambda node, step_variable, global_variable: False)\n        finally:\n            current_node.node_chunk.end()\n            # 归还链接到连接池\n            connection.close()\n\n    def run_node_async(self, node):\n        future = executor.submit(self.run_node, node)\n        return future\n\n    def run_node_future(self, node):\n        try:\n            node.valid_args(node.node_params, node.workflow_params)\n            result = self.run_node(node)\n            return NodeResultFuture(result, None, 200)\n        except Exception as e:\n            return NodeResultFuture(None, e, 500)\n\n    def run_node(self, node):\n        result = node.run()\n        return result\n\n    def is_result(self, current_node, current_node_result):\n        return current_node.node_params.get('is_result', not self._has_next_node(\n            current_node, current_node_result)) if current_node.node_params is not None else False\n\n    def get_chat_info(self):\n        return self.work_flow_post_handler.chat_info\n\n    def get_chunk_content(self, chunk, is_end=False):\n        return 'data: ' + json.dumps(\n            {'chat_id': self.params['chat_id'], 'id': self.params['chat_record_id'], 'operate': True,\n             'content': chunk, 'is_end': is_end}, ensure_ascii=False) + \"\\n\\n\"\n\n    def _has_next_node(self, current_node, node_result: NodeResult | None):\n        \"\"\"\n        是否有下一个可运行的节点\n        \"\"\"\n        next_edge_node_list = self.flow.get_next_edge_nodes(current_node.id) or []\n        for next_edge_node in next_edge_node_list:\n            if node_result is not None and node_result.is_assertion_result():\n                edge = next_edge_node.edge\n                if (edge.sourceNodeId == current_node.id and\n                        f\"{edge.sourceNodeId}_{node_result.node_variable.get('branch_id')}_right\" == edge.sourceAnchorId):\n                    return True\n        return len(next_edge_node_list) > 0\n\n    def has_next_node(self, node_result: NodeResult | None):\n        \"\"\"\n        是否有下一个可运行的节点\n        \"\"\"\n        return self._has_next_node(self.get_start_node() if self.current_node is None else self.current_node,\n                                   node_result)\n\n    def get_runtime_details(self, get_details=lambda n, index: n.get_details(index)):\n        details_result = {}\n        for index in range(len(self.node_context)):\n            node = self.node_context[index]\n            if self.chat_record is not None and self.chat_record.details is not None and self.start_node:\n                details = self.chat_record.details.get(node.runtime_node_id)\n                if details is not None and self.start_node.runtime_node_id != node.runtime_node_id:\n                    details_result[node.runtime_node_id] = details\n                    continue\n            details = get_details(node, index)\n            details['node_id'] = node.id\n            details['up_node_id_list'] = node.up_node_id_list\n            details['runtime_node_id'] = node.runtime_node_id\n            details_result[node.runtime_node_id] = details\n        return details_result\n\n    def get_record_answer_list(self):\n        answer_text_list = self.get_answer_text_list()\n        return reduce(lambda pre, _n: [*pre, *_n], answer_text_list, [])\n\n    def get_answer_text_list(self):\n        result = []\n        answer_list = reduce(lambda x, y: [*x, *y],\n                             [n.get_answer_list() for n in self.node_context if n.get_answer_list() is not None],\n                             [])\n        up_node = None\n        for index in range(len(answer_list)):\n            current_answer = answer_list[index]\n            if len(current_answer.content) > 0:\n                if up_node is None or current_answer.view_type == 'single_view' or (\n                        current_answer.view_type == 'many_view' and up_node.view_type == 'single_view'):\n                    result.append([current_answer])\n                else:\n                    if len(result) > 0:\n                        exec_index = len(result) - 1\n                        if isinstance(result[exec_index], list):\n                            result[exec_index].append(current_answer)\n                    else:\n                        result.insert(0, [current_answer])\n                up_node = current_answer\n        if len(result) == 0:\n            # 如果没有响应 就响应一个空数据\n            return [[]]\n        return [[item.to_dict() for item in r] for r in result]\n\n    @staticmethod\n    def dependent_node(edge, node):\n        up_node_id = edge.sourceNodeId\n        if not node.node_chunk.is_end():\n            return False\n        if node.id == up_node_id:\n            if node.context.get('branch_id', None):\n                if edge.sourceAnchorId == f\"{node.id}_{node.context.get('branch_id', None)}_right\":\n                    return True\n                else:\n                    return False\n            if node.type == 'form-node':\n                if node.context.get('form_data', None) is not None:\n                    return True\n                return False\n            return True\n\n    def dependent_node_been_executed(self, node_id):\n        \"\"\"\n        判断依赖节点是否都已执行\n        @param node_id: 需要判断的节点id\n        @return:\n        \"\"\"\n        up_edge_list = [edge for edge in self.flow.edges if edge.targetNodeId == node_id]\n        return all(\n            [any([self.dependent_node(edge, node) for node in self.node_context if node.id == edge.sourceNodeId]) for\n             edge in\n             up_edge_list])\n\n    def get_next_node_list(self, current_node, current_node_result):\n        \"\"\"\n        获取下一个可执行节点列表\n        @param current_node:         当前可执行节点\n        @param current_node_result:  当前可执行节点结果\n        @return:  可执行节点列表\n        \"\"\"\n        # 判断是否中断执行\n        if current_node_result.is_interrupt_exec(current_node):\n            return []\n        node_list = []\n        next_edge_node_list = self.flow.get_next_edge_nodes(current_node.id) or []\n        if current_node_result is not None and current_node_result.is_assertion_result():\n            for edge_node in next_edge_node_list:\n                edge = edge_node.edge\n                next_node = edge_node.node\n                if (\n                        f\"{edge.sourceNodeId}_{current_node_result.node_variable.get('branch_id')}_right\" == edge.sourceAnchorId):\n                    if next_node.properties.get('condition', \"AND\") == 'AND':\n                        if self.dependent_node_been_executed(edge.targetNodeId):\n                            up_nodes = self.flow.get_up_nodes(edge.targetNodeId)\n                            up_node_id_list = [*current_node.up_node_id_list, current_node.node.id]\n                            if up_nodes and len(up_nodes) > 1:\n                                up_nodes.sort(key=lambda node: node.id)\n                                first = up_nodes[0]\n                                up_node_id_list = [n_c for n_c in self.node_context if n_c.node.id == first.id][\n                                    0].up_node_id_list\n                                up_node_id_list = [*up_node_id_list, first.id]\n                            node_list.append(\n                                self.get_node_cls_by_id(edge.targetNodeId,\n                                                        up_node_id_list))\n                    else:\n                        node_list.append(\n                            self.get_node_cls_by_id(edge.targetNodeId,\n                                                    [*current_node.up_node_id_list, current_node.node.id]))\n        else:\n            for edge_node in next_edge_node_list:\n                edge = edge_node.edge\n                if edge.sourceNodeId + '_right' == edge.sourceAnchorId:\n                    next_node = edge_node.node\n                    if next_node.properties.get('condition', \"AND\") == 'AND':\n                        if self.dependent_node_been_executed(edge.targetNodeId):\n                            up_nodes = self.flow.get_up_nodes(edge.targetNodeId)\n                            up_node_id_list = [*current_node.up_node_id_list, current_node.node.id]\n                            if up_nodes and len(up_nodes) > 1:\n                                up_nodes.sort(key=lambda node: node.id)\n                                first = up_nodes[0]\n                                up_node_id_list = [n_c for n_c in self.node_context if n_c.node.id == first.id][\n                                    0].up_node_id_list\n                                up_node_id_list = [*up_node_id_list, first.id]\n                            node_list.append(\n                                self.get_node_cls_by_id(edge.targetNodeId,\n                                                        up_node_id_list))\n                    else:\n                        node_list.append(\n                            self.get_node_cls_by_id(edge.targetNodeId,\n                                                    [*current_node.up_node_id_list, current_node.node.id]))\n\n        return node_list\n\n    def get_reference_field(self, node_id: str, fields: List[str]):\n        \"\"\"\n        @param node_id: 节点id\n        @param fields:  字段\n        @return:\n        \"\"\"\n        if node_id == 'global':\n            return INode.get_field(self.context, fields)\n        elif node_id == 'chat':\n            return INode.get_field(self.chat_context, fields)\n        else:\n            node = self.get_node_by_id(node_id)\n            if node:\n                return node.get_reference_field(fields)\n            return None\n\n    def get_workflow_content(self):\n        context = {\n            'global': self.context,\n            'chat': self.chat_context\n        }\n\n        for node in self.node_context:\n            context[node.id] = node.context\n        return context\n\n    def reset_prompt(self, prompt: str):\n        placeholder = \"{}\"\n        for field in self.field_list:\n            globeLabel = f\"{field.get('node_name')}.{field.get('value')}\"\n            globeValue = f\"context.get('{field.get('node_id')}',{placeholder}).get('{field.get('value', '')}','')\"\n            prompt = prompt.replace(globeLabel, globeValue)\n        for field in self.global_field_list:\n            globeLabel = f\"全局变量.{field.get('value')}\"\n            globeLabelNew = f\"global.{field.get('value')}\"\n            globeValue = f\"context.get('global').get('{field.get('value', '')}','')\"\n            prompt = prompt.replace(globeLabel, globeValue).replace(globeLabelNew, globeValue)\n        for field in self.chat_field_list:\n            chatLabel = f\"chat.{field.get('value')}\"\n            chatValue = f\"context.get('chat').get('{field.get('value', '')}','')\"\n            prompt = prompt.replace(chatLabel, chatValue)\n\n        return prompt\n\n    def generate_prompt(self, prompt: str):\n        \"\"\"\n        格式化生成提示词\n        @param prompt: 提示词信息\n        @return: 格式化后的提示词\n        \"\"\"\n        context = self.get_workflow_content()\n        prompt = self.reset_prompt(prompt)\n        prompt_template = PromptTemplate.from_template(prompt, template_format='jinja2')\n        value = prompt_template.format(context=context)\n        return value\n\n    def get_start_node(self):\n        \"\"\"\n        获取启动节点\n        @return:\n        \"\"\"\n        start_node_list = [node for node in self.flow.nodes if node.type == 'start-node']\n        return start_node_list[0]\n\n    def get_base_node(self):\n        \"\"\"\n        获取基础节点\n        @return:\n        \"\"\"\n        base_node_list = [node for node in self.flow.nodes if node.type == 'base-node']\n        return base_node_list[0]\n\n    def get_node_cls_by_id(self, node_id, up_node_id_list=None,\n                           get_node_params=lambda node: node.properties.get('node_data')):\n        for node in self.flow.nodes:\n            if node.id == node_id:\n                node_instance = get_node(node.type, self.flow.workflow_mode)(node,\n                                                                             self.params, self, up_node_id_list,\n                                                                             get_node_params)\n                return node_instance\n        return None\n\n    def get_node_by_id(self, node_id):\n        for node in self.node_context:\n            if node.id == node_id:\n                return node\n        return None\n\n    def get_node_reference(self, reference_address: Dict):\n        node = self.get_node_by_id(reference_address.get('node_id'))\n        return node.context[reference_address.get('node_field')]\n\n    def get_params_serializer_class(self):\n        return FlowParamsSerializer\n\n    def get_source_type(self):\n        return \"APPLICATION\"\n\n    def get_source_id(self):\n        return self.params.get('application_id')\n"
  },
  {
    "path": "apps/application/migrations/0001_initial.py",
    "content": "# Generated by Django 5.2.4 on 2025-07-14 11:45\nfrom django.db.models import QuerySet\n\nimport application.models.application\nimport application.models.application_chat\nimport common.encoder.encoder\nimport django.contrib.postgres.fields\nimport django.db.models.deletion\nimport mptt.fields\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\n\ndef insert_default_data(apps, schema_editor):\n    # 创建一个根模块（没有父节点）\n    QuerySet(application.models.application.ApplicationFolder).create(id='default', name='根目录',\n                                                                      user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', workspace_id='default')\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('knowledge', '0001_initial'),\n        ('models_provider', '0001_initial'),\n        ('users', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Application',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('is_publish', models.BooleanField(default=False, verbose_name='是否发布')),\n                ('name', models.CharField(db_index=True, max_length=128, verbose_name='应用名称')),\n                ('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')),\n                ('prologue', models.CharField(default='', max_length=40960, verbose_name='开场白')),\n                ('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')),\n                ('knowledge_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')),\n                ('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')),\n                ('model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),\n                ('tts_model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),\n                ('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')),\n                ('icon', models.CharField(default='./favicon.ico', max_length=256, verbose_name='应用icon')),\n                ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),\n                ('type', models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型')),\n                ('problem_optimization_prompt', models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中', max_length=102400, null=True, verbose_name='问题优化提示词')),\n                ('tts_model_enable', models.BooleanField(default=False, verbose_name='语音合成模型是否启用')),\n                ('stt_model_enable', models.BooleanField(default=False, verbose_name='语音识别模型是否启用')),\n                ('tts_type', models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型')),\n                ('tts_autoplay', models.BooleanField(default=False, verbose_name='自动播放')),\n                ('stt_autosend', models.BooleanField(default=False, verbose_name='自动发送')),\n                ('clean_time', models.IntegerField(default=180, verbose_name='清理时间')),\n                ('publish_time', models.DateTimeField(blank=True, default=None, null=True, verbose_name='发布时间')),\n                ('file_upload_enable', models.BooleanField(default=False, verbose_name='文件上传是否启用')),\n                ('file_upload_setting', models.JSONField(default=dict, verbose_name='文件上传相关设置')),\n                ('model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='models_provider.model')),\n                ('stt_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stt_model_id', to='models_provider.model')),\n                ('tts_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tts_model_id', to='models_provider.model')),\n                ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),\n            ],\n            options={\n                'db_table': 'application',\n            },\n        ),\n        migrations.CreateModel(\n            name='ApplicationAccessToken',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('application', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='application.application', verbose_name='应用id')),\n                ('access_token', models.CharField(max_length=128, unique=True, verbose_name='用户公开访问 认证token')),\n                ('is_active', models.BooleanField(default=True, verbose_name='是否开启公开访问')),\n                ('access_num', models.IntegerField(default=100, verbose_name='访问次数')),\n                ('white_active', models.BooleanField(default=False, verbose_name='是否开启白名单')),\n                ('white_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='白名单列表')),\n                ('show_source', models.BooleanField(default=False, verbose_name='是否显示知识来源')),\n                ('show_exec', models.BooleanField(default=False, verbose_name='是否显示执行详情')),\n                ('authentication', models.BooleanField(default=False, verbose_name='是否需要认证')),\n                ('authentication_value', models.JSONField(default=dict, verbose_name='认证的值')),\n                ('language', models.CharField(default=None, max_length=10, null=True, verbose_name='语言')),\n            ],\n            options={\n                'db_table': 'application_access_token',\n            },\n        ),\n        migrations.CreateModel(\n            name='ApplicationApiKey',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('secret_key', models.CharField(max_length=1024, unique=True, verbose_name='秘钥')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('is_active', models.BooleanField(default=True, verbose_name='是否开启')),\n                ('allow_cross_domain', models.BooleanField(default=False, verbose_name='是否允许跨域')),\n                ('cross_domain_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, max_length=128), default=list, size=None, verbose_name='跨域列表')),\n                ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')),\n                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='用户id')),\n            ],\n            options={\n                'db_table': 'application_api_key',\n            },\n        ),\n        migrations.CreateModel(\n            name='ApplicationFolder',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('name', models.CharField(db_index=True, max_length=64, verbose_name='文件夹名称')),\n                ('desc', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('lft', models.PositiveIntegerField(editable=False)),\n                ('rght', models.PositiveIntegerField(editable=False)),\n                ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),\n                ('level', models.PositiveIntegerField(editable=False)),\n                ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='application.applicationfolder')),\n                ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),\n            ],\n            options={\n                'db_table': 'application_folder',\n            },\n        ),\n        migrations.AddField(\n            model_name='application',\n            name='folder',\n            field=models.ForeignKey(default='default', on_delete=django.db.models.deletion.DO_NOTHING, to='application.applicationfolder', verbose_name='文件夹id'),\n        ),\n        migrations.CreateModel(\n            name='ApplicationKnowledgeMapping',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('application', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='application.application')),\n                ('knowledge', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')),\n            ],\n            options={\n                'db_table': 'application_knowledge_mapping',\n            },\n        ),\n        migrations.CreateModel(\n            name='ApplicationVersion',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('name', models.CharField(default='', max_length=128, verbose_name='版本名称')),\n                ('publish_user_id', models.UUIDField(default=None, null=True, verbose_name='发布者id')),\n                ('publish_user_name', models.CharField(default='', max_length=128, verbose_name='发布者名称')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('application_name', models.CharField(max_length=128, verbose_name='应用名称')),\n                ('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')),\n                ('prologue', models.CharField(default='', max_length=40960, verbose_name='开场白')),\n                ('dialogue_number', models.IntegerField(default=0, verbose_name='会话数量')),\n                ('model_id', models.UUIDField(blank=True, null=True, verbose_name='大语言模型')),\n                ('knowledge_setting', models.JSONField(default=application.models.application.get_dataset_setting_dict, verbose_name='数据集参数设置')),\n                ('model_setting', models.JSONField(default=application.models.application.get_model_setting_dict, verbose_name='模型参数相关设置')),\n                ('model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),\n                ('tts_model_params_setting', models.JSONField(default=dict, verbose_name='模型参数相关设置')),\n                ('problem_optimization', models.BooleanField(default=False, verbose_name='问题优化')),\n                ('icon', models.CharField(default='./favicon.ico', max_length=256, verbose_name='应用icon')),\n                ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),\n                ('type', models.CharField(choices=[('SIMPLE', '简易'), ('WORK_FLOW', '工作流')], default='SIMPLE', max_length=256, verbose_name='应用类型')),\n                ('problem_optimization_prompt', models.CharField(blank=True, default='()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中', max_length=102400, null=True, verbose_name='问题优化提示词')),\n                ('tts_model_id', models.UUIDField(blank=True, null=True, verbose_name='文本转语音模型id')),\n                ('stt_model_id', models.UUIDField(blank=True, null=True, verbose_name='语音转文本模型id')),\n                ('tts_model_enable', models.BooleanField(default=False, verbose_name='语音合成模型是否启用')),\n                ('stt_model_enable', models.BooleanField(default=False, verbose_name='语音识别模型是否启用')),\n                ('tts_type', models.CharField(default='BROWSER', max_length=20, verbose_name='语音播放类型')),\n                ('tts_autoplay', models.BooleanField(default=False, verbose_name='自动播放')),\n                ('stt_autosend', models.BooleanField(default=False, verbose_name='自动发送')),\n                ('clean_time', models.IntegerField(default=180, verbose_name='清理时间')),\n                ('file_upload_enable', models.BooleanField(default=False, verbose_name='文件上传是否启用')),\n                ('file_upload_setting', models.JSONField(default=dict, verbose_name='文件上传相关设置')),\n                ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),\n                ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),\n            ],\n            options={\n                'db_table': 'application_version',\n            },\n        ),\n        migrations.CreateModel(\n            name='Chat',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('abstract', models.CharField(max_length=1024, verbose_name='摘要')),\n                ('chat_user_id', models.CharField(default=None, null=True, verbose_name='对话用户id')),\n                ('chat_user_type', models.CharField(choices=[('ANONYMOUS_USER', '匿名用户'), ('CHAT_USER', '对话用户'), ('SYSTEM_API_KEY', '系统API_KEY'), ('APPLICATION_API_KEY', '应用API_KEY'), ('PLATFORM_USER', '平台用户')], default='ANONYMOUS_USER', max_length=64, verbose_name='客户端类型')),\n                ('is_deleted', models.BooleanField(default=False, verbose_name='逻辑删除')),\n                ('asker', models.JSONField(default=application.models.application_chat.default_asker, encoder=common.encoder.encoder.SystemEncoder, verbose_name='访问者')),\n                ('meta', models.JSONField(default=dict, verbose_name='元数据')),\n                ('star_num', models.IntegerField(default=0, verbose_name='点赞数量')),\n                ('trample_num', models.IntegerField(default=0, verbose_name='点踩数量')),\n                ('chat_record_count', models.IntegerField(default=0, verbose_name='对话次数')),\n                ('mark_sum', models.IntegerField(default=0, verbose_name='标记数量')),\n                ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),\n            ],\n            options={\n                'db_table': 'application_chat',\n            },\n        ),\n        migrations.CreateModel(\n            name='ChatRecord',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('vote_status', models.CharField(choices=[('-1', '未投票'), ('0', '赞同'), ('1', '反对')], default='-1', max_length=10, verbose_name='投票')),\n                ('problem_text', models.CharField(max_length=10240, verbose_name='问题')),\n                ('answer_text', models.CharField(max_length=40960, verbose_name='答案')),\n                ('answer_text_list', django.contrib.postgres.fields.ArrayField(base_field=models.JSONField(), default=list, size=None, verbose_name='改进标注列表')),\n                ('message_tokens', models.IntegerField(default=0, verbose_name='请求token数量')),\n                ('answer_tokens', models.IntegerField(default=0, verbose_name='响应token数量')),\n                ('const', models.IntegerField(default=0, verbose_name='总费用')),\n                ('details', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='对话详情')),\n                ('improve_paragraph_id_list', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(blank=True), default=list, size=None, verbose_name='改进标注列表')),\n                ('run_time', models.FloatField(default=0, verbose_name='运行时长')),\n                ('index', models.IntegerField(verbose_name='对话下标')),\n                ('chat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.chat')),\n            ],\n            options={\n                'db_table': 'application_chat_record',\n            },\n        ),\n        migrations.CreateModel(\n            name='ApplicationChatUserStats',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('chat_user_id', models.UUIDField(default=uuid_utils.compat.uuid7, verbose_name='对话用户id')),\n                ('chat_user_type', models.CharField(choices=[('ANONYMOUS_USER', '匿名用户'), ('CHAT_USER', '对话用户'), ('SYSTEM_API_KEY', '系统API_KEY'), ('APPLICATION_API_KEY', '应用API_KEY'), ('PLATFORM_USER', '平台用户')], default='ANONYMOUS_USER', max_length=64, verbose_name='对话用户类型')),\n                ('access_num', models.IntegerField(default=0, verbose_name='访问总次数次数')),\n                ('intraday_access_num', models.IntegerField(default=0, verbose_name='当日访问次数')),\n                ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application', verbose_name='应用id')),\n            ],\n            options={\n                'db_table': 'application_chat_user_stats',\n                'indexes': [models.Index(fields=['application_id', 'chat_user_id'], name='application_applica_1652ba_idx')],\n            },\n        ),\n        migrations.RunPython(insert_default_data)\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0002_application_simple_mcp.py",
    "content": "# Generated by Django 5.2.4 on 2025-09-08 03:16\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('application', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='application',\n            name='mcp_enable',\n            field=models.BooleanField(default=False, verbose_name='MCP否启用'),\n        ),\n        migrations.AddField(\n            model_name='application',\n            name='mcp_servers',\n            field=models.JSONField(default=dict, verbose_name='MCP服务列表'),\n        ),\n        migrations.AddField(\n            model_name='application',\n            name='mcp_source',\n            field=models.CharField(default='referencing', max_length=20, verbose_name='MCP Source'),\n        ),\n        migrations.AddField(\n            model_name='application',\n            name='mcp_tool_ids',\n            field=models.JSONField(default=list, verbose_name='MCP工具ID列表'),\n        ),\n        migrations.AddField(\n            model_name='application',\n            name='tool_enable',\n            field=models.BooleanField(default=False, verbose_name='工具是否启用'),\n        ),\n        migrations.AddField(\n            model_name='application',\n            name='tool_ids',\n            field=models.JSONField(default=list, verbose_name='工具ID列表'),\n        ),\n        migrations.AddField(\n            model_name='application',\n            name='mcp_output_enable',\n            field=models.BooleanField(default=True, verbose_name='MCP输出是否启用'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='mcp_enable',\n            field=models.BooleanField(default=False, verbose_name='MCP否启用'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='mcp_servers',\n            field=models.JSONField(default=dict, verbose_name='MCP服务列表'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='mcp_source',\n            field=models.CharField(default='referencing', max_length=20, verbose_name='MCP Source'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='mcp_tool_ids',\n            field=models.JSONField(default=list, verbose_name='MCP工具ID列表'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='tool_enable',\n            field=models.BooleanField(default=False, verbose_name='工具是否启用'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='tool_ids',\n            field=models.JSONField(default=list, verbose_name='工具ID列表'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='mcp_output_enable',\n            field=models.BooleanField(default=True, verbose_name='MCP输出是否启用'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0003_application_stt_model_params_setting_and_more.py",
    "content": "# Generated by Django 5.2.4 on 2025-09-16 08:10\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('application', '0002_application_simple_mcp'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='application',\n            name='stt_model_params_setting',\n            field=models.JSONField(default=dict, verbose_name='STT模型参数相关设置'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='stt_model_params_setting',\n            field=models.JSONField(default=dict, verbose_name='STT模型参数相关设置'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0004_application_application_enable_and_more.py",
    "content": "# Generated by Django 5.2.9 on 2025-12-16 08:02\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('application', '0003_application_stt_model_params_setting_and_more'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='application',\n            name='application_enable',\n            field=models.BooleanField(default=False, verbose_name='应用是否启用'),\n        ),\n        migrations.AddField(\n            model_name='application',\n            name='application_ids',\n            field=models.JSONField(default=list, verbose_name='应用ID列表'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='application_enable',\n            field=models.BooleanField(default=False, verbose_name='应用是否启用'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='application_ids',\n            field=models.JSONField(default=list, verbose_name='应用ID列表'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0005_chatrecord_vote_other_content_chatrecord_vote_reason.py",
    "content": "# Generated by Django 5.2.8 on 2025-12-23 10:28\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('application', '0004_application_application_enable_and_more'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='chatrecord',\n            name='vote_other_content',\n            field=models.CharField(default='', max_length=1024, verbose_name='其他原因'),\n        ),\n        migrations.AddField(\n            model_name='chatrecord',\n            name='vote_reason',\n            field=models.CharField(blank=True, choices=[('accurate', '内容准确'), ('complete', '内容完善'), ('inaccurate', '内容不准确'), ('incomplete', '内容不完善'), ('other', '其他')], max_length=50, null=True, verbose_name='投票原因'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0006_application_file_clean_time.py",
    "content": "# Generated by Django 5.2.9 on 2026-01-14 06:51\n\nfrom django.db import migrations, models\n\n\ndef insert_default_data(apps, schema_editor):\n    Application = apps.get_model('application', 'Application')\n    # 批量修改 把所有的file_clean_time的值设置成clean_time的值\n    Application.objects.all().update(file_clean_time=models.F('clean_time'))\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        ('application', '0005_chatrecord_vote_other_content_chatrecord_vote_reason'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='application',\n            name='file_clean_time',\n            field=models.IntegerField(default=180, verbose_name='文件清理时间'),\n        ),\n        migrations.RunPython(insert_default_data)\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0007_applicationapikey_expire_time_and_more.py",
    "content": "# Generated by Django 5.2.8 on 2026-01-16 06:10\n\nimport django.utils.timezone\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('application', '0006_application_file_clean_time'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='applicationapikey',\n            name='expire_time',\n            field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='过期时间'),\n        ),\n        migrations.AddField(\n            model_name='applicationapikey',\n            name='is_permanent',\n            field=models.BooleanField(default=True, verbose_name='是否永久'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0008_chat_ip_address_chat_source_chatrecord_ip_address_and_more.py",
    "content": "# Generated by Django 5.2.8 on 2026-01-19 07:30\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('application', '0007_applicationapikey_expire_time_and_more'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='chat',\n            name='ip_address',\n            field=models.CharField(default='', max_length=128, verbose_name='ip地址'),\n        ),\n        migrations.AddField(\n            model_name='chat',\n            name='source',\n            field=models.JSONField(default=dict, verbose_name='来源'),\n        ),\n        migrations.AddField(\n            model_name='chatrecord',\n            name='ip_address',\n            field=models.CharField(default='', max_length=128, verbose_name='ip地址'),\n        ),\n        migrations.AddField(\n            model_name='chatrecord',\n            name='source',\n            field=models.JSONField(default=dict, verbose_name='来源'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0009_clean_application_knowledge_mapping.py",
    "content": "from django.db import migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('application', '0008_chat_ip_address_chat_source_chatrecord_ip_address_and_more'),\n        ('system_manage', '0005_resourcemapping'),\n    ]\n\n    operations = [\n        migrations.RunSQL(\n            \"DELETE FROM application_knowledge_mapping;\",\n            reverse_sql=migrations.RunSQL.noop\n        ),\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0010_chatsharelink.py",
    "content": "# Generated by Django 5.2.9 on 2026-02-09 02:39\n\nimport django.contrib.postgres.fields\nimport django.db.models.deletion\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('application', '0009_clean_application_knowledge_mapping'),\n        ('users', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ChatShareLink',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('share_type', models.CharField(choices=[('PUBLIC', 'public'), ('PRIVATE', 'private')], default='PUBLIC', max_length=20)),\n                ('chat_record_ids', django.contrib.postgres.fields.ArrayField(base_field=models.UUIDField(), size=None)),\n                ('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.application')),\n                ('chat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='application.chat')),\n                ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),\n            ],\n            options={\n                'db_table': 'application_chat_share_link',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0011_application_skill_tool_ids.py",
    "content": "# Generated by Django 5.2.11 on 2026-02-27 07:03\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('application', '0010_chatsharelink'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='application',\n            name='skill_tool_ids',\n            field=models.JSONField(default=list, verbose_name='技能ID列表'),\n        ),\n        migrations.AddField(\n            model_name='applicationversion',\n            name='skill_tool_ids',\n            field=models.JSONField(default=list, verbose_name='技能ID列表'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/application/migrations/0012_remove_applicationapikey_user.py",
    "content": "# Generated by Django 5.2.11 on 2026-03-18 09:18\n\nfrom django.db import migrations\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('application', '0011_application_skill_tool_ids'),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name='applicationapikey',\n            name='user',\n        ),\n    ]\n"
  },
  {
    "path": "apps/application/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "apps/application/models/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py\n    @date：2025/5/7 15:14\n    @desc:\n\"\"\"\nfrom .application import *\nfrom .application_access_token import *\nfrom .application_chat import *\nfrom .application_api_key import *\n"
  },
  {
    "path": "apps/application/models/application.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application.py\n    @date：2025/5/7 15:29\n    @desc:\n\"\"\"\nimport uuid_utils.compat as uuid\nfrom django.db import models\nfrom mptt.fields import TreeForeignKey\nfrom mptt.models import MPTTModel\n\nfrom common.mixins.app_model_mixin import AppModelMixin\nfrom knowledge.models import Knowledge\nfrom models_provider.models import Model\nfrom users.models import User\n\n\nclass ApplicationFolder(MPTTModel, AppModelMixin):\n    id = models.CharField(primary_key=True, max_length=64, editable=False, verbose_name=\"主键id\")\n    name = models.CharField(max_length=64, verbose_name=\"文件夹名称\", db_index=True)\n    desc = models.CharField(max_length=200, null=True, blank=True, verbose_name=\"描述\")\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    parent = TreeForeignKey('self', on_delete=models.DO_NOTHING, null=True, blank=True, related_name='children')\n\n    class Meta:\n        db_table = \"application_folder\"\n\n    class MPTTMeta:\n        order_insertion_by = ['name']\n\n\nclass ApplicationTypeChoices(models.TextChoices):\n    \"\"\"订单类型\"\"\"\n    SIMPLE = 'SIMPLE', '简易'\n    WORK_FLOW = 'WORK_FLOW', '工作流'\n\n\ndef get_dataset_setting_dict():\n    return {'top_n': 3, 'similarity': 0.6, 'max_paragraph_char_number': 5000, 'search_mode': 'embedding',\n            'no_references_setting': {\n                'status': 'ai_questioning',\n                'value': '{question}'\n            }}\n\n\ndef get_model_setting_dict():\n    return {\n        'prompt': Application.get_default_model_prompt(),\n        'no_references_prompt': '{question}',\n        'reasoning_content_start': '<think>',\n        'reasoning_content_end': '</think>',\n        'reasoning_content_enable': False,\n    }\n\n\nclass Application(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    folder = models.ForeignKey(ApplicationFolder, on_delete=models.DO_NOTHING, verbose_name=\"文件夹id\",\n                               default='default')\n    is_publish = models.BooleanField(verbose_name=\"是否发布\", default=False)\n    name = models.CharField(max_length=128, verbose_name=\"应用名称\", db_index=True)\n    desc = models.CharField(max_length=512, verbose_name=\"引用描述\", default=\"\")\n    prologue = models.CharField(max_length=40960, verbose_name=\"开场白\", default=\"\")\n    dialogue_number = models.IntegerField(default=0, verbose_name=\"会话数量\")\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n    model = models.ForeignKey(Model, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n    knowledge_setting = models.JSONField(verbose_name=\"数据集参数设置\", default=get_dataset_setting_dict)\n    model_setting = models.JSONField(verbose_name=\"模型参数相关设置\", default=get_model_setting_dict)\n    model_params_setting = models.JSONField(verbose_name=\"模型参数相关设置\", default=dict)\n    tts_model_params_setting = models.JSONField(verbose_name=\"模型参数相关设置\", default=dict)\n    stt_model_params_setting = models.JSONField(verbose_name=\"STT模型参数相关设置\", default=dict)\n    problem_optimization = models.BooleanField(verbose_name=\"问题优化\", default=False)\n    icon = models.CharField(max_length=256, verbose_name=\"应用icon\", default=\"./favicon.ico\")\n    work_flow = models.JSONField(verbose_name=\"工作流数据\", default=dict)\n    type = models.CharField(verbose_name=\"应用类型\", choices=ApplicationTypeChoices.choices,\n                            default=ApplicationTypeChoices.SIMPLE, max_length=256)\n    problem_optimization_prompt = models.CharField(verbose_name=\"问题优化提示词\", max_length=102400, blank=True,\n                                                   null=True,\n                                                   default=\"()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中\")\n    tts_model = models.ForeignKey(Model, related_name='tts_model_id', on_delete=models.SET_NULL, db_constraint=False,\n                                  blank=True, null=True)\n    stt_model = models.ForeignKey(Model, related_name='stt_model_id', on_delete=models.SET_NULL, db_constraint=False,\n                                  blank=True, null=True)\n    tts_model_enable = models.BooleanField(verbose_name=\"语音合成模型是否启用\", default=False)\n    stt_model_enable = models.BooleanField(verbose_name=\"语音识别模型是否启用\", default=False)\n    tts_type = models.CharField(verbose_name=\"语音播放类型\", max_length=20, default=\"BROWSER\")\n    tts_autoplay = models.BooleanField(verbose_name=\"自动播放\", default=False)\n    stt_autosend = models.BooleanField(verbose_name=\"自动发送\", default=False)\n    clean_time = models.IntegerField(verbose_name=\"清理时间\", default=180)\n    publish_time = models.DateTimeField(verbose_name=\"发布时间\", default=None, null=True, blank=True)\n    file_upload_enable = models.BooleanField(verbose_name=\"文件上传是否启用\", default=False)\n    file_upload_setting = models.JSONField(verbose_name=\"文件上传相关设置\", default=dict)\n    mcp_enable = models.BooleanField(verbose_name=\"MCP否启用\", default=False)\n    mcp_tool_ids = models.JSONField(verbose_name=\"MCP工具ID列表\", default=list)\n    mcp_servers = models.JSONField(verbose_name=\"MCP服务列表\", default=dict)\n    mcp_source = models.CharField(verbose_name=\"MCP Source\", max_length=20, default=\"referencing\")\n    tool_enable = models.BooleanField(verbose_name=\"工具是否启用\", default=False)\n    tool_ids = models.JSONField(verbose_name=\"工具ID列表\", default=list)\n    application_enable = models.BooleanField(verbose_name=\"应用是否启用\", default=False)\n    application_ids = models.JSONField(verbose_name=\"应用ID列表\", default=list)\n    skill_tool_ids = models.JSONField(verbose_name=\"技能ID列表\", default=list)\n    mcp_output_enable = models.BooleanField(verbose_name=\"MCP输出是否启用\", default=True)\n    file_clean_time = models.IntegerField(verbose_name=\"文件清理时间\", default=180)\n\n    @staticmethod\n    def get_default_model_prompt():\n        return ('已知信息：'\n                '\\n{data}'\n                '\\n回答要求：'\n                '\\n- 如果你不知道答案或者没有从获取答案，请回答“没有在知识库中查找到相关信息，建议咨询相关技术支持或参考官方文档进行操作”。'\n                '\\n- 避免提及你是从<data></data>中获得的知识。'\n                '\\n- 请保持答案与<data></data>中描述的一致。'\n                '\\n- 请使用markdown 语法优化答案的格式。'\n                '\\n- <data></data>中的图片链接、链接地址和脚本语言请完整返回。'\n                '\\n- 请使用与问题相同的语言来回答。'\n                '\\n问题：'\n                '\\n{question}')\n\n    class Meta:\n        db_table = \"application\"\n\n\nclass ApplicationKnowledgeMapping(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    application = models.ForeignKey(Application, on_delete=models.DO_NOTHING)\n    knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING)\n\n    class Meta:\n        db_table = \"application_knowledge_mapping\"\n\n\nclass ApplicationVersion(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    application = models.ForeignKey(Application, on_delete=models.CASCADE)\n    name = models.CharField(verbose_name=\"版本名称\", max_length=128, default=\"\")\n    publish_user_id = models.UUIDField(verbose_name=\"发布者id\", max_length=128, default=None, null=True)\n    publish_user_name = models.CharField(verbose_name=\"发布者名称\", max_length=128, default=\"\")\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    application_name = models.CharField(max_length=128, verbose_name=\"应用名称\")\n    desc = models.CharField(max_length=512, verbose_name=\"引用描述\", default=\"\")\n    prologue = models.CharField(max_length=40960, verbose_name=\"开场白\", default=\"\")\n    dialogue_number = models.IntegerField(default=0, verbose_name=\"会话数量\")\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n    model_id = models.UUIDField(verbose_name=\"大语言模型\", blank=True, null=True)\n    knowledge_setting = models.JSONField(verbose_name=\"数据集参数设置\", default=get_dataset_setting_dict)\n    model_setting = models.JSONField(verbose_name=\"模型参数相关设置\", default=get_model_setting_dict)\n    model_params_setting = models.JSONField(verbose_name=\"模型参数相关设置\", default=dict)\n    tts_model_params_setting = models.JSONField(verbose_name=\"模型参数相关设置\", default=dict)\n    stt_model_params_setting = models.JSONField(verbose_name=\"STT模型参数相关设置\", default=dict)\n    problem_optimization = models.BooleanField(verbose_name=\"问题优化\", default=False)\n    icon = models.CharField(max_length=256, verbose_name=\"应用icon\", default=\"./favicon.ico\")\n    work_flow = models.JSONField(verbose_name=\"工作流数据\", default=dict)\n    type = models.CharField(verbose_name=\"应用类型\", choices=ApplicationTypeChoices.choices,\n                            default=ApplicationTypeChoices.SIMPLE, max_length=256)\n    problem_optimization_prompt = models.CharField(verbose_name=\"问题优化提示词\", max_length=102400, blank=True,\n                                                   null=True,\n                                                   default=\"()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在<data></data>标签中\")\n    tts_model_id = models.UUIDField(verbose_name=\"文本转语音模型id\",\n                                    blank=True, null=True)\n    stt_model_id = models.UUIDField(verbose_name=\"语音转文本模型id\",\n                                    blank=True, null=True)\n    tts_model_enable = models.BooleanField(verbose_name=\"语音合成模型是否启用\", default=False)\n    stt_model_enable = models.BooleanField(verbose_name=\"语音识别模型是否启用\", default=False)\n    tts_type = models.CharField(verbose_name=\"语音播放类型\", max_length=20, default=\"BROWSER\")\n    tts_autoplay = models.BooleanField(verbose_name=\"自动播放\", default=False)\n    stt_autosend = models.BooleanField(verbose_name=\"自动发送\", default=False)\n    clean_time = models.IntegerField(verbose_name=\"清理时间\", default=180)\n    file_upload_enable = models.BooleanField(verbose_name=\"文件上传是否启用\", default=False)\n    file_upload_setting = models.JSONField(verbose_name=\"文件上传相关设置\", default=dict)\n    mcp_enable = models.BooleanField(verbose_name=\"MCP否启用\", default=False)\n    mcp_tool_ids = models.JSONField(verbose_name=\"MCP工具ID列表\", default=list)\n    mcp_servers = models.JSONField(verbose_name=\"MCP服务列表\", default=dict)\n    mcp_source = models.CharField(verbose_name=\"MCP Source\", max_length=20, default=\"referencing\")\n    tool_enable = models.BooleanField(verbose_name=\"工具是否启用\", default=False)\n    tool_ids = models.JSONField(verbose_name=\"工具ID列表\", default=list)\n    application_enable = models.BooleanField(verbose_name=\"应用是否启用\", default=False)\n    application_ids = models.JSONField(verbose_name=\"应用ID列表\", default=list)\n    skill_tool_ids = models.JSONField(verbose_name=\"技能ID列表\", default=list)\n    mcp_output_enable = models.BooleanField(verbose_name=\"MCP输出是否启用\", default=True)\n\n    class Meta:\n        db_table = \"application_version\"\n"
  },
  {
    "path": "apps/application/models/application_access_token.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_access_token.py\n    @date：2025/5/27 9:55\n    @desc:\n\"\"\"\nfrom django.contrib.postgres.fields import ArrayField\nfrom django.db import models\n\nfrom application.models.application import Application\nfrom common.mixins.app_model_mixin import AppModelMixin\n\n\nclass ApplicationAccessToken(AppModelMixin):\n    \"\"\"\n    应用认证token\n    \"\"\"\n    application = models.OneToOneField(Application, primary_key=True, on_delete=models.CASCADE, verbose_name=\"应用id\")\n    access_token = models.CharField(max_length=128, verbose_name=\"用户公开访问 认证token\", unique=True)\n    is_active = models.BooleanField(default=True, verbose_name=\"是否开启公开访问\")\n    access_num = models.IntegerField(default=100, verbose_name=\"访问次数\")\n    white_active = models.BooleanField(default=False, verbose_name=\"是否开启白名单\")\n    white_list = ArrayField(verbose_name=\"白名单列表\",\n                            base_field=models.CharField(max_length=128, blank=True)\n                            , default=list)\n    show_source = models.BooleanField(default=False, verbose_name=\"是否显示知识来源\")\n    show_exec = models.BooleanField(default=False, verbose_name=\"是否显示执行详情\")\n    authentication = models.BooleanField(default=False, verbose_name=\"是否需要认证\")\n    authentication_value = models.JSONField(verbose_name=\"认证的值\", default=dict)\n\n    language = models.CharField(max_length=10, verbose_name=\"语言\", default=None, null=True)\n\n    class Meta:\n        db_table = \"application_access_token\"\n"
  },
  {
    "path": "apps/application/models/application_api_key.py",
    "content": "import uuid_utils.compat as uuid\n\nfrom django.contrib.postgres.fields import ArrayField\nfrom django.db import models\nfrom django.utils import timezone\n\nfrom application.models import Application\nfrom common.mixins.app_model_mixin import AppModelMixin\n\n\nclass ApplicationApiKey(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    secret_key = models.CharField(max_length=1024, verbose_name=\"秘钥\", unique=True)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    application = models.ForeignKey(Application, on_delete=models.CASCADE, verbose_name=\"应用id\")\n    is_active = models.BooleanField(default=True, verbose_name=\"是否开启\")\n    allow_cross_domain = models.BooleanField(default=False, verbose_name=\"是否允许跨域\")\n    cross_domain_list = ArrayField(verbose_name=\"跨域列表\",\n                                   base_field=models.CharField(max_length=128, blank=True)\n                                   , default=list)\n    expire_time = models.DateTimeField(verbose_name=\"过期时间\", default=timezone.now)\n    is_permanent = models.BooleanField(default=True, verbose_name=\"是否永久\")\n\n    class Meta:\n        db_table = \"application_api_key\"\n"
  },
  {
    "path": "apps/application/models/application_chat.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_chat_log.py\n    @date：2025/5/29 17:12\n    @desc:\n\"\"\"\nimport uuid_utils.compat as uuid\nfrom django.contrib.postgres.fields import ArrayField\nfrom django.db import models\nfrom django.utils.translation import gettext as _\nfrom langchain_core.messages import HumanMessage, AIMessage\n\nfrom application.models import Application\nfrom common.encoder.encoder import SystemEncoder\nfrom common.mixins.app_model_mixin import AppModelMixin\nfrom users.models import User\n\n\nclass ChatUserType(models.TextChoices):\n    ANONYMOUS_USER = \"ANONYMOUS_USER\", '匿名用户'\n    CHAT_USER = \"CHAT_USER\", \"对话用户\"\n    SYSTEM_API_KEY = \"SYSTEM_API_KEY\", \"系统API_KEY\"\n    APPLICATION_API_KEY = \"APPLICATION_API_KEY\", \"应用API_KEY\"\n    PLATFORM_USER = \"PLATFORM_USER\", \"平台用户\"\n\n\ndef default_asker():\n    return {'username': '游客'}\n\n\nclass Chat(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    application = models.ForeignKey(Application, on_delete=models.CASCADE)\n    abstract = models.CharField(max_length=1024, verbose_name=\"摘要\")\n    chat_user_id = models.CharField(verbose_name=\"对话用户id\", default=None, null=True)\n    chat_user_type = models.CharField(max_length=64, verbose_name=\"客户端类型\", choices=ChatUserType.choices,\n                                      default=ChatUserType.ANONYMOUS_USER)\n    is_deleted = models.BooleanField(verbose_name=\"逻辑删除\", default=False)\n    asker = models.JSONField(verbose_name=\"访问者\", default=default_asker, encoder=SystemEncoder)\n    meta = models.JSONField(verbose_name=\"元数据\", default=dict)\n    star_num = models.IntegerField(verbose_name=\"点赞数量\", default=0)\n    trample_num = models.IntegerField(verbose_name=\"点踩数量\", default=0)\n    chat_record_count = models.IntegerField(verbose_name=\"对话次数\", default=0)\n    mark_sum = models.IntegerField(verbose_name=\"标记数量\", default=0)\n    source = models.JSONField(verbose_name=\"来源\", default=dict)\n    ip_address = models.CharField(max_length=128, verbose_name=\"ip地址\", default='')\n\n    class Meta:\n        db_table = \"application_chat\"\n\n\nclass VoteChoices(models.TextChoices):\n    \"\"\"订单类型\"\"\"\n    UN_VOTE = \"-1\", '未投票'\n    STAR = \"0\", '赞同'\n    TRAMPLE = \"1\", '反对'\n\n\nclass VoteReasonChoices(models.TextChoices):\n    ACCURATE = 'accurate', '内容准确'\n    COMPLETE = 'complete', '内容完善'\n    INACCURATE = 'inaccurate', '内容不准确'\n    INCOMPLETE = 'incomplete', '内容不完善'\n    OTHER = 'other', '其他'\n\nclass ShareLinkType(models.TextChoices):\n    PUBLIC = \"PUBLIC\", 'public'\n    PRIVATE = \"PRIVATE\", 'private'\n\nclass ChatSourceChoices(models.TextChoices):\n    ONLINE = \"ONLINE\", \"线上使用\"\n    API_CALL = \"API_CALL\", \"API调用\"\n    ENTERPRISE_WECHAT = \"ENTERPRISE_WECHAT\", \"企业微信\"\n    WECHAT_PUBLIC_ACCOUNT = \"WECHAT_PUBLIC_ACCOUNT\", \"微信公众号\"\n    LARK = \"LARK\", \"飞书\"\n    DINGTALK = \"DINGTALK\", \"钉钉\"\n    ENTERPRISE_WECHAT_ROBOT = \"ENTERPRISE_WECHAT_ROBOT\", \"企业微信机器人\"\n    TRIGGER = \"TRIGGER\", \"触发器\"\n    SLACK = \"SLACK\", \"Slack\"\n\n\nclass ChatRecord(AppModelMixin):\n    \"\"\"\n    对话日志 详情\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    chat = models.ForeignKey(Chat, on_delete=models.CASCADE)\n    vote_status = models.CharField(verbose_name='投票', max_length=10, choices=VoteChoices.choices,\n                                   default=VoteChoices.UN_VOTE)\n    vote_reason = models.CharField(verbose_name='投票原因', max_length=50, choices=VoteReasonChoices.choices, null=True,\n                                   blank=True)\n    vote_other_content = models.CharField(verbose_name='其他原因', max_length=1024, default='')\n    problem_text = models.CharField(max_length=10240, verbose_name=\"问题\")\n    answer_text = models.CharField(max_length=40960, verbose_name=\"答案\")\n    answer_text_list = ArrayField(verbose_name=\"改进标注列表\",\n                                  base_field=models.JSONField()\n                                  , default=list)\n    message_tokens = models.IntegerField(verbose_name=\"请求token数量\", default=0)\n    answer_tokens = models.IntegerField(verbose_name=\"响应token数量\", default=0)\n    const = models.IntegerField(verbose_name=\"总费用\", default=0)\n    details = models.JSONField(verbose_name=\"对话详情\", default=dict, encoder=SystemEncoder)\n    improve_paragraph_id_list = ArrayField(verbose_name=\"改进标注列表\",\n                                           base_field=models.UUIDField(max_length=128, blank=True)\n                                           , default=list)\n    run_time = models.FloatField(verbose_name=\"运行时长\", default=0)\n    index = models.IntegerField(verbose_name=\"对话下标\")\n    source = models.JSONField(verbose_name=\"来源\", default=dict)\n    ip_address = models.CharField(max_length=128, verbose_name=\"ip地址\", default='')\n\n    def get_human_message(self):\n        if 'problem_padding' in self.details:\n            return HumanMessage(content=self.details.get('problem_padding').get('padding_problem_text'))\n        return HumanMessage(content=self.problem_text)\n\n    def get_ai_message(self):\n        answer_text = self.answer_text\n        if answer_text is None or len(str(answer_text).strip()) == 0:\n            answer_text = _(\n                'Sorry, no relevant content was found. Please re-describe your problem or provide more information. ')\n        return AIMessage(content=answer_text)\n\n    def get_node_details_runtime_node_id(self, runtime_node_id):\n        return self.details.get(runtime_node_id, None)\n\n    class Meta:\n        db_table = \"application_chat_record\"\n\n\nclass ApplicationChatUserStats(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    chat_user_id = models.UUIDField(max_length=128, default=uuid.uuid7, verbose_name=\"对话用户id\")\n    chat_user_type = models.CharField(max_length=64, verbose_name=\"对话用户类型\", choices=ChatUserType.choices,\n                                      default=ChatUserType.ANONYMOUS_USER)\n    application = models.ForeignKey(Application, on_delete=models.CASCADE, verbose_name=\"应用id\")\n    access_num = models.IntegerField(default=0, verbose_name=\"访问总次数次数\")\n    intraday_access_num = models.IntegerField(default=0, verbose_name=\"当日访问次数\")\n\n    class Meta:\n        db_table = \"application_chat_user_stats\"\n        indexes = [\n            models.Index(fields=['application_id', 'chat_user_id']),\n        ]\n\nclass ChatShareLink(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    chat = models.ForeignKey(Chat, on_delete=models.CASCADE)\n    application = models.ForeignKey(Application,on_delete=models.CASCADE)\n    share_type = models.CharField(max_length=20, choices=ShareLinkType.choices, default=ShareLinkType.PUBLIC)\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n    chat_record_ids = ArrayField(base_field=models.UUIDField(max_length=128))\n\n    class Meta:\n        db_table = \"application_chat_share_link\""
  },
  {
    "path": "apps/application/serializers/__init__.py",
    "content": ""
  },
  {
    "path": "apps/application/serializers/application.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application.py\n    @date：2025/5/26 17:03\n    @desc:\n\"\"\"\nimport asyncio\nimport base64\nimport hashlib\nimport json\nimport os\nimport pickle\nimport re\nimport tempfile\nimport zipfile\nfrom functools import reduce\nfrom typing import Dict, List\n\nimport requests\nimport uuid_utils.compat as uuid\nfrom django.core import validators\nfrom django.db import models, transaction\nfrom django.db.models import QuerySet, Q\nfrom django.http import HttpResponse\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom rest_framework import serializers, status\nfrom rest_framework.utils.formatting import lazy_format\n\nfrom application.flow.common import Workflow\nfrom application.models.application import Application, ApplicationTypeChoices, \\\n    ApplicationFolder, ApplicationVersion\nfrom application.models.application_access_token import ApplicationAccessToken\nfrom application.serializers.common import update_resource_mapping_by_application\nfrom common import result\nfrom common.cache_data.application_access_token_cache import del_application_access_token\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.db.search import native_search, native_page_search\nfrom common.exception.app_exception import AppApiException\nfrom common.field.common import UploadedFileField\nfrom common.utils.common import get_file_content, restricted_loads, generate_uuid, _remove_empty_lines, \\\n    bytes_to_uploaded_file\nfrom common.utils.logger import maxkb_logger\nfrom knowledge.models import Knowledge, KnowledgeScope, File, FileSourceType\nfrom knowledge.serializers.knowledge import KnowledgeSerializer, KnowledgeModelSerializer\nfrom maxkb.conf import PROJECT_DIR\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\nfrom system_manage.models import WorkspaceUserResourcePermission, AuthTargetType\nfrom system_manage.models.resource_mapping import ResourceMapping\nfrom system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer\nfrom system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer\nfrom tools.models import Tool, ToolScope, ToolType\nfrom tools.serializers.tool import ToolExportModelSerializer\nfrom trigger.models import TriggerTask, Trigger\nfrom users.models import User\nfrom users.serializers.user import is_workspace_manage\n\n\ndef get_base_node_work_flow(work_flow):\n    node_list = work_flow.get('nodes')\n    base_node_list = [node for node in node_list if node.get('id') == 'base-node']\n    if len(base_node_list) > 0:\n        return base_node_list[-1]\n    return None\n\n\ndef hand_node(node, update_tool_map):\n    if node.get('type') == 'tool-lib-node':\n        tool_lib_id = (node.get('properties', {}).get('node_data', {}).get('tool_lib_id') or '')\n        node.get('properties', {}).get('node_data', {})['tool_lib_id'] = update_tool_map.get(tool_lib_id,\n                                                                                             tool_lib_id)\n    if node.get('type') == 'search-knowledge-node':\n        node.get('properties', {}).get('node_data', {})['knowledge_id_list'] = []\n    if node.get('type') == 'ai-chat-node':\n        node_data = node.get('properties', {}).get('node_data', {})\n\n        mcp_tool_ids = node_data.get('mcp_tool_ids') or []\n        node_data['mcp_tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in mcp_tool_ids]\n\n        skill_tool_ids = node_data.get('skill_tool_ids') or []\n        node_data['skill_tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in skill_tool_ids]\n\n        tool_ids = node_data.get('tool_ids') or []\n        node_data['tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in tool_ids]\n\n    if node.get('type') == 'mcp-node':\n        mcp_tool_id = (node.get('properties', {}).get('node_data', {}).get('mcp_tool_id') or '')\n        node.get('properties', {}).get('node_data', {})['mcp_tool_id'] = update_tool_map.get(mcp_tool_id,\n                                                                                             mcp_tool_id)\n\n\nclass MKInstance:\n\n    def __init__(self, application: dict, function_lib_list: List[dict], version: str, tool_list: List[dict]):\n        self.application = application\n        self.function_lib_list = function_lib_list\n        self.version = version\n        self.tool_list = tool_list\n\n    def get_tool_list(self):\n        return [*(self.tool_list or []), *(self.function_lib_list or [])]\n\n\nclass ApplicationSerializerModel(serializers.ModelSerializer):\n    class Meta:\n        model = Application\n        fields = \"__all__\"\n\n\nclass NoReferencesChoices(models.TextChoices):\n    \"\"\"订单类型\"\"\"\n    ai_questioning = 'ai_questioning', 'ai回答'\n    designated_answer = 'designated_answer', '指定回答'\n\n\nclass NoReferencesSetting(serializers.Serializer):\n    status = serializers.ChoiceField(required=True, choices=NoReferencesChoices.choices,\n                                     label=_(\"No reference status\"))\n    value = serializers.CharField(required=True, label=_(\"Prompt word\"))\n\n\nclass KnowledgeSettingSerializer(serializers.Serializer):\n    top_n = serializers.FloatField(required=True, max_value=10000, min_value=1,\n                                   label=_(\"Reference segment number\"))\n    similarity = serializers.FloatField(required=True, max_value=1, min_value=0,\n                                        label=_(\"Acquaintance\"))\n    max_paragraph_char_number = serializers.IntegerField(required=True, min_value=500, max_value=100000,\n                                                         label=_(\"Maximum number of quoted characters\"))\n    search_mode = serializers.CharField(required=True, validators=[\n        validators.RegexValidator(regex=re.compile(\"^embedding|keywords|blend$\"),\n                                  message=_(\"The type only supports embedding|keywords|blend\"), code=500)\n    ], label=_(\"Retrieval Mode\"))\n\n    no_references_setting = NoReferencesSetting(required=True,\n                                                label=_(\"Segment settings not referenced\"))\n\n\nclass ModelKnowledgeAssociation(serializers.Serializer):\n    user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n    model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,\n                                     label=_(\"Model id\"))\n    knowledge_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True,\n                                                                                               label=_(\n                                                                                                   \"Knowledge base id\")),\n                                                   label=_(\"Knowledge Base List\"))\n\n    def is_valid(self, *, raise_exception=True):\n        super().is_valid(raise_exception=True)\n        model_id = self.data.get('model_id')\n        user_id = self.data.get('user_id')\n        if model_id is not None and len(model_id) > 0:\n            if not QuerySet(Model).filter(id=model_id).exists():\n                raise AppApiException(500, f'{_(\"Model does not exist\")}【{model_id}】')\n        knowledge_id_list = list(set(self.data.get('knowledge_id_list', [])))\n        exist_knowledge_id_list = [str(knowledge.id) for knowledge in\n                                   QuerySet(Knowledge).filter(id__in=knowledge_id_list, user_id=user_id)]\n        for knowledge_id in knowledge_id_list:\n            if not exist_knowledge_id_list.__contains__(knowledge_id):\n                raise AppApiException(500, f'{_(\"The knowledge base id does not exist\")}【{knowledge_id}】')\n\n\nclass ModelSettingSerializer(serializers.Serializer):\n    prompt = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,\n                                   label=_(\"Prompt word\"))\n    system = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,\n                                   label=_(\"Role prompts\"))\n    no_references_prompt = serializers.CharField(required=True, max_length=102400, allow_null=True, allow_blank=True,\n                                                 label=_(\"No citation segmentation prompt\"))\n    reasoning_content_enable = serializers.BooleanField(required=False,\n                                                        label=_(\"Thinking process switch\"))\n    reasoning_content_start = serializers.CharField(required=False, allow_null=True, default=\"<think>\",\n                                                    allow_blank=True, max_length=256,\n                                                    trim_whitespace=False,\n                                                    label=_(\"The thinking process begins to mark\"))\n    reasoning_content_end = serializers.CharField(required=False, allow_null=True, allow_blank=True, default=\"</think>\",\n                                                  max_length=256,\n                                                  trim_whitespace=False,\n                                                  label=_(\"End of thinking process marker\"))\n\n\nclass ApplicationCreateSerializer(serializers.Serializer):\n    class ApplicationResponse(serializers.ModelSerializer):\n        class Meta:\n            model = Application\n            fields = \"__all__\"\n\n    class WorkflowRequest(serializers.Serializer):\n        name = serializers.CharField(required=True, max_length=64, min_length=1,\n                                     label=_(\"Application Name\"))\n        desc = serializers.CharField(required=False, allow_null=True, allow_blank=True,\n                                     max_length=256, min_length=1,\n                                     label=_(\"Application Description\"))\n        work_flow = serializers.DictField(required=True, label=_(\"Workflow Objects\"))\n        prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,\n                                         label=_(\"Opening remarks\"))\n        folder_id = serializers.CharField(required=True, label=_('folder id'))\n\n        @staticmethod\n        def to_application_model(user_id: str, workspace_id: str, application: Dict):\n            default_workflow = application.get('work_flow')\n            for node in default_workflow.get('nodes'):\n                if node.get('id') == 'base-node':\n                    node.get('properties')['node_data']['desc'] = application.get('desc')\n                    node.get('properties')['node_data']['name'] = application.get('name')\n                    node.get('properties')['node_data']['prologue'] = application.get('prologue')\n            return Application(\n                id=uuid.uuid7(),\n                name=application.get('name'),\n                desc=application.get('desc'),\n                workspace_id=workspace_id,\n                folder_id=application.get('folder_id', application.get('workspace_id')),\n                prologue=\"\",\n                dialogue_number=0,\n                user_id=user_id, model_id=None,\n                knowledge_setting={},\n                model_setting={},\n                problem_optimization=False,\n                type=ApplicationTypeChoices.WORK_FLOW,\n                stt_model_enable=application.get('stt_model_enable', False),\n                stt_model_id=application.get('stt_model', None),\n                tts_model_id=application.get('tts_model', None),\n                tts_model_enable=application.get('tts_model_enable', False),\n                tts_model_params_setting=application.get('tts_model_params_setting', {}),\n                tts_type=application.get('tts_type', 'BROWSER'),\n                file_upload_enable=application.get('file_upload_enable', False),\n                file_upload_setting=application.get('file_upload_setting', {}),\n                work_flow=default_workflow\n            )\n\n    class SimplateRequest(serializers.Serializer):\n        name = serializers.CharField(required=True, max_length=64, min_length=1,\n                                     label=_(\"application name\"))\n        desc = serializers.CharField(required=False, allow_null=True, allow_blank=True,\n                                     max_length=256, min_length=1,\n                                     label=_(\"application describe\"))\n        folder_id = serializers.CharField(required=True, label=_('folder id'))\n        model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,\n                                         label=_(\"Model\"))\n        dialogue_number = serializers.IntegerField(required=True,\n                                                   min_value=0,\n                                                   max_value=1024,\n                                                   label=_(\"Historical chat records\"))\n        prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=40960,\n                                         label=_(\"Opening remarks\"))\n        knowledge_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),\n                                                       allow_null=True,\n                                                       label=_(\"Related Knowledge Base\"))\n        # 数据集相关设置\n        knowledge_setting = KnowledgeSettingSerializer(required=True)\n        # 模型相关设置\n        model_setting = ModelSettingSerializer(required=True)\n        # 问题补全\n        problem_optimization = serializers.BooleanField(required=True,\n                                                        label=_(\"Question completion\"))\n        problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,\n                                                            label=_(\"Question completion prompt\"))\n        # 应用类型\n        type = serializers.CharField(required=True, label=_(\"Application Type\"),\n                                     validators=[\n                                         validators.RegexValidator(regex=re.compile(\"^SIMPLE|WORK_FLOW$\"),\n                                                                   message=_(\n                                                                       \"Application type only supports SIMPLE|WORK_FLOW\"),\n                                                                   code=500)\n                                     ]\n                                     )\n        model_params_setting = serializers.DictField(required=False,\n                                                     label=_('Model parameters'))\n\n        tts_model_enable = serializers.BooleanField(required=False, label=_('Voice playback enabled'))\n\n        tts_model_id = serializers.UUIDField(required=False, allow_null=True, label=_(\"Voice playback model ID\"))\n\n        tts_type = serializers.CharField(required=False, label=_('Voice playback type'))\n\n        tts_autoplay = serializers.BooleanField(required=False, label=_('Voice playback autoplay'))\n\n        stt_model_enable = serializers.BooleanField(required=False, label=_('Voice recognition enabled'))\n\n        stt_model_id = serializers.UUIDField(required=False, allow_null=True, label=_('Speech recognition model ID'))\n\n        stt_autosend = serializers.BooleanField(required=False, label=_('Voice recognition automatic transmission'))\n\n        def is_valid(self, *, user_id=None, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            ModelKnowledgeAssociation(data={'user_id': user_id, 'model_id': self.data.get('model_id'),\n                                            'knowledge_id_list': self.data.get('knowledge_id_list')}).is_valid()\n\n        @staticmethod\n        def to_application_model(user_id: str, workspace_id: str, application: Dict):\n            return Application(\n                id=uuid.uuid7(),\n                name=application.get('name'),\n                desc=application.get('desc'),\n                workspace_id=workspace_id,\n                prologue=application.get('prologue'),\n                dialogue_number=application.get('dialogue_number', 0),\n                user_id=user_id, model_id=application.get('model_id'),\n                folder_id=application.get('folder_id', application.get('workspace_id')),\n                knowledge_setting=application.get('knowledge_setting'),\n                model_setting=application.get('model_setting'),\n                problem_optimization=application.get('problem_optimization'),\n                type=ApplicationTypeChoices.SIMPLE,\n                model_params_setting=application.get('model_params_setting', {}),\n                problem_optimization_prompt=application.get('problem_optimization_prompt', None),\n                stt_model_enable=application.get('stt_model_enable', False),\n                stt_model_id=application.get('stt_model', None),\n                stt_autosend=application.get('stt_autosend', False),\n                tts_model_id=application.get('tts_model', None),\n                tts_model_enable=application.get('tts_model_enable', False),\n                tts_model_params_setting=application.get('tts_model_params_setting', {}),\n                tts_type=application.get('tts_type', 'BROWSER'),\n                file_upload_enable=application.get('file_upload_enable', False),\n                file_upload_setting=application.get('file_upload_setting', {}),\n                work_flow={},\n                mcp_enable=application.get('mcp_enable', False),\n                mcp_tool_ids=application.get('mcp_tool_ids', []),\n                mcp_servers=application.get('mcp_servers', {}),\n                mcp_source=application.get('mcp_source', 'referencing'),\n                tool_enable=application.get('tool_enable', False),\n                tool_ids=application.get('tool_ids', []),\n                mcp_output_enable=application.get('mcp_output_enable', False),\n            )\n\n\nclass ApplicationQueryRequest(serializers.Serializer):\n    folder_id = serializers.CharField(required=False, label=_(\"folder id\"))\n    name = serializers.CharField(required=False, label=_('Application Name'))\n    desc = serializers.CharField(required=False, label=_(\"Application Description\"))\n    publish_status = serializers.ChoiceField(required=False, label=_(\"Publish status\"),\n                                             choices=[('published', _(\"Published\")),\n                                                      ('unpublished', _(\"Unpublished\"))])\n    user_id = serializers.UUIDField(required=False, label=_(\"User ID\"))\n\n\nclass ApplicationListResponse(serializers.Serializer):\n    id = serializers.CharField(required=True, label=_(\"Primary key id\"), help_text=_(\"Primary key id\"))\n    name = serializers.CharField(required=True, label=_(\"Application Name\"), help_text=_(\"Application Name\"))\n    desc = serializers.CharField(required=True, label=_(\"Application Description\"),\n                                 help_text=_(\"Application Description\"))\n    is_publish = serializers.BooleanField(required=True, label=_(\"Model id\"), help_text=_(\"Model id\"))\n    type = serializers.CharField(required=True, label=_(\"Application type\"), help_text=_(\"Application type\"))\n    resource_type = serializers.CharField(required=True, label=_(\"Resource type\"), help_text=_(\"Resource type\"))\n    user_id = serializers.CharField(required=True, label=_('Affiliation user'), help_text=_(\"Affiliation user\"))\n    create_time = serializers.CharField(required=True, label=_('Creation time'), help_text=_(\"Creation time\"))\n    update_time = serializers.CharField(required=True, label=_('Modification time'), help_text=_(\"Modification time\"))\n\n\nclass Query(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, label=_('Workspace ID'))\n    user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n\n    def get_query_set(self, instance: Dict, workspace_manage: bool, is_x_pack_ee: bool):\n        folder_query_set = QuerySet(ApplicationFolder)\n        application_query_set = QuerySet(Application)\n        workspace_id = self.data.get('workspace_id')\n        user_id = self.data.get('user_id')\n        desc = instance.get('desc')\n        name = instance.get('name')\n        publish_status = instance.get(\"publish_status\")\n        create_user = instance.get('create_user')\n        if publish_status is not None:\n            is_publish = True if publish_status == \"published\" else False\n            application_query_set = application_query_set.filter(is_publish=is_publish)\n        if workspace_id is not None:\n            folder_query_set = folder_query_set.filter(workspace_id=workspace_id)\n            application_query_set = application_query_set.filter(workspace_id=workspace_id)\n        folder_id = instance.get('folder_id')\n        if folder_id is not None and folder_id != workspace_id:\n            folder_query_set = folder_query_set.filter(parent=folder_id)\n            application_query_set = application_query_set.filter(folder_id=folder_id)\n        if name is not None:\n            folder_query_set = folder_query_set.filter(name__contains=name)\n            application_query_set = application_query_set.filter(name__contains=name)\n        if desc is not None:\n            folder_query_set = folder_query_set.filter(desc__contains=desc)\n            application_query_set = application_query_set.filter(desc__contains=desc)\n        if create_user is not None:\n            application_query_set = application_query_set.filter(user_id=create_user)\n        application_custom_sql_query_set = application_query_set\n        application_query_set = application_query_set.order_by(\"-create_time\")\n\n        resource_and_folder_query_set = QuerySet(WorkspaceUserResourcePermission).filter(\n            auth_target_type=\"APPLICATION\",\n            workspace_id=workspace_id,\n            user_id=user_id)\n\n        return {'application_query_set': application_query_set,\n                'workspace_user_resource_permission_query_set': resource_and_folder_query_set,\n                } if (\n            not workspace_manage) else {\n            'application_query_set': application_query_set,\n            'application_custom_sql': application_custom_sql_query_set\n        }\n\n    @staticmethod\n    def is_x_pack_ee():\n        workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n        role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n        return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None\n\n    def list(self, instance: Dict):\n        self.is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        user_id = self.data.get(\"user_id\")\n        ApplicationQueryRequest(data=instance).is_valid(raise_exception=True)\n        workspace_manage = is_workspace_manage(user_id, workspace_id)\n        is_x_pack_ee = self.is_x_pack_ee()\n        return native_search(self.get_query_set(instance, workspace_manage, is_x_pack_ee),\n                             select_string=get_file_content(\n                                 os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql',\n                                              'list_application.sql' if workspace_manage else (\n                                                  'list_application_user_ee.sql' if is_x_pack_ee else 'list_application_user.sql')\n                                              )))\n\n    def page(self, current_page: int, page_size: int, instance: Dict):\n        self.is_valid(raise_exception=True)\n        ApplicationQueryRequest(data=instance).is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        user_id = self.data.get(\"user_id\")\n        workspace_manage = is_workspace_manage(user_id, workspace_id)\n        is_x_pack_ee = self.is_x_pack_ee()\n        result = native_page_search(current_page, page_size,\n                                    self.get_query_set(instance, workspace_manage, is_x_pack_ee),\n                                    get_file_content(\n                                        os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql',\n                                                     'list_application.sql' if workspace_manage else (\n                                                         'list_application_user_ee.sql' if is_x_pack_ee else 'list_application_user.sql'))),\n                                    )\n\n        return ResourceMappingSerializer().get_resource_count(result)\n\n\nclass ApplicationImportRequest(serializers.Serializer):\n    file = UploadedFileField(required=True, label=_(\"file\"))\n    folder_id = serializers.CharField(required=True, label=_(\"Folder ID\"))\n\n\nclass ApplicationEditSerializer(serializers.Serializer):\n    name = serializers.CharField(required=False, max_length=64, min_length=1,\n                                 label=_(\"Application Name\"))\n    desc = serializers.CharField(required=False, max_length=256, min_length=1, allow_null=True, allow_blank=True,\n                                 label=_(\"Application Description\"))\n    model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True,\n                                     label=_(\"Model\"))\n    dialogue_number = serializers.IntegerField(required=False,\n                                               min_value=0,\n                                               max_value=1024,\n                                               label=_(\"Historical chat records\"))\n    prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,\n                                     label=_(\"Opening remarks\"))\n    knowledge_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),\n                                                   label=_(\"Related Knowledge Base\")\n                                                   )\n    # 数据集相关设置\n    knowledge_setting = KnowledgeSettingSerializer(required=False, allow_null=True,\n                                                   label=_(\"Dataset settings\"))\n    # 模型相关设置\n    model_setting = ModelSettingSerializer(required=False, allow_null=True,\n                                           label=_(\"Model setup\"))\n    # 问题补全\n    problem_optimization = serializers.BooleanField(required=False, allow_null=True,\n                                                    label=_(\"Question completion\"))\n    icon = serializers.CharField(required=False, allow_null=True, label=_(\"Icon\"))\n\n    model_params_setting = serializers.DictField(required=False,\n                                                 label=_('Model parameters'))\n\n    tts_model_enable = serializers.BooleanField(required=False, label=_('Voice playback enabled'))\n\n    tts_model_id = serializers.UUIDField(required=False, allow_null=True, label=_(\"Voice playback model ID\"))\n\n    tts_type = serializers.CharField(required=False, label=_('Voice playback type'))\n\n    tts_autoplay = serializers.BooleanField(required=False, label=_('Voice playback autoplay'))\n\n    stt_model_enable = serializers.BooleanField(required=False, label=_('Voice recognition enabled'))\n\n    stt_model_id = serializers.UUIDField(required=False, allow_null=True, label=_('Speech recognition model ID'))\n\n    stt_autosend = serializers.BooleanField(required=False, label=_('Voice recognition automatic transmission'))\n\n\nclass ApplicationSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n    user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n\n    @transaction.atomic\n    def insert(self, instance: Dict):\n        work_flow_template = instance.get('work_flow_template')\n        application_type = instance.get('type')\n\n        # 处理工作流模板安装逻辑\n        if work_flow_template:\n            return self.insert_template_workflow(instance)\n        if 'WORK_FLOW' == application_type:\n            r = self.insert_workflow(instance)\n        else:\n            r = self.insert_simple(instance)\n        UserResourcePermissionSerializer(data={\n            'workspace_id': self.data.get('workspace_id'),\n            'user_id': self.data.get('user_id'),\n            'auth_target_type': AuthTargetType.APPLICATION.value\n        }).auth_resource(str(r.get('id')))\n        return r\n\n    def insert_template_workflow(self, instance: Dict):\n        self.is_valid(raise_exception=True)\n        work_flow_template = instance.get('work_flow_template')\n        download_url = work_flow_template.get('downloadUrl')\n        # 查找匹配的版本名称\n        res = requests.get(download_url, timeout=5)\n        app = ApplicationSerializer(\n            data={'user_id': self.data.get('user_id'), 'workspace_id': self.data.get('workspace_id')}\n        ).import_({\n            'file': bytes_to_uploaded_file(res.content, 'file.mk'),\n            'folder_id': instance.get('folder_id', instance.get('workspace_id'))\n        }, True)\n        work_flow = app.get('work_flow')\n        for node in work_flow.get('nodes', []):\n            if node.get('type') == 'base-node':\n                node_data = node.get('properties').get('node_data')\n                node_data['name'] = instance.get('name')\n                node_data['desc'] = instance.get('desc')\n        QuerySet(Application).filter(id=app.get('id')).update(\n            name=instance.get('name'),\n            desc=instance.get('desc'),\n            work_flow=work_flow\n        )\n        try:\n            requests.get(work_flow_template.get('downloadCallbackUrl'), timeout=5)\n        except Exception as e:\n            maxkb_logger.error(f\"callback appstore tool download error: {e}\")\n        return app\n\n    def insert_workflow(self, instance: Dict):\n        self.is_valid(raise_exception=True)\n        user_id = self.data.get('user_id')\n        workspace_id = self.data.get('workspace_id')\n        wq = ApplicationCreateSerializer.WorkflowRequest(data=instance)\n        wq.is_valid(raise_exception=True)\n        application_model = wq.to_application_model(user_id, workspace_id, instance)\n        application_model.save()\n        # 插入认证信息\n        ApplicationAccessToken(application_id=application_model.id,\n                               access_token=hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24]).save()\n        return ApplicationCreateSerializer.ApplicationResponse(application_model).data\n\n    @staticmethod\n    def to_application_knowledge_mapping(application_id: str, knowledge_id: str):\n        return ResourceMapping(id=uuid.uuid7(), source_id=application_id, target_id=knowledge_id,\n                               source_type=\"APPLICATION\",\n                               target_type=\"KNOWLEDGE\")\n\n    def insert_simple(self, instance: Dict):\n        self.is_valid(raise_exception=True)\n        user_id = self.data.get('user_id')\n        workspace_id = self.data.get(\"workspace_id\")\n        ApplicationCreateSerializer.SimplateRequest(data=instance).is_valid(user_id=user_id, raise_exception=True)\n        application_model = ApplicationCreateSerializer.SimplateRequest.to_application_model(user_id, workspace_id,\n                                                                                             instance)\n        knowledge_id_list = instance.get('knowledge_id_list', [])\n        application_knowledge_mapping_model_list = [\n            self.to_application_knowledge_mapping(application_model.id, knowledge_id) for\n            knowledge_id in knowledge_id_list]\n        # 插入应用\n        application_model.save()\n        # 插入认证信息\n        ApplicationAccessToken(application_id=application_model.id,\n                               access_token=hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24]).save()\n        # 插入关联数据\n        QuerySet(ResourceMapping).bulk_create(application_knowledge_mapping_model_list)\n        return ApplicationCreateSerializer.ApplicationResponse(application_model).data\n\n    @transaction.atomic\n    def import_(self, instance: dict, is_import_tool, with_valid=True):\n        if with_valid:\n            self.is_valid()\n            ApplicationImportRequest(data=instance).is_valid(raise_exception=True)\n        user_id = self.data.get('user_id')\n        workspace_id = self.data.get(\"workspace_id\")\n        folder_id = instance.get('folder_id')\n        mk_instance_bytes = instance.get('file').read()\n        try:\n            mk_instance = restricted_loads(mk_instance_bytes)\n        except Exception as e:\n            raise AppApiException(1001, _(\"Unsupported file format\"))\n        application = mk_instance.application\n        tool_list = mk_instance.get_tool_list()\n        update_tool_map = {}\n        if len(tool_list) > 0:\n            tool_id_list = reduce(lambda x, y: [*x, *y],\n                                  [[tool.get('id'), generate_uuid((tool.get('id') + workspace_id or ''))]\n                                   for tool\n                                   in\n                                   tool_list], [])\n            # 存在的工具列表\n            exits_tool_id_list = [str(tool.id) for tool in\n                                  QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=workspace_id)]\n            # 需要更新的工具集合\n            update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + workspace_id or '')) for tool\n                               in\n                               tool_list if\n                               not exits_tool_id_list.__contains__(\n                                   tool.get('id'))}\n\n            tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if\n                         not exits_tool_id_list.__contains__(\n                             tool.get('id')) and not exits_tool_id_list.__contains__(\n                             generate_uuid((tool.get('id') + workspace_id or '')))]\n        application_model = self.to_application(application, workspace_id, user_id, update_tool_map, folder_id)\n        tool_model_list = [self.to_tool(f, workspace_id, user_id) for f in tool_list]\n        application_model.save()\n        # 插入授权数据\n        UserResourcePermissionSerializer(data={\n            'workspace_id': self.data.get('workspace_id'),\n            'user_id': self.data.get('user_id'),\n            'auth_target_type': AuthTargetType.APPLICATION.value\n        }).auth_resource(str(application_model.id))\n        # 插入认证信息\n        ApplicationAccessToken(application_id=application_model.id,\n                               access_token=hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24]).save()\n        if is_import_tool:\n            if len(tool_model_list) > 0:\n                QuerySet(Tool).bulk_create(tool_model_list)\n                UserResourcePermissionSerializer(data={\n                    'workspace_id': self.data.get('workspace_id'),\n                    'user_id': self.data.get('user_id'),\n                    'auth_target_type': AuthTargetType.TOOL.value\n                }).auth_resource_batch([t.id for t in tool_model_list])\n\n        return ApplicationCreateSerializer.ApplicationResponse(application_model).data\n\n    @staticmethod\n    def to_tool(tool, workspace_id, user_id):\n        \"\"\"\n        @param workspace_id:\n        @param user_id: 用户id\n        @param tool: 工具\n        @return:\n        \"\"\"\n        # 如果是技能类型的工具，需要将code保存为文件\n        code = tool.get('code')\n        if tool.get('tool_type') == ToolType.SKILL:\n            skill_file_id = uuid.uuid7()\n            skill_file = File(\n                id=skill_file_id,\n                file_name=f\"{tool.get('name')}.zip\",\n                source_type=FileSourceType.TOOL,\n                source_id=tool.get('id'),\n                meta={}\n            )\n            skill_file.save(base64.b64decode(code))\n            tool['code'] = skill_file_id\n        return Tool(id=tool.get('id'),\n                    user_id=user_id,\n                    name=tool.get('name'),\n                    code=tool.get('code'),\n                    template_id=tool.get('template_id'),\n                    input_field_list=tool.get('input_field_list'),\n                    init_field_list=tool.get('init_field_list'),\n                    is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'),\n                    tool_type=tool.get('tool_type', 'CUSTOM') or 'CUSTOM',\n                    scope=ToolScope.WORKSPACE,\n                    folder_id=workspace_id,\n                    workspace_id=workspace_id)\n\n    @staticmethod\n    def to_application(application, workspace_id, user_id, update_tool_map, folder_id):\n        work_flow = application.get('work_flow')\n        for node in work_flow.get('nodes', []):\n            hand_node(node, update_tool_map)\n            if node.get('type') == 'loop-node':\n                for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []):\n                    hand_node(n, update_tool_map)\n        return Application(id=uuid.uuid7(),\n                           user_id=user_id,\n                           name=application.get('name'),\n                           workspace_id=workspace_id,\n                           folder_id=folder_id,\n                           desc=application.get('desc'),\n                           prologue=application.get('prologue'), dialogue_number=application.get('dialogue_number'),\n                           knowledge_setting=application.get('knowledge_setting'),\n                           model_setting=application.get('model_setting'),\n                           model_params_setting=application.get('model_params_setting'),\n                           tts_model_params_setting=application.get('tts_model_params_setting'),\n                           problem_optimization=application.get('problem_optimization'),\n                           icon=\"./favicon.ico\",\n                           work_flow=work_flow,\n                           type=application.get('type'),\n                           problem_optimization_prompt=application.get('problem_optimization_prompt'),\n                           tts_model_enable=application.get('tts_model_enable'),\n                           stt_model_enable=application.get('stt_model_enable'),\n                           tts_type=application.get('tts_type'),\n                           clean_time=application.get('clean_time'),\n                           file_clean_time=application.get('file_clean_time') or 180,\n                           file_upload_enable=application.get('file_upload_enable'),\n                           file_upload_setting=application.get('file_upload_setting'),\n                           tool_ids=[update_tool_map.get(tool_id, tool_id) for tool_id in\n                                     application.get('tool_ids', [])],\n                           skill_tool_ids=[update_tool_map.get(tool_id, tool_id) for tool_id in\n                                           application.get('skill_tool_ids', [])],\n                           mcp_tool_ids=[update_tool_map.get(tool_id, tool_id) for tool_id in\n                                         application.get('mcp_tool_ids', [])],\n                           )\n\n    class StoreApplication(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n        name = serializers.CharField(required=False, label=_(\"tool name\"), allow_null=True, allow_blank=True)\n\n        def get_appstore_templates(self):\n            self.is_valid(raise_exception=True)\n            # 下载zip文件\n            try:\n                res = requests.get('https://apps-assets.fit2cloud.com/stable/maxkb.json.zip', timeout=5)\n                res.raise_for_status()\n                # 创建临时文件保存zip\n                with tempfile.NamedTemporaryFile(delete=False, suffix='.zip') as temp_zip:\n                    temp_zip.write(res.content)\n                    temp_zip_path = temp_zip.name\n\n                try:\n                    # 解压zip文件\n                    with zipfile.ZipFile(temp_zip_path, 'r') as zip_ref:\n                        # 获取zip中的第一个文件（假设只有一个json文件）\n                        json_filename = zip_ref.namelist()[0]\n                        json_content = zip_ref.read(json_filename)\n\n                    # 将json转换为字典\n                    tool_store = json.loads(json_content.decode('utf-8'))\n                    tag_dict = {tag['name']: tag['key'] for tag in tool_store['additionalProperties']['tags']}\n                    filter_apps = []\n                    for tool in tool_store['apps']:\n                        if self.data.get('name', '') != '':\n                            if self.data.get('name').lower() not in tool.get('name', '').lower():\n                                continue\n                        if not tool['downloadUrl'].endswith('.mk'):\n                            continue\n                        versions = tool.get('versions', [])\n                        tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else ''\n                        tool['version'] = next(\n                            (version.get('name') for version in versions if\n                             version.get('downloadUrl') == tool['downloadUrl']),\n                        )\n                        filter_apps.append(tool)\n\n                    tool_store['apps'] = filter_apps\n                    return tool_store\n                finally:\n                    # 清理临时文件\n                    os.unlink(temp_zip_path)\n            except Exception as e:\n                maxkb_logger.error(f\"fetch appstore tools error: {e}\")\n                return {'apps': [], 'additionalProperties': {'tags': []}}\n\n\nclass TextToSpeechRequest(serializers.Serializer):\n    text = serializers.CharField(required=True, label=_('Text'))\n\n\nclass SpeechToTextRequest(serializers.Serializer):\n    file = UploadedFileField(required=True, label=_(\"file\"))\n\n\nclass PlayDemoTextRequest(serializers.Serializer):\n    tts_model_id = serializers.UUIDField(required=True, label=_('Text to speech model ID'))\n\n\nasync def get_mcp_tools(servers):\n    client = MultiServerMCPClient(servers)\n    return await client.get_tools()\n\n\nclass McpServersSerializer(serializers.Serializer):\n    mcp_servers = serializers.JSONField(required=True)\n\n\nclass ApplicationOperateSerializer(serializers.Serializer):\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Application id does not exist'))\n\n    def get_mcp_servers(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            McpServersSerializer(data=instance).is_valid(raise_exception=True)\n        servers = json.loads(instance.get('mcp_servers'))\n        for server, config in servers.items():\n            if config.get('transport') not in ['sse', 'streamable_http']:\n                raise AppApiException(500, _('Only support transport=sse or transport=streamable_http'))\n        tools = []\n        for server in servers:\n            tools += [\n                {\n                    'server': server,\n                    'name': tool.name,\n                    'description': tool.description,\n                    'args_schema': tool.args_schema,\n                }\n                for tool in asyncio.run(get_mcp_tools({server: servers[server]}))]\n        return tools\n\n    def delete(self, with_valid=True):\n        from trigger.handler.simple_tools import deploy\n        from trigger.serializers.trigger import TriggerModelSerializer\n        if with_valid:\n            self.is_valid()\n        application_id = self.data.get('application_id')\n        QuerySet(ApplicationVersion).filter(application_id=application_id).delete()\n        QuerySet(ResourceMapping).filter(\n            Q(target_id=application_id) | Q(source_id=application_id)\n        ).delete()\n        QuerySet(Application).filter(id=application_id).delete()\n        trigger_ids = list(\n            QuerySet(TriggerTask).filter(\n                source_type=\"APPLICATION\", source_id=application_id\n            ).values('trigger_id').distinct()\n        )\n        QuerySet(TriggerTask).filter(source_type=\"APPLICATION\", source_id=application_id).delete()\n        for trigger_id in trigger_ids:\n            trigger = Trigger.objects.filter(id=trigger_id['trigger_id']).first()\n            if trigger and trigger.is_active:\n                deploy(TriggerModelSerializer(trigger).data, **{})\n        return True\n\n    def export(self, with_valid=True):\n        try:\n            if with_valid:\n                self.is_valid()\n            application_id = self.data.get('application_id')\n            application = QuerySet(Application).filter(id=application_id).first()\n            from application.flow.tools import get_tool_id_list\n            tool_id_list = get_tool_id_list(application.work_flow)\n            if len(tool_id_list) > 0:\n                tool_list = QuerySet(Tool).filter(id__in=tool_id_list).exclude(scope=ToolScope.SHARED)\n            else:\n                tool_list = QuerySet(Tool).filter(\n                    id__in=application.tool_ids + application.mcp_tool_ids + application.skill_tool_ids\n                ).exclude(scope=ToolScope.SHARED)\n            # 如果是技能工具，则需要将code字段转换为文件内容的base64字符串\n            for tool in tool_list:\n                if tool.tool_type == ToolType.SKILL:\n                    skill_file = QuerySet(File).filter(id=tool.code).first()\n                    if skill_file:\n                        tool.code = base64.b64encode(skill_file.get_bytes()).decode('utf-8')\n            application_dict = ApplicationSerializerModel(application).data\n\n            mk_instance = MKInstance(application_dict,\n                                     [],\n                                     'v2',\n                                     [ToolExportModelSerializer(tool).data for tool in\n                                      tool_list])\n            application_pickle = pickle.dumps(mk_instance)\n            response = HttpResponse(content_type='text/plain', content=application_pickle)\n            response['Content-Disposition'] = f'attachment; filename=\"{application.name}.mk\"'\n            return response\n        except Exception as e:\n            return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR)\n\n    @staticmethod\n    def reset_application_version(application_version, application):\n        update_field_dict = {\n            'application_name': 'name', 'desc': 'desc', 'prologue': 'prologue', 'dialogue_number': 'dialogue_number',\n            'user_id': 'user_id', 'model_id': 'model_id', 'knowledge_setting': 'knowledge_setting',\n            'model_setting': 'model_setting', 'model_params_setting': 'model_params_setting',\n            'tts_model_params_setting': 'tts_model_params_setting',\n            'stt_model_params_setting': 'stt_model_params_setting',\n            'problem_optimization': 'problem_optimization', 'icon': 'icon', 'work_flow': 'work_flow',\n            'problem_optimization_prompt': 'problem_optimization_prompt', 'tts_model_id': 'tts_model_id',\n            'stt_model_id': 'stt_model_id', 'tts_model_enable': 'tts_model_enable',\n            'stt_model_enable': 'stt_model_enable', 'tts_type': 'tts_type',\n            'tts_autoplay': 'tts_autoplay', 'stt_autosend': 'stt_autosend', 'file_upload_enable': 'file_upload_enable',\n            'file_upload_setting': 'file_upload_setting',\n            'mcp_enable': 'mcp_enable', 'mcp_tool_ids': 'mcp_tool_ids', 'mcp_servers': 'mcp_servers',\n            'mcp_source': 'mcp_source', 'tool_enable': 'tool_enable', 'tool_ids': 'tool_ids',\n            'application_enable': 'application_enable', 'application_ids': 'application_ids',\n            'skill_tool_ids': 'skill_tool_ids',\n            'mcp_output_enable': 'mcp_output_enable',\n            'type': 'type'\n        }\n\n        for (version_field, app_field) in update_field_dict.items():\n            _v = getattr(application, app_field)\n            setattr(application_version, version_field, _v)\n\n    @transaction.atomic\n    def publish(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid()\n        user_id = self.data.get('user_id')\n        workspace_id = self.data.get(\"workspace_id\")\n        user = QuerySet(User).filter(id=user_id).first()\n        application = QuerySet(Application).filter(id=self.data.get(\"application_id\"),\n                                                   workspace_id=workspace_id).first()\n        if application.type == ApplicationTypeChoices.WORK_FLOW:\n            work_flow = application.work_flow\n            if work_flow is None:\n                raise AppApiException(500, _(\"work_flow is a required field\"))\n            Workflow.new_instance(work_flow).is_valid()\n            base_node = get_base_node_work_flow(work_flow)\n            if base_node is not None:\n                node_data = base_node.get('properties').get('node_data')\n                if node_data is not None:\n                    application.name = node_data.get('name')\n                    application.desc = node_data.get('desc')\n                    application.prologue = node_data.get('prologue')\n            application.work_flow = work_flow\n        application.publish_time = timezone.now()\n        application.is_publish = True\n        application.save()\n        work_flow_version = ApplicationVersion(work_flow=application.work_flow, application=application,\n                                               name=timezone.localtime(timezone.now()).strftime('%Y-%m-%d %H:%M:%S'),\n                                               publish_user_id=user_id,\n                                               publish_user_name=user.username,\n                                               workspace_id=workspace_id)\n        self.reset_application_version(work_flow_version, application)\n        work_flow_version.save()\n        access_token = hashlib.md5(\n            str(uuid.uuid7()).encode()).hexdigest()[\n                       8:24]\n        application_access_token = QuerySet(ApplicationAccessToken).filter(\n            application_id=application.id).first()\n        if application_access_token is None:\n            application_access_token = ApplicationAccessToken(application_id=application.id,\n                                                              access_token=access_token, is_active=True)\n            application_access_token.save()\n        else:\n            access_token = application_access_token.access_token\n        del_application_access_token(access_token)\n        QuerySet(TriggerTask).filter(source_type=\"APPLICATION\", source_id=self.data.get(\"application_id\")).update(\n            is_active=True)\n        return self.one(with_valid=False)\n\n    @staticmethod\n    def update_work_flow_model(instance):\n        if 'nodes' not in instance.get('work_flow'):\n            return\n        nodes = instance.get('work_flow')['nodes']\n        for node in nodes:\n            if node['id'] == 'base-node':\n                node_data = node['properties']['node_data']\n                if 'stt_model_id' in node_data:\n                    instance['stt_model_id'] = node_data['stt_model_id']\n                if 'tts_model_id' in node_data:\n                    instance['tts_model_id'] = node_data['tts_model_id']\n                if 'stt_model_enable' in node_data:\n                    instance['stt_model_enable'] = node_data['stt_model_enable']\n                if 'tts_model_enable' in node_data:\n                    instance['tts_model_enable'] = node_data['tts_model_enable']\n                if 'tts_type' in node_data:\n                    instance['tts_type'] = node_data['tts_type']\n                if 'tts_autoplay' in node_data:\n                    instance['tts_autoplay'] = node_data['tts_autoplay']\n                if 'stt_autosend' in node_data:\n                    instance['stt_autosend'] = node_data['stt_autosend']\n                if 'tts_model_params_setting' in node_data:\n                    instance['tts_model_params_setting'] = node_data['tts_model_params_setting']\n                if 'stt_model_params_setting' in node_data:\n                    instance['stt_model_params_setting'] = node_data['stt_model_params_setting']\n                if 'file_upload_enable' in node_data:\n                    instance['file_upload_enable'] = node_data['file_upload_enable']\n                if 'file_upload_setting' in node_data:\n                    instance['file_upload_setting'] = node_data['file_upload_setting']\n                if 'name' in node_data:\n                    instance['name'] = node_data['name']\n                break\n        knowledge_node_list = ApplicationOperateSerializer.get_search_node(instance.get('work_flow'))\n        for knowledge_node in knowledge_node_list:\n            node_data = knowledge_node.get('properties').get('node_data')\n            # 全部知识库id\n            all_knowledge_id_list = node_data.get('all_knowledge_id_list') or []\n            # 用户修改的知识库id\n            knowledge_id_list = node_data.get('knowledge_id_list') or []\n            # 用户可以看到的知识库\n            knowledge_list = node_data.get('knowledge_list') or []\n            view_knowledge_id_list = [knowledge.get('id') for knowledge in knowledge_list]\n            other_knowledge_id_list = [knowledge_id for knowledge_id in all_knowledge_id_list if\n                                       not view_knowledge_id_list.__contains__(knowledge_id)]\n            node_data['knowledge_id_list'] = other_knowledge_id_list + knowledge_id_list\n\n    def move(self, folder_id: str):\n        self.is_valid(raise_exception=True)\n        application_id = self.data.get(\"application_id\")\n        application = QuerySet(Application).get(id=application_id)\n        application.folder_id = folder_id\n        application.save()\n        return True\n\n    @transaction.atomic\n    def edit(self, instance: Dict, with_valid=True):\n        if with_valid:\n            self.is_valid()\n            ApplicationEditSerializer(data=instance).is_valid(\n                raise_exception=True)\n        application_id = self.data.get(\"application_id\")\n\n        application = QuerySet(Application).get(id=application_id)\n        #  处理工作流模板逻辑\n        if 'work_flow_template' in instance:\n            return self.update_template_workflow(instance, application)\n\n        if instance.get('model_id') is None or len(instance.get('model_id')) == 0:\n            application.model_id = None\n        else:\n            model = QuerySet(Model).filter(\n                id=instance.get('model_id')).first()\n            if model is None:\n                raise AppApiException(500, _(\"Model does not exist\"))\n        if instance.get('stt_model_id') is None or len(instance.get('stt_model_id')) == 0:\n            application.stt_model_id = None\n        else:\n            model = QuerySet(Model).filter(\n                id=instance.get('stt_model_id')).first()\n            if model is None:\n                raise AppApiException(500, _(\"Model does not exist\"))\n        if instance.get('tts_model_id') is None or len(instance.get('tts_model_id')) == 0:\n            application.tts_model_id = None\n        else:\n            model = QuerySet(Model).filter(\n                id=instance.get('tts_model_id')).first()\n            if model is None:\n                raise AppApiException(500, _(\"Model does not exist\"))\n        if 'work_flow' in instance:\n            # 修改语音配置相关\n            self.update_work_flow_model(instance)\n        update_keys = ['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'prologue', 'status',\n                       'knowledge_setting', 'model_setting', 'problem_optimization', 'dialogue_number',\n                       'stt_model_id', 'tts_model_id', 'tts_model_enable', 'stt_model_enable', 'tts_type',\n                       'tts_autoplay', 'stt_autosend', 'file_upload_enable', 'file_upload_setting',\n                       'api_key_is_active', 'icon', 'work_flow', 'model_params_setting', 'tts_model_params_setting',\n                       'stt_model_params_setting',\n                       'mcp_enable', 'mcp_tool_ids', 'mcp_servers', 'mcp_source', 'tool_enable', 'tool_ids',\n                       'mcp_output_enable', 'application_enable', 'application_ids', 'skill_tool_ids',\n                       'problem_optimization_prompt', 'clean_time', 'file_clean_time', 'folder_id']\n        for update_key in update_keys:\n            if update_key in instance and instance.get(update_key) is not None:\n                application.__setattr__(update_key, instance.get(update_key))\n        application.save()\n        # 当前用户可修改关联的知识库列表\n        application_knowledge_id_list = [str(knowledge.get('id')) for knowledge in\n                                         self.list_knowledge(with_valid=False)]\n        knowledge_id_list = []\n        if 'knowledge_id_list' in instance:\n            # 当前用户可修改关联的知识库列表\n            application_knowledge_id_list = [str(knowledge.get('id')) for knowledge in\n                                             self.list_knowledge(with_valid=False)]\n            knowledge_id_list = instance.get('knowledge_id_list')\n            for knowledge_id in knowledge_id_list:\n                if not application_knowledge_id_list.__contains__(knowledge_id):\n                    message = lazy_format(_('Unknown knowledge base id {dataset_id}, unable to associate'),\n                                          dataset_id=knowledge_id)\n                    raise AppApiException(500, str(message))\n\n        update_resource_mapping_by_application(application_id,\n                                               self.get_application_knowledge_mapping(application_knowledge_id_list,\n                                                                                      knowledge_id_list,\n                                                                                      application_id))\n        return self.one(with_valid=False)\n\n    def update_template_workflow(self, instance: Dict, app: Application):\n        self.is_valid(raise_exception=True)\n        work_flow_template = instance.get('work_flow_template')\n        download_url = work_flow_template.get('downloadUrl')\n        # 查找匹配的版本名称\n        res = requests.get(download_url, timeout=5)\n        try:\n            mk_instance = restricted_loads(res.content)\n        except Exception as e:\n            raise AppApiException(1001, _(\"Unsupported file format\"))\n        application = mk_instance.application\n        tool_list = mk_instance.get_tool_list()\n        update_tool_map = {}\n        if len(tool_list) > 0:\n            tool_id_list = reduce(lambda x, y: [*x, *y],\n                                  [[tool.get('id'), generate_uuid((tool.get('id') + app.workspace_id or ''))]\n                                   for tool\n                                   in\n                                   tool_list], [])\n            # 存在的工具列表\n            exits_tool_id_list = [str(tool.id) for tool in\n                                  QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=app.workspace_id)]\n            # 需要更新的工具集合\n            update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + app.workspace_id or '')) for tool\n                               in\n                               tool_list if\n                               not exits_tool_id_list.__contains__(\n                                   tool.get('id'))}\n\n            tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if\n                         not exits_tool_id_list.__contains__(\n                             tool.get('id')) and not exits_tool_id_list.__contains__(\n                             generate_uuid((tool.get('id') + app.workspace_id or '')))]\n\n        tool_model_list = [self.to_tool(f, app.workspace_id, self.data.get('user_id')) for f in tool_list]\n        work_flow = application.get('work_flow')\n        for node in work_flow.get('nodes', []):\n            hand_node(node, update_tool_map)\n            if node.get('type') == 'loop-node':\n                for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []):\n                    hand_node(n, update_tool_map)\n        app.work_flow = work_flow\n        application = mk_instance.application\n        app.name = application.get('name')\n        app.desc = application.get('desc')\n        app.save()\n\n        if len(tool_model_list) > 0:\n            QuerySet(Tool).bulk_create(tool_model_list)\n            UserResourcePermissionSerializer(data={\n                'workspace_id': app.workspace_id,\n                'user_id': self.data.get('user_id'),\n                'auth_target_type': AuthTargetType.TOOL.value\n            }).auth_resource_batch([t.id for t in tool_model_list])\n        try:\n            requests.get(work_flow_template.get('downloadCallbackUrl'), timeout=5)\n        except Exception as e:\n            maxkb_logger.error(f\"callback appstore tool download error: {e}\")\n\n        return self.one(with_valid=False)\n\n    @staticmethod\n    def to_tool(tool, workspace_id, user_id):\n        return Tool(\n            id=tool.get('id'),\n            user_id=user_id,\n            name=tool.get('name'),\n            code=tool.get('code'),\n            template_id=tool.get('template_id'),\n            input_field_list=tool.get('input_field_list'),\n            init_field_list=tool.get('init_field_list'),\n            is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'),\n            scope=ToolScope.WORKSPACE,\n            folder_id=workspace_id,\n            workspace_id=workspace_id\n        )\n\n    def one(self, with_valid=True):\n        if with_valid:\n            self.is_valid()\n        application_id = self.data.get(\"application_id\")\n        application = QuerySet(Application).get(id=application_id)\n        available_knowledge_list = self.list_knowledge(with_valid=False)\n        available_knowledge_dict = {knowledge.get('id'): knowledge for knowledge in available_knowledge_list}\n        knowledge_list = []\n        knowledge_id_list = []\n        if application.type == 'SIMPLE':\n            mapping_knowledge_list = QuerySet(ResourceMapping).filter(source_id=application_id,\n                                                                      source_type=\"APPLICATION\",\n                                                                      target_type=\"KNOWLEDGE\")\n            knowledge_list = [available_knowledge_dict.get(str(km.target_id)) for km in mapping_knowledge_list if\n                              available_knowledge_dict.__contains__(str(km.target_id))]\n            knowledge_id_list = [k.get('id') for k in knowledge_list]\n        else:\n            self.update_knowledge_node(application.work_flow, available_knowledge_dict)\n\n        return {**ApplicationSerializerModel(application).data,\n                'knowledge_id_list': knowledge_id_list,\n                'knowledge_list': knowledge_list}\n\n    @staticmethod\n    def get_search_node(work_flow):\n        if work_flow is None:\n            return []\n        response = []\n        if 'nodes' in work_flow:\n            for node in work_flow.get('nodes'):\n                if node.get('type', '') == 'search-knowledge-node':\n                    response.append(node)\n                if node.get('type') == 'loop-node':\n                    r = ApplicationOperateSerializer.get_search_node(\n                        node.get('properties', {}).get('node_data', {}).get('loop_body'))\n                    for rn in r:\n                        response.append(rn)\n        return response\n\n    def update_knowledge_node(self, workflow, available_knowledge_dict):\n        \"\"\"\n        修改知识库检索节点 数据\n        定义 all_knowledge_id_list:    所有的关联知识库\n            knowledge_id_list:          当前用户可看到的关联知识库列表\n            knowledge_list:           用户\n        @param workflow:              知识库\n        @param available_knowledge_dict:   当前用户可用的知识库\n        @return:\n        \"\"\"\n        knowledge_node_list = self.get_search_node(workflow)\n        for search_node in knowledge_node_list:\n            node_data = search_node.get('properties', {}).get('node_data', {})\n            # 当前知识库关联的所有知识库\n            knowledge_id_list = node_data.get('knowledge_id_list', [])\n            knowledge_list = [available_knowledge_dict.get(knowledge_id) for knowledge_id in knowledge_id_list if\n                              available_knowledge_dict.__contains__(knowledge_id)]\n            node_data['all_knowledge_id_list'] = knowledge_id_list\n            node_data['knowledge_id_list'] = [knowledge.get('id') for knowledge in knowledge_list]\n            node_data['knowledge_list'] = knowledge_list\n\n    def list_knowledge(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        workspace_id = self.data.get(\"workspace_id\")\n        user_id = self.data.get('user_id')\n        knowledge_workspace_authorization_model = DatabaseModelManage.get_model('knowledge_workspace_authorization')\n        share_knowledge_list = []\n        if knowledge_workspace_authorization_model is not None:\n            white_list_condition = Q(authentication_type='WHITE_LIST') & Q(\n                workspace_id_list__contains=[workspace_id])\n            default_condition = ~Q(authentication_type='WHITE_LIST') & ~Q(\n                workspace_id_list__contains=[workspace_id])\n            # 组合查询\n            query = white_list_condition | default_condition\n            inner = QuerySet(knowledge_workspace_authorization_model).filter(query)\n            share_knowledge_list = [{**KnowledgeModelSerializer(k).data, 'scope': 'SHARED'} for k in\n                                    QuerySet(Knowledge).filter(id__in=inner)]\n        workspace_knowledge_list = [{**k, 'scope': 'WORKSPACE'} for k in KnowledgeSerializer.Query(\n            data={\n                'workspace_id': workspace_id,\n                'scope': KnowledgeScope.WORKSPACE,\n                'user_id': user_id\n            }\n        ).list() if k.get('resource_type') == 'knowledge']\n\n        return [*workspace_knowledge_list, *share_knowledge_list]\n\n    @staticmethod\n    def save_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id):\n        # 需要排除已删除的数据集\n        knowledge_id_list = [knowledge.id for knowledge in QuerySet(Knowledge).filter(id__in=knowledge_id_list)]\n\n        # 删除已经关联的id\n        QuerySet(ResourceMapping).filter(target_id__in=application_knowledge_id_list,\n                                         source_id=application_id,\n                                         source_type='APPLICATION',\n                                         target_type=\"KNOWLEDGE\").delete()\n        # 插入\n        QuerySet(ResourceMapping).bulk_create(\n            [ResourceMapping(source_id=application_id, target_id=knowledge_id, source_type='APPLICATION',\n                             target_type=\"KNOWLEDGE\") for knowledge_id in\n             knowledge_id_list]) if len(knowledge_id_list) > 0 else None\n\n    @staticmethod\n    def get_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id):\n        \"\"\"\n\n        @param application_knowledge_id_list:  当前应用可修改的知识库列表\n        @param knowledge_id_list:              用户修改的知识库列表\n        @param application_id:                 应用id\n        @return:\n        \"\"\"\n        # 当前知识库和应用已关联列表\n        knowledge_application_mapping_list = QuerySet(ResourceMapping).filter(source_id=application_id,\n                                                                              source_type='APPLICATION',\n                                                                              target_type=\"KNOWLEDGE\",\n                                                                              ).exclude(\n            target_id__in=application_knowledge_id_list)\n        edit_knowledge_list = [ResourceMapping(source_id=application_id, target_id=knowledge_id,\n                                               source_type='APPLICATION',\n                                               target_type=\"KNOWLEDGE\")\n                               for knowledge_id in knowledge_id_list]\n        return list(knowledge_application_mapping_list) + edit_knowledge_list\n\n    def speech_to_text(self, instance, debug=True, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            SpeechToTextRequest(data=instance).is_valid(raise_exception=True)\n        application_id = self.data.get('application_id')\n        if debug:\n            application = QuerySet(Application).filter(id=application_id).first()\n        else:\n            application = QuerySet(ApplicationVersion).filter(application_id=application_id).order_by(\n                '-create_time').first()\n        if application.stt_model_enable:\n            model = get_model_instance_by_model_workspace_id(application.stt_model_id, application.workspace_id,\n                                                             **application.stt_model_params_setting)\n            text = model.speech_to_text(instance.get('file'))\n            return text\n\n    def text_to_speech(self, instance, debug=True, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            TextToSpeechRequest(data=instance).is_valid(raise_exception=True)\n        application_id = self.data.get('application_id')\n        if debug:\n            application = QuerySet(Application).filter(id=application_id).first()\n        else:\n            application = QuerySet(ApplicationVersion).filter(application_id=application_id).order_by(\n                '-create_time').first()\n        if application.tts_model_enable:\n            model = get_model_instance_by_model_workspace_id(application.tts_model_id, application.workspace_id,\n                                                             **application.tts_model_params_setting)\n            content = _remove_empty_lines(instance.get('text', ''))\n\n            return model.text_to_speech(content)\n\n    def play_demo_text(self, instance, with_valid=True):\n        text = '你好，这里是语音播放测试'\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            PlayDemoTextRequest(data=instance).is_valid(raise_exception=True)\n        tts_model_id = instance.pop('tts_model_id')\n        model = get_model_instance_by_model_workspace_id(tts_model_id, self.data.get('workspace_id'), **instance)\n        return model.text_to_speech(text)\n"
  },
  {
    "path": "apps/application/serializers/application_access_token.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_access_token.py\n    @date：2025/6/9 17:49\n    @desc:\n\"\"\"\nimport hashlib\n\nimport uuid_utils.compat as uuid\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import ApplicationAccessToken, Application\nfrom common.constants.cache_version import Cache_Version\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.exception.app_exception import AppApiException\n\n\nclass AccessTokenEditSerializer(serializers.Serializer):\n    access_token_reset = serializers.BooleanField(required=False,\n                                                  label=_(\"Reset Token\"))\n    is_active = serializers.BooleanField(required=False, label=_(\"Is it enabled\"))\n    access_num = serializers.IntegerField(required=False, max_value=10000000,\n                                          min_value=0,\n                                          label=_(\"Number of visits\"))\n    white_active = serializers.BooleanField(required=False,\n                                            label=_(\"Whether to enable whitelist\"))\n    white_list = serializers.ListSerializer(required=False, child=serializers.CharField(required=True,\n                                                                                        label=_(\"Whitelist\")),\n                                            label=_(\"Whitelist\")),\n    show_source = serializers.BooleanField(required=False,\n                                           label=_(\"Whether to display knowledge sources\"))\n    show_exec = serializers.BooleanField(required=False,\n                                         label=_(\"Display execution details\"))\n    language = serializers.CharField(required=False, allow_blank=True, allow_null=True,\n                                     label=_(\"language\"))\n    authentication = serializers.BooleanField(default=False, label=\"Do you need authentication\")\n\n    authentication_value = serializers.JSONField(required=False, label=\"Certified value\", default=dict)\n\n\nclass AccessTokenSerializer(serializers.Serializer):\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Application id does not exist'))\n\n    def edit(self, instance):\n        self.is_valid(raise_exception=True)\n        AccessTokenEditSerializer(data=instance).is_valid(raise_exception=True)\n        application_access_token = QuerySet(ApplicationAccessToken).get(\n            application_id=self.data.get('application_id'))\n        if 'is_active' in instance:\n            application_access_token.is_active = instance.get(\"is_active\")\n        if 'access_token_reset' in instance and instance.get('access_token_reset'):\n            application_access_token.access_token = hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24]\n        if 'access_num' in instance and instance.get('access_num') is not None:\n            application_access_token.access_num = instance.get(\"access_num\")\n        if 'white_active' in instance and instance.get('white_active') is not None:\n            application_access_token.white_active = instance.get(\"white_active\")\n        if 'white_list' in instance and instance.get('white_list') is not None:\n            application_access_token.white_list = instance.get('white_list')\n        if 'show_source' in instance and instance.get('show_source') is not None:\n            application_access_token.show_source = instance.get('show_source')\n        if 'show_exec' in instance and instance.get('show_exec') is not None:\n            application_access_token.show_exec = instance.get('show_exec')\n        if 'language' in instance and instance.get('language') is not None:\n            application_access_token.language = instance.get('language')\n        if 'language' not in instance or instance.get('language') is None:\n            application_access_token.language = None\n\n        application_access_token.save()\n        license_is_valid = cache.get(Cache_Version.SYSTEM.get_key(key='license_is_valid'),\n                                     version=Cache_Version.SYSTEM.get_version())\n        if license_is_valid:\n            if instance.get('authentication') is not None and instance.get(\n                    'authentication_value') is not None:\n                application_access_token.authentication = instance.get('authentication')\n                application_access_token.authentication_value = instance.get('authentication_value')\n                application_access_token.save()\n        return self.one(with_valid=False)\n\n    def one(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        application_id = self.data.get(\"application_id\")\n        application_access_token = QuerySet(ApplicationAccessToken).filter(\n            application_id=application_id).first()\n        if application_access_token is None:\n            application_access_token = ApplicationAccessToken(application_id=application_id,\n                                                              access_token=hashlib.md5(\n                                                                  str(uuid.uuid7()).encode()).hexdigest()[\n                                                                           8:24], is_active=True)\n            application_access_token.save()\n        other = {}\n        license_is_valid = cache.get(Cache_Version.SYSTEM.get_key(key='license_is_valid'),\n                                     version=Cache_Version.SYSTEM.get_version())\n        if license_is_valid:\n            other = {'authentication': application_access_token.authentication,\n                     'authentication_value': application_access_token.authentication_value}\n\n        return {'application_id': application_access_token.application_id,\n                'access_token': application_access_token.access_token,\n                \"is_active\": application_access_token.is_active,\n                'access_num': application_access_token.access_num,\n                'white_active': application_access_token.white_active,\n                'white_list': application_access_token.white_list,\n                'show_source': application_access_token.show_source,\n                'show_exec': application_access_token.show_exec,\n                'language': application_access_token.language,\n                **other,\n                }\n"
  },
  {
    "path": "apps/application/serializers/application_api_key.py",
    "content": "import hashlib\n\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import Application\nfrom application.models.application_api_key import ApplicationApiKey\nfrom common.cache_data.application_api_key_cache import get_application_api_key, del_application_api_key\nfrom common.db.search import page_search\nfrom common.exception.app_exception import AppApiException\n\n\nclass ApplicationKeySerializerModel(serializers.ModelSerializer):\n    class Meta:\n        model = ApplicationApiKey\n        fields = \"__all__\"\n\n\nclass EditApplicationKeySerializer(serializers.Serializer):\n    is_active = serializers.BooleanField(required=False, label=_(\"Availability\"))\n\n    allow_cross_domain = serializers.BooleanField(required=False,\n                                                  label=_(\"Is cross-domain allowed\"))\n\n    cross_domain_list = serializers.ListSerializer(required=False,\n                                                   child=serializers.CharField(required=True,\n                                                                               label=_(\"Cross-domain address\")),\n                                                   label=_(\"Cross-domain list\"))\n    is_permanent = serializers.BooleanField(required=False, label=_(\"Is permanent\"))\n    expire_time = serializers.DateTimeField(required=False, allow_null=True, label=_(\"Expiration time\"))\n\n\nclass ApplicationKeySerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n    application_id = serializers.UUIDField(required=True, label=_('application id'))\n    order_by = serializers.CharField(required=False, label=_('order by'), allow_null=True, allow_blank=True)\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Application id does not exist'))\n\n    def generate(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        application_id = self.data.get(\"application_id\")\n        secret_key = 'agent-' + hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()\n        application_api_key = ApplicationApiKey(id=uuid.uuid7(),\n                                                secret_key=secret_key,\n                                                application_id=application_id)\n        application_api_key.save()\n        return ApplicationKeySerializerModel(application_api_key).data\n\n    def page(self, current_page: int, page_size: int, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        application_id = self.data.get(\"application_id\")\n        query_set = QuerySet(ApplicationApiKey).filter(application_id=application_id)\n        order_by = '-create_time' if self.data.get('order_by') is None or self.data.get(\n            'order_by') == '' else self.data.get('order_by')\n        query_set = query_set.order_by(order_by)\n        return page_search(current_page, page_size,\n                           query_set,\n                           post_records_handler=lambda u: ApplicationKeySerializerModel(u).data)\n\n    class Operate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n        application_id = serializers.UUIDField(required=True, label=_('application id'))\n        api_key_id = serializers.UUIDField(required=True, label=_('ApiKeyId'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Application id does not exist'))\n\n        def delete(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            api_key_id = self.data.get(\"api_key_id\")\n            application_id = self.data.get('application_id')\n            application_api_key = QuerySet(ApplicationApiKey).filter(id=api_key_id,\n                                                                     application_id=application_id).first()\n            del_application_api_key(application_api_key.secret_key)\n            application_api_key.delete()\n\n        def edit(self, instance, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                EditApplicationKeySerializer(data=instance).is_valid(raise_exception=True)\n            api_key_id = self.data.get(\"api_key_id\")\n            application_id = self.data.get('application_id')\n            application_api_key = QuerySet(ApplicationApiKey).filter(id=api_key_id,\n                                                                     application_id=application_id).first()\n            if application_api_key is None:\n                raise AppApiException(500, _('APIKey does not exist'))\n            if 'is_active' in instance and instance.get('is_active') is not None:\n                application_api_key.is_active = instance.get('is_active')\n            if 'allow_cross_domain' in instance and instance.get('allow_cross_domain') is not None:\n                application_api_key.allow_cross_domain = instance.get('allow_cross_domain')\n            if 'cross_domain_list' in instance and instance.get('cross_domain_list') is not None:\n                application_api_key.cross_domain_list = instance.get('cross_domain_list')\n            if 'is_permanent' in instance and instance.get('is_permanent') is not None:\n                application_api_key.is_permanent = instance.get('is_permanent')\n                if not application_api_key.is_permanent:\n                    application_api_key.expire_time = instance.get('expire_time')\n                else:\n                    application_api_key.expire_time = timezone.now()\n            application_api_key.save()\n            # 写入缓存\n            get_application_api_key('Bearer ' + application_api_key.secret_key, False)\n            return True\n"
  },
  {
    "path": "apps/application/serializers/application_chat.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_chat.py\n    @date：2025/6/10 11:06\n    @desc:\n\"\"\"\nimport datetime\nimport os\nimport re\nfrom io import BytesIO\nfrom typing import Dict\n\nimport openpyxl\nimport pytz\nfrom django.core import validators\nfrom django.db import models\nfrom django.db.models import QuerySet, Q\nfrom django.http import StreamingHttpResponse\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE\nfrom rest_framework import serializers\n\nfrom application.models import Chat, Application, ChatRecord, ChatSourceChoices\nfrom common.db.search import get_dynamics_model, native_search, native_page_search, native_page_handler\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import get_file_content\nfrom maxkb.conf import PROJECT_DIR\nfrom maxkb.settings import TIME_ZONE, edition\n\n\nclass ApplicationChatResponseSerializers(serializers.Serializer):\n    id = serializers.UUIDField(required=True, label=_(\"chat id\"))\n    abstract = serializers.CharField(required=True, label=_(\"summary\"))\n    chat_user_id = serializers.UUIDField(required=True, label=_(\"Chat User ID\"))\n    chat_user_type = serializers.CharField(required=True, label=_(\"Chat User Type\"))\n    is_deleted = serializers.BooleanField(required=True, label=_(\"Is delete\"))\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    chat_record_count = serializers.IntegerField(required=True, label=_(\"Number of conversations\"))\n    star_num = serializers.IntegerField(required=True, label=_(\"Number of Likes\"))\n    trample_num = serializers.IntegerField(required=True, label=_(\"Number of thumbs-downs\"))\n    mark_sum = serializers.IntegerField(required=True, label=_(\"Number of tags\"))\n\n\nclass ApplicationChatRecordExportRequest(serializers.Serializer):\n    select_ids = serializers.ListField(required=True, label=_(\"Chat ID List\"),\n                                       child=serializers.UUIDField(required=True, label=_(\"Chat ID\")))\n\n\nclass ApplicationChatQuerySerializers(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n    abstract = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_(\"summary\"))\n    username = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_(\"username\"))\n    start_time = serializers.DateField(format='%Y-%m-%d', label=_(\"Start time\"))\n    end_time = serializers.DateField(format='%Y-%m-%d', label=_(\"End time\"))\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    min_star = serializers.IntegerField(required=False, min_value=0,\n                                        label=_(\"Minimum number of likes\"))\n    min_trample = serializers.IntegerField(required=False, min_value=0,\n                                           label=_(\"Minimum number of clicks\"))\n    comparer = serializers.CharField(required=False, label=_(\"Comparator\"), validators=[\n        validators.RegexValidator(regex=re.compile(\"^and|or$\"),\n                                  message=_(\"Only supports and|or\"), code=500)\n    ])\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Application id does not exist'))\n\n    def get_end_time(self):\n        d = datetime.datetime.strptime(self.data.get('end_time'), '%Y-%m-%d').date()\n        naive = datetime.datetime.combine(d, datetime.time.max)\n        return timezone.make_aware(naive, timezone.get_default_timezone())\n\n    def get_start_time(self):\n        d = datetime.datetime.strptime(self.data.get('start_time'), '%Y-%m-%d').date()\n        naive = datetime.datetime.combine(d, datetime.time.min)\n        return timezone.make_aware(naive, timezone.get_default_timezone())\n\n    def get_query_set(self, select_ids=None):\n        end_time = self.get_end_time()\n        start_time = self.get_start_time()\n        query_set = QuerySet(model=get_dynamics_model(\n            {'application_chat.application_id': models.CharField(),\n             'application_chat.abstract': models.CharField(),\n             'application_chat.asker': models.JSONField(),\n             \"star_num\": models.IntegerField(),\n             'trample_num': models.IntegerField(),\n             'comparer': models.CharField(),\n             'application_chat.update_time': models.DateTimeField(),\n             'application_chat.id': models.UUIDField(),\n             'application_chat_record_temp.id': models.UUIDField()}))\n\n        base_query_dict = {'application_chat.application_id': self.data.get(\"application_id\"),\n                           'application_chat.update_time__gte': start_time,\n                           'application_chat.update_time__lte': end_time,\n                           }\n        if 'abstract' in self.data and self.data.get('abstract') is not None:\n            base_query_dict['application_chat.abstract__icontains'] = self.data.get('abstract')\n        if 'username' in self.data and self.data.get('username') is not None:\n            base_query_dict['application_chat.asker__username__icontains'] = self.data.get('username')\n\n        if select_ids is not None and len(select_ids) > 0:\n            base_query_dict['application_chat.id__in'] = select_ids\n        base_condition = Q(**base_query_dict)\n        min_star_query = None\n        min_trample_query = None\n        if 'min_star' in self.data and self.data.get('min_star') is not None:\n            min_star_query = Q(star_num__gte=self.data.get('min_star'))\n        if 'min_trample' in self.data and self.data.get('min_trample') is not None:\n            min_trample_query = Q(trample_num__gte=self.data.get('min_trample'))\n        if min_star_query is not None and min_trample_query is not None:\n            if self.data.get(\n                    'comparer') is not None and self.data.get('comparer') == 'or':\n                condition = base_condition & (min_star_query | min_trample_query)\n            else:\n                condition = base_condition & (min_star_query & min_trample_query)\n        elif min_star_query is not None:\n            condition = base_condition & min_star_query\n        elif min_trample_query is not None:\n            condition = base_condition & min_trample_query\n        else:\n            condition = base_condition\n\n        return {\n            'default_queryset': query_set.filter(condition).order_by(\"-application_chat.update_time\")\n        }\n\n    def list(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        return native_search(self.get_query_set(), select_string=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql',\n                         ('list_application_chat_ee.sql' if ['PE', 'EE'].__contains__(\n                             edition) else 'list_application_chat.sql'))),\n                             with_table_name=False)\n\n    @staticmethod\n    def paragraph_list_to_string(paragraph_list):\n        return \"\\n**********\\n\".join(\n            [f\"{paragraph.get('title')}:\\n{paragraph.get('content')}\" for paragraph in\n             paragraph_list] if paragraph_list is not None else '')\n\n    @staticmethod\n    def to_row(row: Dict):\n        details = row.get('details') or {}\n        padding_problem_text = ' '.join((node.get(\"answer\", \"\") or \"\") for key, node in details.items() if\n                                        node.get(\"type\") == 'question-node')\n        search_dataset_node_list = [(key, node) for key, node in details.items() if\n                                    node.get(\"type\") == 'search-dataset-node' or node.get(\n                                        \"step_type\") == 'search_step' or node.get(\"type\") == 'search-knowledge-node']\n        reference_paragraph_len = '\\n'.join([str(len(node.get('paragraph_list',\n                                                              []))) if key == 'search_step' else node.get(\n            'name') + ':' + str(\n            len(node.get('paragraph_list', [])) if node.get('paragraph_list', []) is not None else '0') for\n                                             key, node in search_dataset_node_list])\n        reference_paragraph = '\\n----------\\n'.join(\n            [ApplicationChatQuerySerializers.paragraph_list_to_string(node.get('paragraph_list',\n                                                                               [])) if key == 'search_step' else node.get(\n                'name') + ':\\n' + ApplicationChatQuerySerializers.paragraph_list_to_string(node.get('paragraph_list',\n                                                                                                    [])) for\n             key, node in search_dataset_node_list])\n        improve_paragraph_list = row.get('improve_paragraph_list') or []\n        vote_status_map = {'-1': '未投票', '0': '赞同', '1': '反对'}\n        vote_reason_map = {'accurate': gettext('accurate'), 'complete': gettext('complete'),\n                           'inaccurate': gettext('inaccurate'), 'incomplete': gettext('incomplete'),\n                           'other': gettext('Other'), }\n        return [str(row.get('chat_id')), row.get('abstract'), row.get('problem_text'), padding_problem_text,\n                row.get('answer_text'), vote_status_map.get(row.get('vote_status')),\n                vote_reason_map.get(row.get('vote_reason')),\n                row.get('vote_other_content'),\n                reference_paragraph_len,\n                reference_paragraph,\n                \"\\n\".join([\n                    f\"{improve_paragraph_list[index].get('title')}\\n{improve_paragraph_list[index].get('content')}\"\n                    for index in range(len(improve_paragraph_list))]),\n                row.get('asker').get('username'),\n                (row.get('message_tokens') or 0) + (row.get('answer_tokens') or 0),\n                row.get('ip_address') or '-',\n                get_source_display(row.get('source')),\n                row.get('run_time'),\n                str(row.get('create_time').astimezone(pytz.timezone(TIME_ZONE)).strftime('%Y-%m-%d %H:%M:%S')\n                    if row.get('create_time') is not None else None)]\n\n    @staticmethod\n    def reset_value(value):\n        if isinstance(value, str):\n            value = re.sub(ILLEGAL_CHARACTERS_RE, '', value)\n        if isinstance(value, datetime.datetime):\n            eastern = pytz.timezone(TIME_ZONE)\n            c = datetime.timezone(eastern._utcoffset)\n            value = value.astimezone(c)\n        return value\n\n    def export(self, data, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        ApplicationChatRecordExportRequest(data=data).is_valid(raise_exception=True)\n\n        def stream_response():\n            workbook = openpyxl.Workbook(write_only=True)\n            worksheet = workbook.create_sheet(title='Sheet1')\n            current_page = 1\n            page_size = 500\n            headers = [gettext('Conversation ID'), gettext('summary'), gettext('User Questions'),\n                       gettext('Problem after optimization'),\n                       gettext('answer'), gettext('User feedback'), gettext('Feedback reason'),\n                       gettext('Other reason content'),\n                       gettext('Reference segment number'),\n                       gettext('Section title + content'),\n                       gettext('Annotation'), gettext('USER'), gettext('Consuming tokens'),\n                       gettext('Ip Address'), gettext('source'),\n                       gettext('Time consumed (s)'),\n                       gettext('Question Time')]\n            worksheet.append(headers)\n            for data_list in native_page_handler(page_size, self.get_query_set(data.get('select_ids')),\n                                                 primary_key='application_chat_record_temp.id',\n                                                 primary_queryset='default_queryset',\n                                                 get_primary_value=lambda item: item.get('id'),\n                                                 select_string=get_file_content(\n                                                     os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql',\n                                                                  ('export_application_chat_ee.sql' if ['PE',\n                                                                                                        'EE'].__contains__(\n                                                                      edition) else 'export_application_chat.sql'))),\n                                                 with_table_name=False):\n\n                for item in data_list:\n                    row = [self.reset_value(v) for v in self.to_row(item)]\n                    worksheet.append(row)\n                current_page = current_page + 1\n            output = BytesIO()\n            workbook.save(output)\n            output.seek(0)\n            yield output.getvalue()\n            output.close()\n            workbook.close()\n\n        response = StreamingHttpResponse(stream_response(),\n                                         content_type='application/vnd.open.xmlformats-officedocument.spreadsheetml.sheet')\n        response['Content-Disposition'] = 'attachment; filename=\"data.xlsx\"'\n        return response\n\n    def page(self, current_page: int, page_size: int, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        return native_page_search(current_page, page_size, self.get_query_set(), select_string=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql',\n                         ('list_application_chat_ee.sql' if ['PE', 'EE'].__contains__(\n                             edition) else 'list_application_chat.sql'))),\n                                  with_table_name=False)\n\n\nclass ChatCountSerializer(serializers.Serializer):\n    chat_id = serializers.UUIDField(required=True, label=_(\"Conversation ID\"))\n\n    def get_query_set(self):\n        return QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id'))\n\n    def update_chat(self):\n        self.is_valid(raise_exception=True)\n        count_chat_record = native_search(self.get_query_set(), get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql', 'count_chat_record.sql')), with_search_one=True)\n        QuerySet(Chat).filter(id=self.data.get('chat_id')).update(star_num=count_chat_record.get('star_num', 0) or 0,\n                                                                  trample_num=count_chat_record.get('trample_num',\n                                                                                                    0) or 0,\n                                                                  chat_record_count=count_chat_record.get(\n                                                                      'chat_record_count', 0) or 0,\n                                                                  mark_sum=count_chat_record.get('mark_sum', 0) or 0)\n        return True\n\n\ndef get_source_display(source):\n    if not source or not isinstance(source, dict) or 'type' not in source:\n        return '-'\n    source_type = source.get('type')\n\n    # 定义映射关系\n    source_mapping = {\n        ChatSourceChoices.ONLINE.value: gettext('Online Usage'),\n        ChatSourceChoices.API_CALL.value: gettext('API Call'),\n        ChatSourceChoices.ENTERPRISE_WECHAT.value: gettext('Enterprise WeChat'),\n        ChatSourceChoices.WECHAT_PUBLIC_ACCOUNT.value: gettext('WeChat Public Account'),\n        ChatSourceChoices.LARK.value: gettext('Lark'),\n        ChatSourceChoices.DINGTALK.value: gettext('DingTalk'),\n        ChatSourceChoices.ENTERPRISE_WECHAT_ROBOT.value: gettext('Enterprise WeChat Robot'),\n        ChatSourceChoices.TRIGGER.value: gettext('Trigger'),\n        ChatSourceChoices.SLACK.value: gettext('Slack'),\n    }\n\n    return source_mapping.get(source_type, str(source_type))\n"
  },
  {
    "path": "apps/application/serializers/application_chat_link.py",
    "content": "\"\"\"\n    @project: MaxKB\n    @Author: niu\n    @file: application_chat_link.py\n    @date: 2026/2/9 10:50\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import Chat, ChatShareLink, ShareLinkType, ChatRecord\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.chat_link_code import UUIDEncoder\nimport uuid_utils.compat as uuid\n\n\nclass ShareChatRecordModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = ChatRecord\n        fields = ['id', 'problem_text', 'answer_text', 'answer_text_list', 'create_time']\n\nclass ChatRecordShareLinkRequestSerializer(serializers.Serializer):\n    chat_record_ids = serializers.ListSerializer(\n        child=serializers.UUIDField(),\n        required=False,\n        allow_empty=False,\n        label=_(\"Chat record IDs\")\n    )\n    is_current_all = serializers.BooleanField(required=False, default=False)\n\n    def validate(self, attrs):\n        if not attrs.get('is_current_all') and not attrs.get('chat_record_ids'):\n            raise serializers.ValidationError(_('Chat record ids can not be empty'))\n        return attrs\n\nclass ChatRecordShareLinkSerializer(serializers.Serializer):\n    chat_id = serializers.UUIDField(required=True, label=_(\"Conversation ID\"))\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    user_id = serializers.UUIDField(required=False, label=_(\"User ID\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        chat_id = self.data.get('chat_id')\n        application_id = self.data.get('application_id')\n\n        chat_query_set = Chat.objects.filter(id=chat_id, application_id=application_id, is_deleted=False)\n        if not chat_query_set.exists():\n            raise AppApiException(500, _('Chat id does not exist'))\n\n    def generate_link(self, instance, with_valid=True):\n        if with_valid:\n            request_serializer = ChatRecordShareLinkRequestSerializer(data=instance)\n            request_serializer.is_valid(raise_exception=True)\n            self.is_valid(raise_exception=True)\n            if not instance.get('is_current_all', False):\n                chat_record_ids: list[str] = instance.get('chat_record_ids')\n\n                record_count = ChatRecord.objects.filter(id__in=chat_record_ids, chat_id=self.data.get('chat_id')).count()\n                if record_count != len(chat_record_ids):\n                    raise AppApiException(500, _('Invalid chat record ids'))\n        chat_id = self.data.get('chat_id')\n        application_id = self.data.get('application_id')\n        user_id = self.data.get('user_id')\n\n        is_current_all = instance.get('is_current_all', False)\n        if is_current_all:\n            sorted_ids = list(\n                ChatRecord.objects.filter(chat_id=chat_id).order_by('create_time').values_list('id',flat=True)\n            )\n        else:\n            chat_record_ids: list[str] = instance.get('chat_record_ids')\n            sorted_ids = list(ChatRecord.objects.filter(id__in=chat_record_ids).order_by('create_time').values_list('id',flat=True))\n\n        existing = ChatShareLink.objects.filter(\n            chat_id=chat_id, application_id=application_id,\n            share_type=ShareLinkType.PUBLIC,\n            user_id=user_id,\n            chat_record_ids=sorted_ids\n        ).first()\n\n        if existing:\n            return {'link': UUIDEncoder.encode(existing.id)}\n\n        chat_share_link_model = ChatShareLink(\n            id=uuid.uuid7(),\n            chat_id=chat_id,\n            application_id=application_id,\n            share_type=ShareLinkType.PUBLIC,\n            user_id=user_id,\n            chat_record_ids=sorted_ids\n        )\n        chat_share_link_model.save()\n\n        link = UUIDEncoder.encode(chat_share_link_model.id)\n\n        return {'link': link}\n\n\nclass ChatShareLinkDetailSerializer(serializers.Serializer):\n    link = serializers.CharField(required=True, label=_(\"Link\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n\n        link = self.data.get('link')\n        share_link_id = UUIDEncoder.decode_to_str(link)\n\n        share_link_query_set = ChatShareLink.objects.filter(id=share_link_id).first()\n        if not share_link_query_set:\n            raise AppApiException(500, _('Share link does not exist'))\n        if share_link_query_set.chat.is_deleted:\n            raise AppApiException(500, _('Chat has been deleted'))\n\n        return share_link_query_set\n\n    def get_record_list(self):\n        share_link_model = self.is_valid(raise_exception=True)\n\n        chat_record_model_list = ChatRecord.objects.filter(id__in=share_link_model.chat_record_ids,\n                                                           chat_id=share_link_model.chat_id).order_by('create_time')\n\n        abstract = Chat.objects.filter(\n            id=share_link_model.chat_id\n        ).values_list('abstract', flat=True).first()\n        chat_record_list = ShareChatRecordModelSerializer(chat_record_model_list, many=True).data\n\n        return {\n            'abstract': abstract,\n            'chat_record_list': chat_record_list\n        }\n"
  },
  {
    "path": "apps/application/serializers/application_chat_record.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_chat_record.py\n    @date：2025/6/10 15:10\n    @desc:\n\"\"\"\nfrom functools import reduce\nfrom typing import Dict\n\nimport uuid_utils.compat as uuid\nfrom django.db import transaction\nfrom django.db.models import QuerySet\nfrom django.db.models.aggregates import Max, Min\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom rest_framework import serializers\nfrom rest_framework.utils.formatting import lazy_format\n\nfrom application.models import ChatRecord, ApplicationAccessToken, Application\nfrom application.serializers.application_chat import ChatCountSerializer\nfrom application.serializers.common import ChatInfo\nfrom common.auth.authentication import get_is_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.db.search import page_search\nfrom common.exception.app_exception import AppApiException, AppUnauthorizedFailed\nfrom common.utils.common import post\nfrom knowledge.models import Paragraph, Document, Problem, ProblemParagraphMapping, Knowledge\nfrom knowledge.serializers.common import get_embedding_model_id_by_knowledge_id, update_document_char_length\nfrom knowledge.serializers.paragraph import ParagraphSerializers\nfrom knowledge.task.embedding import embedding_by_paragraph, embedding_by_paragraph_list\n\n\nclass ChatRecordSerializerModel(serializers.ModelSerializer):\n    class Meta:\n        model = ChatRecord\n        fields = ['id', 'chat_id', 'vote_status','vote_reason','vote_other_content', 'problem_text', 'answer_text',\n                  'message_tokens', 'answer_tokens', 'const', 'improve_paragraph_id_list', 'run_time', 'index',\n                  'answer_text_list',\n                  'create_time', 'update_time']\n\n\nclass ChatRecordOperateSerializer(serializers.Serializer):\n    chat_id = serializers.UUIDField(required=True, label=_(\"Conversation ID\"))\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    chat_record_id = serializers.UUIDField(required=True, label=_(\"Conversation record id\"))\n\n    def is_valid(self, *, debug=False, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Application id does not exist'))\n        application_access_token = QuerySet(ApplicationAccessToken).filter(\n            application_id=self.data.get('application_id')).first()\n        if application_access_token is None:\n            raise AppApiException(500, gettext('Application authentication information does not exist'))\n\n    def get_chat_record(self):\n        chat_record_id = self.data.get('chat_record_id')\n        chat_id = self.data.get('chat_id')\n        chat_info: ChatInfo = ChatInfo.get_cache(chat_id)\n        if chat_info is not None:\n            chat_record_list = [chat_record for chat_record in chat_info.chat_record_list if\n                                str(chat_record.id) == str(chat_record_id)]\n            if chat_record_list is not None and len(chat_record_list):\n                return chat_record_list[-1]\n        return QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first()\n\n    def one(self, debug):\n        self.is_valid(debug=debug, raise_exception=True)\n        chat_record = self.get_chat_record()\n        if chat_record is None:\n            raise AppApiException(500, gettext(\"Conversation does not exist\"))\n        application_access_token = QuerySet(ApplicationAccessToken).filter(\n            application_id=self.data.get('application_id')).first()\n        show_source = False\n        show_exec = False\n        if application_access_token is not None:\n            show_exec = application_access_token.show_exec\n            show_source = application_access_token.show_source\n        return ApplicationChatRecordQuerySerializers.reset_chat_record(\n            chat_record, True if debug else show_source, True if debug else show_exec)\n\n\nclass ApplicationChatRecordQuerySerializers(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    chat_id = serializers.UUIDField(required=True, label=_(\"Chat ID\"))\n    order_asc = serializers.BooleanField(required=False, allow_null=True, label=_(\"Is it in order\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Application id does not exist'))\n\n    def list(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id'))\n        order_by = 'create_time' if self.data.get('order_asc') is None or self.data.get(\n            'order_asc') else '-create_time'\n        return [ChatRecordSerializerModel(chat_record).data for chat_record in\n                QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')).order_by(order_by)]\n\n    @staticmethod\n    def get_loop_workflow_node(details):\n        result = []\n        for item in details.values():\n            if item.get('type') == 'loop-node':\n                for loop_item in item.get('loop_node_data') or []:\n                    for inner_item in loop_item.values():\n                        result.append(inner_item)\n        return result\n\n    @staticmethod\n    def reset_chat_record(chat_record, show_source, show_exec):\n        knowledge_list = []\n        paragraph_list = []\n        if 'search_step' in chat_record.details and chat_record.details.get('search_step').get(\n                'paragraph_list') is not None:\n            paragraph_list = chat_record.details.get('search_step').get(\n                'paragraph_list')\n\n        for item in [*chat_record.details.values(),\n                     *ApplicationChatRecordQuerySerializers.get_loop_workflow_node(chat_record.details)]:\n            if item.get('type') == 'search-knowledge-node' and item.get('show_knowledge', False):\n                paragraph_list = paragraph_list + (item.get(\n                    'paragraph_list') or [])\n\n            if item.get('type') == 'reranker-node' and item.get('show_knowledge', False):\n                paragraph_list = paragraph_list + [rl.get('metadata') for rl in (item.get('result_list') or []) if\n                                                   'document_id' in (rl.get('metadata') or {}) and 'knowledge_id' in (\n                                                           rl.get(\n                                                               'metadata') or {})]\n        paragraph_list = list({p.get('id'): p for p in paragraph_list}.values())\n        knowledge_list = knowledge_list + [{'id': knowledge_id, **knowledge} for knowledge_id, knowledge in\n                                           reduce(lambda x, y: {**x, **y},\n                                                  [{row.get(\n                                                      'knowledge_id'): {'knowledge_name': row.get(\n                                                      \"knowledge_name\"),\n                                                      'knowledge_type': row.get('knowledge_type')}} for\n                                                      row in\n                                                      paragraph_list],\n                                                  {}).items()]\n\n        if len(chat_record.improve_paragraph_id_list) > 0:\n            paragraph_model_list = QuerySet(Paragraph).filter(id__in=chat_record.improve_paragraph_id_list)\n            if len(paragraph_model_list) < len(chat_record.improve_paragraph_id_list):\n                paragraph_model_id_list = [str(p.id) for p in paragraph_model_list]\n                chat_record.improve_paragraph_id_list = list(\n                    filter(lambda p_id: paragraph_model_id_list.__contains__(p_id),\n                           chat_record.improve_paragraph_id_list))\n                chat_record.save()\n        show_source_dict = {'knowledge_list': knowledge_list,\n                            'paragraph_list': paragraph_list, }\n        show_exec_dict = {'execution_details': [chat_record.details[key] for key in chat_record.details if\n                                                (True if show_exec else chat_record.details[key].get(\n                                                    'type') == 'start-node')]}\n        return {\n            **ChatRecordSerializerModel(chat_record).data,\n            'padding_problem_text': chat_record.details.get('problem_padding').get(\n                'padding_problem_text') if 'problem_padding' in chat_record.details else None,\n            **(show_source_dict if show_source else {}),\n            **(show_exec_dict if show_exec else show_exec_dict)\n        }\n\n    def page(self, current_page: int, page_size: int, with_valid=True, show_source=None, show_exec=None):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        order_by = '-create_time' if self.data.get('order_asc') is None or self.data.get(\n            'order_asc') else 'create_time'\n        if show_source is None:\n            show_source = True\n        if show_exec is None:\n            show_exec = True\n        page = page_search(current_page, page_size,\n                           QuerySet(ChatRecord).filter(chat_id=self.data.get('chat_id')).order_by(order_by),\n                           post_records_handler=lambda chat_record: self.reset_chat_record(chat_record, show_source,\n                                                                                           show_exec))\n        return page\n\n\nclass ParagraphModel(serializers.ModelSerializer):\n    class Meta:\n        model = Paragraph\n        fields = \"__all__\"\n\n\nclass ChatRecordImproveSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n\n    chat_id = serializers.UUIDField(required=True, label=_(\"Conversation ID\"))\n\n    chat_record_id = serializers.UUIDField(required=True,\n                                           label=_(\"Conversation record id\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Application id does not exist'))\n\n    def get(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        chat_record_id = self.data.get('chat_record_id')\n        chat_id = self.data.get('chat_id')\n        chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first()\n        if chat_record is None:\n            raise AppApiException(500, gettext('Conversation record does not exist'))\n        if chat_record.improve_paragraph_id_list is None or len(chat_record.improve_paragraph_id_list) == 0:\n            return []\n\n        paragraph_model_list = QuerySet(Paragraph).filter(id__in=chat_record.improve_paragraph_id_list)\n        if len(paragraph_model_list) < len(chat_record.improve_paragraph_id_list):\n            paragraph_model_id_list = [str(p.id) for p in paragraph_model_list]\n            chat_record.improve_paragraph_id_list = list(\n                filter(lambda p_id: paragraph_model_id_list.__contains__(p_id),\n                       chat_record.improve_paragraph_id_list))\n            chat_record.save()\n        return [ParagraphModel(p).data for p in paragraph_model_list]\n\n\nclass ApplicationChatRecordImproveInstanceSerializer(serializers.Serializer):\n    title = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True,\n                                  label=_(\"Section title\"))\n    content = serializers.CharField(required=True, label=_(\"Paragraph content\"))\n\n    problem_text = serializers.CharField(required=False, max_length=256, allow_null=True, allow_blank=True,\n                                         label=_(\"question\"))\n\n\nclass ApplicationChatRecordAddKnowledgeSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    knowledge_id = serializers.UUIDField(required=True, label=_(\"Knowledge base id\"))\n    document_id = serializers.UUIDField(required=True, label=_(\"Document id\"))\n    chat_ids = serializers.ListSerializer(child=serializers.UUIDField(), required=True,\n                                          label=_(\"Conversation ID\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Application id does not exist'))\n        if not Document.objects.filter(id=self.data['document_id'], knowledge_id=self.data['knowledge_id']).exists():\n            raise AppApiException(500, gettext(\"The document id is incorrect\"))\n\n    @staticmethod\n    def post_embedding_paragraph(paragraph_ids, knowledge_id):\n        model_id = get_embedding_model_id_by_knowledge_id(knowledge_id)\n        embedding_by_paragraph_list(paragraph_ids, model_id)\n\n    @post(post_function=post_embedding_paragraph)\n    @transaction.atomic\n    def post_improve(self, instance: Dict, request=None, scope='WORKSPACE', with_valid=True):\n        if with_valid:\n            ApplicationChatRecordAddKnowledgeSerializer(data=instance).is_valid(raise_exception=True)\n        self.is_valid(raise_exception=True)\n        if scope == 'WORKSPACE':\n            is_permission = get_is_permissions(request=request, workspace_id=self.data.get('workspace_id'),\n                                               knowledge_id=self.data.get(\"knowledge_id\"))(\n                PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n                PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n                RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                ViewPermission([RoleConstants.USER.get_workspace_role()],\n                               [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                               CompareConstants.AND),\n            )\n        else:\n            is_permission = get_is_permissions(request=request, workspace_id=self.data.get('workspace_id'),\n                                               knowledge_id=self.data.get(\"knowledge_id\"))(\n                PermissionConstants.RESOURCE_KNOWLEDGE_DOCUMENT_EDIT, RoleConstants.ADMIN\n            )\n        if not is_permission:\n            raise AppUnauthorizedFailed(403, gettext('No permission to access'))\n\n        chat_ids = instance['chat_ids']\n        document_id = instance['document_id']\n        knowledge_id = instance['knowledge_id']\n\n        # 获取所有聊天记录\n        chat_record_list = list(ChatRecord.objects.filter(chat_id__in=chat_ids))\n        if len(chat_record_list) < len(chat_ids):\n            raise AppApiException(500, gettext(\"Conversation records that do not exist\"))\n\n        # 批量创建段落和问题映射\n        paragraphs = []\n        paragraph_ids = []\n        problem_paragraph_mappings = []\n        for chat_record in chat_record_list:\n            paragraph = Paragraph(\n                id=uuid.uuid7(),\n                document_id=document_id,\n                content=chat_record.answer_text,\n                knowledge_id=knowledge_id,\n                title=chat_record.problem_text\n            )\n            problem, _ = Problem.objects.get_or_create(content=chat_record.problem_text, knowledge_id=knowledge_id)\n            problem_paragraph_mapping = ProblemParagraphMapping(\n                id=uuid.uuid7(),\n                knowledge_id=knowledge_id,\n                document_id=document_id,\n                problem_id=problem.id,\n                paragraph_id=paragraph.id\n            )\n            paragraphs.append(paragraph)\n            paragraph_ids.append(paragraph.id)\n            problem_paragraph_mappings.append(problem_paragraph_mapping)\n            chat_record.improve_paragraph_id_list.append(paragraph.id)\n\n        # 处理段落位置\n        self.prepend_paragraphs(document_id, paragraphs)\n\n        # 批量创建新段落和问题映射\n        Paragraph.objects.bulk_create(paragraphs)\n        ProblemParagraphMapping.objects.bulk_create(problem_paragraph_mappings)\n\n        # 批量保存聊天记录\n        ChatRecord.objects.bulk_update(chat_record_list, ['improve_paragraph_id_list'])\n        update_document_char_length(document_id)\n        for chat_id in chat_ids:\n            ChatCountSerializer(data={'chat_id': chat_id}).update_chat()\n\n        return paragraph_ids, knowledge_id\n\n    @staticmethod\n    def prepend_paragraphs(document_id, paragraphs):\n        # 获取所有现有段落\n        existing_paragraphs = list(Paragraph.objects.filter(\n            document_id=document_id\n        ).order_by('position'))\n\n        # 计算新段落数量\n        new_count = len(paragraphs)\n\n        # 如果已有段落，需要重新调整所有段落的位置\n        if existing_paragraphs:\n            # 为现有段落重新分配位置，从新段落数量+1开始\n            for i, existing_paragraph in enumerate(existing_paragraphs):\n                existing_paragraph.position = new_count + i + 1\n\n            # 批量更新现有段落位置\n            if existing_paragraphs:\n                Paragraph.objects.bulk_update(existing_paragraphs, ['position'])\n\n        # 为新段落分配位置，从1开始\n        for i, paragraph in enumerate(paragraphs):\n            paragraph.position = i + 1\n\n\nclass ApplicationChatRecordImproveSerializer(serializers.Serializer):\n    chat_id = serializers.UUIDField(required=True, label=_(\"Conversation ID\"))\n\n    chat_record_id = serializers.UUIDField(required=True,\n                                           label=_(\"Conversation record id\"))\n\n    knowledge_id = serializers.UUIDField(required=True, label=_(\"Knowledge base id\"))\n\n    document_id = serializers.UUIDField(required=True, label=_(\"Document id\"))\n    application_id = serializers.UUIDField(required=True, label=_(\"Application id\"))\n\n    workspace_id = serializers.CharField(required=True, label=_(\"Workspace ID\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Application id does not exist'))\n\n        query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Knowledge id does not exist'))\n\n        if not QuerySet(Document).filter(id=self.data.get('document_id'),\n                                         knowledge_id=self.data.get('knowledge_id')).exists():\n            raise AppApiException(500, gettext(\"The document id is incorrect\"))\n\n    @staticmethod\n    def post_embedding_paragraph(chat_record, paragraph_id, knowledge_id):\n        model_id = get_embedding_model_id_by_knowledge_id(knowledge_id)\n        # 发送向量化事件\n        embedding_by_paragraph(paragraph_id, model_id)\n        return chat_record\n\n    @post(post_function=post_embedding_paragraph)\n    @transaction.atomic\n    def improve(self, instance: Dict, request=None, scope='WORKSPACE', with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        if scope == 'WORKSPACE':\n            is_permission = get_is_permissions(request, workspace_id=self.data.get('workspace_id'),\n                                               knowledge_id=self.data.get(\"knowledge_id\"))(\n                PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n                PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n                RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                ViewPermission([RoleConstants.USER.get_workspace_role()],\n                               [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                               CompareConstants.AND),\n            )\n        else:\n            is_permission = get_is_permissions(request, workspace_id=self.data.get('workspace_id'),\n                                               knowledge_id=self.data.get(\"knowledge_id\"))(\n                PermissionConstants.RESOURCE_KNOWLEDGE_DOCUMENT_EDIT, RoleConstants.ADMIN\n            )\n        if not is_permission:\n            raise AppUnauthorizedFailed(403, gettext('No permission to access'))\n        ApplicationChatRecordImproveInstanceSerializer(data=instance).is_valid(raise_exception=True)\n        chat_record_id = self.data.get('chat_record_id')\n        chat_id = self.data.get('chat_id')\n        chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first()\n        if chat_record is None:\n            raise AppApiException(500, gettext('Conversation record does not exist'))\n\n        document_id = self.data.get(\"document_id\")\n        knowledge_id = self.data.get(\"knowledge_id\")\n        max_position = Paragraph.objects.filter(document_id=document_id).aggregate(\n            max_position=Max('position')\n        )['max_position'] or 0\n        paragraph = Paragraph(\n            id=uuid.uuid7(),\n            document_id=document_id,\n            content=instance.get(\"content\"),\n            knowledge_id=knowledge_id,\n            title=instance.get(\"title\") if 'title' in instance else '',\n            position=max_position + 1\n        )\n        problem_text = instance.get('problem_text') if instance.get(\n            'problem_text') is not None else chat_record.problem_text\n        problem, _ = QuerySet(Problem).get_or_create(content=problem_text, knowledge_id=knowledge_id)\n        problem_paragraph_mapping = ProblemParagraphMapping(id=uuid.uuid7(), knowledge_id=knowledge_id,\n                                                            document_id=document_id,\n                                                            problem_id=problem.id,\n                                                            paragraph_id=paragraph.id)\n        # 插入段落\n        paragraph.save()\n        # 插入关联问题\n        problem_paragraph_mapping.save()\n        chat_record.improve_paragraph_id_list.append(paragraph.id)\n        update_document_char_length(document_id)\n        # 添加标注\n        chat_record.save()\n        ChatCountSerializer(data={'chat_id': chat_id}).update_chat()\n        return ChatRecordSerializerModel(chat_record).data, paragraph.id, knowledge_id\n\n    class Operate(serializers.Serializer):\n        chat_id = serializers.UUIDField(required=True, label=_(\"Conversation ID\"))\n\n        chat_record_id = serializers.UUIDField(required=True,\n                                               label=_(\"Conversation record id\"))\n\n        knowledge_id = serializers.UUIDField(required=True, label=_(\"Knowledge base id\"))\n\n        document_id = serializers.UUIDField(required=True, label=_(\"Document id\"))\n\n        paragraph_id = serializers.UUIDField(required=True, label=_(\"Paragraph id\"))\n\n        workspace_id = serializers.CharField(required=True, label=_(\"Workspace ID\"))\n\n        def delete(self, request=None, scope='WORKSPACE', with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                if scope == 'WORKSPACE':\n                    is_permission = get_is_permissions(request=request, workspace_id=self.data.get('workspace_id'),\n                                                       knowledge_id=self.data.get(\"knowledge_id\"))(\n                        PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n                        PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n                        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                                       CompareConstants.AND),\n                    )\n                else:\n                    is_permission = get_is_permissions(request=request, workspace_id=self.data.get('workspace_id'),\n                                                       knowledge_id=self.data.get(\"knowledge_id\"))(\n                        PermissionConstants.RESOURCE_KNOWLEDGE_DOCUMENT_EDIT, RoleConstants.ADMIN\n                    )\n\n                if not is_permission:\n                    raise AppUnauthorizedFailed(403, gettext('No permission to access'))\n\n            workspace_id = self.data.get('workspace_id')\n            chat_record_id = self.data.get('chat_record_id')\n            chat_id = self.data.get('chat_id')\n            knowledge_id = self.data.get('knowledge_id')\n            document_id = self.data.get('document_id')\n            paragraph_id = self.data.get('paragraph_id')\n            chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_id).first()\n            if chat_record is None:\n                raise AppApiException(500, gettext('Conversation record does not exist'))\n            if not chat_record.improve_paragraph_id_list.__contains__(uuid.UUID(paragraph_id)):\n                message = lazy_format(\n                    gettext(\n                        'The paragraph id is wrong. The current conversation record does not exist. [{paragraph_id}] paragraph id'),\n                    paragraph_id=paragraph_id)\n                raise AppApiException(500, message.__str__())\n            chat_record.improve_paragraph_id_list = [row for row in chat_record.improve_paragraph_id_list if\n                                                     str(row) != paragraph_id]\n            chat_record.save()\n            o = ParagraphSerializers.Operate(\n                data={\"workspace_id\": workspace_id, \"knowledge_id\": knowledge_id, 'document_id': document_id,\n                      \"paragraph_id\": paragraph_id})\n            o.is_valid(raise_exception=True)\n            o.delete()\n            return True\n"
  },
  {
    "path": "apps/application/serializers/application_folder.py",
    "content": "from rest_framework import serializers\n\nfrom application.models import ApplicationFolder\n\n\nclass ApplicationFolderTreeSerializer(serializers.ModelSerializer):\n    children = serializers.SerializerMethodField()\n\n    class Meta:\n        model = ApplicationFolder\n        fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id', 'children','create_time','update_time']\n\n    def get_children(self, obj):\n        return ApplicationFolderTreeSerializer(obj.get_children(), many=True).data\n\n\nclass ApplicationFolderFlatSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = ApplicationFolder\n        fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id']\n"
  },
  {
    "path": "apps/application/serializers/application_stats.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_stats.py\n    @date：2025/6/9 20:34\n    @desc:\n\"\"\"\nimport datetime\nimport os\nfrom typing import Dict, List\n\nfrom django.db import models\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom django.utils import timezone\nfrom rest_framework import serializers\n\nfrom application.models import ApplicationChatUserStats, Application\nfrom common.db.search import native_search, get_dynamics_model\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import get_file_content\nfrom maxkb.conf import PROJECT_DIR\nfrom maxkb.settings import edition\n\n\nclass ApplicationStatsSerializer(serializers.Serializer):\n    chat_record_count = serializers.IntegerField(required=True, label=_(\"Number of conversations\"))\n    customer_added_count = serializers.IntegerField(required=True, label=_(\"Number of new users\"))\n    customer_num = serializers.IntegerField(required=True, label=_(\"Total number of users\"))\n    day = serializers.CharField(required=True, label=_(\"date\"))\n    star_num = serializers.IntegerField(required=True, label=_(\"Number of Likes\"))\n    tokens_num = serializers.IntegerField(required=True, label=_(\"Tokens consumption\"))\n    trample_num = serializers.IntegerField(required=True, label=_(\"Number of thumbs-downs\"))\n\n\nclass ApplicationStatisticsSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    start_time = serializers.DateField(format='%Y-%m-%d', label=_(\"Start time\"))\n    end_time = serializers.DateField(format='%Y-%m-%d', label=_(\"End time\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Application id does not exist'))\n\n    def get_end_time(self):\n        d = datetime.datetime.strptime(self.data.get('end_time'), '%Y-%m-%d').date()\n        naive = datetime.datetime.combine(d, datetime.time.max)\n        return timezone.make_aware(naive, timezone.get_default_timezone())\n\n    def get_start_time(self):\n        d = datetime.datetime.strptime(self.data.get('start_time'), '%Y-%m-%d').date()\n        naive = datetime.datetime.combine(d, datetime.time.min)\n        return timezone.make_aware(naive, timezone.get_default_timezone())\n\n    def get_customer_count_trend(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        start_time = self.get_start_time()\n        end_time = self.get_end_time()\n        return native_search(\n            {'default_sql': QuerySet(ApplicationChatUserStats).filter(\n                application_id=self.data.get('application_id'),\n                create_time__gte=start_time,\n                create_time__lte=end_time)},\n            select_string=get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql', 'customer_count_trend.sql')))\n\n    def get_chat_record_aggregate_trend(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        start_time = self.get_start_time()\n        end_time = self.get_end_time()\n        chat_record_aggregate_trend = native_search(\n            {'default_sql': QuerySet(model=get_dynamics_model(\n                {'application_chat.application_id': models.UUIDField(),\n                 'application_chat_record.create_time': models.DateTimeField()})).filter(\n                **{'application_chat.application_id': self.data.get('application_id'),\n                   'application_chat_record.create_time__gte': start_time,\n                   'application_chat_record.create_time__lte': end_time}\n            )},\n            select_string=get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql', 'chat_record_count_trend.sql')))\n        customer_count_trend = self.get_customer_count_trend(with_valid=False)\n        return self.merge_customer_chat_record(chat_record_aggregate_trend, customer_count_trend)\n\n    def merge_customer_chat_record(self, chat_record_aggregate_trend: List[Dict], customer_count_trend: List[Dict]):\n\n        return [{**self.find(chat_record_aggregate_trend, lambda c: c.get('day').strftime('%Y-%m-%d') == day,\n                             {'star_num': 0, 'trample_num': 0, 'tokens_num': 0, 'chat_record_count': 0,\n                              'customer_num': 0,\n                              'day': day}),\n                 **self.find(customer_count_trend, lambda c: c.get('day').strftime('%Y-%m-%d') == day,\n                             {'customer_added_count': 0})}\n                for\n                day in\n                self.get_days_between_dates(self.data.get('start_time'), self.data.get('end_time'))]\n\n    @staticmethod\n    def find(source_list, condition, default):\n        value_list = [row for row in source_list if condition(row)]\n        if len(value_list) > 0:\n            return value_list[0]\n        return default\n\n    @staticmethod\n    def get_days_between_dates(start_date, end_date):\n        start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d')\n        end_date = datetime.datetime.strptime(end_date, '%Y-%m-%d')\n        days = []\n        current_date = start_date\n        while current_date <= end_date:\n            days.append(current_date.strftime('%Y-%m-%d'))\n            current_date += datetime.timedelta(days=1)\n        return days\n\n    def get_token_usage_statistics(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        start_time = self.get_start_time()\n        end_time = self.get_end_time()\n        get_token_usage = native_search(\n            {'default_sql': QuerySet(model=get_dynamics_model(\n                {'application_chat.application_id': models.UUIDField(),\n                 'application_chat_record.create_time': models.DateTimeField()})).filter(\n                **{'application_chat.application_id': self.data.get('application_id'),\n                   'application_chat_record.create_time__gte': start_time,\n                   'application_chat_record.create_time__lte': end_time}\n            )},\n            select_string=get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql',\n                             ('get_token_usage_ee.sql' if ['PE', 'EE'].__contains__(\n                                 edition) else 'get_token_usage.sql'))))\n        return get_token_usage\n\n    def get_top_questions_statistics(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        start_time = self.get_start_time()\n        end_time = self.get_end_time()\n        get_top_questions = native_search(\n            {'default_sql': QuerySet(model=get_dynamics_model(\n                {'application_chat.application_id': models.UUIDField(),\n                 'application_chat_record.create_time': models.DateTimeField()})).filter(\n                **{'application_chat.application_id': self.data.get('application_id'),\n                   'application_chat_record.create_time__gte': start_time,\n                   'application_chat_record.create_time__lte': end_time}\n            )},\n            select_string=get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"application\", 'sql', (\n                    'top_questions_ee.sql' if ['PE', 'EE'].__contains__(edition) else 'top_questions.sql'))))\n        return get_top_questions\n"
  },
  {
    "path": "apps/application/serializers/application_version.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_version.py\n    @date：2025/6/3 16:25\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import Application, ApplicationVersion\nfrom common.db.search import page_search\nfrom common.exception.app_exception import AppApiException\n\n\nclass ApplicationVersionQuerySerializer(serializers.Serializer):\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    name = serializers.CharField(required=False, allow_null=True, allow_blank=True,\n                                 label=_(\"summary\"))\n\n\nclass ApplicationVersionModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = ApplicationVersion\n        fields = ['id', 'name', 'workspace_id', 'application_id', 'work_flow', 'publish_user_id', 'publish_user_name',\n                  'create_time',\n                  'update_time']\n\n\nclass ApplicationVersionEditSerializer(serializers.Serializer):\n    name = serializers.CharField(required=False, max_length=128, allow_null=True, allow_blank=True,\n                                 label=_(\"Version Name\"))\n\n\nclass ApplicationVersionSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, label=_(\"Workspace ID\"))\n\n    class Query(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n\n        def get_query_set(self, query):\n            query_set = QuerySet(ApplicationVersion).filter(application_id=query.get('application_id'))\n            if 'name' in query and query.get('name') is not None:\n                query_set = query_set.filter(name__contains=query.get('name'))\n            if 'workspace_id' in self.data and self.data.get('workspace_id') is not None:\n                query_set = query_set.filter(workspace_id=self.data.get('workspace_id'))\n            return query_set.order_by(\"-create_time\")\n\n        def list(self, query, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                ApplicationVersionQuerySerializer(data=query).is_valid(raise_exception=True)\n            query_set = self.get_query_set(query)\n            return [ApplicationVersionModelSerializer(v).data for v in query_set]\n\n        def page(self, query, current_page, page_size, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            return page_search(current_page, page_size,\n                               self.get_query_set(query),\n                               post_records_handler=lambda v: ApplicationVersionModelSerializer(v).data)\n\n    class Operate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n        application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n        application_version_id = serializers.UUIDField(required=True,\n                                                       label=_(\"Application version ID\"))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Application id does not exist'))\n\n        def one(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            application_version = QuerySet(ApplicationVersion).filter(application_id=self.data.get('application_id'),\n                                                                      id=self.data.get(\n                                                                          'application_version_id')).first()\n            if application_version is not None:\n                return ApplicationVersionModelSerializer(application_version).data\n            else:\n                raise AppApiException(500, _('Workflow version does not exist'))\n\n        def edit(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                ApplicationVersionEditSerializer(data=instance).is_valid(raise_exception=True)\n            application_version = QuerySet(ApplicationVersion).filter(application_id=self.data.get('application_id'),\n                                                                      id=self.data.get(\n                                                                          'application_version_id')).first()\n            if application_version is not None:\n                name = instance.get('name', None)\n                if name is not None and len(name) > 0:\n                    application_version.name = name\n                application_version.save()\n                return ApplicationVersionModelSerializer(application_version).data\n            else:\n                raise AppApiException(500, _('Workflow version does not exist'))\n"
  },
  {
    "path": "apps/application/serializers/common.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： common.py\n    @date：2025/6/9 13:42\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\n\nfrom application.models import Application, ChatRecord, Chat, ApplicationVersion, ChatUserType, ApplicationTypeChoices\nfrom application.serializers.application_chat import ChatCountSerializer\nfrom common.constants.cache_version import Cache_Version\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.exception.app_exception import ChatException\nfrom knowledge.models import Document\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model_credential\nfrom system_manage.models.resource_mapping import ResourceMapping\nfrom tools.models import ToolRecord\n\n\nclass ToolExecute:\n    def __init__(self, tool_id: str,\n                 tool_record_id: str,\n                 workspace_id: str,\n                 source_type,\n                 source_id,\n                 debug=False):\n        self.tool_id = tool_id\n        self.workspace_id = workspace_id\n        self.source_type = source_type\n        self.source_id = source_id\n        self.tool_record_id = tool_record_id\n        self.debug = debug\n\n    def get_record(self):\n        if self.tool_record_id:\n            if self.debug:\n                return self.to_record(cache.get(Cache_Version.TOOL_WORKFLOW_EXECUTE.get_key(key=self.tool_record_id),\n                                                version=Cache_Version.TOOL_WORKFLOW_EXECUTE.get_version()))\n            else:\n                return QuerySet(ToolRecord).filter(tool_id=self.tool_id, id=self.tool_record_id).first()\n        return None\n\n    def to_record(self, tool_record_dict):\n        if tool_record_dict is None:\n            return None\n        return ToolRecord(id=tool_record_dict.get('id'),\n                          tool_id=tool_record_dict.get('tool_id'),\n                          workspace_id=tool_record_dict.get('workspace_id'),\n                          source_type=tool_record_dict.get('source_type'),\n                          source_id=tool_record_dict.get('source_id'),\n                          meta=tool_record_dict.get('meta'),\n                          state=tool_record_dict.get('state'),\n                          run_time=tool_record_dict.get('run_time'))\n\n    def to_dict(self, tool_record):\n        return {'id': tool_record.id,\n                'tool_id': tool_record.tool_id,\n                'workspace_id': tool_record.workspace_id,\n                'source_type': tool_record.source_type,\n                'source_id': tool_record.source_id,\n                'meta': tool_record.meta,\n                'state': tool_record.state,\n                'run_time': tool_record.run_time}\n\n    def set_record(self, tool_record):\n        cache.set(Cache_Version.TOOL_WORKFLOW_EXECUTE.get_key(key=self.tool_record_id), self.to_dict(tool_record),\n                  version=Cache_Version.TOOL_WORKFLOW_EXECUTE.get_version(),\n                  timeout=60 * 30)\n        if not self.debug:\n            QuerySet(ToolRecord).update_or_create(id=tool_record.id,\n                                                  create_defaults={'id': tool_record.id,\n                                                                   'tool_id': tool_record.tool_id,\n                                                                   'workspace_id': tool_record.workspace_id,\n                                                                   \"source_type\": tool_record.source_type,\n                                                                   'source_id': tool_record.source_id,\n                                                                   'meta': tool_record.meta,\n                                                                   'run_time': tool_record.run_time},\n                                                  defaults={\n                                                      'workspace_id': tool_record.workspace_id,\n                                                      'tool_id': tool_record.tool_id,\n                                                      \"source_type\": tool_record.source_type,\n                                                      'source_id': tool_record.source_id,\n                                                      'meta': tool_record.meta,\n                                                      'run_time': tool_record.run_time\n                                                  })\n\n\nclass ChatInfo:\n    def __init__(self,\n                 chat_id: str,\n                 chat_user_id: str,\n                 chat_user_type: str,\n                 ip_address: str,\n                 source: {},\n                 knowledge_id_list: List[str],\n                 exclude_document_id_list: list[str],\n                 application_id: str,\n                 debug=False):\n        \"\"\"\n        :param chat_id:                     对话id\n        :param chat_user_id                 对话用户id\n        :param chat_user_type               对话用户类型\n        :param knowledge_id_list:           知识库列表\n        :param exclude_document_id_list:    排除的文档\n        :param application_id               应用id\n        :param debug                        是否是调试\n        :param ip_address:                  用户ip地址\n        :param source:                      用户来源\n        \"\"\"\n        self.chat_id = chat_id\n        self.chat_user_id = chat_user_id\n        self.chat_user_type = chat_user_type\n        self.knowledge_id_list = knowledge_id_list\n        self.exclude_document_id_list = exclude_document_id_list\n        self.application_id = application_id\n        self.chat_record_list: List[ChatRecord] = []\n        self.application = None\n        self.chat_user = None\n        self.ip_address = ip_address\n        self.source = source\n        self.debug = debug\n\n    @staticmethod\n    def get_no_references_setting(knowledge_setting, model_setting):\n        no_references_setting = knowledge_setting.get(\n            'no_references_setting', {\n                'status': 'ai_questioning',\n                'value': '{question}'})\n        if no_references_setting.get('status') == 'ai_questioning':\n            no_references_prompt = model_setting.get('no_references_prompt', '{question}')\n            no_references_setting['value'] = no_references_prompt if len(no_references_prompt) > 0 else \"{question}\"\n        return no_references_setting\n\n    def get_application(self):\n        if self.debug:\n            application = QuerySet(Application).filter(id=self.application_id).first()\n            if not application:\n                raise ChatException(500, _('The application does not exist'))\n        else:\n            application = QuerySet(ApplicationVersion).filter(application_id=self.application_id).order_by(\n                '-create_time')[0:1].first()\n            if not application:\n                raise ChatException(500, _(\"The application has not been published. Please use it after publishing.\"))\n        if application.type == ApplicationTypeChoices.SIMPLE.value:\n            # 数据集id列表\n            knowledge_id_list = [str(row.target_id) for row in\n                                 QuerySet(ResourceMapping).filter(source_id=self.application_id,\n                                                                  source_type='APPLICATION',\n                                                                  target_type='KNOWLEDGE')]\n\n            # 需要排除的文档\n            exclude_document_id_list = [str(document.id) for document in\n                                        QuerySet(Document).filter(\n                                            knowledge_id__in=knowledge_id_list,\n                                            is_active=False)]\n            self.knowledge_id_list = knowledge_id_list\n            self.exclude_document_id_list = exclude_document_id_list\n        self.application = application\n        return application\n\n    def get_chat_user(self, asker=None):\n        if self.chat_user:\n            return self.chat_user\n        chat_user_model = DatabaseModelManage.get_model(\"chat_user\")\n        if self.chat_user_type == ChatUserType.CHAT_USER.value and chat_user_model:\n            chat_user = QuerySet(chat_user_model).filter(id=self.chat_user_id).first()\n            return {\n                'id': str(chat_user.id),\n                'email': chat_user.email,\n                'phone': chat_user.phone,\n                'nick_name': chat_user.nick_name,\n                'username': chat_user.username,\n                'source': chat_user.source\n            }\n        else:\n            if asker:\n                if isinstance(asker, dict):\n                    self.chat_user = asker\n                else:\n                    self.chat_user = {'username': asker}\n            else:\n                self.chat_user = {'username': '游客'}\n        return self.chat_user\n\n    def get_chat_user_group(self, asker=None):\n        chat_user = self.get_chat_user(asker=asker)\n        chat_user_id = chat_user.get('id')\n\n        if not chat_user_id:\n            return []\n\n        user_group_relation_model = DatabaseModelManage.get_model(\"user_group_relation\")\n        if user_group_relation_model:\n            return [{\n                'id': user_group_relation.group_id,\n                'name': user_group_relation.group.name\n            } for user_group_relation in\n                QuerySet(user_group_relation_model).select_related('group').filter(user_id=chat_user_id)]\n        return []\n\n    def to_base_pipeline_manage_params(self):\n        self.get_application()\n        self.get_chat_user()\n        knowledge_setting = self.application.knowledge_setting\n        model_setting = self.application.model_setting\n        model_id = self.application.model_id\n        model_params_setting = None\n        if model_id is not None:\n            model = QuerySet(Model).filter(id=model_id).first()\n            credential = get_model_credential(model.provider, model.model_type, model.model_name)\n            model_params_setting = credential.get_model_params_setting_form(model.model_name).get_default_form_data()\n        return {\n            'knowledge_id_list': self.knowledge_id_list,\n            'exclude_document_id_list': self.exclude_document_id_list,\n            'exclude_paragraph_id_list': [],\n            'top_n': 3 if knowledge_setting.get('top_n') is None else knowledge_setting.get('top_n'),\n            'similarity': 0.6 if knowledge_setting.get('similarity') is None else knowledge_setting.get('similarity'),\n            'max_paragraph_char_number': knowledge_setting.get('max_paragraph_char_number') or 5000,\n            'history_chat_record': self.chat_record_list,\n            'chat_id': self.chat_id,\n            'dialogue_number': self.application.dialogue_number,\n            'problem_optimization_prompt': self.application.problem_optimization_prompt if self.application.problem_optimization_prompt is not None and len(\n                self.application.problem_optimization_prompt) > 0 else _(\n                \"() contains the user's question. Answer the guessed user's question based on the context ({question}) Requirement: Output a complete question and put it in the <data></data> tag\"),\n            'prompt': model_setting.get(\n                'prompt') if 'prompt' in model_setting and len(model_setting.get(\n                'prompt')) > 0 else Application.get_default_model_prompt(),\n            'system': model_setting.get(\n                'system', None),\n            'model_id': model_id,\n            'problem_optimization': self.application.problem_optimization,\n            'stream': True,\n            'model_setting': model_setting,\n            'model_params_setting': model_params_setting if self.application.model_params_setting is None or len(\n                self.application.model_params_setting.keys()) == 0 else self.application.model_params_setting,\n            'search_mode': self.application.knowledge_setting.get('search_mode') or 'embedding',\n            'no_references_setting': self.get_no_references_setting(self.application.knowledge_setting, model_setting),\n            'workspace_id': self.application.workspace_id,\n            'application_id': self.application_id,\n            'mcp_enable': self.application.mcp_enable,\n            'mcp_tool_ids': self.application.mcp_tool_ids,\n            'mcp_servers': self.application.mcp_servers,\n            'mcp_source': self.application.mcp_source,\n            'tool_enable': self.application.tool_enable,\n            'tool_ids': self.application.tool_ids,\n            'application_enable': self.application.application_enable,\n            'application_ids': self.application.application_ids,\n            'skill_tool_ids': self.application.skill_tool_ids,\n            'mcp_output_enable': self.application.mcp_output_enable,\n        }\n\n    def to_pipeline_manage_params(self, problem_text: str, post_response_handler,\n                                  exclude_paragraph_id_list, chat_user_id: str, chat_user_type, ip_address, source,\n                                  stream=True,\n                                  form_data=None):\n        if form_data is None:\n            form_data = {}\n        params = self.to_base_pipeline_manage_params()\n        return {**params, 'problem_text': problem_text, 'post_response_handler': post_response_handler,\n                'exclude_paragraph_id_list': exclude_paragraph_id_list, 'stream': stream, 'chat_user_id': chat_user_id,\n                'chat_user_type': chat_user_type, 'ip_address': ip_address, 'source': source, 'form_data': form_data}\n\n    def set_chat(self, question):\n        if not self.debug:\n            if not QuerySet(Chat).filter(id=self.chat_id).exists():\n                Chat(id=self.chat_id, application_id=self.application_id, abstract=question[0:1024],\n                     chat_user_id=self.chat_user_id, chat_user_type=self.chat_user_type,\n                     ip_address=self.ip_address, source=self.source,\n                     asker=self.get_chat_user()).save()\n\n    def set_chat_variable(self, chat_context):\n        if not self.debug:\n            chat = QuerySet(Chat).filter(id=self.chat_id).first()\n            if chat:\n                chat.meta = {**(chat.meta if isinstance(chat.meta, dict) else {}), **chat_context}\n                chat.save()\n        else:\n            cache.set(Cache_Version.CHAT_VARIABLE.get_key(key=self.chat_id), chat_context,\n                      version=Cache_Version.CHAT_VARIABLE.get_version(),\n                      timeout=60 * 30)\n\n    def get_chat_variable(self):\n        if not self.debug:\n            chat = QuerySet(Chat).filter(id=self.chat_id).first()\n            if chat:\n                return chat.meta\n            return {}\n        else:\n            return cache.get(Cache_Version.CHAT_VARIABLE.get_key(key=self.chat_id),\n                             version=Cache_Version.CHAT_VARIABLE.get_version()) or {}\n\n    def append_chat_record(self, chat_record: ChatRecord):\n        chat_record.problem_text = chat_record.problem_text[0:10240] if chat_record.problem_text is not None else \"\"\n        chat_record.answer_text = chat_record.answer_text[0:40960] if chat_record.problem_text is not None else \"\"\n        is_save = True\n        # 存入缓存中\n        for index in range(len(self.chat_record_list)):\n            record = self.chat_record_list[index]\n            if record.id == chat_record.id:\n                self.chat_record_list[index] = chat_record\n                is_save = False\n                break\n        if is_save:\n            self.chat_record_list.append(chat_record)\n        if not self.debug:\n            if not QuerySet(Chat).filter(id=self.chat_id).exists():\n                Chat(id=self.chat_id, application_id=self.application_id, abstract=chat_record.problem_text[0:1024],\n                     chat_user_id=self.chat_user_id, chat_user_type=self.chat_user_type,\n                     ip_address=self.ip_address, source=self.source,\n                     asker=self.get_chat_user()).save()\n            else:\n                QuerySet(Chat).filter(id=self.chat_id).update(update_time=timezone.now())\n            # 插入会话记录\n            QuerySet(ChatRecord).update_or_create(id=chat_record.id,\n                                                  create_defaults={'id': chat_record.id,\n                                                                   'chat_id': chat_record.chat_id,\n                                                                   \"vote_status\": chat_record.vote_status,\n                                                                   'problem_text': chat_record.problem_text,\n                                                                   'answer_text': chat_record.answer_text,\n                                                                   'answer_text_list': chat_record.answer_text_list,\n                                                                   'message_tokens': chat_record.message_tokens,\n                                                                   'answer_tokens': chat_record.answer_tokens,\n                                                                   'const': chat_record.const,\n                                                                   'details': chat_record.details,\n                                                                   'improve_paragraph_id_list': chat_record.improve_paragraph_id_list,\n                                                                   'run_time': chat_record.run_time,\n                                                                   'source': chat_record.source,\n                                                                   'ip_address': chat_record.ip_address or '',\n                                                                   'index': chat_record.index},\n                                                  defaults={\n                                                      \"vote_status\": chat_record.vote_status,\n                                                      'problem_text': chat_record.problem_text,\n                                                      'answer_text': chat_record.answer_text,\n                                                      'answer_text_list': chat_record.answer_text_list,\n                                                      'message_tokens': chat_record.message_tokens,\n                                                      'answer_tokens': chat_record.answer_tokens,\n                                                      'const': chat_record.const,\n                                                      'details': chat_record.details,\n                                                      'improve_paragraph_id_list': chat_record.improve_paragraph_id_list,\n                                                      'run_time': chat_record.run_time,\n                                                      'index': chat_record.index,\n                                                      'source': chat_record.source,\n                                                      'ip_address': chat_record.ip_address or '',\n                                                  })\n            ChatCountSerializer(data={'chat_id': self.chat_id}).update_chat()\n\n    def to_dict(self):\n\n        return {\n            'chat_id': self.chat_id,\n            'chat_user_id': self.chat_user_id,\n            'chat_user_type': self.chat_user_type,\n            'ip_address': self.ip_address,\n            'source': self.source,\n            'knowledge_id_list': self.knowledge_id_list,\n            'exclude_document_id_list': self.exclude_document_id_list,\n            'application_id': self.application_id,\n            'chat_record_list': [self.chat_record_to_map(c) for c in self.chat_record_list][-20:],\n            'debug': self.debug\n        }\n\n    def chat_record_to_map(self, chat_record):\n        return {'id': chat_record.id,\n                'chat_id': chat_record.chat_id,\n                'vote_status': chat_record.vote_status,\n                'problem_text': chat_record.problem_text,\n                'answer_text': chat_record.answer_text,\n                'answer_text_list': chat_record.answer_text_list,\n                'message_tokens': chat_record.message_tokens,\n                'answer_tokens': chat_record.answer_tokens,\n                'const': chat_record.const,\n                'details': chat_record.details,\n                'improve_paragraph_id_list': chat_record.improve_paragraph_id_list,\n                'run_time': chat_record.run_time,\n                'source': chat_record.source,\n                'ip_address': chat_record.ip_address,\n                'index': chat_record.index}\n\n    @staticmethod\n    def map_to_chat_record(chat_record_dict):\n        return ChatRecord(id=chat_record_dict.get('id'),\n                          chat_id=chat_record_dict.get('chat_id'),\n                          vote_status=chat_record_dict.get('vote_status'),\n                          problem_text=chat_record_dict.get('problem_text'),\n                          answer_text=chat_record_dict.get('answer_text'),\n                          answer_text_list=chat_record_dict.get('answer_text_list'),\n                          message_tokens=chat_record_dict.get('message_tokens'),\n                          answer_tokens=chat_record_dict.get('answer_tokens'),\n                          const=chat_record_dict.get('const'),\n                          details=chat_record_dict.get('details'),\n                          improve_paragraph_id_list=chat_record_dict.get('improve_paragraph_id_list'),\n                          run_time=chat_record_dict.get('run_time'),\n                          index=chat_record_dict.get('index'),\n                          source=chat_record_dict.get('source'),\n                          ip_address=chat_record_dict.get('ip_address'))\n\n    def set_cache(self):\n        cache.set(Cache_Version.CHAT.get_key(key=self.chat_id), self.to_dict(),\n                  version=Cache_Version.CHAT_INFO.get_version(),\n                  timeout=60 * 30)\n\n    @staticmethod\n    def map_to_chat_info(chat_info_dict):\n        c = ChatInfo(chat_info_dict.get('chat_id'), chat_info_dict.get('chat_user_id'),\n                     chat_info_dict.get('chat_user_type'), chat_info_dict.get('ip_address'),\n                     chat_info_dict.get('source'),\n                     chat_info_dict.get('knowledge_id_list'),\n                     chat_info_dict.get('exclude_document_id_list'),\n                     chat_info_dict.get('application_id'),\n                     debug=chat_info_dict.get('debug'))\n        c.chat_record_list = [ChatInfo.map_to_chat_record(c_r) for c_r in chat_info_dict.get('chat_record_list')]\n        return c\n\n    @staticmethod\n    def get_cache(chat_id):\n        chat_info_dict = cache.get(Cache_Version.CHAT.get_key(key=chat_id),\n                                   version=Cache_Version.CHAT_INFO.get_version())\n        if chat_info_dict:\n            return ChatInfo.map_to_chat_info(chat_info_dict)\n        return None\n\n\ndef update_resource_mapping_by_application(application_id: str, other_resource_mapping=None):\n    from application.flow.tools import get_instance_resource, save_workflow_mapping, \\\n        application_instance_field_call_dict\n    from system_manage.models.resource_mapping import ResourceType\n    if other_resource_mapping is None:\n        other_resource_mapping = []\n    application = QuerySet(Application).filter(id=application_id).first()\n    instance_mapping = get_instance_resource(application, ResourceType.APPLICATION, str(application.id),\n                                             application_instance_field_call_dict)\n    if application.type == 'WORK_FLOW':\n        save_workflow_mapping(application.work_flow, ResourceType.APPLICATION, str(application_id),\n                              instance_mapping + other_resource_mapping)\n        return\n    else:\n        save_workflow_mapping({}, ResourceType.APPLICATION, str(application_id),\n                              instance_mapping + other_resource_mapping)\n"
  },
  {
    "path": "apps/application/sql/chat_record_count_trend.sql",
    "content": "SELECT SUM\n\t( CASE WHEN application_chat_record.vote_status = '0' THEN 1 ELSE 0 END ) AS \"star_num\",\n\tSUM ( CASE WHEN application_chat_record.vote_status = '1' THEN 1 ELSE 0 END ) AS \"trample_num\",\n\tSUM ( application_chat_record.message_tokens + application_chat_record.answer_tokens ) as \"tokens_num\",\n\t\"count\"(application_chat_record.\"id\") as chat_record_count,\n\t\"count\"(DISTINCT application_chat.chat_user_id) customer_num,\n\tapplication_chat_record.create_time :: DATE as \"day\"\nFROM\n\tapplication_chat_record application_chat_record\n\tLEFT JOIN application_chat application_chat ON application_chat.\"id\" = application_chat_record.chat_id\n${default_sql}\nGROUP BY \"day\""
  },
  {
    "path": "apps/application/sql/count_chat_record.sql",
    "content": "SELECT COUNT\n\t\t( \"id\" ) AS chat_record_count,\n\t\tSUM ( CASE WHEN \"vote_status\" = '0' THEN 1 ELSE 0 END ) AS star_num,\n\t\tSUM ( CASE WHEN \"vote_status\" = '1' THEN 1 ELSE 0 END ) AS trample_num,\n\t\tSUM ( CASE WHEN array_length( application_chat_record.improve_paragraph_id_list, 1 ) IS NULL THEN 0 ELSE array_length( application_chat_record.improve_paragraph_id_list, 1 ) END ) AS mark_sum\nFROM\n\t\tapplication_chat_record"
  },
  {
    "path": "apps/application/sql/customer_count_trend.sql",
    "content": "SELECT\n\tCOUNT ( \"application_chat_user_stats\".\"id\" ) AS \"customer_added_count\",\n\tcreate_time :: DATE as \"day\"\nFROM\n\t\"application_chat_user_stats\"\n${default_sql}\nGROUP BY \"day\""
  },
  {
    "path": "apps/application/sql/export_application_chat.sql",
    "content": "SELECT application_chat_record_temp.id                     AS id,\n       application_chat.\"id\"                               as chat_id,\n       application_chat.abstract                           as abstract,\n       application_chat_record_temp.problem_text           as problem_text,\n       application_chat_record_temp.answer_text            as answer_text,\n       application_chat_record_temp.message_tokens         as message_tokens,\n       application_chat_record_temp.answer_tokens          as answer_tokens,\n       application_chat_record_temp.run_time               as run_time,\n       application_chat_record_temp.details::JSON as details, application_chat_record_temp.\"index\" as \"index\",\n       application_chat_record_temp.improve_paragraph_list as improve_paragraph_list,\n       application_chat_record_temp.vote_status            as vote_status,\n       application_chat_record_temp.vote_reason            as vote_reason,\n       application_chat_record_temp.vote_other_content     as vote_other_content,\n       application_chat_record_temp.create_time            as create_time,\n       application_chat.asker::json AS asker, application_chat_record_temp.ip_address as ip_address,\n       application_chat_record_temp.source::json AS source\nFROM application_chat application_chat\n         LEFT JOIN (SELECT *,\n                           CASE\n                               WHEN array_length(application_chat_record.improve_paragraph_id_list, 1) IS NULL THEN\n                                   '{}'\n                               ELSE (SELECT ARRAY_AGG(row_to_json(paragraph))\n                                     FROM paragraph\n                                     WHERE \"id\" = ANY (application_chat_record.improve_paragraph_id_list))\n                               END as improve_paragraph_list\n                    FROM application_chat_record application_chat_record) application_chat_record_temp\n                   ON application_chat_record_temp.chat_id = application_chat.\"id\"\n    ${default_queryset}"
  },
  {
    "path": "apps/application/sql/export_application_chat_ee.sql",
    "content": "SELECT application_chat_record_temp.id                     AS id,\n       application_chat.\"id\"                               as chat_id,\n       application_chat.abstract                           as abstract,\n       application_chat_record_temp.problem_text           as problem_text,\n       application_chat_record_temp.answer_text            as answer_text,\n       application_chat_record_temp.message_tokens         as message_tokens,\n       application_chat_record_temp.answer_tokens          as answer_tokens,\n       application_chat_record_temp.run_time               as run_time,\n       application_chat_record_temp.details::JSON as details, application_chat_record_temp.\"index\" as \"index\",\n       application_chat_record_temp.improve_paragraph_list as improve_paragraph_list,\n       application_chat_record_temp.vote_status            as vote_status,\n       application_chat_record_temp.vote_reason            as vote_reason,\n       application_chat_record_temp.vote_other_content     as vote_other_content,\n       application_chat_record_temp.create_time            as create_time,\n       (CASE\n            WHEN \"chat_user\".id is NULL THEN application_chat.asker\n            ELSE jsonb_build_object('id', chat_user.id, 'username',\n                                    chat_user.username) END)::json AS asker, application_chat_record_temp.ip_address as ip_address,\n       application_chat_record_temp.source::json AS source\nFROM application_chat application_chat\n         left join chat_user chat_user on chat_user.id::varchar = application_chat.chat_user_id\n\tLEFT JOIN (\n\tSELECT\n\t\t*,\n\tCASE\n\t\t\tWHEN array_length( application_chat_record.improve_paragraph_id_list, 1 ) IS NULL THEN\n\t\t\t'{}' ELSE ( SELECT ARRAY_AGG ( row_to_json ( paragraph ) ) FROM paragraph WHERE \"id\" = ANY ( application_chat_record.improve_paragraph_id_list ) )\n\t\tEND as improve_paragraph_list\n\t\tFROM\n\t\tapplication_chat_record application_chat_record\n\t) application_chat_record_temp\nON application_chat_record_temp.chat_id = application_chat.\"id\"\n    ${default_queryset}"
  },
  {
    "path": "apps/application/sql/get_token_usage.sql",
    "content": "SELECT SUM(application_chat_record.message_tokens + application_chat_record.answer_tokens) as \"token_usage\",\n       MAX(COALESCE(application_chat.asker ->>'username', '游客'))                         as \"username\"\nFROM application_chat_record application_chat_record\n         LEFT JOIN application_chat application_chat ON application_chat.\"id\" = application_chat_record.chat_id\n    ${default_sql}\nGROUP BY\n    application_chat.chat_user_id\nORDER BY\n    \"token_usage\" DESC\n"
  },
  {
    "path": "apps/application/sql/get_token_usage_ee.sql",
    "content": "SELECT SUM(application_chat_record.message_tokens + application_chat_record.answer_tokens) as \"token_usage\",\n       MAX(COALESCE(chat_user.username, application_chat.asker ->>'username', '游客')) as \"username\"\nFROM application_chat_record application_chat_record\n         LEFT JOIN application_chat application_chat ON application_chat.\"id\" = application_chat_record.chat_id\n         LEFT JOIN chat_user chat_user ON chat_user.id::varchar = application_chat.chat_user_id\n    ${default_sql}\nGROUP BY\n    application_chat.chat_user_id\nORDER BY\n    \"token_usage\" DESC"
  },
  {
    "path": "apps/application/sql/list_application.sql",
    "content": "select *\nfrom (select application.\"id\"::text, application.\"name\",\n             application.\"desc\",\n             application.\"is_publish\",\n             application.\"type\",\n             'application'      as \"resource_type\",\n             application.\"workspace_id\",\n             application.\"folder_id\",\n             application.\"user_id\",\n             \"user\".\"nick_name\" as \"nick_name\",\n             application.\"create_time\",\n             application.\"update_time\",\n             application.\"publish_time\",\n             application.icon\n      from application\n               left join \"user\" on user_id = \"user\".id\n          ${application_custom_sql}\n      ) temp\n    ${application_query_set}"
  },
  {
    "path": "apps/application/sql/list_application_chat.sql",
    "content": "select application_chat.*, application_chat.asker::json AS asker, application_chat.source::json AS source\nfrom application_chat application_chat\n    ${default_queryset}"
  },
  {
    "path": "apps/application/sql/list_application_chat_ee.sql",
    "content": "select application_chat.*,\n       (CASE\n            WHEN \"chat_user\".id is NULL THEN application_chat.asker\n            ELSE jsonb_build_object('id', chat_user.id, 'username',\n                                    chat_user.username) END) ::json AS asker, application_chat.source::json AS source\nfrom application_chat application_chat\n         left join chat_user chat_user on chat_user.id::varchar = application_chat.chat_user_id ${default_queryset}"
  },
  {
    "path": "apps/application/sql/list_application_user.sql",
    "content": "select *\nfrom (select application.\"id\"::text, application.\"name\",\n             application.\"desc\",\n             application.\"is_publish\",\n             application.\"type\",\n             'application'      as \"resource_type\",\n             application.\"workspace_id\",\n             application.\"folder_id\",\n             application.\"user_id\",\n             \"user\".\"nick_name\" as \"nick_name\",\n             application.\"create_time\",\n             application.\"update_time\",\n             application.\"publish_time\",\n             application.icon\n      from application\n               left join \"user\" on user_id = \"user\".id\n      where application.\"id\"::text in (select target\n                                 from workspace_user_resource_permission ${workspace_user_resource_permission_query_set}\n        and 'VIEW' = any (permission_list))) temp\n${application_query_set}"
  },
  {
    "path": "apps/application/sql/list_application_user_ee.sql",
    "content": "select *\nfrom (select application.\"id\"::text, application.\"name\",\n             application.\"desc\",\n             application.\"is_publish\",\n             application.\"type\",\n             'application'      as \"resource_type\",\n             application.\"workspace_id\",\n             application.\"folder_id\",\n             application.\"user_id\",\n             \"user\".\"nick_name\" as \"nick_name\",\n             application.\"create_time\",\n             application.\"update_time\",\n             application.\"publish_time\",\n             application.icon\n      from application\n               left join \"user\" on user_id = \"user\".id\n      where \"application\".id::text in (select target\n                                 from workspace_user_resource_permission ${workspace_user_resource_permission_query_set}\n        and case\n                when auth_type = 'ROLE' then\n                    'ROLE' = any (permission_list)\n                        and\n                    'APPLICATION:READ' in (select (case\n                                                       when user_role_relation.role_id = any (array['USER'])\n                                                           THEN 'APPLICATION:READ'\n                                                       else role_permission.permission_id END)\n                                           from role_permission role_permission\n                                                    right join user_role_relation user_role_relation\n                                                               on user_role_relation.role_id = role_permission.role_id\n                                           where user_role_relation.user_id = workspace_user_resource_permission.user_id\n                                             and user_role_relation.workspace_id =\n                                                 workspace_user_resource_permission.workspace_id)\n\n                else\n                    'VIEW' = any (permission_list)\n          end)) temp\n${application_query_set}"
  },
  {
    "path": "apps/application/sql/list_knowledge_paragraph_by_paragraph_id.sql",
    "content": "SELECT\n\tparagraph.*,\n\tknowledge.\"name\" AS \"knowledge_name\",\n\tknowledge.\"type\" AS \"knowledge_type\",\n\t\"document\".\"name\" AS \"document_name\",\n\t\"document\".\"meta\"::json AS \"meta\",\n\t\"document\".\"hit_handling_method\" AS \"hit_handling_method\",\n\t\"document\".\"directly_return_similarity\" as \"directly_return_similarity\"\nFROM\n\tparagraph paragraph\n\tLEFT JOIN knowledge knowledge ON knowledge.\"id\" = paragraph.knowledge_id\n\tLEFT JOIN \"document\" \"document\" ON \"document\".\"id\" =paragraph.document_id"
  },
  {
    "path": "apps/application/sql/top_questions.sql",
    "content": "SELECT COUNT(application_chat_record.\"id\")                         AS chat_record_count,\n       MAX(COALESCE(application_chat.asker ->>'username', '游客')) as \"username\"\nFROM application_chat_record application_chat_record\n         LEFT JOIN application_chat application_chat ON application_chat.\"id\" = application_chat_record.chat_id\n    ${default_sql}\nGROUP BY\n    application_chat.chat_user_id\nORDER BY\n    chat_record_count DESC,\n    username ASC\n\n"
  },
  {
    "path": "apps/application/sql/top_questions_ee.sql",
    "content": "SELECT COUNT(application_chat_record.\"id\")                                             AS chat_record_count,\n       MAX(COALESCE(chat_user.username, application_chat.asker ->>'username', '游客')) as \"username\"\nFROM application_chat_record application_chat_record\n         LEFT JOIN application_chat application_chat ON application_chat.\"id\" = application_chat_record.chat_id\n         LEFT JOIN chat_user chat_user ON chat_user.id::varchar = application_chat.chat_user_id ${default_sql}\nGROUP BY\n    application_chat.chat_user_id\nORDER BY\n    chat_record_count DESC,\n    username ASC\n\n"
  },
  {
    "path": "apps/application/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/application/urls.py",
    "content": "from django.urls import path\n\nfrom . import views\n\napp_name = 'application'\n# @formatter:off\nurlpatterns = [\n    path('workspace/store/application_template', views.ApplicationAPI.StoreApplication.as_view()),\n    path('workspace/<str:workspace_id>/application', views.ApplicationAPI.as_view(), name='application'),\n    path('workspace/<str:workspace_id>/application/folder/<str:folder_id>/import', views.ApplicationAPI.Import.as_view()),\n    path('workspace/<str:workspace_id>/application/<int:current_page>/<int:page_size>', views.ApplicationAPI.Page.as_view(), name='application_page'),\n    path('workspace/<str:workspace_id>/application/<str:application_id>', views.ApplicationAPI.Operate.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/publish', views.ApplicationAPI.Publish.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/move/<str:folder_id>', views.ApplicationAPI.Move.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/application_key', views.ApplicationKey.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/application_stats', views.ApplicationStats.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/application_token_usage', views.ApplicationStats.TokenUsageStatistics.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/top_questions', views.ApplicationStats.TopQuestionsStatistics.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/application_key/<str:api_key_id>', views.ApplicationKey.Operate.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/application_key/<int:current_page>/<int:page_size>', views.ApplicationKey.Page.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/export', views.ApplicationAPI.Export.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/application_version', views.ApplicationVersionView.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/access_token', views.AccessToken.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/add_knowledge', views.ApplicationChatRecordAddKnowledge.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/chat', views.ApplicationChat.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/chat/export', views.ApplicationChat.Export.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<int:current_page>/<int:page_size>', views.ApplicationChat.Page.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record', views.ApplicationChatRecord.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>', views.ApplicationChatRecordOperateAPI.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<int:current_page>/<int:page_size>', views.ApplicationChatRecord.Page.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/improve', views.ApplicationChatRecordImprove.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/improve', views.ApplicationChatRecordImproveParagraph.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/chat/<str:chat_id>/chat_record/<str:chat_record_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/<str:paragraph_id>/improve', views.ApplicationChatRecordImproveParagraph.Operate.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/application_version/<int:current_page>/<int:page_size>', views.ApplicationVersionView.Page.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/application_version/<str:application_version_id>', views.ApplicationVersionView.Operate.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/open', views.OpenView.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/text_to_speech', views.TextToSpeech.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/speech_to_text', views.SpeechToText.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/play_demo_text', views.PlayDemoText.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/mcp_tools', views.McpServers.as_view()),\n    path('workspace/<str:workspace_id>/application/<str:application_id>/model/<str:model_id>/prompt_generate', views.PromptGenerateView.as_view()),\n    path('chat_message/<str:chat_id>', views.ChatView.as_view()),\n]\n"
  },
  {
    "path": "apps/application/views/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py\n    @date：2025/5/9 18:51\n    @desc:\n\"\"\"\nfrom .application_api_key import *\nfrom .application import *\nfrom .application_version import *\nfrom .application_access_token import *\nfrom .application_stats import *\nfrom .application_chat import *\nfrom .application_chat_record import *\nfrom .application_chat_link import *"
  },
  {
    "path": "apps/application/views/application.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application.py\n    @date：2025/5/26 16:51\n    @desc:\n\"\"\"\nfrom django.db.models import QuerySet\nfrom django.http import HttpResponse\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.parsers import MultiPartParser\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_api import ApplicationCreateAPI, ApplicationQueryAPI, ApplicationImportAPI, \\\n    ApplicationExportAPI, ApplicationOperateAPI, ApplicationEditAPI, TextToSpeechAPI, SpeechToTextAPI, PlayDemoTextAPI\nfrom application.models import Application\nfrom application.serializers.application import ApplicationSerializer, Query, ApplicationOperateSerializer\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions, get_is_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom tools.api.tool import GetInternalToolAPI\n\n\ndef get_application_operation_object(application_id):\n    application_model = QuerySet(model=Application).filter(id=application_id).first()\n    if application_model is not None:\n        return {\n            'name': application_model.name\n        }\n    return {}\n\n\nclass ApplicationAPI(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create an application'),\n        summary=_('Create an application'),\n        operation_id=_('Create an application'),  # type: ignore\n        parameters=ApplicationCreateAPI.get_parameters(),\n        request=ApplicationCreateAPI.get_request(),\n        responses=ApplicationCreateAPI.get_response(),\n        tags=[_('Application')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_CREATE.get_workspace_permission(),\n                     RoleConstants.USER.get_workspace_role(),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    @log(menu='Application', operate='Create an application',\n         get_operation_object=lambda r, k: {'name': r.data.get('name')},\n         )\n    def post(self, request: Request, workspace_id: str):\n        return result.success(\n            ApplicationSerializer(data={'workspace_id': workspace_id, 'user_id': request.user.id}).insert(request.data))\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get the application list'),\n        summary=_('Get the application list'),\n        operation_id=_('Get the application list'),  # type: ignore\n        parameters=ApplicationQueryAPI.get_parameters(),\n        responses=ApplicationQueryAPI.get_response(),\n        tags=[_('Application')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_permission(),\n                     RoleConstants.USER.get_workspace_role(),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str):\n        return result.success(\n            Query(data={'workspace_id': workspace_id, 'user_id': request.user.id}).list(request.query_params))\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get the application list by page'),\n            summary=_('Get the application list by page'),\n            operation_id=_('Get the application list by page'),  # type: ignore\n            parameters=ApplicationQueryAPI.get_parameters(),\n            responses=ApplicationQueryAPI.get_page_response(),\n            tags=[_('Application')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_permission(),\n                         RoleConstants.USER.get_workspace_role(),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, current_page: int, page_size: int):\n            return result.success(\n                Query(data={'workspace_id': workspace_id, 'user_id': request.user.id}).page(current_page, page_size,\n                                                                                            request.query_params))\n\n    class Import(APIView):\n        authentication_classes = [TokenAuth]\n        parser_classes = [MultiPartParser]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_('Import Application'),\n            summary=_('Import Application'),\n            operation_id=_('Import Application'),  # type: ignore\n            parameters=ApplicationImportAPI.get_parameters(),\n            request=ApplicationImportAPI.get_request(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Application')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_IMPORT.get_workspace_permission(),\n                         RoleConstants.USER.get_workspace_role(),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Application', operate=\"Import Application\", )\n        def post(self, request: Request, workspace_id: str, folder_id: str):\n            is_import_tool = get_is_permissions(request, workspace_id=workspace_id, folder_id=folder_id)(\n                PermissionConstants.TOOL_IMPORT.get_workspace_permission(),\n                PermissionConstants.TOOL_IMPORT.get_workspace_permission_workspace_manage_role(),\n                RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n            )\n            return result.success(ApplicationSerializer(\n                data={'user_id': request.user.id, 'workspace_id': workspace_id,\n                      }).import_({'file': request.FILES.get('file'), 'folder_id': folder_id}, is_import_tool))\n\n    class Export(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_('Export application'),\n            summary=_('Export application'),\n            operation_id=_('Export application'),  # type: ignore\n            parameters=ApplicationExportAPI.get_parameters(),\n            request=None,\n            responses=ApplicationExportAPI.get_response(),\n            tags=[_('Application')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_EXPORT.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_EXPORT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Application', operate=\"Export Application\",\n             get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')),\n             )\n        def get(self, request: Request, workspace_id: str, application_id: str):\n            return ApplicationOperateSerializer(\n                data={'application_id': application_id,\n                      'workspace_id': workspace_id,\n                      'user_id': request.user.id}).export()\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['DELETE'],\n            description=_('Deleting application'),\n            summary=_('Deleting application'),\n            operation_id=_('Deleting application'),  # type: ignore\n            parameters=ApplicationOperateAPI.get_parameters(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Application')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_DELETE.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_DELETE.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Application', operate='Deleting application',\n             get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')),\n\n             )\n        def delete(self, request: Request, workspace_id: str, application_id: str):\n            return result.success(ApplicationOperateSerializer(\n                data={'application_id': application_id, 'user_id': request.user.id,\n                      'workspace_id': workspace_id, }).delete(\n                with_valid=True))\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Modify the application'),\n            summary=_('Modify the application'),\n            operation_id=_('Modify the application'),  # type: ignore\n            parameters=ApplicationOperateAPI.get_parameters(),\n            request=ApplicationEditAPI.get_request(),\n            responses=ApplicationCreateAPI.get_response(),\n            tags=[_('Application')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Application', operate=\"Modify the application\",\n             get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')),\n             )\n        def put(self, request: Request, workspace_id: str, application_id: str):\n            return result.success(\n                ApplicationOperateSerializer(\n                    data={'application_id': application_id, 'user_id': request.user.id,\n                          'workspace_id': workspace_id, }).edit(\n                    request.data))\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get application details'),\n            summary=_('Get application details'),\n            operation_id=_('Get application details'),  # type: ignore\n            parameters=ApplicationOperateAPI.get_parameters(),\n            request=ApplicationEditAPI.get_request(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Application')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, application_id: str):\n            return result.success(ApplicationOperateSerializer(\n                data={'application_id': application_id, 'user_id': request.user.id,\n                      'workspace_id': workspace_id, }).one())\n\n    class Move(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_(\"Move an application\"),\n            summary=_(\"Move an application\"),\n            operation_id=_(\"Move an application\"),  # type: ignore\n            parameters=ApplicationOperateAPI.get_parameters(),\n            request=None,\n            responses=result.DefaultResultSerializer,\n            tags=[_('Application')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Application', operate='Move an application',\n             get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))\n        def put(self, request: Request, workspace_id: str, application_id: str, folder_id: str):\n            return result.success(\n                ApplicationOperateSerializer(\n                    data={'application_id': application_id, 'user_id': request.user.id,\n                          'workspace_id': workspace_id, }).move(folder_id))\n\n    class Publish(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_(\"Publishing an application\"),\n            summary=_(\"Publishing an application\"),\n            operation_id=_(\"Publishing an application\"),  # type: ignore\n            parameters=ApplicationOperateAPI.get_parameters(),\n            request=None,\n            responses=result.DefaultResultSerializer,\n            tags=[_('Application')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Application', operate='Publishing an application',\n             get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))\n        def put(self, request: Request, workspace_id: str, application_id: str):\n            return result.success(\n                ApplicationOperateSerializer(\n                    data={'application_id': application_id, 'user_id': request.user.id,\n                          'workspace_id': workspace_id, }).publish(request.data))\n\n    class StoreApplication(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get Appstore apps\"),\n            summary=_(\"Get Appstore apps\"),\n            operation_id=_(\"Get Appstore apps\"),  # type: ignore\n            responses=GetInternalToolAPI.get_response(),\n            tags=[_(\"Application\")]  # type: ignore\n        )\n        def get(self, request: Request):\n            return result.success(ApplicationSerializer.StoreApplication(data={\n                'user_id': request.user.id,\n                'name': request.query_params.get('name', ''),\n            }).get_appstore_templates())\n\n\nclass McpServers(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"speech to text\"),\n        summary=_(\"speech to text\"),\n        operation_id=_(\"speech to text\"),  # type: ignore\n        parameters=SpeechToTextAPI.get_parameters(),\n        request=SpeechToTextAPI.get_request(),\n        responses=SpeechToTextAPI.get_response(),\n        tags=[_('Application')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def post(self, request: Request, workspace_id, application_id: str):\n        return result.success(ApplicationOperateSerializer(\n            data={'mcp_servers': request.query_params.get('mcp_servers'), 'workspace_id': workspace_id,\n                  'user_id': request.user.id,\n                  'application_id': application_id}).get_mcp_servers(request.data))\n\n\nclass SpeechToText(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"speech to text\"),\n        summary=_(\"speech to text\"),\n        operation_id=_(\"speech to text\"),  # type: ignore\n        parameters=SpeechToTextAPI.get_parameters(),\n        request=SpeechToTextAPI.get_request(),\n        responses=SpeechToTextAPI.get_response(),\n        tags=[_('Application')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def post(self, request: Request, workspace_id: str, application_id: str):\n        return result.success(\n            ApplicationOperateSerializer(\n                data={'application_id': application_id, 'workspace_id': workspace_id, 'user_id': request.user.id})\n            .speech_to_text({'file': request.FILES.get('file')}))\n\n\nclass TextToSpeech(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"text to speech\"),\n        summary=_(\"text to speech\"),\n        operation_id=_(\"text to speech\"),  # type: ignore\n        parameters=TextToSpeechAPI.get_parameters(),\n        request=TextToSpeechAPI.get_request(),\n        responses=TextToSpeechAPI.get_response(),\n        tags=[_('Application')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def post(self, request: Request, workspace_id: str, application_id: str):\n        byte_data = ApplicationOperateSerializer(\n            data={'application_id': application_id, 'workspace_id': workspace_id,\n                  'user_id': request.user.id}).text_to_speech(request.data)\n        return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',\n                                                            'Content-Disposition': 'attachment; filename=\"abc.mp3\"'})\n\n\nclass PlayDemoText(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"PlayDemo\"),\n        summary=_(\"PlayDemo\"),\n        operation_id=_(\"PlayDemo\"),  # type: ignore\n        parameters=PlayDemoTextAPI.get_parameters(),\n        request=PlayDemoTextAPI.get_request(),\n        responses=PlayDemoTextAPI.get_response(),\n        tags=[_('Application')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    @log(menu='Application', operate=\"trial listening\",\n         get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))\n    def post(self, request: Request, workspace_id: str, application_id: str):\n        byte_data = ApplicationOperateSerializer(\n            data={'application_id': application_id, 'workspace_id': workspace_id,\n                  'user_id': request.user.id}).play_demo_text(request.data)\n        return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',\n                                                            'Content-Disposition': 'attachment; filename=\"abc.mp3\"'})\n"
  },
  {
    "path": "apps/application/views/application_access_token.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_token.py\n    @date：2025/6/9 17:42\n    @desc:\n\"\"\"\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_access_token import ApplicationAccessTokenAPI\nfrom application.models import Application\nfrom application.serializers.application_access_token import AccessTokenSerializer\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\n\ndef get_application_operation_object(application_id):\n    application_model = QuerySet(model=Application).filter(id=application_id).first()\n    if application_model is not None:\n        return {\n            \"name\": application_model.name\n        }\n    return {}\n\n\nclass AccessToken(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['PUT'],\n        description=_(\"Modify application access restriction information\"),\n        summary=_(\"Modify application access restriction information\"),\n        operation_id=_(\"Modify application access restriction information\"),  # type: ignore\n        parameters=ApplicationAccessTokenAPI.get_parameters(),\n        request=ApplicationAccessTokenAPI.get_request(),\n        tags=[_('Application')]  # type: ignore\n    )\n    @log(menu='Application', operate=\"Modify application access token\",\n         get_operation_object= lambda r,k: get_application_operation_object((k.get('application_id')))\n         )\n    @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_ACCESS.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_OVERVIEW_ACCESS.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def put(self, request: Request, workspace_id: str, application_id: str):\n        return result.success(\n            AccessTokenSerializer(data={'workspace_id': workspace_id, 'application_id': application_id}).edit(\n                request.data))\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get application access restriction information\"),\n        summary=_(\"Get application access restriction information\"),\n        operation_id=_(\"Get application access restriction information\"),  # type: ignore\n        parameters=ApplicationAccessTokenAPI.get_parameters(),\n        tags=[_('Application')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role()\n                     )\n    def get(self, request: Request, workspace_id: str, application_id: str):\n        return result.success(\n            AccessTokenSerializer(data={'workspace_id': workspace_id, 'application_id': application_id}).one())\n"
  },
  {
    "path": "apps/application/views/application_api_key.py",
    "content": "from django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_api_key import ApplicationKeyAPI\nfrom application.models import Application\nfrom application.serializers.application_api_key import ApplicationKeySerializer\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result, DefaultResultSerializer\n\n\ndef get_application_operation_object(application_id):\n    application_model = QuerySet(model=Application).filter(id=application_id).first()\n    if application_model is not None:\n        return {\n            \"name\": application_model.name\n        }\n    return {}\n\n\nclass ApplicationKey(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create application ApiKey'),\n        summary=_('Create application ApiKey'),\n        operation_id=_('Create application ApiKey'),  # type: ignore\n        parameters=ApplicationKeyAPI.get_parameters(),\n        request=None,\n        responses=ApplicationKeyAPI.get_response(),\n        tags=[_('Application Api Key')]  # type: ignore\n    )\n    @log(menu='Application', operate=\"Add ApiKey\",\n         get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')),\n         )\n    @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role()\n                     )\n    def post(self, request: Request, workspace_id: str, application_id: str):\n        return result.success(ApplicationKeySerializer(\n            data={'application_id': application_id,\n                  'workspace_id': workspace_id}).generate())\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('GET application ApiKey List'),\n            summary=_('Create application ApiKey List'),\n            operation_id=_('Create application ApiKey List'),  # type: ignore\n            parameters=ApplicationKeyAPI.get_parameters(),\n            responses=ApplicationKeyAPI.List.get_response(),\n            tags=[_('Application Api Key')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, application_id: str, current_page: int, page_size: int):\n            return result.success(ApplicationKeySerializer(\n                data={'application_id': application_id,\n                      'workspace_id': workspace_id,\n                      'order_by': request.query_params.get('order_by')}).page(current_page, page_size))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Modify application API_KEY'),\n            summary=_('Modify application API_KEY'),\n            operation_id=_('Modify application API_KEY'),  # type: ignore\n            parameters=ApplicationKeyAPI.Operate.get_parameters(),\n            request=ApplicationKeyAPI.Operate.get_request(),\n            responses=DefaultResultSerializer,\n            tags=[_('Application Api Key')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Application', operate=\"Modify application API_KEY\",\n             get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')),\n             )\n        def put(self, request: Request, workspace_id: str, application_id: str, api_key_id: str):\n            return result.success(\n                ApplicationKeySerializer.Operate(\n                    data={'workspace_id': workspace_id, 'application_id': application_id,\n                          'api_key_id': api_key_id}).edit(\n                    request.data))\n\n        @extend_schema(\n            methods=['DELETE'],\n            description=_('Delete Application API_KEY'),\n            summary=_('Delete Application API_KEY'),\n            operation_id=_('Delete Application API_KEY'),  # type: ignore\n            parameters=ApplicationKeyAPI.Operate.get_parameters(),\n            request=ApplicationKeyAPI.Operate.get_request(),\n            responses=DefaultResultSerializer,\n            tags=[_('Application Api Key')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_OVERVIEW_API_KEY.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Application', operate=\"Delete application API_KEY\",\n             get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')),\n             )\n        def delete(self, request: Request, workspace_id: str, application_id: str, api_key_id: str):\n            return result.success(\n                ApplicationKeySerializer.Operate(\n                    data={'workspace_id': workspace_id, 'application_id': application_id,\n                          'api_key_id': api_key_id}).delete())\n"
  },
  {
    "path": "apps/application/views/application_chat.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_chat.py\n    @date：2025/6/10 11:00\n    @desc:\n\"\"\"\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\n\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_chat import ApplicationChatQueryAPI, ApplicationChatQueryPageAPI, \\\n    ApplicationChatExportAPI\nfrom application.models import ChatUserType, Application\nfrom application.serializers.application_chat import ApplicationChatQuerySerializers\nfrom chat.api.chat_api import ChatAPI, PromptGenerateAPI\nfrom chat.api.chat_authentication_api import ChatOpenAPI\nfrom chat.serializers.chat import OpenChatSerializers, ChatSerializers, DebugChatSerializers, PromptGenerateSerializer\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result\nfrom common.utils.common import query_params_to_single_dict\n\ndef get_application_operation_object(application_id):\n    application_model = QuerySet(model=Application).filter(id=application_id).first()\n    if application_model is not None:\n        return {\n            'name': application_model.name\n        }\n    return {}\n\nclass ApplicationChat(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get the conversation list\"),\n        summary=_(\"Get the conversation list\"),\n        operation_id=_(\"Get the conversation list\"),  # type: ignore\n        request=ApplicationChatQueryAPI.get_request(),\n        parameters=ApplicationChatQueryAPI.get_parameters(),\n        responses=ApplicationChatQueryAPI.get_response(),\n        tags=[_(\"Application/Conversation Log\")]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str, application_id: str):\n        return result.success(ApplicationChatQuerySerializers(\n            data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id,\n                  'application_id': application_id,\n                  }).list())\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get the conversation list by page\"),\n            summary=_(\"Get the conversation list by page\"),\n            operation_id=_(\"Get the conversation list by page\"),  # type: ignore\n            request=ApplicationChatQueryPageAPI.get_request(),\n            parameters=ApplicationChatQueryPageAPI.get_parameters(),\n            responses=ApplicationChatQueryPageAPI.get_response(),\n            tags=[_(\"Application/Conversation Log\")]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, application_id: str, current_page: int, page_size: int):\n            return result.success(ApplicationChatQuerySerializers(\n                data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id,\n                      'application_id': application_id,\n                      }).page(current_page=current_page,\n                              page_size=page_size))\n\n    class Export(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_(\"Export conversation\"),\n            summary=_(\"Export conversation\"),\n            operation_id=_(\"Export conversation\"),  # type: ignore\n            request=ApplicationChatExportAPI.get_request(),\n            parameters=ApplicationChatExportAPI.get_parameters(),\n            responses=ApplicationChatExportAPI.get_response(),\n            tags=[_(\"Application/Conversation Log\")]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_EXPORT.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_CHAT_LOG_EXPORT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def post(self, request: Request, workspace_id: str, application_id: str):\n            return ApplicationChatQuerySerializers(\n                data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id,\n                      'application_id': application_id,\n                      }).export(request.data)\n\n\nclass OpenView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get a temporary session id based on the application id\"),\n        summary=_(\"Get a temporary session id based on the application id\"),\n        operation_id=_(\"Get a temporary session id based on the application id\"),  # type: ignore\n        parameters=ChatOpenAPI.get_parameters(),\n        responses=None,\n        tags=[_('Application')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str, application_id: str):\n        return result.success(OpenChatSerializers(\n            data={'workspace_id': workspace_id, 'application_id': application_id,\n                  'chat_user_id': str(uuid.uuid7()), 'chat_user_type': ChatUserType.ANONYMOUS_USER,\n                  'debug': True}).open())\n\n\nclass ChatView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"dialogue\"),\n        summary=_(\"dialogue\"),\n        operation_id=_(\"dialogue\"),  # type: ignore\n        request=ChatAPI.get_request(),\n        parameters=ChatAPI.get_parameters(),\n        responses=None,\n        tags=[_('Application')]  # type: ignore\n    )\n    def post(self, request: Request, chat_id: str):\n        return DebugChatSerializers(data={'chat_id': chat_id}).chat(request.data)\n\nclass PromptGenerateView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"generate prompt\"),\n        summary=_(\"generate prompt\"),\n        operation_id=_(\"generate prompt\"),  # type: ignore\n        request=PromptGenerateAPI.get_request(),\n        parameters=PromptGenerateAPI.get_parameters(),\n        responses=None,\n        tags=[_('Application')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    @log(menu='Application', operate='Generate prompt',\n         get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')))\n    def post(self, request: Request, workspace_id: str, model_id:str, application_id: str):\n        return PromptGenerateSerializer(data={'workspace_id': workspace_id, 'model_id': model_id, 'application_id': application_id}).generate_prompt(instance=request.data)"
  },
  {
    "path": "apps/application/views/application_chat_link.py",
    "content": "\"\"\"\n    @project: MaxKB\n    @Author: niu\n    @file: application_chat_link.py\n    @date: 2026/2/9 10:44\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_chat_link import ChatRecordLinkAPI, ChatRecordDetailShareAPI\nfrom application.serializers.application_chat_link import ChatRecordShareLinkSerializer, ChatShareLinkDetailSerializer\nfrom common import result\nfrom common.auth import ChatTokenAuth\n\n\nclass ChatRecordLinkView(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"Generate share link\"),\n        summary=_(\"Generate share link\"),\n        operation_id=_(\"Generate share link\"),  # type: ignore\n        request=ChatRecordLinkAPI.get_request(),\n        parameters=ChatRecordLinkAPI.get_parameters(),\n        responses=ChatRecordLinkAPI.get_response(),\n        tags=[_(\"Chat record link\")]  # type: ignore\n    )\n\n    def post(self, request: Request, application_id: str, chat_id: str):\n        return result.success(ChatRecordShareLinkSerializer(data={\n            \"application_id\": application_id,\n            \"chat_id\": chat_id,\n            \"user_id\": request.auth.chat_user_id\n        }).generate_link(request.data))\n\n\nclass ChatRecordDetailView(APIView):\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get chat record by share link\"),\n        summary=_(\"Get chat record by share link\"),\n        operation_id=_(\"Get chat record by share link\"),  # type: ignore\n        parameters=ChatRecordDetailShareAPI.get_parameters(),\n        responses=ChatRecordDetailShareAPI.get_response(),\n        tags=[_(\"Chat record link\")]  # type: ignore\n    )\n    def get(self, request, link: str):\n        return result.success(\n            ChatShareLinkDetailSerializer(data={'link':link}).get_record_list()\n        )\n"
  },
  {
    "path": "apps/application/views/application_chat_record.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_chat_record.py\n    @date：2025/6/10 15:08\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_chat_record import ApplicationChatRecordQueryAPI, \\\n    ApplicationChatRecordImproveParagraphAPI, ApplicationChatRecordAddKnowledgeAPI\nfrom application.serializers.application_chat_record import ApplicationChatRecordQuerySerializers, \\\n    ApplicationChatRecordImproveSerializer, ChatRecordImproveSerializer, ApplicationChatRecordAddKnowledgeSerializer, \\\n    ChatRecordOperateSerializer\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.utils.common import query_params_to_single_dict\n\n\nclass ApplicationChatRecord(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get the conversation record list\"),\n        summary=_(\"Get the conversation record list\"),\n        operation_id=_(\"Get the conversation record list\"),  # type: ignore\n        request=ApplicationChatRecordQueryAPI.get_request(),\n        parameters=ApplicationChatRecordQueryAPI.get_parameters(),\n        responses=ApplicationChatRecordQueryAPI.get_response(),\n        tags=[_(\"Application/Conversation Log\")]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str):\n        return result.success(ApplicationChatRecordQuerySerializers(\n            data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id,\n                  'application_id': application_id,\n                  'chat_id': chat_id\n                  }).list())\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get the conversation record list by page\"),\n            summary=_(\"Get the conversation record list by page\"),\n            operation_id=_(\"Get the conversation record list by page\"),  # type: ignore\n            request=ApplicationChatRecordQueryAPI.get_request(),\n            parameters=ApplicationChatRecordQueryAPI.get_parameters(),\n            responses=ApplicationChatRecordQueryAPI.get_response(),\n            tags=[_(\"Application/Conversation Log\")]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str, current_page: int,\n                page_size: int):\n            return result.success(ApplicationChatRecordQuerySerializers(\n                data={**query_params_to_single_dict(request.query_params), 'workspace_id': workspace_id,\n                      'application_id': application_id,\n                      'chat_id': chat_id}).page(\n                current_page=current_page,\n                page_size=page_size))\n\n\nclass ApplicationChatRecordOperateAPI(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get conversation record details\"),\n        summary=_(\"Get conversation record details\"),\n        operation_id=_(\"Get conversation record details\"),  # type: ignore\n        request=ApplicationChatRecordQueryAPI.get_request(),\n        parameters=ApplicationChatRecordQueryAPI.get_parameters(),\n        responses=ApplicationChatRecordQueryAPI.get_response(),\n        tags=[_(\"Application/Conversation Log\")]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_CHAT_LOG_READ.get_workspace_permission_workspace_manage_role(),\n                     PermissionConstants.APPLICATION_READ.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str, chat_record_id: str):\n        return result.success(ChatRecordOperateSerializer(\n            data={\n                'workspace_id': workspace_id,\n                'application_id': application_id,\n                'chat_id': chat_id,\n                'chat_record_id': chat_record_id}).one(True))\n\n\nclass ApplicationChatRecordAddKnowledge(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"Add to Knowledge Base\"),\n        summary=_(\"Add to Knowledge Base\"),\n        operation_id=_(\"Add to Knowledge Base\"),  # type: ignore\n        request=ApplicationChatRecordAddKnowledgeAPI.get_request(),\n        parameters=ApplicationChatRecordAddKnowledgeAPI.get_parameters(),\n        responses=ApplicationChatRecordAddKnowledgeAPI.get_response(),\n        tags=[_(\"Application/Conversation Log\")]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_ADD_KNOWLEDGE.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_CHAT_LOG_ADD_KNOWLEDGE.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def post(self, request: Request, workspace_id: str, application_id: str):\n        return result.success(ApplicationChatRecordAddKnowledgeSerializer(data = {'workspace_id': workspace_id, 'application_id': application_id, **request.data}).post_improve(\n            {'workspace_id': workspace_id, 'application_id': application_id, **request.data}, request=request))\n\n\nclass ApplicationChatRecordImprove(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get the list of marked paragraphs\"),\n        summary=_(\"Get the list of marked paragraphs\"),\n        operation_id=_(\"Get the list of marked paragraphs\"),  # type: ignore\n        request=ApplicationChatRecordQueryAPI.get_request(),\n        parameters=ApplicationChatRecordQueryAPI.get_parameters(),\n        responses=ApplicationChatRecordQueryAPI.get_response(),\n        tags=[_(\"Application/Conversation Log\")]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str, application_id: str, chat_id: str, chat_record_id: str):\n        return result.success(ChatRecordImproveSerializer(\n            data={'workspace_id': workspace_id, 'application_id': application_id, 'chat_id': chat_id,\n                  'chat_record_id': chat_record_id}).get())\n\n\nclass ApplicationChatRecordImproveParagraph(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['PUT'],\n        description=_(\"Annotation\"),\n        summary=_(\"Annotation\"),\n        operation_id=_(\"Annotation\"),  # type: ignore\n        request=ApplicationChatRecordImproveParagraphAPI.get_request(),\n        parameters=ApplicationChatRecordImproveParagraphAPI.get_parameters(),\n        responses=ApplicationChatRecordImproveParagraphAPI.get_response(),\n        tags=[_(\"Application/Conversation Log\")]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def put(self, request: Request,\n            workspace_id: str,\n            application_id: str,\n            chat_id: str,\n            chat_record_id: str,\n            knowledge_id: str,\n            document_id: str):\n        return result.success(ApplicationChatRecordImproveSerializer(\n            data={'workspace_id': workspace_id, 'application_id': application_id, 'chat_id': chat_id,\n                  'chat_record_id': chat_record_id,\n                  'knowledge_id': knowledge_id, 'document_id': document_id}).improve(request.data, request=request))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['DELETE'],\n            description=_(\"Delete a Annotation\"),\n            summary=_(\"Delete a Annotation\"),\n            operation_id=_(\"Delete a Annotation\"),  # type: ignore\n            request=ApplicationChatRecordImproveParagraphAPI.Operate.get_request(),\n            parameters=ApplicationChatRecordImproveParagraphAPI.Operate.get_parameters(),\n            responses=ApplicationChatRecordImproveParagraphAPI.Operate.get_response(),\n            tags=[_(\"Application/Conversation Log\")]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_CHAT_LOG_ANNOTATION.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def delete(self, request: Request, workspace_id: str, application_id: str, chat_id: str, chat_record_id: str,\n                   knowledge_id: str,\n                   document_id: str, paragraph_id: str):\n            return result.success(ApplicationChatRecordImproveSerializer.Operate(\n                data={'chat_id': chat_id, 'chat_record_id': chat_record_id, 'workspace_id': workspace_id,\n                      'application_id': application_id,\n                      'knowledge_id': knowledge_id, 'document_id': document_id,\n                      'paragraph_id': paragraph_id}).delete(request=request))\n"
  },
  {
    "path": "apps/application/views/application_stats.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_stats.py\n    @date：2025/6/9 20:30\n    @desc:\n\"\"\"\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_stats import ApplicationStatsAPI\nfrom application.serializers.application_stats import ApplicationStatisticsSerializer\nfrom common import result\nfrom common.auth import TokenAuth\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\n\n\nclass ApplicationStats(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Dialogue-related statistical trends'),\n        summary=_('Dialogue-related statistical trends'),\n        operation_id=_('Dialogue-related statistical trends'),  # type: ignore\n        parameters=ApplicationStatsAPI.get_parameters(),\n        responses=ApplicationStatsAPI.get_response(),\n        tags=[_('Application')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str, application_id: str):\n        return result.success(\n            ApplicationStatisticsSerializer(data={'application_id': application_id, 'workspace_id': workspace_id,\n                                                  'start_time': request.query_params.get(\n                                                      'start_time'),\n                                                  'end_time': request.query_params.get(\n                                                      'end_time')\n                                                  }).get_chat_record_aggregate_trend())\n\n    class TokenUsageStatistics(APIView):\n        authentication_classes = [TokenAuth]\n\n        # 应用的token使用统计 根据人的使用数排序\n        @extend_schema(\n            methods=['GET'],\n            description=_('Application token usage statistics'),\n            summary=_('Application token usage statistics'),\n            operation_id=_('Application token usage statistics'),  # type: ignore\n            parameters=ApplicationStatsAPI.get_parameters(),\n            responses=ApplicationStatsAPI.get_response(),\n            tags=[_('Application')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, application_id: str):\n            return result.success(\n                ApplicationStatisticsSerializer(data={'application_id': application_id, 'workspace_id': workspace_id,\n                                                      'start_time': request.query_params.get(\n                                                          'start_time'),\n                                                      'end_time': request.query_params.get(\n                                                          'end_time')\n                                                      }).get_token_usage_statistics())\n\n    class TopQuestionsStatistics(APIView):\n        authentication_classes = [TokenAuth]\n        # 应用的top问题统计\n        @extend_schema(\n            methods=['GET'],\n            description=_('Application top question statistics'),\n            summary=_('Application top question statistics'),\n            operation_id=_('Application top question statistics'),  # type: ignore\n            parameters=ApplicationStatsAPI.get_parameters(),\n            responses=ApplicationStatsAPI.get_response(),\n            tags=[_('Application')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_OVERVIEW_READ.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, application_id: str):\n            return result.success(\n                ApplicationStatisticsSerializer(data={'application_id': application_id, 'workspace_id': workspace_id,\n                                                      'start_time': request.query_params.get(\n                                                          'start_time'),\n                                                      'end_time': request.query_params.get(\n                                                          'end_time')\n                                                      }).get_top_questions_statistics())\n"
  },
  {
    "path": "apps/application/views/application_version.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_version.py.py\n    @date：2025/6/3 15:46\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_version import ApplicationVersionListAPI, ApplicationVersionPageAPI, \\\n    ApplicationVersionOperateAPI\nfrom application.serializers.application_version import ApplicationVersionSerializer\nfrom application.views import get_application_operation_object\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\n\n\nclass ApplicationVersionView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get the application version list\"),\n        summary=_(\"Get the application version list\"),\n        operation_id=_(\"Get the application version list\"),  # type: ignore\n        parameters=ApplicationVersionListAPI.get_parameters(),\n        responses=ApplicationVersionListAPI.get_response(),\n        tags=[_('Application/Version')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(),\n                     PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id, application_id: str):\n        return result.success(\n            ApplicationVersionSerializer.Query(\n                data={'workspace_id': workspace_id}).list(\n                {'name': request.query_params.get(\"name\"), 'application_id': application_id}))\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get the list of application versions by page\"),\n            summary=_(\"Get the list of application versions by page\"),\n            operation_id=_(\"Get the list of application versions by page\"),  # type: ignore\n            parameters=ApplicationVersionPageAPI.get_parameters(),\n            responses=ApplicationVersionPageAPI.get_response(),\n            tags=[_('Application/Version')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_READ.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, application_id: str, current_page: int, page_size: int):\n            return result.success(\n                ApplicationVersionSerializer.Query(\n                    data={'workspace_id': workspace_id}).page(\n                    {'name': request.query_params.get(\"name\"), 'application_id': application_id},\n                    current_page, page_size))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get application version details\"),\n            summary=_(\"Get application version details\"),\n            operation_id=_(\"Get application version details\"),  # type: ignore\n            parameters=ApplicationVersionOperateAPI.get_parameters(),\n            responses=ApplicationVersionOperateAPI.get_response(),\n            tags=[_('Application/Version')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, application_id: str, application_version_id: str):\n            return result.success(\n                ApplicationVersionSerializer.Operate(\n                    data={'user_id': request.user, 'workspace_id': workspace_id,\n                          'application_id': application_id, 'application_version_id': application_version_id}).one())\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_(\"Modify application version information\"),\n            summary=_(\"Modify application version information\"),\n            operation_id=_(\"Modify application version information\"),  # type: ignore\n            parameters=ApplicationVersionOperateAPI.get_parameters(),\n            request=None,\n            responses=ApplicationVersionOperateAPI.get_response(),\n            tags=[_('Application/Version')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission(),\n                         PermissionConstants.APPLICATION_EDIT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.APPLICATION.get_workspace_application_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Application', operate=\"Modify application version information\",\n             get_operation_object=lambda r, k: get_application_operation_object(k.get('application_id')),\n             )\n        def put(self, request: Request, workspace_id: str, application_id: str, application_version_id: str):\n            return result.success(\n                ApplicationVersionSerializer.Operate(\n                    data={'application_id': application_id, 'workspace_id': workspace_id,\n                          'application_version_id': application_version_id,\n                          'user_id': request.user.id}).edit(\n                    request.data))\n"
  },
  {
    "path": "apps/chat/__init__.py",
    "content": ""
  },
  {
    "path": "apps/chat/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "apps/chat/api/chat_api.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： chat_api.py\n    @date：2025/6/9 15:23\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom application.serializers.application_chat_record import ChatRecordSerializerModel\nfrom chat.serializers.chat import ChatMessageSerializers, GeneratePromptSerializers\nfrom chat.serializers.chat_record import HistoryChatModel, EditAbstractSerializer\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, ResultPageSerializer, DefaultResultSerializer\n\n\nclass PromptGenerateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"workspace_id\",\n            description=\"工作空间id\",\n            type=OpenApiTypes.STR,\n            location='path',\n            required=True,\n        ),\n            OpenApiParameter(\n                name=\"model_id\",\n                description=\"模型id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n        ),\n            OpenApiParameter(\n                name=\"application_id\",\n                description=\"应用id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return GeneratePromptSerializers\n\n\nclass ChatAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"chat_id\",\n            description=\"对话id\",\n            type=OpenApiTypes.STR,\n            location='path',\n            required=True,\n        )]\n\n    @staticmethod\n    def get_request():\n        return ChatMessageSerializers\n\n\nclass ApplicationCreateResponse(ResultSerializer):\n    def get_data(self):\n        return HistoryChatModel(many=True)\n\n\nclass PageApplicationCreateResponse(ResultPageSerializer):\n    def get_data(self):\n        return HistoryChatModel(many=True)\n\n\nclass ApplicationRecordResponse(ResultSerializer):\n    def get_data(self):\n        return ChatRecordSerializerModel(many=True)\n\n\nclass PageApplicationRecordResponse(ResultPageSerializer):\n    def get_data(self):\n        return ChatRecordSerializerModel(many=True)\n\n\nclass HistoricalConversationAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return []\n\n    @staticmethod\n    def get_response():\n        return ApplicationCreateResponse\n\n\nclass PageHistoricalConversationAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return []\n\n    @staticmethod\n    def get_response():\n        return PageApplicationCreateResponse\n\n\nclass HistoricalConversationOperateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"chat_id\",\n            description=\"对话id\",\n            type=OpenApiTypes.STR,\n            location='path',\n            required=True\n        )]\n\n    @staticmethod\n    def get_request():\n        return EditAbstractSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass HistoricalConversationRecordAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"chat_id\",\n            description=\"对话id\",\n            type=OpenApiTypes.STR,\n            location='path',\n            required=True,\n        )]\n\n    @staticmethod\n    def get_response():\n        return ApplicationRecordResponse\n\n\nclass PageHistoricalConversationRecordAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"chat_id\",\n            description=\"对话id\",\n            type=OpenApiTypes.STR,\n            location='path',\n            required=True,\n        )]\n\n    @staticmethod\n    def get_response():\n        return PageApplicationRecordResponse\n"
  },
  {
    "path": "apps/chat/api/chat_authentication_api.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： chat_authentication_api.py\n    @date：2025/6/6 19:59\n    @desc:\n\"\"\"\n\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom chat.serializers.chat import OpenAIInstanceSerializer\nfrom chat.serializers.chat_authentication import AnonymousAuthenticationSerializer\nfrom common.mixins.api_mixin import APIMixin\n\n\nclass OpenAIAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return OpenAIInstanceSerializer\n\n\nclass ChatAuthenticationAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return AnonymousAuthenticationSerializer\n\n    @staticmethod\n    def get_parameters():\n        pass\n\n    @staticmethod\n    def get_response():\n        pass\n\n\nclass ChatAuthenticationProfileAPI(APIMixin):\n\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"access_token\",\n            description=_(\"access_token\"),\n            type=OpenApiTypes.STR,\n            location='query',\n            required=True,\n        )]\n\n\nclass ChatOpenAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return []\n"
  },
  {
    "path": "apps/chat/api/chat_embed_api.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： chat_embed_api.py\n    @date：2025/5/30 15:25\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.result import DefaultResultSerializer\n\n\nclass ChatEmbedAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"host\",\n                description=_(\"host\"),\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"protocol\",\n                description=_(\"protocol\"),\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"token\",\n                description=_(\"token\"),\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            )\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n"
  },
  {
    "path": "apps/chat/api/vote_api.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： vote_api.py\n    @date：2025/6/23 17:35\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom chat.serializers.chat_record import VoteRequest\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import DefaultResultSerializer\n\n\nclass VoteAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return VoteRequest\n\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"chat_id\",\n            description=_(\"Chat ID\"),\n            type=OpenApiTypes.STR,\n            location='path',\n            required=True,\n        ),\n            OpenApiParameter(\n                name=\"chat_record_id\",\n                description=_(\"Chat Record ID\"),\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n"
  },
  {
    "path": "apps/chat/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass ChatConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'chat'\n"
  },
  {
    "path": "apps/chat/mcp/__init__.py",
    "content": ""
  },
  {
    "path": "apps/chat/mcp/tools.py",
    "content": "import json\n\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\n\nfrom application.models import ApplicationApiKey, Application, ChatUserType, ChatSourceChoices\nfrom chat.serializers.chat import ChatSerializers\n\n\nclass MCPToolHandler:\n    def __init__(self, auth_header):\n        app_key = QuerySet(ApplicationApiKey).filter(secret_key=auth_header, is_active=True).first()\n        if not app_key:\n            raise PermissionError(\"Invalid API Key\")\n\n        self.application = QuerySet(Application).filter(id=app_key.application_id, is_publish=True).first()\n        if not self.application:\n            raise PermissionError(\"Application is not found or not published\")\n\n    def initialize(self):\n        return {\n            \"protocolVersion\": \"2025-06-18\",\n            \"serverInfo\": {\n                \"name\": \"maxkb-mcp\",\n                \"version\": \"1.0.0\"\n            },\n            \"capabilities\": {\n                \"tools\": {}\n            }\n        }\n\n    def list_tools(self):\n        return {\n            \"tools\": [\n                {\n                    \"name\": f'agent_{str(self.application.id)[:8]}',\n                    \"description\": f'{self.application.name} {self.application.desc}',\n                    \"inputSchema\": {\n                        \"type\": \"object\",\n                        \"properties\": {\n                            \"message\": {\"type\": \"string\", \"description\": \"The message to send to the AI.\"},\n                        },\n                        \"required\": [\"message\"]\n                    }\n                }\n            ]\n        }\n\n    def _get_chat_id(self):\n        from application.models import ChatUserType\n        from chat.serializers.chat import OpenChatSerializers\n        from common.init import init_template\n\n        init_template.run()\n\n        return OpenChatSerializers(data={\n            'application_id': self.application.id,\n            'chat_user_id': str(uuid.uuid7()),\n            'chat_user_type': ChatUserType.ANONYMOUS_USER,\n            'ip_address': '-',\n            'source': {\"type\": ChatSourceChoices.ONLINE.value},\n            'debug': False\n        }).open()\n\n    def call_tool(self, params):\n        name = params[\"name\"]\n        args = params.get(\"arguments\", {})\n        # print(params)\n\n        payload = {\n            'message': args.get('message'),\n            'stream': False,\n            're_chat': False\n        }\n        resp = ChatSerializers(data={\n            'chat_id': self._get_chat_id(),\n            'chat_user_id': str(uuid.uuid7()),\n            'chat_user_type': ChatUserType.ANONYMOUS_USER,\n            'application_id': self.application.id,\n            'ip_address': '-',\n            'source': {\"type\": ChatSourceChoices.ONLINE.value},\n            'debug': False,\n        }).chat(payload)\n        data = json.loads(str(resp.text))\n\n        return {\"content\": [{\"type\": \"text\", \"text\": data.get('data', {}).get('content')}]}\n"
  },
  {
    "path": "apps/chat/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "apps/chat/models/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/5/29 16:08\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/chat/serializers/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/5/29 16:08\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/chat/serializers/chat.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： chat.py\n    @date：2025/6/9 11:23\n    @desc:\n\"\"\"\nimport json\nimport os\nfrom gettext import gettext\nfrom typing import List, Dict\n\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom langchain_core.messages import HumanMessage, AIMessage, SystemMessage\nfrom rest_framework import serializers\n\nfrom application.chat_pipeline.pipeline_manage import PipelineManage\nfrom application.chat_pipeline.step.chat_step.i_chat_step import PostResponseHandler\nfrom application.chat_pipeline.step.chat_step.impl.base_chat_step import BaseChatStep\nfrom application.chat_pipeline.step.generate_human_message_step.impl.base_generate_human_message_step import \\\n    BaseGenerateHumanMessageStep\nfrom application.chat_pipeline.step.reset_problem_step.impl.base_reset_problem_step import BaseResetProblemStep\nfrom application.chat_pipeline.step.search_dataset_step.impl.base_search_dataset_step import BaseSearchDatasetStep\nfrom application.flow.common import Answer, Workflow\nfrom application.flow.i_step_node import WorkFlowPostHandler\nfrom application.flow.tools import to_stream_response_simple\nfrom application.flow.workflow_manage import WorkflowManage\nfrom application.models import Application, ApplicationTypeChoices, \\\n    ChatUserType, ApplicationChatUserStats, ApplicationAccessToken, ChatRecord, Chat, ApplicationVersion\nfrom application.serializers.application import ApplicationOperateSerializer\nfrom application.serializers.common import ChatInfo\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.exception.app_exception import AppApiException, AppChatNumOutOfBoundsFailed, ChatException\nfrom common.handle.base_to_response import BaseToResponse\nfrom common.handle.impl.response.openai_to_response import OpenaiToResponse\nfrom common.handle.impl.response.system_to_response import SystemToResponse\nfrom common.utils.common import flat_map, get_file_content, is_valid_uuid\nfrom knowledge.models import Document, Paragraph\nfrom maxkb.conf import PROJECT_DIR\nfrom models_provider.models import Model, Status\nfrom models_provider.tools import get_model_instance_by_model_workspace_id\nfrom system_manage.models.resource_mapping import ResourceMapping\n\n\nclass ChatMessagesSerializers(serializers.Serializer):\n    role = serializers.CharField(required=True, label=_(\"Role\"))\n    content = serializers.CharField(required=True, label=_(\"Content\"))\n\n\nclass GeneratePromptSerializers(serializers.Serializer):\n    prompt = serializers.CharField(required=True, label=_(\"Prompt template\"))\n    messages = serializers.ListSerializer(child=ChatMessagesSerializers(), required=True, label=_(\"Chat context\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        messages = self.data.get(\"messages\")\n\n        if len(messages) > 30:\n            raise AppApiException(400, _(\"Too many messages\"))\n\n        for index in range(len(messages)):\n            role = messages[index].get('role')\n            if role == 'ai' and index % 2 != 1:\n                raise AppApiException(400, _(\"Authentication failed. Please verify that the parameters are correct.\"))\n            if role == 'user' and index % 2 != 0:\n                raise AppApiException(400, _(\"Authentication failed. Please verify that the parameters are correct.\"))\n            if role not in ['user', 'ai']:\n                raise AppApiException(400, _(\"Authentication failed. Please verify that the parameters are correct.\"))\n\n\nclass ChatMessageSerializers(serializers.Serializer):\n    message = serializers.CharField(required=True, label=_(\"User Questions\"))\n    stream = serializers.BooleanField(required=True,\n                                      label=_(\"Is the answer in streaming mode\"))\n    re_chat = serializers.BooleanField(required=True, label=_(\"Do you want to reply again\"))\n    chat_record_id = serializers.UUIDField(required=False, allow_null=True,\n                                           label=_(\"Conversation record id\"))\n\n    node_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,\n                                    label=_(\"Node id\"))\n\n    runtime_node_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,\n                                            label=_(\"Runtime node id\"))\n\n    node_data = serializers.DictField(required=False, allow_null=True,\n                                      label=_(\"Node parameters\"))\n\n    form_data = serializers.DictField(required=False, label=_(\"Global variables\"))\n    image_list = serializers.ListField(required=False, label=_(\"picture\"))\n    document_list = serializers.ListField(required=False, label=_(\"document\"))\n    audio_list = serializers.ListField(required=False, label=_(\"Audio\"))\n    other_list = serializers.ListField(required=False, label=_(\"Other\"))\n    child_node = serializers.DictField(required=False, allow_null=True,\n                                       label=_(\"Child Nodes\"))\n\n\ndef get_post_handler(chat_info: ChatInfo):\n    class PostHandler(PostResponseHandler):\n\n        def handler(self,\n                    chat_id,\n                    chat_record_id,\n                    paragraph_list: List[Paragraph],\n                    problem_text: str,\n                    answer_text,\n                    manage: PipelineManage,\n                    step: BaseChatStep,\n                    padding_problem_text: str = None,\n                    **kwargs):\n            answer_list = [[Answer(answer_text, 'ai-chat-node', 'ai-chat-node', 'ai-chat-node', {}, 'ai-chat-node',\n                                   kwargs.get('reasoning_content', '')).to_dict()]]\n            chat_record = ChatRecord(id=chat_record_id,\n                                     chat_id=chat_id,\n                                     problem_text=problem_text,\n                                     answer_text=answer_text,\n                                     details=manage.get_details(),\n                                     message_tokens=manage.context['message_tokens'],\n                                     answer_tokens=manage.context['answer_tokens'],\n                                     answer_text_list=answer_list,\n                                     run_time=manage.context['run_time'],\n                                     index=len(chat_info.chat_record_list) + 1,\n                                     ip_address=chat_info.ip_address,\n                                     source=chat_info.source\n                                     )\n            chat_info.append_chat_record(chat_record)\n            # 重新设置缓存\n            chat_info.set_cache()\n\n    return PostHandler()\n\n\nclass DebugChatSerializers(serializers.Serializer):\n    chat_id = serializers.UUIDField(required=True, label=_(\"Conversation ID\"))\n\n    def chat(self, instance: dict, base_to_response: BaseToResponse = SystemToResponse()):\n        self.is_valid(raise_exception=True)\n        chat_id = self.data.get('chat_id')\n        chat_info: ChatInfo = ChatInfo.get_cache(chat_id)\n        application = QuerySet(Application).filter(id=chat_info.application_id).first()\n        chat_info.application = application\n        return ChatSerializers(data={\n            'chat_id': chat_id, \"chat_user_id\": chat_info.chat_user_id,\n            \"chat_user_type\": chat_info.chat_user_type,\n            \"application_id\": chat_info.application.id, \"debug\": True\n        }).chat(instance, base_to_response)\n\n\nSYSTEM_ROLE = get_file_content(os.path.join(PROJECT_DIR, \"apps\", \"chat\", 'template', 'generate_prompt_system'))\n\n\nclass PromptGenerateSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, label=_('Workspace ID'))\n    model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_(\"Model\"))\n    application_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_(\"Application\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Application).filter(id=self.data.get('application_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        application = query_set.first()\n        if application is None:\n            raise AppApiException(500, _('Application id does not exist'))\n        return application\n\n    def generate_prompt(self, instance: dict):\n        application = self.is_valid(raise_exception=True)\n        GeneratePromptSerializers(data=instance).is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        model_id = self.data.get('model_id')\n        prompt = instance.get('prompt')\n        messages = instance.get('messages')\n\n        message = messages[-1]['content']\n        q = prompt.replace(\"{userInput}\", message)\n\n        messages[-1]['content'] = q\n        SUPPORTED_MODEL_TYPES = [\"LLM\", \"IMAGE\"]\n        model_exist = QuerySet(Model).filter(\n            id=model_id,\n            model_type__in=SUPPORTED_MODEL_TYPES\n        ).exists()\n        if not model_exist:\n            raise Exception(_(\"Model does not exists or is not an LLM model\"))\n\n        def process():\n            model = get_model_instance_by_model_workspace_id(model_id=model_id, workspace_id=workspace_id,\n                                                             **application.model_params_setting)\n            try:\n                for r in model.stream([SystemMessage(content=SYSTEM_ROLE),\n                                       *[HumanMessage(content=m.get('content')) if m.get(\n                                           'role') == 'user' else AIMessage(\n                                           content=m.get('content')) for m in messages]]):\n                    yield 'data: ' + json.dumps({'content': r.content}) + '\\n\\n'\n            except Exception as e:\n                yield 'data: ' + json.dumps({'error': str(e)}) + '\\n\\n'\n\n        return to_stream_response_simple(process())\n\n\nclass OpenAIMessage(serializers.Serializer):\n    content = serializers.CharField(required=True, label=_('content'))\n    role = serializers.CharField(required=True, label=_('Role'))\n\n\nclass OpenAIInstanceSerializer(serializers.Serializer):\n    messages = serializers.ListField(child=OpenAIMessage())\n    chat_id = serializers.UUIDField(required=False, label=_(\"Conversation ID\"))\n    re_chat = serializers.BooleanField(required=False, label=_(\"Regenerate\"))\n    stream = serializers.BooleanField(required=False, label=_(\"Streaming Output\"))\n\n\nclass OpenAIChatSerializer(serializers.Serializer):\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n    chat_user_id = serializers.CharField(required=True, label=_(\"Client id\"))\n    chat_user_type = serializers.CharField(required=True, label=_(\"Client Type\"))\n    ip_address = serializers.CharField(required=False, label=_(\"IP Address\"))\n    source = serializers.JSONField(required=False, label=_(\"Source\"))\n\n    @staticmethod\n    def get_message(instance):\n        return instance.get('messages')[-1].get('content')\n\n    @staticmethod\n    def generate_chat(chat_id, application_id, message, chat_user_id, chat_user_type, ip_address, source):\n        if chat_id is None:\n            chat_id = str(uuid.uuid1())\n            chat_info = ChatInfo(chat_id, chat_user_id, chat_user_type, ip_address, source, [], [],\n                                 application_id)\n            chat_info.set_cache()\n        else:\n            chat_info = ChatInfo.get_cache(chat_id)\n            if chat_info is None:\n                open_chat = ChatSerializers(data={\n                    'chat_id': chat_id,\n                    'chat_user_id': chat_user_id,\n                    'chat_user_type': chat_user_type,\n                    'application_id': application_id,\n                    'ip_address': ip_address,\n                    'source': source,\n                })\n                open_chat.is_valid(raise_exception=True)\n                chat_info = open_chat.re_open_chat(chat_id)\n                chat_info.set_cache()\n        return chat_id\n\n    def chat(self, instance: Dict, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            OpenAIInstanceSerializer(data=instance).is_valid(raise_exception=True)\n        chat_id = instance.get('chat_id')\n        message = self.get_message(instance)\n        re_chat = instance.get('re_chat', False)\n        stream = instance.get('stream', False)\n        application_id = self.data.get('application_id')\n        chat_user_id = self.data.get('chat_user_id')\n        chat_user_type = self.data.get('chat_user_type')\n        ip_address = self.data.get('ip_address')\n        source = self.data.get('source')\n        chat_id = self.generate_chat(chat_id, application_id, message, chat_user_id, chat_user_type, ip_address, source)\n        return ChatSerializers(\n            data={\n                'chat_id': chat_id,\n                'chat_user_id': chat_user_id,\n                'chat_user_type': chat_user_type,\n                'application_id': application_id,\n                'ip_address': ip_address,\n                'source': source,\n            }\n        ).chat({'message': message,\n                're_chat': re_chat,\n                'stream': stream,\n                'form_data': instance.get('form_data', {}),\n                'image_list': instance.get('image_list', []),\n                'document_list': instance.get('document_list', []),\n                'audio_list': instance.get('audio_list', []),\n                'other_list': instance.get('other_list', [])},\n               base_to_response=OpenaiToResponse())\n\n\nclass ChatSerializers(serializers.Serializer):\n    chat_id = serializers.UUIDField(required=True, label=_(\"Conversation ID\"))\n    chat_user_id = serializers.CharField(required=True, label=_(\"Client id\"))\n    chat_user_type = serializers.CharField(required=True, label=_(\"Client Type\"))\n    application_id = serializers.UUIDField(required=True, allow_null=True,\n                                           label=_(\"Application ID\"))\n    debug = serializers.BooleanField(required=False, label=_(\"Debug\"))\n    ip_address = serializers.CharField(required=False, label=_(\"IP Address\"), allow_null=True, allow_blank=True)\n    source = serializers.JSONField(required=False, label=_(\"Source\"))\n\n    def is_valid_application_workflow(self, *, raise_exception=False):\n        self.is_valid_intraday_access_num()\n\n    def is_valid_chat_id(self, chat_info: ChatInfo):\n        if self.data.get('application_id') is not None and self.data.get('application_id') != str(\n                chat_info.application_id):\n            raise ChatException(500, _(\"Conversation does not exist\"))\n\n    def is_valid_intraday_access_num(self):\n        if not self.data.get('debug') and [ChatUserType.ANONYMOUS_USER.value,\n                                           ChatUserType.CHAT_USER.value].__contains__(\n            self.data.get('chat_user_type')):\n            access_client = QuerySet(ApplicationChatUserStats).filter(chat_user_id=self.data.get('chat_user_id'),\n                                                                      application_id=self.data.get(\n                                                                          'application_id')).first()\n            if access_client is None:\n                access_client = ApplicationChatUserStats(chat_user_id=self.data.get('chat_user_id'),\n                                                         chat_user_type=self.data.get('chat_user_type'),\n                                                         application_id=self.data.get('application_id'),\n                                                         access_num=0,\n                                                         intraday_access_num=0)\n                access_client.save()\n\n            application_access_token = QuerySet(ApplicationAccessToken).filter(\n                application_id=self.data.get('application_id')).first()\n            if application_access_token.access_num <= access_client.intraday_access_num:\n                raise AppChatNumOutOfBoundsFailed(1002, _(\"The number of visits exceeds today's visits\"))\n\n    def is_valid_application_simple(self, *, chat_info: ChatInfo, raise_exception=False):\n        self.is_valid_intraday_access_num()\n        model_id = chat_info.application.model_id\n        if model_id is None:\n            return chat_info\n        model = QuerySet(Model).filter(id=model_id).first()\n        if model is None:\n            return chat_info\n        if model.status == Status.ERROR:\n            raise ChatException(500, _(\"The current model is not available\"))\n        if model.status == Status.DOWNLOAD:\n            raise ChatException(500, _(\"The model is downloading, please try again later\"))\n        return chat_info\n\n    def chat_simple(self, chat_info: ChatInfo, instance, base_to_response):\n        message = instance.get('message')\n        re_chat = instance.get('re_chat')\n        stream = instance.get('stream')\n        chat_user_id = self.data.get('chat_user_id')\n        chat_user_type = self.data.get('chat_user_type')\n        ip_address = self.data.get('ip_address')\n        source = self.data.get('source')\n        form_data = instance.get(\"form_data\")\n        chat_record_id = instance.get('chat_record_id')\n        pipeline_manage_builder = PipelineManage.builder()\n        # 如果开启了问题优化,则添加上问题优化步骤\n        if chat_info.application.problem_optimization:\n            pipeline_manage_builder.append_step(BaseResetProblemStep)\n        # 构建流水线管理器\n        pipeline_message = (pipeline_manage_builder.append_step(BaseSearchDatasetStep)\n                            .append_step(BaseGenerateHumanMessageStep)\n                            .append_step(BaseChatStep)\n                            .add_base_to_response(base_to_response)\n                            .add_debug(self.data.get('debug', False))\n                            .build())\n        exclude_paragraph_id_list = []\n        # 相同问题是否需要排除已经查询到的段落\n        if re_chat:\n            paragraph_id_list = flat_map(\n                [[paragraph.get('id') for paragraph in chat_record.details['search_step']['paragraph_list']] for\n                 chat_record in chat_info.chat_record_list if\n                 chat_record.problem_text == message and 'search_step' in chat_record.details and 'paragraph_list' in\n                 chat_record.details['search_step']])\n            exclude_paragraph_id_list = list(set(paragraph_id_list))\n        # 构建运行参数\n        params = chat_info.to_pipeline_manage_params(message, get_post_handler(chat_info), exclude_paragraph_id_list,\n                                                     chat_user_id, chat_user_type, ip_address, source, stream,\n                                                     form_data)\n        if chat_record_id:\n            params['chat_record_id'] = chat_record_id\n        chat_info.set_chat(message)\n        # 运行流水线作业\n        pipeline_message.run(params)\n        return pipeline_message.context['chat_result']\n\n    @staticmethod\n    def get_chat_record(chat_info, chat_record_id):\n        if chat_info is not None:\n            chat_record_list = [chat_record for chat_record in chat_info.chat_record_list if\n                                str(chat_record.id) == str(chat_record_id)]\n            if chat_record_list is not None and len(chat_record_list):\n                return chat_record_list[-1]\n        chat_record = QuerySet(ChatRecord).filter(id=chat_record_id, chat_id=chat_info.chat_id).first()\n        if chat_record is None:\n            if not is_valid_uuid(chat_record_id):\n                raise ChatException(500, _(\"Conversation record does not exist\"))\n        chat_record = QuerySet(ChatRecord).filter(id=chat_record_id).first()\n        return chat_record\n\n    def chat_work_flow(self, chat_info: ChatInfo, instance: dict, base_to_response):\n        message = instance.get('message')\n        re_chat = instance.get('re_chat')\n        stream = instance.get('stream')\n        chat_user_id = self.data.get(\"chat_user_id\")\n        chat_user_type = self.data.get('chat_user_type')\n        ip_address = self.data.get('ip_address')\n        source = self.data.get('source')\n        form_data = instance.get('form_data')\n        image_list = instance.get('image_list')\n        video_list = instance.get('video_list')\n        document_list = instance.get('document_list')\n        audio_list = instance.get('audio_list')\n        other_list = instance.get('other_list')\n        workspace_id = chat_info.application.workspace_id\n        chat_record_id = instance.get('chat_record_id')\n        debug = self.data.get('debug', False)\n        chat_record = None\n        history_chat_record = chat_info.chat_record_list\n        if chat_record_id is not None:\n            chat_record = self.get_chat_record(chat_info, chat_record_id)\n            if chat_record:\n                history_chat_record = [r for r in chat_info.chat_record_list if str(r.id) != chat_record_id]\n        work_flow = chat_info.application.work_flow\n        work_flow_manage = WorkflowManage(Workflow.new_instance(work_flow),\n                                          {'history_chat_record': history_chat_record, 'question': message,\n                                           'chat_id': chat_info.chat_id, 'chat_record_id': str(\n                                              uuid.uuid7()) if chat_record_id is None else str(chat_record_id),\n                                           'stream': stream,\n                                           're_chat': re_chat,\n                                           'chat_user_id': chat_user_id,\n                                           'chat_user_type': chat_user_type,\n                                           'ip_address': ip_address,\n                                           'source': source,\n                                           'workspace_id': workspace_id,\n                                           'debug': debug,\n                                           'chat_user': chat_info.get_chat_user(),\n                                           'chat_user_group': chat_info.get_chat_user_group(),\n                                           'application_id': str(chat_info.application_id)},\n                                          WorkFlowPostHandler(chat_info),\n                                          base_to_response, form_data, image_list, document_list, audio_list,\n                                          video_list,\n                                          other_list,\n                                          instance.get('runtime_node_id'),\n                                          instance.get('node_data'), chat_record, instance.get('child_node'))\n        chat_info.set_chat(message)\n        r = work_flow_manage.run()\n        return r\n\n    def is_valid_chat_user(self):\n        chat_user_id = self.data.get('chat_user_id')\n        application_id = self.data.get('application_id')\n        chat_user_type = self.data.get('chat_user_type')\n        is_auth_chat_user = DatabaseModelManage.get_model(\"is_auth_chat_user\")\n        application_access_token = QuerySet(ApplicationAccessToken).filter(application_id=application_id).first()\n        if application_access_token and application_access_token.authentication and application_access_token.authentication_value.get(\n                'type') == 'login':\n            if chat_user_type == ChatUserType.CHAT_USER.value and is_auth_chat_user:\n                is_auth = is_auth_chat_user(chat_user_id, application_id)\n                if not is_auth:\n                    raise ChatException(500, _(\"The chat user is not authorized.\"))\n\n    def chat(self, instance: dict, base_to_response: BaseToResponse = SystemToResponse()):\n        super().is_valid(raise_exception=True)\n        ChatMessageSerializers(data=instance).is_valid(raise_exception=True)\n        chat_info = self.get_chat_info()\n        chat_info.get_application()\n        chat_info.get_chat_user(asker=(instance.get('form_data') or {}).get('asker'))\n        self.is_valid_chat_id(chat_info)\n        self.is_valid_chat_user()\n        if chat_info.application.type == ApplicationTypeChoices.SIMPLE:\n            self.is_valid_application_simple(raise_exception=True, chat_info=chat_info)\n            return self.chat_simple(chat_info, instance, base_to_response)\n        else:\n            self.is_valid_application_workflow(raise_exception=True)\n            return self.chat_work_flow(chat_info, instance, base_to_response)\n\n    def get_chat_info(self):\n        self.is_valid(raise_exception=True)\n        chat_id = self.data.get('chat_id')\n        chat_info: ChatInfo = ChatInfo.get_cache(chat_id)\n        if chat_info is None:\n            chat_info: ChatInfo = self.re_open_chat(chat_id)\n            chat_info.set_cache()\n        return chat_info\n\n    def re_open_chat(self, chat_id: str):\n        chat = QuerySet(Chat).filter(id=chat_id).first()\n        if chat is None:\n            raise ChatException(500, _(\"Conversation does not exist\"))\n        application = QuerySet(Application).filter(id=chat.application_id).first()\n        if application is None:\n            raise ChatException(500, _(\"Application does not exist\"))\n        application_version = QuerySet(ApplicationVersion).filter(application_id=application.id).order_by(\n            '-create_time')[0:1].first()\n        if application_version is None:\n            raise ChatException(500, _(\"The application has not been published. Please use it after publishing.\"))\n        if application.type == ApplicationTypeChoices.SIMPLE:\n            return self.re_open_chat_simple(chat_id, application)\n        else:\n            return self.re_open_chat_work_flow(chat_id, application)\n\n    def re_open_chat_simple(self, chat_id, application):\n        # 数据集id列表\n        knowledge_id_list = [str(row.target_id) for row in\n                             QuerySet(ResourceMapping).filter(source_id=str(application.id),\n                                                              source_type='APPLICATION',\n                                                              target_type='KNOWLEDGE')]\n\n        # 需要排除的文档\n        exclude_document_id_list = [str(document.id) for document in\n                                    QuerySet(Document).filter(\n                                        knowledge_id__in=knowledge_id_list,\n                                        is_active=False)]\n        chat_info = ChatInfo(chat_id, self.data.get('chat_user_id'), self.data.get('chat_user_type'),\n                             self.data.get('ip_address'),\n                             self.data.get('source'), knowledge_id_list,\n                             exclude_document_id_list, application.id)\n        chat_record_list = list(QuerySet(ChatRecord).filter(chat_id=chat_id).order_by('-create_time')[0:5])\n        chat_record_list.sort(key=lambda r: r.create_time)\n        for chat_record in chat_record_list:\n            chat_info.chat_record_list.append(chat_record)\n        return chat_info\n\n    def re_open_chat_work_flow(self, chat_id, application):\n        chat_info = ChatInfo(chat_id, self.data.get('chat_user_id'), self.data.get('chat_user_type'),\n                             self.data.get('ip_address'),\n                             self.data.get('source'), [], [],\n                             application.id)\n        chat_record_list = list(QuerySet(ChatRecord).filter(chat_id=chat_id).order_by('-create_time')[0:5])\n        chat_record_list.sort(key=lambda r: r.create_time)\n        for chat_record in chat_record_list:\n            chat_info.chat_record_list.append(chat_record)\n        return chat_info\n\n\nclass OpenChatSerializers(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n    application_id = serializers.UUIDField(required=True)\n    chat_user_id = serializers.CharField(required=True, label=_(\"Client id\"))\n    chat_user_type = serializers.CharField(required=True, label=_(\"Client Type\"))\n    debug = serializers.BooleanField(required=True, label=_(\"Debug\"))\n    ip_address = serializers.CharField(required=False, label=_(\"IP Address\"))\n    source = serializers.JSONField(required=False, label=_(\"Source\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        application_id = self.data.get('application_id')\n        query_set = QuerySet(Application).filter(id=application_id)\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, gettext('Application does not exist'))\n\n    def open(self):\n        self.is_valid(raise_exception=True)\n        application_id = self.data.get('application_id')\n        application = QuerySet(Application).get(id=application_id)\n        debug = self.data.get(\"debug\")\n        if not debug:\n            application_version = QuerySet(ApplicationVersion).filter(application_id=application_id).order_by(\n                '-create_time')[0:1].first()\n            if application_version is None:\n                raise AppApiException(500,\n                                      _(\"The application has not been published. Please use it after publishing.\"))\n        if application.type == ApplicationTypeChoices.SIMPLE:\n            return self.open_simple(application)\n        else:\n            return self.open_work_flow(application)\n\n    def open_work_flow(self, application):\n        self.is_valid(raise_exception=True)\n        application_id = self.data.get('application_id')\n        chat_user_id = self.data.get(\"chat_user_id\")\n        chat_user_type = self.data.get(\"chat_user_type\")\n        ip_address = self.data.get(\"ip_address\")\n        source = self.data.get(\"source\")\n        debug = self.data.get(\"debug\")\n        chat_id = str(uuid.uuid7())\n        ChatInfo(chat_id, chat_user_id, chat_user_type, ip_address, source, [],\n                 [],\n                 application_id, debug).set_cache()\n        return chat_id\n\n    def open_simple(self, application):\n        application_id = self.data.get('application_id')\n        chat_user_id = self.data.get(\"chat_user_id\")\n        chat_user_type = self.data.get(\"chat_user_type\")\n        ip_address = self.data.get(\"ip_address\")\n        source = self.data.get(\"source\")\n        debug = self.data.get(\"debug\")\n        knowledge_id_list = [str(row.target_id) for row in\n                             QuerySet(ResourceMapping).filter(source_id=str(application_id),\n                                                              source_type='APPLICATION',\n                                                              target_type='KNOWLEDGE')]\n\n        chat_id = str(uuid.uuid7())\n        ChatInfo(chat_id, chat_user_id, chat_user_type, ip_address, source, knowledge_id_list,\n                 [str(document.id) for document in\n                  QuerySet(Document).filter(\n                      knowledge_id__in=knowledge_id_list,\n                      is_active=False)],\n                 application_id,\n                 debug=debug).set_cache()\n        return chat_id\n\n\nclass TextToSpeechSerializers(serializers.Serializer):\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n\n    def text_to_speech(self, instance):\n        self.is_valid(raise_exception=True)\n        application_id = self.data.get('application_id')\n        application = QuerySet(Application).filter(id=application_id).first()\n        return ApplicationOperateSerializer(\n            data={'application_id': application_id,\n                  'user_id': application.user_id}).text_to_speech(instance, False)\n\n\nclass SpeechToTextSerializers(serializers.Serializer):\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n\n    def speech_to_text(self, instance):\n        self.is_valid(raise_exception=True)\n        application_id = self.data.get('application_id')\n        application = QuerySet(Application).filter(id=application_id).first()\n        return ApplicationOperateSerializer(\n            data={'application_id': application_id,\n                  'user_id': application.user_id}).speech_to_text(instance, False)\n"
  },
  {
    "path": "apps/chat/serializers/chat_authentication.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： ChatAuthentication.py\n    @date：2025/6/6 13:48\n    @desc:\n\"\"\"\nimport uuid_utils.compat as uuid\nfrom django.core import signing\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import ApplicationAccessToken, ChatUserType, Application, ApplicationVersion\nfrom application.serializers.application import ApplicationSerializerModel\nfrom common.auth.common import ChatUserToken, ChatAuthentication\nfrom common.constants.authentication_type import AuthenticationType\nfrom common.constants.cache_version import Cache_Version\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.exception.app_exception import NotFound404, AppUnauthorizedFailed\nfrom common.utils.rsa_util import get_key_pair_by_sql\n\n\nclass AnonymousAuthenticationSerializer(serializers.Serializer):\n    access_token = serializers.CharField(required=True, label=_(\"access_token\"))\n\n    def auth(self, request, with_valid=True):\n        token = request.META.get('HTTP_AUTHORIZATION')\n        token_details = {}\n        try:\n            # 校验token\n            if token is not None:\n                token_details = signing.loads(token[7:])\n        except Exception as e:\n            pass\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        access_token = self.data.get(\"access_token\")\n        application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first()\n        if application_access_token is not None and application_access_token.is_active:\n            chat_user_id = token_details.get('chat_user_id') or str(uuid.uuid7())\n            _type = AuthenticationType.CHAT_ANONYMOUS_USER\n            return ChatUserToken(application_access_token.application_id, None, access_token, _type,\n                                 ChatUserType.ANONYMOUS_USER,\n                                 chat_user_id, ChatAuthentication(None)).to_token()\n        else:\n            raise NotFound404(404, _(\"Invalid access_token\"))\n\n\nclass AuthProfileSerializer(serializers.Serializer):\n    access_token = serializers.CharField(required=True, label=_(\"access_token\"))\n\n    def profile(self):\n        self.is_valid(raise_exception=True)\n        access_token = self.data.get(\"access_token\")\n        application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first()\n        if application_access_token is None:\n            raise NotFound404(404, _(\"Invalid access_token\"))\n        if not application_access_token.is_active:\n            raise NotFound404(404, _(\"Invalid access_token\"))\n        application_id = application_access_token.application_id\n        profile = {\n            'authentication': False\n        }\n        application_setting_model = DatabaseModelManage.get_model('application_setting')\n        chat_platform = DatabaseModelManage.get_model('chat_platform')\n        if application_setting_model and chat_platform:\n            application_setting = QuerySet(application_setting_model).filter(application_id=application_id).first()\n            types = QuerySet(chat_platform).filter(is_active=True, is_valid=True).values_list('auth_type', flat=True)\n            login_value = application_access_token.authentication_value.get('login_value', [])\n            max_attempts = application_access_token.authentication_value.get('max_attempts', 1)\n            final_login_value = list(set(login_value) & set(types))\n            if 'LOCAL' in login_value:\n                final_login_value.insert(0, 'LOCAL')\n            if application_setting is not None:\n                profile = {\n                    'icon': application_setting.application.icon,\n                    'application_name': application_setting.application.name,\n                    'bg_icon': application_setting.chat_background,\n                    'authentication': application_access_token.authentication,\n                    'authentication_type': application_access_token.authentication_value.get(\n                        'type', 'password'),\n                    'max_attempts': max_attempts,\n                    'login_value': final_login_value,\n                    'rasKey' : get_key_pair_by_sql().get('key')\n                }\n        return profile\n\n\nclass ApplicationProfileSerializer(serializers.Serializer):\n    application_id = serializers.UUIDField(required=True, label=_(\"Application ID\"))\n\n    @staticmethod\n    def reset_application(application, application_version):\n        update_field_dict = {\n            'application_name': 'name', 'desc': 'desc', 'prologue': 'prologue', 'dialogue_number': 'dialogue_number',\n            'user_id': 'user_id', 'model_id': 'model_id', 'knowledge_setting': 'knowledge_setting',\n            'model_setting': 'model_setting', 'model_params_setting': 'model_params_setting',\n            'tts_model_params_setting': 'tts_model_params_setting',\n            'problem_optimization': 'problem_optimization', 'work_flow': 'work_flow',\n            'problem_optimization_prompt': 'problem_optimization_prompt', 'tts_model_id': 'tts_model_id',\n            'stt_model_id': 'stt_model_id', 'tts_model_enable': 'tts_model_enable',\n            'stt_model_enable': 'stt_model_enable', 'tts_type': 'tts_type',\n            'tts_autoplay': 'tts_autoplay', 'stt_autosend': 'stt_autosend', 'file_upload_enable': 'file_upload_enable',\n            'file_upload_setting': 'file_upload_setting'\n        }\n        for (version_field, app_field) in update_field_dict.items():\n            _v = getattr(application_version, version_field)\n            setattr(application, app_field, _v)\n\n    def profile(self, with_valid=True):\n        if with_valid:\n            self.is_valid()\n        application_id = self.data.get(\"application_id\")\n        application = QuerySet(Application).get(id=application_id)\n        application_access_token = QuerySet(ApplicationAccessToken).filter(application_id=application.id).first()\n        if application_access_token is None:\n            raise AppUnauthorizedFailed(500, _(\"Illegal User\"))\n        application_setting_model = DatabaseModelManage.get_model('application_setting')\n        application_version = QuerySet(ApplicationVersion).filter(application_id=application.id).order_by(\n            '-create_time').first()\n        if application_version is not None:\n            self.reset_application(application, application_version)\n        license_is_valid = cache.get(Cache_Version.SYSTEM.get_key(key='license_is_valid'),\n                                     version=Cache_Version.SYSTEM.get_version())\n        application_setting_dict = {}\n        if application_setting_model is not None and license_is_valid:\n            application_setting = QuerySet(application_setting_model).filter(\n                application_id=application_access_token.application_id).first()\n            if application_setting is not None:\n                custom_theme = getattr(application_setting, 'custom_theme', {})\n                float_location = getattr(application_setting, 'float_location', {})\n                if not custom_theme:\n                    application_setting.custom_theme = {\n                        'theme_color': '',\n                        'header_font_color': ''\n                    }\n                if not float_location:\n                    application_setting.float_location = {\n                        'x': {'type': '', 'value': ''},\n                        'y': {'type': '', 'value': ''}\n                    }\n                application_setting_dict = {'show_source': application_access_token.show_source,\n                                            'show_history': application_setting.show_history,\n                                            'draggable': application_setting.draggable,\n                                            'show_guide': application_setting.show_guide,\n                                            'avatar': application_setting.avatar,\n                                            'show_avatar': application_setting.show_avatar,\n                                            'float_icon': application_setting.float_icon,\n                                            'disclaimer': application_setting.disclaimer,\n                                            'disclaimer_value': application_setting.disclaimer_value,\n                                            'custom_theme': application_setting.custom_theme,\n                                            'user_avatar': application_setting.user_avatar,\n                                            'show_user_avatar': application_setting.show_user_avatar,\n                                            'float_location': application_setting.float_location,\n                                            'chat_background': application_setting.chat_background}\n        base_node = [node for node in ((application.work_flow or {}).get('nodes', []) or []) if\n                     node.get('id') == 'base-node']\n        return {**ApplicationSerializerModel(application).data,\n                'stt_model_id': application.stt_model_id,\n                'tts_model_id': application.tts_model_id,\n                'stt_model_enable': application.stt_model_enable,\n                'tts_model_enable': application.tts_model_enable,\n                'tts_type': application.tts_type,\n                'tts_autoplay': application.tts_autoplay,\n                'stt_autosend': application.stt_autosend,\n                'file_upload_enable': application.file_upload_enable,\n                'file_upload_setting': application.file_upload_setting,\n                'work_flow': {'nodes': base_node} if base_node else None,\n                'show_source': application_access_token.show_source,\n                'show_exec': application_access_token.show_exec,\n                'language': application_access_token.language,\n                **application_setting_dict}\n"
  },
  {
    "path": "apps/chat/serializers/chat_embed_serializers.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： chat_embed_serializers.py\n    @date：2025/5/30 14:34\n    @desc:\n\"\"\"\nimport os\nimport uuid_utils.compat as uuid\n\nfrom django.db.models import QuerySet\nfrom django.http import HttpResponse\nfrom django.template import Template, Context\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import ApplicationAccessToken\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom maxkb.conf import PROJECT_DIR\nfrom maxkb.const import CONFIG\n\n\nclass ChatEmbedSerializer(serializers.Serializer):\n    host = serializers.CharField(required=True, label=_(\"Host\"))\n    protocol = serializers.CharField(required=True, label=_(\"protocol\"))\n    token = serializers.CharField(required=True, label=_(\"token\"))\n\n    def get_embed(self, with_valid=True, params=None):\n        if params is None:\n            params = {}\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        index_path = os.path.join(PROJECT_DIR, 'apps', \"chat\", 'template', 'embed.js')\n        file = open(index_path, \"r\", encoding='utf-8')\n        content = file.read()\n        file.close()\n        application_access_token = QuerySet(ApplicationAccessToken).filter(\n            access_token=self.data.get('token')).first()\n        is_draggable = 'false'\n        show_guide = 'true'\n        float_icon = f\"{self.data.get('protocol')}://{self.data.get('host')}{CONFIG.get_chat_path()}/MaxKB.gif\"\n        is_license_valid = DatabaseModelManage.get_model('license_is_valid')\n        X_PACK_LICENSE_IS_VALID = is_license_valid() if is_license_valid is not None else False\n        # 获取接入的query参数\n        query = self.get_query_api_input(application_access_token.application, params)\n        float_location = {\"x\": {\"type\": \"right\", \"value\": 0}, \"y\": {\"type\": \"bottom\", \"value\": 30}}\n        header_font_color = \"rgb(100, 106, 115)\"\n        application_setting_model = DatabaseModelManage.get_model('application_setting')\n        if application_setting_model is not None and X_PACK_LICENSE_IS_VALID:\n            application_setting = QuerySet(application_setting_model).filter(\n                application_id=application_access_token.application_id).first()\n            if application_setting is not None:\n                is_draggable = 'true' if application_setting.draggable else 'false'\n                if application_setting.float_icon is not None and len(application_setting.float_icon) > 0:\n                    float_icon = application_setting.float_icon[1:] if application_setting.float_icon.startswith(\n                        '.') else application_setting.float_icon\n                    float_icon = f\"{self.data.get('protocol')}://{self.data.get('host')}{CONFIG.get_chat_path()}{float_icon}\"\n                show_guide = 'true' if application_setting.show_guide else 'false'\n                if application_setting.float_location is not None:\n                    float_location = application_setting.float_location\n                if application_setting.custom_theme is not None and len(\n                        application_setting.custom_theme.get('header_font_color', 'rgb(100, 106, 115)')) > 0:\n                    header_font_color = application_setting.custom_theme.get('header_font_color',\n                                                                             'rgb(100, 106, 115)')\n\n        is_auth = 'true' if application_access_token is not None and application_access_token.is_active else 'false'\n        t = Template(content)\n        s = t.render(\n            Context(\n                {'is_auth': is_auth, 'protocol': self.data.get('protocol'), 'host': self.data.get('host'),\n                 'token': self.data.get('token'),\n                 'white_list_str': \",\".join(\n                     application_access_token.white_list if application_access_token.white_list is not None else []),\n                 'white_active': 'true' if application_access_token.white_active else 'false',\n                 'is_draggable': is_draggable,\n                 'float_icon': float_icon,\n                 'prefix': CONFIG.get_chat_path(),\n                 'query': query,\n                 'show_guide': show_guide,\n                 'x_type': float_location.get('x', {}).get('type', 'right'),\n                 'x_value': float_location.get('x', {}).get('value', 0),\n                 'y_type': float_location.get('y', {}).get('type', 'bottom'),\n                 'y_value': float_location.get('y', {}).get('value', 30),\n                 'max_kb_id': str(uuid.uuid7()).replace('-', ''),\n                 'header_font_color': header_font_color}))\n        response = HttpResponse(s, status=200, headers={'Content-Type': 'text/javascript'})\n        return response\n\n    @staticmethod\n    def get_query_api_input(application, params):\n        query = ''\n        if application.work_flow is not None:\n            work_flow = application.work_flow\n            if work_flow is not None:\n                for node in work_flow.get('nodes', []):\n                    if node['id'] == 'base-node':\n                        input_field_list = node.get('properties', {}).get('api_input_field_list',\n                                                                          node.get('properties', {}).get(\n                                                                              'input_field_list', []))\n                        if input_field_list is not None:\n                            for field in input_field_list:\n                                if field['assignment_method'] == 'api_input' and field['variable'] in params:\n                                    query += f\"&{field['variable']}={params[field['variable']]}\"\n        if 'asker' in params:\n            query += f\"&asker={params.get('asker')}\"\n        return query\n"
  },
  {
    "path": "apps/chat/serializers/chat_record.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： chat_record.py\n    @date：2025/6/23 11:16\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.db import transaction\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom rest_framework import serializers\n\nfrom application.models import VoteChoices, ChatRecord, Chat, ApplicationAccessToken, VoteReasonChoices\nfrom application.serializers.application_chat import ChatCountSerializer\nfrom application.serializers.application_chat_record import ChatRecordSerializerModel, \\\n    ApplicationChatRecordQuerySerializers\nfrom common.db.search import page_search\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.lock import RedisLock\n\n\nclass VoteRequest(serializers.Serializer):\n    vote_status = serializers.ChoiceField(choices=VoteChoices.choices,\n                                          label=_(\"Bidding Status\"))\n    vote_reason = serializers.ChoiceField(choices=VoteReasonChoices.choices, label=_(\"Vote Reason\"), required=False,\n                                          allow_null=True)\n\n    vote_other_content = serializers.CharField(required=False, allow_blank=True, label=_(\"Vote other content\"))\n\n\nclass HistoryChatModel(serializers.ModelSerializer):\n    class Meta:\n        model = Chat\n        fields = ['id',\n                  'application_id',\n                  'abstract',\n                  'create_time',\n                  'update_time']\n\n\nclass VoteSerializer(serializers.Serializer):\n    chat_id = serializers.UUIDField(required=True, label=_(\"Conversation ID\"))\n\n    chat_record_id = serializers.UUIDField(required=True,\n                                           label=_(\"Conversation record id\"))\n\n    @transaction.atomic\n    def vote(self, instance: Dict, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            VoteRequest(data=instance).is_valid(raise_exception=True)\n        rlock = RedisLock()\n        if not rlock.try_lock(self.data.get('chat_record_id')):\n            raise AppApiException(500,\n                                  gettext(\n                                      \"Voting on the current session minutes, please do not send repeated requests\"))\n        try:\n            chat_record_details_model = QuerySet(ChatRecord).get(id=self.data.get('chat_record_id'),\n                                                                 chat_id=self.data.get('chat_id'))\n            if chat_record_details_model is None:\n                raise AppApiException(500, gettext(\"Non-existent conversation chat_record_id\"))\n            vote_status = instance.get(\"vote_status\")\n\n            # 未投票状态，可以进行投票\n            if chat_record_details_model.vote_status == VoteChoices.UN_VOTE:\n                # 投票时获取字段\n                vote_reason = instance.get(\"vote_reason\")\n                vote_other_content = instance.get(\"vote_other_content\") or ''\n\n                if vote_status == VoteChoices.STAR:\n                    # 点赞\n                    chat_record_details_model.vote_status = VoteChoices.STAR\n                    chat_record_details_model.vote_reason = vote_reason\n                    chat_record_details_model.vote_other_content = vote_other_content\n\n                if vote_status == VoteChoices.TRAMPLE:\n                    # 点踩\n                    chat_record_details_model.vote_status = VoteChoices.TRAMPLE\n                    chat_record_details_model.vote_reason = vote_reason\n                    chat_record_details_model.vote_other_content = vote_other_content\n\n                chat_record_details_model.save()\n            # 已投票状态\n            else:\n                if vote_status == VoteChoices.UN_VOTE:\n                    # 取消点赞\n                    chat_record_details_model.vote_status = VoteChoices.UN_VOTE\n                    chat_record_details_model.vote_reason = None\n                    chat_record_details_model.vote_other_content = ''\n                    chat_record_details_model.save()\n                else:\n                    raise AppApiException(500, gettext(\"Already voted, please cancel first and then vote again\"))\n        finally:\n            rlock.un_lock(self.data.get('chat_record_id'))\n        ChatCountSerializer(data={'chat_id': self.data.get('chat_id')}).update_chat()\n        return True\n\n\nclass HistoricalConversationSerializer(serializers.Serializer):\n    application_id = serializers.UUIDField(required=True, label=_('Application ID'))\n    chat_user_id = serializers.UUIDField(required=True, label=_('Chat User ID'))\n\n    def get_queryset(self):\n        chat_user_id = self.data.get('chat_user_id')\n        application_id = self.data.get(\"application_id\")\n        return QuerySet(Chat).filter(application_id=application_id, chat_user_id=chat_user_id,\n                                     is_deleted=False).order_by('-update_time', 'id')\n\n    def list(self):\n        self.is_valid(raise_exception=True)\n        queryset = self.get_queryset()\n        return [HistoryChatModel(r).data for r in queryset]\n\n    def page(self, current_page, page_size):\n        self.is_valid(raise_exception=True)\n        return page_search(current_page, page_size, self.get_queryset(), lambda r: HistoryChatModel(r).data)\n\n\nclass EditAbstractSerializer(serializers.Serializer):\n    abstract = serializers.CharField(required=True, label=_('Abstract'))\n\n\nclass HistoricalConversationOperateSerializer(serializers.Serializer):\n    application_id = serializers.UUIDField(required=True, label=_('Application ID'))\n    chat_user_id = serializers.UUIDField(required=True, label=_('Chat User ID'))\n    chat_id = serializers.UUIDField(required=True, label=_('Chat ID'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        e = QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id'),\n                                  chat_user_id=self.data.get('chat_user_id')).exists()\n        if not e:\n            raise AppApiException(500, _('Chat is not exist'))\n\n    def edit_abstract(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            EditAbstractSerializer(data=instance).is_valid(raise_exception=True)\n\n        QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id'),\n                              chat_user_id=self.data.get('chat_user_id')).update(abstract=instance.get('abstract'))\n        return True\n\n    def logic_delete(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        QuerySet(Chat).filter(id=self.data.get('chat_id'), application_id=self.data.get('application_id'),\n                              chat_user_id=self.data.get('chat_user_id')).update(is_deleted=True)\n        return True\n\n    class Clear(serializers.Serializer):\n        application_id = serializers.UUIDField(required=True, label=_('Application ID'))\n        chat_user_id = serializers.UUIDField(required=True, label=_('Chat User ID'))\n\n        def batch_logic_delete(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            QuerySet(Chat).filter(application_id=self.data.get('application_id'),\n                                  chat_user_id=self.data.get('chat_user_id')).update(is_deleted=True)\n            return True\n\n\nclass HistoricalConversationRecordSerializer(serializers.Serializer):\n    application_id = serializers.UUIDField(required=True, label=_('Application ID'))\n    chat_id = serializers.UUIDField(required=True, label=_('Chat ID'))\n    chat_user_id = serializers.UUIDField(required=True, label=_('Chat User ID'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        chat_user_id = self.data.get('chat_user_id')\n        application_id = self.data.get(\"application_id\")\n        chat_id = self.data.get('chat_id')\n        chat_exist = QuerySet(Chat).filter(application_id=application_id, chat_user_id=chat_user_id,\n                                           id=chat_id).exists()\n        if not chat_exist:\n            raise AppApiException(500, _('Non-existent chatID'))\n\n    def get_queryset(self):\n        chat_id = self.data.get('chat_id')\n        return QuerySet(ChatRecord).filter(chat_id=chat_id).order_by('-create_time')\n\n    def list(self):\n        self.is_valid(raise_exception=True)\n        queryset = self.get_queryset()\n        return [ChatRecordSerializerModel(r).data for r in queryset]\n\n    def page(self, current_page, page_size):\n        self.is_valid(raise_exception=True)\n        application_access_token = QuerySet(ApplicationAccessToken).filter(\n            application_id=self.data.get('application_id')).first()\n        show_source = False\n        show_exec = False\n        if application_access_token is not None:\n            show_exec = application_access_token.show_exec\n            show_source = application_access_token.show_source\n        return ApplicationChatRecordQuerySerializers(\n            data={'application_id': self.data.get('application_id'), 'chat_id': self.data.get('chat_id')}).page(\n            current_page, page_size, show_source=show_source, show_exec=show_exec)\n"
  },
  {
    "path": "apps/chat/template/embed.js",
    "content": "(function() {\nconst guideHtml=`\n<div class=\"maxkb-mask\">\n  <div class=\"maxkb-content\"></div>\n</div>\n<div class=\"maxkb-tips\">\n  <div class=\"maxkb-close\">\n      <svg style=\"vertical-align: middle;overflow: hidden;\" xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n          <path d=\"M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z\" fill=\"#ffffff\"></path>\n          </svg>\n  </div>\n\n  <div class=\"maxkb-title\"> 🌟 遇见问题，不再有障碍！</div>\n  <p>你好，我是你的智能小助手。<br/>\n      点我，开启高效解答模式，让问题变成过去式。</p>\n  <div class=\"maxkb-button\">\n      <button>我知道了</button>\n  </div>\n  <span class=\"maxkb-arrow\" ></span>\n</div>\n`\nconst chatButtonHtml=\n`<div class=\"maxkb-chat-button\" >\n<img style=\"height:100%;width:100%;\" src=\"{{float_icon}}\">\n</div>`\n\n\n\nconst getChatContainerHtml=(protocol,host,token,query,prefix)=>{\n return `<div id=\"maxkb-chat-container\">\n<iframe id=\"maxkb-chat\" allow=\"microphone\" src=${protocol}://${host}${prefix}/${token}?mode=embed${query}></iframe>\n<div class=\"maxkb-operate\"><div class=\"maxkb-closeviewport maxkb-viewportnone\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n<path d=\"M7.507 11.6645C7.73712 11.6645 7.94545 11.7578 8.09625 11.9086C8.24706 12.0594 8.34033 12.2677 8.34033 12.4978V16.7976C8.34033 17.0277 8.15378 17.2143 7.92366 17.2143H7.09033C6.86021 17.2143 6.67366 17.0277 6.67366 16.7976V14.5812L3.41075 17.843C3.24803 18.0057 2.98421 18.0057 2.82149 17.843L2.23224 17.2537C2.06952 17.091 2.06952 16.8272 2.23224 16.6645L5.56668 13.3311H3.19634C2.96622 13.3311 2.77967 13.1446 2.77967 12.9145V12.0811C2.77967 11.851 2.96622 11.6645 3.19634 11.6645H7.507ZM16.5991 2.1572C16.7619 1.99448 17.0257 1.99448 17.1884 2.1572L17.7777 2.74645C17.9404 2.90917 17.9404 3.17299 17.7777 3.33571L14.4432 6.66904H16.8136C17.0437 6.66904 17.2302 6.85559 17.2302 7.08571V7.91904C17.2302 8.14916 17.0437 8.33571 16.8136 8.33571H12.5029C12.2728 8.33571 12.0644 8.24243 11.9136 8.09163C11.7628 7.94082 11.6696 7.73249 11.6696 7.50237V3.20257C11.6696 2.97245 11.8561 2.7859 12.0862 2.7859H12.9196C13.1497 2.7859 13.3362 2.97245 13.3362 3.20257V5.419L16.5991 2.1572Z\" fill=\"{{header_font_color}}\"/>\n</svg></div>\n<div class=\"maxkb-openviewport\">\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n<path d=\"M7.15209 11.5968C7.31481 11.4341 7.57862 11.4341 7.74134 11.5968L8.3306 12.186C8.49332 12.3487 8.49332 12.6126 8.3306 12.7753L4.99615 16.1086H7.3665C7.59662 16.1086 7.78316 16.2952 7.78316 16.5253V17.3586C7.78316 17.5887 7.59662 17.7753 7.3665 17.7753H3.05584C2.82572 17.7753 2.61738 17.682 2.46658 17.5312C2.31578 17.3804 2.2225 17.1721 2.2225 16.9419V12.6421C2.2225 12.412 2.40905 12.2255 2.63917 12.2255H3.4725C3.70262 12.2255 3.88917 12.412 3.88917 12.6421V14.8586L7.15209 11.5968ZM16.937 2.22217C17.1671 2.22217 17.3754 2.31544 17.5262 2.46625C17.677 2.61705 17.7703 2.82538 17.7703 3.0555V7.35531C17.7703 7.58543 17.5837 7.77198 17.3536 7.77198H16.5203C16.2902 7.77198 16.1036 7.58543 16.1036 7.35531V5.13888L12.8407 8.40068C12.678 8.5634 12.4142 8.5634 12.2515 8.40068L11.6622 7.81142C11.4995 7.64871 11.4995 7.38489 11.6622 7.22217L14.9966 3.88883H12.6263C12.3962 3.88883 12.2096 3.70229 12.2096 3.47217V2.63883C12.2096 2.40872 12.3962 2.22217 12.6263 2.22217H16.937Z\" fill=\"{{header_font_color}}\"/>\n</svg></div>\n<div class=\"maxkb-chat-close\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 20 20\" fill=\"none\">\n           <path d=\"M9.95317 8.73169L15.5511 3.13376C15.7138 2.97104 15.9776 2.97104 16.1403 3.13376L16.7296 3.72301C16.8923 3.88573 16.8923 4.14955 16.7296 4.31227L11.1317 9.9102L16.7296 15.5081C16.8923 15.6708 16.8923 15.9347 16.7296 16.0974L16.1403 16.6866C15.9776 16.8494 15.7138 16.8494 15.5511 16.6866L9.95317 11.0887L4.35524 16.6866C4.19252 16.8494 3.9287 16.8494 3.76598 16.6866L3.17673 16.0974C3.01401 15.9347 3.01401 15.6708 3.17673 15.5081L8.77465 9.9102L3.17673 4.31227C3.01401 4.14955 3.01401 3.88573 3.17673 3.72301L3.76598 3.13376C3.9287 2.97104 4.19252 2.97104 4.35524 3.13376L9.95317 8.73169Z\" fill=\"{{header_font_color}}\"/>\n           </svg>\n </div></div>\n`\n}\n/**\n * 初始化引导\n * @param {*} root\n */\nconst initGuide=(root)=>{\n   root.insertAdjacentHTML(\"beforeend\",guideHtml)\n   const button=root.querySelector(\".maxkb-button\")\n   const close_icon=root.querySelector('.maxkb-close')\n   const close_func=()=>{\n     root.removeChild(root.querySelector('.maxkb-tips'))\n     root.removeChild(root.querySelector('.maxkb-mask'))\n     localStorage.setItem('maxkbMaskTip',true)\n   }\n   button.onclick=close_func\n   close_icon.onclick=close_func\n}\nconst initChat=(root)=>{\n  // 添加对话icon\n  root.insertAdjacentHTML(\"beforeend\",chatButtonHtml)\n  // 添加对话框\n  root.insertAdjacentHTML('beforeend',getChatContainerHtml('{{protocol}}','{{host}}','{{token}}','{{query}}','{{prefix}}'))\n  // 按钮元素\n  const chat_button=root.querySelector('.maxkb-chat-button')\n  const chat_button_img=root.querySelector('.maxkb-chat-button > img')\n  //  对话框元素\n  const chat_container=root.querySelector('#maxkb-chat-container')\n    // 引导层\n  const mask_content = root.querySelector('.maxkb-mask > .maxkb-content')\n  const mask_tips = root.querySelector('.maxkb-tips')\n chat_button_img.onload=(event)=>{\n if(mask_content){\n    mask_content.style.width = chat_button_img.width + 'px'\n    mask_content.style.height = chat_button_img.height + 'px'\n    if('{{x_type}}'=='left'){\n     mask_tips.style.marginLeft = (chat_button_img.naturalWidth>500?500:chat_button_img.naturalWidth)-64 + 'px'\n    }else{\n    mask_tips.style.marginRight = (chat_button_img.naturalWidth>500?500:chat_button_img.naturalWidth)-64 + 'px'\n    }\n   }\n  }\n\n  const viewport=root.querySelector('.maxkb-openviewport')\n  const closeviewport=root.querySelector('.maxkb-closeviewport')\n  const close_func=()=>{\n    chat_container.style['display']=chat_container.style['display']=='block'?'none':'block'\n    chat_button.style['display']=chat_container.style['display']=='block'?'none':'block'\n  }\n  close_icon=chat_container.querySelector('.maxkb-chat-close')\n  chat_button.onclick = close_func\n  close_icon.onclick=close_func\n  const viewport_func=()=>{\n    if(chat_container.classList.contains('maxkb-enlarge')){\n      chat_container.classList.remove(\"maxkb-enlarge\");\n      viewport.classList.remove('maxkb-viewportnone')\n      closeviewport.classList.add('maxkb-viewportnone')\n    }else{\n      chat_container.classList.add(\"maxkb-enlarge\");\n      viewport.classList.add('maxkb-viewportnone')\n      closeviewport.classList.remove('maxkb-viewportnone')\n    }\n  }\n     const drag=(e)=>{\n            if (['touchmove','touchstart'].includes(e.type)) {\n             chat_button.style.top=(e.touches[0].clientY-chat_button_img.naturalHeight/2)+'px'\n             chat_button.style.left=(e.touches[0].clientX-chat_button_img.naturalWidth/2)+'px'\n          } else {\n             chat_button.style.top=(e.y-chat_button_img.naturalHeight/2)+'px'\n             chat_button.style.left=(e.x-chat_button_img.naturalWidth/2)+'px'\n          }\n            chat_button.style.width =chat_button_img.naturalWidth+'px'\n            chat_button.style.height =chat_button_img.naturalHeight+'px'\n        }\n  if({{is_draggable}}){\n  chat_button.addEventListener(\"drag\",drag)\n  chat_button.addEventListener(\"dragover\",(e)=>{\n             e.preventDefault()\n   })\n  chat_button.addEventListener(\"dragend\",drag)\n  chat_button.addEventListener(\"touchstart\",drag)\n  chat_button.addEventListener(\"touchmove\",drag)\n  }\n  viewport.onclick=viewport_func\n  closeviewport.onclick=viewport_func\n}\n/**\n * 第一次进来的引导提示\n */\nfunction initMaxkb(){\n  const maxkb=document.createElement('div')\n  const root=document.createElement('div')\n  const maxkbId = 'maxkb-'+'{{max_kb_id}}'\n  root.id=maxkbId\n  initMaxkbStyle(maxkb, maxkbId)\n  maxkb.appendChild(root)\n  document.body.appendChild(maxkb)\n  const maxkbMaskTip=localStorage.getItem('maxkbMaskTip')\n  if(maxkbMaskTip==null && {{show_guide}}){\n    initGuide(root)\n  }\n  initChat(root)\n}\n\n\n// 初始化全局样式\nfunction initMaxkbStyle(root, maxkbId){\n  style=document.createElement('style')\n  style.type='text/css'\n  style.innerText=  `\n  /* 放大 */\n  #maxkb .maxkb-enlarge {\n      width: 50%!important;\n      height: 100%!important;\n      bottom: 0!important;\n      right: 0 !important;\n  }\n  @media only screen and (max-width: 768px){\n  #maxkb .maxkb-enlarge {\n      width: 100%!important;\n      height: 100%!important;\n      right: 0 !important;\n      bottom: 0!important;\n  }\n  }\n\n  /* 引导 */\n\n  #maxkb .maxkb-mask {\n      position: fixed;\n      z-index: 10001;\n      background-color: transparent;\n      height: 100%;\n      width: 100%;\n      top: 0;\n      left: 0;\n  }\n  #maxkb .maxkb-mask .maxkb-content {\n      width: 64px;\n      height: 64px;\n      box-shadow: 1px 1px 1px 9999px rgba(0,0,0,.6);\n      position: absolute;\n      {{x_type}}: {{x_value}}px;\n      {{y_type}}: {{y_value}}px;\n      z-index: 10001;\n  }\n  #maxkb .maxkb-tips {\n      position: fixed;\n      {{x_type}}:calc({{x_value}}px + 75px);\n      {{y_type}}: calc({{y_value}}px + 0px);\n      padding: 22px 24px 24px;\n      border-radius: 6px;\n      color: #ffffff;\n      font-size: 14px;\n      background: #3370FF;\n      z-index: 10001;\n  }\n  #maxkb .maxkb-tips .maxkb-arrow {\n      position: absolute;\n      background: #3370FF;\n      width: 10px;\n      height: 10px;\n      pointer-events: none;\n      transform: rotate(45deg);\n      box-sizing: border-box;\n      /* left  */\n      {{x_type}}: -5px;\n      {{y_type}}: 33px;\n      border-left-color: transparent;\n      border-bottom-color: transparent\n  }\n  #maxkb .maxkb-tips .maxkb-title {\n      font-size: 20px;\n      font-weight: 500;\n      margin-bottom: 8px;\n  }\n  #maxkb .maxkb-tips .maxkb-button {\n      text-align: right;\n      margin-top: 24px;\n  }\n  #maxkb .maxkb-tips .maxkb-button button {\n      border-radius: 4px;\n      background: #FFF;\n      padding: 3px 12px;\n      color: #3370FF;\n      cursor: pointer;\n      outline: none;\n      border: none;\n  }\n  #maxkb .maxkb-tips .maxkb-button button::after{\n      border: none;\n    }\n  #maxkb .maxkb-tips .maxkb-close {\n      position: absolute;\n      right: 20px;\n      top: 20px;\n      cursor: pointer;\n\n  }\n  #maxkb-chat-container {\n        width: 460px;\n        height: 680px;\n        display:none;\n      }\n @media only screen and (max-height: 680px) {\n  #maxkb-chat-container{\n    height: 600px}\n }\n  @media only screen and (max-width: 768px) {\n        #maxkb-chat-container {\n          width: 100%;\n          height: 70%;\n          right: 0 !important;\n        }\n      }\n\n      #maxkb .maxkb-chat-button{\n        position: fixed;\n        {{x_type}}: {{x_value}}px;\n        {{y_type}}: {{y_value}}px;\n        cursor: pointer;\n        z-index:10000;\n    }\n    #maxkb #maxkb-chat-container{\n        z-index:10000;position: relative;\n              border-radius: 8px;\n              border: 1px solid #ffffff;\n              background: linear-gradient(188deg, rgba(235, 241, 255, 0.20) 39.6%, rgba(231, 249, 255, 0.20) 94.3%), #EFF0F1;\n              box-shadow: 0px 4px 8px 0px rgba(31, 35, 41, 0.10);\n              position: fixed;bottom: 16px;right: 16px;overflow: hidden;\n    }\n\n     #maxkb #maxkb-chat-container .maxkb-operate{\n     top: 18px;\n     right: 15px;\n     position: absolute;\n     display: flex;\n     align-items: center;\n         line-height: 18px;\n     }\n    #maxkb #maxkb-chat-container .maxkb-operate .maxkb-chat-close{\n            margin-left:15px;\n            cursor: pointer;\n    }\n    #maxkb #maxkb-chat-container .maxkb-operate .maxkb-openviewport{\n\n            cursor: pointer;\n    }\n    #maxkb #maxkb-chat-container .maxkb-operate .maxkb-closeviewport{\n\n      cursor: pointer;\n    }\n    #maxkb #maxkb-chat-container .maxkb-viewportnone{\n      display:none;\n    }\n    #maxkb #maxkb-chat-container #maxkb-chat{\n     height:100%;\n     width:100%;\n     border: none;\n}\n    #maxkb #maxkb-chat-container {\n                animation: appear .4s ease-in-out;\n              }\n              @keyframes appear {\n                from {\n                  height: 0;;\n                }\n\n                to {\n                  height: 600px;\n                }\n              }`\n      .replaceAll('#maxkb ',`#${maxkbId} `)\n  root.appendChild(style)\n}\n\nfunction embedChatbot() {\n  white_list_str='{{white_list_str}}'\n  white_list=white_list_str.split(',')\n\n  if ({{is_auth}}&&({{white_active}}?white_list.includes(window.location.origin):true)) {\n    // 初始化maxkb智能小助手\n    initMaxkb()\n  } else console.error('invalid parameter')\n}\nwindow.addEventListener('load',embedChatbot)\n})();"
  },
  {
    "path": "apps/chat/template/generate_prompt_system",
    "content": "## 人设\n你是一个专业的提示词生成优化专家，擅长为各种智能体应用场景创建高质量的系统角色设定。你具有深厚的AI应用理解能力和丰富的提示词工程经验。\n\n## 技能\n1. 深度分析用户提供的智能体名称和功能描述\n2. 根据应用场景生成结构化的系统角色设定\n3. 优化提示词的逻辑结构和语言表达\n4. 确保生成的角色设定具有清晰的人物设定、功能流程、约束限制和回复格式\n\n当用户提供智能体信息时，你需要按照标准格式生成包含人物设定、功能和流程、约束与限制、回复格式四个核心模块的完整系统角色设定。\n\n## 限制\n1. 严格按照人物设定、功能和流程、约束与限制、回复格式的结构输出\n2. 不输出与角色设定生成无关的内容\n3. 如果用户输入信息不够明确，基于智能体名称和已有描述进行合理推测\n\n## 回复格式\n请严格按照以下格式输出：\n\n# 角色:\n角色简短描述一句话\n\n## 目标：\n角色的工作目标,如果有多目标可以分点列出,但建议更聚焦1-2个目标\n\n## 核心技能：\n### 技能 1: [技能名称，如作品推荐/信息查询/专业分析等]\n1. [执行步骤1 - 描述该技能的第一个具体操作步骤，包括条件判断和处理方式]\n2. [执行步骤2 - 描述该技能的第二个具体操作步骤，包括如何获取或处理信息]\n3. [执行步骤3 - 描述该技能的最终输出步骤，说明如何呈现结果]\n\n===回复示例===\n- 📋 [标识符]: <具体内容格式说明>\n- 🎯 [标识符]: <具体内容格式说明>\n- 💡 [标识符]: <具体内容格式说明>\n===示例结束===\n\n### 技能 2: [技能名称]\n1. [执行步骤1 - 描述触发条件和初始处理方式]\n2. [执行步骤2 - 描述信息获取和深化处理的具体方法]\n3. [执行步骤3 - 描述最终输出的具体要求和格式]\n\n### 技能 3: [技能名称]\n- [核心能力描述 - 说明该技能的主要作用和知识基础]\n- [应用方法 - 描述如何运用该技能为用户提供服务，包括具体的实施方式]\n\n## 工作流：\n1. 描述角色工作流程的第一步\n2. 描述角色工作流程的第二步\n3. 描述角色工作流程的第三步\n\n## 输出格式：\n如果对角色的输出格式有特定要求，可以在这里强调并举例说明想要的输出格式\n\n## 限制：\n1. **严格限制回答范围**：仅回答与角色设定相关的问题。\n   - 如果用户提问与角色无关，必须使用以下固定格式回复：\n     “对不起，我只能回答与[角色设定]相关的问题，您的问题不在服务范围内。”\n   - 不得提供任何与角色设定无关的回答。\n2. 描述角色在互动过程中需要遵循的限制条件2\n3. 描述角色在互动过程中需要遵循的限制条件3\n\n输出时不得包含任何解释或附加说明，只能返回符合以上格式的内容。\n\n"
  },
  {
    "path": "apps/chat/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/chat/urls.py",
    "content": "from django.urls import path\n\nfrom application.views import ChatRecordDetailView, ChatRecordLinkView\nfrom chat.views.mcp import mcp_view\nfrom . import views\n\napp_name = 'chat'\n# @formatter:off\nurlpatterns = [\n    path('embed', views.ChatEmbedView.as_view()),\n    path('mcp', mcp_view),\n    path('auth/anonymous', views.AnonymousAuthentication.as_view()),\n    path('profile', views.AuthProfile.as_view()),\n    path('application/profile', views.ApplicationProfile.as_view(), name='profile'),\n    path('chat_message/<str:chat_id>', views.ChatView.as_view(), name='chat'),\n    path('open', views.OpenView.as_view(), name='open'),\n    path('text_to_speech', views.TextToSpeech.as_view()),\n    path('speech_to_text', views.SpeechToText.as_view()),\n    path('captcha', views.CaptchaView.as_view(), name='captcha'),\n    path('<str:application_id>/chat/completions', views.OpenAIView.as_view(), name='application/chat_completions'),\n    path('vote/chat/<str:chat_id>/chat_record/<str:chat_record_id>', views.VoteView.as_view(), name='vote'),\n    path('historical_conversation', views.HistoricalConversationView.as_view(), name='historical_conversation'),\n    path('historical_conversation/<str:chat_id>/record/<str:chat_record_id>',views.ChatRecordView.as_view(),name='conversation_details'),\n    path('historical_conversation/<int:current_page>/<int:page_size>', views.HistoricalConversationView.PageView.as_view(), name='historical_conversation'),\n    path('historical_conversation/clear',views.HistoricalConversationView.BatchDelete.as_view(), name='historical_conversation_clear'),\n    path('historical_conversation/<str:chat_id>',views.HistoricalConversationView.Operate.as_view(), name='historical_conversation_operate'),\n    path('historical_conversation_record/<str:chat_id>', views.HistoricalConversationRecordView.as_view(), name='historical_conversation_record'),\n    path('historical_conversation_record/<str:chat_id>/<int:current_page>/<int:page_size>', views.HistoricalConversationRecordView.PageView.as_view(), name='historical_conversation_record'),\n    path('share/<str:link>', ChatRecordDetailView.as_view()),\n    path('<str:application_id>/chat/<str:chat_id>/share_chat', ChatRecordLinkView.as_view()),\n]\n"
  },
  {
    "path": "apps/chat/views/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/5/29 16:08\n    @desc:\n\"\"\"\nfrom .chat_embed import *\nfrom .chat import *\nfrom .chat_record import *\n"
  },
  {
    "path": "apps/chat/views/chat.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： chat.py\n    @date：2025/6/6 11:18\n    @desc:\n\"\"\"\nimport requests\nfrom django.http import HttpResponse, StreamingHttpResponse\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.parsers import MultiPartParser\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_api import SpeechToTextAPI, TextToSpeechAPI\nfrom application.models import ChatUserType, ChatSourceChoices\nfrom chat.api.chat_api import ChatAPI\nfrom chat.api.chat_authentication_api import ChatAuthenticationAPI, ChatAuthenticationProfileAPI, ChatOpenAPI, OpenAIAPI\nfrom chat.serializers.chat import OpenChatSerializers, ChatSerializers, SpeechToTextSerializers, \\\n    TextToSpeechSerializers, OpenAIChatSerializer\nfrom chat.serializers.chat_authentication import AnonymousAuthenticationSerializer, ApplicationProfileSerializer, \\\n    AuthProfileSerializer\nfrom common.auth import ChatTokenAuth\nfrom common.constants.permission_constants import ChatAuth\nfrom common.exception.app_exception import AppAuthenticationFailed\nfrom common.log.log import _get_ip_address\nfrom common.result import result\nfrom knowledge.models import FileSourceType\nfrom oss.serializers.file import FileSerializer\nfrom users.api import CaptchaAPI\nfrom users.serializers.login import CaptchaSerializer\n\n\ndef stream_image(response):\n    \"\"\"生成器函数，用于流式传输图片数据\"\"\"\n    for chunk in response.iter_content(chunk_size=4096):\n        if chunk:  # 过滤掉保持连接的空块\n            yield chunk\n\n\nclass ResourceProxy(APIView):\n    def get(self, request: Request):\n        image_url = request.query_params.get(\"url\")\n        if not image_url:\n            return result.error(\"Missing 'url' parameter\")\n        try:\n\n            # 发送GET请求，流式获取图片内容\n            response = requests.get(\n                image_url,\n                stream=True,  # 启用流式响应\n                allow_redirects=True,\n                timeout=10\n            )\n            content_type = response.headers.get('Content-Type', '').split(';')[0]\n            # 创建Django流式响应\n            django_response = StreamingHttpResponse(\n                stream_image(response),  # 使用生成器\n                content_type=content_type\n            )\n\n            return django_response\n        except Exception as e:\n            return result.error(f\"Image request failed: {str(e)}\")\n\n\nclass OpenAIView(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('OpenAI Interface Dialogue'),\n        summary=_('OpenAI Interface Dialogue'),\n        operation_id=_('OpenAI Interface Dialogue'),  # type: ignore\n        request=OpenAIAPI.get_request(),\n        responses=None,\n        tags=[_('Chat')]  # type: ignore\n    )\n    def post(self, request: Request, application_id: str):\n        ip_address = _get_ip_address(request)\n        if application_id != str(request.auth.application_id):\n            raise AppAuthenticationFailed(500, _('Secret key is invalid'))\n        return OpenAIChatSerializer(\n            data={'application_id': application_id, 'chat_user_id': request.auth.chat_user_id,\n                  'chat_user_type': request.auth.chat_user_type,\n                  'ip_address': ip_address,\n                  'source': {\"type\": ChatSourceChoices.API_CALL.value}}).chat(request.data)\n\n\nclass AnonymousAuthentication(APIView):\n    def options(self, request, *args, **kwargs):\n        return HttpResponse(\n            headers={\"Access-Control-Allow-Origin\": \"*\", \"Access-Control-Allow-Credentials\": \"true\",\n                     \"Access-Control-Allow-Methods\": \"POST\",\n                     \"Access-Control-Allow-Headers\": \"Origin,Content-Type,Cookie,Accept,Token\"}, )\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Application Anonymous Certification'),\n        summary=_('Application Anonymous Certification'),\n        operation_id=_('Application Anonymous Certification'),  # type: ignore\n        request=ChatAuthenticationAPI.get_request(),\n        responses=None,\n        tags=[_('Chat')]  # type: ignore\n    )\n    def post(self, request: Request):\n        return result.success(\n            AnonymousAuthenticationSerializer(data={'access_token': request.data.get(\"access_token\")}).auth(\n                request),\n            headers={\"Access-Control-Allow-Origin\": \"*\", \"Access-Control-Allow-Credentials\": \"true\",\n                     \"Access-Control-Allow-Methods\": \"POST\",\n                     \"Access-Control-Allow-Headers\": \"Origin,Content-Type,Cookie,Accept,Token\"}\n        )\n\n\nclass ApplicationProfile(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get application related information\"),\n        summary=_(\"Get application related information\"),\n        operation_id=_(\"Get application related information\"),  # type: ignore\n        request=None,\n        responses=None,\n        tags=[_('Chat')]  # type: ignore\n    )\n    def get(self, request: Request):\n        if isinstance(request.auth, ChatAuth):\n            return result.success(ApplicationProfileSerializer(\n                data={'application_id': request.auth.application_id}).profile())\n        raise AppAuthenticationFailed(401, \"身份异常\")\n\n\nclass AuthProfile(APIView):\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get application authentication information\"),\n        summary=_(\"Get application authentication information\"),\n        operation_id=_(\"Get application authentication information\"),  # type: ignore\n        parameters=ChatAuthenticationProfileAPI.get_parameters(),\n        responses=None,\n        tags=[_('Chat')]  # type: ignore\n    )\n    def get(self, request: Request):\n        return result.success(\n            AuthProfileSerializer(data={'access_token': request.query_params.get(\"access_token\")}).profile())\n\n\nclass ChatView(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"dialogue\"),\n        summary=_(\"dialogue\"),\n        operation_id=_(\"dialogue\"),  # type: ignore\n        request=ChatAPI.get_request(),\n        parameters=ChatAPI.get_parameters(),\n        responses=None,\n        tags=[_('Chat')]  # type: ignore\n    )\n    def post(self, request: Request, chat_id: str):\n        ip_address = _get_ip_address(request)\n        return ChatSerializers(data={'chat_id': chat_id,\n                                     'chat_user_id': request.auth.chat_user_id,\n                                     'chat_user_type': request.auth.chat_user_type,\n                                     'application_id': request.auth.application_id,\n                                     'debug': False,\n                                     'ip_address': ip_address,\n                                     'source': {\n                                         'type': ChatSourceChoices.API_CALL.value if request.auth.chat_user_type == ChatUserType.APPLICATION_API_KEY.value else ChatSourceChoices.ONLINE.value}\n                                     }\n                               ).chat(request.data)\n\n\nclass OpenView(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get the session id according to the application id\"),\n        summary=_(\"Get the session id according to the application id\"),\n        operation_id=_(\"Get the session id according to the application id\"),  # type: ignore\n        parameters=ChatOpenAPI.get_parameters(),\n        responses=None,\n        tags=[_('Chat')]  # type: ignore\n    )\n    def get(self, request: Request):\n        ip_address = _get_ip_address(request)\n        return result.success(OpenChatSerializers(\n            data={'application_id': request.auth.application_id,\n                  'chat_user_id': request.auth.chat_user_id, 'chat_user_type': request.auth.chat_user_type,\n                  'ip_address': ip_address,\n                  'source': {\n                      'type': ChatSourceChoices.API_CALL.value if request.auth.chat_user_type == ChatUserType.APPLICATION_API_KEY.value else ChatSourceChoices.ONLINE.value},\n                  'debug': False}).open())\n\n\nclass CaptchaView(APIView):\n    @extend_schema(methods=['GET'],\n                   summary=_(\"Get Chat captcha\"),\n                   description=_(\"Get Chat captcha\"),\n                   operation_id=_(\"Get Chat captcha\"),  # type: ignore\n                   tags=[_(\"Chat\")],  # type: ignore\n                   responses=CaptchaAPI.get_response())\n    def get(self, request: Request):\n        username = request.query_params.get('username', None)\n        accessToken = request.query_params.get('accessToken', None)\n        return result.success(CaptchaSerializer().chat_generate(username, 'chat', accessToken))\n\n\nclass SpeechToText(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"speech to text\"),\n        summary=_(\"speech to text\"),\n        operation_id=_(\"speech to text\"),  # type: ignore\n        request=SpeechToTextAPI.get_request(),\n        responses=SpeechToTextAPI.get_response(),\n        tags=[_('Chat')]  # type: ignore\n    )\n    def post(self, request: Request):\n        return result.success(\n            SpeechToTextSerializers(\n                data={'application_id': request.auth.application_id})\n            .speech_to_text({'file': request.FILES.get('file')}))\n\n\nclass TextToSpeech(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"text to speech\"),\n        summary=_(\"text to speech\"),\n        operation_id=_(\"text to speech\"),  # type: ignore\n        request=TextToSpeechAPI.get_request(),\n        responses=TextToSpeechAPI.get_response(),\n        tags=[_('Chat')]  # type: ignore\n    )\n    def post(self, request: Request):\n        byte_data = TextToSpeechSerializers(\n            data={'application_id': request.auth.application_id}).text_to_speech(request.data)\n        return HttpResponse(byte_data, status=200, headers={'Content-Type': 'audio/mp3',\n                                                            'Content-Disposition': 'attachment; filename=\"abc.mp3\"'})\n\n\nclass UploadFile(APIView):\n    authentication_classes = [ChatTokenAuth]\n    parser_classes = [MultiPartParser]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_(\"Upload files\"),\n        summary=_(\"Upload files\"),\n        operation_id=_(\"Upload files\"),  # type: ignore\n        request=TextToSpeechAPI.get_request(),\n        responses=TextToSpeechAPI.get_response(),\n        tags=[_('Application')]  # type: ignore\n    )\n    def post(self, request: Request, chat_id: str):\n        files = request.FILES.getlist('file')\n        file_ids = []\n        meta = {}\n        for file in files:\n            file_url = FileSerializer(\n                data={'file': file, 'meta': meta, 'source_id': chat_id, 'source_type': FileSourceType.CHAT, }).upload()\n            file_ids.append({'name': file.name, 'url': file_url, 'file_id': file_url.split('/')[-1]})\n        return result.success(file_ids)\n"
  },
  {
    "path": "apps/chat/views/chat_embed.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： chat_embed.py\n    @date：2025/5/30 15:22\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom chat.api.chat_embed_api import ChatEmbedAPI\nfrom chat.serializers.chat_embed_serializers import ChatEmbedSerializer\n\n\nclass ChatEmbedView(APIView):\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get embedded js'),\n        summary=_('Get embedded js'),\n        operation_id=_('Get embedded js'),  # type: ignore\n        parameters=ChatEmbedAPI.get_parameters(),\n        responses=ChatEmbedAPI.get_response(),\n        tags=[_('Chat')]  # type: ignore\n    )\n    def get(self, request: Request):\n        return ChatEmbedSerializer(\n            data={'protocol': request.query_params.get('protocol'), 'token': request.query_params.get('token'),\n                  'host': request.query_params.get('host'), }).get_embed(params=request.query_params)\n"
  },
  {
    "path": "apps/chat/views/chat_record.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： chat_record.py\n    @date：2025/6/23 10:42\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.serializers.application_chat_record import ChatRecordOperateSerializer\nfrom chat.api.chat_api import HistoricalConversationAPI, PageHistoricalConversationAPI, \\\n    PageHistoricalConversationRecordAPI, HistoricalConversationRecordAPI, HistoricalConversationOperateAPI\nfrom chat.api.vote_api import VoteAPI\nfrom chat.serializers.chat_record import VoteSerializer, HistoricalConversationSerializer, \\\n    HistoricalConversationRecordSerializer, HistoricalConversationOperateSerializer\nfrom common import result\nfrom common.auth import ChatTokenAuth\n\n\nclass VoteView(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['PUT'],\n        description=_(\"Like, Dislike\"),\n        summary=_(\"Like, Dislike\"),\n        operation_id=_(\"Like, Dislike\"),  # type: ignore\n        parameters=VoteAPI.get_parameters(),\n        request=VoteAPI.get_request(),\n        responses=VoteAPI.get_response(),\n        tags=[_('Chat')]  # type: ignore\n    )\n    def put(self, request: Request, chat_id: str, chat_record_id: str):\n        return result.success(VoteSerializer(\n            data={'chat_id': chat_id,\n                  'chat_record_id': chat_record_id\n                  }).vote(request.data))\n\n\nclass HistoricalConversationView(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get historical conversation\"),\n        summary=_(\"Get historical conversation\"),\n        operation_id=_(\"Get historical conversation\"),  # type: ignore\n        parameters=HistoricalConversationAPI.get_parameters(),\n        responses=HistoricalConversationAPI.get_response(),\n        tags=[_('Chat')]  # type: ignore\n    )\n    def get(self, request: Request):\n        return result.success(HistoricalConversationSerializer(\n            data={\n                'application_id': request.auth.application_id,\n                'chat_user_id': request.auth.chat_user_id,\n            }).list())\n\n    class Operate(APIView):\n        authentication_classes = [ChatTokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_(\"Modify conversation about\"),\n            summary=_(\"Modify conversation about\"),\n            operation_id=_(\"Modify conversation about\"),  # type: ignore\n            parameters=HistoricalConversationOperateAPI.get_parameters(),\n            request=HistoricalConversationOperateAPI.get_request(),\n            responses=HistoricalConversationOperateAPI.get_response(),\n            tags=[_('Chat')]  # type: ignore\n        )\n        def put(self, request: Request, chat_id: str):\n            return result.success(HistoricalConversationOperateSerializer(\n                data={\n                    'application_id': request.auth.application_id,\n                    'chat_user_id': request.auth.chat_user_id,\n                    'chat_id': chat_id,\n                }).edit_abstract(request.data)\n                                  )\n\n        @extend_schema(\n            methods=['DELETE'],\n            description=_(\"Delete history conversation\"),\n            summary=_(\"Delete history conversation\"),\n            operation_id=_(\"Delete history conversation\"),  # type: ignore\n            parameters=HistoricalConversationOperateAPI.get_parameters(),\n            responses=HistoricalConversationOperateAPI.get_response(),\n            tags=[_('Chat')]  # type: ignore\n        )\n        def delete(self, request: Request, chat_id: str):\n            return result.success(HistoricalConversationOperateSerializer(\n                data={\n                    'application_id': request.auth.application_id,\n                    'chat_user_id': request.auth.chat_user_id,\n                    'chat_id': chat_id,\n                }).logic_delete())\n\n    class BatchDelete(APIView):\n        authentication_classes = [ChatTokenAuth]\n\n        @extend_schema(\n            methods=['DELETE'],\n            description=_(\"Batch delete history conversation\"),\n            summary=_(\"Batch delete history conversation\"),\n            operation_id=_(\"Batch delete history conversation\"),  # type: ignore\n            parameters=HistoricalConversationOperateAPI.get_parameters(),\n            responses=HistoricalConversationOperateAPI.get_response(),\n            tags=[_('Chat')]  # type: ignore\n        )\n        def delete(self, request: Request):\n            return result.success(HistoricalConversationOperateSerializer.Clear(data={\n                'application_id': request.auth.application_id,\n                'chat_user_id': request.auth.chat_user_id,\n            }).batch_logic_delete())\n\n    class PageView(APIView):\n        authentication_classes = [ChatTokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get historical conversation by page\"),\n            summary=_(\"Get historical conversation by page\"),\n            operation_id=_(\"Get historical conversation by page\"),  # type: ignore\n            parameters=PageHistoricalConversationAPI.get_parameters(),\n            responses=PageHistoricalConversationAPI.get_response(),\n            tags=[_('Chat')]  # type: ignore\n        )\n        def get(self, request: Request, current_page: int, page_size: int):\n            return result.success(HistoricalConversationSerializer(\n                data={\n                    'application_id': request.auth.application_id,\n                    'chat_user_id': request.auth.chat_user_id,\n                }).page(current_page, page_size))\n\n\nclass HistoricalConversationRecordView(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get historical conversation records\"),\n        summary=_(\"Get historical conversation records\"),\n        operation_id=_(\"Get historical conversation records\"),  # type: ignore\n        parameters=HistoricalConversationRecordAPI.get_parameters(),\n        responses=HistoricalConversationRecordAPI.get_response(),\n        tags=[_('Chat')]  # type: ignore\n    )\n    def get(self, request: Request, chat_id: str):\n        return result.success(HistoricalConversationRecordSerializer(\n            data={\n                'chat_id': chat_id,\n                'application_id': request.auth.application_id,\n                'chat_user_id': request.auth.chat_user_id,\n            }).list())\n\n    class PageView(APIView):\n        authentication_classes = [ChatTokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get historical conversation records by page \"),\n            summary=_(\"Get historical conversation records by page\"),\n            operation_id=_(\"Get historical conversation records by page\"),  # type: ignore\n            parameters=PageHistoricalConversationRecordAPI.get_parameters(),\n            responses=PageHistoricalConversationRecordAPI.get_response(),\n            tags=[_('Chat')]  # type: ignore\n        )\n        def get(self, request: Request, chat_id: str, current_page: int, page_size: int):\n            return result.success(HistoricalConversationRecordSerializer(\n                data={\n                    'chat_id': chat_id,\n                    'application_id': request.auth.application_id,\n                    'chat_user_id': request.auth.chat_user_id,\n                }).page(current_page, page_size))\n\n\nclass ChatRecordView(APIView):\n    authentication_classes = [ChatTokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get conversation details\"),\n        summary=_(\"Get conversation details\"),\n        operation_id=_(\"Get conversation details\"),  # type: ignore\n        parameters=PageHistoricalConversationRecordAPI.get_parameters(),\n        responses=PageHistoricalConversationRecordAPI.get_response(),\n        tags=[_('Chat')]  # type: ignore\n    )\n    def get(self, request: Request, chat_id: str, chat_record_id: str):\n        return result.success(ChatRecordOperateSerializer(\n            data={\n                'chat_id': chat_id,\n                'chat_record_id': chat_record_id,\n                'application_id': request.auth.application_id,\n                'chat_user_id': request.auth.chat_user_id,\n            }).one(False))\n"
  },
  {
    "path": "apps/chat/views/mcp.py",
    "content": "import json\n\nfrom django.http import JsonResponse, HttpResponse\nfrom django.views.decorators.csrf import csrf_exempt\n\nfrom chat.mcp.tools import MCPToolHandler\n\n\n@csrf_exempt\ndef mcp_view(request):\n    request_id = None\n    try:\n        data = json.loads(request.body)\n        method = data.get(\"method\")\n        params = data.get(\"params\", {})\n        request_id = data.get(\"id\")\n\n        if request_id is None:\n            return HttpResponse(status=204)\n\n        auth_header = request.headers.get(\"Authorization\", \"\").replace(\"Bearer \", \"\")\n        handler = MCPToolHandler(auth_header)\n\n        # 路由方法\n        if method == \"initialize\":\n            result = handler.initialize()\n\n        elif method == \"tools/list\":\n            result = handler.list_tools()\n\n        elif method == \"tools/call\":\n            result = handler.call_tool(params)\n\n        else:\n            return JsonResponse({\n                \"jsonrpc\": \"2.0\",\n                \"id\": request_id,\n                \"error\": {\n                    \"code\": -32601,\n                    \"message\": f\"Method not found: {method}\"\n                }\n            })\n\n        # 成功响应\n        return JsonResponse({\n            \"jsonrpc\": \"2.0\",\n            \"id\": request_id,\n            \"result\": result\n        })\n\n    except Exception as e:\n        return JsonResponse({\n            \"jsonrpc\": \"2.0\",\n            \"id\": request_id,\n            \"error\": {\n                \"code\": -32603,\n                \"message\": f\"Internal error: {str(e)}\"\n            }\n        })\n"
  },
  {
    "path": "apps/common/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/auth/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/4/14 10:44\n    @desc:\n\"\"\"\nfrom .authenticate import *\n"
  },
  {
    "path": "apps/common/auth/authenticate.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎虎\n    @file： authenticate.py\n    @date：2023/9/4 11:16\n    @desc:  认证类\n\"\"\"\nfrom importlib import import_module\n\nfrom django.conf import settings\nfrom django.core import cache\nfrom django.core import signing\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.extensions import OpenApiAuthenticationExtension\nfrom rest_framework.authentication import TokenAuthentication\n\nfrom common.exception.app_exception import AppAuthenticationFailed, AppEmbedIdentityFailed, AppChatNumOutOfBoundsFailed, \\\n    AppApiException\nfrom common.utils.logger import maxkb_logger\n\ntoken_cache = cache.caches['default']\n\n\nclass AnonymousAuthentication(TokenAuthentication):\n    def authenticate(self, request):\n        return None, None\n\n\nclass AnonymousAuthenticationScheme(OpenApiAuthenticationExtension):\n    target_class = AnonymousAuthentication  # 绑定到你的自定义认证类\n    name = \"AnonymousAuth\"  # 自定义认证名称（显示在 Swagger UI 中）\n\n    def get_security_definition(self, auto_schema):\n        # 定义认证方式，这里假设匿名认证不需要凭证\n        return {\n        }\n\n    def get_security_requirement(self, auto_schema):\n        # 返回安全要求（空字典表示无需认证）\n        return {}\n\n\ndef new_instance_by_class_path(class_path: str):\n    parts = class_path.rpartition('.')\n    package_path = parts[0]\n    class_name = parts[2]\n    module = import_module(package_path)\n    HandlerClass = getattr(module, class_name)\n    return HandlerClass()\n\n\nhandles = [new_instance_by_class_path(class_path) for class_path in settings.AUTH_HANDLES]\nchat_handles = [new_instance_by_class_path(class_path) for class_path in settings.CHAT_AUTH_HANDLES]\nall_handles = handles + chat_handles\n\n\nclass TokenDetails:\n    token_details = None\n    is_load = False\n\n    def __init__(self, token: str):\n        self.token = token\n\n    def get_token_details(self):\n        if self.token_details is None and not self.is_load:\n            try:\n                self.token_details = signing.loads(self.token)\n            except Exception as e:\n                self.is_load = True\n        return self.token_details\n\n\nclass TokenAuth(TokenAuthentication):\n    keyword = \"Bearer\"\n\n    # 重新 authenticate 方法，自定义认证规则\n    def authenticate(self, request):\n        auth = request.META.get('HTTP_AUTHORIZATION')\n        # 未认证\n        if auth is None:\n            raise AppAuthenticationFailed(1003, _('Not logged in, please log in first'))\n        if not auth.startswith(\"Bearer \"):\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user'))\n        try:\n            token = auth[7:]\n            token_details = TokenDetails(token)\n            for handle in handles:\n                if handle.support(request, token, token_details.get_token_details):\n                    return handle.handle(request, token, token_details.get_token_details)\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed) or isinstance(e,\n                                                                                                                 AppApiException):\n                raise e\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user'))\n\n\nclass ChatTokenAuth(TokenAuthentication):\n    keyword = \"Bearer\"\n\n    # 重新 authenticate 方法，自定义认证规则\n    def authenticate(self, request):\n        auth = request.META.get('HTTP_AUTHORIZATION')\n        # 未认证\n        if auth is None:\n            raise AppAuthenticationFailed(1003, _('Not logged in, please log in first'))\n        if not auth.startswith(\"Bearer \"):\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user'))\n        try:\n            token = auth[7:]\n            token_details = TokenDetails(token)\n            for handle in chat_handles:\n                if handle.support(request, token, token_details.get_token_details):\n                    return handle.handle(request, token, token_details.get_token_details)\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed) or isinstance(e,\n                                                                                                                 AppApiException):\n                raise e\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user'))\n\n\nclass AllTokenAuth(TokenAuthentication):\n    keyword = \"Bearer\"\n\n    # 重新 authenticate 方法，自定义认证规则\n    def authenticate(self, request):\n        auth = request.META.get('HTTP_AUTHORIZATION')\n        # 未认证\n        if auth is None:\n            raise AppAuthenticationFailed(1003, _('Not logged in, please log in first'))\n        if not auth.startswith(\"Bearer \"):\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user'))\n        try:\n            token = auth[7:]\n            token_details = TokenDetails(token)\n            for handle in all_handles:\n                if handle.support(request, token, token_details.get_token_details):\n                    return handle.handle(request, token, token_details.get_token_details)\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppEmbedIdentityFailed) or isinstance(e, AppChatNumOutOfBoundsFailed) or isinstance(e,\n                                                                                                                 AppApiException):\n                raise e\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect! illegal user'))\n\n\nclass WebhookAuth(TokenAuthentication):\n    keyword = \"Bearer\"\n\n    # 重新 authenticate 方法，自定义认证规则\n    def authenticate(self, request):\n        return None, {}\n"
  },
  {
    "path": "apps/common/auth/authentication.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： authentication.py\n    @date：2025/4/15 20:12\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants, \\\n    Permission, Role\nfrom common.exception.app_exception import AppUnauthorizedFailed\n\n\ndef exist_permissions_by_permission_constants(user_permission: List[PermissionConstants],\n                                              permission_list: List[PermissionConstants]):\n    \"\"\"\n    用户是否拥有 permission_list的权限\n    :param user_permission:  用户权限\n    :param permission_list:  需要的权限\n    :return: 是否拥有\n    \"\"\"\n    return any(list(map(lambda up: permission_list.__contains__(up), user_permission)))\n\n\ndef exist_role_by_role_constants(user_role: List[RoleConstants],\n                                 role_list: List[RoleConstants]):\n    \"\"\"\n    用户是否拥有这个角色\n    :param user_role: 用户角色\n    :param role_list: 需要拥有的角色\n    :return:  是否拥有\n    \"\"\"\n    return any([True for role in role_list if user_role.__contains__(role.value.__str__())])\n\n\ndef exist_permissions_by_view_permission(user_role: List[RoleConstants],\n                                         user_permission: List[PermissionConstants | object],\n                                         permission: ViewPermission, request, **kwargs):\n    \"\"\"\n    用户是否存在这些权限\n    :param request:\n    :param user_role:        用户角色\n    :param user_permission:  用户权限\n    :param permission:       所属权限\n    :return:                 是否存在 True False\n    \"\"\"\n\n    role_list = [user_r(request, kwargs) if callable(user_r) else user_r for user_r in\n                 permission.roleList]\n    role_ok = any(list(map(lambda up: role_list.__contains__(up),\n                           user_role)))\n    permission_list = [user_p(request, kwargs) if callable(user_p) else user_p for user_p in\n                       permission.permissionList\n                       ]\n    permission_ok = any(list(map(lambda up: permission_list.__contains__(up),\n                                 user_permission)))\n    return role_ok | permission_ok if permission.compare == CompareConstants.OR else role_ok & permission_ok\n\n\ndef exist_permissions(user_role: List[RoleConstants], user_permission: List[PermissionConstants], permission, request,\n                      **kwargs):\n    if isinstance(permission, ViewPermission):\n        return exist_permissions_by_view_permission(user_role, user_permission, permission, request, **kwargs)\n    if isinstance(permission, RoleConstants):\n        return exist_role_by_role_constants(user_role, [permission])\n    if isinstance(permission, PermissionConstants):\n        return exist_permissions_by_permission_constants(user_permission, [permission])\n    if isinstance(permission, Permission):\n        return user_permission.__contains__(permission)\n    if isinstance(permission, Role):\n        return user_role.__contains__(permission.__str__())\n    return False\n\n\ndef exist(user_role: List[RoleConstants], user_permission: List[PermissionConstants], permission, request, **kwargs):\n    if callable(permission):\n        p = permission(request, kwargs)\n        return exist_permissions(user_role, user_permission, p, request, **kwargs)\n    return exist_permissions(user_role, user_permission, permission, request, **kwargs)\n\n\ndef get_is_permissions(request, **kwargs):\n    def is_permissions(*permission, compare=CompareConstants.OR):\n        exit_list = list(\n            map(lambda p: exist(request.auth.role_list, request.auth.permission_list, p, request, **kwargs),\n                permission))\n        return any(exit_list) if compare == CompareConstants.OR else all(exit_list)\n\n    return is_permissions\n\n\ndef has_permissions(*permission, compare=CompareConstants.OR):\n    \"\"\"\n    权限 role or permission\n    :param compare:    比较符号\n    :param permission: 如果是角色 role:roleId\n    :return: 权限装饰器函数,用于判断用户是否有权限访问当前接口\n    \"\"\"\n\n    def inner(func):\n        def run(view, request, **kwargs):\n            exit_list = list(\n                map(lambda p: exist(request.auth.role_list, request.auth.permission_list, p, request, **kwargs),\n                    permission))\n            # 判断是否有权限\n            if any(exit_list) if compare == CompareConstants.OR else all(exit_list):\n                return func(view, request, **kwargs)\n            raise AppUnauthorizedFailed(403, _('No permission to access'))\n\n        return run\n\n    return inner\n"
  },
  {
    "path": "apps/common/auth/common.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： common.py\n    @date：2025/6/6 19:55\n    @desc:\n\"\"\"\nimport hashlib\nimport json\nimport threading\n\nfrom django.core import signing, cache\n\nfrom common.constants.cache_version import Cache_Version\nfrom common.utils.rsa_util import encrypt, decrypt\n\nauthentication_cache = cache.cache\nlock = threading.Lock()\n\n\ndef _decrypt(authentication: str):\n    cache_key = hashlib.sha256(authentication.encode()).hexdigest()\n    result = authentication_cache.get(key=cache_key, version=Cache_Version.CHAT.value)\n    if result is None:\n        with lock:\n            result = authentication_cache.get(cache_key, version=Cache_Version.CHAT.value)\n            if result is None:\n                result = decrypt(authentication)\n                authentication_cache.set(cache_key, result, version=Cache_Version.CHAT.value, timeout=60 * 60 * 2)\n\n    return result\n\n\nclass ChatAuthentication:\n    def __init__(self, auth_type: str | None, **kwargs):\n        self.auth_type = auth_type\n        for k, v in kwargs.items():\n            self.__setattr__(k, v)\n\n    def to_dict(self):\n        return self.__dict__\n\n    def to_string(self):\n        value = json.dumps(self.to_dict())\n        authentication = encrypt(value)\n        cache_key = hashlib.sha256(authentication.encode()).hexdigest()\n        authentication_cache.set(cache_key, value, version=Cache_Version.CHAT.get_version(), timeout=60 * 60 * 2)\n        return authentication\n\n    @staticmethod\n    def new_instance(authentication: str):\n        auth = json.loads(_decrypt(authentication))\n        return ChatAuthentication(**auth)\n\n\nclass ChatUserToken:\n    def __init__(self, application_id, user_id, access_token, _type, chat_user_type, chat_user_id,\n                 authentication: ChatAuthentication):\n        self.application_id = application_id\n        self.user_id = user_id\n        self.access_token = access_token\n        self.type = _type\n        self.chat_user_type = chat_user_type\n        self.chat_user_id = chat_user_id\n        self.authentication = authentication\n\n    def to_dict(self):\n        return {\n            'application_id': str(self.application_id),\n            'user_id': str(self.user_id),\n            'access_token': self.access_token,\n            'type': str(self.type.value),\n            'chat_user_type': str(self.chat_user_type),\n            'chat_user_id': str(self.chat_user_id),\n            'authentication': self.authentication.to_string()\n        }\n\n    def to_token(self):\n        return signing.dumps(self.to_dict())\n\n    @staticmethod\n    def new_instance(token_dict):\n        return ChatUserToken(token_dict.get('application_id'), token_dict.get('user_id'),\n                             token_dict.get('access_token'), token_dict.get('type'), token_dict.get('chat_user_type'),\n                             token_dict.get('chat_user_id'),\n                             ChatAuthentication.new_instance(token_dict.get('authentication')))\n"
  },
  {
    "path": "apps/common/auth/handle/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/auth/handle/auth_base_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎虎\n    @file： authenticate.py\n    @date：2024/3/14 03:02\n    @desc:  认证处理器\n\"\"\"\nfrom abc import ABC, abstractmethod\n\n\nclass AuthBaseHandle(ABC):\n    @abstractmethod\n    def support(self, request, token: str, get_token_details):\n        pass\n\n    @abstractmethod\n    def handle(self, request, token: str, get_token_details):\n        pass\n"
  },
  {
    "path": "apps/common/auth/handle/impl/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/auth/handle/impl/application_key.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_key.py\n    @date：2025/7/10 03:02\n    @desc:  应用api key认证\n\"\"\"\nfrom django.db.models import QuerySet\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\n\nfrom application.models import ApplicationApiKey, ChatUserType, ApplicationAccessToken\nfrom common.auth.handle.auth_base_handle import AuthBaseHandle\nfrom common.constants.permission_constants import Permission, Group, Operate, RoleConstants, ChatAuth\nfrom common.exception.app_exception import AppAuthenticationFailed\n\n\nclass ApplicationKey(AuthBaseHandle):\n    def handle(self, request, token: str, get_token_details):\n        application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=token).first()\n        if application_api_key is None:\n            raise AppAuthenticationFailed(500, _('Secret key is invalid'))\n        if not application_api_key.is_active:\n            raise AppAuthenticationFailed(500, _('Secret key is invalid'))\n        if application_api_key.is_permanent is False and application_api_key.expire_time < timezone.now():\n            raise AppAuthenticationFailed(500, _('Secret key is expired'))\n        application_access_token = QuerySet(ApplicationAccessToken).filter(\n            application_id=application_api_key.application_id).first()\n        if application_access_token is not None:\n            if application_access_token.authentication:\n                if application_access_token.authentication_value.get('type',\n                                                                     'password') != 'password':\n                    raise AppAuthenticationFailed(1002, _('Authentication information is incorrect'))\n        return None, ChatAuth(\n            current_role_list=[RoleConstants.CHAT_ANONYMOUS_USER],\n            permission_list=[\n                Permission(group=Group.APPLICATION,\n                           operate=Operate.READ)],\n            application_id=application_api_key.application_id,\n            chat_user_id=str(application_api_key.id),\n            chat_user_type=ChatUserType.APPLICATION_API_KEY.value)\n\n    def support(self, request, token: str, get_token_details):\n        return str(token).startswith(\"application-\") or str(token).startswith('agent-')\n"
  },
  {
    "path": "apps/common/auth/handle/impl/chat_anonymous_user_token.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： chat_anonymous_user_token.py\n    @date：2025/6/6 15:08\n    @desc:\n\"\"\"\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\n\nfrom application.models import ApplicationAccessToken\nfrom common.auth.common import ChatUserToken\nfrom common.auth.handle.auth_base_handle import AuthBaseHandle\nfrom common.constants.authentication_type import AuthenticationType\nfrom common.constants.permission_constants import RoleConstants, Permission, Group, Operate, ChatAuth\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.exception.app_exception import AppAuthenticationFailed\nfrom maxkb.settings import edition\n\n\nclass ChatAnonymousUserToken(AuthBaseHandle):\n    def support(self, request, token: str, get_token_details):\n        token_details = get_token_details()\n        if token_details is None:\n            return False\n        return (\n                'application_id' in token_details and\n                'access_token' in token_details and\n                token_details.get('type') == AuthenticationType.CHAT_ANONYMOUS_USER.value)\n\n    def handle(self, request, token: str, get_token_details):\n        auth_details = get_token_details()\n        chat_user_token = ChatUserToken.new_instance(auth_details)\n        application_id = chat_user_token.application_id\n        access_token = chat_user_token.access_token\n        application_access_token = QuerySet(ApplicationAccessToken).filter(\n            application_id=application_id).first()\n        if application_access_token is None:\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect'))\n        if not application_access_token.is_active:\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect'))\n        if not application_access_token.access_token == access_token:\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect'))\n        if application_access_token.authentication and ['PE', 'EE'].__contains__(edition):\n            if chat_user_token.authentication.auth_type != application_access_token.authentication_value.get('type',\n                                                                                                             ''):\n                raise AppAuthenticationFailed(1002, _('Authentication information is incorrect'))\n        return None, ChatAuth(\n            current_role_list=[RoleConstants.CHAT_ANONYMOUS_USER],\n            permission_list=[\n                Permission(group=Group.APPLICATION,\n                           operate=Operate.USE)],\n            application_id=application_access_token.application_id,\n            chat_user_id=chat_user_token.chat_user_id,\n            chat_user_type=chat_user_token.chat_user_type)\n"
  },
  {
    "path": "apps/common/auth/handle/impl/user_token.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： authenticate.py\n    @date：2024/3/14 03:02\n    @desc:  用户认证\n\"\"\"\nfrom functools import reduce\nfrom typing import List\n\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.auth.handle.auth_base_handle import AuthBaseHandle\nfrom common.constants.authentication_type import AuthenticationType\nfrom common.constants.cache_version import Cache_Version\nfrom common.constants.permission_constants import Auth, PermissionConstants, ResourcePermissionGroup, \\\n    get_permission_list_by_resource_group, ResourceAuthType, \\\n    ResourcePermissionRole, get_default_role_permission_mapping_list, get_default_workspace_user_role_mapping_list, \\\n    RoleConstants, ResourcePermission, Resource, WorkspaceGroup\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.exception.app_exception import AppAuthenticationFailed\nfrom common.utils.common import group_by\nfrom maxkb.const import CONFIG\nfrom system_manage.models.workspace_user_permission import WorkspaceUserResourcePermission\nfrom users.models import User\n\npermission_constants_dict = {p.value.__str__(): p for p in PermissionConstants}\n\ndef get_permission(permission_id):\n    \"\"\"\n    获取权限字符串\n    @param permission_id: 权限id\n    @return:  权限字符串\n    \"\"\"\n    if isinstance(permission_id, PermissionConstants):\n        permission_id = permission_id.value\n    return f\"{permission_id}\"\n\n\ndef get_workspace_permission(permission_id, workspace_id, role=None):\n    \"\"\"\n    获取工作空间权限字符串\n    @param permission_id: 权限id\n    @param workspace_id:  工作空间id\n    @param role:     角色\n    @return:\n    \"\"\"\n    if isinstance(permission_id, PermissionConstants):\n        permission_id = permission_id.value\n    if role and role.type == RoleConstants.WORKSPACE_MANAGE.value.__str__():\n        return [f\"{permission_id}:/WORKSPACE/{workspace_id}:ROLE/{role.type}\",\n                f\"{permission_id}:/WORKSPACE/{workspace_id}\"]\n    return [f\"{permission_id}:/WORKSPACE/{workspace_id}\"]\n\n\ndef get_role_permission(role, workspace_id):\n    \"\"\"\n    获取工作空间角色\n    @param role:  角色\n    @param workspace_id: 工作空间id\n    @return:\n    \"\"\"\n    if isinstance(role, RoleConstants):\n        role = role.value\n    return f\"{role}:/WORKSPACE/{workspace_id}\"\n\n\ndef get_workspace_permission_list(role_permission_mapping_dict, workspace_user_role_mapping_list, role_model_dict):\n    \"\"\"\n    获取工作空间下所有的权限\n    @param role_permission_mapping_dict:   角色权限关联字典\n    @param workspace_user_role_mapping_list: 工作空间用户角色关联列表\n    @param role_model_dict:         角色字典\n    @return: 工作空间下的权限\n    \"\"\"\n    workspace_permission_list = [\n        [get_workspace_permission(role_permission_mapping.permission_id, w_u_r.workspace_id,\n                                  role_model_dict.get(w_u_r.role_id, None)) for role_permission_mapping\n         in\n         role_permission_mapping_dict.get(w_u_r.role_id, [])] for w_u_r in workspace_user_role_mapping_list]\n    return reduce(lambda x, y: [*x, *y], reduce(lambda x, y: [*x, *y], workspace_permission_list, []), [])\n\n\ndef get_workspace_resource_permission_list(\n        workspace_user_resource_permission_list: List[WorkspaceUserResourcePermission],\n        role_permission_mapping_dict,\n        workspace_user_role_mapping_dict):\n    \"\"\"\n\n    @param workspace_user_resource_permission_list: 工作空间用户资源权限列表\n    @param role_permission_mapping_dict:            角色权限关联字典        key为role_id\n    @param workspace_user_role_mapping_dict:        工作空间用户角色映射字典  key为role_id\n    @return: 工作空间资源权限列表\n    \"\"\"\n    resource_permission_list = [\n        get_workspace_resource_permission_list_by_workspace_user_permission(workspace_user_resource_permission,\n                                                                            role_permission_mapping_dict,\n                                                                            workspace_user_role_mapping_dict) for\n        workspace_user_resource_permission in workspace_user_resource_permission_list]\n    # 将二维数组扁平为一维\n    return reduce(lambda x, y: [*x, *y], resource_permission_list, [])\n\n\ndef get_workspace_resource_permission_list_by_workspace_user_permission(\n        workspace_user_resource_permission: WorkspaceUserResourcePermission,\n        role_permission_mapping_dict,\n        workspace_user_role_mapping_dict):\n    \"\"\"\n\n    @param workspace_user_resource_permission: 工作空间用户资源权限对象\n    @param role_permission_mapping_dict:       角色权限关联字典            key为role_id\n    @param workspace_user_role_mapping_dict:   工作空间用户角色关联字典  key为role_id\n    @return: 工作空间用户资源的权限列表\n    \"\"\"\n    role_permission_mapping_list = [role_permission_mapping_dict.get(workspace_user_role_mapping.role_id, []) for\n                                    workspace_user_role_mapping in\n                                    workspace_user_role_mapping_dict.get(\n                                        workspace_user_resource_permission.workspace_id)]\n    role_permission_mapping_list = reduce(lambda x, y: [*x, *y], role_permission_mapping_list, [])\n    # 如果是根据角色\n    if (workspace_user_resource_permission.auth_type == ResourceAuthType.ROLE\n            and workspace_user_resource_permission.permission_list.__contains__(\n                ResourcePermissionRole.ROLE)):\n        return [\n            f\"{role_permission_mapping.permission_id}:/WORKSPACE/{workspace_user_resource_permission.workspace_id}/{workspace_user_resource_permission.auth_target_type}/{workspace_user_resource_permission.target}\"\n            for role_permission_mapping in role_permission_mapping_list if (permission_constants_dict.get(role_permission_mapping.permission_id).value.parent_group or []).__contains__(\n                                        WorkspaceGroup(workspace_user_resource_permission.auth_target_type))] + [\n            f\"{workspace_user_resource_permission.auth_target_type}:/WORKSPACE/{workspace_user_resource_permission.workspace_id}/{workspace_user_resource_permission.auth_target_type}/{workspace_user_resource_permission.target}\"]\n\n    elif workspace_user_resource_permission.auth_type == ResourceAuthType.RESOURCE_PERMISSION_GROUP:\n        resource_permission_list = [\n            [\n                f\"{permission}:/WORKSPACE/{workspace_user_resource_permission.workspace_id}/{workspace_user_resource_permission.auth_target_type}/{workspace_user_resource_permission.target}\"\n                for permission in get_permission_list_by_resource_group(\n                ResourcePermissionGroup(Resource(workspace_user_resource_permission.auth_target_type),\n                                        ResourcePermission(resource_permission)))]\n            for resource_permission in workspace_user_resource_permission.permission_list if\n            ResourcePermission.values.__contains__(resource_permission)]\n        # 将二维数组扁平为一维\n        return reduce(lambda x, y: [*x, *y], resource_permission_list, [])\n    return []\n\n\ndef get_permission_list(user,\n                        workspace_user_role_mapping_model,\n                        workspace_model,\n                        role_model,\n                        role_permission_mapping_model):\n    user_id = user.id\n    version = Cache_Version.PERMISSION_LIST.get_version()\n    key = Cache_Version.PERMISSION_LIST.get_key(user_id=user_id)\n    # 获取权限列表\n    is_query_model = workspace_user_role_mapping_model is not None and workspace_model is not None and role_model is not None and role_permission_mapping_model is not None\n    permission_list = cache.get(key, version=version)\n    if permission_list is None:\n        if is_query_model:\n            # 获取工作空间 用户 角色映射数据\n            workspace_user_role_mapping_list = QuerySet(workspace_user_role_mapping_model).filter(user_id=user_id)\n            workspace_user_role_mapping_dict = group_by(workspace_user_role_mapping_list,\n                                                        lambda item: item.workspace_id)\n            role_id_list = list(set([workspace_user_role_mapping.role_id for workspace_user_role_mapping in\n                                     workspace_user_role_mapping_list]))\n            # 获取角色权限映射数据\n            role_permission_mapping_list = QuerySet(role_permission_mapping_model).filter(\n                role_id__in=role_id_list)\n            role_model_list = QuerySet(role_model).filter(id__in=role_id_list)\n\n            role_model_dict = {role_model.id: role_model for role_model in role_model_list}\n\n            role_permission_mapping_dict = group_by(\n                role_permission_mapping_list, lambda item: item.role_id)\n\n            workspace_user_permission_list = QuerySet(WorkspaceUserResourcePermission).filter(\n                workspace_id__in=[workspace_user_role.workspace_id for workspace_user_role in\n                                  workspace_user_role_mapping_list if\n                                  (role_model_dict.get(workspace_user_role.role_id).type == 'USER' if\n                                   role_model_dict.get(workspace_user_role.role_id) else False)],\n                user_id=user_id)\n\n            # 资源权限\n            workspace_resource_permission_list = get_workspace_resource_permission_list(workspace_user_permission_list,\n                                                                                        role_permission_mapping_dict,\n                                                                                        workspace_user_role_mapping_dict)\n\n            workspace_permission_list = get_workspace_permission_list(role_permission_mapping_dict,\n                                                                      workspace_user_role_mapping_list, role_model_dict)\n            # 系统权限\n            system_permission_list = [role_permission_mapping.permission_id for role_permission_mapping in\n                                      role_permission_mapping_list]\n            # 合并权限\n            permission_list = system_permission_list + workspace_permission_list + workspace_resource_permission_list\n            permission_list = list(set(permission_list))\n            cache.set(key, permission_list, version=version)\n        else:\n            workspace_id_list = ['default']\n            workspace_user_resource_permission_list = QuerySet(WorkspaceUserResourcePermission).filter(\n                workspace_id__in=workspace_id_list, user_id=user_id)\n            role_permission_mapping_list = get_default_role_permission_mapping_list()\n            role_permission_mapping_dict = group_by(role_permission_mapping_list, lambda item: item.role_id)\n            workspace_user_role_mapping_list = get_default_workspace_user_role_mapping_list([user.role])\n            workspace_user_role_mapping_dict = group_by(workspace_user_role_mapping_list,\n                                                        lambda item: item.workspace_id)\n            # 资源权限\n            workspace_resource_permission_list = get_workspace_resource_permission_list(\n                workspace_user_resource_permission_list,\n                role_permission_mapping_dict,\n                workspace_user_role_mapping_dict)\n            # 合并权限\n            permission_list = workspace_resource_permission_list\n            permission_list = list(set(permission_list))\n            cache.set(key, permission_list, version=version)\n    return permission_list\n\n\nsystem_role_list = [RoleConstants.ADMIN.value.name, RoleConstants.WORKSPACE_MANAGE.value.name,\n                    RoleConstants.USER.value.name]\n\nsystem_role = RoleConstants.ADMIN.value.name\n\n\ndef reset_workspace_role(role_id, workspace_id, role_dict):\n    if system_role_list.__contains__(role_id):\n        if system_role == role_id:\n            return [role_id]\n        else:\n            return [f\"{role_id}:/WORKSPACE/{workspace_id}\", role_id]\n    else:\n        r = role_dict.get(role_id)\n        if r is None:\n            return ''\n        role_type = role_dict.get(role_id).type\n        if system_role == role_type:\n            return [RoleConstants.EXTENDS_ADMIN.value.name]\n        return [f\"EXTENDS_{role_type}:/WORKSPACE/{workspace_id}\", f\"EXTENDS_{role_type}\"]\n\n\ndef get_role_list(user,\n                  workspace_user_role_mapping_model,\n                  workspace_model,\n                  role_model,\n                  role_permission_mapping_model):\n    \"\"\"\n    获取当前用户的角色列表\n    \"\"\"\n    version = Cache_Version.ROLE_LIST.get_version()\n    key = Cache_Version.ROLE_LIST.get_key(user_id=user.id)\n    workspace_list = cache.get(key, version=version)\n    # 获取权限列表\n    is_query_model = workspace_user_role_mapping_model is not None and workspace_model is not None and role_model is not None and role_permission_mapping_model is not None\n    if workspace_list is None:\n        if is_query_model:\n            # 获取工作空间 用户 角色映射数据\n            workspace_user_role_mapping_list = QuerySet(workspace_user_role_mapping_model).filter(user_id=user.id)\n            role_list = QuerySet(role_model).filter(id__in=[wurm.role_id for wurm in workspace_user_role_mapping_list])\n            role_dict = {r.id: r for r in role_list}\n            role_list = list(\n                set(reduce(lambda x, y: [*x, *y], [reset_workspace_role(workspace_user_role_mapping.role_id,\n                                                                        workspace_user_role_mapping.workspace_id,\n                                                                        role_dict)\n                                                   for\n                                                   workspace_user_role_mapping in\n                                                   workspace_user_role_mapping_list], [])))\n            cache.set(key, workspace_list, version=version)\n            return role_list\n        else:\n            if user.role == RoleConstants.ADMIN.value.__str__():\n                role_list = [user.role, get_role_permission(RoleConstants.WORKSPACE_MANAGE, 'default')]\n            else:\n                role_list = [user.role, get_role_permission(RoleConstants.USER, 'default')]\n            cache.set(key, role_list, version=version)\n            return role_list\n    return workspace_list\n\n\ndef get_auth(user):\n    workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n    workspace_model = DatabaseModelManage.get_model(\"workspace_model\")\n    role_model = DatabaseModelManage.get_model(\"role_model\")\n    role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n\n    permission_list = get_permission_list(user, workspace_user_role_mapping_model, workspace_model,\n                                          role_model, role_permission_mapping_model)\n    role_list = get_role_list(user, workspace_user_role_mapping_model, workspace_model,\n                              role_model, role_permission_mapping_model)\n    return Auth(role_list, permission_list)\n\n\nclass UserToken(AuthBaseHandle):\n    def support(self, request, token: str, get_token_details):\n        auth_details = get_token_details()\n        if auth_details is None:\n            return False\n        return 'id' in auth_details and auth_details.get('type') == AuthenticationType.SYSTEM_USER.value\n\n    def handle(self, request, token: str, get_token_details):\n        version, get_key = Cache_Version.TOKEN.value\n        cache_token = cache.get(get_key(token), version=version)\n        if cache_token is None:\n            raise AppAuthenticationFailed(1002, _('Login expired'))\n        auth_details = get_token_details()\n        timeout = CONFIG.get_session_timeout()\n        cache.touch(token, timeout=timeout, version=version)\n        user = QuerySet(User).get(id=auth_details['id'])\n        if not user.is_active or user.password != cache_token.password:\n            raise AppAuthenticationFailed(1002, _('Authentication information is incorrect'))\n        auth = get_auth(user)\n        return user, auth\n"
  },
  {
    "path": "apps/common/cache/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/cache/mem_cache.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： mem_cache.py\n    @date：2024/3/6 11:20\n    @desc:\n\"\"\"\nfrom django.core.cache.backends.base import DEFAULT_TIMEOUT\nfrom django.core.cache.backends.locmem import LocMemCache\n\n\nclass MemCache(LocMemCache):\n    def __init__(self, name, params):\n        super().__init__(name, params)\n\n    def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):\n        key = self.make_and_validate_key(key, version=version)\n        pickled = value\n        with self._lock:\n            self._set(key, pickled, timeout)\n\n    def get(self, key, default=None, version=None):\n        key = self.make_and_validate_key(key, version=version)\n        with self._lock:\n            if self._has_expired(key):\n                self._delete(key)\n                return default\n            pickled = self._cache[key]\n            self._cache.move_to_end(key, last=False)\n        return pickled\n\n    def clear_by_application_id(self, application_id):\n        delete_keys = []\n        for key in self._cache.keys():\n            value = self._cache.get(key)\n            if (hasattr(value,\n                        'application') and value.application is not None and value.application.id is not None and\n                    str(\n                        value.application.id) == application_id):\n                delete_keys.append(key)\n        for key in delete_keys:\n            self._delete(key)\n\n    def clear_timeout_data(self):\n        for key in self._cache.keys():\n            self.get(key)\n"
  },
  {
    "path": "apps/common/cache_data/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/cache_data/application_access_token_cache.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： application_access_token_cache.py\n    @date：2024/7/25 11:34\n    @desc:\n\"\"\"\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\n\nfrom application.models import ApplicationAccessToken\nfrom common.utils.cache_util import get_cache\n\n\n@get_cache(cache_key=lambda access_token, use_get_data: access_token,\n           use_get_data=lambda access_token, use_get_data: use_get_data,\n           version='APPLICATION_ACCESS_TOKEN_CACHE')\ndef get_application_access_token(access_token, use_get_data):\n    application_access_token = QuerySet(ApplicationAccessToken).filter(access_token=access_token).first()\n    if application_access_token is None:\n        return None\n    return {'white_active': application_access_token.white_active,\n            'white_list': application_access_token.white_list,\n            'application_icon': application_access_token.application.icon,\n            'application_name': application_access_token.application.name}\n\n\ndef del_application_access_token(access_token):\n    cache.delete(access_token, version='APPLICATION_ACCESS_TOKEN_CACHE')\n"
  },
  {
    "path": "apps/common/cache_data/application_api_key_cache.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： application_api_key_cache.py\n    @date：2024/7/25 11:30\n    @desc:\n\"\"\"\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\n\nfrom application.models import ApplicationApiKey\nfrom common.constants.cache_version import Cache_Version\nfrom common.utils.cache_util import get_cache\n\n\n@get_cache(cache_key=Cache_Version.APPLICATION_API_KEY.get_key_func(),\n           use_get_data=lambda secret_key, use_get_data: use_get_data,\n           version=Cache_Version.APPLICATION_API_KEY.get_version())\ndef get_application_api_key(secret_key, use_get_data):\n    application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=secret_key[7:]).first()\n    return {'allow_cross_domain': application_api_key.allow_cross_domain,\n            'cross_domain_list': application_api_key.cross_domain_list}\n\n\ndef del_application_api_key(secret_key):\n    cache.delete(Cache_Version.APPLICATION_API_KEY.get_key(secret_key=secret_key, use_get_data=True),\n                 version=Cache_Version.APPLICATION_API_KEY.get_version())\n"
  },
  {
    "path": "apps/common/chunk/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py\n    @date：2024/7/23 17:03\n    @desc:\n\"\"\"\nfrom common.chunk.impl.mark_chunk_handle import MarkChunkHandle\n\nhandles = [MarkChunkHandle()]\n\n\ndef text_to_chunk(text: str, chunk_size: int = 256):\n    chunk_list = [text]\n    for handle in handles:\n        chunk_list = handle.handle(chunk_list, chunk_size)\n    return chunk_list\n"
  },
  {
    "path": "apps/common/chunk/i_chunk_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： i_chunk_handle.py\n    @date：2024/7/23 16:51\n    @desc:\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import List\n\n\nclass IChunkHandle(ABC):\n    @abstractmethod\n    def handle(self, chunk_list: List[str], chunk_size: int = 256):\n        pass\n"
  },
  {
    "path": "apps/common/chunk/impl/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/chunk/impl/mark_chunk_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： mark_chunk_handle.py\n    @date：2024/7/23 16:52\n    @desc:\n\"\"\"\nimport re\nfrom typing import List\n\nfrom common.chunk.i_chunk_handle import IChunkHandle\n\nclass MarkChunkHandle(IChunkHandle):\n    def handle(self, chunk_list: List[str], chunk_size: int = 256):\n        split_chunk_pattern = r'.{1,%d}[。| |\\\\.|！|;|；|!|\\n]' % chunk_size\n        max_chunk_pattern = r'.{1,%d}' % chunk_size\n\n        result = []\n        for chunk in chunk_list:\n            chunk_result = re.findall(split_chunk_pattern, chunk, flags=re.DOTALL)\n            for c_r in chunk_result:\n                if len(c_r.strip()) > 0:\n                    result.append(c_r.strip())\n\n            other_chunk_list = re.split(split_chunk_pattern, chunk, flags=re.DOTALL)\n            for other_chunk in other_chunk_list:\n                if len(other_chunk) > 0:\n                    if len(other_chunk) < chunk_size:\n                        if len(other_chunk.strip()) > 0:\n                            result.append(other_chunk.strip())\n                    else:\n                        max_chunk_list = re.findall(max_chunk_pattern, other_chunk, flags=re.DOTALL)\n                        for m_c in max_chunk_list:\n                            if len(m_c.strip()) > 0:\n                                result.append(m_c.strip())\n\n        return result\n"
  },
  {
    "path": "apps/common/config/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/config/embedding_config.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： embedding_config.py\n    @date：2023/10/23 16:03\n    @desc:\n\"\"\"\n\nimport threading\nimport time\n\nfrom common.cache.mem_cache import MemCache\n\n_lock = threading.Lock()\nlocks = {}\n\n\nclass ModelManage:\n    cache = MemCache('model', {})\n    up_clear_time = time.time()\n\n    @staticmethod\n    def _get_lock(_id):\n        lock = locks.get(_id)\n        if lock is None:\n            with _lock:\n                lock = locks.get(_id)\n                if lock is None:\n                    lock = threading.Lock()\n                    locks[_id] = lock\n\n        return lock\n\n    @staticmethod\n    def get_model(_id, get_model):\n        model_instance = ModelManage.cache.get(_id)\n        if model_instance is None:\n            lock = ModelManage._get_lock(_id)\n            with lock:\n                model_instance = ModelManage.cache.get(_id)\n                if model_instance is None:\n                    model_instance = get_model(_id)\n                    ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8)\n        else:\n            if model_instance.is_cache_model():\n                ModelManage.cache.touch(_id, timeout=60 * 60 * 8)\n            else:\n                model_instance = get_model(_id)\n                ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8)\n        ModelManage.clear_timeout_cache()\n        return model_instance\n\n    @staticmethod\n    def clear_timeout_cache():\n        if time.time() - ModelManage.up_clear_time > 60 * 60:\n            threading.Thread(target=lambda: ModelManage.cache.clear_timeout_data()).start()\n            ModelManage.up_clear_time = time.time()\n\n    @staticmethod\n    def delete_key(_id):\n        if ModelManage.cache.has_key(_id):\n            ModelManage.cache.delete(_id)\n\n\nclass VectorStore:\n    from knowledge.vector.pg_vector import PGVector\n    from knowledge.vector.base_vector import BaseVectorStore\n    instance_map = {\n        'pg_vector': PGVector,\n    }\n    instance = None\n\n    @staticmethod\n    def get_embedding_vector() -> BaseVectorStore:\n        from knowledge.vector.pg_vector import PGVector\n        if VectorStore.instance is None:\n            from maxkb.const import CONFIG\n            vector_store_class = VectorStore.instance_map.get(CONFIG.get(\"VECTOR_STORE_NAME\"),\n                                                              PGVector)\n            VectorStore.instance = vector_store_class()\n        return VectorStore.instance\n"
  },
  {
    "path": "apps/common/config/tokenizer_manage_config.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： tokenizer_manage_config.py\n    @date：2024/4/28 10:17\n    @desc:\n\"\"\"\n\nimport os\n\nclass MKTokenizer:\n    def __init__(self, tokenizer):\n        self.tokenizer = tokenizer\n\n    def encode(self, text):\n        return self.tokenizer.encode(text).ids\n\n\nclass TokenizerManage:\n    tokenizer = None\n\n    @staticmethod\n    def get_tokenizer():\n        from tokenizers import Tokenizer\n        # 创建Tokenizer\n        model_path = os.path.join(\"/opt/maxkb-app\", \"model\", \"tokenizer\", \"models--bert-base-cased\")\n        with open(f\"{model_path}/refs/main\", encoding=\"utf-8\") as f: snapshot = f.read()\n        TokenizerManage.tokenizer = Tokenizer.from_file(f\"{model_path}/snapshots/{snapshot}/tokenizer.json\")\n        return MKTokenizer(TokenizerManage.tokenizer)\n"
  },
  {
    "path": "apps/common/constants/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/constants/authentication_type.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎虎\n    @file： authentication_type.py\n    @date：2023/11/14 20:03\n    @desc:\n\"\"\"\nfrom enum import Enum\n\n\nclass AuthenticationType(Enum):\n    # 系统用户\n    SYSTEM_USER = \"SYSTEM_USER\"\n    # 对话用户\n    CHAT_USER = \"CHAT_USER\"\n    # 对话匿名用户\n    CHAT_ANONYMOUS_USER = \"CHAT_ANONYMOUS_USER\"\n    # APIKEY\n    API_KEY = \"API_KEY\"\n"
  },
  {
    "path": "apps/common/constants/cache_version.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： cache_version.py\n    @date：2025/4/14 19:09\n    @desc:\n\"\"\"\nfrom enum import Enum\n\n\nclass Cache_Version(Enum):\n    # 令牌\n    TOKEN = \"TOKEN\", lambda token: token\n    # 工作空间列表\n    WORKSPACE_LIST = \"WORKSPACE:LIST\", lambda user_id: user_id\n    # 用户数据\n    USER = \"USER\", lambda user_id: user_id\n    # 当前用户所有的角色\n    ROLE_LIST = \"ROLE:LIST\", lambda user_id: user_id\n    # 当前用户所有权限\n    PERMISSION_LIST = \"PERMISSION:LIST\", lambda user_id: user_id\n    # 验证码\n    CAPTCHA = \"CAPTCHA\", lambda captcha: captcha\n    # 系统\n    SYSTEM = \"SYSTEM\", lambda key: key\n    # 应用对接三方应用的缓存\n    APPLICATION_THIRD_PARTY = \"APPLICATION:THIRD_PARTY\", lambda key: key\n    KNOWLEDGE_WORKFLOW_INTERRUPTED = \"KNOWLEDGE_WORKFLOW_INTERRUPTED\", lambda action_id: action_id\n    # 对话\n    CHAT = \"CHAT\", lambda key: key\n\n    CHAT_INFO = \"CHAT_INFO\", lambda key: key\n\n    CHAT_VARIABLE = \"CHAT_VARIABLE\", lambda key: key\n\n    # 应用API KEY\n    APPLICATION_API_KEY = \"APPLICATION_API_KEY\", lambda secret_key, use_get_data: secret_key\n\n    CHAT_USER_TOKEN = \"CHAT_USER_TOKEN\", lambda token: token\n\n    TOOL_WORKFLOW_EXECUTE = \"TOOL_WORKFLOW_EXECUTE\", lambda key: key\n\n    def get_version(self):\n        return self.value[0]\n\n    def get_key_func(self):\n        return self.value[1]\n\n    def get_key(self, **kwargs):\n        return self.value[1](**kwargs)\n"
  },
  {
    "path": "apps/common/constants/exception_code_constants.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎\n    @file： exception_code_constants.py\n    @date：2023/9/4 14:09\n    @desc: 异常常量类\n\"\"\"\nfrom enum import Enum\n\nfrom common.exception.app_exception import AppApiException\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass ExceptionCodeConstantsValue:\n    def __init__(self, code, message):\n        self.code = code\n        self.message = message\n\n    def get_message(self):\n        return self.message\n\n    def get_code(self):\n        return self.code\n\n    def to_app_api_exception(self):\n        return AppApiException(code=self.code, message=self.message)\n\n\nclass ExceptionCodeConstants(Enum):\n    INCORRECT_USERNAME_AND_PASSWORD = ExceptionCodeConstantsValue(1000, _('The username or password is incorrect'))\n    NOT_AUTHENTICATION = ExceptionCodeConstantsValue(1001, _('Please log in first and bring the user Token'))\n    EMAIL_SEND_ERROR = ExceptionCodeConstantsValue(1002, _('Email sending failed'))\n    EMAIL_FORMAT_ERROR = ExceptionCodeConstantsValue(1003, _('Email format error'))\n    EMAIL_IS_EXIST = ExceptionCodeConstantsValue(1004, _('The email has been registered, please log in directly'))\n    EMAIL_IS_NOT_EXIST = ExceptionCodeConstantsValue(1005, _('The email is not registered, please register first'))\n    CODE_ERROR = ExceptionCodeConstantsValue(1005,\n                                             _('The verification code is incorrect or the verification code has expired'))\n    USERNAME_IS_EXIST = ExceptionCodeConstantsValue(1006, _('The username has been registered, please log in directly'))\n    USERNAME_ERROR = ExceptionCodeConstantsValue(1006,\n                                                 _('The username cannot be empty and must be between 6 and 20 characters long.'))\n    PASSWORD_NOT_EQ_RE_PASSWORD = ExceptionCodeConstantsValue(1007,\n                                                              _('Password and confirmation password are inconsistent'))\n    NICKNAME_IS_EXIST = ExceptionCodeConstantsValue(1008, _('The nickname is already registered'))\n    SEND_EMAIL_ERROR = ExceptionCodeConstantsValue(1009, _(\"Email sending failed\"))\n"
  },
  {
    "path": "apps/common/constants/permission_constants.py",
    "content": "\"\"\"\n    @project: qabot\n    @Author：虎虎\n    @file： permission_constants.py\n    @date：2023/9/13 18:23\n    @desc: 权限,角色 常量\n\"\"\"\nfrom enum import Enum\nfrom functools import reduce\nfrom typing import List\n\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\n\nfrom maxkb import settings\n\n\nclass Group(Enum):\n    \"\"\"\n    权限组 一个组一般对应前端一个菜单\n    \"\"\"\n\n    USER = \"USER_MANAGEMENT\"\n    # 应用\n    APPLICATION = \"APPLICATION\"\n    # 应用概览\n    APPLICATION_OVERVIEW = \"APPLICATION_OVERVIEW\"\n    # 应用接入\n    APPLICATION_ACCESS = \"APPLICATION_ACCESS\"\n    # 应用 对话用户\n    APPLICATION_CHAT_USER = \"APPLICATION_CHAT_USER\"\n    # 知识库 对话用户\n    KNOWLEDGE_CHAT_USER = \"KNOWLEDGE_CHAT_USER\"\n    # 应用对话日志\n    APPLICATION_CHAT_LOG = \"APPLICATION_CHAT_LOG\"\n\n    KNOWLEDGE = \"KNOWLEDGE\"\n    SYSTEM_KNOWLEDGE = \"SYSTEM_KNOWLEDGE\"\n    SYSTEM_RES_KNOWLEDGE = \"SYSTEM_RESOURCE_KNOWLEDGE\"\n    KNOWLEDGE_HIT_TEST = \"KNOWLEDGE_HIT_TEST\"\n    KNOWLEDGE_DOCUMENT = \"KNOWLEDGE_DOCUMENT\"\n    KNOWLEDGE_WORKFLOW = \"KNOWLEDGE_WORKFLOW\"\n    KNOWLEDGE_TAG = \"KNOWLEDGE_TAG\"\n    SYSTEM_KNOWLEDGE_DOCUMENT = \"SYSTEM_KNOWLEDGE_DOCUMENT\"\n    SYSTEM_KNOWLEDGE_WORKFLOW = \"SYSTEM_KNOWLEDGE_WORKFLOW\"\n    SYSTEM_RES_KNOWLEDGE_DOCUMENT = \"SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT\"\n    SYSTEM_RES_KNOWLEDGE_WORKFLOW = \"SYSTEM_RESOURCE_KNOWLEDGE_WORKFLOW\"\n    SYSTEM_RES_KNOWLEDGE_TAG = \"SYSTEM_RES_KNOWLEDGE_TAG\"\n    SYSTEM_KNOWLEDGE_TAG = \"SYSTEM_KNOWLEDGE_TAG\"\n\n    KNOWLEDGE_PROBLEM = \"KNOWLEDGE_PROBLEM\"\n    SYSTEM_KNOWLEDGE_PROBLEM = \"SYSTEM_KNOWLEDGE_PROBLEM\"\n    SYSTEM_RES_KNOWLEDGE_PROBLEM = \"SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM\"\n\n    SYSTEM_KNOWLEDGE_HIT_TEST = \"SYSTEM_KNOWLEDGE_HIT_TEST\"\n    SYSTEM_RES_KNOWLEDGE_HIT_TEST = \"SYSTEM_RESOURCE_KNOWLEDGE_HIT_TEST\"\n    SYSTEM_KNOWLEDGE_CHAT_USER = \"SYSTEM_KNOWLEDGE_CHAT_USER\"\n    SYSTEM_RES_KNOWLEDGE_CHAT_USER = \"SYSTEM_RESOURCE_KNOWLEDGE_CHAT_USER\"\n\n    MODEL = \"MODEL\"\n    SYSTEM_MODEL = \"SYSTEM_MODEL\"\n    SYSTEM_RES_MODEL = \"SYSTEM_RESOURCE_MODEL\"\n    SYSTEM_RES_APPLICATION = \"SYSTEM_RESOURCE_APPLICATION\"\n    SYSTEM_RES_APPLICATION_OVERVIEW = \"SYSTEM_RESOURCE_APPLICATION_OVERVIEW\"\n    SYSTEM_RES_APPLICATION_ACCESS = \"SYSTEM_RESOURCE_APPLICATION_ACCESS\"\n    SYSTEM_RES_APPLICATION_CHAT_USER = \"SYSTEM_RESOURCE_APPLICATION_CHAT_USER\"\n    SYSTEM_RES_APPLICATION_CHAT_LOG = \"SYSTEM_RESOURCE_APPLICATION_CHAT_LOG\"\n\n    TOOL = \"TOOL\"\n    SYSTEM_TOOL = \"SYSTEM_TOOL\"\n    SYSTEM_RES_TOOL = \"SYSTEM_RESOURCE_TOOL\"\n\n    TRIGGER = \"TRIGGER\"\n\n    APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION = \"APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION\"\n    KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION = \"KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION\"\n    TOOL_WORKSPACE_USER_RESOURCE_PERMISSION = \"TOOL_WORKSPACE_USER_RESOURCE_PERMISSION\"\n    MODEL_WORKSPACE_USER_RESOURCE_PERMISSION = \"MODEL_WORKSPACE_USER_RESOURCE_PERMISSION\"\n\n    EMAIL_SETTING = \"EMAIL_SETTING\"\n    ROLE = \"ROLE\"\n    WORKSPACE_ROLE = \"WORKSPACE_ROLE\"\n    WORKSPACE = \"WORKSPACE\"\n    WORKSPACE_WORKSPACE = \"WORKSPACE_WORKSPACE\"\n\n    DISPLAY_SETTINGS = \"DISPLAY_SETTINGS\"\n    LOGIN_AUTH = \"LOGIN_AUTH\"\n    SYSTEM_API_KEY = \"SYSTEM_API_KEY\"\n    APPEARANCE_SETTINGS = \"APPEARANCE_SETTINGS\"\n    CHAT_USER = \"CHAT_USER\"\n    WORKSPACE_CHAT_USER = \"WORKSPACE_CHAT_USER\"\n    USER_GROUP = \"USER_GROUP\"\n    WORKSPACE_USER_GROUP = \"WORKSPACE_USER_GROUP\"\n    CHAT_USER_AUTH = \"CHAT_USER_AUTH\"\n    OTHER = \"OTHER\"\n    OVERVIEW = \"OVERVIEW\"\n    OPERATION_LOG = \"OPERATION_LOG\"\n\n    APPLICATION_FOLDER = \"APPLICATION_FOLDER\"\n    KNOWLEDGE_FOLDER = \"KNOWLEDGE_FOLDER\"\n    TOOL_FOLDER = \"TOOL_FOLDER\"\n\n\nclass SystemGroup(Enum):\n    \"\"\"\n    一级菜单\n    \"\"\"\n    USER_MANAGEMENT = \"USER_MANAGEMENT\"\n    ROLE = \"ROLE\"\n    WORKSPACE = \"WORKSPACE\"\n    # RESOURCE = \"RESOURCE\"\n    RESOURCE_APPLICATION = \"RESOURCE_APPLICATION\"\n    RESOURCE_KNOWLEDGE = \"RESOURCE_KNOWLEDGE\"\n    RESOURCE_TOOL = \"RESOURCE_TOOL\"\n    RESOURCE_MODEL = \"RESOURCE_MODEL\"\n    RESOURCE_PERMISSION = \"RESOURCE_PERMISSION\"\n    SHARED_KNOWLEDGE = \"SHARED_KNOWLEDGE\"\n    SHARED_MODEL = \"SHARED_MODEL\"\n    SHARED_TOOL = \"SHARED_TOOL\"\n    CHAT_USER = \"CHAT_USER\"\n    SYSTEM_SETTING = \"SYSTEM_SETTING\"\n    OPERATION_LOG = \"OPERATION_LOG\"\n    OTHER = \"OTHER\"\n\n\nclass WorkspaceGroup(Enum):\n    SYSTEM_MANAGEMENT = \"SYSTEM_MANAGEMENT\"\n    APPLICATION = \"APPLICATION\"\n    KNOWLEDGE = \"KNOWLEDGE\"\n    MODEL = \"MODEL\"\n    TOOL = \"TOOL\"\n    TRIGGER = \"TRIGGER\"\n    RESOURCE_PERMISSION = \"RESOURCE_PERMISSION\"\n    OTHER = \"OTHER\"\n\n\nclass UserGroup(Enum):\n    APPLICATION = \"APPLICATION\"\n    KNOWLEDGE = \"KNOWLEDGE\"\n    MODEL = \"MODEL\"\n    TOOL = \"TOOL\"\n    OTHER = \"OTHER\"\n\n\nclass Operate(Enum):\n    \"\"\"\n     一个权限组的操作权限\n    \"\"\"\n    SELF = \"\"\n    READ = 'READ'\n    EDIT = \"READ+EDIT\"\n    CREATE = \"READ+CREATE\"\n    DELETE = \"READ+DELETE\"\n    \"\"\"\n    使用权限\n    \"\"\"\n    USE = \"USE\"\n    IMPORT = \"READ+IMPORT\"\n    EXPORT = \"READ+EXPORT\"  # 导入导出\n    SYNC = \"READ+SYNC\"  # 同步\n    GENERATE = \"READ+GENERATE\"  # 生成\n    ADD_MEMBER = \"READ+ADD_MEMBER\"  # 添加成员\n    REMOVE_MEMBER = \"READ+REMOVE_MEMBER\"  # 添加成员\n    VECTOR = \"READ+VECTOR\"  # 向量化\n    MIGRATE = \"READ+MIGRATE\"  # 迁移\n    RELATE = \"READ+RELATE\"  # 关联\n    USER_GROUP = \"READ+USER_GROUP\"  # 用户组\n    ANNOTATION = \"READ+ANNOTATION\"  # 标注\n    CLEAR_POLICY = \"READ+CLEAR_POLICY\"\n    EMBED = \"READ+EMBED\"  # 嵌入\n    ACCESS = \"READ+ACCESS\"  # 访问限制\n    DISPLAY = \"READ+DISPLAY\"  # 显示设置\n    API_KEY = \"READ+API_KEY\"  # API_KEY\n    PUBLIC_ACCESS = \"READ+PUBLIC_ACCESS\"  # 公共访问链接\n    Q_WEIXIN = \"READ+Q_WEIXIN\"  # 企业微信\n    FEISHU = \"READ+FEISHU\"  # 飞书\n    DD = \"READ+DD\"  # 钉钉\n    WEIXIN_PUBLIC_ACCOUNT = \"READ+WEIXIN_PUBLIC_ACCOUNT\"  # 微信公众号\n    SLACK = \"READ+SLACK\"  # SLACK\n    ADD_KNOWLEDGE = \"READ+ADD_KNOWLEDGE\"  # 添加到知识库\n    TO_CHAT = \"READ+TO_CHAT\"  # 去对话\n    SETTING = \"READ+SETTING\"  # 管理\n    DOWNLOAD = \"READ+DOWNLOAD\"  # 下载\n    AUTH = \"READ+AUTH\"  # 资源授权\n    TAG = \"READ+TAG\"  # 标签设置\n    REPLACE = \"READ+REPLACE\"  # 标签设置\n    UPDATE = \"READ+UPDATE\"  # 更新license\n    RELATE_VIEW = \"READ+RELATE_VIEW\"\n    RECORD = \"READ+RECORD\"\n    TRIGGER_READ = \"READ+TRIGGER_READ\"\n    TRIGGER_EDIT = \"READ+TRIGGER_EDIT\"\n    TRIGGER_CREATE = \"READ+TRIGGER_CREATE\"\n    TRIGGER_DELETE = \"READ+TRIGGER_DELETE\"\n\n\nclass RoleGroup(Enum):\n    # 系统用户\n    SYSTEM_USER = \"SYSTEM_USER\"\n    # 对话用户\n    CHAT_USER = \"CHAT_USER\"\n\n\nclass ResourcePermissionRole(models.TextChoices):\n    \"\"\"\n    资源权限根据角色\n    \"\"\"\n    ROLE = \"ROLE\"\n\n    def __eq__(self, other):\n        return str(self) == str(other)\n\n\nclass ResourcePermission(models.TextChoices):\n    \"\"\"\n    资源权限组\n    \"\"\"\n    # 查看\n    VIEW = \"VIEW\"\n    # 管理\n    MANAGE = \"MANAGE\"\n\n    def __eq__(self, other):\n        return str(self) == str(other)\n\n\nclass Resource(models.TextChoices):\n    KNOWLEDGE = Group.KNOWLEDGE.value\n    KNOWLEDGE_FOLDER = Group.KNOWLEDGE_FOLDER.value\n    APPLICATION = Group.APPLICATION.value\n    APPLICATION_FOLDER = Group.APPLICATION_FOLDER.value\n    TOOL = Group.TOOL.value\n    TOOL_FOLDER = Group.TOOL_FOLDER.value\n    MODEL = Group.MODEL.value\n\n    def __eq__(self, other):\n        return str(self) == str(other)\n\n\nclass ResourcePermissionGroup:\n    def __init__(self, resource: Resource, permission: ResourcePermission):\n        self.permission = permission\n        self.resource = resource\n\n    def __eq__(self, other):\n        return str(self.permission) == str(other.permission) and str(self.resource) == str(other.resource)\n\n\nclass ResourcePermissionConst:\n    KNOWLEDGE_MANGE = ResourcePermissionGroup(Resource.KNOWLEDGE, ResourcePermission.MANAGE)\n    KNOWLEDGE_FOLDER_MANGE = ResourcePermissionGroup(Resource.KNOWLEDGE_FOLDER, ResourcePermission.MANAGE)\n    KNOWLEDGE_FOLDER_VIEW = ResourcePermissionGroup(Resource.KNOWLEDGE_FOLDER, ResourcePermission.VIEW)\n    KNOWLEDGE_VIEW = ResourcePermissionGroup(Resource.KNOWLEDGE, ResourcePermission.VIEW)\n    APPLICATION_MANGE = ResourcePermissionGroup(Resource.APPLICATION, ResourcePermission.MANAGE)\n    APPLICATION_FOLDER_MANGE = ResourcePermissionGroup(Resource.APPLICATION_FOLDER, ResourcePermission.MANAGE)\n    APPLICATION_FOLDER_VIEW = ResourcePermissionGroup(Resource.APPLICATION_FOLDER, ResourcePermission.VIEW)\n    APPLICATION_VIEW = ResourcePermissionGroup(Resource.APPLICATION, ResourcePermission.VIEW)\n    TOOL_MANGE = ResourcePermissionGroup(Resource.TOOL, ResourcePermission.MANAGE)\n    TOOL_FOLDER_MANGE = ResourcePermissionGroup(Resource.TOOL_FOLDER, ResourcePermission.MANAGE)\n    TOOL_FOLDER_VIEW = ResourcePermissionGroup(Resource.TOOL_FOLDER, ResourcePermission.VIEW)\n    TOOL_VIEW = ResourcePermissionGroup(Resource.TOOL, ResourcePermission.VIEW)\n    MODEL_MANGE = ResourcePermissionGroup(Resource.MODEL, ResourcePermission.MANAGE)\n    MODEL_VIEW = ResourcePermissionGroup(Resource.MODEL, ResourcePermission.VIEW)\n\n\nclass ResourceAuthType(models.TextChoices):\n    \"\"\"\n    资源授权类型\n    \"\"\"\n    \"当授权类型是Role时候\"\n    ROLE = \"ROLE\"\n\n    \"\"\"资源权限组\"\"\"\n    RESOURCE_PERMISSION_GROUP = \"RESOURCE_PERMISSION_GROUP\"\n\n\nclass Role:\n    def __init__(self, name: str, decs: str, group: RoleGroup, resource_path=None):\n        self.name = name\n        self.decs = decs\n        self.group = group\n        self.resource_path = resource_path\n\n    def __str__(self):\n        return self.name + (\n            (\":\" + self.resource_path) if self.resource_path is not None else '')\n\n    def __eq__(self, other):\n        return str(self) == str(other)\n\n    def get_workspace_role(self):\n        return lambda r, kwargs: Role(self.name, self.decs, self.group,\n                                      resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}\")\n\n\nclass RoleConstants(Enum):\n    ADMIN = Role(\"ADMIN\", '超级管理员', RoleGroup.SYSTEM_USER)\n    WORKSPACE_MANAGE = Role(\"WORKSPACE_MANAGE\", '工作空间管理员', RoleGroup.SYSTEM_USER)\n    USER = Role(\"USER\", '普通用户', RoleGroup.SYSTEM_USER)\n    CHAT_ANONYMOUS_USER = Role(\"CHAT_ANONYMOUS_USER\", \"对话匿名用户\", RoleGroup.CHAT_USER)\n    CHAT_USER = Role(\"CHAT_USER\", \"对话用户\", RoleGroup.CHAT_USER)\n\n    EXTENDS_ADMIN = Role(\"EXTENDS_ADMIN\", '继承超级管理员', RoleGroup.SYSTEM_USER)\n    EXTENDS_WORKSPACE_MANAGE = Role(\"EXTENDS_WORKSPACE_MANAGE\", \"继承工作空间管理员\", RoleGroup.CHAT_USER)\n    EXTENDS_USER = Role(\"EXTENDS_USER\", \"继承普通用户\", RoleGroup.CHAT_USER)\n\n    def get_workspace_role(self):\n        return lambda r, kwargs: Role(name=self.value.name,\n                                      decs=self.value.decs,\n                                      group=self.value.group,\n                                      resource_path=\n                                      f\"/WORKSPACE/{kwargs.get('workspace_id')}\")\n\n\nPermission_Label = {\n    SystemGroup.SYSTEM_SETTING.value: _(\"System Setting\"),\n    SystemGroup.USER_MANAGEMENT.value: _(\"User Management\"),\n    SystemGroup.ROLE.value: _(\"Role\"),\n    SystemGroup.WORKSPACE.value: _(\"Workspace\"),\n    SystemGroup.RESOURCE_APPLICATION.value: _(\"Resource Application\"),\n    SystemGroup.RESOURCE_KNOWLEDGE.value: _(\"Resource Knowledge\"),\n    SystemGroup.RESOURCE_TOOL.value: _(\"Resource Tool\"),\n    SystemGroup.RESOURCE_MODEL.value: _(\"Resource Model\"),\n    SystemGroup.RESOURCE_PERMISSION.value: _(\"Resource Permission\"),\n    SystemGroup.SHARED_KNOWLEDGE.value: _(\"Shared Knowledge\"),\n    SystemGroup.SHARED_MODEL.value: _(\"Shared Model\"),\n    SystemGroup.SHARED_TOOL.value: _(\"Shared Tool\"),\n    SystemGroup.OPERATION_LOG.value: _(\"Operation Log\"),\n    SystemGroup.OTHER.value: _(\"Other\"),\n    WorkspaceGroup.SYSTEM_MANAGEMENT.value: _(\"System Management\"),\n    WorkspaceGroup.APPLICATION.value: _(\"Application\"),\n    WorkspaceGroup.KNOWLEDGE.value: _(\"Knowledge\"),\n    WorkspaceGroup.MODEL.value: _(\"Model\"),\n    WorkspaceGroup.TOOL.value: _(\"Tool\"),\n    WorkspaceGroup.TRIGGER.value: _(\"Trigger\"),\n    WorkspaceGroup.OTHER.value: _(\"Other\"),\n    Operate.READ.value: _(\"Read\"),\n    Operate.EDIT.value: _(\"Edit\"),\n    Operate.CREATE.value: _(\"Create\"),\n    Operate.DELETE.value: _(\"Delete\"),\n    Group.EMAIL_SETTING.value: _(\"Email Setting\"),\n    Group.APPLICATION.value: _(\"Application\"),\n    Group.KNOWLEDGE.value: _(\"Knowledge\"),\n    Group.KNOWLEDGE_DOCUMENT.value: _(\"Document\"),\n    Group.KNOWLEDGE_WORKFLOW.value: _(\"Workflow\"),\n    Group.KNOWLEDGE_TAG.value: _(\"Tag\"),\n    Group.KNOWLEDGE_PROBLEM.value: _(\"Problem\"),\n    Group.KNOWLEDGE_HIT_TEST.value: _(\"Hit-Test\"),\n    Operate.IMPORT.value: _(\"Import\"),\n    Operate.EXPORT.value: _(\"Export\"),\n    Operate.SYNC.value: _(\"Sync\"),\n    Operate.GENERATE.value: _(\"Generate\"),\n    Operate.ADD_MEMBER.value: _(\"Add Member\"),\n    Operate.REMOVE_MEMBER.value: _(\"Remove Member\"),\n    Operate.VECTOR.value: _(\"Vector\"),\n    Operate.MIGRATE.value: _(\"Migrate\"),\n    Operate.RELATE.value: _(\"Relate\"),\n    Operate.ANNOTATION.value: _(\"Annotation\"),\n    Operate.CLEAR_POLICY.value: _(\"Clear Policy\"),\n    Operate.DOWNLOAD.value: _('Download Original Document'),\n    Operate.EMBED.value: _('Embed third party'),\n    Operate.ACCESS.value: _('Access restrictions'),\n    Operate.DISPLAY.value: _('Display Settings'),\n    Operate.API_KEY.value: _('API KEY'),\n    Operate.PUBLIC_ACCESS.value: _('Public access link'),\n    Operate.Q_WEIXIN.value: _('Enterprise WeiXin'),\n    Operate.FEISHU.value: _('Feishu'),\n    Operate.DD.value: _('Dingding'),\n    Operate.WEIXIN_PUBLIC_ACCOUNT.value: _('Weixin Public Account'),\n    Operate.ADD_KNOWLEDGE.value: _('Add to Knowledge Base'),\n    Operate.AUTH.value: _('resource authorization'),\n    Operate.TAG.value: _('Tag Setting'),\n    Operate.REPLACE.value: _('Replace Original Document'),\n    Operate.RELATE_VIEW.value: _('View related resources'),\n    Operate.TRIGGER_READ.value: _('Read Trigger'),\n    Operate.TRIGGER_CREATE.value: _('Create Trigger'),\n    Operate.TRIGGER_EDIT.value: _('Edit Trigger'),\n    Operate.TRIGGER_DELETE.value: _('Delete Trigger'),\n    Operate.RECORD.value: _('Read execute record'),\n\n    Group.APPLICATION_OVERVIEW.value: _('Overview'),\n    Group.APPLICATION_ACCESS.value: _('Application Access'),\n    Group.APPLICATION_CHAT_USER.value: _('Dialogue users'),\n    Group.APPLICATION_CHAT_LOG.value: _('Conversation log'),\n    Group.KNOWLEDGE_CHAT_USER.value: _('Dialogue users'),\n\n    Group.LOGIN_AUTH.value: _(\"Login Auth\"),\n    Group.DISPLAY_SETTINGS.value: _(\"Display Settings\"),\n    Group.SYSTEM_API_KEY.value: _(\"System API Key\"),\n    Group.APPEARANCE_SETTINGS.value: _(\"Appearance Settings\"),\n    Group.CHAT_USER.value: _(\"Chat User\"),\n    Group.USER_GROUP.value: _(\"User Group\"),\n    Group.CHAT_USER_AUTH.value: _(\"Chat User Auth\"),\n    Group.OVERVIEW.value: _(\"Overview\"),\n    Group.SYSTEM_TOOL.value: _(\"Tool\"),\n    Group.SYSTEM_MODEL.value: _(\"Model\"),\n    Group.SYSTEM_KNOWLEDGE.value: _(\"Knowledge\"),\n    Group.SYSTEM_KNOWLEDGE_DOCUMENT.value: _(\"Document\"),\n    Group.SYSTEM_KNOWLEDGE_WORKFLOW.value: _(\"Workflow\"),\n    Group.SYSTEM_KNOWLEDGE_TAG.value: _(\"Tag\"),\n    Group.SYSTEM_KNOWLEDGE_PROBLEM.value: _(\"Problem\"),\n    Group.SYSTEM_KNOWLEDGE_HIT_TEST.value: _(\"Hit-Test\"),\n    Group.SYSTEM_KNOWLEDGE_CHAT_USER.value: _(\"Dialogue users\"),\n    Group.SYSTEM_RES_TOOL.value: _(\"Tool\"),\n    Group.SYSTEM_RES_MODEL.value: _(\"Model\"),\n    Group.SYSTEM_RES_KNOWLEDGE.value: _(\"Knowledge\"),\n    Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT.value: _(\"Document\"),\n    Group.SYSTEM_RES_KNOWLEDGE_WORKFLOW.value: _(\"Workflow\"),\n    Group.SYSTEM_RES_KNOWLEDGE_TAG.value: _(\"Tag\"),\n    Group.SYSTEM_RES_KNOWLEDGE_PROBLEM.value: _(\"Problem\"),\n    Group.SYSTEM_RES_KNOWLEDGE_HIT_TEST.value: _(\"Hit-Test\"),\n    Group.SYSTEM_RES_KNOWLEDGE_CHAT_USER.value: _(\"Dialogue users\"),\n    Group.WORKSPACE_USER_GROUP.value: _(\"User Group\"),\n    Group.WORKSPACE_CHAT_USER.value: _(\"Chat User\"),\n    Group.WORKSPACE_WORKSPACE.value: _(\"Workspace\"),\n    Group.WORKSPACE_ROLE.value: _(\"Role\"),\n    Group.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION.value: _(\"Application\"),\n    Group.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION.value: _(\"Knowledge\"),\n    Group.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION.value: _(\"Model\"),\n    Group.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION.value: _(\"Tool\"),\n    Group.SYSTEM_RES_APPLICATION.value: _(\"Application\"),\n    Group.SYSTEM_RES_APPLICATION_OVERVIEW.value: _(\"Overview\"),\n    Group.SYSTEM_RES_APPLICATION_ACCESS.value: _(\"Application Access\"),\n    Group.SYSTEM_RES_APPLICATION_CHAT_USER.value: _(\"Dialogue users\"),\n    Group.SYSTEM_RES_APPLICATION_CHAT_LOG.value: _(\"Conversation log\"),\n    Group.APPLICATION_FOLDER.value: _(\"Folder\"),\n    Group.KNOWLEDGE_FOLDER.value: _(\"Folder\"),\n    Group.TOOL_FOLDER.value: _(\"Folder\"),\n    # SystemGroup.RESOURCE.value: _(\"Resource\"),\n}\n\n\nclass Permission:\n    \"\"\"\n    权限信息\n    \"\"\"\n\n    def __init__(self, group: Group, operate: Operate, resource_path=None, role_list=None,\n                 resource_permission_group_list=None, parent_group=None, label=None, is_ee=True):\n        if role_list is None:\n            role_list = []\n        if resource_permission_group_list is None:\n            resource_permission_group_list = []\n        self.group = group\n        self.operate = operate\n        self.resource_path = resource_path\n        # 用于获取角色与权限的关系,只适用于没有权限管理的\n        self.role_list = role_list\n        # 用于资源权限权限分组\n        self.resource_permission_group_list = resource_permission_group_list\n        self.parent_group = parent_group  # 新增字段：父级组\n        self.label = label\n        self.is_ee = is_ee  # 是否是企业版权限\n\n    @staticmethod\n    def new_instance(permission_str: str):\n        permission_split = permission_str.split(\":\")\n        group = Group[permission_split[0]]\n        operate = Operate[permission_split[1]]\n        if len(permission_split) > 2:\n            dynamic_tag = \":\".join(permission_split[2:])\n            return Permission(group, operate, dynamic_tag)\n        return Permission(group, operate)\n\n    def __str__(self):\n\n        return self.group.value + (\n            (\":\" + self.operate.value) if self.operate.value else '') + (\n            (\":\" + self.resource_path) if self.resource_path is not None else '')\n\n    def __eq__(self, other):\n        return str(self) == str(other)\n\n\nclass PermissionConstants(Enum):\n    \"\"\"\n     权限枚举\n    \"\"\"\n    KNOWLEDGE = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.SELF, role_list=[RoleConstants.ADMIN, RoleConstants.USER]\n    )\n    APPLICATION = Permission(\n        group=Group.APPLICATION, operate=Operate.SELF, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n    )\n    MODEL = Permission(\n        group=Group.MODEL, operate=Operate.SELF, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n    )\n    TOOL = Permission(\n        group=Group.TOOL, operate=Operate.SELF, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n    )\n    USER_READ = Permission(\n        group=Group.USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[SystemGroup.USER_MANAGEMENT]\n    )\n\n    USER_CREATE = Permission(\n        group=Group.USER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.USER_MANAGEMENT]\n    )\n\n    USER_EDIT = Permission(\n        group=Group.USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.USER_MANAGEMENT]\n    )\n\n    USER_DELETE = Permission(\n        group=Group.USER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.USER_MANAGEMENT]\n    )\n\n    MODEL_READ = Permission(\n        group=Group.MODEL, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL],\n        resource_permission_group_list=[ResourcePermissionConst.MODEL_VIEW]\n    )\n\n    MODEL_CREATE = Permission(\n        group=Group.MODEL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL],\n        resource_permission_group_list=[ResourcePermissionConst.MODEL_MANGE]\n    )\n\n    MODEL_EDIT = Permission(\n        group=Group.MODEL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL],\n        resource_permission_group_list=[ResourcePermissionConst.MODEL_MANGE]\n    )\n    MODEL_DELETE = Permission(\n        group=Group.MODEL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL],\n        resource_permission_group_list=[ResourcePermissionConst.MODEL_MANGE]\n    )\n    MODEL_RESOURCE_AUTHORIZATION = Permission(\n        group=Group.MODEL, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL],\n        resource_permission_group_list=[ResourcePermissionConst.MODEL_MANGE]\n    )\n    MODEL_RELATE_RESOURCE_VIEW = Permission(\n        group=Group.MODEL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.MODEL, UserGroup.MODEL],\n        resource_permission_group_list=[ResourcePermissionConst.MODEL_MANGE]\n    )\n    # trigger\n    TRIGGER_READ = Permission(\n        group=Group.TRIGGER, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.TRIGGER],\n    )\n    TRIGGER_CREATE = Permission(\n        group=Group.TRIGGER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.TRIGGER],\n    )\n    TRIGGER_EDIT = Permission(\n        group=Group.TRIGGER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.TRIGGER],\n    )\n    TRIGGER_DELETE = Permission(\n        group=Group.TRIGGER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.TRIGGER],\n    )\n    TRIGGER_RECORD = Permission(\n        group=Group.TRIGGER, operate=Operate.RECORD, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.TRIGGER],\n    )\n    TOOL_READ = Permission(\n        group=Group.TOOL, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW]\n    )\n\n    TOOL_CREATE = Permission(\n        group=Group.TOOL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n\n    TOOL_EDIT = Permission(\n        group=Group.TOOL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n\n    TOOL_DELETE = Permission(\n        group=Group.TOOL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    TOOL_IMPORT = Permission(\n        group=Group.TOOL, operate=Operate.IMPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    TOOL_EXPORT = Permission(\n        group=Group.TOOL, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    TOOL_RESOURCE_AUTHORIZATION = Permission(\n        group=Group.TOOL, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    TOOL_RELATE_RESOURCE_VIEW = Permission(\n        group=Group.TOOL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    TOOL_EXECUTE_RECORD = Permission(\n        group=Group.TOOL, operate=Operate.RECORD, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    # source point trigger\n    TOOL_TRIGGER_READ = Permission(\n        group=Group.TOOL, operate=Operate.TRIGGER_READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    TOOL_TRIGGER_CREATE = Permission(\n        group=Group.TOOL, operate=Operate.TRIGGER_CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW]\n    )\n    TOOL_TRIGGER_EDIT = Permission(\n        group=Group.TOOL, operate=Operate.TRIGGER_EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW]\n    )\n    TOOL_TRIGGER_DELETE = Permission(\n        group=Group.TOOL, operate=Operate.TRIGGER_DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW]\n    )\n    TOOL_FOLDER_READ = Permission(\n        group=Group.TOOL_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_VIEW]\n    )\n    TOOL_FOLDER_CREATE = Permission(\n        group=Group.TOOL_FOLDER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    TOOL_FOLDER_EDIT = Permission(\n        group=Group.TOOL_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    TOOL_FOLDER_DELETE = Permission(\n        group=Group.TOOL_FOLDER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    TOOL_FOLDER_AUTH = Permission(\n        group=Group.TOOL_FOLDER, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.TOOL, UserGroup.TOOL],\n        resource_permission_group_list=[ResourcePermissionConst.TOOL_MANGE]\n    )\n    KNOWLEDGE_READ = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_CREATE = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_EDIT = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DELETE = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_SYNC = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_EXPORT = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_VECTOR = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_GENERATE = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_RESOURCE_AUTHORIZATION = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_RELATE_RESOURCE_VIEW = Permission(\n        group=Group.KNOWLEDGE, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE]\n    )\n    KNOWLEDGE_FOLDER_READ = Permission(\n        group=Group.KNOWLEDGE_FOLDER, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW],\n        parent_group=[UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_FOLDER_CREATE = Permission(\n        group=Group.KNOWLEDGE_FOLDER, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_FOLDER_EDIT = Permission(\n        group=Group.KNOWLEDGE_FOLDER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_FOLDER_DELETE = Permission(\n        group=Group.KNOWLEDGE_FOLDER, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_FOLDER_AUTH = Permission(\n        group=Group.KNOWLEDGE_FOLDER, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_WORKFLOW_READ = Permission(\n        group=Group.KNOWLEDGE_WORKFLOW, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_WORKFLOW_EDIT = Permission(\n        group=Group.KNOWLEDGE_WORKFLOW, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_WORKFLOW_EXPORT = Permission(\n        group=Group.KNOWLEDGE_WORKFLOW, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_READ = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.READ,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_CREATE = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.CREATE,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_EDIT = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_DELETE = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_SYNC = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_EXPORT = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.EXPORT,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.DOWNLOAD,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_GENERATE = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.GENERATE,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_VECTOR = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.VECTOR,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_MIGRATE = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.MIGRATE,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_TAG = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.TAG,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_DOCUMENT_REPLACE = Permission(\n        group=Group.KNOWLEDGE_DOCUMENT, operate=Operate.REPLACE,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_HIT_TEST = Permission(\n        group=Group.KNOWLEDGE_HIT_TEST, operate=Operate.READ,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_PROBLEM_READ = Permission(\n        group=Group.KNOWLEDGE_PROBLEM, operate=Operate.READ,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_PROBLEM_CREATE = Permission(\n        group=Group.KNOWLEDGE_PROBLEM, operate=Operate.CREATE,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_PROBLEM_EDIT = Permission(\n        group=Group.KNOWLEDGE_PROBLEM, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_PROBLEM_DELETE = Permission(\n        group=Group.KNOWLEDGE_PROBLEM, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_PROBLEM_RELATE = Permission(\n        group=Group.KNOWLEDGE_PROBLEM, operate=Operate.RELATE,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_TAG_READ = Permission(\n        group=Group.KNOWLEDGE_TAG, operate=Operate.READ,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_TAG_CREATE = Permission(\n        group=Group.KNOWLEDGE_TAG, operate=Operate.CREATE,\n        role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_TAG_EDIT = Permission(\n        group=Group.KNOWLEDGE_TAG, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    KNOWLEDGE_TAG_DELETE = Permission(\n        group=Group.KNOWLEDGE_TAG, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n        parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE]\n    )\n    APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_READ = Permission(\n        group=Group.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.READ,\n        role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE],\n        parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION]\n    )\n    APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT = Permission(\n        group=Group.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.EDIT,\n        role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE],\n        parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION]\n    )\n    KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_READ = Permission(\n        group=Group.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.READ,\n        role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE],\n        parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION]\n    )\n    KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT = Permission(\n        group=Group.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.EDIT,\n        role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE],\n        parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION]\n    )\n    TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_READ = Permission(\n        group=Group.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.READ,\n        role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE],\n        parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION]\n    )\n    TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT = Permission(\n        group=Group.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.EDIT,\n        role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE],\n        parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION]\n\n    )\n    MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_READ = Permission(\n        group=Group.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.READ,\n        role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE],\n        parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION]\n    )\n    MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT = Permission(\n        group=Group.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION, operate=Operate.EDIT,\n        role_list=[RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE],\n        parent_group=[SystemGroup.RESOURCE_PERMISSION, WorkspaceGroup.RESOURCE_PERMISSION]\n    )\n\n    EMAIL_SETTING_READ = Permission(\n        group=Group.EMAIL_SETTING, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SYSTEM_SETTING]\n    )\n    EMAIL_SETTING_EDIT = Permission(\n        group=Group.EMAIL_SETTING, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SYSTEM_SETTING]\n    )\n\n    ROLE_READ = Permission(\n        group=Group.ROLE, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[SystemGroup.ROLE]\n    )\n    ROLE_CREATE = Permission(\n        group=Group.ROLE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.ROLE]\n    )\n    ROLE_EDIT = Permission(\n        group=Group.ROLE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.ROLE]\n    )\n    ROLE_DELETE = Permission(\n        group=Group.ROLE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.ROLE]\n    )\n    ROLE_ADD_MEMBER = Permission(\n        group=Group.ROLE, operate=Operate.ADD_MEMBER, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.ROLE]\n    )\n    ROLE_REMOVE_MEMBER = Permission(\n        group=Group.ROLE, operate=Operate.REMOVE_MEMBER, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.ROLE]\n    )\n    WORKSPACE_ROLE_READ = Permission(\n        group=Group.WORKSPACE_ROLE, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n    )\n    WORKSPACE_ROLE_ADD_MEMBER = Permission(\n        group=Group.WORKSPACE_ROLE, operate=Operate.ADD_MEMBER, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n    )\n    WORKSPACE_ROLE_REMOVE_MEMBER = Permission(\n        group=Group.WORKSPACE_ROLE, operate=Operate.REMOVE_MEMBER, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n    )\n\n    WORKSPACE_READ = Permission(\n        group=Group.WORKSPACE, operate=Operate.READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == \"EE\"\n    )\n    WORKSPACE_CREATE = Permission(\n        group=Group.WORKSPACE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == \"EE\"\n    )\n    WORKSPACE_EDIT = Permission(\n        group=Group.WORKSPACE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == \"EE\"\n    )\n    WORKSPACE_DELETE = Permission(\n        group=Group.WORKSPACE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == \"EE\"\n    )\n    WORKSPACE_ADD_MEMBER = Permission(\n        group=Group.WORKSPACE, operate=Operate.ADD_MEMBER, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == \"EE\"\n    )\n    WORKSPACE_REMOVE_MEMBER = Permission(\n        group=Group.WORKSPACE, operate=Operate.REMOVE_MEMBER, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.WORKSPACE], is_ee=settings.edition == \"EE\"\n    )\n    WORKSPACE_WORKSPACE_READ = Permission(\n        group=Group.WORKSPACE_WORKSPACE, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT], is_ee=settings.edition == \"EE\"\n    )\n    WORKSPACE_WORKSPACE_ADD_MEMBER = Permission(\n        group=Group.WORKSPACE_WORKSPACE, operate=Operate.ADD_MEMBER, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT], is_ee=settings.edition == \"EE\"\n    )\n    WORKSPACE_WORKSPACE_REMOVE_MEMBER = Permission(\n        group=Group.WORKSPACE_WORKSPACE, operate=Operate.REMOVE_MEMBER, role_list=[RoleConstants.ADMIN],\n        parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT], is_ee=settings.edition == \"EE\"\n    )\n    LOGIN_AUTH_READ = Permission(\n        group=Group.LOGIN_AUTH, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SYSTEM_SETTING]\n    )\n    LOGIN_AUTH_EDIT = Permission(\n        group=Group.LOGIN_AUTH, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SYSTEM_SETTING]\n    )\n    APPLICATION_READ = Permission(group=Group.APPLICATION, operate=Operate.READ,\n                                  role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                  parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                  resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW],\n                                  )\n    APPLICATION_CREATE = Permission(group=Group.APPLICATION, operate=Operate.CREATE,\n                                    role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                    parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                    resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE],\n                                    )\n    APPLICATION_EDIT = Permission(group=Group.APPLICATION, operate=Operate.EDIT,\n                                  role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                  parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                  resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE],\n                                  )\n    APPLICATION_DELETE = Permission(group=Group.APPLICATION, operate=Operate.DELETE,\n                                    role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                    parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                    resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE],\n                                    )\n    APPLICATION_IMPORT = Permission(group=Group.APPLICATION, operate=Operate.IMPORT,\n                                    role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                    parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                    resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE]\n                                    )\n    APPLICATION_EXPORT = Permission(group=Group.APPLICATION, operate=Operate.EXPORT,\n                                    role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                    resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE],\n                                    parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                    )\n    APPLICATION_RESOURCE_AUTHORIZATION = Permission(group=Group.APPLICATION, operate=Operate.AUTH,\n                                                    role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                                    parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                                    resource_permission_group_list=[\n                                                        ResourcePermissionConst.APPLICATION_MANGE],\n                                                    )\n    APPLICATION_TRIGGER_READ = Permission(\n        group=Group.APPLICATION, operate=Operate.TRIGGER_READ, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n        resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE]\n    )\n    APPLICATION_TRIGGER_CREATE = Permission(\n        group=Group.APPLICATION, operate=Operate.TRIGGER_CREATE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n        resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE]\n    )\n    APPLICATION_TRIGGER_EDIT = Permission(\n        group=Group.APPLICATION, operate=Operate.TRIGGER_EDIT, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n        resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE]\n    )\n    APPLICATION_TRIGGER_DELETE = Permission(\n        group=Group.APPLICATION, operate=Operate.TRIGGER_DELETE, role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n        parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n        resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE]\n    )\n    APPLICATION_FOLDER_READ = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.READ,\n                                         role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                         parent_group=[UserGroup.APPLICATION],\n                                         resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW]\n                                         )\n    APPLICATION_FOLDER_CREATE = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.CREATE,\n                                           role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                           parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                           resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE]\n                                           )\n    APPLICATION_FOLDER_EDIT = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.EDIT,\n                                         role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                         parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                         resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE]\n                                         )\n    APPLICATION_FOLDER_DELETE = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.DELETE,\n                                           role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                           parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                           resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE]\n                                           )\n    APPLICATION_FOLDER_AUTH = Permission(group=Group.APPLICATION_FOLDER, operate=Operate.AUTH,\n                                         role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                         parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                         resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE]\n                                         )\n    APPLICATION_OVERVIEW_READ = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.READ,\n                                           role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                           parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                           resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW],\n                                           )\n\n    APPLICATION_OVERVIEW_EMBED = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.EMBED,\n                                            role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                            parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                            resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE],\n\n                                            )\n\n    APPLICATION_OVERVIEW_ACCESS = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.ACCESS,\n                                             role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                             parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                             resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE],\n\n                                             )\n    APPLICATION_OVERVIEW_DISPLAY = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.DISPLAY,\n                                              role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                              parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                              resource_permission_group_list=[\n                                                  ResourcePermissionConst.APPLICATION_MANGE],\n\n                                              )\n    APPLICATION_OVERVIEW_API_KEY = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.API_KEY,\n                                              role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                              parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                              resource_permission_group_list=[\n                                                  ResourcePermissionConst.APPLICATION_MANGE],\n\n                                              )\n    APPLICATION_OVERVIEW_PUBLIC = Permission(group=Group.APPLICATION_OVERVIEW, operate=Operate.PUBLIC_ACCESS,\n                                             role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                             parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                             resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE],\n\n                                             )\n    # 应用接入\n    APPLICATION_ACCESS_READ = Permission(group=Group.APPLICATION_ACCESS, operate=Operate.READ,\n                                         role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                         parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                         resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW],\n\n                                         )\n    APPLICATION_ACCESS_EDIT = Permission(group=Group.APPLICATION_ACCESS, operate=Operate.EDIT,\n                                         role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                         parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                         resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE])\n\n    APPLICATION_CHAT_USER_READ = Permission(group=Group.APPLICATION_CHAT_USER, operate=Operate.READ,\n                                            role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                            parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                            resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW],\n                                            )\n    APPLICATION_CHAT_USER_EDIT = Permission(group=Group.APPLICATION_CHAT_USER, operate=Operate.EDIT,\n                                            role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                            parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                            resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE],\n                                            )\n    KNOWLEDGE_CHAT_USER_READ = Permission(group=Group.KNOWLEDGE_CHAT_USER, operate=Operate.READ,\n                                          role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                          parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE],\n                                          resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_VIEW],\n                                          )\n\n    KNOWLEDGE_CHAT_USER_EDIT = Permission(group=Group.KNOWLEDGE_CHAT_USER, operate=Operate.EDIT,\n                                          role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                          parent_group=[WorkspaceGroup.KNOWLEDGE, UserGroup.KNOWLEDGE],\n                                          resource_permission_group_list=[ResourcePermissionConst.KNOWLEDGE_MANGE],\n                                          )\n\n    APPLICATION_CHAT_LOG_READ = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.READ,\n                                           role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                           parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                           resource_permission_group_list=[ResourcePermissionConst.APPLICATION_VIEW],\n                                           )\n\n    APPLICATION_CHAT_LOG_ANNOTATION = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.ANNOTATION,\n                                                 role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                                 parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                                 resource_permission_group_list=[\n                                                     ResourcePermissionConst.APPLICATION_MANGE],\n                                                 )\n\n    APPLICATION_CHAT_LOG_EXPORT = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.EXPORT,\n                                             role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                             parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                             resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANGE],\n                                             )\n\n    APPLICATION_CHAT_LOG_CLEAR_POLICY = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.CLEAR_POLICY,\n                                                   role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                                   parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                                   resource_permission_group_list=[\n                                                       ResourcePermissionConst.APPLICATION_MANGE],\n                                                   )\n    APPLICATION_CHAT_LOG_ADD_KNOWLEDGE = Permission(group=Group.APPLICATION_CHAT_LOG, operate=Operate.ADD_KNOWLEDGE,\n                                                    role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                                    parent_group=[WorkspaceGroup.APPLICATION, UserGroup.APPLICATION],\n                                                    resource_permission_group_list=[\n                                                        ResourcePermissionConst.APPLICATION_MANGE],\n                                                    )\n\n    ABOUT_READ = Permission(group=Group.OTHER, operate=Operate.READ,\n                            role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                            parent_group=[SystemGroup.OTHER, WorkspaceGroup.OTHER, UserGroup.OTHER],\n                            label=_('About')\n                            )\n    ABOUT_UPDATE = Permission(group=Group.OTHER, operate=Operate.UPDATE,\n                              role_list=[RoleConstants.ADMIN],\n                              parent_group=[SystemGroup.OTHER],\n                              label=_('Update License')\n                              )\n    SWITCH_LANGUAGE = Permission(group=Group.OTHER, operate=Operate.EDIT,\n                                 role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                 parent_group=[SystemGroup.OTHER, WorkspaceGroup.OTHER, UserGroup.OTHER],\n                                 label=_('Switch Language')\n                                 )\n    CHANGE_PASSWORD = Permission(group=Group.OTHER, operate=Operate.CREATE,\n                                 role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                 parent_group=[SystemGroup.OTHER, WorkspaceGroup.OTHER, UserGroup.OTHER],\n                                 label=_('Change Password')\n                                 )\n\n    SYSTEM_API_KEY_EDIT = Permission(group=Group.OTHER, operate=Operate.DELETE,\n                                     role_list=[RoleConstants.ADMIN, RoleConstants.USER],\n                                     parent_group=[SystemGroup.OTHER, WorkspaceGroup.OTHER, UserGroup.OTHER],\n                                     label=_('System API Key')\n                                     )\n\n    APPEARANCE_SETTINGS_READ = Permission(group=Group.APPEARANCE_SETTINGS, operate=Operate.READ,\n                                          role_list=[RoleConstants.ADMIN],\n                                          parent_group=[SystemGroup.SYSTEM_SETTING]\n                                          )\n    APPEARANCE_SETTINGS_EDIT = Permission(group=Group.APPEARANCE_SETTINGS, operate=Operate.EDIT,\n                                          role_list=[RoleConstants.ADMIN],\n                                          parent_group=[SystemGroup.SYSTEM_SETTING]\n                                          )\n    CHAT_USER_READ = Permission(group=Group.CHAT_USER, operate=Operate.READ,\n                                role_list=[RoleConstants.ADMIN],\n                                parent_group=[SystemGroup.CHAT_USER],\n                                )\n    CHAT_USER_CREATE = Permission(group=Group.CHAT_USER, operate=Operate.CREATE,\n                                  role_list=[RoleConstants.ADMIN],\n                                  parent_group=[SystemGroup.CHAT_USER]\n                                  )\n    CHAT_USER_SYNC = Permission(group=Group.CHAT_USER, operate=Operate.SYNC,\n                                role_list=[RoleConstants.ADMIN],\n                                parent_group=[SystemGroup.CHAT_USER]\n                                )\n    CHAT_USER_EDIT = Permission(group=Group.CHAT_USER, operate=Operate.EDIT,\n                                role_list=[RoleConstants.ADMIN],\n                                parent_group=[SystemGroup.CHAT_USER]\n                                )\n    CHAT_USER_DELETE = Permission(group=Group.CHAT_USER, operate=Operate.DELETE,\n                                  role_list=[RoleConstants.ADMIN],\n                                  parent_group=[SystemGroup.CHAT_USER]\n                                  )\n    CHAT_USER_GROUP = Permission(group=Group.CHAT_USER, operate=Operate.USER_GROUP,\n                                 role_list=[RoleConstants.ADMIN],\n                                 parent_group=[SystemGroup.CHAT_USER],\n                                 label=_('Set up user groups')\n                                 )\n    USER_GROUP_READ = Permission(group=Group.USER_GROUP, operate=Operate.READ,\n                                 role_list=[RoleConstants.ADMIN],\n                                 parent_group=[SystemGroup.CHAT_USER]\n                                 )\n    USER_GROUP_CREATE = Permission(group=Group.USER_GROUP, operate=Operate.CREATE,\n                                   role_list=[RoleConstants.ADMIN],\n                                   parent_group=[SystemGroup.CHAT_USER]\n                                   )\n    USER_GROUP_EDIT = Permission(group=Group.USER_GROUP, operate=Operate.EDIT,\n                                 role_list=[RoleConstants.ADMIN],\n                                 parent_group=[SystemGroup.CHAT_USER]\n                                 )\n    USER_GROUP_DELETE = Permission(group=Group.USER_GROUP, operate=Operate.DELETE,\n                                   role_list=[RoleConstants.ADMIN],\n                                   parent_group=[SystemGroup.CHAT_USER]\n                                   )\n    USER_GROUP_ADD_MEMBER = Permission(group=Group.USER_GROUP, operate=Operate.ADD_MEMBER,\n                                       role_list=[RoleConstants.ADMIN],\n                                       parent_group=[SystemGroup.CHAT_USER]\n                                       )\n    USER_GROUP_REMOVE_MEMBER = Permission(group=Group.USER_GROUP, operate=Operate.REMOVE_MEMBER,\n                                          role_list=[RoleConstants.ADMIN],\n                                          parent_group=[SystemGroup.CHAT_USER]\n                                          )\n    CHAT_USER_AUTH_READ = Permission(group=Group.CHAT_USER_AUTH, operate=Operate.READ,\n                                     role_list=[RoleConstants.ADMIN],\n                                     parent_group=[SystemGroup.CHAT_USER]\n                                     )\n    CHAT_USER_AUTH_EDIT = Permission(group=Group.CHAT_USER_AUTH, operate=Operate.EDIT,\n                                     role_list=[RoleConstants.ADMIN],\n                                     parent_group=[SystemGroup.CHAT_USER]\n                                     )\n    WORKSPACE_CHAT_USER_READ = Permission(group=Group.WORKSPACE_CHAT_USER, operate=Operate.READ,\n                                          role_list=[RoleConstants.ADMIN],\n                                          parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n                                          )\n    WORKSPACE_CHAT_USER_CREATE = Permission(group=Group.WORKSPACE_CHAT_USER, operate=Operate.CREATE,\n                                            role_list=[RoleConstants.ADMIN],\n                                            parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n                                            )\n    WORKSPACE_CHAT_USER_EDIT = Permission(group=Group.WORKSPACE_CHAT_USER, operate=Operate.EDIT,\n                                          role_list=[RoleConstants.ADMIN],\n                                          parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n                                          )\n    WORKSPACE_CHAT_USER_DELETE = Permission(group=Group.WORKSPACE_CHAT_USER, operate=Operate.DELETE,\n                                            role_list=[RoleConstants.ADMIN],\n                                            parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n                                            )\n    WORKSPACE_CHAT_USER_GROUP = Permission(group=Group.WORKSPACE_CHAT_USER, operate=Operate.USER_GROUP,\n                                           role_list=[RoleConstants.ADMIN],\n                                           parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT],\n                                           label=_('Set up user groups')\n                                           )\n    WORKSPACE_USER_GROUP_READ = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.READ,\n                                           role_list=[RoleConstants.ADMIN],\n                                           parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n                                           )\n    WORKSPACE_USER_GROUP_CREATE = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.CREATE,\n                                             role_list=[RoleConstants.ADMIN],\n                                             parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n                                             )\n    WORKSPACE_USER_GROUP_EDIT = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.EDIT,\n                                           role_list=[RoleConstants.ADMIN],\n                                           parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n                                           )\n    WORKSPACE_USER_GROUP_DELETE = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.DELETE,\n                                             role_list=[RoleConstants.ADMIN],\n                                             parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n                                             )\n    WORKSPACE_USER_GROUP_ADD_MEMBER = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.ADD_MEMBER,\n                                                 role_list=[RoleConstants.ADMIN],\n                                                 parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n                                                 )\n    WORKSPACE_USER_GROUP_REMOVE_MEMBER = Permission(group=Group.WORKSPACE_USER_GROUP, operate=Operate.REMOVE_MEMBER,\n                                                    role_list=[RoleConstants.ADMIN],\n                                                    parent_group=[WorkspaceGroup.SYSTEM_MANAGEMENT]\n                                                    )\n\n    SHARED_TOOL_READ = Permission(group=Group.SYSTEM_TOOL, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n                                  parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == \"EE\"\n                                  )\n\n    SHARED_TOOL_CREATE = Permission(group=Group.SYSTEM_TOOL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n                                    parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == \"EE\"\n                                    )\n\n    SHARED_TOOL_EDIT = Permission(\n        group=Group.SYSTEM_TOOL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == \"EE\"\n    )\n\n    SHARED_TOOL_DELETE = Permission(\n        group=Group.SYSTEM_TOOL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_TOOL_IMPORT = Permission(\n        group=Group.SYSTEM_TOOL, operate=Operate.IMPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_TOOL_EXPORT = Permission(\n        group=Group.SYSTEM_TOOL, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_TOOL_RELATE_RESOURCE_VIEW = Permission(\n        group=Group.SYSTEM_TOOL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_TOOL_EXECUTE_RECORD = Permission(\n        group=Group.SYSTEM_TOOL, operate=Operate.RECORD, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_READ = Permission(\n        group=Group.SYSTEM_KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_CREATE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_EDIT = Permission(\n        group=Group.SYSTEM_KNOWLEDGE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_SYNC = Permission(\n        group=Group.SYSTEM_KNOWLEDGE, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_VECTOR = Permission(\n        group=Group.SYSTEM_KNOWLEDGE, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_EXPORT = Permission(\n        group=Group.SYSTEM_KNOWLEDGE, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_GENERATE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DELETE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_RELATE_RESOURCE_VIEW = Permission(\n        group=Group.SYSTEM_KNOWLEDGE, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_WORKFLOW_READ = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_WORKFLOW, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_WORKFLOW_EDIT = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_WORKFLOW, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_WORKFLOW_EXPORT = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_WORKFLOW, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_READ = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_CREATE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_EDIT = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_DELETE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_SYNC = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_EXPORT = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.DOWNLOAD, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_GENERATE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_VECTOR = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_MIGRATE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.MIGRATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_TAG = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.TAG, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_DOCUMENT_REPLACE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_DOCUMENT, operate=Operate.REPLACE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_TAG_READ = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_TAG_CREATE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_TAG_EDIT = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_TAG_DELETE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_TAG, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_PROBLEM_READ = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_PROBLEM_CREATE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_PROBLEM_EDIT = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_PROBLEM_DELETE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_PROBLEM_RELATE = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_PROBLEM, operate=Operate.RELATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_HIT_TEST = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_HIT_TEST, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_CHAT_USER_READ = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_KNOWLEDGE_CHAT_USER_EDIT = Permission(\n        group=Group.SYSTEM_KNOWLEDGE_CHAT_USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_MODEL_READ = Permission(\n        group=Group.SYSTEM_MODEL, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_MODEL], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_MODEL_CREATE = Permission(\n        group=Group.SYSTEM_MODEL, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_MODEL], is_ee=settings.edition == \"EE\"\n    )\n\n    SHARED_MODEL_EDIT = Permission(\n        group=Group.SYSTEM_MODEL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_MODEL], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_MODEL_DELETE = Permission(\n        group=Group.SYSTEM_MODEL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_MODEL], is_ee=settings.edition == \"EE\"\n    )\n    SHARED_MODEL_RELATE_RESOURCE_VIEW = Permission(\n        group=Group.SYSTEM_MODEL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.SHARED_MODEL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_READ = Permission(\n        group=Group.SYSTEM_RES_APPLICATION, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_EDIT = Permission(\n        group=Group.SYSTEM_RES_APPLICATION, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_DELETE = Permission(\n        group=Group.SYSTEM_RES_APPLICATION, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_EXPORT = Permission(\n        group=Group.SYSTEM_RES_APPLICATION, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_AUTH = Permission(\n        group=Group.SYSTEM_RES_APPLICATION, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_TRIGGER_READ = Permission(\n        group=Group.SYSTEM_RES_APPLICATION, operate=Operate.TRIGGER_READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_TRIGGER_CREATE = Permission(\n        group=Group.SYSTEM_RES_APPLICATION, operate=Operate.TRIGGER_CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_TRIGGER_EDIT = Permission(\n        group=Group.SYSTEM_RES_APPLICATION, operate=Operate.TRIGGER_EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_TRIGGER_DELETE = Permission(\n        group=Group.SYSTEM_RES_APPLICATION, operate=Operate.TRIGGER_DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_OVERVIEW_READ = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_OVERVIEW_EMBED = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.EMBED, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_OVERVIEW_ACCESS = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.ACCESS, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_OVERVIEW_DISPLAY = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.DISPLAY, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_OVERVIEW_API_KEY = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.API_KEY, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_OVERVIEW_PUBLIC = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_OVERVIEW, operate=Operate.PUBLIC_ACCESS, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    # 应用接入\n    RESOURCE_APPLICATION_ACCESS_READ = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_ACCESS, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_ACCESS_EDIT = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_ACCESS, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_CHAT_USER_READ = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_CHAT_USER_EDIT = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_CHAT_USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_CHAT_LOG_READ = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_CHAT_LOG, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_CHAT_LOG_ADD_KNOWLEDGE = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_CHAT_LOG, operate=Operate.ADD_KNOWLEDGE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_CHAT_LOG_ANNOTATION = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_CHAT_LOG, operate=Operate.ANNOTATION, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_CHAT_LOG_EXPORT = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_CHAT_LOG, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_APPLICATION_CHAT_LOG_CLEAR_POLICY = Permission(\n        group=Group.SYSTEM_RES_APPLICATION_CHAT_LOG, operate=Operate.CLEAR_POLICY, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_APPLICATION], is_ee=settings.edition == \"EE\"\n    )\n    # 知识库\n    RESOURCE_KNOWLEDGE_READ = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_EDIT = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DELETE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_SYNC = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_EXPORT = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_VECTOR = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_GENERATE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_AUTH = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_RELATE_RESOURCE_VIEW = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    # 文档\n    RESOURCE_KNOWLEDGE_WORKFLOW_READ = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_WORKFLOW, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_WORKFLOW_EDIT = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_WORKFLOW, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_WORKFLOW_EXPORT = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_WORKFLOW, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_READ = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_CREATE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_EDIT = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_DELETE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_SYNC = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.SYNC, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_EXPORT = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.DOWNLOAD, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_GENERATE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.GENERATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_VECTOR = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.VECTOR, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_MIGRATE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.MIGRATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_TAG = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.TAG, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_DOCUMENT_REPLACE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_DOCUMENT, operate=Operate.REPLACE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_HIT_TEST = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_HIT_TEST, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_PROBLEM_READ = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_PROBLEM_CREATE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_PROBLEM_EDIT = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_PROBLEM_DELETE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_PROBLEM_RELATE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_PROBLEM, operate=Operate.RELATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_TAG_READ = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_TAG_CREATE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_TAG_EDIT = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_TAG_DELETE = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_TAG, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_CHAT_USER_READ = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_CHAT_USER, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_KNOWLEDGE_CHAT_USER_EDIT = Permission(\n        group=Group.SYSTEM_RES_KNOWLEDGE_CHAT_USER, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_KNOWLEDGE], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_READ = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_EDIT = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_DELETE = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_EXPORT = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_AUTH = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_RELATE_RESOURCE_VIEW = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_EXECUTE_RECORD = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.RECORD, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_TRIGGER_READ = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.TRIGGER_READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_TRIGGER_CREATE = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.TRIGGER_CREATE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_TRIGGER_EDIT = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.TRIGGER_EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_TOOL_TRIGGER_DELETE = Permission(\n        group=Group.SYSTEM_RES_TOOL, operate=Operate.TRIGGER_DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_TOOL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_MODEL_READ = Permission(\n        group=Group.SYSTEM_RES_MODEL, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_MODEL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_MODEL_EDIT = Permission(\n        group=Group.SYSTEM_RES_MODEL, operate=Operate.EDIT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_MODEL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_MODEL_DELETE = Permission(\n        group=Group.SYSTEM_RES_MODEL, operate=Operate.DELETE, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_MODEL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_MODEL_AUTH = Permission(\n        group=Group.SYSTEM_RES_MODEL, operate=Operate.AUTH, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_MODEL], is_ee=settings.edition == \"EE\"\n    )\n    RESOURCE_MODEL_RELATE_RESOURCE_VIEW = Permission(\n        group=Group.SYSTEM_RES_MODEL, operate=Operate.RELATE_VIEW, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.RESOURCE_MODEL], is_ee=settings.edition == \"EE\"\n    )\n    OPERATION_LOG_READ = Permission(\n        group=Group.OPERATION_LOG, operate=Operate.READ, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.OPERATION_LOG]\n    )\n    OPERATION_LOG_EXPORT = Permission(\n        group=Group.OPERATION_LOG, operate=Operate.EXPORT, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.OPERATION_LOG]\n    )\n    OPERATION_LOG_CLEAR_POLICY = Permission(\n        group=Group.OPERATION_LOG, operate=Operate.CLEAR_POLICY, role_list=[RoleConstants.ADMIN],\n        parent_group=[SystemGroup.OPERATION_LOG]\n    )\n\n    def get_workspace_application_permission(self):\n        return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,\n                                            resource_path=\n                                            f\"/WORKSPACE/{kwargs.get('workspace_id')}/APPLICATION/{kwargs.get('application_id')}\")\n\n    def get_workspace_knowledge_permission(self):\n        return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,\n                                            resource_path=\n                                            f\"/WORKSPACE/{kwargs.get('workspace_id')}/KNOWLEDGE/{kwargs.get('knowledge_id')}\")\n\n    def get_workspace_model_permission(self):\n        return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,\n                                            resource_path=\n                                            f\"/WORKSPACE/{kwargs.get('workspace_id')}/MODEL/{kwargs.get('model_id')}\")\n\n    def get_workspace_tool_permission(self):\n        return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,\n                                            resource_path=\n                                            f\"/WORKSPACE/{kwargs.get('workspace_id')}/TOOL/{kwargs.get('tool_id')}\")\n\n    def get_workspace_permission(self):\n        return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,\n                                            resource_path=\n                                            f\"/WORKSPACE/{kwargs.get('workspace_id')}\")\n\n    def get_workspace_permission_workspace_manage_role(self):\n        return lambda r, kwargs: Permission(group=self.value.group, operate=self.value.operate,\n                                            resource_path=\n                                            f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/{RoleConstants.WORKSPACE_MANAGE.value.__str__()}\")\n\n    def __eq__(self, other):\n        if isinstance(other, PermissionConstants):\n            return other == self\n        else:\n            return self.value == other\n\n\ndef get_default_permission_list_by_role(role: RoleConstants):\n    \"\"\"\n    根据角色 获取角色对应的权限\n    :param role: 角色\n    :return: 权限\n    \"\"\"\n    return list(map(lambda k: PermissionConstants[k],\n                    list(filter(lambda k: PermissionConstants[k].value.role_list.__contains__(role),\n                                PermissionConstants.__members__))))\n\n\nclass RolePermissionMapping:\n    def __init__(self, role_id, permission_id):\n        self.role_id = role_id\n        self.permission_id = permission_id\n\n\nclass WorkspaceUserRoleMapping:\n    def __init__(self, workspace_id, role_id, user_id):\n        self.workspace_id = workspace_id\n        self.role_id = role_id\n        self.user_id = user_id\n\n\ndef get_default_role_permission_mapping_list():\n    role_permission_mapping_list = [\n        [RolePermissionMapping(role.value.name, PermissionConstants[k].value.__str__()) for role in\n         PermissionConstants[k].value.role_list] for k in PermissionConstants.__members__]\n    return reduce(lambda x, y: [*x, *y], role_permission_mapping_list, [])\n\n\ndef get_default_workspace_user_role_mapping_list(user_role_list: list):\n    return [WorkspaceUserRoleMapping('default', role.value.name, 'default') for role in RoleConstants if\n            user_role_list.__contains__(role.value.name)]\n\n\ndef get_permission_list_by_resource_group(resource_group: ResourcePermissionGroup):\n    \"\"\"\n    根据资源组获取权限\n    \"\"\"\n    return [PermissionConstants[k].value for k in PermissionConstants.__members__ if\n            PermissionConstants[k].value.resource_permission_group_list.__contains__(resource_group)]\n\n\nclass ChatAuth:\n    def __init__(self,\n                 current_role_list: List[RoleConstants | Role],\n                 permission_list: List[PermissionConstants | Permission],\n                 chat_user_id,\n                 chat_user_type,\n                 application_id):\n        # 权限列表\n        self.permission_list = permission_list\n        # 角色列表\n        self.role_list = current_role_list\n        self.chat_user_id = chat_user_id\n        self.chat_user_type = chat_user_type\n        self.application_id = application_id\n\n\nclass Auth:\n    \"\"\"\n     用于存储当前用户的角色和权限\n    \"\"\"\n\n    def __init__(self,\n                 current_role_list: List[RoleConstants | Role],\n                 permission_list: List[PermissionConstants | Permission],\n                 **keywords):\n        # 权限列表\n        self.permission_list = permission_list\n        # 角色列表\n        self.role_list = current_role_list\n        self.keywords = keywords\n\n\nclass CompareConstants(Enum):\n    # 或者\n    OR = \"OR\"\n    # 并且\n    AND = \"AND\"\n\n\nclass ViewPermission:\n    def __init__(self, roleList: List[RoleConstants], permissionList: List[PermissionConstants | object],\n                 compare=CompareConstants.OR):\n        self.roleList = roleList\n        self.permissionList = permissionList\n        self.compare = compare\n"
  },
  {
    "path": "apps/common/database_model_manage/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/database_model_manage/database_model_manage.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： database_model_manage.py\n    @date：2025/4/15 11:06\n    @desc:\n\"\"\"\nfrom importlib import import_module\nfrom django.conf import settings\n\n\ndef new_instance_by_class_path(class_path: str):\n    \"\"\"\n    根据class_path 创建实例\n    \"\"\"\n    parts = class_path.rpartition('.')\n    package_path = parts[0]\n    class_name = parts[2]\n    module = import_module(package_path)\n    HandlerClass = getattr(module, class_name)\n    return HandlerClass()\n\n\nclass DatabaseModelManage:\n    \"\"\"\n    模型字典\n    \"\"\"\n    model_dict = {}\n\n    @staticmethod\n    def get_model(model_name):\n        \"\"\"\n        根据模型\n        \"\"\"\n        return DatabaseModelManage.model_dict.get(model_name)\n\n    @staticmethod\n    def init():\n        handles = [new_instance_by_class_path(class_path) for class_path in\n                   (settings.MODEL_HANDLES if hasattr(settings, 'MODEL_HANDLES') else [])]\n        for h in handles:\n            model_dict = h.get_model_dict()\n            DatabaseModelManage.model_dict = {**DatabaseModelManage.model_dict, **model_dict}\n"
  },
  {
    "path": "apps/common/database_model_manage/handle/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/database_model_manage/handle/base_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： base_handle.py\n    @date：2025/4/15 11:16\n    @desc:\n\"\"\"\nfrom abc import ABC, abstractmethod\n\n\nclass IBaseModelHandle(ABC):\n    @abstractmethod\n    def get_model_dict(self):\n        pass\n"
  },
  {
    "path": "apps/common/database_model_manage/handle/impl/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/database_model_manage/handle/impl/default_base_model_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： default_base_model_handle.py\n    @date：2025/4/15 11:20\n    @desc:\n\"\"\"\nfrom common.database_model_manage.handle.base_handle import IBaseModelHandle\n\n\nclass DefaultBaseModelHandle(IBaseModelHandle):\n    def get_model_dict(self):\n        return {}\n"
  },
  {
    "path": "apps/common/db/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/db/compiler.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： compiler.py\n    @date：2023/10/7 10:53\n    @desc:\n\"\"\"\n\nfrom django.core.exceptions import EmptyResultSet, FullResultSet\nfrom django.db import NotSupportedError\nfrom django.db.models.sql.compiler import SQLCompiler\nfrom django.db.transaction import TransactionManagementError\n\n\nclass AppSQLCompiler(SQLCompiler):\n    def __init__(self, query, connection, using, elide_empty=True, field_replace_dict=None):\n        super().__init__(query, connection, using, elide_empty)\n        if field_replace_dict is None:\n            field_replace_dict = {}\n        self.field_replace_dict = field_replace_dict\n\n    def get_query_str(self, with_limits=True, with_table_name=False, with_col_aliases=False):\n        refcounts_before = self.query.alias_refcount.copy()\n        try:\n            combinator = self.query.combinator\n            extra_select, order_by, group_by = self.pre_sql_setup(\n                with_col_aliases=with_col_aliases or bool(combinator),\n            )\n            for_update_part = None\n            # Is a LIMIT/OFFSET clause needed?\n            with_limit_offset = with_limits and self.query.is_sliced\n            combinator = self.query.combinator\n            features = self.connection.features\n            if combinator:\n                if not getattr(features, \"supports_select_{}\".format(combinator)):\n                    raise NotSupportedError(\n                        \"{} is not supported on this database backend.\".format(\n                            combinator\n                        )\n                    )\n                result, params = self.get_combinator_sql(\n                    combinator, self.query.combinator_all\n                )\n            elif self.qualify:\n                result, params = self.get_qualify_sql()\n                order_by = None\n            else:\n                distinct_fields, distinct_params = self.get_distinct()\n                # This must come after 'select', 'ordering', and 'distinct'\n                # (see docstring of get_from_clause() for details).\n                from_, f_params = self.get_from_clause()\n                try:\n                    where, w_params = (\n                        self.compile(self.where) if self.where is not None else (\"\", [])\n                    )\n                except EmptyResultSet:\n                    if self.elide_empty:\n                        raise\n                    # Use a predicate that's always False.\n                    where, w_params = \"0 = 1\", []\n                except FullResultSet:\n                    where, w_params = \"\", []\n                try:\n                    having, h_params = (\n                        self.compile(self.having)\n                        if self.having is not None\n                        else (\"\", [])\n                    )\n                except FullResultSet:\n                    having, h_params = \"\", []\n                result = []\n                params = []\n\n                if self.query.distinct:\n                    distinct_result, distinct_params = self.connection.ops.distinct_sql(\n                        distinct_fields,\n                        distinct_params,\n                    )\n                    result += distinct_result\n                    params += distinct_params\n\n                out_cols = []\n                for _, (s_sql, s_params), alias in self.select + extra_select:\n                    if alias:\n                        s_sql = \"%s AS %s\" % (\n                            s_sql,\n                            self.connection.ops.quote_name(alias),\n                        )\n                    params.extend(s_params)\n                    out_cols.append(s_sql)\n\n                params.extend(f_params)\n\n                if self.query.select_for_update and features.has_select_for_update:\n                    if (\n                            self.connection.get_autocommit()\n                            # Don't raise an exception when database doesn't\n                            # support transactions, as it's a noop.\n                            and features.supports_transactions\n                    ):\n                        raise TransactionManagementError(\n                            \"select_for_update cannot be used outside of a transaction.\"\n                        )\n\n                    if (\n                            with_limit_offset\n                            and not features.supports_select_for_update_with_limit\n                    ):\n                        raise NotSupportedError(\n                            \"LIMIT/OFFSET is not supported with \"\n                            \"select_for_update on this database backend.\"\n                        )\n                    nowait = self.query.select_for_update_nowait\n                    skip_locked = self.query.select_for_update_skip_locked\n                    of = self.query.select_for_update_of\n                    no_key = self.query.select_for_no_key_update\n                    # If it's a NOWAIT/SKIP LOCKED/OF/NO KEY query but the\n                    # backend doesn't support it, raise NotSupportedError to\n                    # prevent a possible deadlock.\n                    if nowait and not features.has_select_for_update_nowait:\n                        raise NotSupportedError(\n                            \"NOWAIT is not supported on this database backend.\"\n                        )\n                    elif skip_locked and not features.has_select_for_update_skip_locked:\n                        raise NotSupportedError(\n                            \"SKIP LOCKED is not supported on this database backend.\"\n                        )\n                    elif of and not features.has_select_for_update_of:\n                        raise NotSupportedError(\n                            \"FOR UPDATE OF is not supported on this database backend.\"\n                        )\n                    elif no_key and not features.has_select_for_no_key_update:\n                        raise NotSupportedError(\n                            \"FOR NO KEY UPDATE is not supported on this \"\n                            \"database backend.\"\n                        )\n                    for_update_part = self.connection.ops.for_update_sql(\n                        nowait=nowait,\n                        skip_locked=skip_locked,\n                        of=self.get_select_for_update_of_arguments(),\n                        no_key=no_key,\n                    )\n\n                if for_update_part and features.for_update_after_from:\n                    result.append(for_update_part)\n\n                if where:\n                    result.append(\"WHERE %s\" % where)\n                    params.extend(w_params)\n\n                grouping = []\n                for g_sql, g_params in group_by:\n                    grouping.append(g_sql)\n                    params.extend(g_params)\n                if grouping:\n                    if distinct_fields:\n                        raise NotImplementedError(\n                            \"annotate() + distinct(fields) is not implemented.\"\n                        )\n                    order_by = order_by or self.connection.ops.force_no_ordering()\n                    result.append(\"GROUP BY %s\" % \", \".join(grouping))\n                    if self._meta_ordering:\n                        order_by = None\n                if having:\n                    result.append(\"HAVING %s\" % having)\n                    params.extend(h_params)\n\n            if self.query.explain_info:\n                result.insert(\n                    0,\n                    self.connection.ops.explain_query_prefix(\n                        self.query.explain_info.format,\n                        **self.query.explain_info.options,\n                    ),\n                )\n\n            if order_by:\n                ordering = []\n                for _, (o_sql, o_params, _) in order_by:\n                    ordering.append(o_sql)\n                    params.extend(o_params)\n                order_by_sql = \"ORDER BY %s\" % \", \".join(ordering)\n                if combinator and features.requires_compound_order_by_subquery:\n                    result = [\"SELECT * FROM (\", *result, \")\", order_by_sql]\n                else:\n                    result.append(order_by_sql)\n\n            if with_limit_offset:\n                result.append(\n                    self.connection.ops.limit_offset_sql(\n                        self.query.low_mark, self.query.high_mark\n                    )\n                )\n\n            if for_update_part and not features.for_update_after_from:\n                result.append(for_update_part)\n\n            from_, f_params = self.get_from_clause()\n            sql = \" \".join(result)\n            if not with_table_name:\n                for table_name in from_:\n                    sql = sql.replace(table_name + \".\", \"\")\n            for key in self.field_replace_dict.keys():\n                value = self.field_replace_dict.get(key)\n                sql = sql.replace(key, value)\n            return sql, tuple(params)\n        finally:\n            # Finally do cleanup - get rid of the joins we created above.\n            self.query.reset_refcounts(refcounts_before)\n\n    def as_sql(self, with_limits=True, with_col_aliases=False, select_string=None):\n        if select_string is None:\n            return super().as_sql(with_limits, with_col_aliases)\n        else:\n            sql, params = self.get_query_str(with_table_name=False)\n            return (select_string + \" \" + sql), params\n"
  },
  {
    "path": "apps/common/db/search.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： search.py\n    @date：2023/10/7 18:20\n    @desc:\n\"\"\"\nimport hashlib\nfrom typing import Dict, Any\n\nfrom django.db import DEFAULT_DB_ALIAS, models, connections\nfrom django.db.models import QuerySet\n\nfrom common.db.compiler import AppSQLCompiler\nfrom common.db.sql_execute import select_one, select_list, update_execute\nfrom common.result import Page\n\n# 添加模型缓存\n_model_cache = {}\n\n\ndef get_dynamics_model(attr: dict, table_name='dynamics'):\n    \"\"\"\n    获取一个动态的django模型\n    :param attr:       模型字段\n    :param table_name: 表名\n    :return: django 模型\n    \"\"\"\n    # 创建缓存键，基于属性和表名\n    cache_key = hashlib.md5(f\"{table_name}_{str(sorted(attr.items()))}\".encode()).hexdigest()\n    # print(f'cache_key: {cache_key}')\n\n    # 如果模型已存在，直接返回缓存的模型\n    if cache_key in _model_cache:\n        return _model_cache[cache_key]\n\n    attributes = {\n        \"__module__\": \"knowledge.models\",\n        \"Meta\": type(\"Meta\", (), {'db_table': table_name}),\n        **attr\n    }\n\n    # 使用唯一的类名避免冲突\n    class_name = f'Dynamics_{cache_key[:8]}'\n    model_class = type(class_name, (models.Model,), attributes)\n\n    # 缓存模型\n    _model_cache[cache_key] = model_class\n\n    return model_class\n\n\ndef generate_sql_by_query_dict(queryset_dict: Dict[str, QuerySet], select_string: str,\n                               field_replace_dict: None | Dict[str, Dict[str, str]] = None, with_table_name=False):\n    \"\"\"\n    生成 查询sql\n    :param with_table_name:\n    :param queryset_dict: 多条件 查询条件\n    :param select_string: 查询sql\n    :param field_replace_dict:  需要替换的查询字段,一般不需要传入如果有特殊的需要传入\n    :return: sql:需要查询的sql params: sql 参数\n    \"\"\"\n\n    params_dict: Dict[int, Any] = {}\n    result_params = []\n    for key in queryset_dict.keys():\n        value = queryset_dict.get(key)\n        sql, params = compiler_queryset(value, None if field_replace_dict is None else field_replace_dict.get(key),\n                                        with_table_name)\n        params_dict = {**params_dict, select_string.index(\"${\" + key + \"}\"): params}\n        select_string = select_string.replace(\"${\" + key + \"}\", sql)\n\n    for key in sorted(list(params_dict.keys())):\n        result_params = [*result_params, *params_dict.get(key)]\n    return select_string, result_params\n\n\ndef generate_sql_by_query(queryset: QuerySet, select_string: str,\n                          field_replace_dict: None | Dict[str, str] = None, with_table_name=False):\n    \"\"\"\n    生成 查询sql\n    :param queryset:            查询条件\n    :param select_string:       原始sql\n    :param field_replace_dict:  需要替换的查询字段,一般不需要传入如果有特殊的需要传入\n    :return:  sql:需要查询的sql params: sql 参数\n    \"\"\"\n    sql, params = compiler_queryset(queryset, field_replace_dict, with_table_name)\n    return select_string + \" \" + sql, params\n\n\ndef compiler_queryset(queryset: QuerySet, field_replace_dict: None | Dict[str, str] = None, with_table_name=False):\n    \"\"\"\n    解析 queryset查询对象\n    :param with_table_name:\n    :param queryset:            查询对象\n    :param field_replace_dict:  需要替换的查询字段,一般不需要传入如果有特殊的需要传入\n    :return: sql:需要查询的sql params: sql 参数\n    \"\"\"\n    q = queryset.query\n    compiler = q.get_compiler(DEFAULT_DB_ALIAS)\n    if field_replace_dict is None:\n        field_replace_dict = get_field_replace_dict(queryset)\n    app_sql_compiler = AppSQLCompiler(q, using=DEFAULT_DB_ALIAS, connection=compiler.connection,\n                                      field_replace_dict=field_replace_dict)\n    sql, params = app_sql_compiler.get_query_str(with_table_name=with_table_name)\n    return sql, params\n\n\ndef native_search(queryset: QuerySet | Dict[str, QuerySet], select_string: str,\n                  field_replace_dict: None | Dict[str, Dict[str, str]] | Dict[str, str] = None,\n                  with_search_one=False, with_table_name=False):\n    \"\"\"\n    复杂查询\n    :param with_table_name:     生成sql是否包含表名\n    :param queryset:            查询条件构造器\n    :param select_string:       查询前缀 不包括 where limit 等信息\n    :param field_replace_dict:  需要替换的字段\n    :param with_search_one:     查询\n    :return: 查询结果\n    \"\"\"\n    if isinstance(queryset, Dict):\n        exec_sql, exec_params = generate_sql_by_query_dict(queryset, select_string, field_replace_dict, with_table_name)\n    else:\n        exec_sql, exec_params = generate_sql_by_query(queryset, select_string, field_replace_dict, with_table_name)\n    if with_search_one:\n        return select_one(exec_sql, exec_params)\n    else:\n        return select_list(exec_sql, exec_params)\n\n\ndef native_update(queryset: QuerySet | Dict[str, QuerySet], select_string: str,\n                  field_replace_dict: None | Dict[str, Dict[str, str]] | Dict[str, str] = None,\n                  with_table_name=False):\n    \"\"\"\n    复杂查询\n    :param with_table_name:     生成sql是否包含表名\n    :param queryset:            查询条件构造器\n    :param select_string:       查询前缀 不包括 where limit 等信息\n    :param field_replace_dict:  需要替换的字段\n    :return: 查询结果\n    \"\"\"\n    if isinstance(queryset, Dict):\n        exec_sql, exec_params = generate_sql_by_query_dict(queryset, select_string, field_replace_dict, with_table_name)\n    else:\n        exec_sql, exec_params = generate_sql_by_query(queryset, select_string, field_replace_dict, with_table_name)\n    return update_execute(exec_sql, exec_params)\n\n\ndef page_search(current_page: int, page_size: int, queryset: QuerySet, post_records_handler):\n    \"\"\"\n    分页查询\n    :param current_page:         当前页\n    :param page_size:            每页大小\n    :param queryset:             查询条件\n    :param post_records_handler: 数据处理器\n    :return:  分页结果\n    \"\"\"\n    total = QuerySet(query=queryset.query.clone(), model=queryset.model).count()\n    result = queryset.all()[((current_page - 1) * page_size):(current_page * page_size)]\n    return Page(total, list(map(post_records_handler, result)), current_page, page_size)\n\n\ndef native_page_search(current_page: int, page_size: int, queryset: QuerySet | Dict[str, QuerySet], select_string: str,\n                       field_replace_dict=None,\n                       post_records_handler=lambda r: r,\n                       with_table_name=False):\n    \"\"\"\n    复杂分页查询\n    :param with_table_name:\n    :param current_page:          当前页\n    :param page_size:             每页大小\n    :param queryset:              查询条件\n    :param select_string:         查询\n    :param field_replace_dict:    特殊字段替换\n    :param post_records_handler:  数据row处理器\n    :return: 分页结果\n    \"\"\"\n    if isinstance(queryset, Dict):\n        exec_sql, exec_params = generate_sql_by_query_dict(queryset, select_string, field_replace_dict, with_table_name)\n    else:\n        exec_sql, exec_params = generate_sql_by_query(queryset, select_string, field_replace_dict, with_table_name)\n    total_sql = \"SELECT \\\"count\\\"(*) FROM (%s) temp\" % exec_sql\n    total = select_one(total_sql, exec_params)\n    limit_sql = connections[DEFAULT_DB_ALIAS].ops.limit_offset_sql(\n        ((current_page - 1) * page_size), (current_page * page_size)\n    )\n    page_sql = exec_sql + \" \" + limit_sql\n    result = select_list(page_sql, exec_params)\n    return Page(total.get(\"count\"), list(map(post_records_handler, result)), current_page, page_size)\n\n\ndef native_page_handler(page_size: int,\n                        queryset: QuerySet | Dict[str, QuerySet],\n                        select_string: str,\n                        field_replace_dict=None,\n                        with_table_name=False,\n                        primary_key=None,\n                        get_primary_value=None,\n                        primary_queryset: str = None,\n                        ):\n    if isinstance(queryset, Dict):\n        exec_sql, exec_params = generate_sql_by_query_dict({**queryset,\n            primary_queryset: queryset[primary_queryset].order_by(\n                primary_key)}, select_string, field_replace_dict, with_table_name)\n    else:\n        exec_sql, exec_params = generate_sql_by_query(queryset.order_by(\n            primary_key), select_string, field_replace_dict, with_table_name)\n    total_sql = \"SELECT \\\"count\\\"(*) FROM (%s) temp\" % exec_sql\n    total = select_one(total_sql, exec_params)\n    processed_count = 0\n    last_id = None\n    while processed_count < total.get(\"count\"):\n        if last_id is not None:\n            if isinstance(queryset, Dict):\n                exec_sql, exec_params = generate_sql_by_query_dict({**queryset,\n                    primary_queryset: queryset[primary_queryset].filter(\n                        **{f\"{primary_key}__gt\": last_id}).order_by(\n                        primary_key)},\n                    select_string, field_replace_dict,\n                    with_table_name)\n            else:\n                exec_sql, exec_params = generate_sql_by_query(\n                    queryset.filter(**{f\"{primary_key}__gt\": last_id}).order_by(\n                        primary_key),\n                    select_string, field_replace_dict,\n                    with_table_name)\n        limit_sql = connections[DEFAULT_DB_ALIAS].ops.limit_offset_sql(\n            0, page_size\n        )\n        page_sql = exec_sql + \" \" + limit_sql\n        result = select_list(page_sql, exec_params)\n        yield result\n        processed_count += page_size\n        last_id = get_primary_value(result[-1])\n\n\ndef get_field_replace_dict(queryset: QuerySet):\n    \"\"\"\n    获取需要替换的字段 默认 “xxx.xxx”需要被替换成 “xxx”.\"xxx\"\n    :param queryset: 查询对象\n    :return: 需要替换的字典\n    \"\"\"\n    result = {}\n    for field in queryset.model._meta.local_fields:\n        if field.attname.__contains__(\".\"):\n            replace_field = to_replace_field(field.attname)\n            result.__setitem__('\"' + field.attname + '\"', replace_field)\n    return result\n\n\ndef to_replace_field(field: str):\n    \"\"\"\n    将field 转换为 需要替换的field  “xxx.xxx”需要被替换成 “xxx”.\"xxx\" 只替换 field包含.的字段\n    :param field: django field字段\n    :return: 替换字段\n    \"\"\"\n    split_field = field.split(\".\")\n    return \".\".join(list(map(lambda sf: '\"' + sf + '\"', split_field)))\n"
  },
  {
    "path": "apps/common/db/sql_execute.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： sql_execute.py\n    @date：2023/9/25 20:05\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom django.db import connection\n\n\ndef sql_execute(sql: str, params):\n    \"\"\"\n    执行一条sql\n    :param sql:     需要执行的sql\n    :param params:  sql参数\n    :return:        执行结果\n    \"\"\"\n    with connection.cursor() as cursor:\n        cursor.execute(sql, params)\n        columns = list(map(lambda d: d.name, cursor.description))\n        res = cursor.fetchall()\n        result = list(map(lambda row: dict(list(zip(columns, row))), res))\n        cursor.close()\n        return result\n\n\ndef update_execute(sql: str, params):\n    \"\"\"\n      执行一条sql\n      :param sql:     需要执行的sql\n      :param params:  sql参数\n      :return:        执行结果\n      \"\"\"\n    with connection.cursor() as cursor:\n        cursor.execute(sql, params)\n        affected_rows = cursor.rowcount\n        cursor.close()\n        return affected_rows\n\n\ndef select_list(sql: str, params: List):\n    \"\"\"\n    执行sql 查询列表数据\n    :param sql:     需要执行的sql\n    :param params:  sql的参数\n    :return: 查询结果\n    \"\"\"\n    result_list = sql_execute(sql, params)\n    if result_list is None:\n        return []\n    return result_list\n\n\ndef select_one(sql: str, params: List):\n    \"\"\"\n    执行sql 查询一条数据\n    :param sql:     需要执行的sql\n    :param params:  参数\n    :return: 查询结果\n    \"\"\"\n    result_list = sql_execute(sql, params)\n    if result_list is None or len(result_list) == 0:\n        return None\n    return result_list[0]\n"
  },
  {
    "path": "apps/common/encoder/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/encoder/encoder.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： SystemEncoder.py\n    @date：2025/3/17 16:38\n    @desc:\n\"\"\"\nimport datetime\nimport decimal\nimport json\nimport uuid\n\nfrom django.core.files.uploadedfile import InMemoryUploadedFile, TemporaryUploadedFile\n\n\nclass SystemEncoder(json.JSONEncoder):\n    def encode(self, obj):\n        # 先序列化为字符串\n        json_str = super().encode(obj)\n        # 移除所有空字符\n        json_str = json_str.replace('\\\\u0000', '')\n        return json_str\n\n    def default(self, obj):\n        if isinstance(obj, uuid.UUID):\n            return str(obj)\n        if isinstance(obj, datetime.datetime):\n            return obj.strftime(\"%Y-%m-%d %H:%M:%S\")\n        if isinstance(obj, decimal.Decimal):\n            return float(obj)\n        if isinstance(obj, InMemoryUploadedFile):\n            return {'name': obj.name, 'size': obj.size}\n        if isinstance(obj, TemporaryUploadedFile):\n            return {'name': obj.name, 'size': obj.size}\n        else:\n            return json.JSONEncoder.default(self, obj)\n"
  },
  {
    "path": "apps/common/event/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2023/11/10 10:43\n    @desc:\n\"\"\"\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext as _\n\n\nfrom ..constants.cache_version import Cache_Version\nfrom ..db.sql_execute import update_execute\nfrom ..utils.lock import RedisLock\n\nupdate_document_status_sql = \"\"\"\n                             UPDATE \"public\".\"document\"\n                             SET status =\"replace\"(\"replace\"(\"replace\"(status, '1', '3'), '0', '3'), '4', '3')\n                             WHERE status ~ '1|0|4' \\\n                             \"\"\"\n\n\ndef run():\n    from models_provider.models import Model, Status\n    rlock = RedisLock()\n    if rlock.try_lock('event_init', 30 * 30):\n        try:\n            # 修改Model状态为ERROR\n            QuerySet(Model).filter(\n                status=Status.DOWNLOAD\n            ).update(\n                status=Status.ERROR, meta={'message': _('The download process was interrupted, please try again')}\n            )\n            # 更新文档状态\n            update_execute(update_document_status_sql, [])\n            version, get_key = Cache_Version.SYSTEM.value\n            cache.delete(get_key(key='rsa_key'), version=version)\n        finally:\n            rlock.un_lock('event_init')\n"
  },
  {
    "path": "apps/common/event/common.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： common.py\n    @date：2023/11/10 10:41\n    @desc:\n\"\"\"\nfrom concurrent.futures import ThreadPoolExecutor\n\nfrom django.core.cache.backends.locmem import LocMemCache\n\nwork_thread_pool = ThreadPoolExecutor(5)\n\nembedding_thread_pool = ThreadPoolExecutor(3)\n\nmemory_cache = LocMemCache('task', {\"OPTIONS\": {\"MAX_ENTRIES\": 1000}})\n\n\ndef poxy(poxy_function):\n    def inner(args, **keywords):\n        work_thread_pool.submit(poxy_function, args, **keywords)\n\n    return inner\n\n\ndef get_cache_key(poxy_function, args):\n    return poxy_function.__name__ + str(args)\n\n\ndef get_cache_poxy_function(poxy_function, cache_key):\n    def fun(args, **keywords):\n        try:\n            poxy_function(args, **keywords)\n        finally:\n            memory_cache.delete(cache_key)\n\n    return fun\n\n\ndef embedding_poxy(poxy_function):\n    def inner(*args, **keywords):\n        key = get_cache_key(poxy_function, args)\n        if memory_cache.has_key(key):\n            return\n        memory_cache.add(key, None)\n        f = get_cache_poxy_function(poxy_function, key)\n        embedding_thread_pool.submit(f, args, **keywords)\n\n    return inner\n"
  },
  {
    "path": "apps/common/event/listener_manage.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： listener_manage.py\n    @date：2023/10/20 14:01\n    @desc:\n\"\"\"\nimport datetime\nimport os\nimport threading\nimport traceback\nfrom typing import List\n\nimport django.db.models\nfrom django.db.models import QuerySet\nfrom django.db.models.functions import Substr, Reverse\nfrom django.utils.translation import gettext_lazy as _\nfrom django.utils import timezone\nfrom langchain_core.embeddings import Embeddings\n\nfrom common.config.embedding_config import VectorStore\nfrom common.db.search import native_search, get_dynamics_model, native_update\nfrom common.utils.common import get_file_content\nfrom common.utils.lock import RedisLock\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.page_utils import page_desc\nfrom knowledge.models import Paragraph, Status, Document, ProblemParagraphMapping, TaskType, State, SourceType, \\\n    SearchMode\nfrom knowledge.serializers.common import create_knowledge_index\nfrom maxkb.conf import (PROJECT_DIR)\n\nlock = threading.Lock()\n\n\nclass SyncWebKnowledgeArgs:\n    def __init__(self, lock_key: str, url: str, selector: str, handler):\n        self.lock_key = lock_key\n        self.url = url\n        self.selector = selector\n        self.handler = handler\n\n\nclass SyncWebDocumentArgs:\n    def __init__(self, source_url_list: List[str], selector: str, handler):\n        self.source_url_list = source_url_list\n        self.selector = selector\n        self.handler = handler\n\n\nclass UpdateProblemArgs:\n    def __init__(self, problem_id: str, problem_content: str, embedding_model: Embeddings):\n        self.problem_id = problem_id\n        self.problem_content = problem_content\n        self.embedding_model = embedding_model\n\n\nclass UpdateEmbeddingKnowledgeIdArgs:\n    def __init__(self, paragraph_id_list: List[str], target_knowledge_id: str):\n        self.paragraph_id_list = paragraph_id_list\n        self.target_knowledge_id = target_knowledge_id\n\n\nclass UpdateEmbeddingDocumentIdArgs:\n    def __init__(self, paragraph_id_list: List[str], target_document_id: str, target_knowledge_id: str,\n                 target_embedding_model: Embeddings = None):\n        self.paragraph_id_list = paragraph_id_list\n        self.target_document_id = target_document_id\n        self.target_knowledge_id = target_knowledge_id\n        self.target_embedding_model = target_embedding_model\n\n\nclass ListenerManagement:\n\n    @staticmethod\n    def embedding_by_problem(args, embedding_model: Embeddings):\n        VectorStore.get_embedding_vector().save(**args, embedding=embedding_model)\n\n    @staticmethod\n    def embedding_by_paragraph_list(paragraph_id_list, embedding_model: Embeddings):\n        try:\n            data_list = native_search(\n                {'problem': QuerySet(get_dynamics_model({'paragraph.id': django.db.models.CharField()})).filter(\n                    **{'paragraph.id__in': paragraph_id_list}),\n                    'paragraph': QuerySet(Paragraph).filter(id__in=paragraph_id_list)},\n                select_string=get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"common\", 'sql', 'list_embedding_text.sql')))\n            ListenerManagement.embedding_by_paragraph_data_list(data_list, paragraph_id_list=paragraph_id_list,\n                                                                embedding_model=embedding_model)\n        except Exception as e:\n            maxkb_logger.error(_('Query vector data: {paragraph_id_list} error {error} {traceback}').format(\n                paragraph_id_list=paragraph_id_list, error=str(e), traceback=traceback.format_exc()))\n\n    @staticmethod\n    def embedding_by_paragraph_data_list(data_list, paragraph_id_list, embedding_model: Embeddings):\n        maxkb_logger.info(_('Start--->Embedding paragraph: {paragraph_id_list}').format(\n            paragraph_id_list=paragraph_id_list)\n        )\n        try:\n            # 删除段落\n            VectorStore.get_embedding_vector().delete_by_paragraph_ids(paragraph_id_list)\n\n            def is_save_function():\n                return QuerySet(Paragraph).filter(id__in=paragraph_id_list).exists()\n\n            # 批量向量化\n            VectorStore.get_embedding_vector().batch_save(data_list, embedding_model, is_save_function)\n            ListenerManagement.update_status(\n                QuerySet(Paragraph).filter(id__in=paragraph_id_list), TaskType.EMBEDDING, State.SUCCESS\n            )\n        except Exception as e:\n            maxkb_logger.error(_('Vectorized paragraph: {paragraph_id_list} error {error} {traceback}').format(\n                paragraph_id_list=paragraph_id_list, error=str(e), traceback=traceback.format_exc())\n            )\n            ListenerManagement.update_status(\n                QuerySet(Paragraph).filter(id__in=paragraph_id_list), TaskType.EMBEDDING, State.FAILURE\n            )\n        finally:\n            maxkb_logger.info(_('End--->Embedding paragraph: {paragraph_id_list}').format(\n                paragraph_id_list=paragraph_id_list)\n            )\n\n    @staticmethod\n    def embedding_by_paragraph(paragraph_id, embedding_model: Embeddings):\n        \"\"\"\n        向量化段落 根据段落id\n        @param paragraph_id:    段落id\n        @param embedding_model:  向量模型\n        \"\"\"\n        maxkb_logger.info(_('Start--->Embedding paragraph: {paragraph_id}').format(paragraph_id=paragraph_id))\n        # 更新到开始状态\n        ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph_id), TaskType.EMBEDDING, State.STARTED)\n        try:\n            data_list = native_search(\n                {'problem': QuerySet(get_dynamics_model({'paragraph.id': django.db.models.CharField()})).filter(\n                    **{'paragraph.id': paragraph_id}),\n                    'paragraph': QuerySet(Paragraph).filter(id=paragraph_id)},\n                select_string=get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"common\", 'sql', 'list_embedding_text.sql')))\n            # 删除段落\n            VectorStore.get_embedding_vector().delete_by_paragraph_id(paragraph_id)\n\n            def is_the_task_interrupted():\n                _paragraph = QuerySet(Paragraph).filter(id=paragraph_id).first()\n                if _paragraph is None or Status(_paragraph.status)[TaskType.EMBEDDING] == State.REVOKE:\n                    return True\n                return False\n\n            # 批量向量化\n            VectorStore.get_embedding_vector().batch_save(data_list, embedding_model, is_the_task_interrupted)\n            # 更新到开始状态\n            ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph_id), TaskType.EMBEDDING,\n                                             State.SUCCESS)\n        except Exception as e:\n            maxkb_logger.error(_('Vectorized paragraph: {paragraph_id} error {error} {traceback}').format(\n                paragraph_id=paragraph_id, error=str(e), traceback=traceback.format_exc()))\n            ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph_id), TaskType.EMBEDDING,\n                                             State.FAILURE)\n        finally:\n            maxkb_logger.info(_('End--->Embedding paragraph: {paragraph_id}').format(paragraph_id=paragraph_id))\n\n    @staticmethod\n    def embedding_by_data_list(data_list: List, embedding_model: Embeddings):\n        # 批量向量化\n        VectorStore.get_embedding_vector().batch_save(data_list, embedding_model, lambda: False)\n\n    @staticmethod\n    def get_embedding_paragraph_apply(embedding_model, is_the_task_interrupted, post_apply=lambda: None):\n        def embedding_paragraph_apply(paragraph_list):\n            for paragraph in paragraph_list:\n                if is_the_task_interrupted():\n                    break\n                ListenerManagement.embedding_by_paragraph(str(paragraph.get('id')), embedding_model)\n            post_apply()\n\n        return embedding_paragraph_apply\n\n    @staticmethod\n    def get_aggregation_document_status(document_id):\n        def aggregation_document_status():\n            sql = get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'update_document_status_meta.sql'))\n            native_update({'document_custom_sql': QuerySet(Document).filter(id=document_id)}, sql, with_table_name=True)\n\n        return aggregation_document_status\n\n    @staticmethod\n    def get_aggregation_document_status_by_knowledge_id(knowledge_id):\n        def aggregation_document_status():\n            sql = get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'update_document_status_meta.sql'))\n            native_update({'document_custom_sql': QuerySet(Document).filter(knowledge_id=knowledge_id)}, sql,\n                          with_table_name=True)\n\n        return aggregation_document_status\n\n    @staticmethod\n    def get_aggregation_document_status_by_query_set(queryset):\n        def aggregation_document_status():\n            sql = get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'update_document_status_meta.sql'))\n            native_update({'document_custom_sql': queryset}, sql, with_table_name=True)\n\n        return aggregation_document_status\n\n    @staticmethod\n    def post_update_document_status(document_id, task_type: TaskType):\n        _document = QuerySet(Document).filter(id=document_id).first()\n\n        status = Status(_document.status)\n        if status[task_type] == State.REVOKE:\n            status[task_type] = State.REVOKED\n        else:\n            status[task_type] = State.SUCCESS\n        for item in _document.status_meta.get('aggs', []):\n            agg_status = item.get('status')\n            agg_count = item.get('count')\n            if Status(agg_status)[task_type] == State.FAILURE and agg_count > 0:\n                status[task_type] = State.FAILURE\n        ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), task_type, status[task_type])\n\n        ListenerManagement.update_status(QuerySet(Paragraph).annotate(\n            reversed_status=Reverse('status'),\n            task_type_status=Substr('reversed_status', task_type.value,\n                                    task_type.value),\n        ).filter(task_type_status=State.REVOKE.value).filter(document_id=document_id).values('id'),\n                                         task_type,\n                                         State.REVOKED)\n\n    @staticmethod\n    def update_status(query_set: QuerySet, taskType: TaskType, state: State):\n        exec_sql = get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'update_paragraph_status.sql'))\n        bit_number = len(TaskType)\n        up_index = taskType.value - 1\n        next_index = taskType.value + 1\n        current_index = taskType.value\n        status_number = state.value\n        current_time = timezone.now().astimezone(timezone.get_default_timezone()).strftime('%Y-%m-%d %H:%M:%S.%f%z')\n        params_dict = {'${bit_number}': bit_number, '${up_index}': up_index,\n                       '${status_number}': status_number, '${next_index}': next_index,\n                       '${table_name}': query_set.model._meta.db_table, '${current_index}': current_index,\n                       '${current_time}': current_time}\n        for key in params_dict:\n            _value_ = params_dict[key]\n            exec_sql = exec_sql.replace(key, str(_value_))\n        lock.acquire()\n        try:\n            native_update(query_set, exec_sql)\n        finally:\n            lock.release()\n\n    @staticmethod\n    def embedding_by_document(document_id, embedding_model: Embeddings, state_list=None):\n        \"\"\"\n        向量化文档\n        @param state_list:\n        @param document_id: 文档id\n        @param embedding_model 向量模型\n        :return: None\n        \"\"\"\n        if state_list is None:\n            state_list = [State.PENDING, State.SUCCESS, State.FAILURE, State.REVOKE, State.REVOKED]\n        rlock = RedisLock()\n        if not rlock.try_lock('embedding:' + str(document_id)):\n            return\n        try:\n            def is_the_task_interrupted():\n                document = QuerySet(Document).filter(id=document_id).first()\n                if document is None or Status(document.status)[TaskType.EMBEDDING] == State.REVOKE:\n                    return True\n                return False\n\n            if is_the_task_interrupted():\n                return\n            maxkb_logger.info(_('Start--->Embedding document: {document_id}').format(document_id=document_id)\n                              )\n            # 批量修改状态为PADDING\n            ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), TaskType.EMBEDDING,\n                                             State.STARTED)\n\n            # 根据段落进行向量化处理\n            page_desc(QuerySet(Paragraph)\n                      .annotate(\n                reversed_status=Reverse('status'),\n                task_type_status=Substr('reversed_status', TaskType.EMBEDDING.value,\n                                        1),\n            ).filter(task_type_status__in=state_list, document_id=document_id)\n                      .values('id'), 5,\n                      ListenerManagement.get_embedding_paragraph_apply(embedding_model, is_the_task_interrupted,\n                                                                       ListenerManagement.get_aggregation_document_status(\n                                                                           document_id)),\n                      is_the_task_interrupted)\n            # 检查是否存在索引\n            create_knowledge_index(document_id=document_id)\n        except Exception as e:\n            maxkb_logger.error(_('Vectorized document: {document_id} error {error} {traceback}').format(\n                document_id=document_id, error=str(e), traceback=traceback.format_exc()))\n        finally:\n            ListenerManagement.post_update_document_status(document_id, TaskType.EMBEDDING)\n            ListenerManagement.get_aggregation_document_status(document_id)()\n            maxkb_logger.info(_('End--->Embedding document: {document_id}').format(document_id=document_id))\n            rlock.un_lock('embedding:' + str(document_id))\n\n    @staticmethod\n    def embedding_by_knowledge(knowledge_id, embedding_model: Embeddings):\n        \"\"\"\n        向量化知识库\n        @param knowledge_id: 知识库id\n        @param embedding_model 向量模型\n        :return: None\n        \"\"\"\n        maxkb_logger.info(_('Start--->Embedding knowledge: {knowledge_id}').format(knowledge_id=knowledge_id))\n        try:\n            ListenerManagement.delete_embedding_by_knowledge(knowledge_id)\n            document_list = QuerySet(Document).filter(knowledge_id=knowledge_id)\n            maxkb_logger.info(_('Start--->Embedding document: {document_list}').format(document_list=document_list))\n            for document in document_list:\n                ListenerManagement.embedding_by_document(document.id, embedding_model=embedding_model)\n        except Exception as e:\n            maxkb_logger.error(_('Vectorized knowledge: {knowledge_id} error {error} {traceback}').format(\n                knowledge_id=knowledge_id, error=str(e), traceback=traceback.format_exc()))\n        finally:\n            maxkb_logger.info(_('End--->Embedding knowledge: {knowledge_id}').format(knowledge_id=knowledge_id))\n\n    @staticmethod\n    def delete_embedding_by_document(document_id):\n        VectorStore.get_embedding_vector().delete_by_document_id(document_id)\n\n    @staticmethod\n    def delete_embedding_by_document_list(document_id_list: List[str]):\n        VectorStore.get_embedding_vector().delete_by_document_id_list(document_id_list)\n\n    @staticmethod\n    def delete_embedding_by_knowledge(knowledge_id):\n        VectorStore.get_embedding_vector().delete_by_knowledge_id(knowledge_id)\n\n    @staticmethod\n    def delete_embedding_by_paragraph(paragraph_id):\n        VectorStore.get_embedding_vector().delete_by_paragraph_id(paragraph_id)\n\n    @staticmethod\n    def delete_embedding_by_source(source_id):\n        VectorStore.get_embedding_vector().delete_by_source_id(source_id, SourceType.PROBLEM)\n\n    @staticmethod\n    def disable_embedding_by_paragraph(paragraph_id):\n        VectorStore.get_embedding_vector().update_by_paragraph_id(paragraph_id, {'is_active': False})\n\n    @staticmethod\n    def enable_embedding_by_paragraph(paragraph_id):\n        VectorStore.get_embedding_vector().update_by_paragraph_id(paragraph_id, {'is_active': True})\n\n    @staticmethod\n    def update_problem(args: UpdateProblemArgs):\n        problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(problem_id=args.problem_id)\n        embed_value = args.embedding_model.embed_query(args.problem_content)\n        VectorStore.get_embedding_vector().update_by_source_ids([v.id for v in problem_paragraph_mapping_list],\n                                                                {'embedding': embed_value})\n\n    @staticmethod\n    def update_embedding_knowledge_id(args: UpdateEmbeddingKnowledgeIdArgs):\n        VectorStore.get_embedding_vector().update_by_paragraph_ids(args.paragraph_id_list,\n                                                                   {'knowledge_id': args.target_knowledge_id})\n\n    @staticmethod\n    def update_embedding_document_id(args: UpdateEmbeddingDocumentIdArgs):\n        if args.target_embedding_model is None:\n            VectorStore.get_embedding_vector().update_by_paragraph_ids(args.paragraph_id_list,\n                                                                       {'document_id': args.target_document_id,\n                                                                        'knowledge_id': args.target_knowledge_id})\n        else:\n            ListenerManagement.embedding_by_paragraph_list(args.paragraph_id_list,\n                                                           embedding_model=args.target_embedding_model)\n\n    @staticmethod\n    def delete_embedding_by_source_ids(source_ids: List[str]):\n        VectorStore.get_embedding_vector().delete_by_source_ids(source_ids, SourceType.PROBLEM)\n\n    @staticmethod\n    def delete_embedding_by_paragraph_ids(paragraph_ids: List[str]):\n        VectorStore.get_embedding_vector().delete_by_paragraph_ids(paragraph_ids)\n\n    @staticmethod\n    def delete_embedding_by_knowledge_id_list(source_ids: List[str]):\n        VectorStore.get_embedding_vector().delete_by_knowledge_id_list(source_ids)\n\n    @staticmethod\n    def hit_test(query_text, knowledge_id: list[str], exclude_document_id_list: list[str], top_number: int,\n                 similarity: float,\n                 search_mode: SearchMode,\n                 embedding: Embeddings):\n        return VectorStore.get_embedding_vector().hit_test(query_text, knowledge_id, exclude_document_id_list,\n                                                           top_number,\n                                                           similarity, search_mode, embedding)\n"
  },
  {
    "path": "apps/common/exception/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/exception/app_exception.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎虎\n    @file： app_exception.py\n    @date：2023/9/4 14:04\n    @desc:\n\"\"\"\nfrom rest_framework import status\n\n\nclass AppApiException(Exception):\n    \"\"\"\n    项目内异常\n    \"\"\"\n    status_code = status.HTTP_200_OK\n\n    def __init__(self, code, message):\n        self.code = code\n        self.message = message\n\n\nclass NotFound404(AppApiException):\n    \"\"\"\n       未认证(未登录)异常\n       \"\"\"\n    status_code = status.HTTP_404_NOT_FOUND\n\n    def __init__(self, code, message):\n        self.code = code\n        self.message = message\n\n\nclass AppAuthenticationFailed(AppApiException):\n    \"\"\"\n    未认证(未登录)异常\n    \"\"\"\n    status_code = status.HTTP_401_UNAUTHORIZED\n\n    def __init__(self, code, message):\n        self.code = code\n        self.message = message\n\n\nclass AppUnauthorizedFailed(AppApiException):\n    \"\"\"\n    未授权(没有权限)异常\n    \"\"\"\n    status_code = status.HTTP_403_FORBIDDEN\n\n    def __init__(self, code, message):\n        self.code = code\n        self.message = message\n\n\nclass AppEmbedIdentityFailed(AppApiException):\n    \"\"\"\n    嵌入cookie异常\n    \"\"\"\n    status_code = 460\n\n    def __init__(self, code, message):\n        self.code = code\n        self.message = message\n\n\nclass AppChatNumOutOfBoundsFailed(AppApiException):\n    \"\"\"\n      访问次数超过今日访问量\n    \"\"\"\n    status_code = 461\n\n    def __init__(self, code, message):\n        self.code = code\n        self.message = message\n\n\nclass ChatException(AppApiException):\n    status_code = 500\n\n    def __init__(self, code, message):\n        self.code = code\n        self.message = message\n"
  },
  {
    "path": "apps/common/exception/handle_exception.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎虎\n    @file： handle_exception.py\n    @date：2023/9/5 19:29\n    @desc:\n\"\"\"\nimport logging\nimport traceback\n\nfrom rest_framework.exceptions import ValidationError, ErrorDetail, APIException\nfrom rest_framework.utils.serializer_helpers import ReturnDict\nfrom rest_framework.views import exception_handler\n\nfrom common import result\nfrom common.exception.app_exception import AppApiException\n\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.utils.logger import maxkb_logger\n\n\ndef to_result(key, args, parent_key=None):\n    \"\"\"\n    将校验异常 args转换为统一数据\n    :param key:       校验key\n    :param args:      校验异常参数\n    :param parent_key 父key\n    :return: 接口响应对象\n    \"\"\"\n    error_detail = list(filter(\n        lambda d: True if isinstance(d, ErrorDetail) else True if isinstance(d, dict) and len(\n            d.keys()) > 0 else False,\n        (args[0] if len(args) > 0 else {key: [ErrorDetail(_('Unknown exception'), code='unknown')]}).get(key)))[0]\n\n    if isinstance(error_detail, dict):\n        return list(map(lambda k: to_result(k, args=[error_detail],\n                                            parent_key=key if parent_key is None else parent_key + '.' + key),\n                        error_detail.keys() if len(error_detail) > 0 else []))[0]\n\n    return result.Result(500 if isinstance(error_detail.code, str) else error_detail.code,\n                         message=f\"【{key if parent_key is None else parent_key + '.' + key}】为必填参数\" if str(\n                             error_detail) == \"This field is required.\" else error_detail)\n\n\ndef validation_error_to_result(exc: ValidationError):\n    \"\"\"\n    校验异常转响应对象\n    :param exc: 校验异常\n    :return: 接口响应对象\n    \"\"\"\n    try:\n        v = find_err_detail(exc.detail)\n        if v is None:\n            return result.error(str(exc.detail))\n        return result.error(str(v))\n    except Exception as e:\n        return result.error(str(exc.detail))\n\n\ndef find_err_detail(exc_detail):\n    if isinstance(exc_detail, ErrorDetail):\n        return exc_detail\n    if isinstance(exc_detail, dict):\n        keys = exc_detail.keys()\n        for key in keys:\n            _label = get_label(key, exc_detail)\n            _value = exc_detail[key]\n            if isinstance(_value, list):\n                for v in _value:\n                    r = find_err_detail(ReturnDict({key: v}, serializer=exc_detail.serializer))\n                    if r is not None:\n                        return r\n            if isinstance(_value, ErrorDetail):\n                return f\"{_label}:{find_err_detail(_value)}\"\n            if isinstance(_value, dict) and len(_value.keys()) > 0:\n                try:\n                    return find_err_detail(ReturnDict(_value, serializer=exc_detail.serializer.fields[key]))\n                except Exception as e:\n                    return _value\n    if isinstance(exc_detail, list):\n        for v in exc_detail:\n            r = find_err_detail(ReturnDict(v, serializer=exc_detail.serializer.child))\n            if r is not None:\n                return r\n\n\ndef get_label(key, exc_detail):\n    try:\n        return exc_detail.serializer.fields[key].label\n    except Exception as e:\n        return key\n\n\ndef handle_exception(exc, context):\n    exception_class = exc.__class__\n    # 先调用REST framework默认的异常处理方法获得标准错误响应对象\n    response = exception_handler(exc, context)\n    # 在此处补充自定义的异常处理\n    if issubclass(exception_class, ValidationError):\n        return validation_error_to_result(exc)\n    if issubclass(exception_class, AppApiException):\n        return result.Result(exc.code, exc.message, response_status=exc.status_code)\n    if issubclass(exception_class, APIException):\n        return result.error(exc.detail)\n    if response is None:\n        maxkb_logger.error(f'{str(exc)}:{traceback.format_exc()}')\n        return result.error(str(exc))\n    return response\n"
  },
  {
    "path": "apps/common/field/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/field/common.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： common.py\n    @date：2024/1/11 18:44\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import extend_schema_field\nfrom rest_framework import serializers\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass ObjectField(serializers.Field):\n    def __init__(self, model_type_list, **kwargs):\n        self.model_type_list = model_type_list\n        super().__init__(**kwargs)\n\n    def to_internal_value(self, data):\n        for model_type in self.model_type_list:\n            if isinstance(data, model_type):\n                return data\n        self.fail(_('Message type error'), value=data)\n\n    def to_representation(self, value):\n        return value\n\n\nclass InstanceField(serializers.Field):\n    def __init__(self, model_type, **kwargs):\n        self.model_type = model_type\n        super().__init__(**kwargs)\n\n    def to_internal_value(self, data):\n        if not isinstance(data, self.model_type):\n            self.fail(_('Message type error'), value=data)\n        return data\n\n    def to_representation(self, value):\n        return value\n\n\nclass FunctionField(serializers.Field):\n\n    def to_internal_value(self, data):\n        if not callable(data):\n            self.fail(_('not a function'), value=data)\n        return data\n\n    def to_representation(self, value):\n        return value\n\n@extend_schema_field(OpenApiTypes.BINARY)\nclass UploadedImageField(serializers.ImageField):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n\n    def to_representation(self, value):\n        return value\n\nclass UploadedFileField(serializers.FileField):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n\n    def to_representation(self, value):\n        return value\n"
  },
  {
    "path": "apps/common/forms/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2023/10/31 17:56\n    @desc:\n\"\"\"\nfrom .array_object_card import *\nfrom .base_field import *\nfrom .base_form import *\nfrom .multi_select import *\nfrom .object_card import *\nfrom .password_input import *\nfrom .radio_field import *\nfrom .single_select_field import *\nfrom .tab_card import *\nfrom .table_radio import *\nfrom .text_input_field import *\nfrom .radio_button_field import *\nfrom .table_checkbox import *\nfrom .radio_card_field import *\nfrom .label import *\nfrom .slider_field import *\n"
  },
  {
    "path": "apps/common/forms/array_object_card.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： array_object_card.py\n    @date：2023/10/31 18:03\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom common.forms.base_field import BaseExecField, TriggerType\n\n\nclass ArrayCard(BaseExecField):\n    \"\"\"\n    收集List[Object]\n    \"\"\"\n\n    def __init__(self,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 provider: str,\n                 method: str,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        super().__init__(\"ArrayObjectCard\", label, text_field, value_field, provider, method, required, default_value,\n                         relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info)\n"
  },
  {
    "path": "apps/common/forms/base_field.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_field.py\n    @date：2023/10/31 18:07\n    @desc:\n\"\"\"\nfrom enum import Enum\nfrom typing import List, Dict\n\nfrom common.exception.app_exception import AppApiException\nfrom common.forms.label.base_label import BaseLabel\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass TriggerType(Enum):\n    # 执行函数获取 OptionList数据\n    OPTION_LIST = 'OPTION_LIST'\n    # 执行函数获取子表单\n    CHILD_FORMS = 'CHILD_FORMS'\n\n\nclass BaseField:\n    def __init__(self,\n                 input_type: str,\n                 label: str or BaseLabel,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        \"\"\"\n\n        :param input_type: 字段\n        :param label: 提示\n        :param default_value: 默认值\n        :param relation_show_field_dict:        {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才显示\n        :param relation_trigger_field_dict:     {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才 执行函数获取 数据\n        :param trigger_type:                    执行器类型  OPTION_LIST请求Option_list数据 CHILD_FORMS请求子表单\n        :param attrs:                           前端attr数据\n        :param props_info:                      其他额外信息\n        \"\"\"\n        if props_info is None:\n            props_info = {}\n        if attrs is None:\n            attrs = {}\n        self.label = label\n        self.attrs = attrs\n        self.props_info = props_info\n        self.default_value = default_value\n        self.input_type = input_type\n        self.relation_show_field_dict = {} if relation_show_field_dict is None else relation_show_field_dict\n        self.relation_trigger_field_dict = [] if relation_trigger_field_dict is None else relation_trigger_field_dict\n        self.required = required\n        self.trigger_type = trigger_type\n\n    def is_valid(self, value):\n        field_label = self.label.label if hasattr(self.label, 'to_dict') else self.label\n        if self.required and value is None:\n            raise AppApiException(500,\n                                  _('The field {field_label} is required').format(field_label=field_label))\n\n    def to_dict(self, **kwargs):\n        return {\n            'input_type': self.input_type,\n            'label': self.label.to_dict(**kwargs) if hasattr(self.label, 'to_dict') else self.label,\n            'required': self.required,\n            'default_value': self.default_value,\n            'relation_show_field_dict': self.relation_show_field_dict,\n            'relation_trigger_field_dict': self.relation_trigger_field_dict,\n            'trigger_type': self.trigger_type.value,\n            'attrs': self.attrs,\n            'props_info': self.props_info,\n            **kwargs\n        }\n\n\nclass BaseDefaultOptionField(BaseField):\n    def __init__(self, input_type: str,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 option_list: List[dict],\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict[str, object] = None,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        \"\"\"\n\n        :param input_type:           字段\n        :param label:           label\n        :param text_field:      文本字段\n        :param value_field:     值字段\n        :param option_list:     可选列表\n        :param required:        是否必填\n        :param default_value:   默认值\n        :param relation_show_field_dict:        {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才显示\n        :param attrs:                           前端attr数据\n        :param props_info:                      其他额外信息\n        \"\"\"\n        super().__init__(input_type, label, required, default_value, relation_show_field_dict,\n                         {}, TriggerType.OPTION_LIST, attrs, props_info)\n        self.text_field = text_field\n        self.value_field = value_field\n        self.option_list = option_list\n\n    def to_dict(self, **kwargs):\n        return {**super().to_dict(**kwargs), 'text_field': self.text_field, 'value_field': self.value_field,\n                'option_list': self.option_list}\n\n\nclass BaseExecField(BaseField):\n    def __init__(self,\n                 input_type: str,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 provider: str,\n                 method: str,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        \"\"\"\n\n        :param input_type:  字段\n        :param label:  提示\n        :param text_field:  文本字段\n        :param value_field: 值字段\n        :param provider:    指定供应商\n        :param method:      执行供应商函数 method\n        :param required:    是否必填\n        :param default_value: 默认值\n        :param relation_show_field_dict:        {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才显示\n        :param relation_trigger_field_dict:     {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才 执行函数获取 数据\n        :param trigger_type:                    执行器类型  OPTION_LIST请求Option_list数据 CHILD_FORMS请求子表单\n        :param attrs:                           前端attr数据\n        :param props_info:                      其他额外信息\n        \"\"\"\n        super().__init__(input_type, label, required, default_value, relation_show_field_dict,\n                         relation_trigger_field_dict,\n                         trigger_type, attrs, props_info)\n        self.text_field = text_field\n        self.value_field = value_field\n        self.provider = provider\n        self.method = method\n\n    def to_dict(self, **kwargs):\n        return {**super().to_dict(**kwargs), 'text_field': self.text_field, 'value_field': self.value_field,\n                'provider': self.provider, 'method': self.method}\n"
  },
  {
    "path": "apps/common/forms/base_form.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_form.py\n    @date：2023/11/1 16:04\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom common.forms import BaseField\n\n\nclass BaseForm:\n    def to_form_list(self, **kwargs):\n        return [{**self.__getattribute__(key).to_dict(**kwargs), 'field': key} for key in\n                list(filter(lambda key: isinstance(self.__getattribute__(key), BaseField),\n                            [attr for attr in vars(self.__class__) if not attr.startswith(\"__\")]))]\n\n    def valid_form(self, form_data):\n        field_keys = list(filter(lambda key: isinstance(self.__getattribute__(key), BaseField),\n                                 [attr for attr in vars(self.__class__) if not attr.startswith(\"__\")]))\n        for field_key in field_keys:\n            self.__getattribute__(field_key).is_valid(form_data.get(field_key))\n\n    def get_default_form_data(self):\n        return {key: self.__getattribute__(key).default_value for key in\n                [attr for attr in vars(self.__class__) if not attr.startswith(\"__\")] if\n                isinstance(self.__getattribute__(key), BaseField) and self.__getattribute__(\n                    key).default_value is not None}\n"
  },
  {
    "path": "apps/common/forms/label/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/8/22 17:19\n    @desc:\n\"\"\"\nfrom .base_label import *\nfrom .tooltip_label import *\n"
  },
  {
    "path": "apps/common/forms/label/base_label.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： base_label.py\n    @date：2024/8/22 17:11\n    @desc:\n\"\"\"\n\n\nclass BaseLabel:\n    def __init__(self,\n                 input_type: str,\n                 label: str,\n                 attrs=None,\n                 props_info=None):\n        self.input_type = input_type\n        self.label = label\n        self.attrs = attrs\n        self.props_info = props_info\n\n    def to_dict(self, **kwargs):\n        return {\n            'input_type': self.input_type,\n            'label': self.label,\n            'attrs': {} if self.attrs is None else self.attrs,\n            'props_info': {} if self.props_info is None else self.props_info,\n        }\n"
  },
  {
    "path": "apps/common/forms/label/tooltip_label.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： tooltip_label.py\n    @date：2024/8/22 17:19\n    @desc:\n\"\"\"\nfrom common.forms.label.base_label import BaseLabel\n\n\nclass TooltipLabel(BaseLabel):\n    def __init__(self, label, tooltip):\n        super().__init__('TooltipLabel', label, attrs={'tooltip': tooltip}, props_info={})\n"
  },
  {
    "path": "apps/common/forms/multi_select.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： multi_select.py\n    @date：2023/10/31 18:00\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom common.forms.base_field import BaseExecField, TriggerType\n\n\nclass MultiSelect(BaseExecField):\n    \"\"\"\n    下拉单选\n    \"\"\"\n\n    def __init__(self,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 option_list: List[str:object],\n                 provider: str = None,\n                 method: str = None,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        super().__init__(\"MultiSelect\", label, text_field, value_field, provider, method, required, default_value,\n                         relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info)\n        self.option_list = option_list\n\n    def to_dict(self):\n        return {**super().to_dict(), 'option_list': self.option_list}\n"
  },
  {
    "path": "apps/common/forms/object_card.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： object_card.py\n    @date：2023/10/31 18:02\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom common.forms.base_field import BaseExecField, TriggerType\n\n\nclass ObjectCard(BaseExecField):\n    \"\"\"\n    收集对象子表卡片\n    \"\"\"\n\n    def __init__(self,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 provider: str,\n                 method: str,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        super().__init__(\"ObjectCard\", label, text_field, value_field, provider, method, required, default_value,\n                         relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info)\n"
  },
  {
    "path": "apps/common/forms/password_input.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： password_input.py\n    @date：2023/11/1 14:48\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom common.forms import BaseField, TriggerType\n\n\nclass PasswordInputField(BaseField):\n    \"\"\"\n    文本输入框\n    \"\"\"\n\n    def __init__(self, label: str,\n                 required: bool = False,\n                 default_value=None,\n                 relation_show_field_dict: Dict = None,\n                 attrs=None, props_info=None):\n        super().__init__('PasswordInput', label, required, default_value, relation_show_field_dict,\n                         {},\n                         TriggerType.OPTION_LIST, attrs, props_info)\n"
  },
  {
    "path": "apps/common/forms/radio_button_field.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： radio_field.py\n    @date：2023/10/31 17:59\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom common.forms.base_field import BaseExecField, TriggerType\n\n\nclass RadioButton(BaseExecField):\n    \"\"\"\n    下拉单选\n    \"\"\"\n\n    def __init__(self,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 option_list: List[str:object],\n                 provider: str,\n                 method: str,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        super().__init__(\"RadioButton\", label, text_field, value_field, provider, method, required, default_value,\n                         relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info)\n        self.option_list = option_list\n\n    def to_dict(self):\n        return {**super().to_dict(), 'option_list': self.option_list}\n"
  },
  {
    "path": "apps/common/forms/radio_card_field.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： radio_field.py\n    @date：2023/10/31 17:59\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom common.forms.base_field import BaseExecField, TriggerType\n\n\nclass RadioCard(BaseExecField):\n    \"\"\"\n    下拉单选\n    \"\"\"\n\n    def __init__(self,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 option_list: List[str:object],\n                 provider: str,\n                 method: str,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        super().__init__(\"RadioCard\", label, text_field, value_field, provider, method, required, default_value,\n                         relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info)\n        self.option_list = option_list\n\n    def to_dict(self):\n        return {**super().to_dict(), 'option_list': self.option_list}\n"
  },
  {
    "path": "apps/common/forms/radio_field.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： radio_field.py\n    @date：2023/10/31 17:59\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom common.forms.base_field import BaseExecField, TriggerType\n\n\nclass Radio(BaseExecField):\n    \"\"\"\n    下拉单选\n    \"\"\"\n\n    def __init__(self,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 option_list: List[str:object],\n                 provider: str,\n                 method: str,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        super().__init__(\"Radio\", label, text_field, value_field, provider, method, required, default_value,\n                         relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info)\n        self.option_list = option_list\n\n    def to_dict(self):\n        return {**super().to_dict(), 'option_list': self.option_list}\n"
  },
  {
    "path": "apps/common/forms/single_select_field.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： single_select_field.py\n    @date：2023/10/31 18:00\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom common.forms import BaseLabel\nfrom common.forms.base_field import TriggerType, BaseExecField\n\n\nclass SingleSelect(BaseExecField):\n    \"\"\"\n    下拉单选\n    \"\"\"\n\n    def __init__(self,\n                 label: str or BaseLabel,\n                 text_field: str,\n                 value_field: str,\n                 option_list: List[str:object],\n                 provider: str = None,\n                 method: str = None,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        super().__init__(\"SingleSelect\", label, text_field, value_field, provider, method, required, default_value,\n                         relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info)\n        self.option_list = option_list\n\n    def to_dict(self):\n        return {**super().to_dict(), 'option_list': self.option_list}\n"
  },
  {
    "path": "apps/common/forms/slider_field.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： slider_field.py\n    @date：2024/8/22 17:06\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseField, TriggerType, BaseLabel\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass SliderField(BaseField):\n    \"\"\"\n    滑块输入框\n    \"\"\"\n\n    def __init__(self, label: str or BaseLabel,\n                 _min,\n                 _max,\n                 _step,\n                 precision,\n                 required: bool = False,\n                 default_value=None,\n                 relation_show_field_dict: Dict = None,\n                 attrs=None, props_info=None):\n        \"\"\"\n        @param label: 提示\n        @param _min:  最小值\n        @param _max:  最大值\n        @param _step: 步长\n        @param precision: 保留多少小数\n        @param required:  是否必填\n        @param default_value: 默认值\n        @param relation_show_field_dict:\n        @param attrs:\n        @param props_info:\n        \"\"\"\n        _attrs = {'min': _min, 'max': _max, 'step': _step,\n                  'precision': precision, 'show-input-controls': False, 'show-input': True}\n        if attrs is not None:\n            _attrs.update(attrs)\n        super().__init__('Slider', label, required, default_value, relation_show_field_dict,\n                         {},\n                         TriggerType.OPTION_LIST, _attrs, props_info)\n\n    def is_valid(self, value):\n        super().is_valid(value)\n        field_label = self.label.label if hasattr(self.label, 'to_dict') else self.label\n        if value is not None:\n            if value < self.attrs.get('min'):\n                raise AppApiException(500,\n                                      _(\"The {field_label} cannot be less than {min}\").format(field_label=field_label,\n                                                                                              min=self.attrs.get(\n                                                                                                  'min')))\n\n            if value > self.attrs.get('max'):\n                raise AppApiException(500,\n                                      _(\"The {field_label} cannot be greater than {max}\").format(\n                                          field_label=field_label,\n                                          max=self.attrs.get(\n                                              'max')))\n"
  },
  {
    "path": "apps/common/forms/switch_field.py",
    "content": "\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： switch_field.py\n    @date：2024/10/13 19:43\n    @desc:\n\"\"\"\nfrom typing import Dict\nfrom common.forms import BaseField, TriggerType, BaseLabel\n\n\nclass SwitchField(BaseField):\n    \"\"\"\n    滑块输入框\n    \"\"\"\n\n    def __init__(self, label: str or BaseLabel,\n                 required: bool = False,\n                 default_value=None,\n                 relation_show_field_dict: Dict = None,\n\n                 attrs=None, props_info=None):\n        \"\"\"\n        @param required:  是否必填\n        @param default_value: 默认值\n        @param relation_show_field_dict:\n        @param attrs:\n        @param props_info:\n        \"\"\"\n\n        super().__init__('SwitchInput', label, required, default_value, relation_show_field_dict,\n                         {},\n                         TriggerType.OPTION_LIST, attrs, props_info)\n"
  },
  {
    "path": "apps/common/forms/tab_card.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： tab_card.py\n    @date：2023/10/31 18:03\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom common.forms.base_field import BaseExecField, TriggerType\n\n\nclass TabCard(BaseExecField):\n    \"\"\"\n    收集 Tab类型数据 tab1:{},tab2:{}\n    \"\"\"\n\n    def __init__(self,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 provider: str,\n                 method: str,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        super().__init__(\"TabCard\", label, text_field, value_field, provider, method, required, default_value,\n                         relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info)\n"
  },
  {
    "path": "apps/common/forms/table_checkbox.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： table_radio.py\n    @date：2023/10/31 18:01\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom common.forms.base_field import TriggerType, BaseExecField\n\n\nclass TableRadio(BaseExecField):\n    \"\"\"\n    table 单选\n    \"\"\"\n\n    def __init__(self,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 provider: str,\n                 method: str,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        super().__init__(\"TableCheckbox\", label, text_field, value_field, provider, method, required, default_value,\n                         relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info)\n"
  },
  {
    "path": "apps/common/forms/table_radio.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： table_radio.py\n    @date：2023/10/31 18:01\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom common.forms.base_field import TriggerType, BaseExecField\n\n\nclass TableRadio(BaseExecField):\n    \"\"\"\n    table 单选\n    \"\"\"\n\n    def __init__(self,\n                 label: str,\n                 text_field: str,\n                 value_field: str,\n                 provider: str,\n                 method: str,\n                 required: bool = False,\n                 default_value: object = None,\n                 relation_show_field_dict: Dict = None,\n                 relation_trigger_field_dict: Dict = None,\n                 trigger_type: TriggerType = TriggerType.OPTION_LIST,\n                 attrs: Dict[str, object] = None,\n                 props_info: Dict[str, object] = None):\n        super().__init__(\"TableRadio\", label, text_field, value_field, provider, method, required, default_value,\n                         relation_show_field_dict, relation_trigger_field_dict, trigger_type, attrs, props_info)\n"
  },
  {
    "path": "apps/common/forms/text_input_field.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： text_input_field.py\n    @date：2023/10/31 17:58\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom common.forms import BaseLabel\nfrom common.forms.base_field import BaseField, TriggerType\n\n\nclass TextInputField(BaseField):\n    \"\"\"\n    文本输入框\n    \"\"\"\n\n    def __init__(self, label: str or BaseLabel,\n                 required: bool = False,\n                 default_value=None,\n                 relation_show_field_dict: Dict = None,\n\n                 attrs=None, props_info=None):\n        super().__init__('TextInput', label, required, default_value, relation_show_field_dict,\n                         {},\n                         TriggerType.OPTION_LIST, attrs, props_info)\n"
  },
  {
    "path": "apps/common/handle/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎\n    @file： __init__.py.py\n    @date：2023/9/6 10:09\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/common/handle/base_parse_qa_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_parse_qa_handle.py\n    @date：2024/5/21 14:56\n    @desc:\n\"\"\"\nfrom abc import ABC, abstractmethod\n\n\ndef get_row_value(row, title_row_index_dict, field):\n    index = title_row_index_dict.get(field)\n    if index is None:\n        return None\n    if (len(row) - 1) >= index:\n        return row[index]\n    return None\n\n\ndef get_title_row_index_dict(title_row_list):\n    title_row_index_dict = {}\n    if len(title_row_list) == 1:\n        title_row_index_dict['content'] = 0\n    elif len(title_row_list) == 1:\n        title_row_index_dict['title'] = 0\n        title_row_index_dict['content'] = 1\n    else:\n        title_row_index_dict['title'] = 0\n        title_row_index_dict['content'] = 1\n        title_row_index_dict['problem_list'] = 2\n    for index in range(len(title_row_list)):\n        title_row = title_row_list[index]\n        if title_row is None:\n            title_row = ''\n        if title_row.startswith('分段标题'):\n            title_row_index_dict['title'] = index\n        if title_row.startswith('分段内容'):\n            title_row_index_dict['content'] = index\n        if title_row.startswith('问题'):\n            title_row_index_dict['problem_list'] = index\n    return title_row_index_dict\n\n\nclass BaseParseQAHandle(ABC):\n    @abstractmethod\n    def support(self, file, get_buffer):\n        pass\n\n    @abstractmethod\n    def handle(self, file, get_buffer, save_image):\n        pass\n"
  },
  {
    "path": "apps/common/handle/base_parse_table_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_parse_qa_handle.py\n    @date：2024/5/21 14:56\n    @desc:\n\"\"\"\nfrom abc import ABC, abstractmethod\n\n\nclass BaseParseTableHandle(ABC):\n    @abstractmethod\n    def support(self, file, get_buffer):\n        pass\n\n    @abstractmethod\n    def handle(self, file, get_buffer,save_image):\n        pass\n\n    @abstractmethod\n    def get_content(self, file, save_image):\n        pass"
  },
  {
    "path": "apps/common/handle/base_split_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_split_handle.py\n    @date：2024/3/27 18:13\n    @desc:\n\"\"\"\nfrom abc import ABC, abstractmethod\nfrom typing import List\n\n\nclass BaseSplitHandle(ABC):\n    @abstractmethod\n    def support(self, file, get_buffer):\n        pass\n\n    @abstractmethod\n    def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):\n        pass\n\n    @abstractmethod\n    def get_content(self, file, save_image):\n        pass\n"
  },
  {
    "path": "apps/common/handle/base_to_response.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： base_to_response.py\n    @date：2024/9/6 16:04\n    @desc:\n\"\"\"\nfrom abc import ABC, abstractmethod\n\nfrom rest_framework import status\n\n\nclass BaseToResponse(ABC):\n\n    @abstractmethod\n    def to_block_response(self, chat_id, chat_record_id, content, is_end, completion_tokens,\n                          prompt_tokens, other_params: dict = None,\n                          _status=status.HTTP_200_OK):\n        pass\n\n    @abstractmethod\n    def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end,\n                                 completion_tokens,\n                                 prompt_tokens, other_params: dict = None):\n        pass\n\n    @staticmethod\n    def format_stream_chunk(response_str):\n        return 'data: ' + response_str + '\\n\\n'\n"
  },
  {
    "path": "apps/common/handle/handle_exception.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎\n    @file： handle_exception.py\n    @date：2023/9/5 19:29\n    @desc:\n\"\"\"\nimport logging\nimport traceback\n\nfrom rest_framework.exceptions import ValidationError, ErrorDetail, APIException\nfrom rest_framework.views import exception_handler\n\nfrom common.exception.app_exception import AppApiException\n\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.result import result\nfrom common.utils.logger import maxkb_logger\n\n\ndef to_result(key, args, parent_key=None):\n    \"\"\"\n    将校验异常 args转换为统一数据\n    :param key:       校验key\n    :param args:      校验异常参数\n    :param parent_key 父key\n    :return: 接口响应对象\n    \"\"\"\n    error_detail = list(filter(\n        lambda d: True if isinstance(d, ErrorDetail) else True if isinstance(d, dict) and len(\n            d.keys()) > 0 else False,\n        (args[0] if len(args) > 0 else {key: [ErrorDetail(_('Unknown exception'), code='unknown')]}).get(key)))[0]\n\n    if isinstance(error_detail, dict):\n        return list(map(lambda k: to_result(k, args=[error_detail],\n                                            parent_key=key if parent_key is None else parent_key + '.' + key),\n                        error_detail.keys() if len(error_detail) > 0 else []))[0]\n\n    return result.Result(500 if isinstance(error_detail.code, str) else error_detail.code,\n                         message=f\"【{key if parent_key is None else parent_key + '.' + key}】为必填参数\" if str(\n                             error_detail) == \"This field is required.\" else error_detail)\n\n\ndef validation_error_to_result(exc: ValidationError):\n    \"\"\"\n    校验异常转响应对象\n    :param exc: 校验异常\n    :return: 接口响应对象\n    \"\"\"\n    try:\n        v = find_err_detail(exc.detail)\n        if v is None:\n            return result.error(str(exc.detail))\n        return result.error(str(v))\n    except Exception as e:\n        return result.error(str(exc.detail))\n\n\ndef find_err_detail(exc_detail):\n    if isinstance(exc_detail, ErrorDetail):\n        return exc_detail\n    if isinstance(exc_detail, dict):\n        keys = exc_detail.keys()\n        for key in keys:\n            _value = exc_detail[key]\n            if isinstance(_value, list):\n                return find_err_detail(_value)\n            if isinstance(_value, ErrorDetail):\n                return _value\n            if isinstance(_value, dict) and len(_value.keys()) > 0:\n                return find_err_detail(_value)\n    if isinstance(exc_detail, list):\n        for v in exc_detail:\n            r = find_err_detail(v)\n            if r is not None:\n                return r\n\n\ndef handle_exception(exc, context):\n    exception_class = exc.__class__\n    # 先调用REST framework默认的异常处理方法获得标准错误响应对象\n    response = exception_handler(exc, context)\n    # 在此处补充自定义的异常处理\n    if issubclass(exception_class, ValidationError):\n        return validation_error_to_result(exc)\n    if issubclass(exception_class, AppApiException):\n        return result.Result(exc.code, exc.message, response_status=exc.status_code)\n    if issubclass(exception_class, APIException):\n        return result.error(exc.detail)\n    if response is None:\n        maxkb_logger.error(f'{str(exc)}:{traceback.format_exc()}')\n        return result.error(str(exc))\n    return response\n"
  },
  {
    "path": "apps/common/handle/impl/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎\n    @file： __init__.py.py\n    @date：2023/9/6 10:09\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/common/handle/impl/common_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： tools.py\n    @date：2024/9/11 16:41\n    @desc:\n\"\"\"\nimport io\nimport traceback\nfrom functools import reduce\nfrom io import BytesIO\nfrom xml.etree.ElementTree import fromstring\nfrom zipfile import ZipFile\n\nimport uuid_utils.compat as uuid\nfrom PIL import Image as PILImage\nfrom openpyxl.drawing.image import Image as openpyxl_Image\nfrom openpyxl.packaging.relationship import get_rels_path, get_dependents\nfrom openpyxl.xml.constants import SHEET_DRAWING_NS, REL_NS, SHEET_MAIN_NS\n\nfrom common.utils.logger import maxkb_logger\nfrom knowledge.models import File\n\nfrom PIL import ImageFile\nImageFile.LOAD_TRUNCATED_IMAGES = True\nPILImage.MAX_IMAGE_PIXELS = None\n\ndef parse_element(element) -> {}:\n    data = {}\n    xdr_namespace = \"{%s}\" % SHEET_DRAWING_NS\n    targets = level_order_traversal(element, xdr_namespace + \"nvPicPr\")\n    for target in targets:\n        cNvPr = embed = \"\"\n        for child in target:\n            if child.tag == xdr_namespace + \"nvPicPr\":\n                cNvPr = child[0].attrib[\"name\"]\n            elif child.tag == xdr_namespace + \"blipFill\":\n                _rel_embed = \"{%s}embed\" % REL_NS\n                embed = child[0].attrib[_rel_embed]\n        if cNvPr:\n            data[cNvPr] = embed\n    return data\n\n\ndef parse_element_sheet_xml(element) -> []:\n    data = []\n    xdr_namespace = \"{%s}\" % SHEET_MAIN_NS\n    targets = level_order_traversal(element, xdr_namespace + \"f\")\n    for target in targets:\n        for child in target:\n            if child.tag == xdr_namespace + \"f\":\n                data.append(child.text)\n    return data\n\n\ndef level_order_traversal(root, flag: str) -> []:\n    queue = [root]\n    targets = []\n    while queue:\n        node = queue.pop(0)\n        children = [child.tag for child in node]\n        if flag in children:\n            targets.append(node)\n            continue\n        for child in node:\n            queue.append(child)\n    return targets\n\n\ndef handle_images(deps, archive: ZipFile) -> []:\n    images = []\n    if not PILImage:  # Pillow not installed, drop images\n        return images\n    for dep in deps:\n        try:\n            image_io = archive.read(dep.target)\n            image = openpyxl_Image(BytesIO(image_io))\n        except Exception as e:\n            maxkb_logger.error(f\"Error reading image {dep.target}: {e}, {traceback.format_exc()}\")\n            continue\n        image.embed = dep.id  # 文件rId\n        image.target = dep.target  # 文件地址\n        images.append(image)\n    return images\n\n\ndef xlsx_embed_cells_images(buffer) -> {}:\n    archive = ZipFile(buffer)\n    # 解析cellImage.xml文件\n    deps = get_dependents(archive, get_rels_path(\"xl/cellimages.xml\"))\n    image_rel = handle_images(deps=deps, archive=archive)\n    # 工作表及其中图片ID\n    sheet_list = {}\n    for item in archive.namelist():\n        if not item.startswith('xl/worksheets/sheet'):\n            continue\n        key = item.split('/')[-1].split('.')[0].split('sheet')[-1]\n        sheet_list[key] = parse_element_sheet_xml(fromstring(archive.read(item)))\n    cell_images_xml = parse_element(fromstring(archive.read(\"xl/cellimages.xml\")))\n    cell_images_rel = {}\n    for image in image_rel:\n        cell_images_rel[image.embed] = image\n    for cnv, embed in cell_images_xml.items():\n        cell_images_xml[cnv] = cell_images_rel.get(embed)\n    result = {}\n    for key, img in cell_images_xml.items():\n        all_cells = [\n            cell\n            for _sheet_id, sheet in sheet_list.items()\n            if sheet is not None\n            for cell in sheet or []\n        ]\n\n        image_excel_id_list = [\n            cell for cell in all_cells\n            if isinstance(cell, str) and key in cell\n        ]\n        # print(key, img)\n        if img is None:\n            continue\n        if len(image_excel_id_list) > 0:\n            image_excel_id = image_excel_id_list[-1]\n            f = archive.open(img.target)\n            img_byte = io.BytesIO()\n            im = PILImage.open(f).convert('RGB')\n            im.save(img_byte, format='JPEG')\n            image = File(id=uuid.uuid7(), file_name=img.path, meta={'debug': False, 'content': img_byte.getvalue()})\n            result['=' + image_excel_id] = image\n    archive.close()\n    return result\n"
  },
  {
    "path": "apps/common/handle/impl/qa/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎\n    @file： __init__.py.py\n    @date：2023/9/6 10:09\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/common/handle/impl/qa/csv_parse_qa_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： csv_parse_qa_handle.py\n    @date：2024/5/21 14:59\n    @desc:\n\"\"\"\nimport csv\nimport io\nimport traceback\n\nfrom charset_normalizer import detect\n\nfrom common.handle.base_parse_qa_handle import BaseParseQAHandle, get_title_row_index_dict, get_row_value\nfrom common.utils.logger import maxkb_logger\n\n\ndef read_csv_standard(file_path):\n    data = []\n    with open(file_path, 'r') as file:\n        reader = csv.reader(file)\n        for row in reader:\n            data.append(row)\n    return data\n\n\nclass CsvParseQAHandle(BaseParseQAHandle):\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".csv\"):\n            return True\n        return False\n\n    def handle(self, file, get_buffer, save_image):\n        buffer = get_buffer(file)\n        try:\n            reader = csv.reader(io.TextIOWrapper(io.BytesIO(buffer), encoding=detect(buffer)['encoding']))\n            try:\n                title_row_list = reader.__next__()\n            except Exception as e:\n                return [{'name': file.name, 'paragraphs': []}]\n            if len(title_row_list) == 0:\n                return [{'name': file.name, 'paragraphs': []}]\n            title_row_index_dict = get_title_row_index_dict(title_row_list)\n            paragraph_list = []\n            for row in reader:\n                content = get_row_value(row, title_row_index_dict, 'content')\n                if content is None:\n                    continue\n                problem = get_row_value(row, title_row_index_dict, 'problem_list')\n                problem = str(problem) if problem is not None else ''\n                problem_list = [{'content': p[0:255]} for p in problem.split('\\n') if len(p.strip()) > 0]\n                title = get_row_value(row, title_row_index_dict, 'title')\n                title = str(title) if title is not None else ''\n                paragraph_list.append({'title': title[0:255],\n                                       'content': content[0:102400],\n                                       'problem_list': problem_list})\n            return [{'name': file.name, 'paragraphs': paragraph_list}]\n        except Exception as e:\n            maxkb_logger.error(f\"Error processing CSV file {file.name}: {e}, {traceback.format_exc()}\")\n            return [{'name': file.name, 'paragraphs': []}]\n"
  },
  {
    "path": "apps/common/handle/impl/qa/md_parse_qa_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： md_parse_qa_handle.py\n    @date：2024/5/21 14:59\n    @desc:\n\"\"\"\nimport re\nimport traceback\nfrom typing import Any\n\nfrom charset_normalizer import detect\n\nfrom common.handle.base_parse_qa_handle import BaseParseQAHandle, get_title_row_index_dict, get_row_value\nfrom common.utils.logger import maxkb_logger\n\n\nclass MarkdownParseQAHandle(BaseParseQAHandle):\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".md\") or file_name.endswith(\".markdown\"):\n            return True\n        return False\n\n    def parse_markdown_table(self, content):\n        \"\"\"解析 Markdown 表格,返回表格数据列表\"\"\"\n        tables = []\n        lines = content.split('\\n')\n        i = 0\n\n        while i < len(lines):\n            line = lines[i].strip()\n            # 检测表格开始(包含 | 符号)\n            if '|' in line and line.startswith('|'):\n                table_data = []\n                # 读取表头\n                header = [cell.strip() for cell in line.split('|')[1:-1]]\n                table_data.append(header)\n                i += 1\n\n                # 跳过分隔行 (例如: | --- | --- |)\n                if i < len(lines) and re.match(r'\\s*\\|[\\s\\-:]+\\|\\s*', lines[i]):\n                    i += 1\n\n                # 读取数据行\n                while i < len(lines):\n                    line = lines[i].strip()\n                    if not line.startswith('|'):\n                        break\n                    row = [self._unescape_cell_content(cell) for cell in line.split('|')[1:-1]]\n                    if len(row) > 0:\n                        table_data.append(row)\n                    i += 1\n\n                if len(table_data) > 1:  # 至少有表头和一行数据\n                    tables.append(table_data)\n            else:\n                i += 1\n\n        return tables\n\n    def _unescape_cell_content(self, cell) -> Any:\n        text = cell.strip().replace('&#124;', '|')\n        text = text.replace('|<br>|', '|\\n|')\n        return text\n\n    def handle(self, file, get_buffer, save_image):\n        buffer = get_buffer(file)\n        try:\n            # 检测编码并读取文件内容\n            encoding = detect(buffer)['encoding']\n            content = buffer.decode(encoding if encoding else 'utf-8')\n\n            # 按 sheet 分割内容\n            sheet_sections = self.split_by_sheets(content)\n\n            result = []\n\n            for sheet_name, sheet_content in sheet_sections:\n                # 解析该 sheet 的表格\n                tables = self.parse_markdown_table(sheet_content)\n\n                paragraph_list = []\n\n                # 处理每个表格\n                for table in tables:\n                    if len(table) < 2:\n                        continue\n\n                    title_row_list = table[0]\n                    title_row_index_dict = get_title_row_index_dict(title_row_list)\n\n                    # 处理表格的每一行数据\n                    for row in table[1:]:\n                        content_text = get_row_value(row, title_row_index_dict, 'content')\n                        if content_text is None:\n                            continue\n\n                        problem = get_row_value(row, title_row_index_dict, 'problem_list')\n                        problem = str(problem) if problem is not None else ''\n                        problem_list = [{'content': p[0:255]} for p in problem.split('\\n') if len(p.strip()) > 0]\n\n                        title = get_row_value(row, title_row_index_dict, 'title')\n                        title = str(title) if title is not None else ''\n\n                        paragraph_list.append({\n                            'title': title[0:255],\n                            'content': content_text[0:102400],\n                            'problem_list': problem_list\n                        })\n\n                result.append({'name': sheet_name, 'paragraphs': paragraph_list})\n\n            return result if result else [{'name': file.name, 'paragraphs': []}]\n\n        except Exception as e:\n            maxkb_logger.error(f\"Error processing Markdown file {file.name}: {e}, {traceback.format_exc()}\")\n            return [{'name': file.name, 'paragraphs': []}]\n\n    def split_by_sheets(self, content):\n        \"\"\"按二级标题(##)分割 sheet\"\"\"\n        lines = content.split('\\n')\n        sheets = []\n        current_sheet_name = None\n        current_content = []\n\n        for line in lines:\n            # 检测二级标题作为 sheet 名称\n            if line.strip().startswith('## '):\n                if current_sheet_name is not None:\n                    sheets.append((current_sheet_name, '\\n'.join(current_content)))\n                current_sheet_name = line.strip()[3:].strip()\n                current_content = []\n            else:\n                current_content.append(line)\n\n        # 添加最后一个 sheet\n        if current_sheet_name is not None:\n            sheets.append((current_sheet_name, '\\n'.join(current_content)))\n\n        # 如果没有找到 sheet 标题,返回整个内容\n        if not sheets:\n            sheets.append(('default', content))\n\n        return sheets\n"
  },
  {
    "path": "apps/common/handle/impl/qa/xls_parse_qa_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： xls_parse_qa_handle.py\n    @date：2024/5/21 14:59\n    @desc:\n\"\"\"\nimport traceback\n\nimport xlrd\n\nfrom common.handle.base_parse_qa_handle import BaseParseQAHandle, get_title_row_index_dict, get_row_value\nfrom common.utils.logger import maxkb_logger\n\n\ndef handle_sheet(file_name, sheet):\n    rows = iter([sheet.row_values(i) for i in range(sheet.nrows)])\n    try:\n        title_row_list = next(rows)\n    except Exception as e:\n        return {'name': file_name, 'paragraphs': []}\n    if len(title_row_list) == 0:\n        return {'name': file_name, 'paragraphs': []}\n    title_row_index_dict = get_title_row_index_dict(title_row_list)\n    paragraph_list = []\n    for row in rows:\n        content = get_row_value(row, title_row_index_dict, 'content')\n        if content is None:\n            continue\n        problem = get_row_value(row, title_row_index_dict, 'problem_list')\n        problem = str(problem) if problem is not None else ''\n        problem_list = [{'content': p[0:255]} for p in problem.split('\\n') if len(p.strip()) > 0]\n        title = get_row_value(row, title_row_index_dict, 'title')\n        title = str(title) if title is not None else ''\n        content = str(content)\n        paragraph_list.append({'title': title[0:255],\n                               'content': content[0:102400],\n                               'problem_list': problem_list})\n    return {'name': file_name, 'paragraphs': paragraph_list}\n\n\nclass XlsParseQAHandle(BaseParseQAHandle):\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        buffer = get_buffer(file)\n        if file_name.endswith(\".xls\") and xlrd.inspect_format(content=buffer):\n            return True\n        return False\n\n    def handle(self, file, get_buffer, save_image):\n        buffer = get_buffer(file)\n        try:\n            workbook = xlrd.open_workbook(file_contents=buffer)\n            worksheets = workbook.sheets()\n            worksheets_size = len(worksheets)\n            return [row for row in\n                    [handle_sheet(file.name,\n                                  sheet) if worksheets_size == 1 and sheet.name == 'Sheet1' else handle_sheet(\n                        sheet.name, sheet) for sheet\n                     in worksheets] if row is not None]\n        except Exception as e:\n            maxkb_logger.error(f\"Error processing XLS file {file.name}: {e}, {traceback.format_exc()}\")\n            return [{'name': file.name, 'paragraphs': []}]\n"
  },
  {
    "path": "apps/common/handle/impl/qa/xlsx_parse_qa_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： xlsx_parse_qa_handle.py\n    @date：2024/5/21 14:59\n    @desc:\n\"\"\"\nimport io\nimport traceback\n\nimport openpyxl\n\nfrom common.handle.base_parse_qa_handle import BaseParseQAHandle, get_title_row_index_dict, get_row_value\nfrom common.handle.impl.common_handle import xlsx_embed_cells_images\nfrom common.utils.logger import maxkb_logger\n\n\ndef handle_sheet(file_name, sheet, image_dict):\n    rows = sheet.rows\n    try:\n        title_row_list = next(rows)\n        title_row_list = [row.value for row in title_row_list]\n    except Exception as e:\n        return {'name': file_name, 'paragraphs': []}\n    if len(title_row_list) == 0:\n        return {'name': file_name, 'paragraphs': []}\n    title_row_index_dict = get_title_row_index_dict(title_row_list)\n    paragraph_list = []\n    for row in rows:\n        content = get_row_value(row, title_row_index_dict, 'content')\n        if content is None or content.value is None:\n            continue\n        problem = get_row_value(row, title_row_index_dict, 'problem_list')\n        problem = str(problem.value) if problem is not None and problem.value is not None else ''\n        problem_list = [{'content': p[0:255]} for p in problem.split('\\n') if len(p.strip()) > 0]\n        title = get_row_value(row, title_row_index_dict, 'title')\n        title = str(title.value) if title is not None and title.value is not None else ''\n        content = str(content.value)\n        image = image_dict.get(content, None)\n        if image is not None:\n            content = f'![](./oss/file/{image.id})'\n        paragraph_list.append({'title': title[0:255],\n                               'content': content[0:102400],\n                               'problem_list': problem_list})\n    return {'name': file_name, 'paragraphs': paragraph_list}\n\n\nclass XlsxParseQAHandle(BaseParseQAHandle):\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".xlsx\"):\n            return True\n        return False\n\n    def handle(self, file, get_buffer, save_image):\n        buffer = get_buffer(file)\n        try:\n            workbook = openpyxl.load_workbook(io.BytesIO(buffer))\n            try:\n                image_dict: dict = xlsx_embed_cells_images(io.BytesIO(buffer))\n                save_image([item for item in image_dict.values()])\n            except Exception as e:\n                image_dict = {}\n            worksheets = workbook.worksheets\n            worksheets_size = len(worksheets)\n            return [row for row in\n                    [handle_sheet(file.name,\n                                  sheet,\n                                  image_dict) if worksheets_size == 1 and sheet.title == 'Sheet1' else handle_sheet(\n                        sheet.title, sheet, image_dict) for sheet\n                     in worksheets] if row is not None]\n        except Exception as e:\n            maxkb_logger.error(f\"Error processing XLSX file {file.name}: {e}, {traceback.format_exc()}\")\n            return [{'name': file.name, 'paragraphs': []}]\n"
  },
  {
    "path": "apps/common/handle/impl/qa/zip_parse_qa_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： text_split_handle.py\n    @date：2024/3/27 18:19\n    @desc:\n\"\"\"\nimport io\nimport os\nimport re\nimport zipfile\nfrom typing import List\nfrom urllib.parse import urljoin\n\nimport uuid_utils.compat as uuid\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.handle.base_parse_qa_handle import BaseParseQAHandle\nfrom common.handle.impl.qa.csv_parse_qa_handle import CsvParseQAHandle\nfrom common.handle.impl.qa.xls_parse_qa_handle import XlsParseQAHandle\nfrom common.handle.impl.qa.xlsx_parse_qa_handle import XlsxParseQAHandle\nfrom common.utils.common import parse_md_image\nfrom knowledge.models import File\n\n\nclass FileBufferHandle:\n    buffer = None\n\n    def get_buffer(self, file):\n        if self.buffer is None:\n            self.buffer = file.read()\n        return self.buffer\n\n\nsplit_handles = [\n    XlsParseQAHandle(),\n    XlsxParseQAHandle(),\n    CsvParseQAHandle()\n]\n\n\ndef file_to_paragraph(file, save_inner_image):\n    \"\"\"\n    文件转换为段落列表\n    @param file: 文件\n    @return: {\n      name:文件名\n      paragraphs:段落列表\n    }\n    \"\"\"\n    get_buffer = FileBufferHandle().get_buffer\n    for split_handle in split_handles:\n        if split_handle.support(file, get_buffer):\n            return split_handle.handle(file, get_buffer, save_inner_image)\n    raise Exception(_(\"Unsupported file format\"))\n\n\ndef is_valid_uuid(uuid_str: str):\n    \"\"\"\n    校验字符串是否是uuid\n    @param uuid_str: 需要校验的字符串\n    @return: bool\n    \"\"\"\n    try:\n        uuid.UUID(uuid_str)\n    except ValueError:\n        return False\n    return True\n\n\ndef get_image_list(result_list: list, zip_files: List[str]):\n    \"\"\"\n    获取图片文件列表\n    @param result_list:\n    @param zip_files:\n    @return:\n    \"\"\"\n    image_file_list = []\n    for result in result_list:\n        for p in result.get('paragraphs', []):\n            content: str = p.get('content', '')\n            image_list = parse_md_image(content)\n            for image in image_list:\n                search = re.search(\"\\(.*\\)\", image)\n                if search:\n                    new_image_id = str(uuid.uuid7())\n                    source_image_path = search.group().replace('(', '').replace(')', '')\n                    image_path = urljoin(result.get('name'), '.' + source_image_path if source_image_path.startswith(\n                        '/') else source_image_path)\n                    if not zip_files.__contains__(image_path):\n                        continue\n                    if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'):\n                        image_id = image_path.replace('oss/file/', '')\n                        if is_valid_uuid(image_id):\n                            image_file_list.append({'source_file': image_path,\n                                                    'image_id': image_id})\n                        else:\n                            image_file_list.append({'source_file': image_path,\n                                                    'image_id': new_image_id})\n                            content = content.replace(source_image_path, f'./oss/file/{new_image_id}')\n                            p['content'] = content\n                    else:\n                        image_file_list.append({'source_file': image_path,\n                                                'image_id': new_image_id})\n                        content = content.replace(source_image_path, f'./oss/file/{new_image_id}')\n                        p['content'] = content\n\n    return image_file_list\n\n\ndef filter_image_file(result_list: list, image_list):\n    image_source_file_list = [image.get('source_file') for image in image_list]\n    return [r for r in result_list if not image_source_file_list.__contains__(r.get('name', ''))]\n\n\nclass ZipParseQAHandle(BaseParseQAHandle):\n\n    def handle(self, file, get_buffer, save_image):\n        buffer = get_buffer(file)\n        bytes_io = io.BytesIO(buffer)\n        result = []\n        # 打开zip文件\n        with zipfile.ZipFile(bytes_io, 'r') as zip_ref:\n            # 获取压缩包中的文件名列表\n            files = zip_ref.namelist()\n            # 读取压缩包中的文件内容\n            for file in files:\n                # 跳过 macOS 特有的元数据目录和文件\n                if file.endswith('/') or file.startswith('__MACOSX'):\n                    continue\n                with zip_ref.open(file) as f:\n                    # 对文件内容进行处理\n                    try:\n                        value = file_to_paragraph(f, save_image)\n                        if isinstance(value, list):\n                            result = [*result, *value]\n                        else:\n                            result.append(value)\n                    except Exception:\n                        pass\n            image_list = get_image_list(result, files)\n            result = filter_image_file(result, image_list)\n            image_mode_list = []\n            for image in image_list:\n                with zip_ref.open(image.get('source_file')) as f:\n                    i = File(\n                        id=image.get('image_id'),\n                        file_name=os.path.basename(image.get('source_file')),\n                        meta={'debug': False, 'content': f.read()}\n                    )\n                    image_mode_list.append(i)\n            save_image(image_mode_list)\n        return result\n\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".zip\") or file_name.endswith(\".ZIP\"):\n            return True\n        return False\n"
  },
  {
    "path": "apps/common/handle/impl/response/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎\n    @file： __init__.py.py\n    @date：2023/9/6 10:09\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/common/handle/impl/response/loop_to_response.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： LoopToResponse.py\n    @date：2025/3/12 17:21\n    @desc:\n\"\"\"\nimport json\n\nfrom common.handle.impl.response.system_to_response import SystemToResponse\n\n\nclass LoopToResponse(SystemToResponse):\n\n    def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end,\n                                 completion_tokens,\n                                 prompt_tokens, other_params: dict = None):\n        if other_params is None:\n            other_params = {}\n        return {'chat_id': str(chat_id), 'chat_record_id': str(chat_record_id), 'operate': True,\n                'content': content, 'node_id': node_id, 'up_node_id_list': up_node_id_list,\n                'is_end': is_end,\n                'usage': {'completion_tokens': completion_tokens,\n                          'prompt_tokens': prompt_tokens,\n                          'total_tokens': completion_tokens + prompt_tokens},\n                **other_params}\n"
  },
  {
    "path": "apps/common/handle/impl/response/openai_to_response.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： openai_to_response.py\n    @date：2024/9/6 16:08\n    @desc:\n\"\"\"\nimport datetime\n\nfrom django.http import JsonResponse\nfrom django.utils import timezone\nfrom openai.types import CompletionUsage\nfrom openai.types.chat import ChatCompletionChunk, ChatCompletionMessage, ChatCompletion\nfrom openai.types.chat.chat_completion import Choice as BlockChoice\nfrom openai.types.chat.chat_completion_chunk import Choice, ChoiceDelta\nfrom rest_framework import status\n\nfrom common.handle.base_to_response import BaseToResponse\n\n\nclass OpenaiToResponse(BaseToResponse):\n    def to_block_response(self, chat_id, chat_record_id, content, is_end, prompt_tokens, completion_tokens,\n                          other_params: dict = None,\n                          _status=status.HTTP_200_OK):\n        if other_params is None:\n            other_params = {}\n        data = ChatCompletion(id=chat_record_id, choices=[\n            BlockChoice(finish_reason='stop', index=0, chat_id=chat_id,\n                        answer_list=other_params.get('answer_list', \"\"),\n                        message=ChatCompletionMessage(role='assistant', content=content))],\n                              created=timezone.now().second, model='', object='chat.completion',\n                              usage=CompletionUsage(completion_tokens=completion_tokens,\n                                                    prompt_tokens=prompt_tokens,\n                                                    total_tokens=completion_tokens + prompt_tokens)\n                              ).dict()\n        return JsonResponse(data=data, status=_status)\n\n    def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end,\n                                 prompt_tokens,\n                                 completion_tokens, other_params: dict = None):\n        if other_params is None:\n            other_params = {}\n        chunk = ChatCompletionChunk(id=chat_record_id, model='', object='chat.completion.chunk',\n                                    created=timezone.now().second, choices=[\n                Choice(delta=ChoiceDelta(content=content, reasoning_content=other_params.get('reasoning_content', \"\"),\n                                         chat_id=chat_id),\n                       finish_reason='stop' if is_end else None,\n                       index=0)],\n                                    usage=CompletionUsage(completion_tokens=completion_tokens,\n                                                          prompt_tokens=prompt_tokens,\n                                                          total_tokens=completion_tokens + prompt_tokens)).json()\n        return super().format_stream_chunk(chunk)\n"
  },
  {
    "path": "apps/common/handle/impl/response/system_to_response.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： system_to_response.py\n    @date：2024/9/6 18:03\n    @desc:\n\"\"\"\nimport json\n\nfrom rest_framework import status\n\nfrom common.handle.base_to_response import BaseToResponse\nfrom common.result import result\n\n\nclass SystemToResponse(BaseToResponse):\n    def to_block_response(self, chat_id, chat_record_id, content, is_end, completion_tokens,\n                          prompt_tokens, other_params: dict = None,\n                          _status=status.HTTP_200_OK):\n        if other_params is None:\n            other_params = {}\n        return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True,\n                               'content': content, 'is_end': is_end, **other_params,\n                               'completion_tokens': completion_tokens, 'prompt_tokens': prompt_tokens},\n                              response_status=_status,\n                              code=_status)\n\n    def to_stream_chunk_response(self, chat_id, chat_record_id, node_id, up_node_id_list, content, is_end,\n                                 completion_tokens,\n                                 prompt_tokens, other_params: dict = None):\n        if other_params is None:\n            other_params = {}\n        chunk = json.dumps({'chat_id': str(chat_id), 'chat_record_id': str(chat_record_id), 'operate': True,\n                            'content': content, 'node_id': node_id, 'up_node_id_list': up_node_id_list,\n                            'is_end': is_end,\n                            'usage': {'completion_tokens': completion_tokens,\n                                      'prompt_tokens': prompt_tokens,\n                                      'total_tokens': completion_tokens + prompt_tokens},\n                            **other_params})\n        return super().format_stream_chunk(chunk)\n"
  },
  {
    "path": "apps/common/handle/impl/table/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎\n    @file： __init__.py.py\n    @date：2023/9/6 10:09\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/common/handle/impl/table/csv_parse_table_handle.py",
    "content": "# coding=utf-8\nimport csv\nimport io\nimport traceback\n\nfrom charset_normalizer import detect\nfrom common.handle.base_parse_qa_handle import get_title_row_index_dict, get_row_value\nfrom common.handle.base_parse_table_handle import BaseParseTableHandle\nfrom common.utils.logger import maxkb_logger\n\n\nclass CsvParseTableHandle(BaseParseTableHandle):\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".csv\"):\n            return True\n        return False\n\n    def handle(self, file, get_buffer, save_image):\n        buffer = get_buffer(file)\n        try:\n            content = buffer.decode(detect(buffer)['encoding'])\n        except BaseException as e:\n            maxkb_logger.error(f\"Error processing CSV file {file.name}: {e}, {traceback.format_exc()}\")\n            return [{'name': file.name, 'paragraphs': []}]\n\n        csv_model = content.split('\\n')\n        paragraphs = []\n        # 第一行为标题\n        title = csv_model[0].split(',')\n        for row in csv_model[1:]:\n            if not row:\n                continue\n            line = '; '.join([f'{key}:{value}' for key, value in zip(title, row.split(','))])\n            paragraphs.append({'title': '', 'content': line})\n\n        return [{'name': file.name, 'paragraphs': paragraphs}]\n\n    def get_content(self, file, save_image):\n        buffer = file.read()\n        try:\n            reader = csv.reader(io.TextIOWrapper(io.BytesIO(buffer), encoding=detect(buffer)['encoding']))\n            rows = list(reader)\n\n            if not rows:\n                return \"\"\n\n            # 构建 Markdown 表格\n            md_lines = []\n\n            # 添加表头\n            header = [cell.replace('\\n', '<br>').replace('\\r', '') for cell in rows[0]]\n            md_lines.append('| ' + ' | '.join(header) + ' |')\n\n            # 添加分隔线\n            md_lines.append('| ' + ' | '.join(['---'] * len(header)) + ' |')\n\n            # 添加数据行\n            for row in rows[1:]:\n                if row:  # 跳过空行\n                    # 确保行长度与表头一致,并将换行符转换为 <br>\n                    padded_row = [\n                                     cell.replace('\\n', '<br>').replace('\\r', '') for cell in row\n                                 ] + [''] * (len(header) - len(row))\n                    md_lines.append('| ' + ' | '.join(padded_row[:len(header)]) + ' |')\n\n            return '\\n'.join(md_lines)\n\n        except Exception as e:\n            maxkb_logger.error(f\"Error processing CSV file {file.name}: {e}, {traceback.format_exc()}\")\n            return \"\"\n"
  },
  {
    "path": "apps/common/handle/impl/table/xls_parse_table_handle.py",
    "content": "# coding=utf-8\nimport logging\nimport traceback\n\nimport xlrd\n\nfrom common.handle.base_parse_table_handle import BaseParseTableHandle\nfrom common.utils.logger import maxkb_logger\n\n\nclass XlsParseTableHandle(BaseParseTableHandle):\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        buffer = get_buffer(file)\n        if file_name.endswith(\".xls\") and xlrd.inspect_format(content=buffer):\n            return True\n        return False\n\n    def handle(self, file, get_buffer, save_image):\n        buffer = get_buffer(file)\n        try:\n            wb = xlrd.open_workbook(file_contents=buffer, formatting_info=True)\n            result = []\n            sheets = wb.sheets()\n            for sheet in sheets:\n                # 获取合并单元格的范围信息\n                merged_cells = sheet.merged_cells\n                data = []\n                paragraphs = []\n                # 获取第一行作为标题行\n                headers = [sheet.cell_value(0, col_idx) for col_idx in range(sheet.ncols)]\n                # 从第二行开始遍历每一行（跳过标题行）\n                for row_idx in range(1, sheet.nrows):\n                    row_data = {}\n                    for col_idx in range(sheet.ncols):\n                        cell_value = sheet.cell_value(row_idx, col_idx)\n\n                        # 检查是否为空单元格，如果为空检查是否在合并区域中\n                        if cell_value == \"\":\n                            # 检查当前单元格是否在合并区域\n                            for (rlo, rhi, clo, chi) in merged_cells:\n                                if rlo <= row_idx < rhi and clo <= col_idx < chi:\n                                    # 使用合并区域的左上角单元格的值\n                                    cell_value = sheet.cell_value(rlo, clo)\n                                    break\n\n                        # 将标题作为键，单元格的值作为值存入字典\n                        row_data[headers[col_idx]] = cell_value\n                    data.append(row_data)\n\n                for row in data:\n                    row_output = \"; \".join([f\"{key}: {value}\" for key, value in row.items()])\n                    # print(row_output)\n                    paragraphs.append({'title': '', 'content': row_output})\n\n                result.append({'name': sheet.name, 'paragraphs': paragraphs})\n\n        except BaseException as e:\n            maxkb_logger.error(f\"Error processing XLS file {file.name}: {e}, {traceback.format_exc()}\")\n            return [{'name': file.name, 'paragraphs': []}]\n        return result\n\n    def get_content(self, file, save_image):\n        # 打开 .xls 文件\n        try:\n            workbook = xlrd.open_workbook(file_contents=file.read(), formatting_info=True)\n            sheets = workbook.sheets()\n            md_tables = ''\n            for sheet in sheets:\n                # 过滤空白的sheet\n                if sheet.nrows == 0 or sheet.ncols == 0:\n                    continue\n\n                # 获取表头和内容\n                headers = sheet.row_values(0)\n                data = [sheet.row_values(row_idx) for row_idx in range(1, sheet.nrows)]\n\n                # 构建 Markdown 表格\n                md_table = '| ' + ' | '.join(headers) + ' |\\n'\n                md_table += '| ' + ' | '.join(['---'] * len(headers)) + ' |\\n'\n                for row in data:\n                    # 将每个单元格中的内容替换换行符为 <br> 以保留原始格式\n                    md_table += '| ' + ' | '.join(\n                        [str(cell)\n                         .replace('\\r\\n', '<br>')\n                         .replace('\\n', '<br>')\n                         if cell else '' for cell in row]) + ' |\\n'\n                md_tables += md_table + '\\n\\n'\n\n            return md_tables\n        except Exception as e:\n            maxkb_logger.error(f'excel split handle error: {e}')\n            return f'error: {e}'\n"
  },
  {
    "path": "apps/common/handle/impl/table/xlsx_parse_table_handle.py",
    "content": "# coding=utf-8\nimport io\nimport logging\nimport traceback\n\nfrom openpyxl import load_workbook\n\nfrom common.handle.base_parse_table_handle import BaseParseTableHandle\nfrom common.handle.impl.common_handle import xlsx_embed_cells_images\nfrom common.utils.logger import maxkb_logger\n\n\nclass XlsxParseTableHandle(BaseParseTableHandle):\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith('.xlsx'):\n            return True\n        return False\n\n    def fill_merged_cells(self, sheet, image_dict):\n        data = []\n\n        # 获取第一行作为标题行\n        headers = []\n        for idx, cell in enumerate(sheet[1]):\n            if cell.value is None:\n                headers.append(' ' * (idx + 1))\n            else:\n                headers.append(cell.value)\n\n        # 从第二行开始遍历每一行\n        for row in sheet.iter_rows(min_row=2, values_only=False):\n            row_data = {}\n            for col_idx, cell in enumerate(row):\n                cell_value = cell.value\n\n                # 如果单元格为空，并且该单元格在合并单元格内，获取合并单元格的值\n                if cell_value is None:\n                    for merged_range in sheet.merged_cells.ranges:\n                        if cell.coordinate in merged_range:\n                            cell_value = sheet[merged_range.min_row][merged_range.min_col - 1].value\n                            break\n                if cell_value is None:\n                    cell_value = ''\n                image = image_dict.get(cell_value, None)\n                if image is not None:\n                    cell_value = f'![](./oss/file/{image.id})'\n\n                # 使用标题作为键，单元格的值作为值存入字典\n                row_data[headers[col_idx]] = cell_value\n            data.append(row_data)\n\n        return data\n\n    def handle(self, file, get_buffer, save_image):\n        buffer = get_buffer(file)\n        try:\n            wb = load_workbook(io.BytesIO(buffer))\n            try:\n                image_dict: dict = xlsx_embed_cells_images(io.BytesIO(buffer))\n                save_image([item for item in image_dict.values()])\n            except Exception as e:\n                image_dict = {}\n            result = []\n            for sheetname in wb.sheetnames:\n                paragraphs = []\n                ws = wb[sheetname]\n                data = self.fill_merged_cells(ws, image_dict)\n\n                for row in data:\n                    row_output = \"; \".join([f\"{key}: {value}\" for key, value in row.items()])\n                    # print(row_output)\n                    paragraphs.append({'title': '', 'content': row_output})\n\n                result.append({'name': sheetname, 'paragraphs': paragraphs})\n\n        except BaseException as e:\n            maxkb_logger.error(f\"Error processing XLSX file {file.name}: {e}, {traceback.format_exc()}\")\n            return [{'name': file.name, 'paragraphs': []}]\n        return result\n\n    def get_content(self, file, save_image):\n        try:\n            # 加载 Excel 文件\n            workbook = load_workbook(file)\n            try:\n                image_dict: dict = xlsx_embed_cells_images(file)\n                if len(image_dict) > 0:\n                    save_image(image_dict.values())\n            except Exception as e:\n                maxkb_logger.error(f'Exception: {e}')\n                image_dict = {}\n            md_tables = ''\n            # 遍历所有工作表\n            for sheetname in workbook.sheetnames:\n                sheet = workbook[sheetname]\n                rows = self.fill_merged_cells(sheet, image_dict)\n                if len(rows) == 0:\n                    continue\n\n                # 添加 sheet 名称作为标题\n                md_tables += f'## {sheetname}\\n\\n'\n\n                # 提取表头和内容\n                headers = [f\"{key}\" for key, value in rows[0].items()]\n\n                # 构建 Markdown 表格\n                md_table = '| ' + ' | '.join(headers) + ' |\\n'\n                md_table += '| ' + ' | '.join(['---'] * len(headers)) + ' |\\n'\n                for row in rows:\n                    r = [f'{value}' for key, value in row.items()]\n                    md_table += '| ' + ' | '.join(\n                        [str(cell).replace('\\n', '<br>') if cell is not None else '' for cell in r]) + ' |\\n'\n\n                md_tables += md_table + '\\n\\n'\n\n            return md_tables\n        except Exception as e:\n            maxkb_logger.error(f'excel split handle error: {e}')\n            return f'error: {e}'\n"
  },
  {
    "path": "apps/common/handle/impl/text/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/handle/impl/text/csv_split_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： csv_parse_qa_handle.py\n    @date：2024/5/21 14:59\n    @desc:\n\"\"\"\nimport csv\nimport io\nimport os\nimport traceback\nfrom typing import List\n\nfrom charset_normalizer import detect\n\nfrom common.handle.base_split_handle import BaseSplitHandle\nfrom common.utils.logger import maxkb_logger\n\n\ndef post_cell(cell_value):\n    return cell_value.replace('\\n', '<br>').replace('|', '&#124;')\n\n\ndef row_to_md(row):\n    return '| ' + ' | '.join(\n        [post_cell(cell) if cell is not None else '' for cell in row]) + ' |\\n'\n\n\nclass CsvSplitHandle(BaseSplitHandle):\n    def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):\n        buffer = get_buffer(file)\n        paragraphs = []\n        file_name = os.path.basename(file.name)\n        result = {'name': file_name, 'content': paragraphs}\n        try:\n            if type(limit) is str:\n                limit = int(limit)\n            reader = csv.reader(io.TextIOWrapper(io.BytesIO(buffer), encoding=detect(buffer)['encoding']))\n            try:\n                title_row_list = reader.__next__()\n                title_md_content = row_to_md(title_row_list)\n                title_md_content += '| ' + ' | '.join(\n                    ['---' if cell is not None else '' for cell in title_row_list]) + ' |\\n'\n            except Exception as e:\n                return result\n            if len(title_row_list) == 0:\n                return result\n            result_item_content = ''\n            for row in reader:\n                next_md_content = row_to_md(row)\n                next_md_content_len = len(next_md_content)\n                result_item_content_len = len(result_item_content)\n                if len(result_item_content) == 0:\n                    result_item_content += title_md_content\n                    result_item_content += next_md_content\n                else:\n                    if result_item_content_len + next_md_content_len < limit:\n                        result_item_content += next_md_content\n                    else:\n                        paragraphs.append({'content': result_item_content, 'title': ''})\n                        result_item_content = title_md_content + next_md_content\n            if len(result_item_content) > 0:\n                paragraphs.append({'content': result_item_content, 'title': ''})\n            return result\n        except Exception as e:\n            maxkb_logger.error(f\"Error processing CSV file {file.name}: {e}, {traceback.format_exc()}\")\n            return result\n\n    def get_content(self, file, save_image):\n        buffer = file.read()\n        try:\n            reader = csv.reader(io.TextIOWrapper(io.BytesIO(buffer), encoding=detect(buffer)['encoding']))\n            rows = list(reader)\n\n            if not rows:\n                return \"\"\n\n            # 构建 Markdown 表格\n            md_lines = []\n\n            # 添加表头\n            header = [cell.replace('\\n', '<br>').replace('\\r', '') for cell in rows[0]]\n            md_lines.append('| ' + ' | '.join(header) + ' |')\n\n            # 添加分隔线\n            md_lines.append('| ' + ' | '.join(['---'] * len(header)) + ' |')\n\n            # 添加数据行\n            for row in rows[1:]:\n                if row:  # 跳过空行\n                    # 确保行长度与表头一致,并将换行符转换为 <br>\n                    padded_row = [\n                                     cell.replace('\\n', '<br>').replace('\\r', '') for cell in row\n                                 ] + [''] * (len(header) - len(row))\n                    md_lines.append('| ' + ' | '.join(padded_row[:len(header)]) + ' |')\n\n            return '\\n'.join(md_lines)\n\n        except Exception as e:\n            maxkb_logger.error(f\"Error processing CSV file {file.name}: {e}, {traceback.format_exc()}\")\n            return \"\"\n\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".csv\"):\n            return True\n        return False\n"
  },
  {
    "path": "apps/common/handle/impl/text/doc_split_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： text_split_handle.py\n    @date：2024/3/27 18:19\n    @desc:\n\"\"\"\nimport io\nimport os\nimport re\nimport traceback\nfrom functools import reduce\nfrom typing import List\n\nimport uuid_utils.compat as uuid\nfrom docx import Document, ImagePart\nfrom docx.oxml import ns\nfrom docx.table import Table\nfrom docx.text.paragraph import Paragraph\n\nfrom common.handle.base_split_handle import BaseSplitHandle\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.split_model import SplitModel\nfrom knowledge.models import File\n\ndefault_pattern_list = [re.compile('(?<=^)# .*|(?<=\\\\n)# .*'),\n                        re.compile('(?<=\\\\n)(?<!#)## (?!#).*|(?<=^)(?<!#)## (?!#).*'),\n                        re.compile(\"(?<=\\\\n)(?<!#)### (?!#).*|(?<=^)(?<!#)### (?!#).*\"),\n                        re.compile(\"(?<=\\\\n)(?<!#)#### (?!#).*|(?<=^)(?<!#)#### (?!#).*\"),\n                        re.compile(\"(?<=\\\\n)(?<!#)##### (?!#).*|(?<=^)(?<!#)##### (?!#).*\"),\n                        re.compile(\"(?<=\\\\n)(?<!#)###### (?!#).*|(?<=^)(?<!#)###### (?!#).*\")]\n\nold_docx_nsmap = {'v': 'urn:schemas-microsoft-com:vml'}\ncombine_nsmap = {**ns.nsmap, **old_docx_nsmap}\n\n\ndef image_to_mode(image, doc: Document, images_list, get_image_id):\n    image_ids = image['get_image_id_handle'](image.get('image'))\n    for img_id in image_ids:  # 获取图片id\n        part = doc.part.related_parts[img_id]  # 根据图片id获取对应的图片\n        if isinstance(part, ImagePart):\n            image_uuid = get_image_id(img_id)\n            if len([i for i in images_list if i.id == image_uuid]) == 0:\n                image = File(id=image_uuid, file_name=part.filename, meta={'debug': False, 'content': part.blob})\n                images_list.append(image)\n            return f'![{part.filename.replace(\"[\", \"\").replace(\"]\", \"\")}](./oss/file/{image_uuid})'\n        return None\n    return None\n\n\ndef get_paragraph_element_images(paragraph_element, doc: Document, images_list, get_image_id):\n    images_xpath_list = [(\".//pic:pic\", lambda img: img.xpath('.//a:blip/@r:embed')),\n                         (\".//w:pict\", lambda img: img.xpath('.//v:imagedata/@r:id', namespaces=combine_nsmap))]\n    images = []\n    for images_xpath, get_image_id_handle in images_xpath_list:\n        try:\n            _images = paragraph_element.xpath(images_xpath)\n            if _images is not None and len(_images) > 0:\n                for image in _images:\n                    images.append({'image': image, 'get_image_id_handle': get_image_id_handle})\n        except Exception as e:\n            pass\n    return images\n\n\ndef images_to_string(images, doc: Document, images_list, get_image_id):\n    return \"\".join(\n        [item for item in [image_to_mode(image, doc, images_list, get_image_id) for image in images] if\n         item is not None])\n\n\ndef get_paragraph_element_txt(paragraph_element, doc: Document, images_list, get_image_id):\n    try:\n        images = get_paragraph_element_images(paragraph_element, doc, images_list, get_image_id)\n        if len(images) > 0:\n            return images_to_string(images, doc, images_list, get_image_id)\n        elif paragraph_element.text is not None:\n            return paragraph_element.text\n        return \"\"\n    except Exception as e:\n        maxkb_logger.error(f'Error getting paragraph element text: {e}')\n    return \"\"\n\n\ndef get_paragraph_txt(paragraph: Paragraph, doc: Document, images_list, get_image_id):\n    try:\n        return \"\".join([get_paragraph_element_txt(e, doc, images_list, get_image_id) for e in paragraph._element])\n    except Exception as e:\n        return \"\"\n\n\ndef get_cell_text(cell, doc: Document, images_list, get_image_id):\n    try:\n        return \"\".join(\n            [get_paragraph_txt(paragraph, doc, images_list, get_image_id) for paragraph in cell.paragraphs]).replace(\n            \"\\n\", '</br>')\n    except Exception as e:\n        return \"\"\n\n\ndef get_image_id_func():\n    image_map = {}\n\n    def get_image_id(image_id):\n        _v = image_map.get(image_id)\n        if _v is None:\n            image_map[image_id] = uuid.uuid7()\n            return image_map.get(image_id)\n        return _v\n\n    return get_image_id\n\n\ntitle_font_list = [\n    [36, 100],\n    [26, 36],\n    [24, 26],\n    [22, 24],\n    [18, 22],\n    [16, 18]\n]\n\n\ndef get_title_level(paragraph: Paragraph):\n    try:\n        if paragraph.style is not None:\n            psn = paragraph.style.name\n            if psn.startswith('Heading') or psn.startswith('TOC 标题') or psn.startswith('标题'):\n                return int(psn.replace(\"Heading \", '').replace('TOC 标题', '').replace('标题',\n                                                                                       ''))\n        if len(paragraph.runs) >= 1:\n            font_size = paragraph.runs[0].font.size\n            pt = font_size.pt\n            if pt >= 16:\n                for _value, index in zip(title_font_list, range(len(title_font_list))):\n                    if pt >= _value[0] and pt < _value[1] and any([run.font.bold for run in paragraph.runs]):\n                        return index + 1\n    except Exception as e:\n        pass\n    return None\n\n\nclass DocSplitHandle(BaseSplitHandle):\n    @staticmethod\n    def paragraph_to_md(paragraph: Paragraph, doc: Document, images_list, get_image_id):\n        try:\n            title_level = get_title_level(paragraph)\n            if title_level is not None:\n                title = \"\".join([\"#\" for i in range(title_level)]) + \" \" + paragraph.text\n                images = reduce(lambda x, y: [*x, *y],\n                                [get_paragraph_element_images(e, doc, images_list, get_image_id) for e in\n                                 paragraph._element],\n                                [])\n                if len(images) > 0:\n                    return title + '\\n' + images_to_string(images, doc, images_list, get_image_id) if len(\n                        paragraph.text) > 0 else images_to_string(images, doc, images_list, get_image_id)\n                return title\n\n        except Exception as e:\n            maxkb_logger.error(f\"Error processing DOC file: {e}, {traceback.format_exc()}\")\n            return paragraph.text\n        return get_paragraph_txt(paragraph, doc, images_list, get_image_id)\n\n    @staticmethod\n    def table_to_md(table, doc: Document, images_list, get_image_id):\n        rows = table.rows\n\n        # 创建 Markdown 格式的表格\n        md_table = '| ' + ' | '.join(\n            [get_cell_text(cell, doc, images_list, get_image_id) for cell in rows[0].cells]) + ' |\\n'\n        md_table += '| ' + ' | '.join(['---' for i in range(len(rows[0].cells))]) + ' |\\n'\n        for row in rows[1:]:\n            md_table += '| ' + ' | '.join(\n                [get_cell_text(cell, doc, images_list, get_image_id) for cell in row.cells]) + ' |\\n'\n        return md_table\n\n    def to_md(self, doc, images_list, get_image_id):\n        elements = []\n        for element in doc.element.body:\n            tag = str(element.tag)\n            if tag.endswith('tbl'):\n                # 处理表格\n                table = Table(element, doc)\n                elements.append(table)\n            elif tag.endswith('p'):\n                # 处理段落\n                paragraph = Paragraph(element, doc)\n                elements.append(paragraph)\n        return \"\\n\".join(\n            [self.paragraph_to_md(element, doc, images_list, get_image_id) if isinstance(element,\n                                                                                         Paragraph) else self.table_to_md(\n                element,\n                doc,\n                images_list, get_image_id)\n             for element\n             in elements])\n\n    def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):\n        file_name = os.path.basename(file.name)\n        try:\n            if type(limit) is str:\n                limit = int(limit)\n            if type(with_filter) is str:\n                with_filter = with_filter.lower() == 'true'\n            image_list = []\n            buffer = get_buffer(file)\n            doc = Document(io.BytesIO(buffer))\n            content = self.to_md(doc, image_list, get_image_id_func())\n            if len(image_list) > 0:\n                save_image(image_list)\n            if pattern_list is not None and len(pattern_list) > 0:\n                split_model = SplitModel(pattern_list, with_filter, limit)\n            else:\n                split_model = SplitModel(default_pattern_list, with_filter=with_filter, limit=limit)\n        except BaseException as e:\n            maxkb_logger.error(f\"Error processing XLSX file {file.name}: {e}, {traceback.format_exc()}\")\n            return {\n                'name': file_name,\n                'content': []\n            }\n        return {\n            'name': file_name,\n            'content': split_model.parse(content)\n        }\n\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".docx\") or file_name.endswith(\".doc\") or file_name.endswith(\n                \".DOC\") or file_name.endswith(\".DOCX\"):\n            return True\n        return False\n\n    def get_content(self, file, save_image):\n        try:\n            image_list = []\n            buffer = file.read()\n            doc = Document(io.BytesIO(buffer))\n            content = self.to_md(doc, image_list, get_image_id_func())\n            if len(image_list) > 0:\n                save_image(image_list)\n            return content\n        except BaseException as e:\n            traceback.print_exception(e)\n            return f'{e}'\n"
  },
  {
    "path": "apps/common/handle/impl/text/html_split_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： html_split_handle.py\n    @date：2024/5/23 10:58\n    @desc:\n\"\"\"\nimport re\nimport traceback\nfrom typing import List\n\nfrom bs4 import BeautifulSoup\nfrom charset_normalizer import detect\nfrom html2text import html2text\n\nfrom common.handle.base_split_handle import BaseSplitHandle\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.split_model import SplitModel\n\ndefault_pattern_list = [re.compile('(?<=^)# .*|(?<=\\\\n)# .*'),\n                        re.compile('(?<=\\\\n)(?<!#)## (?!#).*|(?<=^)(?<!#)## (?!#).*'),\n                        re.compile(\"(?<=\\\\n)(?<!#)### (?!#).*|(?<=^)(?<!#)### (?!#).*\"),\n                        re.compile(\"(?<=\\\\n)(?<!#)#### (?!#).*|(?<=^)(?<!#)#### (?!#).*\"),\n                        re.compile(\"(?<=\\\\n)(?<!#)##### (?!#).*|(?<=^)(?<!#)##### (?!#).*\"),\n                        re.compile(\"(?<=\\\\n)(?<!#)###### (?!#).*|(?<=^)(?<!#)###### (?!#).*\")]\n\n\ndef get_encoding(buffer):\n    beautiful_soup = BeautifulSoup(buffer, \"html.parser\")\n    meta_list = beautiful_soup.find_all('meta')\n    charset_list = [meta.attrs.get('charset') for meta in meta_list if\n                    meta.attrs is not None and 'charset' in meta.attrs]\n    if len(charset_list) > 0:\n        charset = charset_list[0]\n        return charset\n    return detect(buffer)['encoding']\n\n\nclass HTMLSplitHandle(BaseSplitHandle):\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".html\") or file_name.endswith(\".HTML\"):\n            return True\n        return False\n\n    def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):\n        buffer = get_buffer(file)\n        if type(limit) is str:\n            limit = int(limit)\n        if type(with_filter) is str:\n            with_filter = with_filter.lower() == 'true'\n        if pattern_list is not None and len(pattern_list) > 0:\n            split_model = SplitModel(pattern_list, with_filter, limit)\n        else:\n            split_model = SplitModel(default_pattern_list, with_filter=with_filter, limit=limit)\n        try:\n            encoding = get_encoding(buffer)\n            content = buffer.decode(encoding)\n            content = html2text(content)\n        except BaseException as e:\n            maxkb_logger.error(f\"Error processing HTML file {file.name}: {e}, {traceback.format_exc()}\")\n\n            return {\n                'name': file.name, 'content': []\n            }\n        return {\n            'name': file.name,\n            'content': split_model.parse(content)\n        }\n\n    def get_content(self, file, save_image):\n        buffer = file.read()\n\n        try:\n            encoding = get_encoding(buffer)\n            content = buffer.decode(encoding)\n            return html2text(content)\n        except BaseException as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            return f'{e}'\n"
  },
  {
    "path": "apps/common/handle/impl/text/pdf_split_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： text_split_handle.py\n    @date：2024/3/27 18:19\n    @desc:\n\"\"\"\nimport os\nimport re\nimport tempfile\nimport time\nimport traceback\nfrom typing import List\n\nimport fitz\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.handle.base_split_handle import BaseSplitHandle\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.split_model import SplitModel, smart_split_paragraph\n\ndefault_pattern_list = [re.compile('(?<=^)# .*|(?<=\\\\n)# .*'),\n                        re.compile('(?<=\\\\n)(?<!#)## (?!#).*|(?<=^)(?<!#)## (?!#).*'),\n                        re.compile(\"(?<=\\\\n)(?<!#)### (?!#).*|(?<=^)(?<!#)### (?!#).*\"),\n                        re.compile(\"(?<=\\\\n)(?<!#)#### (?!#).*|(?<=^)(?<!#)#### (?!#).*\"),\n                        re.compile(\"(?<=\\\\n)(?<!#)##### (?!#).*|(?<=^)(?<!#)##### (?!#).*\"),\n                        re.compile(\"(?<=\\\\n)(?<!#)###### (?!#).*|(?<=^)(?<!#)###### (?!#).*\"),\n                        re.compile(\"(?<!\\n)\\n\\n+\")]\n\n\ndef check_links_in_pdf(doc):\n    for page_number in range(len(doc)):\n        page = doc[page_number]\n        links = page.get_links()\n        if links:\n            for link in links:\n                if link['kind'] == 1:\n                    return True\n    return False\n\n\nclass PdfSplitHandle(BaseSplitHandle):\n    def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):\n        with tempfile.NamedTemporaryFile(delete=False) as temp_file:\n            # 将上传的文件保存到临时文件中\n            for chunk in file.chunks():\n                temp_file.write(chunk)\n            # 获取临时文件的路径\n            temp_file_path = temp_file.name\n\n        pdf_document = fitz.open(temp_file_path)\n        try:\n            if type(limit) is str:\n                limit = int(limit)\n            if type(with_filter) is str:\n                with_filter = with_filter.lower() == 'true'\n            # 处理有目录的pdf\n            result = self.handle_toc(pdf_document, limit)\n            if result is not None:\n                return {'name': file.name, 'content': result}\n\n            # 没目录但是有链接的pdf\n            result = self.handle_links(pdf_document, pattern_list, with_filter, limit)\n            if result is not None and len(result) > 0:\n                return {'name': file.name, 'content': result}\n\n            # 没有目录的pdf\n            content = self.handle_pdf_content(file, pdf_document)\n\n            if pattern_list is not None and len(pattern_list) > 0:\n                split_model = SplitModel(pattern_list, with_filter, limit)\n            else:\n                split_model = SplitModel(default_pattern_list, with_filter=with_filter, limit=limit)\n        except BaseException as e:\n            maxkb_logger.error(f\"File: {file.name}, error: {e}, {traceback.format_exc()}\")\n            return {\n                'name': file.name,\n                'content': []\n            }\n        finally:\n            pdf_document.close()\n            # 处理完后可以删除临时文件\n            os.remove(temp_file_path)\n\n        return {\n            'name': file.name,\n            'content': split_model.parse(content)\n        }\n\n    @staticmethod\n    def handle_pdf_content(file, pdf_document):\n        # 第一步:收集所有字体大小\n        font_sizes = []\n        for page_num in range(len(pdf_document)):\n            page = pdf_document.load_page(page_num)\n            blocks = page.get_text(\"dict\")[\"blocks\"]\n            for block in blocks:\n                if block[\"type\"] == 0:\n                    for line in block[\"lines\"]:\n                        for span in line[\"spans\"]:\n                            if span[\"size\"] > 0:\n                                font_sizes.append(span[\"size\"])\n\n        # 计算正文字体大小(众数)\n        if not font_sizes:\n            body_font_size = 12\n        else:\n            from collections import Counter\n            body_font_size = Counter(font_sizes).most_common(1)[0][0]\n\n        # 第二步:提取内容\n        content = \"\"\n        for page_num in range(len(pdf_document)):\n            start_time = time.time()\n            page = pdf_document.load_page(page_num)\n            blocks = page.get_text(\"dict\")[\"blocks\"]\n\n            for block in blocks:\n                if block[\"type\"] == 0:  # 文本块\n                    for line in block[\"lines\"]:\n                        if not line[\"spans\"]:\n                            continue\n\n                        text = \"\".join([span[\"text\"] for span in line[\"spans\"]])\n                        font_size = line[\"spans\"][0][\"size\"]\n\n                        # 根据与正文字体的差值判断\n                        size_diff = font_size - body_font_size\n\n                        if size_diff > 2:  # 明显大于正文\n                            content += f\"## {text}\\n\\n\"\n                        elif size_diff > 0.5:  # 略大于正文\n                            content += f\"### {text}\\n\\n\"\n                        else:  # 正文\n                            content += f\"{text}\\n\"\n\n                elif block[\"type\"] == 1:  # 图片块\n                    content += f\"![image](image_{page_num}_{block['number']})\\n\\n\"\n\n            content = content.replace('\\0', '')\n\n            elapsed_time = time.time() - start_time\n            maxkb_logger.debug(\n                f\"File: {file.name}, Page: {page_num + 1}, Time: {elapsed_time:.3f}s\")\n\n        return content\n\n    @staticmethod\n    def handle_toc(doc, limit):\n        # 找到目录\n        toc = doc.get_toc()\n        if toc is None or len(toc) == 0:\n            return None\n\n        # 创建存储章节内容的数组\n        chapters = []\n\n        # 遍历目录并按章节提取文本\n        for i, entry in enumerate(toc):\n            level, title, start_page = entry\n            start_page -= 1  # PyMuPDF 页码从 0 开始，书签页码从 1 开始\n            chapter_title = title\n            # 确定结束页码，如果是最后一个章节则到文档末尾\n            if i + 1 < len(toc):\n                end_page = toc[i + 1][2] - 1\n            else:\n                end_page = doc.page_count - 1\n\n            # 去掉标题中的符号\n            title = PdfSplitHandle.handle_chapter_title(title)\n\n            # 提取该章节的文本内容\n            chapter_text = \"\"\n            for page_num in range(start_page, end_page + 1):\n                page = doc.load_page(page_num)  # 加载页面\n                text = page.get_text(\"text\")\n                text = re.sub(r'(?<!。)\\n+', '', text)\n                text = re.sub(r'(?<!.)\\n+', '', text)\n                # print(f'title: {title}')\n\n                idx = text.find(title)\n                if idx > -1:\n                    text = text[idx + len(title):]\n\n                if i + 1 < len(toc):\n                    l, next_title, next_start_page = toc[i + 1]\n                    next_title = PdfSplitHandle.handle_chapter_title(next_title)\n                    # print(f'next_title: {next_title}')\n                    idx = text.find(next_title)\n                    if idx > -1:\n                        text = text[:idx]\n\n                chapter_text += text  # 提取文本\n\n            # Null characters are not allowed.\n            chapter_text = chapter_text.replace('\\0', '')\n            # 限制标题长度\n            real_chapter_title = chapter_title[:256]\n            # 限制章节内容长度\n            if 0 < limit < len(chapter_text):\n                split_text = smart_split_paragraph(chapter_text, limit)\n                for text in split_text:\n                    chapters.append({\"title\": real_chapter_title, \"content\": text})\n            else:\n                chapters.append(\n                    {\"title\": real_chapter_title, \"content\": chapter_text if chapter_text else real_chapter_title})\n            # 保存章节内容和章节标题\n        return chapters\n\n    @staticmethod\n    def handle_links(doc, pattern_list, with_filter, limit):\n        # 检查文档是否包含内部链接\n        if not check_links_in_pdf(doc):\n            return\n        # 创建存储章节内容的数组\n        chapters = []\n        toc_start_page = -1\n        page_content = \"\"\n        handle_pre_toc = True\n        # 遍历 PDF 的每一页，查找带有目录链接的页\n        for page_num in range(doc.page_count):\n            page = doc.load_page(page_num)\n            links = page.get_links()\n            # 如果目录开始页码未设置，则设置为当前页码\n            if len(links) > 0:\n                toc_start_page = page_num\n            if toc_start_page < 0:\n                page_content += page.get_text('text')\n            # 检查该页是否包含内部链接（即指向文档内部的页面）\n            for num in range(len(links)):\n                link = links[num]\n                if link['kind'] == 1:  # 'kind' 为 1 表示内部链接\n                    # 获取链接目标的页面\n                    dest_page = link['page']\n                    rect = link['from']  # 获取链接的矩形区域\n                    # 如果目录开始页码包括前言部分，则不处理前言部分\n                    if dest_page < toc_start_page:\n                        handle_pre_toc = False\n\n                    # 提取链接区域的文本作为标题\n                    link_title = page.get_text(\"text\", clip=rect).strip().split(\"\\n\")[0].replace('.', '').strip()\n                    # print(f'link_title: {link_title}')\n                    # 提取目标页面内容作为章节开始\n                    start_page = dest_page\n                    end_page = dest_page\n                    # 下一个link\n                    next_link = links[num + 1] if num + 1 < len(links) else None\n                    next_link_title = None\n                    if next_link is not None and next_link['kind'] == 1:\n                        rect = next_link['from']\n                        next_link_title = page.get_text(\"text\", clip=rect).strip() \\\n                            .split(\"\\n\")[0].replace('.', '').strip()\n                        # print(f'next_link_title: {next_link_title}')\n                        end_page = next_link['page']\n\n                    # 提取章节内容\n                    chapter_text = \"\"\n                    for p_num in range(start_page, end_page + 1):\n                        p = doc.load_page(p_num)\n                        text = p.get_text(\"text\")\n                        text = re.sub(r'(?<!。)\\n+', '', text)\n                        text = re.sub(r'(?<!.)\\n+', '', text)\n                        # print(f'\\n{text}\\n')\n\n                        idx = text.find(link_title)\n                        if idx > -1:\n                            text = text[idx + len(link_title):]\n\n                        if next_link_title is not None:\n                            idx = text.find(next_link_title)\n                            if idx > -1:\n                                text = text[:idx]\n                        chapter_text += text\n\n                    # Null characters are not allowed.\n                    chapter_text = chapter_text.replace('\\0', '')\n\n                    # 限制章节内容长度\n                    if 0 < limit < len(chapter_text):\n                        split_text = smart_split_paragraph(chapter_text, limit)\n                        for text in split_text:\n                            chapters.append({\"title\": link_title, \"content\": text})\n                    else:\n                        # 保存章节信息\n                        chapters.append({\"title\": link_title, \"content\": chapter_text})\n\n        # 目录中没有前言部分，手动处理\n        if handle_pre_toc:\n            pre_toc = []\n            lines = page_content.strip().split('\\n')\n            try:\n                for line in lines:\n                    if re.match(r'^前\\s*言', line):\n                        pre_toc.append({'title': line, 'content': ''})\n                    else:\n                        pre_toc[-1]['content'] += line\n                for i in range(len(pre_toc)):\n                    pre_toc[i]['content'] = re.sub(r'(?<!。)\\n+', '', pre_toc[i]['content'])\n                    pre_toc[i]['content'] = re.sub(r'(?<!.)\\n+', '', pre_toc[i]['content'])\n            except BaseException as e:\n                maxkb_logger.error(_('This document has no preface and is treated as ordinary text: {e}').format(e=e))\n                if pattern_list is not None and len(pattern_list) > 0:\n                    split_model = SplitModel(pattern_list, with_filter, limit)\n                else:\n                    split_model = SplitModel(default_pattern_list, with_filter=with_filter, limit=limit)\n                # 插入目录前的部分\n                page_content = re.sub(r'(?<!。)\\n+', '', page_content)\n                page_content = re.sub(r'(?<!.)\\n+', '', page_content)\n                page_content = page_content.strip()\n                pre_toc = split_model.parse(page_content)\n            chapters = pre_toc + chapters\n        return chapters\n\n    @staticmethod\n    def handle_chapter_title(title):\n        title = re.sub(r'[一二三四五六七八九十\\s*]、\\s*', '', title)\n        title = re.sub(r'第[一二三四五六七八九十]章\\s*', '', title)\n        return title\n\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".pdf\") or file_name.endswith(\".PDF\"):\n            return True\n        return False\n\n    def get_content(self, file, save_image):\n        with tempfile.NamedTemporaryFile(delete=False) as temp_file:\n            # 将上传的文件保存到临时文件中\n            temp_file.write(file.read())\n            # 获取临时文件的路径\n            temp_file_path = temp_file.name\n\n        pdf_document = fitz.open(temp_file_path)\n        try:\n            return self.handle_pdf_content(file, pdf_document)\n        except BaseException as e:\n            traceback.print_exception(e)\n            return f'{e}'\n"
  },
  {
    "path": "apps/common/handle/impl/text/text_split_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： text_split_handle.py\n    @date：2024/3/27 18:19\n    @desc:\n\"\"\"\nimport re\nimport traceback\nfrom typing import List\n\nfrom charset_normalizer import detect\n\nfrom common.handle.base_split_handle import BaseSplitHandle\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.split_model import SplitModel\n\ndefault_pattern_list = [\n    re.compile('(?<=^)# (?!-\\\\*- coding:).*|(?<=\\\\n)# (?!-\\\\*- coding:).*'),\n    re.compile('(?<=\\\\n)(?<!#)## (?!#).*|(?<=^)(?<!#)## (?!#).*'),\n    re.compile(\"(?<=\\\\n)(?<!#)### (?!#).*|(?<=^)(?<!#)### (?!#).*\"),\n    re.compile(\"(?<=\\\\n)(?<!#)#### (?!#).*|(?<=^)(?<!#)#### (?!#).*\"),\n    re.compile(\"(?<=\\\\n)(?<!#)##### (?!#).*|(?<=^)(?<!#)##### (?!#).*\"),\n    re.compile(\"(?<=\\\\n)(?<!#)###### (?!#).*|(?<=^)(?<!#)###### (?!#).*\")\n]\n\nend = [\".mp4\", \".avi\", \".mov\", \".mkv\", \".flv\", \".wmv\", \".webm\", \".mpeg\", \".mpg\", \".3gp\", \".ts\", \".rmvb\",\n       \".mp3\", \".wav\", \".flac\", \".aac\", \".ogg\", \".m4a\", \".wma\", \".opus\", \".alac\", \".aiff\", \".amr\",\n       \".jpg\", \".jpeg\", \".png\", \".gif\", \".bmp\", \".tiff\", \".webp\", \".heif\", \".raw\", \".ico\", \".svg\", \".pdf\"]\n\n\nclass TextSplitHandle(BaseSplitHandle):\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".md\") or file_name.endswith('.txt') or file_name.endswith('.TXT') or file_name.endswith(\n                '.MD'):\n            return True\n        lower_name = file_name.lower()\n        if any([True for item in end if lower_name.endswith(item)]):\n            return False\n        buffer = get_buffer(file)\n        result = detect(buffer)\n        if result['encoding'] is not None and result['confidence'] is not None and result['encoding'] != 'ascii' and \\\n                result['confidence'] > 0.5:\n            return True\n        return False\n\n    def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):\n        buffer = get_buffer(file)\n        if type(limit) is str:\n            limit = int(limit)\n        if type(with_filter) is str:\n            with_filter = with_filter.lower() == 'true'\n        if pattern_list is not None and len(pattern_list) > 0:\n            split_model = SplitModel(pattern_list, with_filter, limit)\n        else:\n            split_model = SplitModel(default_pattern_list, with_filter=with_filter, limit=limit)\n        try:\n            content = buffer.decode(detect(buffer)['encoding'])\n        except BaseException as e:\n            maxkb_logger.error(f\"Error processing TEXT file {file.name}: {e}, {traceback.format_exc()}\")\n            return {'name': file.name, 'content': []}\n        return {'name': file.name, 'content': split_model.parse(content)}\n\n    def get_content(self, file, save_image):\n        buffer = file.read()\n        try:\n            return buffer.decode(detect(buffer)['encoding'])\n        except BaseException as e:\n            traceback.print_exception(e)\n            return f'{e}'\n"
  },
  {
    "path": "apps/common/handle/impl/text/xls_split_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： xls_parse_qa_handle.py\n    @date：2024/5/21 14:59\n    @desc:\n\"\"\"\nimport traceback\nfrom typing import List\n\nimport xlrd\n\nfrom common.handle.base_split_handle import BaseSplitHandle\nfrom common.utils.logger import maxkb_logger\n\n\ndef post_cell(cell_value):\n    return cell_value.replace('\\r\\n', '<br>').replace('\\n', '<br>').replace('|', '&#124;')\n\n\ndef row_to_md(row):\n    return '| ' + ' | '.join(\n        [post_cell(str(cell)) if cell is not None else '' for cell in row]) + ' |\\n'\n\n\ndef handle_sheet(file_name, sheet, limit: int):\n    rows = iter([sheet.row_values(i) for i in range(sheet.nrows)])\n    paragraphs = []\n    result = {'name': file_name, 'content': paragraphs}\n    try:\n        title_row_list = next(rows)\n        title_md_content = row_to_md(title_row_list)\n        title_md_content += '| ' + ' | '.join(\n            ['---' if cell is not None else '' for cell in title_row_list]) + ' |\\n'\n    except Exception as e:\n        return result\n    if len(title_row_list) == 0:\n        return result\n    result_item_content = ''\n    for row in rows:\n        next_md_content = row_to_md(row)\n        next_md_content_len = len(next_md_content)\n        result_item_content_len = len(result_item_content)\n        if len(result_item_content) == 0:\n            result_item_content += title_md_content\n            result_item_content += next_md_content\n        else:\n            if result_item_content_len + next_md_content_len < limit:\n                result_item_content += next_md_content\n            else:\n                paragraphs.append({'content': result_item_content, 'title': ''})\n                result_item_content = title_md_content + next_md_content\n    if len(result_item_content) > 0:\n        paragraphs.append({'content': result_item_content, 'title': ''})\n    return result\n\n\nclass XlsSplitHandle(BaseSplitHandle):\n    def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):\n        buffer = get_buffer(file)\n        try:\n            if type(limit) is str:\n                limit = int(limit)\n            workbook = xlrd.open_workbook(file_contents=buffer)\n            worksheets = workbook.sheets()\n            worksheets_size = len(worksheets)\n            return [row for row in\n                    [handle_sheet(file.name,\n                                  sheet, limit) if worksheets_size == 1 and sheet.name == 'Sheet1' else handle_sheet(\n                        sheet.name, sheet, limit) for sheet\n                     in worksheets] if row is not None]\n        except Exception as e:\n            maxkb_logger.error(f\"Error processing XLS file {file.name}: {e}, {traceback.format_exc()}\")\n            return [{'name': file.name, 'content': []}]\n\n    def get_content(self, file, save_image):\n        # 打开 .xls 文件\n        try:\n            workbook = xlrd.open_workbook(file_contents=file.read(), formatting_info=True)\n            sheets = workbook.sheets()\n            md_tables = ''\n            for sheet in sheets:\n                # 过滤空白的sheet\n                if sheet.nrows == 0 or sheet.ncols == 0:\n                    continue\n\n                # 获取表头和内容\n                headers = sheet.row_values(0)\n                data = [sheet.row_values(row_idx) for row_idx in range(1, sheet.nrows)]\n\n                # 构建 Markdown 表格\n                md_table = '| ' + ' | '.join(headers) + ' |\\n'\n                md_table += '| ' + ' | '.join(['---'] * len(headers)) + ' |\\n'\n                for row in data:\n                    # 将每个单元格中的内容替换换行符为 <br> 以保留原始格式\n                    md_table += '| ' + ' | '.join(\n                        [str(cell)\n                         .replace('\\r\\n', '<br>')\n                         .replace('\\n', '<br>')\n                         if cell else '' for cell in row]) + ' |\\n'\n                md_tables += md_table + '\\n\\n'\n\n            return md_tables\n        except Exception as e:\n            maxkb_logger.error(f'excel split handle error: {e}')\n            return f'error: {e}'\n\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        buffer = get_buffer(file)\n        if file_name.endswith(\".xls\") and xlrd.inspect_format(content=buffer):\n            return True\n        return False\n"
  },
  {
    "path": "apps/common/handle/impl/text/xlsx_split_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： xlsx_parse_qa_handle.py\n    @date：2024/5/21 14:59\n    @desc:\n\"\"\"\nimport io\nimport traceback\nfrom typing import List\n\nimport openpyxl\nfrom openpyxl import load_workbook\n\nfrom common.handle.base_split_handle import BaseSplitHandle\nfrom common.handle.impl.common_handle import xlsx_embed_cells_images\nfrom common.utils.logger import maxkb_logger\n\nsplitter = '\\n`-----------------------------------`\\n'\n\n\ndef post_cell(image_dict, cell_value):\n    image = image_dict.get(cell_value, None)\n    if image is not None:\n        return f'![](./oss/file/{image.id})'\n    return cell_value.replace('\\n', '<br>').replace('|', '&#124;')\n\n\ndef row_to_md(row, image_dict):\n    return '| ' + ' | '.join(\n        [post_cell(image_dict, str(cell.value if cell.value is not None else '')) if cell is not None else '' for cell\n         in row]) + ' |\\n'\n\n\ndef handle_sheet(file_name, sheet, image_dict, limit: int):\n    rows = sheet.rows\n    paragraphs = []\n    result = {'name': file_name, 'content': paragraphs}\n    try:\n        title_row_list = next(rows)\n        title_md_content = row_to_md(title_row_list, image_dict)\n        title_md_content += '| ' + ' | '.join(\n            ['---' if cell is not None else '' for cell in title_row_list]) + ' |\\n'\n    except Exception as e:\n        return result\n    if len(title_row_list) == 0:\n        return result\n    result_item_content = ''\n    for row in rows:\n        next_md_content = row_to_md(row, image_dict)\n        next_md_content_len = len(next_md_content)\n        result_item_content_len = len(result_item_content)\n        if len(result_item_content) == 0:\n            result_item_content += title_md_content\n            result_item_content += next_md_content\n        else:\n            if result_item_content_len + next_md_content_len < limit:\n                result_item_content += next_md_content\n            else:\n                paragraphs.append({'content': result_item_content, 'title': ''})\n                result_item_content = title_md_content + next_md_content\n    if len(result_item_content) > 0:\n        paragraphs.append({'content': result_item_content, 'title': ''})\n    return result\n\n\nclass XlsxSplitHandle(BaseSplitHandle):\n    def fill_merged_cells(self, sheet, image_dict):\n        data = []\n\n        # 获取第一行作为标题行\n        headers = []\n        for idx, cell in enumerate(sheet[1]):\n            if cell.value is None:\n                headers.append(' ' * (idx + 1))\n            else:\n                headers.append(cell.value)\n\n        # 从第二行开始遍历每一行\n        for row in sheet.iter_rows(min_row=2, values_only=False):\n            row_data = {}\n            for col_idx, cell in enumerate(row):\n                cell_value = cell.value\n\n                # 如果单元格为空，并且该单元格在合并单元格内，获取合并单元格的值\n                if cell_value is None:\n                    for merged_range in sheet.merged_cells.ranges:\n                        if cell.coordinate in merged_range:\n                            cell_value = sheet[merged_range.min_row][merged_range.min_col - 1].value\n                            break\n\n                image = image_dict.get(cell_value, None)\n                if image is not None:\n                    cell_value = f'![](./oss/file/{image.id})'\n\n                # 使用标题作为键，单元格的值作为值存入字典\n                row_data[headers[col_idx]] = cell_value\n            data.append(row_data)\n\n        return data\n\n    def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):\n        buffer = get_buffer(file)\n        try:\n            if type(limit) is str:\n                limit = int(limit)\n            workbook = openpyxl.load_workbook(io.BytesIO(buffer))\n            try:\n                image_dict: dict = xlsx_embed_cells_images(io.BytesIO(buffer))\n                save_image([item for item in image_dict.values()])\n            except Exception as e:\n                image_dict = {}\n            worksheets = workbook.worksheets\n            worksheets_size = len(worksheets)\n            return [row for row in\n                    [handle_sheet(file.name,\n                                  sheet,\n                                  image_dict,\n                                  limit) if worksheets_size == 1 and sheet.title == 'Sheet1' else handle_sheet(\n                        sheet.title, sheet, image_dict, limit) for sheet\n                     in worksheets] if row is not None]\n        except Exception as e:\n            maxkb_logger.error(f\"Error processing XLSX file {file.name}: {e}, {traceback.format_exc()}\")\n            return [{'name': file.name, 'content': []}]\n\n    def get_content(self, file, save_image):\n        try:\n            # 加载 Excel 文件\n            workbook = load_workbook(file)\n            try:\n                image_dict: dict = xlsx_embed_cells_images(file)\n                if len(image_dict) > 0:\n                    save_image(image_dict.values())\n            except Exception as e:\n                maxkb_logger.error(f'Exception: {e}')\n                image_dict = {}\n            md_tables = ''\n            # 遍历所有工作表\n            for sheetname in workbook.sheetnames:\n                sheet = workbook[sheetname]\n                rows = self.fill_merged_cells(sheet, image_dict)\n                if len(rows) == 0:\n                    continue\n\n                # 添加 sheet 名称作为标题\n                md_tables += f'## {sheetname}\\n\\n'\n\n                # 提取表头和内容\n                headers = [f\"{key}\" for key, value in rows[0].items()]\n\n                # 构建 Markdown 表格\n                md_table = '| ' + ' | '.join(headers) + ' |\\n'\n                md_table += '| ' + ' | '.join(['---'] * len(headers)) + ' |\\n'\n                for row in rows:\n                    r = [self._escape_cell_content(value) for key, value in row.items()]\n                    md_table += '| ' + ' | '.join(r) + ' |\\n'\n\n                md_tables += md_table + '\\n\\n'\n\n            return md_tables\n        except Exception as e:\n            maxkb_logger.error(f'excel split handle error: {e}')\n            return f'error: {e}'\n\n    def _escape_cell_content(self, cell_value):\n        \"\"\"转义单元格内容,避免破坏 Markdown 表格结构\"\"\"\n        if cell_value is None:\n            return ''\n\n        cell_str = str(cell_value)\n\n        # 替换换行符为 <br>\n        cell_str = cell_str.replace('\\n', '<br>')\n\n        # 转义管道符 | 为 HTML 实体\n        cell_str = cell_str.replace('|', '&#124;')\n\n        # 如果内容包含反引号,需要转义\n        if '`' in cell_str:\n            cell_str = cell_str.replace('`', '&#96;')\n\n        return cell_str\n\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".xlsx\"):\n            return True\n        return False\n"
  },
  {
    "path": "apps/common/handle/impl/text/zip_split_handle.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： text_split_handle.py\n    @date：2024/3/27 18:19\n    @desc:\n\"\"\"\nimport io\nimport os\nimport re\nimport zipfile\nfrom typing import List\nfrom urllib.parse import urljoin\n\nimport uuid_utils.compat as uuid\nfrom charset_normalizer import detect\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.handle.base_split_handle import BaseSplitHandle\nfrom common.handle.impl.text.csv_split_handle import CsvSplitHandle\nfrom common.handle.impl.text.doc_split_handle import DocSplitHandle\nfrom common.handle.impl.text.html_split_handle import HTMLSplitHandle\nfrom common.handle.impl.text.pdf_split_handle import PdfSplitHandle\nfrom common.handle.impl.text.text_split_handle import TextSplitHandle\nfrom common.handle.impl.text.xls_split_handle import XlsSplitHandle\nfrom common.handle.impl.text.xlsx_split_handle import XlsxSplitHandle\nfrom common.utils.common import parse_md_image\nfrom knowledge.models import File\n\n\nclass FileBufferHandle:\n    buffer = None\n\n    def get_buffer(self, file):\n        if self.buffer is None:\n            self.buffer = file.read()\n        return self.buffer\n\n\ndefault_split_handle = TextSplitHandle()\nsplit_handles = [\n    HTMLSplitHandle(),\n    DocSplitHandle(),\n    PdfSplitHandle(),\n    XlsxSplitHandle(),\n    XlsSplitHandle(),\n    CsvSplitHandle(),\n    default_split_handle\n]\n\n\ndef file_to_paragraph(file, pattern_list: List, with_filter: bool, limit: int, save_inner_image):\n    get_buffer = FileBufferHandle().get_buffer\n    for split_handle in split_handles:\n        if split_handle.support(file, get_buffer):\n            return split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, save_inner_image)\n    raise Exception(_('Unsupported file format'))\n\n\ndef is_valid_uuid(uuid_str: str):\n    try:\n        uuid.UUID(uuid_str)\n    except ValueError:\n        return False\n    return True\n\n\ndef get_image_list(result_list: list, zip_files: List[str]):\n    image_file_list = []\n    for result in result_list:\n        for p in result.get('content', []):\n            content: str = p.get('content', '')\n            image_list = parse_md_image(content)\n            for image in image_list:\n                search = re.search(\"\\(.*\\)\", image)\n                if search:\n                    new_image_id = str(uuid.uuid7())\n                    source_image_path = search.group().replace('(', '').replace(')', '')\n                    source_image_path = source_image_path.strip().split(\" \")[0]\n                    image_path = urljoin(result.get('name'), '.' + source_image_path if source_image_path.startswith(\n                        '/') else source_image_path)\n                    if not zip_files.__contains__(image_path):\n                        continue\n                    if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'):\n                        image_id = image_path.replace('oss/file/', '').replace('oss/file/', '')\n                        if is_valid_uuid(image_id):\n                            image_file_list.append({'source_file': image_path,\n                                                    'image_id': image_id})\n                        else:\n                            image_file_list.append({'source_file': image_path,\n                                                    'image_id': new_image_id})\n                            content = content.replace(source_image_path, f'./oss/file/{new_image_id}')\n                            p['content'] = content\n                    else:\n                        image_file_list.append({'source_file': image_path,\n                                                'image_id': new_image_id})\n                        content = content.replace(source_image_path, f'./oss/file/{new_image_id}')\n                        p['content'] = content\n\n    return image_file_list\n\n\ndef get_image_list_by_content(name: str, content: str, zip_files: List[str]):\n    image_file_list = []\n    image_list = parse_md_image(content)\n    for image in image_list:\n        search = re.search(\"\\(.*\\)\", image)\n        if search:\n            new_image_id = str(uuid.uuid7())\n            source_image_path = search.group().replace('(', '').replace(')', '')\n            source_image_path = source_image_path.strip().split(\" \")[0]\n            image_path = urljoin(name, '.' + source_image_path if source_image_path.startswith(\n                '/') else source_image_path)\n            if not zip_files.__contains__(image_path):\n                continue\n            if image_path.startswith('oss/file/') or image_path.startswith('oss/image/'):\n                image_id = image_path.replace('oss/file/', '').replace('oss/file/', '')\n                if is_valid_uuid(image_id):\n                    image_file_list.append({'source_file': image_path,\n                                            'image_id': image_id})\n                else:\n                    image_file_list.append({'source_file': image_path,\n                                            'image_id': new_image_id})\n                    content = content.replace(source_image_path, f'./oss/file/{new_image_id}')\n\n            else:\n                image_file_list.append({'source_file': image_path,\n                                        'image_id': new_image_id})\n                content = content.replace(source_image_path, f'./oss/file/{new_image_id}')\n\n    return image_file_list, content\n\n\ndef get_file_name(file_name):\n    try:\n        file_name_code = file_name.encode('cp437')\n        charset = detect(file_name_code)['encoding']\n        return file_name_code.decode(charset)\n    except Exception as e:\n        return file_name\n\n\ndef filter_image_file(result_list: list, image_list):\n    image_source_file_list = [image.get('source_file') for image in image_list]\n    return [r for r in result_list if not image_source_file_list.__contains__(r.get('name', ''))]\n\n\nclass ZipSplitHandle(BaseSplitHandle):\n    def handle(self, file, pattern_list: List, with_filter: bool, limit: int, get_buffer, save_image):\n        if type(limit) is str:\n            limit = int(limit)\n        if type(with_filter) is str:\n            with_filter = with_filter.lower() == 'true'\n        buffer = get_buffer(file)\n        bytes_io = io.BytesIO(buffer)\n        result = []\n        # 打开zip文件\n        with zipfile.ZipFile(bytes_io, 'r') as zip_ref:\n            # 获取压缩包中的文件名列表\n            files = zip_ref.namelist()\n            # 读取压缩包中的文件内容\n            for file in files:\n                if file.endswith('/') or file.startswith('__MACOSX'):\n                    continue\n                with zip_ref.open(file) as f:\n                    # 对文件内容进行处理\n                    try:\n                        # 处理一下文件名\n                        f.name = get_file_name(f.name)\n                        value = file_to_paragraph(f, pattern_list, with_filter, limit, save_image)\n                        if isinstance(value, list):\n                            result = [*result, *value]\n                        else:\n                            result.append(value)\n                    except Exception:\n                        pass\n            image_list = get_image_list(result, files)\n            result = filter_image_file(result, image_list)\n            image_mode_list = []\n            for image in image_list:\n                with zip_ref.open(image.get('source_file')) as f:\n                    i = File(\n                        id=image.get('image_id'),\n                        file_name=os.path.basename(image.get('source_file')),\n                        meta={'debug': False, 'content': f.read()}  # 这里的content是二进制数据\n                    )\n                    image_mode_list.append(i)\n            save_image(image_mode_list)\n        return result\n\n    def support(self, file, get_buffer):\n        file_name: str = file.name.lower()\n        if file_name.endswith(\".zip\") or file_name.endswith(\".ZIP\"):\n            return True\n        return False\n\n    def get_content(self, file, save_image):\n        \"\"\"\n        从 zip 中提取并返回拼接的 md 文本，同时收集并保存内嵌图片（通过 save_image 回调）。\n        使用 posixpath 来正确处理 zip 内部的路径拼接与规范化。\n        \"\"\"\n        buffer = file.read() if hasattr(file, 'read') else None\n        bytes_io = io.BytesIO(buffer) if buffer is not None else io.BytesIO(file)\n        image_list = []\n        content_parts = []\n\n        with zipfile.ZipFile(bytes_io, 'r') as zip_ref:\n            files = zip_ref.namelist()\n            file_content_list = []\n            for inner_name in files:\n                if inner_name.endswith('/') or inner_name.startswith('__MACOSX'):\n                    continue\n                with zip_ref.open(inner_name) as zf:\n                    try:\n                        real_name = get_file_name(zf.name)\n                    except Exception:\n                        real_name = zf.name\n                    # 为 split_handle 提供可重复读取的 file-like 对象\n                    zf.name = real_name\n                    get_buffer = FileBufferHandle().get_buffer\n                    for split_handle in split_handles:\n                        if split_handle.support(zf, get_buffer):\n                            row = get_buffer(zf)\n                            md_text = split_handle.get_content(io.BytesIO(row), save_image)\n                            file_content_list.append({'content': md_text, 'name': real_name})\n                            break\n            for file_content in file_content_list:\n                _image_list, content = get_image_list_by_content(file_content.get('name'), file_content.get(\"content\"),\n                                                                 files)\n                content_parts.append(content)\n                for image in _image_list:\n                    image_list.append(image)\n\n            # 将收集到的图片通过回调保存（一次性）\n            if image_list:\n                image_mode_list = []\n                for image in image_list:\n                    with zip_ref.open(image.get('source_file')) as f:\n                        i = File(\n                            id=image.get('image_id'),\n                            file_name=os.path.basename(image.get('source_file')),\n                            meta={'debug': False, 'content': f.read()}  # 这里的content是二进制数据\n                        )\n                        image_mode_list.append(i)\n                save_image(image_mode_list)\n\n        return '\\n\\n'.join(content_parts)\n"
  },
  {
    "path": "apps/common/init/init_doc.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： init_doc.py\n    @date：2024/5/24 14:11\n    @desc:\n\"\"\"\nimport hashlib\n\nfrom django.urls import path, URLPattern\nfrom drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView\n\nfrom maxkb.const import CONFIG\n\nchat_api_prefix = CONFIG.get_chat_path()[1:] + '/api/'\n\n\ndef init_app_doc(system_urlpatterns):\n    system_urlpatterns += [\n        path(f'{CONFIG.get_admin_path()[1:]}/api-doc/schema/', SpectacularAPIView.as_view(), name='schema'),\n        # schema的配置文件的路由，下面两个ui也是根据这个配置文件来生成的\n        path(f'{CONFIG.get_admin_path()[1:]}/api-doc/', SpectacularSwaggerView.as_view(url_name='schema'),\n             name='swagger-ui'),  # swagger-ui的路由\n    ]\n\n\nclass ChatSpectacularSwaggerView(SpectacularSwaggerView):\n    @staticmethod\n    def _swagger_ui_resource(filename):\n        return f'{CONFIG.get_chat_path()}/api-doc/swagger-ui-dist/{filename}'\n\n    @staticmethod\n    def _swagger_ui_favicon():\n        return f'{CONFIG.get_chat_path()}/api-doc/swagger-ui-dist/favicon-32x32.png'\n\n\ndef init_chat_doc(system_urlpatterns, chat_urlpatterns):\n    system_urlpatterns += [\n        path(f'{CONFIG.get_chat_path()[1:]}/api-doc/schema/',\n             SpectacularAPIView.as_view(patterns=[\n                 URLPattern(pattern=f'{chat_api_prefix}{str(url.pattern)}', callback=url.callback,\n                            default_args=url.default_args,\n                            name=url.name) for url in chat_urlpatterns if\n                 ['chat', 'open', 'profile'].__contains__(url.name)]),\n             name='chat_schema'),  # schema的配置文件的路由，下面两个ui也是根据这个配置文件来生成的\n        path(f'{CONFIG.get_chat_path()[1:]}/api-doc/', ChatSpectacularSwaggerView.as_view(url_name='chat_schema'),\n             name='swagger-ui'),  # swagger-ui的路由\n    ]\n\n\ndef encrypt(text):\n    md5 = hashlib.md5()\n    md5.update(text.encode())\n    result = md5.hexdigest()\n    return result\n\n\ndef get_call(application_urlpatterns, patterns, params, func):\n    def run():\n        if params['valid']():\n            func(*params['get_params'](application_urlpatterns, patterns))\n\n    return run\n\n\ninit_list = [(init_app_doc, {'valid': lambda: CONFIG.get('DOC_PASSWORD') is not None and encrypt(\n    CONFIG.get('DOC_PASSWORD')) == 'd4fc097197b4b90a122b92cbd5bbe867',\n                             'get_call': get_call,\n                             'get_params': lambda application_urlpatterns, patterns: (application_urlpatterns,)}),\n             (init_chat_doc, {'valid': lambda: CONFIG.get('DOC_PASSWORD') is not None and encrypt(\n                 CONFIG.get('DOC_PASSWORD')) == 'd4fc097197b4b90a122b92cbd5bbe867' or True, 'get_call': get_call,\n                              'get_params': lambda application_urlpatterns, patterns: (\n                                  application_urlpatterns, patterns)})]\n\n\ndef init_doc(system_urlpatterns, chat_patterns):\n    for init, params in init_list:\n        if params['valid']():\n            get_call(system_urlpatterns, chat_patterns, params, init)()\n"
  },
  {
    "path": "apps/common/init/init_template.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： init_jinja.py\n    @date：2025/12/1 17:16\n    @desc:\n\"\"\"\nfrom typing import Any\n\nfrom jinja2.sandbox import SandboxedEnvironment\nfrom langchain_core.prompts.string import DEFAULT_FORMATTER_MAPPING, _HAS_JINJA2\n\n\ndef jinja2_formatter(template: str, /, **kwargs: Any) -> str:\n    \"\"\"Format a template using jinja2.\n\n    *Security warning*:\n        As of LangChain 0.0.329, this method uses Jinja2's\n        SandboxedEnvironment by default. However, this sand-boxing should\n        be treated as a best-effort approach rather than a guarantee of security.\n        Do not accept jinja2 templates from untrusted sources as they may lead\n        to arbitrary Python code execution.\n\n        https://jinja.palletsprojects.com/en/3.1.x/sandbox/\n\n    Args:\n        template: The template string.\n        **kwargs: The variables to format the template with.\n\n    Returns:\n        The formatted string.\n\n    Raises:\n        ImportError: If jinja2 is not installed.\n    \"\"\"\n    if not _HAS_JINJA2:\n        msg = (\n            \"jinja2 not installed, which is needed to use the jinja2_formatter. \"\n            \"Please install it with `pip install jinja2`.\"\n            \"Please be cautious when using jinja2 templates. \"\n            \"Do not expand jinja2 templates using unverified or user-controlled \"\n            \"inputs as that can result in arbitrary Python code execution.\"\n        )\n        raise ImportError(msg)\n\n    # Use a restricted sandbox that blocks ALL attribute/method access\n    # Only simple variable lookups like {{variable}} are allowed\n    # Attribute access like {{variable.attr}} or {{variable.method()}} is blocked\n    return SandboxedEnvironment().from_string(template).render(**kwargs)\n\n\ndef run():\n    DEFAULT_FORMATTER_MAPPING['jinja2'] = jinja2_formatter\n"
  },
  {
    "path": "apps/common/job/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2024/3/14 11:54\n    @desc:\n\"\"\"\nfrom .clean_chat_job import *\nfrom .clean_debug_file_job import *\nfrom .client_access_num_job import *\n\n\ndef run():\n    # client_access_num_job.run()\n    clean_chat_job.run()\n    clean_debug_file_job.run()\n    client_access_num_job.run()\n"
  },
  {
    "path": "apps/common/job/clean_chat_job.py",
    "content": "# coding=utf-8\n\nimport datetime\n\nfrom django.db import transaction\nfrom django.db.models import Q, Max\nfrom django.utils import timezone\n\nfrom application.models import Application, Chat, ChatRecord\nfrom common.job.scheduler import scheduler\nfrom common.utils.lock import lock, RedisLock\nfrom common.utils.logger import maxkb_logger\nfrom knowledge.models import File\n\n\ndef clean_chat_log_job():\n    clean_chat_log_job_lock()\n\n\n@lock(lock_key='clean_chat_log_job_execute', timeout=30)\ndef clean_chat_log_job_lock():\n    from django.utils.translation import gettext_lazy as _\n    maxkb_logger.info(_('start clean chat log'))\n    now = timezone.now()\n\n    applications = Application.objects.all().values('id', 'clean_time', 'file_clean_time')\n    cutoff_dates = {\n        app['id']: now - datetime.timedelta(days=app['clean_time'] or 180)\n        for app in applications\n    }\n    file_cutoff_dates = {\n        app['id']: now - datetime.timedelta(days=app['file_clean_time'] or app['clean_time'] or 180)\n        for app in applications\n    }\n    file_conditions = Q()\n    for app_id, cutoff_date in file_cutoff_dates.items():\n        file_conditions |= Q(chat__application_id=app_id, create_time__lt=cutoff_date)\n    clean_method(file_conditions, clean_log=False)\n\n    query_conditions = Q()\n    for app_id, cutoff_date in cutoff_dates.items():\n        query_conditions |= Q(chat__application_id=app_id, create_time__lt=cutoff_date)\n    clean_method(query_conditions)\n\n    maxkb_logger.info(_('end clean chat log'))\n\n\ndef clean_method(query_conditions, clean_log=True):\n    batch_size = 500\n    while True:\n        with transaction.atomic():\n            chat_records = ChatRecord.objects.filter(query_conditions).select_related('chat').only('id', 'chat_id',\n                                                                                                   'create_time')[\n                           :batch_size]\n            if not chat_records:\n                break\n            chat_record_ids = [record.id for record in chat_records]\n            chat_ids = {record.chat_id for record in chat_records}\n\n            # 计算每个 chat_id 的最大 create_time\n            max_create_times = ChatRecord.objects.filter(id__in=chat_record_ids).values('chat_id').annotate(\n                max_create_time=Max('create_time'))\n\n            # 收集需要删除的文件\n            files_to_delete = []\n            for record in chat_records:\n                max_create_time = next(\n                    (item['max_create_time'] for item in max_create_times if\n                     str(item['chat_id']) == str(record.chat_id)), None)\n                if max_create_time:\n                    files_to_delete.extend(\n                        File.objects.filter(source_id=str(record.chat_id), create_time__lt=max_create_time)\n                    )\n            # 删除 ChatRecord\n            deleted_count = 0\n            if clean_log:\n                deleted_count = ChatRecord.objects.filter(id__in=chat_record_ids).delete()[0]\n\n                from django.db.models import Count\n                updated_counts = ChatRecord.objects.filter(chat_id__in=chat_ids) \\\n                    .values('chat_id') \\\n                    .annotate(count=Count('id'))\n\n                count_map = {item['chat_id']: item['count'] for item in updated_counts}\n\n                for chat_id in chat_ids:\n                    count = count_map.get(chat_id, 0)  # 如果没有记录则为0\n                    Chat.objects.filter(id=chat_id).update(chat_record_count=count)\n\n                # 删除没有关联 ChatRecord 的 Chat\n                Chat.objects.filter(chatrecord__isnull=True, id__in=chat_ids).delete()\n            File.objects.filter(loid__in=[file.loid for file in files_to_delete]).delete()\n\n            if deleted_count < batch_size:\n                break\n\n\ndef run():\n    rlock = RedisLock()\n    if rlock.try_lock('clean_chat_log_job', 30 * 30):\n        try:\n            maxkb_logger.debug('get lock clean_chat_log_job')\n\n            existing_job = scheduler.get_job(job_id='clean_chat_log')\n            if existing_job is not None:\n                existing_job.remove()\n            scheduler.add_job(clean_chat_log_job, 'cron', hour='0', minute='5', id='clean_chat_log')\n        finally:\n            rlock.un_lock('clean_chat_log_job')\n"
  },
  {
    "path": "apps/common/job/clean_debug_file_job.py",
    "content": "# coding=utf-8\nfrom datetime import timedelta\n\nfrom django.db.models import Q\nfrom django.utils import timezone\n\nfrom common.job.scheduler import scheduler\nfrom common.utils.lock import lock, RedisLock\nfrom common.utils.logger import maxkb_logger\nfrom knowledge.models import File, FileSourceType\n\n\ndef clean_debug_file():\n    clean_debug_file_lock()\n\n\n@lock(lock_key='clean_debug_file_execute', timeout=30)\ndef clean_debug_file_lock():\n    from django.utils.translation import gettext_lazy as _\n    maxkb_logger.debug(_('start clean debug file'))\n    minutes_30_ago = timezone.now() - timedelta(minutes=30)\n    two_hours_ago = timezone.now() - timedelta(hours=2)\n    one_days_ago = timezone.now() - timedelta(hours=24)\n    # 删除对应的文件\n    File.objects.filter(\n        Q(create_time__lt=one_days_ago, source_type=FileSourceType.TEMPORARY_1_DAY.value) |\n        Q(create_time__lt=two_hours_ago, source_type=FileSourceType.TEMPORARY_120_MINUTE.value) |\n        Q(create_time__lt=minutes_30_ago, source_type=FileSourceType.TEMPORARY_30_MINUTE.value)\n    ).delete()\n    maxkb_logger.debug(_('end clean debug file'))\n\n\ndef run():\n    rlock = RedisLock()\n    if rlock.try_lock('clean_debug_file', 30 * 30):\n        try:\n            maxkb_logger.debug('get lock clean_debug_file')\n\n            clean_debug_file_job = scheduler.get_job(job_id='clean_debug_file')\n            if clean_debug_file_job is not None:\n                clean_debug_file_job.remove()\n            scheduler.add_job(clean_debug_file, 'cron', hour='*', minute='*/30', second='0', id='clean_debug_file')\n        finally:\n            rlock.un_lock('clean_debug_file')\n"
  },
  {
    "path": "apps/common/job/client_access_num_job.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： client_access_num_job.py\n    @date：2024/3/14 11:56\n    @desc:\n\"\"\"\n\nfrom django.db.models import QuerySet\n\nfrom application.models import ApplicationChatUserStats\nfrom common.job.scheduler import scheduler\nfrom common.utils.lock import lock, RedisLock\nfrom common.utils.logger import maxkb_logger\n\n\ndef client_access_num_reset_job():\n    client_access_num_reset_job_lock()\n\n\n@lock(lock_key=\"access_num_reset_execute\", timeout=30)\ndef client_access_num_reset_job_lock():\n    from django.utils.translation import gettext_lazy as _\n    maxkb_logger.info(_('start reset access_num'))\n    QuerySet(ApplicationChatUserStats).update(intraday_access_num=0)\n    maxkb_logger.info(_('end reset access_num'))\n\n\ndef run():\n    rlock = RedisLock()\n    if rlock.try_lock('access_num_reset', 30 * 30):\n        try:\n            maxkb_logger.debug('get lock access_num_reset')\n\n            access_num_reset = scheduler.get_job(job_id='access_num_reset')\n            if access_num_reset is not None:\n                access_num_reset.remove()\n            scheduler.add_job(client_access_num_reset_job, 'cron', hour='0', minute='0', second='0',\n                              id='access_num_reset')\n        finally:\n            rlock.un_lock('access_num_reset')\n"
  },
  {
    "path": "apps/common/job/scheduler.py",
    "content": "from apscheduler.schedulers.background import BackgroundScheduler\nfrom django_apscheduler.jobstores import DjangoJobStore\n\nscheduler = BackgroundScheduler()\nscheduler.add_jobstore(DjangoJobStore(), \"default\")\n\ntry:\n    scheduler.start()\nexcept Exception as e:\n    from common.utils.logger import maxkb_logger\n\n    maxkb_logger.error(f\"Failed to start scheduler: {e}\")\n"
  },
  {
    "path": "apps/common/lock/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/lock/base_lock.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： base_lock.py\n    @date：2024/8/20 10:33\n    @desc:\n\"\"\"\n\nfrom abc import ABC, abstractmethod\n\n\nclass BaseLock(ABC):\n    @abstractmethod\n    def try_lock(self, key, timeout):\n        pass\n\n    @abstractmethod\n    def un_lock(self, key):\n        pass\n"
  },
  {
    "path": "apps/common/lock/impl/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/log/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/log/log.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： log.py\n    @date：2025/6/4 14:13\n    @desc:\n\"\"\"\n\nfrom system_manage.models.log_management import Log\n\n\ndef _get_ip_address(request):\n    \"\"\"\n    获取ip地址\n    @param request:\n    @return:\n    \"\"\"\n    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')\n    if x_forwarded_for:\n        ip = x_forwarded_for.split(',')[0]\n    else:\n        ip = request.META.get('REMOTE_ADDR')\n    return ip\n\n\ndef _get_user(request):\n    \"\"\"\n    获取用户\n    @param request:\n    @return:\n    \"\"\"\n    user = request.user\n    if user is None:\n        return {\n\n        }\n    user_info = {\n        \"id\": str(user.id),\n        \"email\": user.email,\n        \"phone\": user.phone,\n        \"nick_name\": user.nick_name,\n        \"username\": user.username,\n    }\n    # 如果是 User 模型且有 role 属性\n    if hasattr(user, 'role'):\n        user_info['role'] = user.role\n    return user_info\n\n\ndef _get_details(request):\n    path = request.path\n    body = request.data\n    query = request.query_params\n    return {\n        'path': path,\n        'body': body,\n        'query': query\n    }\n\n\ndef _get_workspace_id(request, kwargs):\n    return kwargs.get('workspace_id', 'None')\n\n\ndef log(menu: str, operate, get_user=_get_user, get_ip_address=_get_ip_address, get_details=_get_details,\n        get_operation_object=None, get_workspace_id=_get_workspace_id):\n    \"\"\"\n    记录审计日志\n    @param menu: 操作菜单 str\n    @param operate: 操作 str|func 如果是一个函数 入参将是一个request 响应为str def operate(request): return \"操作菜单\"\n    @param get_user: 获取用户\n    @param get_ip_address:获取IP地址\n    @param get_details: 获取执行详情\n    @param get_operation_object: 获取操作对象\n    @param get_workspace_id: 获取工作空间id\n    @return:\n    \"\"\"\n\n    def inner(func):\n        def run(view, request, **kwargs):\n            status = 200\n            operation_object = {}\n            try:\n                if get_operation_object is not None:\n                    operation_object = get_operation_object(request, kwargs)\n            except Exception as e:\n                pass\n            try:\n                return func(view, request, **kwargs)\n            except Exception as e:\n                status = 500\n                raise e\n            finally:\n                ip = get_ip_address(request)\n                user = get_user(request)\n                details = get_details(request)\n                workspace_id = get_workspace_id(request, kwargs)\n                _operate = operate\n                if callable(operate):\n                    _operate = operate(request)\n                # 插入审计日志\n                Log(menu=menu, operate=_operate, user=user, status=status, ip_address=ip, details=details,\n                    operation_object=operation_object, workspace_id=workspace_id).save()\n\n        return run\n\n    return inner\n"
  },
  {
    "path": "apps/common/management/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/management/commands/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/management/commands/celery.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： celery.py\n    @date：2024/8/19 11:57\n    @desc:\n\"\"\"\nimport os\nimport subprocess\n\nfrom django.core.management.base import BaseCommand\n\nfrom maxkb.const import BASE_DIR\n\n\nclass Command(BaseCommand):\n    help = 'celery'\n\n    def add_arguments(self, parser):\n        parser.add_argument(\n            'service', nargs='+', type=str, choices=(\"celery\", \"model\"), help='Service',\n        )\n\n    def handle(self, *args, **options):\n        service = options.get('service')\n        os.environ.setdefault('CELERY_NAME', ','.join(service))\n        server_hostname = os.environ.get(\"SERVER_HOSTNAME\")\n        if hasattr(os, 'getuid') and os.getuid() == 0:\n            os.environ.setdefault('C_FORCE_ROOT', '1')\n        if not server_hostname:\n            server_hostname = '%h'\n        cmd = [\n            'celery',\n            '-A', 'ops',\n            'worker',\n            '-P', 'threads',\n            '-l', 'info',\n            '-c', '10',\n            '-Q', ','.join(service),\n            '--heartbeat-interval', '10',\n            '-n', f'{\",\".join(service)}@{server_hostname}',\n            '--without-mingle',\n        ]\n        kwargs = {'cwd': BASE_DIR}\n        subprocess.run(cmd, **kwargs)\n"
  },
  {
    "path": "apps/common/management/commands/restart.py",
    "content": "from .services.command import BaseActionCommand, Action\n\n\nclass Command(BaseActionCommand):\n    help = 'Restart services'\n    action = Action.restart.value\n"
  },
  {
    "path": "apps/common/management/commands/services/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/management/commands/services/command.py",
    "content": "import math\nimport os\n\nfrom django.core.management.base import BaseCommand\nfrom django.db.models import TextChoices\n\nfrom .utils import ServicesUtil\n\n\nclass Services(TextChoices):\n    gunicorn = 'gunicorn', 'gunicorn'\n    celery_default = 'celery_default', 'celery_default'\n    local_model = 'local_model', 'local_model'\n    web = 'web', 'web'\n    celery = 'celery', 'celery'\n    celery_model = 'celery_model', 'celery_model'\n    task = 'task', 'task'\n    all = 'all', 'all'\n\n    @classmethod\n    def get_service_object_class(cls, name):\n        from . import services\n        services_map = {\n            cls.gunicorn.value: services.GunicornService,\n            cls.celery_default: services.CeleryDefaultService,\n            cls.local_model: services.GunicornLocalModelService,\n        }\n        return services_map.get(name)\n\n    @classmethod\n    def web_services(cls):\n        return [cls.gunicorn, cls.local_model]\n\n    @classmethod\n    def celery_services(cls):\n        return [cls.celery_default, cls.celery_model]\n\n    @classmethod\n    def task_services(cls):\n        return cls.celery_services()\n\n\n    @classmethod\n    def all_services(cls):\n        return cls.web_services() + cls.task_services()\n\n    @classmethod\n    def export_services_values(cls):\n        return [cls.all.value, cls.web.value, cls.task.value] + [s.value for s in cls.all_services()]\n\n    @classmethod\n    def get_service_objects(cls, service_names, **kwargs):\n        services = set()\n        for name in service_names:\n            method_name = f'{name}_services'\n            if hasattr(cls, method_name):\n                _services = getattr(cls, method_name)()\n            elif hasattr(cls, name):\n                _services = [getattr(cls, name)]\n            else:\n                continue\n            services.update(set(_services))\n\n        service_objects = []\n        for s in services:\n            service_class = cls.get_service_object_class(s.value)\n            if not service_class:\n                continue\n            kwargs.update({\n                'name': s.value\n            })\n            service_object = service_class(**kwargs)\n            service_objects.append(service_object)\n        return service_objects\n\n\nclass Action(TextChoices):\n    start = 'start', 'start'\n    status = 'status', 'status'\n    stop = 'stop', 'stop'\n    restart = 'restart', 'restart'\n\n\nclass BaseActionCommand(BaseCommand):\n    help = 'Service Base Command'\n\n    action = None\n    util = None\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\n    def add_arguments(self, parser):\n        parser.add_argument(\n            'services', nargs='+', choices=Services.export_services_values(), help='Service',\n        )\n        parser.add_argument('-d', '--daemon', nargs=\"?\", const=True)\n        parser.add_argument('-w', '--worker', type=int, nargs=\"?\",\n                            default=3 if os.cpu_count() > 6 else max(1, math.floor(os.cpu_count() / 2)))\n        parser.add_argument('-f', '--force', nargs=\"?\", const=True)\n\n    def initial_util(self, *args, **options):\n        service_names = options.get('services')\n        service_kwargs = {\n            'worker_gunicorn': options.get('worker')\n        }\n        services = Services.get_service_objects(service_names=service_names, **service_kwargs)\n\n        kwargs = {\n            'services': services,\n            'run_daemon': options.get('daemon', False),\n            'stop_daemon': self.action == Action.stop.value and Services.all.value in service_names,\n            'force_stop': options.get('force') or False,\n        }\n        self.util = ServicesUtil(**kwargs)\n\n    def handle(self, *args, **options):\n        self.initial_util(*args, **options)\n        assert self.action in Action.values, f'The action {self.action} is not in the optional list'\n        _handle = getattr(self, f'_handle_{self.action}', lambda: None)\n        _handle()\n\n    def _handle_start(self):\n        self.util.start_and_watch()\n        os._exit(0)\n\n    def _handle_stop(self):\n        self.util.stop()\n\n    def _handle_restart(self):\n        self.util.restart()\n\n    def _handle_status(self):\n        self.util.show_status()\n"
  },
  {
    "path": "apps/common/management/commands/services/hands.py",
    "content": "import logging\nimport os\nimport sys\n\nfrom maxkb.const import CONFIG, PROJECT_DIR, LOG_DIR\n\ntry:\n    from apps.maxkb import const\n\n    __version__ = const.VERSION\nexcept ImportError as e:\n    print(\"Not found __version__: {}\".format(e))\n    print(\"Python is: \")\n    logging.info(sys.executable)\n    __version__ = 'Unknown'\n    sys.exit(1)\n\nHTTP_HOST = '0.0.0.0'\nHTTP_PORT = CONFIG.HTTP_LISTEN_PORT or 8080\nDEBUG = CONFIG.DEBUG or False\n\nAPPS_DIR = os.path.join(PROJECT_DIR, 'apps')\nTMP_DIR = os.path.join(PROJECT_DIR, 'tmp')\nif not os.path.exists(TMP_DIR):\n    os.makedirs(TMP_DIR, 0o700, exist_ok=True)\n"
  },
  {
    "path": "apps/common/management/commands/services/services/__init__.py",
    "content": "from .celery_default import *\nfrom .gunicorn import *\nfrom .local_model import *\nfrom .scheduler import *\n"
  },
  {
    "path": "apps/common/management/commands/services/services/base.py",
    "content": "import abc\nimport time\nimport shutil\nimport psutil\nimport datetime\nimport threading\nimport subprocess\nfrom ..hands import *\n\n\nclass BaseService(object):\n\n    def __init__(self, **kwargs):\n        self.name = kwargs['name']\n        self._process = None\n        self.STOP_TIMEOUT = 10\n        self.max_retry = 0\n        self.retry = 3\n        self.LOG_KEEP_DAYS = int(CONFIG.get('LOG_RETENTION_DAYS', 7))\n        self.EXIT_EVENT = threading.Event()\n\n    @property\n    @abc.abstractmethod\n    def cmd(self):\n        return []\n\n    @property\n    @abc.abstractmethod\n    def cwd(self):\n        return ''\n\n    @property\n    def is_running(self):\n        if self.pid == 0:\n            return False\n        try:\n            os.kill(self.pid, 0)\n        except (OSError, ProcessLookupError):\n            return False\n        else:\n            return True\n\n    def show_status(self):\n        if self.is_running:\n            msg = f'{self.name} is running: {self.pid}.'\n        else:\n            msg = f'{self.name} is stopped.'\n            if DEBUG:\n                msg = '\\033[31m{} is stopped.\\033[0m\\nYou can manual start it to find the error: \\n' \\\n                      '  $ cd {}\\n' \\\n                      '  $ {}'.format(self.name, self.cwd, ' '.join(self.cmd))\n\n        print(msg)\n\n    # -- log --\n    @property\n    def log_filename(self):\n        return f'{self.name}.log'\n\n    @property\n    def log_filepath(self):\n        return os.path.join(LOG_DIR, self.log_filename)\n\n    @property\n    def log_file(self):\n        return open(self.log_filepath, 'a')\n\n    @property\n    def log_dir(self):\n        return os.path.dirname(self.log_filepath)\n    # -- end log --\n\n    # -- pid --\n    @property\n    def pid_filepath(self):\n        return os.path.join(TMP_DIR, f'{self.name}.pid')\n\n    @property\n    def pid(self):\n        if not os.path.isfile(self.pid_filepath):\n            return 0\n        with open(self.pid_filepath) as f:\n            try:\n                pid = int(f.read().strip())\n            except ValueError:\n                pid = 0\n        return pid\n\n    def write_pid(self):\n        with open(self.pid_filepath, 'w') as f:\n            f.write(str(self.process.pid))\n\n    def remove_pid(self):\n        if os.path.isfile(self.pid_filepath):\n            os.unlink(self.pid_filepath)\n    # -- end pid --\n\n    # -- process --\n    @property\n    def process(self):\n        if not self._process:\n            try:\n                self._process = psutil.Process(self.pid)\n            except:\n                pass\n        return self._process\n\n    # -- end process --\n\n    # -- action --\n    def open_subprocess(self):\n        kwargs = {'cwd': self.cwd, 'stderr': self.log_file, 'stdout': self.log_file}\n        self._process = subprocess.Popen(self.cmd, **kwargs)\n\n    def start(self):\n        if self.is_running:\n            self.show_status()\n            return\n        self.remove_pid()\n        self.open_subprocess()\n        self.write_pid()\n        self.start_other()\n\n    def start_other(self):\n        pass\n\n    def stop(self, force=False):\n        if not self.is_running:\n            self.show_status()\n            # self.remove_pid()\n            return\n\n        print(f'Stop service: {self.name}', end='')\n        sig = 9 if force else 15\n        os.kill(self.pid, sig)\n\n        if self.process is None:\n            print(\"\\033[31m No process found\\033[0m\")\n            return\n        try:\n            self.process.wait(1)\n        except:\n            pass\n\n        for i in range(self.STOP_TIMEOUT):\n            if i == self.STOP_TIMEOUT - 1:\n                print(\"\\033[31m Error\\033[0m\")\n            if not self.is_running:\n                print(\"\\033[32m Ok\\033[0m\")\n                self.remove_pid()\n                break\n            else:\n                continue\n\n    def watch(self):\n        self._check()\n        if not self.is_running:\n            self._restart()\n        self._rotate_log()\n\n    def _check(self):\n        now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')\n        if self.process:\n            try:\n                self.process.wait(1)  # 不wait，子进程可能无法回收\n            except:\n                pass\n\n        if self.is_running:\n            logging.debug(f\"{now} Check service status: {self.name} -> running at {self.pid}\")\n        else:\n            logging.debug(f\"{now} Check service status: {self.name} -> stopped at {self.pid}\")\n\n    def _restart(self):\n        if self.retry > self.max_retry:\n            logging.info(\"Service start failed, exit: {}\".format(self.name))\n            self.EXIT_EVENT.set()\n            return\n        self.retry += 1\n        logging.info(f'> Find {self.name} stopped, retry {self.retry}, {self.pid}')\n        self.start()\n\n    def _rotate_log(self):\n        now = datetime.datetime.now()\n        _time = now.strftime('%H:%M')\n        if _time != '23:59':\n            return\n\n        backup_date = now.strftime('%Y-%m-%d')\n        backup_log_dir = os.path.join(self.log_dir, backup_date)\n        if not os.path.exists(backup_log_dir):\n            os.mkdir(backup_log_dir)\n\n        backup_log_path = os.path.join(backup_log_dir, self.log_filename)\n        if os.path.isfile(self.log_filepath) and not os.path.isfile(backup_log_path):\n            logging.info(f'Rotate log file: {self.log_filepath} => {backup_log_path}')\n            shutil.copy(self.log_filepath, backup_log_path)\n            with open(self.log_filepath, 'w') as f:\n                pass\n\n        to_delete_date = now - datetime.timedelta(days=self.LOG_KEEP_DAYS)\n        to_delete_dir = os.path.join(LOG_DIR, to_delete_date.strftime('%Y-%m-%d'))\n        if os.path.exists(to_delete_dir):\n            logging.info(f'Remove old log: {to_delete_dir}')\n            shutil.rmtree(to_delete_dir, ignore_errors=True)\n    # -- end action --\n"
  },
  {
    "path": "apps/common/management/commands/services/services/celery_base.py",
    "content": "from django.conf import settings\n\nfrom .base import BaseService\nfrom ..hands import *\n\n\nclass CeleryBaseService(BaseService):\n\n    def __init__(self, queue, num=10, **kwargs):\n        super().__init__(**kwargs)\n        self.queue = queue\n        self.num = num\n\n    @property\n    def cmd(self):\n        print('\\n- Start Celery as Distributed Task Queue: {}'.format(self.queue.capitalize()))\n\n        os.environ.setdefault('LC_ALL', 'C.UTF-8')\n        os.environ.setdefault('PYTHONOPTIMIZE', '1')\n        os.environ.setdefault('ANSIBLE_FORCE_COLOR', 'True')\n        os.environ.setdefault('PYTHONPATH', settings.APPS_DIR)\n\n        if os.getuid() == 0:\n            os.environ.setdefault('C_FORCE_ROOT', '1')\n        server_hostname = os.environ.get(\"SERVER_HOSTNAME\")\n        if not server_hostname:\n            server_hostname = '%h'\n\n        cmd = [\n            'celery',\n            '-A', 'ops',\n            'worker',\n            '-P', 'threads',\n            '-l', 'error',\n            '-c', str(self.num),\n            '-Q', self.queue,\n            '--heartbeat-interval', '10',\n            '-n', f'{self.queue}@{server_hostname}',\n            '--without-mingle',\n        ]\n        return cmd\n\n    @property\n    def cwd(self):\n        return APPS_DIR\n"
  },
  {
    "path": "apps/common/management/commands/services/services/celery_default.py",
    "content": "import os\nimport subprocess\n\nfrom .celery_base import CeleryBaseService\nfrom django.conf import settings\n\n__all__ = ['CeleryDefaultService']\n\n\nclass CeleryDefaultService(CeleryBaseService):\n\n    def __init__(self, **kwargs):\n        kwargs['queue'] = 'celery'\n        super().__init__(**kwargs)\n\n    def open_subprocess(self):\n        env = os.environ.copy()\n        env['LC_ALL'] = 'C.UTF-8'\n        env['PYTHONOPTIMIZE'] = '1'\n        env['ANSIBLE_FORCE_COLOR'] = 'True'\n        env['PYTHONPATH'] = settings.APPS_DIR\n        env['SERVER_NAME'] = 'celery'\n        if os.getuid() == 0:\n            env.setdefault('C_FORCE_ROOT', '1')\n        kwargs = {\n            'cwd': self.cwd,\n            'stderr': self.log_file,\n            'stdout': self.log_file,\n            'env': env\n        }\n        self._process = subprocess.Popen(self.cmd, **kwargs)\n"
  },
  {
    "path": "apps/common/management/commands/services/services/gunicorn.py",
    "content": "import subprocess\n\nfrom .base import BaseService\nfrom ..hands import *\n\n__all__ = ['GunicornService']\n\n\nclass GunicornService(BaseService):\n\n    def __init__(self, **kwargs):\n        self.worker = kwargs['worker_gunicorn']\n        super().__init__(**kwargs)\n\n    @property\n    def cmd(self):\n        print(\"\\n- Start Gunicorn WSGI HTTP Server\")\n\n        log_format = '%(h)s %(t)s %(L)ss \"%(r)s\" %(s)s %(b)s '\n        bind = f'{HTTP_HOST}:{HTTP_PORT}'\n        max_requests = 10240 if int(self.worker) > 1 else 0\n        cmd = [\n            'gunicorn', 'maxkb.wsgi:application',\n            '-b', bind,\n            '-k', 'gthread',\n            '--threads', '200',\n            '-w', str(self.worker),\n            '--max-requests', str(max_requests),\n            '--max-requests-jitter', '2048',\n            '--timeout', '30',\n            '--graceful-timeout', '300',\n            '--access-logformat', log_format,\n            '--access-logfile', '/dev/null',\n            '--error-logfile', '-'\n        ]\n        if DEBUG:\n            cmd.append('--reload')\n        return cmd\n\n    @property\n    def cwd(self):\n        return APPS_DIR\n\n    def open_subprocess(self):\n        # 复制当前环境变量，并设置 ENABLE_SCHEDULER=1\n        env = os.environ.copy()\n        env['SERVER_NAME'] = 'web'\n        kwargs = {\n            'cwd': self.cwd,\n            'stderr': self.log_file,\n            'stdout': self.log_file,\n            'env': env\n        }\n        self._process = subprocess.Popen(self.cmd, **kwargs)\n"
  },
  {
    "path": "apps/common/management/commands/services/services/local_model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： local_model.py\n    @date：2024/8/21 13:28\n    @desc:\n\"\"\"\nimport subprocess\n\nfrom maxkb.const import CONFIG\nfrom .base import BaseService\nfrom ..hands import *\n\n__all__ = ['GunicornLocalModelService']\n\n\nclass GunicornLocalModelService(BaseService):\n\n    def __init__(self, **kwargs):\n        self.worker = kwargs['worker_gunicorn']\n        super().__init__(**kwargs)\n\n    @property\n    def cmd(self):\n        print(\"\\n- Start Gunicorn Local Model WSGI HTTP Server\")\n        log_format = '%(h)s %(t)s %(L)ss \"%(r)s\" %(s)s %(b)s '\n        bind = f'{CONFIG.get(\"LOCAL_MODEL_HOST\")}:{CONFIG.get(\"LOCAL_MODEL_PORT\")}'\n        worker = CONFIG.get(\"LOCAL_MODEL_HOST_WORKER\", 1)\n        max_requests = 10240 if int(worker) > 1 else 0\n        cmd = [\n            'gunicorn', 'maxkb.wsgi:application',\n            '-b', bind,\n            '-k', 'gthread',\n            '--threads', '200',\n            '-w', str(worker),\n            '--max-requests', str(max_requests),\n            '--max-requests-jitter', '2048',\n            '--timeout', '30',\n            '--graceful-timeout', '300',\n            '--access-logformat', log_format,\n            '--access-logfile', '/dev/null',\n            '--error-logfile', '-'\n        ]\n        if DEBUG:\n            cmd.append('--reload')\n        return cmd\n\n    @property\n    def cwd(self):\n        return APPS_DIR\n\n    def open_subprocess(self):\n        # 复制当前环境变量，并设置 ENABLE_SCHEDULER=1\n        env = os.environ.copy()\n        env['SERVER_NAME'] = 'local_model'\n        kwargs = {\n            'cwd': self.cwd,\n            'stderr': self.log_file,\n            'stdout': self.log_file,\n            'env': env\n        }\n        self._process = subprocess.Popen(self.cmd, **kwargs)\n"
  },
  {
    "path": "apps/common/management/commands/services/services/scheduler.py",
    "content": "import subprocess\n\nfrom .base import BaseService\nfrom ..hands import *\n\n__all__ = ['SchedulerService']\n\n\nclass SchedulerService(BaseService):\n\n    def __init__(self, **kwargs):\n        self.worker = 1\n        super().__init__(**kwargs)\n\n    @property\n    def cmd(self):\n        print(\"\\n- Start Scheduler Server\")\n\n        log_format = '%(h)s %(t)s %(L)ss \"%(r)s\" %(s)s %(b)s '\n        bind = f'127.0.0.1:6060'\n        max_requests = 10240 if int(self.worker) > 1 else 0\n        cmd = [\n            'gunicorn', 'maxkb.wsgi:application',\n            '-b', bind,\n            '-k', 'gthread',\n            '--threads', '200',\n            '-w', str(self.worker),\n            '--max-requests', str(max_requests),\n            '--max-requests-jitter', '2048',\n            '--timeout', '30',\n            '--graceful-timeout', '300',\n            '--access-logformat', log_format,\n            '--access-logfile', '/dev/null',\n            '--error-logfile', '-'\n        ]\n        if DEBUG:\n            cmd.append('--reload')\n        return cmd\n\n    @property\n    def cwd(self):\n        return APPS_DIR\n\n    def open_subprocess(self):\n        # 复制当前环境变量，并设置 ENABLE_SCHEDULER=1\n        env = os.environ.copy()\n        env['ENABLE_SCHEDULER'] = '1'\n        kwargs = {\n            'cwd': self.cwd,\n            'stderr': self.log_file,\n            'stdout': self.log_file,\n            'env': env\n        }\n        self._process = subprocess.Popen(self.cmd, **kwargs)"
  },
  {
    "path": "apps/common/management/commands/services/utils.py",
    "content": "import threading\nimport signal\nimport time\nimport daemon\nfrom daemon import pidfile\nfrom .hands import *\nfrom .hands import __version__\nfrom .services.base import BaseService\n\n\nclass ServicesUtil(object):\n\n    def __init__(self, services, run_daemon=False, force_stop=False, stop_daemon=False):\n        self._services = services\n        self.run_daemon = run_daemon\n        self.force_stop = force_stop\n        self.stop_daemon = stop_daemon\n        self.EXIT_EVENT = threading.Event()\n        self.check_interval = 30\n        self.files_preserve_map = {}\n\n    def restart(self):\n        self.stop()\n        time.sleep(5)\n        self.start_and_watch()\n\n    def start_and_watch(self):\n        logging.info(time.ctime())\n        logging.info(f'MaxKB version {__version__}, more see https://www.maxkb.cn')\n        self.start()\n        if self.run_daemon:\n            self.show_status()\n            with self.daemon_context:\n                self.watch()\n        else:\n            self.watch()\n\n    def start(self):\n        for service in self._services:\n            service: BaseService\n            service.start()\n            self.files_preserve_map[service.name] = service.log_file\n\n        time.sleep(1)\n\n    def stop(self):\n        for service in self._services:\n            service: BaseService\n            service.stop(force=self.force_stop)\n\n        if self.stop_daemon:\n            self._stop_daemon()\n\n    # -- watch --\n    def watch(self):\n        while not self.EXIT_EVENT.is_set():\n            try:\n                _exit = self._watch()\n                if _exit:\n                    break\n                time.sleep(self.check_interval)\n            except KeyboardInterrupt:\n                print('Start stop services')\n                break\n        self.clean_up()\n\n    def _watch(self):\n        for service in self._services:\n            service: BaseService\n            service.watch()\n            if service.EXIT_EVENT.is_set():\n                self.EXIT_EVENT.set()\n                return True\n        return False\n    # -- end watch --\n\n    def clean_up(self):\n        if not self.EXIT_EVENT.is_set():\n            self.EXIT_EVENT.set()\n        self.stop()\n\n    def show_status(self):\n        for service in self._services:\n            service: BaseService\n            service.show_status()\n\n    # -- daemon --\n    def _stop_daemon(self):\n        if self.daemon_pid and self.daemon_is_running:\n            os.kill(self.daemon_pid, 15)\n        self.remove_daemon_pid()\n\n    def remove_daemon_pid(self):\n        if os.path.isfile(self.daemon_pid_filepath):\n            os.unlink(self.daemon_pid_filepath)\n\n    @property\n    def daemon_pid(self):\n        if not os.path.isfile(self.daemon_pid_filepath):\n            return 0\n        with open(self.daemon_pid_filepath) as f:\n            try:\n                pid = int(f.read().strip())\n            except ValueError:\n                pid = 0\n        return pid\n\n    @property\n    def daemon_is_running(self):\n        try:\n            os.kill(self.daemon_pid, 0)\n        except (OSError, ProcessLookupError):\n            return False\n        else:\n            return True\n\n    @property\n    def daemon_pid_filepath(self):\n        return os.path.join(TMP_DIR, 'mk.pid')\n\n    @property\n    def daemon_log_filepath(self):\n        return os.path.join(LOG_DIR, 'mk.log')\n\n    @property\n    def daemon_context(self):\n        daemon_log_file = open(self.daemon_log_filepath, 'a')\n        context = daemon.DaemonContext(\n            pidfile=pidfile.TimeoutPIDLockFile(self.daemon_pid_filepath),\n            signal_map={\n                signal.SIGTERM: lambda x, y: self.clean_up(),\n                signal.SIGHUP: 'terminate',\n            },\n            stdout=daemon_log_file,\n            stderr=daemon_log_file,\n            files_preserve=list(self.files_preserve_map.values()),\n            detach_process=True,\n        )\n        return context\n    # -- end daemon --\n"
  },
  {
    "path": "apps/common/management/commands/start.py",
    "content": "from .services.command import BaseActionCommand, Action\n\n\nclass Command(BaseActionCommand):\n    help = 'Start services'\n    action = Action.start.value\n"
  },
  {
    "path": "apps/common/management/commands/status.py",
    "content": "from .services.command import BaseActionCommand, Action\n\n\nclass Command(BaseActionCommand):\n    help = 'Show services status'\n    action = Action.status.value\n"
  },
  {
    "path": "apps/common/management/commands/stop.py",
    "content": "from .services.command import BaseActionCommand, Action\n\n\nclass Command(BaseActionCommand):\n    help = 'Stop services'\n    action = Action.stop.value\n"
  },
  {
    "path": "apps/common/middleware/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py\n    @date：2025/7/11 10:43\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/common/middleware/chat_headers_middleware.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： static_headers_middleware.py\n    @date：2024/3/13 18:26\n    @desc:\n\"\"\"\nfrom django.utils.deprecation import MiddlewareMixin\n\nfrom common.cache_data.application_access_token_cache import get_application_access_token\nfrom maxkb.const import CONFIG\n\n\nclass ChatHeadersMiddleware(MiddlewareMixin):\n    def process_response(self, request, response):\n\n        if request.path.startswith(CONFIG.get_chat_path()) and not request.path.startswith(\n                CONFIG.get_chat_path() + '/api'):\n            access_token = request.path.replace(CONFIG.get_chat_path() + '/', '')\n            if access_token.__contains__('/') or access_token == 'undefined':\n                return response\n            application_access_token = get_application_access_token(access_token, True)\n            if application_access_token is not None:\n                white_active = application_access_token.get('white_active', False)\n                white_list = application_access_token.get('white_list', [])\n                application_icon = application_access_token.get('application_icon')\n                application_name = application_access_token.get('application_name')\n                if white_active:\n                    # 添加自定义的响应头\n                    response[\n                        'Content-Security-Policy'] = f'frame-ancestors {\" \".join(white_list)}'\n                response.content = (response.content.decode('utf-8').replace(\n                    '<link rel=\"icon\" href=\"./favicon.ico\"/>',\n                    f'<link rel=\"icon\" href=\"{application_icon}\" />')\n                .replace('<title>MaxKB</title>', f'<title>{application_name}</title>').encode(\n                    \"utf-8\"))\n        return response\n"
  },
  {
    "path": "apps/common/middleware/cross_domain_middleware.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： cross_domain_middleware.py\n    @date：2024/5/8 13:36\n    @desc:\n\"\"\"\nfrom django.http import HttpResponse\nfrom django.utils.deprecation import MiddlewareMixin\n\nfrom common.cache_data.application_api_key_cache import get_application_api_key\n\n\nclass CrossDomainMiddleware(MiddlewareMixin):\n\n    def process_request(self, request):\n        if request.method == 'OPTIONS':\n            return HttpResponse(status=200,\n                                headers={\n                                    \"Access-Control-Allow-Origin\": \"*\",\n                                    \"Access-Control-Allow-Methods\": \"GET,POST,DELETE,PUT\",\n                                    \"Access-Control-Allow-Headers\": \"Origin,X-Requested-With,Content-Type,Accept,Authorization,token\"})\n\n    def process_response(self, request, response):\n        auth = request.META.get('HTTP_AUTHORIZATION')\n        origin = request.META.get('HTTP_ORIGIN')\n\n        if auth is not None and any([str(auth).startswith(prefix) for prefix in\n                                     ['Bearer application-', 'Bearer agent-']]) and origin is not None:\n            application_api_key = get_application_api_key(str(auth), True)\n            cross_domain_list = application_api_key.get('cross_domain_list', [])\n            allow_cross_domain = application_api_key.get('allow_cross_domain', False)\n            if allow_cross_domain:\n                response['Access-Control-Allow-Methods'] = 'GET,POST,DELETE,PUT'\n                response[\n                    'Access-Control-Allow-Headers'] = \"Origin,X-Requested-With,Content-Type,Accept,Authorization,token\"\n                if cross_domain_list is None or len(cross_domain_list) == 0:\n                    response['Access-Control-Allow-Origin'] = \"*\"\n                elif cross_domain_list.__contains__(origin):\n                    response['Access-Control-Allow-Origin'] = origin\n        return response\n"
  },
  {
    "path": "apps/common/middleware/doc_headers_middleware.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： static_headers_middleware.py\n    @date：2024/3/13 18:26\n    @desc:\n\"\"\"\n\nfrom django.http import HttpResponse\nfrom django.utils.deprecation import MiddlewareMixin\n\nfrom common.auth import TokenDetails, handles\nfrom maxkb.const import CONFIG\n\ncontent = \"\"\"\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <style>\n    /* 弹框内容样式 */\n    .modal-content {\n      background-color: #fefefe;\n      margin: 15% auto; /* 15% 从顶部和自动水平居中 */\n      padding: 20px;\n      border: 1px solid #888;\n      width: 80%; /* 宽度 */\n    }\n  </style>\n  <body>\n    <div class=\"modal-content\">\n      <input type=\"text\" id=\"auth-input\" />\n      <button id=\"auth\">认证</button>\n      <button id=\"goLogin\">去登录</button>\n    </div>\n    <script>\n      const setCookie = (name, value, days) => {\n        var expires = \"\";\n        if (days) {\n          var date = new Date();\n          date.setTime(date.getTime() + days * 2);\n          expires = \"; expires=\" + date.toUTCString();\n        }\n        document.cookie = name + \"=\" + (value || \"\") + expires + \"; path=/\";\n      };\n      const authToken = (token) => {\n        return new Promise((resolve, reject) => {\n          try {\n            var xhr = new XMLHttpRequest();\n            xhr.open(\"GET\", \"/api/user/profile\", true);\n            xhr.setRequestHeader(\"Content-Type\", \"application/json\");\n            const pathname = window.location.pathname;\n            if (token) {\n              xhr.setRequestHeader(\"Authorization\", \"Bearer \" + token);\n              xhr.onreadystatechange = function () {\n                if (xhr.readyState === 4) {\n                  if (xhr.status === 200) {\n                    resolve(true);\n                  } else {\n                    reject(true);\n                  }\n                }\n              };\n\n              xhr.send();\n            }\n          } catch (e) {\n            reject(false);\n          }\n        });\n      };\n      window.onload = () => {\n        const token = localStorage.getItem(\"token\");\n        authToken(token)\n          .then(() => {\n            setCookie(\"Authorization\", \"Bearer \" + token);\n            window.location.href = window.location.pathname;\n          })\n          .catch((e) => {});\n      };\n      // 获取元素\n      const auth = document.getElementById(\"auth\");\n      const goLogin = document.getElementById(\"goLogin\");\n\n      // 打开弹框函数\n      auth.onclick = ()=> {\n        const authInput = document.getElementById(\"auth-input\");\n        const token = authInput.value\n        authToken(token)\n          .then(() => {\n            setCookie(\"Authorization\", \"Bearer \" + token);\n            window.location.href = window.location.pathname;\n          })\n          .catch((e) => {\n            alert(\"令牌错误\");\n          });\n      };\n\n      // 去系统的登录页面\n      goLogin.onclick =  ()=> {\n        window.location.href = \"/admin/login\";\n      };\n    </script>\n  </body>\n</html>\n\n\"\"\".replace(\"/api/user/profile\", CONFIG.get_admin_path() + '/api/user/profile').replace('/admin/login',\n                                                                                        CONFIG.get_admin_path() + '/login')\n\n\nclass DocHeadersMiddleware(MiddlewareMixin):\n    def process_response(self, request, response):\n        if request.path.startswith(CONFIG.get_admin_path() + '/api-doc/') or request.path.startswith(\n                CONFIG.get_chat_path() + '/api-doc/'):\n            auth = request.COOKIES.get('Authorization')\n            if auth is None:\n                return HttpResponse(content)\n            else:\n                if not auth.startswith(\"Bearer \"):\n                    return HttpResponse(content)\n                try:\n                    token = auth[7:]\n                    token_details = TokenDetails(token)\n                    for handle in handles:\n                        if handle.support(request, token, token_details.get_token_details):\n                            handle.handle(request, token, token_details.get_token_details)\n                            return response\n                    return HttpResponse(content)\n                except Exception as e:\n                    return HttpResponse(content)\n        return response\n"
  },
  {
    "path": "apps/common/middleware/gzip.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： gzip.py\n    @date：2025/2/27 10:03\n    @desc:\n\"\"\"\nfrom django.utils.cache import patch_vary_headers\nfrom django.utils.deprecation import MiddlewareMixin\nfrom django.utils.regex_helper import _lazy_re_compile\nfrom django.utils.text import compress_sequence, compress_string\n\nre_accepts_gzip = _lazy_re_compile(r\"\\bgzip\\b\")\n\n\nclass GZipMiddleware(MiddlewareMixin):\n    \"\"\"\n    Compress content if the browser allows gzip compression.\n    Set the Vary header accordingly, so that caches will base their storage\n    on the Accept-Encoding header.\n    \"\"\"\n\n    max_random_bytes = 100\n\n    def process_response(self, request, response):\n        if request.method != 'GET' or request.path.startswith('/api'):\n            return response\n        # It's not worth attempting to compress really short responses.\n        if not response.streaming and len(response.content) < 200:\n            return response\n\n        # Avoid gzipping if we've already got a content-encoding.\n        if response.has_header(\"Content-Encoding\"):\n            return response\n\n        patch_vary_headers(response, (\"Accept-Encoding\",))\n\n        ae = request.META.get(\"HTTP_ACCEPT_ENCODING\", \"\")\n        if not re_accepts_gzip.search(ae):\n            return response\n\n        if response.streaming:\n            if response.is_async:\n                # pull to lexical scope to capture fixed reference in case\n                # streaming_content is set again later.\n                original_iterator = response.streaming_content\n\n                async def gzip_wrapper():\n                    async for chunk in original_iterator:\n                        yield compress_string(\n                            chunk,\n                            max_random_bytes=self.max_random_bytes,\n                        )\n\n                response.streaming_content = gzip_wrapper()\n            else:\n                response.streaming_content = compress_sequence(\n                    response.streaming_content,\n                    max_random_bytes=self.max_random_bytes,\n                )\n            # Delete the `Content-Length` header for streaming content, because\n            # we won't know the compressed size until we stream it.\n            del response.headers[\"Content-Length\"]\n        else:\n            # Return the compressed content only if it's actually shorter.\n            compressed_content = compress_string(\n                response.content,\n                max_random_bytes=self.max_random_bytes,\n            )\n            if len(compressed_content) >= len(response.content):\n                return response\n            response.content = compressed_content\n            response.headers[\"Content-Length\"] = str(len(response.content))\n\n        # If there is a strong ETag, make it weak to fulfill the requirements\n        # of RFC 9110 Section 8.8.1 while also allowing conditional request\n        # matches on ETags.\n        etag = response.get(\"ETag\")\n        if etag and etag.startswith('\"'):\n            response.headers[\"ETag\"] = \"W/\" + etag\n        response.headers[\"Content-Encoding\"] = \"gzip\"\n\n        return response\n"
  },
  {
    "path": "apps/common/mixins/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/mixins/api_mixin.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： ApiMixin.py\n    @date：2025/4/14 18:03\n    @desc:\n\"\"\"\n\n\nclass APIMixin:\n    @staticmethod\n    def get_request():\n        return None\n\n    @staticmethod\n    def get_response():\n        return None\n\n    @staticmethod\n    def get_parameters():\n        \"\"\"\n         return OpenApiParameter(\n            # 参数的名称是done\n            name=\"done\",\n            # 对参数的备注\n            description=\"是否完成\",\n            # 指定参数的类型\n            type=OpenApiTypes.BOOL,\n            location=OpenApiParameter.QUERY,\n            # 指定必须给\n            required=True,\n            # 指定枚举项\n            enum=[True, False],\n        )\n\n        \"\"\"\n        return None\n"
  },
  {
    "path": "apps/common/mixins/app_model_mixin.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： app_model_mixin.py\n    @date：2023/9/21 9:41\n    @desc:\n\"\"\"\nfrom django.db import models\n\n\nclass AppModelMixin(models.Model):\n    objects = models.Manager()\n\n    create_time = models.DateTimeField(verbose_name=\"创建时间\", auto_now_add=True, db_index=True)\n    update_time = models.DateTimeField(verbose_name=\"修改时间\", auto_now=True, db_index=True)\n\n    class Meta:\n        abstract = True\n        ordering = ['create_time']\n"
  },
  {
    "path": "apps/common/result/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py\n    @date：2025/4/14 15:45\n    @desc:\n\"\"\"\nfrom .api import *\nfrom .result import *\n"
  },
  {
    "path": "apps/common/result/api.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： api.py\n    @date：2025/4/14 15:20\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\n\nclass DefaultResultSerializer(serializers.Serializer):\n    \"\"\"\n    响应结果\n    \"\"\"\n    code = serializers.IntegerField(required=True, help_text=_('response code'), label=_('response code'))\n    message = serializers.CharField(required=False, default=\"success\", help_text=_('error prompt'),\n                                    label=_('error prompt'))\n    data = serializers.BooleanField(required=False, default=True)\n\n\nclass ResultSerializer(serializers.Serializer):\n    \"\"\"\n    响应结果\n    \"\"\"\n    code = serializers.IntegerField(required=True, help_text=_('response code'), label=_('response code'))\n    message = serializers.CharField(required=False, default=\"success\", help_text=_('error prompt'),\n                                    label=_('error prompt'))\n\n    def get_data(self):\n        pass\n\n    def __init__(self, **kwargs):\n        self.fields['data'] = self.get_data()\n        super().__init__(**kwargs)\n\n\nclass PageDataResponse(serializers.Serializer):\n    \"\"\"\n    分页数据\n    \"\"\"\n    total = serializers.IntegerField(required=True, label=_('total number of data'))\n    current = serializers.IntegerField(required=True, label=_('current page'))\n    size = serializers.IntegerField(required=True, label=_('page size'))\n\n    def __init__(self, records, **kwargs):\n        self.fields['records'] = records\n        super().__init__(**kwargs)\n\n\nclass ResultPageSerializer(ResultSerializer):\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.fields['data'] = PageDataResponse(self.get_data())\n"
  },
  {
    "path": "apps/common/result/result.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： result.py\n    @date：2025/4/14 15:18\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom django.http import JsonResponse\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import status\n\n\nclass Page(dict):\n    \"\"\"\n    分页对象\n    \"\"\"\n\n    def __init__(self, total: int, records: List, current_page: int, page_size: int, **kwargs):\n        super().__init__(**{'total': total, 'records': records, 'current': current_page, 'size': page_size})\n\n\nclass Result(JsonResponse):\n    charset = 'utf-8'\n    \"\"\"\n     接口统一返回对象\n    \"\"\"\n\n    def __init__(self, code=200, message=_('Success'), data=None, response_status=status.HTTP_200_OK, **kwargs):\n        back_info_dict = {\"code\": code, \"message\": message, 'data': data}\n        super().__init__(data=back_info_dict, status=response_status, **kwargs)\n\n\ndef success(data, **kwargs):\n    \"\"\"\n    获取一个成功的响应对象\n    :param data: 接口响应数据\n    :return: 请求响应对象\n    \"\"\"\n    return Result(data=data, **kwargs)\n\n\ndef error(message, **kwargs):\n    \"\"\"\n    获取一个失败的响应对象\n    :param message: 错误提示\n    :return: 接口响应对象\n    \"\"\"\n    return Result(code=500, message=message, **kwargs)\n"
  },
  {
    "path": "apps/common/sql/list_embedding_text.sql",
    "content": "SELECT\n\tproblem_paragraph_mapping.\"id\" AS \"source_id\",\n\tparagraph.document_id AS document_id,\n\tparagraph.\"id\" AS paragraph_id,\n\tproblem.knowledge_id AS knowledge_id,\n\t0 AS source_type,\n\tproblem.\"content\" AS \"text\",\n\tparagraph.is_active AS is_active,\n\tparagraph.chunks AS chunks\nFROM\n\tproblem problem\n\tLEFT JOIN problem_paragraph_mapping problem_paragraph_mapping ON problem_paragraph_mapping.problem_id=problem.\"id\"\n\tLEFT JOIN paragraph paragraph ON paragraph.\"id\" = problem_paragraph_mapping.paragraph_id\n ${problem}\n\nUNION\nSELECT\n\tparagraph.\"id\" AS \"source_id\",\n\tparagraph.document_id AS document_id,\n\tparagraph.\"id\" AS paragraph_id,\n\tparagraph.knowledge_id AS knowledge_id,\n\t1 AS source_type,\n\tconcat_ws(E'\\n',paragraph.title,paragraph.\"content\") AS \"text\",\n\tparagraph.is_active AS is_active,\n\tparagraph.chunks AS chunks\nFROM\n\tparagraph paragraph\n\n ${paragraph}"
  },
  {
    "path": "apps/common/template/email_template_en.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"/>\n    <meta name=\"description\" content=\"email code\"/>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n</head>\n<!--邮箱验证码模板-->\n<body>\n<div style=\"background-color: #ececec; padding: 35px\">\n    <table\n            cellpadding=\"0\"\n            style=\"\n          width: 800px;\n          height: 100%;\n          margin: 0px auto;\n          text-align: left;\n          position: relative;\n          border-top-left-radius: 5px;\n          border-top-right-radius: 5px;\n          border-bottom-right-radius: 5px;\n          border-bottom-left-radius: 5px;\n          font-size: 14px;\n          line-height: 1.5;\n          box-shadow: rgb(153, 153, 153) 0px 0px 5px;\n          border-collapse: collapse;\n          background-position: initial initial;\n          background-repeat: initial initial;\n          background: #fff;\n        \"\n    >\n        <tbody>\n        <tr>\n            <th\n                    valign=\"middle\"\n                    style=\"\n                background: linear-gradient(\n                  90deg,\n                  #ebf1ff 24.34%,\n                  #e5fbf8 56.18%,\n                  #f2ebfe 90.18%\n                );\n                height: 25px;\n                line-height: 25px;\n                padding: 15px 35px;\n                border-bottom-width: 1px;\n                border-bottom-color: rgba(51, 112, 255);\n                border-top-left-radius: 5px;\n                border-top-right-radius: 5px;\n                border-bottom-right-radius: 0px;\n                border-bottom-left-radius: 0px;\n              \"\n            >\n                <div\n                        style=\"\n                  width: 500px;\n                  background: linear-gradient(180deg, #3370FF 0%, #7f3bf5 100%);\n                  -webkit-background-clip: text;\n                  font-size: 16px;\n                  -webkit-text-fill-color: transparent;\n                  font-style: normal;\n                  font-weight: 900;\n                  color: #1f2329;\n                \"\n                >\n                    Powerful and easy-to-use enterprise level intelligent agent platform\n                </div>\n            </th>\n        </tr>\n        <tr>\n            <td style=\"word-break: break-all\">\n                <div\n                        style=\"\n                  padding: 25px 35px 40px;\n                  background-color: #fff;\n                  opacity: 0.8;\n                \"\n                >\n                    <h2 style=\"margin: 5px 0px\">\n                        <font color=\"#333333\" style=\"line-height: 20px\">\n                            <font style=\"line-height: 22px\" size=\"4\">\n\n                                Dear user:</font\n                            >\n                        </font>\n                    </h2>\n                    <!-- 中文 -->\n                    <p>\n                        <font color=\"#ff8c00\" style=\"font-weight: 900\">${code}</font\n                        >&nbsp;&nbsp;This is your dynamic verification code. Please fill it in within 30 minutes. To\n                        protect the security of your account, please do not provide this verification code to anyone.\n                    </p>\n                    <br/>\n\n                    <div style=\"width: 100%; margin: 0 auto\">\n                        <div\n                                style=\"\n                      padding: 10px 10px 0;\n                      border-top: 1px solid #ccc;\n                      color: #747474;\n                      margin-bottom: 20px;\n                      line-height: 1.3em;\n                      font-size: 12px;\n                      text-align: right;\n                    \"\n                        >\n                            <p>Intelligent knowledge base project team</p>\n                            <br/>\n                            <p>\n                                Please do not reply to this system email<br/>\n                            </p>\n                            <!--<p>©***</p>-->\n                        </div>\n                    </div>\n                </div>\n            </td>\n        </tr>\n        </tbody>\n    </table>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "apps/common/template/email_template_zh.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"description\" content=\"email code\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n  </head>\n  <!--邮箱验证码模板-->\n  <body>\n    <div style=\"background-color: #ececec; padding: 35px\">\n      <table\n        cellpadding=\"0\"\n        style=\"\n          width: 800px;\n          height: 100%;\n          margin: 0px auto;\n          text-align: left;\n          position: relative;\n          border-top-left-radius: 5px;\n          border-top-right-radius: 5px;\n          border-bottom-right-radius: 5px;\n          border-bottom-left-radius: 5px;\n          font-size: 14px;\n          line-height: 1.5;\n          box-shadow: rgb(153, 153, 153) 0px 0px 5px;\n          border-collapse: collapse;\n          background-position: initial initial;\n          background-repeat: initial initial;\n          background: #fff;\n        \"\n      >\n        <tbody>\n          <tr>\n            <th\n              valign=\"middle\"\n              style=\"\n                background: linear-gradient(\n                  90deg,\n                  #ebf1ff 24.34%,\n                  #e5fbf8 56.18%,\n                  #f2ebfe 90.18%\n                );\n                height: 25px;\n                line-height: 25px;\n                padding: 15px 35px;\n                border-bottom-width: 1px;\n                border-bottom-color: rgba(51, 112, 255);\n                border-top-left-radius: 5px;\n                border-top-right-radius: 5px;\n                border-bottom-right-radius: 0px;\n                border-bottom-left-radius: 0px;\n              \"\n            >\n              <div\n                style=\"\n                  width: 230px;\n                  background: linear-gradient(180deg, #3370FF 0%, #7f3bf5 100%);\n                  -webkit-background-clip: text;\n                  font-size: 24px;\n                  -webkit-text-fill-color: transparent;\n                  font-style: normal;\n                  font-weight: 900;\n                  color: #1f2329;\n                \"\n              >\n                强大易用的企业级智能体平台\n              </div>\n            </th>\n          </tr>\n          <tr>\n            <td style=\"word-break: break-all\">\n              <div\n                style=\"\n                  padding: 25px 35px 40px;\n                  background-color: #fff;\n                  opacity: 0.8;\n                \"\n              >\n                <h2 style=\"margin: 5px 0px\">\n                  <font color=\"#333333\" style=\"line-height: 20px\">\n                    <font style=\"line-height: 22px\" size=\"4\">\n                      尊敬的用户：</font\n                    >\n                  </font>\n                </h2>\n                <!-- 中文 -->\n                <p>\n                  <font color=\"#ff8c00\" style=\"font-weight: 900\">${code}</font\n                  >&nbsp;&nbsp;为您的动态验证码，请于30分钟内填写，为保障帐户安全，请勿向任何人提供此验证码。\n                </p>\n                <br />\n\n                <div style=\"width: 100%; margin: 0 auto\">\n                  <div\n                    style=\"\n                      padding: 10px 10px 0;\n                      border-top: 1px solid #ccc;\n                      color: #747474;\n                      margin-bottom: 20px;\n                      line-height: 1.3em;\n                      font-size: 12px;\n                      text-align: right;\n                    \"\n                  >\n                    <p>智能知识库项目组</p>\n                    <br />\n                    <p>\n                      此为系统邮件，请勿回复<br />\n                    </p>\n                    <!--<p>©***</p>-->\n                  </div>\n                </div>\n              </div>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "apps/common/template/email_template_zh_Hant.html",
    "content": "<!DOCTYPE html>\n<html xmlns:th=\"http://www.thymeleaf.org\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"description\" content=\"email code\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n  </head>\n  <!--邮箱验证码模板-->\n  <body>\n    <div style=\"background-color: #ececec; padding: 35px\">\n      <table\n        cellpadding=\"0\"\n        style=\"\n          width: 800px;\n          height: 100%;\n          margin: 0px auto;\n          text-align: left;\n          position: relative;\n          border-top-left-radius: 5px;\n          border-top-right-radius: 5px;\n          border-bottom-right-radius: 5px;\n          border-bottom-left-radius: 5px;\n          font-size: 14px;\n          line-height: 1.5;\n          box-shadow: rgb(153, 153, 153) 0px 0px 5px;\n          border-collapse: collapse;\n          background-position: initial initial;\n          background-repeat: initial initial;\n          background: #fff;\n        \"\n      >\n        <tbody>\n          <tr>\n            <th\n              valign=\"middle\"\n              style=\"\n                background: linear-gradient(\n                  90deg,\n                  #ebf1ff 24.34%,\n                  #e5fbf8 56.18%,\n                  #f2ebfe 90.18%\n                );\n                height: 25px;\n                line-height: 25px;\n                padding: 15px 35px;\n                border-bottom-width: 1px;\n                border-bottom-color: rgba(51, 112, 255);\n                border-top-left-radius: 5px;\n                border-top-right-radius: 5px;\n                border-bottom-right-radius: 0px;\n                border-bottom-left-radius: 0px;\n              \"\n            >\n              <div\n                style=\"\n                  width: 230px;\n                  background: linear-gradient(180deg, #3370FF 0%, #7f3bf5 100%);\n                  -webkit-background-clip: text;\n                  font-size: 24px;\n                  -webkit-text-fill-color: transparent;\n                  font-style: normal;\n                  font-weight: 900;\n                  color: #1f2329;\n                \"\n              >\n\n                强大易用的企業級智能體平臺\n              </div>\n            </th>\n          </tr>\n          <tr>\n            <td style=\"word-break: break-all\">\n              <div\n                style=\"\n                  padding: 25px 35px 40px;\n                  background-color: #fff;\n                  opacity: 0.8;\n                \"\n              >\n                <h2 style=\"margin: 5px 0px\">\n                  <font color=\"#333333\" style=\"line-height: 20px\">\n                    <font style=\"line-height: 22px\" size=\"4\">\n\n                    尊敬的用戶：</font\n                    >\n                  </font>\n                </h2>\n                <!-- 中文 -->\n                <p>\n                  <font color=\"#ff8c00\" style=\"font-weight: 900\">${code}</font\n                  >&nbsp;&nbsp;為您的動態驗證碼，請於30分鐘內填寫，為保障帳戶安全，請勿向任何人提供此驗證碼。\n                </p>\n                <br />\n\n                <div style=\"width: 100%; margin: 0 auto\">\n                  <div\n                    style=\"\n                      padding: 10px 10px 0;\n                      border-top: 1px solid #ccc;\n                      color: #747474;\n                      margin-bottom: 20px;\n                      line-height: 1.3em;\n                      font-size: 12px;\n                      text-align: right;\n                    \"\n                  >\n                    <p>智慧知識庫專案組</p>\n                    <br />\n                    <p>\n                      此為系統郵件，請勿回覆<br />\n                    </p>\n                    <!--<p>©***</p>-->\n                  </div>\n                </div>\n              </div>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "apps/common/utils/__init__.py",
    "content": ""
  },
  {
    "path": "apps/common/utils/cache_util.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： cache_util.py\n    @date：2024/7/24 19:23\n    @desc:\n\"\"\"\nfrom django.core.cache import cache\n\n\ndef get_data_by_default_cache(key: str, get_data, cache_instance=cache, version=None, kwargs=None):\n    \"\"\"\n    获取数据, 先从缓存中获取,如果获取不到再调用get_data 获取数据\n    @param kwargs:          get_data所需参数\n    @param key:             key\n    @param get_data:        获取数据函数\n    @param cache_instance:  cache实例\n    @param version:         版本用于隔离\n    @return:\n    \"\"\"\n    if kwargs is None:\n        kwargs = {}\n    if cache_instance.has_key(key, version=version):\n        return cache_instance.get(key, version=version)\n    data = get_data(**kwargs)\n    cache_instance.add(key, data, version=version)\n    return data\n\n\ndef set_data_by_default_cache(key: str, get_data, cache_instance=cache, version=None):\n    data = get_data()\n    cache_instance.set(key, data, version=version)\n    return data\n\n\ndef get_cache(cache_key, use_get_data: any = True, cache_instance=cache, version=None):\n    def inner(get_data):\n        def run(*args, **kwargs):\n            key = cache_key(*args, **kwargs) if callable(cache_key) else cache_key\n            is_use_get_data = use_get_data(*args, **kwargs) if callable(use_get_data) else use_get_data\n            if is_use_get_data:\n                if cache_instance.has_key(key, version=version):\n                    return cache_instance.get(key, version=version)\n                data = get_data(*args, **kwargs)\n                cache_instance.add(key, data, timeout=None, version=version)\n                return data\n            data = get_data(*args, **kwargs)\n            cache_instance.set(key, data, timeout=None, version=version)\n            return data\n\n        return run\n\n    return inner\n\n\ndef del_cache(cache_key, cache_instance=cache, version=None):\n    def inner(func):\n        def run(*args, **kwargs):\n            key = cache_key(*args, **kwargs) if callable(cache_key) else cache_key\n            func(*args, **kwargs)\n            cache_instance.delete(key, version=version)\n\n        return run\n\n    return inner\n"
  },
  {
    "path": "apps/common/utils/chat_link_code.py",
    "content": "\"\"\"\n    @project: MaxKB\n    @Author: niu\n    @file: chat_link_code.py\n    @date: 2026/2/9 11:31\n    @desc:\n\"\"\"\nfrom typing import Union\n\nimport uuid_utils.compat as uuid\n\n\nclass UUIDEncoder:\n\n    BASE62_ALPHABET = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"\n    BASE62_LEN = 62\n\n    @staticmethod\n    def encode(uuid_obj: Union[uuid.UUID, str] = None) -> str:\n\n        if uuid_obj is None:\n            uuid_obj = uuid.uuid7()\n        elif isinstance(uuid_obj, str):\n            uuid_obj = uuid.UUID(uuid_obj)\n\n        num = int(uuid_obj.hex, 16)\n\n        if num == 0:\n            return UUIDEncoder.BASE62_ALPHABET[0]\n\n        result = []\n        while num:\n            num, rem = divmod(num,62)\n            result.append(UUIDEncoder.BASE62_ALPHABET[rem])\n        return ''.join(reversed(result))\n\n    @staticmethod\n    def decode(encoded: str) -> uuid.UUID:\n\n        num = 0\n        for char in encoded:\n            num = num * UUIDEncoder.BASE62_LEN + UUIDEncoder.BASE62_ALPHABET.index(char)\n\n        return uuid.UUID(int=num)\n\n    @staticmethod\n    def decode_to_str(encoded: str) -> str:\n        return str(UUIDEncoder.decode(encoded))"
  },
  {
    "path": "apps/common/utils/common.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： common.py\n    @date：2025/4/14 18:23\n    @desc:\n\"\"\"\nimport hashlib\nimport io\nimport mimetypes\nimport pickle\nimport random\nimport re\nimport shutil\nimport uuid\nfrom functools import reduce\nfrom typing import List, Dict\n\nfrom django.core.files.uploadedfile import InMemoryUploadedFile\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext as _\nfrom pydub import AudioSegment\n\nfrom ..database_model_manage.database_model_manage import DatabaseModelManage\nfrom ..exception.app_exception import AppApiException\n\n\ndef password_encrypt(row_password):\n    \"\"\"\n    密码 md5加密\n    :param row_password: 密码\n    :return:  加密后密码\n    \"\"\"\n    md5 = hashlib.md5()  # 2，实例化md5() 方法\n    md5.update(row_password.encode())  # 3，对字符串的字节类型加密\n    result = md5.hexdigest()  # 4，加密\n    return result\n\n\ndef group_by(list_source: List, key):\n    \"\"\"\n    將數組分組\n    :param list_source: 需要分組的數組\n    :param key: 分組函數\n    :return: key->[]\n    \"\"\"\n    result = {}\n    for e in list_source:\n        k = key(e)\n        array = result.get(k) if k in result else []\n        array.append(e)\n        result[k] = array\n    return result\n\n\nSAFE_CHAR_SET = (\n        [chr(i) for i in range(65, 91) if chr(i) not in {'I', 'O'}] +  # 大写字母 A-H, J-N, P-Z\n        [chr(i) for i in range(97, 123) if chr(i) not in {'i', 'l', 'o'}] +  # 小写字母 a-h, j-n, p-z\n        [str(i) for i in range(10) if str(i) not in {'0', '1', '7'}]  # 数字 2-6, 8-9\n)\n\n\ndef get_random_chars(number=4):\n    if number <= 0:\n        return \"\"\n    return ''.join(random.choices(SAFE_CHAR_SET, k=number))\n\n\ndef encryption(message: str):\n    \"\"\"\n        加密敏感字段数据  加密方式是 如果密码是 1234567890  那么给前端则是 123******890\n    :param message:\n    :return:\n    \"\"\"\n    if not message:  # 处理空字符串情况\n        return \"***************\"\n    max_pre_len = 8\n    max_post_len = 4\n    message_len = len(message)\n    pre_len = int(message_len / 5 * 2)\n    post_len = int(message_len / 5 * 1)\n    pre_str = \"\".join([message[index] for index in\n                       range(0, max_pre_len if pre_len > max_pre_len else 1 if pre_len <= 0 else int(pre_len))])\n    end_str = \"\".join(\n        [message[index] for index in\n         range(message_len - (int(post_len) if pre_len < max_post_len else max_post_len), message_len)])\n    content = \"***************\"\n    return pre_str + content + end_str\n\n\ndef _remove_empty_lines(text):\n    if not isinstance(text, str):\n        raise AppApiException(500, _('Text-to-speech node, the text content must be of string type'))\n    if not text:\n        raise AppApiException(500, _('Text-to-speech node, the text content cannot be empty'))\n    result = '\\n'.join(line for line in text.split('\\n') if line.strip())\n    return markdown_to_plain_text(result)\n\n\ndef markdown_to_plain_text(md: str) -> str:\n    # 移除图片 ![alt](url)\n    text = re.sub(r'!\\[.*?\\]\\(.*?\\)', '', md)\n    # 移除链接 [text](url)\n    text = re.sub(r'\\[([^\\]]+)\\]\\([^)]+\\)', r'\\1', text)\n    # 移除 Markdown 标题符号 (#, ##, ###)\n    text = re.sub(r'^#{1,6}\\s+', '', text, flags=re.MULTILINE)\n    # 移除加粗 **text** 或 __text__\n    text = re.sub(r'\\*\\*(.*?)\\*\\*', r'\\1', text)\n    text = re.sub(r'__(.*?)__', r'\\1', text)\n    # 移除斜体 *text* 或 _text_\n    text = re.sub(r'\\*(.*?)\\*', r'\\1', text)\n    text = re.sub(r'_(.*?)_', r'\\1', text)\n    # 移除行内代码 `code`\n    text = re.sub(r'`(.*?)`', r'\\1', text)\n    # 移除代码块 ```code```\n    text = re.sub(r'```[\\s\\S]*?```', '', text)\n    # 移除多余的换行符\n    text = re.sub(r'\\n{2,}', '\\n', text)\n    # 使用正则表达式去除所有 HTML 标签\n    text = re.sub(r'<[^>]+>', '', text)\n    # 先移除特定媒体标签（优先级高于通用HTML标签移除）\n    text = re.sub(r'<(?:audio|video)(?:\\s+[^>]*)?>[\\s\\S]*?(?:</(?:audio|video)>)?', '', text, flags=re.IGNORECASE)\n    text = re.sub(r'<img[^>]*>', '', text)  # 匹配图片标签\n    # 去除多余的空白字符（包括换行符、制表符等）\n    text = re.sub(r'\\s+', ' ', text)\n    # 去除表单渲染\n    re.sub(r'<form_rander>[\\s\\S]*?<\\/form_rander>', '', text)\n    # 去除首尾空格\n    text = text.strip()\n    return text\n\n\ndef get_file_content(path):\n    with open(path, \"r\", encoding='utf-8') as file:\n        content = file.read()\n    return content\n\n\ndef sub_array(array: List, item_num=10):\n    result = []\n    temp = []\n    for item in array:\n        temp.append(item)\n        if len(temp) >= item_num:\n            result.append(temp)\n            temp = []\n    if len(temp) > 0:\n        result.append(temp)\n    return result\n\n\ndef bytes_to_uploaded_file(file_bytes, file_name=\"file.txt\"):\n    content_type, _ = mimetypes.guess_type(file_name)\n    if content_type is None:\n        # 如果未能识别，设置为默认的二进制文件类型\n        content_type = \"application/octet-stream\"\n    # 创建一个内存中的字节流对象\n    file_stream = io.BytesIO(file_bytes)\n\n    # 获取文件大小\n    file_size = len(file_bytes)\n\n    # 创建 InMemoryUploadedFile 对象\n    uploaded_file = InMemoryUploadedFile(\n        file=file_stream,\n        field_name=None,\n        name=file_name,\n        content_type=content_type,\n        size=file_size,\n        charset=None,\n    )\n    return uploaded_file\n\n\ndef any_to_amr(any_path, amr_path):\n    \"\"\"\n    把任意格式转成amr文件\n    \"\"\"\n    if any_path.endswith(\".amr\"):\n        shutil.copy2(any_path, amr_path)\n        return\n    if any_path.endswith(\".sil\") or any_path.endswith(\".silk\") or any_path.endswith(\".slk\"):\n        raise NotImplementedError(\"Not support file type: {}\".format(any_path))\n    audio = AudioSegment.from_file(any_path)\n    audio = audio.set_frame_rate(8000)  # only support 8000\n    audio.export(amr_path, format=\"amr\")\n    return audio.duration_seconds * 1000\n\n\ndef any_to_mp3(any_path, mp3_path):\n    \"\"\"\n    把任意格式转成mp3文件\n    \"\"\"\n    if any_path.endswith(\".mp3\"):\n        shutil.copy2(any_path, mp3_path)\n        return\n    if any_path.endswith(\".sil\") or any_path.endswith(\".silk\") or any_path.endswith(\".slk\"):\n        sil_to_wav(any_path, any_path)\n        any_path = mp3_path\n    audio = AudioSegment.from_file(any_path)\n    audio = audio.set_frame_rate(16000)\n    audio.export(mp3_path, format=\"mp3\")\n\n\ndef sil_to_wav(silk_path, wav_path, rate: int = 24000):\n    \"\"\"\n    silk 文件转 wav\n    \"\"\"\n    try:\n        import pysilk\n    except ImportError:\n        raise AppApiException(\"import pysilk failed, wechaty voice message will not be supported.\")\n    wav_data = pysilk.decode_file(silk_path, to_wav=True, sample_rate=rate)\n    with open(wav_path, \"wb\") as f:\n        f.write(wav_data)\n\n\ndef split_and_transcribe(file_path, model, max_segment_length_ms=59000, audio_format=\"mp3\"):\n    audio_data = AudioSegment.from_file(file_path, format=audio_format)\n    audio_length_ms = len(audio_data)\n\n    if audio_length_ms <= max_segment_length_ms:\n        return model.speech_to_text(io.BytesIO(audio_data.export(format=audio_format).read()))\n\n    full_text = []\n    for start_ms in range(0, audio_length_ms, max_segment_length_ms):\n        end_ms = min(audio_length_ms, start_ms + max_segment_length_ms)\n        segment = audio_data[start_ms:end_ms]\n        text = model.speech_to_text(io.BytesIO(segment.export(format=audio_format).read()))\n        if isinstance(text, str):\n            full_text.append(text)\n    return ' '.join(full_text)\n\n\ndef query_params_to_single_dict(query_params: Dict):\n    return reduce(lambda x, y: {**x, **y}, list(\n        filter(lambda item: item is not None, [({key: value} if value is not None and len(value) > 0 else None) for\n                                               key, value in\n                                               query_params.items()])), {})\n\n\ndef valid_license(model=None, count=None, message=None):\n    def inner(func):\n        def run(*args, **kwargs):\n            is_license_valid = DatabaseModelManage.get_model('license_is_valid')\n            is_license_valid = is_license_valid() if is_license_valid() is not None else False\n            record_count = QuerySet(model).count()\n\n            if not is_license_valid and record_count >= count:\n                error_message = message or _(\n                    'Limit {count} exceeded, please contact us (https://fit2cloud.com/).').format(\n                    count=count)\n                raise AppApiException(400, error_message)\n\n            return func(*args, **kwargs)\n\n        return run\n\n    return inner\n\n\ndef post(post_function):\n    def inner(func):\n        def run(*args, **kwargs):\n            result = func(*args, **kwargs)\n            return post_function(*result)\n\n        return run\n\n    return inner\n\n\ndef parse_md_image(content: str):\n    matches = re.finditer(\"!\\[.*?\\]\\(.*?\\)\", content)\n    image_list = [match.group() for match in matches]\n    return image_list\n\n\ndef bulk_create_in_batches(model, data, batch_size=1000):\n    if len(data) == 0:\n        return\n    for i in range(0, len(data), batch_size):\n        batch = data[i:i + batch_size]\n        model.objects.bulk_create(batch)\n\n\ndef get_sha256_hash(_v: str | bytes):\n    sha256 = hashlib.sha256()\n    if isinstance(_v, str):\n        sha256.update(_v.encode())\n    else:\n        sha256.update(_v)\n    return sha256.hexdigest()\n\n\nALLOWED_CLASSES = {\n    (\"builtins\", \"dict\"),\n    ('uuid', 'UUID'),\n    (\"application.serializers.application\", \"MKInstance\"),\n    (\"tools.serializers.tool\", \"ToolInstance\"),\n    (\"knowledge.serializers.knowledge_workflow\", \"KBWFInstance\")\n}\n\n\nclass RestrictedUnpickler(pickle.Unpickler):\n\n    def find_class(self, module, name):\n        if (module, name) in ALLOWED_CLASSES:\n            return super().find_class(module, name)\n        raise pickle.UnpicklingError(\"global '%s.%s' is forbidden\" %\n                                     (module, name))\n\n\ndef restricted_loads(s):\n    \"\"\"Helper function analogous to pickle.loads().\"\"\"\n    return RestrictedUnpickler(io.BytesIO(s)).load()\n\n\ndef flat_map(array: List[List]):\n    \"\"\"\n    将二位数组转为一维数组\n    :param array: 二维数组\n    :return: 一维数组\n    \"\"\"\n    result = []\n    for e in array:\n        result += e\n    return result\n\n\ndef parse_image(content: str):\n    matches = re.finditer(\"!\\[.*?\\]\\(\\.\\/oss\\/(image|file)\\/.*?\\)\", content)\n    image_list = [match.group() for match in matches]\n    return image_list\n\n\ndef generate_uuid(tag: str):\n    return str(uuid.uuid5(uuid.NAMESPACE_DNS, tag))\n\n\ndef filter_workspace(query_list):\n    return [q for q in query_list if q.name != \"workspace_id\"]\n\n\ndef filter_special_character(_str):\n    \"\"\"\n    过滤特殊字符\n    \"\"\"\n    s_list = [\"\\\\u0000\"]\n    for t in s_list:\n        _str = _str.replace(t, '')\n    return _str\n\n\ndef is_valid_uuid(uuid_string):\n    \"\"\"判断字符串是否为有效的UUID\"\"\"\n    try:\n        uuid_obj = uuid.UUID(uuid_string)\n        return str(uuid_obj) == uuid_string\n    except ValueError:\n        return False\n"
  },
  {
    "path": "apps/common/utils/fork.py",
    "content": "import copy\nimport re\nimport traceback\nfrom functools import reduce\nfrom typing import List, Set\nfrom urllib.parse import urljoin, urlparse, ParseResult, urlsplit, urlunparse\n\nimport html2text as ht\nimport requests\nfrom bs4 import BeautifulSoup\n\nfrom common.utils.logger import maxkb_logger\n\nrequests.packages.urllib3.disable_warnings()\n\n\nclass ChildLink:\n    def __init__(self, url, tag):\n        self.url = url\n        self.tag = copy.deepcopy(tag)\n\n\nclass ForkManage:\n    def __init__(self, base_url: str, selector_list: List[str]):\n        self.base_url = base_url\n        self.selector_list = selector_list\n\n    def fork(self, level: int, exclude_link_url: Set[str], fork_handler):\n        self.fork_child(ChildLink(self.base_url, None), self.selector_list, level, exclude_link_url, fork_handler)\n\n    @staticmethod\n    def fork_child(child_link: ChildLink, selector_list: List[str], level: int, exclude_link_url: Set[str],\n                   fork_handler):\n        if level < 0:\n            return\n        else:\n            child_link.url = remove_fragment(child_link.url)\n            child_url = child_link.url[:-1] if child_link.url.endswith('/') else child_link.url\n        if not exclude_link_url.__contains__(child_url):\n            exclude_link_url.add(child_url)\n            response = Fork(child_link.url, selector_list).fork()\n            fork_handler(child_link, response)\n            for child_link in response.child_link_list:\n                child_url = child_link.url[:-1] if child_link.url.endswith('/') else child_link.url\n                if not exclude_link_url.__contains__(child_url):\n                    ForkManage.fork_child(child_link, selector_list, level - 1, exclude_link_url, fork_handler)\n\n\ndef remove_fragment(url: str) -> str:\n    parsed_url = urlparse(url)\n    modified_url = ParseResult(scheme=parsed_url.scheme, netloc=parsed_url.netloc, path=parsed_url.path,\n                               params=parsed_url.params, query=parsed_url.query, fragment=None)\n    return urlunparse(modified_url)\n\n\nclass Fork:\n    class Response:\n        def __init__(self, content: str, child_link_list: List[ChildLink], status, message: str):\n            self.content = content\n            self.child_link_list = child_link_list\n            self.status = status\n            self.message = message\n\n        @staticmethod\n        def success(html_content: str, child_link_list: List[ChildLink]):\n            return Fork.Response(html_content, child_link_list, 200, '')\n\n        @staticmethod\n        def error(message: str):\n            return Fork.Response('', [], 500, message)\n\n    def __init__(self, base_fork_url: str, selector_list: List[str]):\n        base_fork_url = remove_fragment(base_fork_url)\n        parsed = urlparse(base_fork_url)\n        path = parsed.path.rstrip('/')\n        self.base_fork_url = urlunparse((\n            parsed.scheme,\n            parsed.netloc,\n            path,\n            None,\n            None,\n            None  # fragment\n        ))\n        parsed = urlsplit(base_fork_url)\n        query = parsed.query\n        if query is not None and len(query) > 0:\n            self.base_fork_url = self.base_fork_url + '?' + query\n        self.selector_list = [selector for selector in selector_list if selector is not None and len(selector) > 0]\n        self.urlparse = urlparse(self.base_fork_url)\n        self.base_url = ParseResult(scheme=self.urlparse.scheme, netloc=self.urlparse.netloc, path='', params='',\n                                    query='',\n                                    fragment='').geturl()\n\n    def get_child_link_list(self, bf: BeautifulSoup):\n        pattern = \"^((?!(http:|https:|tel:/|#|mailto:|javascript:))|\" + self.base_fork_url + \"|/).*\"\n        link_list = bf.find_all(name='a', href=re.compile(pattern))\n        result = [ChildLink(link.get('href'), link) if link.get('href').startswith(self.base_url) else ChildLink(\n            self.base_url + link.get('href'), link) for link in link_list]\n        result = [row for row in result if row.url.startswith(self.base_fork_url)]\n        return result\n\n    def get_content_html(self, bf: BeautifulSoup):\n        if self.selector_list is None or len(self.selector_list) == 0:\n            return str(bf)\n        params = reduce(lambda x, y: {**x, **y},\n                        [{'class_': selector.replace('.', '')} if selector.startswith('.') else\n                         {'id': selector.replace(\"#\", \"\")} if selector.startswith(\"#\") else {'name': selector} for\n                         selector in\n                         self.selector_list], {})\n        f = bf.find_all(**params)\n        return \"\\n\".join([str(row) for row in f])\n\n    @staticmethod\n    def reset_url(tag, field, base_fork_url):\n        field_value: str = tag[field]\n        if field_value.startswith(\"/\"):\n            result = urlparse(base_fork_url)\n            result_url = ParseResult(scheme=result.scheme, netloc=result.netloc, path=field_value, params='', query='',\n                                     fragment='').geturl()\n        else:\n            result_url = urljoin(\n                base_fork_url + '/' + (field_value if field_value.endswith('/') else field_value + '/'),\n                \".\")\n        result_url = result_url[:-1] if result_url.endswith('/') else result_url\n        tag[field] = result_url\n\n    def reset_beautiful_soup(self, bf: BeautifulSoup):\n        reset_config_list = [\n            {\n                'field': 'href',\n            },\n            {\n                'field': 'src',\n            }\n        ]\n        for reset_config in reset_config_list:\n            field = reset_config.get('field')\n            tag_list = bf.find_all(**{field: re.compile('^(?!(http:|https:|tel:/|#|mailto:|javascript:)).*')})\n            for tag in tag_list:\n                self.reset_url(tag, field, self.base_fork_url)\n        return bf\n\n    @staticmethod\n    def get_beautiful_soup(response):\n        encoding = response.encoding if response.encoding is not None and response.encoding != 'ISO-8859-1' else response.apparent_encoding\n        html_content = response.content.decode(encoding)\n        beautiful_soup = BeautifulSoup(html_content, \"html.parser\")\n        meta_list = beautiful_soup.find_all('meta')\n        charset_list = Fork.get_charset_list(meta_list)\n        if len(charset_list) > 0:\n            charset = charset_list[0]\n            if charset != encoding:\n                try:\n                    html_content = response.content.decode(charset, errors='replace')\n                except Exception as e:\n                    maxkb_logger.error(f'{e}: {traceback.format_exc()}')\n                return BeautifulSoup(html_content, \"html.parser\")\n        return beautiful_soup\n\n    @staticmethod\n    def get_charset_list(meta_list):\n        charset_list = []\n        for meta in meta_list:\n            if meta.attrs is not None:\n                if 'charset' in meta.attrs:\n                    charset_list.append(meta.attrs.get('charset'))\n                elif meta.attrs.get('http-equiv', '').lower() == 'content-type' and 'content' in meta.attrs:\n                    match = re.search(r'charset=([^\\s;]+)', meta.attrs['content'], re.I)\n                    if match:\n                        charset_list.append(match.group(1))\n        return charset_list\n\n    def fork(self):\n        try:\n\n            headers = {\n                'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36'\n            }\n\n            maxkb_logger.info(f'fork:{self.base_fork_url}')\n            response = requests.get(self.base_fork_url, verify=False, headers=headers)\n            if response.status_code != 200:\n                maxkb_logger.error(f\"url: {self.base_fork_url} code:{response.status_code}\")\n                return Fork.Response.error(f\"url: {self.base_fork_url} code:{response.status_code}\")\n            bf = self.get_beautiful_soup(response)\n        except Exception as e:\n            maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}')\n            return Fork.Response.error(str(e))\n        bf = self.reset_beautiful_soup(bf)\n        link_list = self.get_child_link_list(bf)\n        content = self.get_content_html(bf)\n        r = ht.html2text(content)\n        return Fork.Response.success(r, link_list)\n\n\ndef handler(base_url, response: Fork.Response):\n    maxkb_logger.info(base_url.url, base_url.tag.text if base_url.tag else None, response.content)\n\n# ForkManage('https://bbs.fit2cloud.com/c/de/6', ['.md-content']).fork(3, set(), handler)\n"
  },
  {
    "path": "apps/common/utils/lock.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎\n    @file： lock.py\n    @date：2023/9/11 11:45\n    @desc:\n\"\"\"\nfrom functools import wraps\n\nimport uuid_utils.compat as uuid\nfrom django.core.cache import caches\nfrom django_redis import get_redis_connection\n\nmemory_cache = caches['default']\n\nclass RedisLock():\n    def __init__(self):\n        self.lock_value = None\n\n    def try_lock(self, key: str, timeout=None):\n        \"\"\"\n        获取锁\n        :param key:    获取锁 key\n        :param timeout 超时时间\n        :return: 是否获取到锁\n        \"\"\"\n        redis_client = get_redis_connection(\"default\")\n        if timeout is None:\n            timeout = 3600  # 默认超时时间为3600秒\n        self.lock_value = str(uuid.uuid7())\n        return redis_client.set(key, self.lock_value, nx=True, ex=timeout)\n\n\n    def un_lock(self, key: str):\n        \"\"\"\n        解锁\n        :param key: 解锁 key\n        :return: 是否解锁成功\n        \"\"\"\n        redis_client = get_redis_connection(\"default\")\n        unlock_script = \"\"\"\n            if redis.call(\"get\", KEYS[1]) == ARGV[1] then\n                return redis.call(\"del\", KEYS[1])\n            else\n                return 0\n            end\n            \"\"\"\n        redis_client.eval(unlock_script, 1, key, self.lock_value)\n\n\ndef lock(lock_key, timeout=None):\n    \"\"\"\n    给一个函数上锁\n    @param lock_key: 上锁key 字符串|函数  函数返回值为字符串\n    @param timeout:  超时时间\n    :return: 装饰器函数 当前装饰器主要限制一个key只能一个线程去调用 相同key只能阻塞等待上一个任务执行完毕 不同key不需要等待\n\n    \"\"\"\n\n    def decorator(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            key = lock_key(*args, **kwargs) if callable(lock_key) else lock_key\n            rlock = RedisLock()\n            if not rlock.try_lock(key, timeout):\n                # 获取锁失败，可自定义异常或返回\n                return None\n            try:\n                return func(*args, **kwargs)\n            finally:\n                rlock.un_lock(key)\n\n        return wrapper\n\n    return decorator\n"
  },
  {
    "path": "apps/common/utils/logger.py",
    "content": "from datetime import datetime, timedelta\nfrom logging.handlers import TimedRotatingFileHandler\nimport os\nimport logging\n\nmaxkb_logger = logging.getLogger('max_kb')\n\n\nclass DailyTimedRotatingFileHandler(TimedRotatingFileHandler):\n    def rotator(self, source, dest):\n        \"\"\" Override the original method to rotate the log file daily.\"\"\"\n        dest = self._get_rotate_dest_filename(source)\n        if os.path.exists(source) and not os.path.exists(dest):\n            # 存在多个服务进程时, 保证只有一个进程成功 rotate\n            os.rename(source, dest)\n\n    @staticmethod\n    def _get_rotate_dest_filename(source):\n        date_yesterday = (\n            datetime.now() - timedelta(days=1)\n        ).strftime('%Y-%m-%d')\n        path = [\n            os.path.dirname(source),\n            date_yesterday,\n            os.path.basename(source)\n        ]\n        filename = os.path.join(*path)\n        os.makedirs(os.path.dirname(filename), 0o700, exist_ok=True)\n        return filename\n"
  },
  {
    "path": "apps/common/utils/page_utils.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： page_utils.py\n    @date：2024/11/21 10:32\n    @desc:\n\"\"\"\nfrom math import ceil\n\n\ndef page(query_set, page_size, handler, is_the_task_interrupted=lambda: False):\n    \"\"\"\n\n    @param query_set: 查询query_set\n    @param page_size: 每次查询大小\n    @param handler:   数据处理器\n    @param is_the_task_interrupted: 任务是否被中断\n    @return:\n    \"\"\"\n    query = query_set.order_by(\"id\")\n    count = query_set.count()\n    for i in range(0, ceil(count / page_size)):\n        if is_the_task_interrupted():\n            return\n        offset = i * page_size\n        paragraph_list = query.all()[offset: offset + page_size]\n        handler(paragraph_list)\n\n\ndef page_desc(query_set, page_size, handler, is_the_task_interrupted=lambda: False):\n    \"\"\"\n\n    @param query_set: 查询query_set\n    @param page_size: 每次查询大小\n    @param handler:   数据处理器\n    @param is_the_task_interrupted: 任务是否被中断\n    @return:\n    \"\"\"\n    query = query_set.order_by(\"id\")\n    count = query_set.count()\n    for i in sorted(range(0, ceil(count / page_size)), reverse=True):\n        if is_the_task_interrupted():\n            return\n        offset = i * page_size\n        paragraph_list = query.all()[offset: offset + page_size]\n        handler(paragraph_list)\n"
  },
  {
    "path": "apps/common/utils/rsa_util.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： rsa_util.py\n    @date：2023/11/3 11:13\n    @desc:\n\"\"\"\nimport base64\nimport threading\nfrom functools import lru_cache\n\nfrom Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher\nfrom Crypto.PublicKey import RSA\nfrom django.core import cache\nfrom django.db.models import QuerySet\n\nfrom common.constants.cache_version import Cache_Version\nfrom system_manage.models import SystemSetting, SettingType\n\nlock = threading.Lock()\nrsa_cache = cache.cache\ncache_key = \"rsa_key\"\n# 对密钥加密的密码\nsecret_code = \"mac_kb_password\"\n\n\ndef generate():\n    \"\"\"\n    生成 私钥秘钥对\n    :return:{key:'公钥',value:'私钥'}\n    \"\"\"\n    # 生成一个 2048 位的密钥\n    key = RSA.generate(2048)\n\n    # 获取私钥\n    encrypted_key = key.export_key(passphrase=secret_code, pkcs=8,\n                                   protection=\"scryptAndAES128-CBC\")\n    return {'key': key.publickey().export_key(), 'value': encrypted_key}\n\n\ndef get_key_pair():\n    rsa_value = rsa_cache.get(cache_key)\n    if rsa_value is None:\n        with lock:\n            rsa_value = rsa_cache.get(cache_key)\n            if rsa_value is not None:\n                return rsa_value\n            rsa_value = get_key_pair_by_sql()\n            version, get_key = Cache_Version.SYSTEM.value\n            rsa_cache.set(get_key(key='rsa_key'), rsa_value, timeout=None, version=version)\n    return rsa_value\n\n\ndef get_key_pair_by_sql():\n    system_setting = QuerySet(SystemSetting).filter(type=SettingType.RSA.value).first()\n    if system_setting is None:\n        kv = generate()\n        system_setting = SystemSetting(type=SettingType.RSA.value,\n                                       meta={'key': kv.get('key').decode(), 'value': kv.get('value').decode()})\n        system_setting.save()\n    return system_setting.meta\n\n\ndef encrypt(msg, public_key: str | None = None):\n    \"\"\"\n    加密\n    :param msg:        加密数据\n    :param public_key: 公钥\n    :return: 加密后的数据\n    \"\"\"\n    if public_key is None:\n        public_key = get_key_pair().get('key')\n    cipher = _get_encrypt_cipher(public_key)\n    encrypt_msg = cipher.encrypt(msg.encode(\"utf-8\"))\n    return base64.b64encode(encrypt_msg).decode()\n\n\ndef decrypt(msg, pri_key: str | None = None):\n    \"\"\"\n    解密\n    :param msg: 需要解密的数据\n    :param pri_key: 私钥\n    :return: 解密后数据\n    \"\"\"\n    if pri_key is None:\n        pri_key = get_key_pair().get('value')\n    cipher = _get_cipher(pri_key)\n    decrypt_data = cipher.decrypt(base64.b64decode(msg), 0)\n    return decrypt_data.decode(\"utf-8\")\n\n\n\n@lru_cache(maxsize=2)\ndef _get_encrypt_cipher(public_key: str):\n    \"\"\"缓存加密 cipher 对象\"\"\"\n    return PKCS1_cipher.new(RSA.importKey(extern_key=public_key, passphrase=secret_code))\n\n\ndef rsa_long_encrypt(message, public_key: str | None = None, length=200):\n    \"\"\"\n    超长文本加密\n\n    :param message:         需要加密的字符串\n    :param public_key   公钥\n    :param length:      1024bit的证书用100, 2048bit的证书用 200\n    :return: 加密后的数据\n    \"\"\"\n    if public_key is None:\n        public_key = get_key_pair().get('key')\n\n    cipher = _get_encrypt_cipher(public_key)\n\n    if len(message) <= length:\n        result = base64.b64encode(cipher.encrypt(message.encode('utf-8')))\n    else:\n        rsa_text = []\n        for i in range(0, len(message), length):\n            cont = message[i:i + length]\n            rsa_text.append(cipher.encrypt(cont.encode('utf-8')))\n        cipher_text = b''.join(rsa_text)\n        result = base64.b64encode(cipher_text)\n\n    return result.decode()\n\n\n@lru_cache(maxsize=2)\ndef _get_cipher(pri_key: str):\n    \"\"\"缓存 cipher 对象,避免重复创建\"\"\"\n    return PKCS1_cipher.new(RSA.importKey(pri_key, passphrase=secret_code))\n\n\ndef rsa_long_decrypt(message, pri_key: str | None = None, length=256):\n    \"\"\"\n    超长文本解密,优化内存使用\n    :param  message:    需要解密的数据\n    :param  pri_key:    秘钥\n    :param  length :     1024bit的证书用128,2048bit证书用256位\n    :return: 解密后的数据\n    \"\"\"\n    if pri_key is None:\n        pri_key = get_key_pair().get('value')\n\n    cipher = _get_cipher(pri_key)\n    base64_de = base64.b64decode(message)\n\n    # 使用 bytearray 减少内存分配\n    result = bytearray()\n    for i in range(0, len(base64_de), length):\n        result.extend(cipher.decrypt(base64_de[i:i + length], 0))\n\n    return result.decode()\n\n"
  },
  {
    "path": "apps/common/utils/shared_resource_auth.py",
    "content": "\"\"\"\n    @project: MaxKB-xpack-ee\n    @Author: niu\n    @file: shared_resource_auth.py\n    @date: 2026/3/11 11:22\n    @desc:\n\"\"\"\nfrom typing import List\n\nfrom django.db.models import QuerySet\n\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom knowledge.models import Knowledge\nfrom tools.models import Tool\n\n\ndef filter_authorized_ids(resource_type: str, ids: List[str], workspace_id: str) -> List[str]:\n    \"\"\"\n    通用授权过滤函数\n\n    @param resource_type: 资源类型 ('model', 'tool', 'knowledge')\n    @param ids: 待过滤的ID列表\n    @param workspace_id: 工作空间ID\n    @return: 授权通过的ID列表\n    \"\"\"\n\n    if not ids:\n        return []\n\n    auth_func = DatabaseModelManage.get_model(f\"get_authorized_{resource_type}\")\n\n    model_class = {'tool': Tool, 'knowledge': Knowledge}.get(resource_type)\n    if model_class is None:\n        return ids\n\n    same_workspace_ids = list(\n        QuerySet(model_class).filter(id__in=ids, workspace_id=workspace_id)\n        .values_list('id', flat=True)\n    )\n\n    cross_workspace_ids = [i for i in ids if i not in set(map(str, same_workspace_ids))]\n\n    authorized_ids = set(map(str, same_workspace_ids))\n\n    if cross_workspace_ids and auth_func is not None:\n        cross_queryset = QuerySet(model_class).filter(id__in=cross_workspace_ids)\n        authorized = auth_func(cross_queryset, workspace_id)\n        authorized_ids.update(str(r.id) for r in authorized)\n\n    return [i for i in ids if i in authorized_ids]"
  },
  {
    "path": "apps/common/utils/split_model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: qabot\n    @Author：虎\n    @file： split_model.py\n    @date：2023/9/1 15:12\n    @desc:\n\"\"\"\nimport re\nfrom functools import reduce\nfrom typing import List, Dict\n\nimport jieba\n\n\ndef get_level_block(text, level_content_list, level_content_index, cursor):\n    \"\"\"\n    从文本中获取块数据\n    :param text: 文本\n    :param level_content_list: 拆分的title数组\n    :param level_content_index: 指定的下标\n    :param cursor: 开始的下标位置\n    :return: 拆分后的文本数据\n    \"\"\"\n    start_content: str = level_content_list[level_content_index].get('content')\n    next_content = level_content_list[level_content_index + 1].get(\"content\") if level_content_index + 1 < len(\n        level_content_list) else None\n    start_index = text.index(start_content, cursor)\n    end_index = text.index(next_content, start_index + 1) if next_content is not None else len(text)\n    return text[start_index + len(start_content):end_index], end_index\n\n\ndef to_tree_obj(content, state='title'):\n    \"\"\"\n    转换为树形对象\n    :param content: 文本数据\n    :param state:   状态: title block\n    :return: 转换后的数据\n    \"\"\"\n    return {'content': content, 'state': state}\n\n\ndef remove_special_symbol(str_source: str):\n    \"\"\"\n    删除特殊字符\n    :param str_source: 需要删除的文本数据\n    :return: 删除后的数据\n    \"\"\"\n    return str_source\n\n\ndef filter_special_symbol(content: dict):\n    \"\"\"\n    过滤文本中的特殊字符\n    :param content: 需要过滤的对象\n    :return: 过滤后返回\n    \"\"\"\n    content['content'] = remove_special_symbol(content['content'])\n    return content\n\n\ndef flat(tree_data_list: List[dict], parent_chain: List[dict], result: List[dict]):\n    \"\"\"\n    扁平化树形结构数据\n    :param tree_data_list: 树形接口数据\n    :param parent_chain:   父级数据 传[] 用于递归存储数据\n    :param result:         响应数据 传[] 用于递归存放数据\n    :return: result 扁平化后的数据\n    \"\"\"\n    if parent_chain is None:\n        parent_chain = []\n    if result is None:\n        result = []\n    for tree_data in tree_data_list:\n        p = parent_chain.copy()\n        p.append(tree_data)\n        result.append(to_flat_obj(parent_chain, content=tree_data[\"content\"], state=tree_data[\"state\"]))\n        children = tree_data.get('children')\n        if children is not None and len(children) > 0:\n            flat(children, p, result)\n    return result\n\n\ndef to_paragraph(obj: dict):\n    \"\"\"\n    转换为段落\n    :param obj: 需要转换的对象\n    :return: 段落对象\n    \"\"\"\n    content = obj['content']\n    return {\"keywords\": get_keyword(content),\n            'parent_chain': list(map(lambda p: p['content'], obj['parent_chain'])),\n            'content': \",\".join(list(map(lambda p: p['content'], obj['parent_chain']))) + content}\n\n\ndef get_keyword(content: str):\n    \"\"\"\n    获取content中的关键词\n    :param content: 文本\n    :return: 关键词数组\n    \"\"\"\n    stopwords = ['：', '“', '！', '”', '\\n', '\\\\s']\n    cutworms = jieba.lcut(content)\n    return list(set(list(filter(lambda k: (k not in stopwords) | len(k) > 1, cutworms))))\n\n\ndef titles_to_paragraph(list_title: List[dict]):\n    \"\"\"\n    将同一父级的title转换为块段落\n    :param list_title: 同父级title\n    :return: 块段落\n    \"\"\"\n    if len(list_title) > 0:\n        content = \"\\n,\".join(\n            list(map(lambda d: d['content'].strip(\"\\r\\n\").strip(\"\\n\").strip(\"\\\\s\"), list_title)))\n\n        return {'keywords': '',\n                'parent_chain': list(\n                    map(lambda p: p['content'].strip(\"\\r\\n\").strip(\"\\n\").strip(\"\\\\s\"), list_title[0]['parent_chain'])),\n                'content': \",\".join(list(\n                    map(lambda p: p['content'].strip(\"\\r\\n\").strip(\"\\n\").strip(\"\\\\s\"),\n                        list_title[0]['parent_chain']))) + content}\n    return None\n\n\ndef parse_group_key(level_list: List[dict]):\n    \"\"\"\n    将同级别同父级的title生成段落,加上本身的段落数据形成新的数据\n    :param level_list: title n 级数据\n    :return: 根据title生成的数据 + 段落数据\n    \"\"\"\n    result = []\n    group_data = group_by(list(filter(lambda f: f['state'] == 'title' and len(f['parent_chain']) > 0, level_list)),\n                          key=lambda d: \",\".join(list(map(lambda p: p['content'], d['parent_chain']))))\n    result += list(map(lambda group_data_key: titles_to_paragraph(group_data[group_data_key]), group_data))\n    result += list(map(to_paragraph, list(filter(lambda f: f['state'] == 'block', level_list))))\n    return result\n\n\ndef to_block_paragraph(tree_data_list: List[dict]):\n    \"\"\"\n    转换为块段落对象\n    :param tree_data_list: 树数据\n    :return: 块段落\n    \"\"\"\n    flat_list = flat(tree_data_list, [], [])\n    level_group_dict: dict = group_by(flat_list, key=lambda f: f['level'])\n    return list(map(lambda level: parse_group_key(level_group_dict[level]), level_group_dict))\n\n\ndef parse_title_level(text, content_level_pattern: List, index):\n    if index >= len(content_level_pattern):\n        return []\n    result = parse_level(text, content_level_pattern[index])\n    if len(result) == 0 and len(content_level_pattern) > index:\n        return parse_title_level(text, content_level_pattern, index + 1)\n    return result\n\n\ndef parse_level(text, pattern: str):\n    \"\"\"\n    获取正则匹配到的文本\n    :param text: 需要匹配的文本\n    :param pattern:  正则\n    :return: 符合正则的文本\n    \"\"\"\n    level_content_list = list(map(to_tree_obj, [r[0:255] for r in re_findall(pattern, text) if r is not None]))\n    # 过滤掉空标题或只包含#和空白字符的标题\n    filtered_list = [item for item in level_content_list\n                     if item['content'].strip(' ') and item['content'].replace('#', '').strip(' ')]\n    return list(map(filter_special_symbol, filtered_list))\n\n\ndef re_findall(pattern, text):\n    # 检查 pattern 是否为空或无效\n    if pattern is None:\n        return []\n\n    # 如果是字符串类型，检查是否为空字符串\n    if isinstance(pattern, str) and (not pattern or not pattern.strip()):\n        return []\n\n    try:\n        result = re.findall(pattern, text, flags=0)\n    except re.error:\n        return []\n\n    return list(filter(lambda r: r is not None and len(r) > 0, reduce(lambda x, y: [*x, *y], list(\n        map(lambda row: [*(row if isinstance(row, tuple) else [row])], result)),\n                                                                      [])))\n\n\ndef to_flat_obj(parent_chain: List[dict], content: str, state: str):\n    \"\"\"\n    将树形属性转换为扁平对象\n    :param parent_chain:\n    :param content:\n    :param state:\n    :return:\n    \"\"\"\n    return {'parent_chain': parent_chain, 'level': len(parent_chain), \"content\": content, 'state': state}\n\n\ndef flat_map(array: List[List]):\n    \"\"\"\n    将二位数组转为一维数组\n    :param array: 二维数组\n    :return: 一维数组\n    \"\"\"\n    result = []\n    for e in array:\n        result += e\n    return result\n\n\ndef group_by(list_source: List, key):\n    \"\"\"\n    將數組分組\n    :param list_source: 需要分組的數組\n    :param key: 分組函數\n    :return: key->[]\n    \"\"\"\n    result = {}\n    for e in list_source:\n        k = key(e)\n        array = result.get(k) if k in result else []\n        array.append(e)\n        result[k] = array\n    return result\n\n\ndef result_tree_to_paragraph(result_tree: List[dict], result, parent_chain, with_filter: bool):\n    \"\"\"\n    转换为分段对象\n    :param result_tree: 解析文本的树\n    :param result:      传[]  用于递归\n    :param parent_chain: 传[] 用户递归存储数据\n    :param with_filter: 是否过滤block\n    :return: List[{'problem':'xx','content':'xx'}]\n    \"\"\"\n    for item in result_tree:\n        if item.get('state') == 'block':\n            result.append({'title': \" \".join(parent_chain),\n                           'content': filter_special_char(item.get(\"content\")) if with_filter else item.get(\"content\")})\n        children = item.get(\"children\")\n        if children is not None and len(children) > 0:\n            result_tree_to_paragraph(children, result,\n                                     [*parent_chain, remove_special_symbol(item.get('content'))], with_filter)\n    return result\n\n\ndef post_handler_paragraph(content: str, limit: int):\n    \"\"\"\n    根据文本的最大字符分段\n    :param content: 需要分段的文本字段\n    :param limit:   最大分段字符\n    :return: 分段后数据\n    \"\"\"\n    result = []\n    temp_char, start = '', 0\n    while (pos := content.find(\"\\n\", start)) != -1:\n        split, start = content[start:pos + 1], pos + 1\n        if len(temp_char + split) > limit:\n            if len(temp_char) > 4096:\n                pass\n            result.append(temp_char)\n            temp_char = ''\n        temp_char = temp_char + split\n    temp_char = temp_char + content[start:]\n    if len(temp_char) > 0:\n        if len(temp_char) > 4096:\n            pass\n        result.append(temp_char)\n\n    pattern = \"[\\\\S\\\\s]{1,\" + str(limit) + '}'\n    # 如果\\n 单段超过限制,则继续拆分\n    return reduce(lambda x, y: [*x, *y], map(lambda row: re.findall(pattern, row), result), [])\n\n\ndef smart_split_paragraph(content: str, limit: int):\n    \"\"\"\n    智能分段:在limit前找到合适的分割点(句号、回车等)\n    :param content: 需要分段的文本\n    :param limit: 最大字符限制\n    :return: 分段后的文本列表\n    \"\"\"\n    if len(content) <= limit:\n        return [content]\n\n    result = []\n    start = 0\n\n    while start < len(content):\n        end = start + limit\n\n        if end >= len(content):\n            # 剩余文本不超过限制,直接添加\n            result.append(content[start:])\n            break\n\n        # 在limit范围内寻找最佳分割点\n        best_split = end\n\n        # 优先级:句号 > 感叹号/问号 > 回车\n        split_chars = [\n            ('。', 0), ('.', 0),  # 中英文句号\n            ('!', 0), ('!', 0),  # 中英文感叹号\n            ('?', 0), ('?', 0),  # 中英文问号\n        ]\n\n        # 从后往前找分割点\n        for i in range(end - 1, start + limit // 2, -1):  # 至少保留一半内容\n            for char, offset in split_chars:\n                if content[i] == char:\n                    best_split = i + 1  # 包含分隔符在当前段\n                    break\n            if best_split != end:\n                break\n\n        # 如果找不到合适分割点,使用原始limit\n        if best_split == end and end < len(content):\n            best_split = end\n\n        result.append(content[start:best_split])\n        start = best_split\n\n    return [text for text in result if text.strip()]\n\n\nreplace_map = {\n    re.compile('\\n+'): '\\n',\n    re.compile(' +'): ' ',\n    re.compile('#+'): \"\",\n    re.compile(\"\\t+\"): ''\n}\n\n\ndef filter_special_char(content: str):\n    \"\"\"\n    过滤特殊字段\n    :param content: 文本\n    :return: 过滤后字段\n    \"\"\"\n    items = replace_map.items()\n    for key, value in items:\n        content = re.sub(key, value, content)\n    return content\n\n\nclass SplitModel:\n\n    def __init__(self, content_level_pattern, with_filter=True, limit=100000):\n        self.content_level_pattern = content_level_pattern\n        self.with_filter = with_filter\n        if type(limit) is not int:\n            limit = int(limit)\n        if limit is None or limit > 100000:\n            limit = 100000\n        if limit < 50:\n            limit = 50\n        self.limit = limit\n\n    def parse_to_tree(self, text: str, index=0):\n        \"\"\"\n         解析文本\n        :param text: 需要解析的文本\n        :param index: 从那个正则开始解析\n        :return: 解析后的树形结果数据\n        \"\"\"\n        level_content_list = parse_title_level(text, self.content_level_pattern, index)\n        if len(level_content_list) == 0:\n            return [to_tree_obj(row, 'block') for row in smart_split_paragraph(text, limit=self.limit)]\n        if index == 0 and text.lstrip().index(level_content_list[0][\"content\"].lstrip()) != 0:\n            level_content_list.insert(0, to_tree_obj(\"\"))\n\n        cursor = 0\n        level_title_content_list = [item for item in level_content_list if item.get('state') == 'title']\n        for i in range(len(level_title_content_list)):\n            start_content: str = level_title_content_list[i].get('content')\n            if cursor < text.index(start_content, cursor):\n                for row in smart_split_paragraph(text[cursor:   text.index(start_content, cursor)], limit=self.limit):\n                    level_content_list.insert(0, to_tree_obj(row, 'block'))\n\n            block, cursor = get_level_block(text, level_title_content_list, i, cursor)\n            if len(block) == 0:\n                continue\n            children = self.parse_to_tree(text=block, index=index + 1)\n            level_title_content_list[i]['children'] = children\n            first_child_idx_in_block = block.lstrip().index(children[0][\"content\"].lstrip())\n            if first_child_idx_in_block != 0:\n                inner_children = self.parse_to_tree(block[:first_child_idx_in_block], index + 1)\n                level_title_content_list[i]['children'].extend(inner_children)\n        return level_content_list\n\n    def parse(self, text: str):\n        \"\"\"\n        解析文本\n        :param text: 文本数据\n        :return: 解析后数据 {content:段落数据,keywords:[‘段落关键词’],parent_chain:['段落父级链路']}\n        \"\"\"\n        text = text.replace('\\r\\n', '\\n')\n        text = text.replace('\\r', '\\n')\n        text = text.replace(\"\\0\", '')\n        result_tree = self.parse_to_tree(text, 0)\n        result = result_tree_to_paragraph(result_tree, [], [], self.with_filter)\n        for e in result:\n            if len(e['content']) > 4096:\n                pass\n        title_list = list(set([row.get('title') for row in result]))\n        return [item for item in [self.post_reset_paragraph(row, title_list) for row in result] if\n                'content' in item and len(item.get('content').strip()) > 0]\n\n    def post_reset_paragraph(self, paragraph: Dict, title_list: List[str]):\n        result = self.content_is_null(paragraph, title_list)\n        result = self.filter_title_special_characters(result)\n        result = self.sub_title(result)\n        return result\n\n    @staticmethod\n    def sub_title(paragraph: Dict):\n        if 'title' in paragraph:\n            title = paragraph.get('title')\n            if len(title) > 255:\n                return {**paragraph, 'title': title[0:255], 'content': title[255:len(title)] + paragraph.get('content')}\n        return paragraph\n\n    @staticmethod\n    def content_is_null(paragraph: Dict, title_list: List[str]):\n        if 'title' in paragraph:\n            title = paragraph.get('title')\n            content = paragraph.get('content')\n            if (content is None or len(content.strip()) == 0) and (title is not None and len(title) > 0):\n                find = [t for t in title_list if t.__contains__(title) and t != title]\n                if find:\n                    return {'title': '', 'content': ''}\n                return {'title': '', 'content': title}\n        return paragraph\n\n    @staticmethod\n    def filter_title_special_characters(paragraph: Dict):\n        title = paragraph.get('title') if 'title' in paragraph else ''\n        for title_special_characters in title_special_characters_list:\n            title = title.replace(title_special_characters, '')\n        return {**paragraph,\n                'title': title}\n\n\ntitle_special_characters_list = ['#', '\\n', '\\r', '\\\\s']\n\ndefault_split_pattern = {\n    'md': [re.compile('(?<=^)# .*|(?<=\\\\n)# .*'),\n           re.compile('(?<=\\\\n)(?<!#)## (?!#).*|(?<=^)(?<!#)## (?!#).*'),\n           re.compile(\"(?<=\\\\n)(?<!#)### (?!#).*|(?<=^)(?<!#)### (?!#).*\"),\n           re.compile(\"(?<=\\\\n)(?<!#)#### (?!#).*|(?<=^)(?<!#)#### (?!#).*\"),\n           re.compile(\"(?<=\\\\n)(?<!#)##### (?!#).*|(?<=^)(?<!#)##### (?!#).*\"),\n           re.compile(\"(?<=\\\\n)(?<!#)###### (?!#).*|(?<=^)(?<!#)###### (?!#).*\")],\n    'default': [re.compile(\"(?<!\\n)\\n\\n+\")]\n}\n\n\ndef get_split_model(filename: str, with_filter: bool = False, limit: int = 100000):\n    \"\"\"\n    根据文件名称获取分段模型\n    :param limit:        每段大小\n    :param with_filter: 是否过滤特殊字符\n    :param filename: 文件名称\n    :return: 分段模型\n    \"\"\"\n    if filename.endswith(\".md\"):\n        pattern_list = default_split_pattern.get('md')\n        return SplitModel(pattern_list, with_filter=with_filter, limit=limit)\n\n    pattern_list = default_split_pattern.get('md')\n    return SplitModel(pattern_list, with_filter=with_filter, limit=limit)\n\n\ndef to_title_tree_string(result_tree: List):\n    f = flat(result_tree, [], [])\n    return \"\\n│\".join(list(map(lambda r: title_tostring(r), list(filter(lambda row: row.get('state') == 'title', f)))))\n\n\ndef title_tostring(title_obj):\n    f = \"│ \".join(list(map(lambda index: \" \", range(0, len(title_obj.get(\"parent_chain\"))))))\n    return f + \"├───\" + title_obj.get('content')\n"
  },
  {
    "path": "apps/common/utils/tool_code.py",
    "content": "# coding=utf-8\nimport ast\nimport base64\nimport getpass\nimport gzip\nimport json\nimport os\nimport pwd\nimport random\nimport resource\nimport socket\nimport subprocess\nimport sys\nimport tempfile\nimport time\nfrom contextlib import contextmanager\nfrom contextlib import suppress\nfrom textwrap import dedent\n\nimport uuid_utils.compat as uuid\nfrom django.utils.translation import gettext_lazy as _\nfrom common.utils.logger import maxkb_logger\nfrom maxkb.const import BASE_DIR, CONFIG\nfrom maxkb.const import PROJECT_DIR\n\n_enable_sandbox = bool(int(CONFIG.get('SANDBOX', 0)))\n_run_user = 'sandbox' if _enable_sandbox else getpass.getuser()\n_sandbox_path = CONFIG.get(\"SANDBOX_HOME\", '/opt/maxkb-app/sandbox') if _enable_sandbox else os.path.join(PROJECT_DIR,\n                                                                                                          'data',\n                                                                                                          'sandbox')\n_sandbox_python_sys_path = CONFIG.get_sandbox_python_package_paths().split(',')\n_process_limit_timeout_seconds = int(CONFIG.get(\"SANDBOX_PYTHON_PROCESS_LIMIT_TIMEOUT_SECONDS\", '3600'))\n_process_limit_cpu_cores = min(max(int(CONFIG.get(\"SANDBOX_PYTHON_PROCESS_LIMIT_CPU_CORES\", '1')), 1),\n                               len(os.sched_getaffinity(0))) if sys.platform.startswith(\n    \"linux\") else os.cpu_count()  # 只支持linux，window和mac不支持\n_process_limit_mem_mb = int(CONFIG.get(\"SANDBOX_PYTHON_PROCESS_LIMIT_MEM_MB\", '256'))\n\n\nclass ToolExecutor:\n\n    def __init__(self):\n        pass\n\n    @staticmethod\n    def init_sandbox_dir():\n        if not _enable_sandbox:\n            # 不启用sandbox就不初始化目录\n            return\n        try:\n            # 只初始化一次\n            fd = os.open(os.path.join(PROJECT_DIR, 'tmp', 'tool_executor_init_dir.lock'),\n                         os.O_CREAT | os.O_EXCL | os.O_WRONLY)\n            os.close(fd)\n        except FileExistsError:\n            # 文件已存在 → 已初始化过\n            return\n        maxkb_logger.info(\"Init sandbox dir.\")\n        try:\n            os.system(\"chmod -R g-rwx /dev/shm /dev/mqueue\")\n            os.system(\"chmod o-rwx /run/postgresql\")\n        except Exception as e:\n            maxkb_logger.warning(f'Exception: {e}', exc_info=True)\n            pass\n        if CONFIG.get(\"SANDBOX_TMP_DIR_ENABLED\", '0') == \"1\":\n            os.system(\"chmod g+rwx /tmp\")\n        # 初始化sandbox配置文件\n        sandbox_lib_path = os.path.dirname(f'{_sandbox_path}/lib/sandbox.so')\n        sandbox_conf_file_path = f'{sandbox_lib_path}/.sandbox.conf'\n        if os.path.exists(sandbox_conf_file_path):\n            os.remove(sandbox_conf_file_path)\n        banned_hosts = CONFIG.get(\"SANDBOX_PYTHON_BANNED_HOSTS\", '').strip()\n        allow_dl_paths = CONFIG.get(\"SANDBOX_PYTHON_ALLOW_DL_PATHS\",'').strip()\n        allow_subprocess = CONFIG.get(\"SANDBOX_PYTHON_ALLOW_SUBPROCESS\", '0')\n        allow_syscall = CONFIG.get(\"SANDBOX_PYTHON_ALLOW_SYSCALL\", '0')\n        if banned_hosts:\n            hostname = socket.gethostname()\n            local_ip = socket.gethostbyname(hostname)\n            banned_hosts = f\"{banned_hosts},{local_ip}\"\n            banned_hosts = \",\".join(s.strip() for s in banned_hosts.split(\",\") if s.strip() and s.strip().lower() != hostname.lower())\n        with open(sandbox_conf_file_path, \"w\") as f:\n            f.write(f\"SANDBOX_PYTHON_BANNED_HOSTS={banned_hosts}\\n\")\n            f.write(f\"SANDBOX_PYTHON_ALLOW_DL_PATHS={','.join(sorted(set(filter(None, sys.path + _sandbox_python_sys_path + allow_dl_paths.split(',')))))}\\n\")\n            f.write(f\"SANDBOX_PYTHON_ALLOW_SUBPROCESS={allow_subprocess}\\n\")\n            f.write(f\"SANDBOX_PYTHON_ALLOW_SYSCALL={allow_syscall}\\n\")\n        os.system(f\"chmod -R 550 {_sandbox_path}\")\n\n    try:\n        init_sandbox_dir()\n    except Exception as e:\n        maxkb_logger.error(f'Exception: {e}', exc_info=True)\n\n    def exec_code(self, code_str, keywords, function_name=None):\n        _id = str(uuid.uuid7())\n        action_function = f'({function_name !a}, locals_v.get({function_name !a}))' if function_name else 'locals_v.popitem()'\n        set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});' if _enable_sandbox else ''\n        _exec_code = f\"\"\"\ntry:\n    import os, sys, json\n    from contextlib import redirect_stdout\n    path_to_exclude = ['/opt/py3/lib/python3.11/site-packages', '/opt/maxkb-app/apps']\n    sys.path = [p for p in sys.path if p not in path_to_exclude]\n    sys.path += {_sandbox_python_sys_path}\n    locals_v={{}}\n    keywords={keywords}\n    globals_v={{}}\n    {set_run_user}\n    os.environ.clear()\n    with redirect_stdout(open(os.devnull, 'w')):\n        exec({dedent(code_str)!a}, globals_v, locals_v)\n        f_name, f = {action_function}\n        globals_v.update(locals_v)\n        exec_result=f(**keywords)\n    sys.stdout.write(\"\\\\n{_id}:\")\n    json.dump({{'code':200,'msg':'success','data':exec_result}}, sys.stdout, default=str)\nexcept Exception as e:\n    if isinstance(e, MemoryError): e = Exception(\"Cannot allocate more memory: exceeded the limit of {_process_limit_mem_mb} MB.\")\n    sys.stdout.write(\"\\\\n{_id}:\")\n    json.dump({{'code':500,'msg':str(e),'data':None}}, sys.stdout, default=str)\nsys.stdout.write(\"\\\\n\")\nsys.stdout.flush()\n\"\"\"\n        maxkb_logger.debug(f\"Sandbox execute code: {_exec_code}\")\n        with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=True) as f:\n            f.write(_exec_code)\n            f.flush()\n            with execution_timer(_id):\n                subprocess_result = self._exec(f.name)\n        if subprocess_result.returncode != 0:\n            raise Exception(subprocess_result.stderr or subprocess_result.stdout or \"Unknown exception occurred\")\n        lines = subprocess_result.stdout.splitlines()\n        result_line = [line for line in lines if line.startswith(_id)]\n        if not result_line:\n            maxkb_logger.error(\"\\n\".join(lines))\n            raise Exception(\"No result found.\")\n        result = json.loads(result_line[-1].split(\":\", 1)[1])\n        if result.get('code') == 200:\n            return result.get('data')\n        raise Exception(result.get('msg') + (f'\\n{subprocess_result.stderr}' if subprocess_result.stderr else ''))\n\n    def _generate_mcp_server_code(self, _code, params, name=None, description=None, tool_id=None):\n        # 解析代码,提取导入语句和函数定义\n        try:\n            tree = ast.parse(_code)\n        except SyntaxError:\n            return _code\n        imports = []\n        functions = []\n        other_code = []\n        for node in tree.body:\n            if isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom):\n                imports.append(ast.unparse(node))\n            elif isinstance(node, ast.FunctionDef):\n                if node.name.startswith('_'):\n                    other_code.append(ast.unparse(node))\n                    continue\n                # 修改函数参数以包含 params 中的默认值\n                arg_names = [arg.arg for arg in node.args.args]\n                # 为参数添加默认值,确保参数顺序正确\n                defaults = []\n                num_defaults = 0\n                # 从后往前检查哪些参数有默认值\n                for i, arg_name in enumerate(arg_names):\n                    if arg_name in params:\n                        num_defaults = len(arg_names) - i\n                        break\n                # 为有默认值的参数创建默认值列表\n                if num_defaults > 0:\n                    for i in range(len(arg_names) - num_defaults, len(arg_names)):\n                        arg_name = arg_names[i]\n                        if arg_name in params:\n                            default_value = params[arg_name]\n                            if isinstance(default_value, str):\n                                defaults.append(ast.Constant(value=default_value))\n                            elif isinstance(default_value, (int, float, bool)):\n                                defaults.append(ast.Constant(value=default_value))\n                            elif default_value is None:\n                                defaults.append(ast.Constant(value=None))\n                            else:\n                                defaults.append(ast.Constant(value=str(default_value)))\n                        else:\n                            # 如果某个参数没有默认值,需要添加 None 占位\n                            defaults.append(ast.Constant(value=None))\n                    node.args.defaults = defaults\n\n                # 修改返回类型注解为 Result\n                node.returns = ast.Name(id='Result', ctx=ast.Load())\n\n                # 修改 return 语句为 return Result(result=..., tool_id=...)\n                class ReturnTransformer(ast.NodeTransformer):\n                    def __init__(self, func_name):\n                        self.func_name = func_name\n\n                    def visit_Return(self, node):\n                        if node.value is None:\n                            # return 语句没有返回值\n                            new_return = ast.Return(\n                                value=ast.Call(\n                                    func=ast.Name(id='Result', ctx=ast.Load()),\n                                    args=[],\n                                    keywords=[\n                                        ast.keyword(arg='result', value=ast.Constant(value=None)),\n                                        ast.keyword(arg='tool_id', value=ast.Constant(value=tool_id))\n                                    ]\n                                )\n                            )\n                        else:\n                            # return 语句有返回值\n                            new_return = ast.Return(\n                                value=ast.Call(\n                                    func=ast.Name(id='Result', ctx=ast.Load()),\n                                    args=[],\n                                    keywords=[\n                                        ast.keyword(arg='result', value=node.value),\n                                        ast.keyword(arg='tool_id', value=ast.Constant(value=tool_id))\n                                    ]\n                                )\n                            )\n                        return ast.copy_location(new_return, node)\n\n                transformer = ReturnTransformer(node.name)\n                node = transformer.visit(node)\n                ast.fix_missing_locations(node)\n\n                func_code = ast.unparse(node)\n                # 有些模型不支持name是中文,例如: deepseek, 其他模型未知\n                escaped_desc = (name + ' ' + description).replace('\\n', ' ').replace(\"'\", \" \")\n                functions.append(f\"@mcp.tool(description='{escaped_desc}')\\n{func_code}\\n\")\n            else:\n                other_code.append(ast.unparse(node))\n\n        # 构建完整的 MCP 服务器代码\n        code_parts = [\"from mcp.server.fastmcp import FastMCP\"]\n        code_parts.extend(imports)\n        code_parts.append(f\"\\nfrom pydantic import BaseModel\")\n        code_parts.append(f\"\\nfrom typing import Any\")\n        code_parts.append(f\"\\nclass Result(BaseModel):\")\n        code_parts.append(f\"\\n\\tresult: Any\")\n        code_parts.append(f\"\\n\\ttool_id: str\\n\")\n        code_parts.append(f\"\\nmcp = FastMCP(\\\"{uuid.uuid7()}\\\")\\n\")\n        code_parts.extend(other_code)\n        code_parts.extend(functions)\n        code_parts.append(\"\\nmcp.run(transport=\\\"stdio\\\")\\n\")\n        return \"\\n\".join(code_parts)\n\n    def generate_mcp_server_code(self, code_str, params, name, description, tool_id):\n        code = self._generate_mcp_server_code(code_str, params, name, description, tool_id)\n        set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});' if _enable_sandbox else ''\n        return f\"\"\"\nimport os, sys, logging\nlogging.basicConfig(level=logging.WARNING)\nlogging.getLogger(\"mcp\").setLevel(logging.ERROR)\nlogging.getLogger(\"mcp.server\").setLevel(logging.ERROR)\npath_to_exclude = ['/opt/py3/lib/python3.11/site-packages', '/opt/maxkb-app/apps']\nsys.path = [p for p in sys.path if p not in path_to_exclude]\nsys.path += {_sandbox_python_sys_path}\n{set_run_user}\nos.environ.clear()\nexec({dedent(code)!a})\n\"\"\"\n\n    def get_tool_mcp_config(self, tool, params):\n        _code = self.generate_mcp_server_code(tool.code, params, tool.name, tool.desc, str(tool.id))\n        maxkb_logger.debug(f\"Python code of mcp tool: {_code}\")\n        compressed_and_base64_encoded_code_str = base64.b64encode(gzip.compress(_code.encode())).decode()\n        tool_config = {\n            'command': sys.executable,\n            'args': [\n                '-c',\n                f'import base64,gzip; exec(gzip.decompress(base64.b64decode(\\'{compressed_and_base64_encoded_code_str}\\')).decode())',\n            ],\n            'cwd': _sandbox_path,\n            'env': {\n                'LD_PRELOAD': f'{_sandbox_path}/lib/sandbox.so',\n            },\n            'transport': 'stdio',\n        }\n        return tool_config\n\n    def get_app_mcp_config(self, api_key):\n        app_config = {\n            'url': f'http://127.0.0.1:8080{CONFIG.get_chat_path()}/api/mcp',\n            'transport': 'streamable_http',\n            'headers': {\n                'Authorization': f'Bearer {api_key}',\n            },\n        }\n        return app_config\n\n    def _exec(self, execute_file):\n        kwargs = {'cwd': BASE_DIR, 'env': {\n            'LD_PRELOAD': f'{_sandbox_path}/lib/sandbox.so',\n        }}\n        def _set_resource_limit():\n            if not _enable_sandbox or not sys.platform.startswith(\"linux\"): return\n            with suppress(Exception): resource.setrlimit(resource.RLIMIT_AS, (_process_limit_mem_mb * 1024 * 1024,) * 2)\n            with suppress(Exception): os.sched_setaffinity(0, set(random.sample(list(os.sched_getaffinity(0)), _process_limit_cpu_cores)))\n        try:\n            subprocess_result = subprocess.run(\n                [sys.executable, execute_file],\n                timeout=_process_limit_timeout_seconds,\n                text=True,\n                capture_output=True,\n                **kwargs,\n                preexec_fn=_set_resource_limit\n            )\n            return subprocess_result\n        except subprocess.TimeoutExpired:\n            raise Exception(_(f\"Process execution timed out after {_process_limit_timeout_seconds} seconds.\"))\n\n    def validate_mcp_transport(self, code_str):\n        servers = json.loads(code_str)\n        for server, config in servers.items():\n            if config.get('transport') not in ['sse', 'streamable_http']:\n                raise Exception(_('Only support transport=sse or transport=streamable_http'))\n\n\n@contextmanager\ndef execution_timer(id=\"\"):\n    start = time.perf_counter()\n    try:\n        yield\n    finally:\n        maxkb_logger.debug(f\"Tool execution({id}) takes {time.perf_counter() - start:.6f} seconds.\")\n"
  },
  {
    "path": "apps/common/utils/ts_vecto_util.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： ts_vecto_util.py\n    @date：2024/4/16 15:26\n    @desc:\n\"\"\"\nimport re\nimport uuid_utils.compat as uuid\nfrom typing import List\n\nimport jieba\nimport jieba.posseg\n\njieba_word_list_cache = [chr(item) for item in range(38, 84)]\n\nfor jieba_word in jieba_word_list_cache:\n    jieba.add_word('#' + jieba_word + '#')\n# r\"(?i)\\b(?:https?|ftp|tcp|file)://[^\\s]+\\b\",\n# 某些不分词数据\n# r'\"([^\"]*)\"'\nword_pattern_list = [r\"v\\d+.\\d+.\\d+\",\n                     r\"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\"]\n\nremove_chars = '\\n , :\\'<>！@#￥%……&*（）!@#$%^&*()： ；，/\"./'\n\njieba_remove_flag_list = ['x', 'w']\n\n\ndef get_word_list(text: str):\n    result = []\n    for pattern in word_pattern_list:\n        word_list = re.findall(pattern, text)\n        for child_list in word_list:\n            for word in child_list if isinstance(child_list, tuple) else [child_list]:\n                # 不能有: 所以再使用: 进行分割\n                if word.__contains__(':'):\n                    item_list = word.split(\":\")\n                    for w in item_list:\n                        result.append(w)\n                else:\n                    result.append(word)\n    return result\n\n\ndef replace_word(word_dict, text: str):\n    for key in word_dict:\n        pattern = '(?<!#)' + re.escape(word_dict[key]) + '(?!#)'\n        text = re.sub(pattern, key, text)\n    return text\n\n\ndef get_word_key(text: str, use_word_list):\n    j_word = next((j for j in jieba_word_list_cache if j not in text and all(j not in used for used in use_word_list)),\n                  None)\n    if j_word:\n        return j_word\n    j_word = str(uuid.uuid7())\n    jieba.add_word(j_word)\n    return j_word\n\n\ndef to_word_dict(word_list: List, text: str):\n    word_dict = {}\n    for word in word_list:\n        key = get_word_key(text, set(word_dict))\n        word_dict['#' + key + '#'] = word\n    return word_dict\n\n\ndef get_key_by_word_dict(key, word_dict):\n    v = word_dict.get(key)\n    if v is None:\n        return key\n    return v\n\n\ndef to_ts_vector(text: str):\n    # 分词\n    result = jieba.lcut(text, cut_all=True)\n    return \" \".join(result)\n\n\ndef to_query(text: str):\n    extract_tags = jieba.lcut(text, cut_all=True)\n    result = \" \".join(extract_tags)\n    return result\n"
  },
  {
    "path": "apps/folders/__init__.py",
    "content": ""
  },
  {
    "path": "apps/folders/api/__init__.py",
    "content": "# coding=utf-8\n"
  },
  {
    "path": "apps/folders/api/folder.py",
    "content": "# coding=utf-8\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, DefaultResultSerializer\nfrom folders.models.folder import FolderCreateRequest, FolderEditRequest\nfrom folders.serializers.folder import FolderSerializer\n\n\nclass FolderCreateResponse(ResultSerializer):\n    def get_data(self):\n        return FolderSerializer()\n\n\nclass FolderCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source\",\n                description=\"菜单\",\n                type=OpenApiTypes.STR,\n                enum=[\"APPLICATION\", \"KNOWLEDGE\", \"TOOL\"],\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_request():\n        return FolderCreateRequest\n\n    @staticmethod\n    def get_response():\n        return FolderCreateResponse\n\n\nclass FolderReadAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source\",\n                description=\"菜单\",\n                type=OpenApiTypes.STR,\n                enum=[\"APPLICATION\", \"KNOWLEDGE\", \"TOOL\"],\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"folder_id\",\n                description=\"文件夹id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_response():\n        return FolderCreateResponse\n\n\nclass FolderEditAPI(FolderReadAPI):\n\n    @staticmethod\n    def get_request():\n        return FolderEditRequest\n\n\nclass FolderDeleteAPI(FolderReadAPI):\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass FolderTreeReadAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source\",\n                description=\"菜单\",\n                type=OpenApiTypes.STR,\n                enum=[\"APPLICATION\", \"KNOWLEDGE\", \"TOOL\"],\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"name\",\n                description=\"名称\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n        ]\n"
  },
  {
    "path": "apps/folders/models/__init__.py",
    "content": ""
  },
  {
    "path": "apps/folders/models/folder.py",
    "content": "from django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\n\nclass FolderCreateRequest(serializers.Serializer):\n    name = serializers.CharField(required=True, max_length=64, label=_('folder name'))\n    desc = serializers.CharField(\n        required=False, max_length=200, allow_null=True, allow_blank=True, label=_('folder description')\n    )\n\n    parent_id = serializers.CharField(\n        required=False, allow_null=True, allow_blank=True, default='root', label=_('parent id')\n    )\n\n\nclass FolderEditRequest(serializers.Serializer):\n    name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('folder name'))\n    desc = serializers.CharField(\n        required=False, max_length=200, allow_null=True, allow_blank=True, label=_('folder description')\n    )\n\n    parent_id = serializers.CharField(\n        required=False, allow_null=True, allow_blank=True, default='root', label=_('parent id')\n    )\n"
  },
  {
    "path": "apps/folders/serializers/__init__.py",
    "content": "# coding=utf-8\n"
  },
  {
    "path": "apps/folders/serializers/folder.py",
    "content": "# -*- coding: utf-8 -*-\n\nimport uuid_utils.compat as uuid\nfrom django.db import transaction\nfrom django.db.models import QuerySet, Q, Func, F, TextField\nfrom django.db.models.functions import Cast\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models.application import Application, ApplicationFolder\nfrom application.serializers.application import ApplicationOperateSerializer\nfrom application.serializers.application_folder import ApplicationFolderTreeSerializer\nfrom common.constants.permission_constants import Group, ResourcePermission, ResourcePermissionRole, RoleConstants\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.exception.app_exception import AppApiException\nfrom folders.api.folder import FolderCreateRequest\nfrom knowledge.models import KnowledgeFolder, Knowledge\nfrom knowledge.serializers.knowledge import KnowledgeSerializer\nfrom knowledge.serializers.knowledge_folder import KnowledgeFolderTreeSerializer\nfrom system_manage.models import WorkspaceUserResourcePermission\nfrom system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer\nfrom tools.models import ToolFolder, Tool\nfrom tools.serializers.tool import ToolSerializer\nfrom tools.serializers.tool_folder import ToolFolderTreeSerializer\nfrom users.serializers.user import is_workspace_manage\n\n\ndef has_exact_permission_by_role(user_id: str, workspace_id: str, permission_id: str, role_type: str):\n    workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n    role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n    is_x_pack_ee = workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None\n    if is_x_pack_ee:\n        return QuerySet(workspace_user_role_mapping_model).select_related('role', 'user').filter(\n            Q(role__rolepermission__permission_id=permission_id) | Q(role__internal=True),\n            workspace_id=workspace_id,\n            user_id=user_id,\n            role__type=role_type,\n        ).exists()\n\n    return False\n\ndef get_source_type(source):\n    if source == Group.TOOL.name:\n        return Tool\n    elif source == Group.APPLICATION.name:\n        return Application\n    elif source == Group.KNOWLEDGE.name:\n        return Knowledge\n    else:\n        return None\n\n\ndef get_folder_type(source):\n    if source == Group.TOOL.name:\n        return ToolFolder\n    elif source == Group.APPLICATION.name:\n        return ApplicationFolder\n    elif source == Group.KNOWLEDGE.name:\n        return KnowledgeFolder\n    else:\n        return None\n\n\ndef get_folder_tree_serializer(source):\n    if source == Group.TOOL.name:\n        return ToolFolderTreeSerializer\n    elif source == Group.APPLICATION.name:\n        return ApplicationFolderTreeSerializer\n    elif source == Group.KNOWLEDGE.name:\n        return KnowledgeFolderTreeSerializer\n    else:\n        return None\n\n\nFOLDER_DEPTH = 10000\n\n\ndef check_depth(source, parent_id, workspace_id, current_depth=0):\n    # Folder 不能超过3层\n    Folder = get_folder_type(source)  # noqa\n\n    if parent_id != workspace_id:\n        # 计算当前层级\n        depth = 1  # 当前要创建的节点算一层\n        current_parent_id = parent_id\n\n        # 向上追溯父节点\n        while current_parent_id != workspace_id:\n            depth += 1\n            parent_node = QuerySet(Folder).filter(id=current_parent_id).first()\n            if parent_node is None:\n                break\n            current_parent_id = parent_node.parent_id\n\n        # 验证层级深度\n        if depth + current_depth > FOLDER_DEPTH:\n            raise serializers.ValidationError(_('Folder depth cannot exceed 10000 levels'))\n\n\ndef get_max_depth(current_node):\n    if not current_node:\n        return 0\n\n    # 获取所有后代节点\n    descendants = current_node.get_descendants()\n\n    if not descendants.exists():\n        return 0\n\n    # 获取最大深度\n    max_level = descendants.order_by('-level').first().level\n    current_level = current_node.level\n    max_depth = max_level - current_level\n\n    return max_depth\n\n\ndef has_target_permission(workspace_id, source, user_id, target):\n    return QuerySet(WorkspaceUserResourcePermission).filter(workspace_id=workspace_id, user_id=user_id,\n                                                            auth_target_type=source, target=target,\n                                                            permission_list__contains=['MANAGE']).exists()\n\n\nclass FolderSerializer(serializers.Serializer):\n    id = serializers.CharField(required=True, label=_('folder id'))\n    name = serializers.CharField(required=True, label=_('folder name'))\n    desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('folder description'))\n    user_id = serializers.CharField(required=True, label=_('folder user id'))\n    workspace_id = serializers.CharField(required=False, label=_('workspace id'))\n    parent_id = serializers.CharField(required=False, label=_('parent id'))\n\n    class Create(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        source = serializers.CharField(required=True, label=_('source'))\n\n        def insert(self, instance, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                FolderCreateRequest(data=instance).is_valid(raise_exception=True)\n\n            workspace_id = self.data.get('workspace_id')\n            if not workspace_id:\n                workspace_id = 'default'\n            parent_id = instance.get('parent_id')\n            if not parent_id:\n                parent_id = workspace_id\n            name = instance.get('name')\n\n            Folder = get_folder_type(self.data.get('source'))  # noqa\n            if QuerySet(Folder).filter(name=name, workspace_id=workspace_id, parent_id=parent_id).exists():\n                raise serializers.ValidationError(_('Folder name already exists'))\n            # Folder 不能超过3层\n            check_depth(self.data.get('source'), parent_id, workspace_id)\n\n            folder = Folder(\n                id=uuid.uuid7(),\n                name=instance.get('name'),\n                desc=instance.get('desc'),\n                user_id=self.data.get('user_id'),\n                workspace_id=workspace_id,\n                parent_id=parent_id\n            )\n            folder.save()\n\n            UserResourcePermissionSerializer(data={\n                'workspace_id': self.data.get('workspace_id'),\n                'user_id': self.data.get('user_id'),\n                'auth_target_type': self.data.get('source')\n            }).auth_resource(str(folder.id), is_folder=True)\n\n            return FolderSerializer(folder).data\n\n    class Operate(serializers.Serializer):\n        id = serializers.CharField(required=True, label=_('folder id'))\n        workspace_id = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('workspace id'))\n        source = serializers.CharField(required=True, label=_('source'))\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n\n        @transaction.atomic\n        def edit(self, instance):\n            self.is_valid(raise_exception=True)\n            Folder = get_folder_type(self.data.get('source'))  # noqa\n            current_id = self.data.get('id')\n            current_node = Folder.objects.get(id=current_id)\n            if current_node is None:\n                raise serializers.ValidationError(_('Folder does not exist'))\n            # 模块间的移动\n            parent_id = instance.get('parent_id')\n            if parent_id is None:\n                parent_id = current_node.parent_id\n            # 如果要修改文件夹名称，检查同级目录下是否存在同名文件夹\n            new_name = instance.get('name')\n            if new_name is not None and new_name != current_node.name:\n                if QuerySet(Folder).filter(\n                        name=new_name,\n                        parent_id=parent_id,\n                        workspace_id=current_node.workspace_id\n                ).exclude(id=current_id).exists():\n                    raise serializers.ValidationError(_('Folder name already exists'))\n\n            edit_field_list = ['name', 'desc']\n            edit_dict = {field: instance.get(field) for field in edit_field_list if (\n                    field in instance and instance.get(field) is not None)}\n\n            QuerySet(Folder).filter(id=current_id).update(**edit_dict)\n            current_node.refresh_from_db()\n\n            if parent_id is not None and current_id != current_node.workspace_id and current_node.parent_id != parent_id:\n\n                source_type = self.data.get('source')\n                if has_target_permission(current_node.workspace_id, source_type, self.data.get('user_id'),\n                                         parent_id) or is_workspace_manage(self.data.get('user_id'),\n                                                                           current_node.workspace_id):\n                    current_depth = get_max_depth(current_node)\n                    check_depth(self.data.get('source'), parent_id, current_node.workspace_id, current_depth)\n                    parent = Folder.objects.get(id=parent_id)\n\n                    if QuerySet(Folder).filter(name=current_node.name, parent_id=parent_id,\n                                               workspace_id=current_node.workspace_id).exists():\n                        raise serializers.ValidationError(_('Folder name already exists'))\n\n                    current_node.parent = parent\n                    current_node.save()\n                    current_node.refresh_from_db()\n                else:\n                    raise AppApiException(403, _('No permission for the target folder'))\n\n            return self.one()\n\n        def one(self):\n            self.is_valid(raise_exception=True)\n            Folder = get_folder_type(self.data.get('source'))  # noqa\n            folder = QuerySet(Folder).filter(id=self.data.get('id')).first()\n            return FolderSerializer(folder).data\n\n        @transaction.atomic\n        def delete(self):\n            self.is_valid(raise_exception=True)\n            Folder = get_folder_type(self.data.get('source'))  # noqa\n            Source = get_source_type(self.data.get('source'))  # noqa\n            folder = Folder.objects.filter(id=self.data.get('id')).first()\n            if not folder:\n                raise serializers.ValidationError(_('Folder does not exist'))\n            if folder.id == folder.workspace_id:\n                raise serializers.ValidationError(_('Cannot delete root folder'))\n\n            # 工作空间管理员可以删除\n            workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id'))\n            if workspace_manage:\n                nodes = Folder.objects.filter(id=self.data.get('id')).get_descendants(include_self=True)\n                for node in nodes:\n                    # print(node)\n                    # 删除相关的资源\n                    self.delete_source(node)\n                    # 删除节点\n                    node.delete()\n            # 普通用户删除的文件夹内全部都得是自己有权限的资源\n            else:\n                nodes = Folder.objects.filter(id=self.data.get('id')).get_descendants(include_self=True)\n                for node in nodes:\n                    # 删除相关的资源\n                    source_ids = (Source.objects.filter(folder_id=node.id)\n                                  .annotate(id_str=Cast('id', TextField()))\n                                  .values_list('id_str', flat=True))\n                    # 检查文件夹是否存在未授权当前用户的资源\n                    auth_list = QuerySet(WorkspaceUserResourcePermission).filter(\n                        Q(workspace_id=self.data.get('workspace_id')) &\n                        Q(user_id=self.data.get('user_id')) &\n                        Q(auth_target_type=self.data.get('source')) &\n                        Q(target__in=source_ids) &\n                        Q(permission_list__overlap=[ResourcePermission.MANAGE, ResourcePermissionRole.ROLE])\n                    ).count()\n                    if auth_list != len(source_ids):\n                        raise AppApiException(500, _('This folder contains resources that you dont have permission'))\n                    self.delete_source(node)\n                    node.delete()\n\n        def delete_source(self, node):\n            Source = get_source_type(self.data.get('source'))  # noqa\n            source_ids = Source.objects.filter(folder_id=node.id).values_list('id', flat=True)\n            source = self.data.get('source')\n\n            for source_id in source_ids:\n                if source == Group.TOOL.name:\n                    ToolSerializer.Operate(data={\n                        'workspace_id': self.data.get('workspace_id'),\n                        'id': source_id,\n                    }).delete()\n                elif source == Group.APPLICATION.name:\n                    ApplicationOperateSerializer(data={\n                        'workspace_id': self.data.get('workspace_id'),\n                        'application_id': source_id,\n                        'user_id': self.data.get('user_id'),\n                    }).delete()\n                elif source == Group.KNOWLEDGE.name:\n                    KnowledgeSerializer.Operate(data={\n                        'workspace_id': self.data.get('workspace_id'),\n                        'knowledge_id': source_id,\n                        'user_id': self.data.get('user_id'),\n                    }).delete()\n\n\nclass FolderTreeSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('workspace id'))\n    source = serializers.CharField(required=True, label=_('source'))\n\n    @staticmethod\n    def _check_tree_integrity(queryset):\n        \"\"\"检查树结构完整性\"\"\"\n        for folder in queryset:\n            if folder.lft >= folder.rght:\n                return True  # 需要重建\n            if folder.is_leaf_node() and folder.get_children().exists():\n                return True  # 需要重建\n        return False\n\n    @staticmethod\n    def _having_read_permission_by_role(user_id: str, workspace_id: str, source: str):\n        workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n        role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n        is_x_pack_ee = workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None\n        if is_x_pack_ee:\n            return QuerySet(workspace_user_role_mapping_model).select_related('role', 'user').filter(\n                Q(role__rolepermission__permission_id=f\"{source}_FOLDER:READ\") | Q(role__internal=True),\n                workspace_id=workspace_id,\n                user_id=user_id,\n                role__type=RoleConstants.USER.value.__str__(),\n            ).exists()\n\n        return False\n\n    def get_folder_tree(self,\n                        current_user, name=None):\n        self.is_valid(raise_exception=True)\n        user_id = current_user.id\n        workspace_id = self.data.get('workspace_id')\n        source = self.data.get('source')\n\n        Folder = get_folder_type(source)  # noqa\n\n        # 检查特定工作空间的树结构完整性\n        workspace_folders = Folder.objects.filter(workspace_id=workspace_id)\n        # 如果发现数据不一致，重建整个表（这是 MPTT 的限制）\n        if self._check_tree_integrity(workspace_folders):\n            Folder.objects.rebuild()\n\n        workspace_manage = is_workspace_manage(user_id, workspace_id)\n\n        base_q = Q(workspace_id=workspace_id)\n\n        if name is not None:\n            base_q &= Q(name__contains=name)\n        if not workspace_manage:\n            having_read_permission_by_role = has_exact_permission_by_role(user_id, workspace_id, f\"{source}_FOLDER:READ\", RoleConstants.USER.value.__str__())\n            permission_condition = ['VIEW']\n            if having_read_permission_by_role:\n                permission_condition = ['VIEW', 'ROLE']\n\n            base_q &= (Q(id__in=WorkspaceUserResourcePermission.objects.filter(user_id=current_user.id,\n                                                                               auth_target_type=self.data.get('source'),\n                                                                               workspace_id=self.data.get(\n                                                                                   'workspace_id'),\n                                                                               permission_list__overlap=permission_condition)\n            .values_list(\n                'target', flat=True)) | Q(id=self.data.get('workspace_id')))\n\n        nodes = Folder.objects.filter(base_q).get_cached_trees()\n\n        TreeSerializer = get_folder_tree_serializer(self.data.get('source'))  # noqa\n        serializer = TreeSerializer(nodes, many=True)\n\n        return [d for d in serializer.data if\n                d.get('id') == d.get('workspace_id')] if name is None else serializer.data  # 这是可序列化的字典\n"
  },
  {
    "path": "apps/folders/urls.py",
    "content": "from django.urls import path\n\nfrom . import views\n\napp_name = \"folder\"\nurlpatterns = [\n    path('workspace/<str:workspace_id>/<str:source>/folder', views.FolderView.as_view()),\n    path('workspace/<str:workspace_id>/<str:source>/folder/<str:folder_id>', views.FolderView.Operate.as_view()),\n]\n"
  },
  {
    "path": "apps/folders/views/__init__.py",
    "content": "from .folder import *\n"
  },
  {
    "path": "apps/folders/views/folder.py",
    "content": "from django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import Permission, Group, Operate, RoleConstants, ViewPermission, \\\n    PermissionConstants, CompareConstants\nfrom common.log.log import log\nfrom common.result import result\nfrom folders.api.folder import FolderCreateAPI, FolderEditAPI, FolderReadAPI, FolderTreeReadAPI, FolderDeleteAPI\nfrom folders.serializers.folder import FolderSerializer, FolderTreeSerializer, get_folder_type\n\n\ndef get_folder_operation_object(folder_id, source):\n    Folder = get_folder_type(source)\n    folder_model = QuerySet(model=Folder).filter(id=folder_id).first()\n    if folder_model is not None:\n        return {\n            'name': folder_model.name\n        }\n    return {}\n\n\nclass FolderView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create folder'),\n        summary=_('Create folder'),\n        operation_id=_('Create folder'),  # type: ignore\n        parameters=FolderCreateAPI.get_parameters(),\n        request=FolderCreateAPI.get_request(),\n        responses=FolderCreateAPI.get_response(),\n        tags=[_('Folder')]  # type: ignore\n    )\n    @has_permissions(\n        lambda r, kwargs: Permission(group=Group(f\"{kwargs.get('source')}_FOLDER\"), operate=Operate.CREATE,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{r.data.get('parent_id')}\"),\n        lambda r, kwargs: Permission(group=Group(f\"{kwargs.get('source')}_FOLDER\"), operate=Operate.CREATE,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"\n                                     ),\n        lambda r, kwargs: ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                         [Permission(group=Group(f\"{kwargs.get('source')}_FOLDER\"),\n                                                     operate=Operate.EDIT,\n                                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{r.data.get('parent_id')}\"\n                                                     )], CompareConstants.AND),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role()\n    )\n    @log(\n        menu='folder', operate='Create folder',\n        get_operation_object=lambda r, k: {'name': r.data.get('name')},\n\n    )\n    def post(self, request: Request, workspace_id: str, source: str):\n        return result.success(FolderSerializer.Create(\n            data={'user_id': request.user.id,\n                  'source': source,\n                  'workspace_id': workspace_id}\n        ).insert(request.data))\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get folder tree'),\n        summary=_('Get folder tree'),\n        operation_id=_('Get folder tree'),  # type: ignore\n        parameters=FolderTreeReadAPI.get_parameters(),\n        responses=FolderTreeReadAPI.get_response(),\n        tags=[_('Folder')]  # type: ignore\n    )\n    @has_permissions(\n        lambda r, kwargs: Permission(group=Group(f\"{kwargs.get('source')}_WORKSPACE_USER_RESOURCE_PERMISSION\"),\n                                     operate=Operate.READ,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}\"),\n        lambda r, kwargs: Permission(group=Group(kwargs.get('source')), operate=Operate.READ,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}\"),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role(),\n        RoleConstants.ADMIN, RoleConstants.EXTENDS_ADMIN\n    )\n    def get(self, request: Request, workspace_id: str, source: str):\n        return result.success(FolderTreeSerializer(\n            data={'workspace_id': workspace_id, 'source': source}\n        ).get_folder_tree(request.user, request.query_params.get('name')))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Update folder'),\n            summary=_('Update folder'),\n            operation_id=_('Update folder'),  # type: ignore\n            parameters=FolderEditAPI.get_parameters(),\n            request=FolderEditAPI.get_request(),\n            responses=FolderEditAPI.get_response(),\n            tags=[_('Folder')]  # type: ignore\n        )\n        @has_permissions(\n            lambda r, kwargs: Permission(group=Group(f\"{kwargs.get('source')}_FOLDER\"), operate=Operate.EDIT,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"\n                                         ),\n            lambda r, kwargs: Permission(group=Group(f\"{kwargs.get('source')}_FOLDER\"), operate=Operate.EDIT,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{kwargs.get('folder_id')}\"\n                                         ),\n            lambda r, kwargs: ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                             [Permission(group=Group(f\"{kwargs.get('source')}_FOLDER\"),\n                                                         operate=Operate.EDIT,\n                                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{kwargs.get('folder_id')}\"\n                                                         )], CompareConstants.AND),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role()\n        )\n        @log(\n            menu='folder', operate='Edit folder',\n            get_operation_object=lambda r, k: get_folder_operation_object(k.get('folder_id'), k.get('source')),\n        )\n        def put(self, request: Request, workspace_id: str, source: str, folder_id: str):\n            return result.success(FolderSerializer.Operate(\n                data={'id': folder_id, 'workspace_id': workspace_id, 'source': source, 'user_id': request.user.id}\n            ).edit(request.data))\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get folder'),\n            summary=_('Get folder'),\n            operation_id=_('Get folder'),  # type: ignore\n            parameters=FolderReadAPI.get_parameters(),\n            responses=FolderReadAPI.get_response(),\n            tags=[_('Folder')]  # type: ignore\n        )\n        @has_permissions(\n            lambda r, kwargs: Permission(group=Group(kwargs.get('source')), operate=Operate.READ,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}\"),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role(),\n            RoleConstants.ADMIN, RoleConstants.EXTENDS_ADMIN\n        )\n        def get(self, request: Request, workspace_id: str, source: str, folder_id: str):\n            return result.success(FolderSerializer.Operate(\n                data={'id': folder_id, 'workspace_id': workspace_id, 'source': source, 'user_id': request.user.id}\n            ).one())\n\n        @extend_schema(\n            methods=['DELETE'],\n            description=_('Delete folder'),\n            summary=_('Delete folder'),\n            operation_id=_('Delete folder'),  # type: ignore\n            parameters=FolderDeleteAPI.get_parameters(),\n            responses=FolderDeleteAPI.get_response(),\n            tags=[_('Folder')]  # type: ignore\n        )\n        @has_permissions(\n            lambda r, kwargs: Permission(group=Group(f\"{kwargs.get('source')}_FOLDER\"), operate=Operate.DELETE,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"\n                                         ),\n            lambda r, kwargs: Permission(group=Group(f\"{kwargs.get('source')}_FOLDER\"), operate=Operate.DELETE,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{kwargs.get('folder_id')}\"\n                                         ),\n            lambda r, kwargs: ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                             [Permission(group=Group(f\"{kwargs.get('source')}_FOLDER\"),\n                                                         operate=Operate.DELETE,\n                                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source')}/{kwargs.get('folder_id')}\"\n                                                         )], CompareConstants.AND),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role()\n        )\n        @log(\n            menu='folder', operate='Delete folder',\n            get_operation_object=lambda r, k: get_folder_operation_object(k.get('folder_id'), k.get('source')),\n        )\n        def delete(self, request: Request, workspace_id: str, source: str, folder_id: str):\n            return result.success(FolderSerializer.Operate(\n                data={'id': folder_id, 'workspace_id': workspace_id, 'source': source, 'user_id': request.user.id}\n            ).delete())\n"
  },
  {
    "path": "apps/knowledge/__init__.py",
    "content": ""
  },
  {
    "path": "apps/knowledge/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "apps/knowledge/api/__init__.py",
    "content": ""
  },
  {
    "path": "apps/knowledge/api/document.py",
    "content": "from drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import DefaultResultSerializer\nfrom knowledge.serializers.common import BatchSerializer\nfrom knowledge.serializers.document import DocumentInstanceSerializer, DocumentWebInstanceSerializer, \\\n    CancelInstanceSerializer, BatchCancelInstanceSerializer, DocumentRefreshSerializer, BatchEditHitHandlingSerializer, \\\n    DocumentBatchRefreshSerializer, DocumentBatchGenerateRelatedSerializer, DocumentMigrateSerializer\n\n\nclass DocumentSplitAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return {\n            'multipart/form-data': {\n                'type': 'object',\n                'properties': {\n                    'file': {\n                        'type': 'string',\n                        'format': 'binary'  # Tells Swagger it's a file\n                    },\n                    'limit': {\n                        'type': 'integer',\n                        'description': '分段长度'\n                    },\n                    'patterns': {\n                        'type': 'string',\n                        'description': '分段正则列表'\n                    },\n                    'with_filter': {\n                        'type': 'boolean',\n                        'description': '是否清除特殊字符'\n                    }\n                }\n            }\n        }\n\n\nclass DocumentBatchAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return BatchSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass DocumentBatchCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return DocumentInstanceSerializer(many=True)\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass DocumentCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return DocumentInstanceSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass DocumentReadAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass DocumentEditAPI(DocumentReadAPI):\n    @staticmethod\n    def get_request():\n        return DocumentInstanceSerializer\n\n\nclass DocumentDeleteAPI(DocumentReadAPI):\n    pass\n\n\nclass TableDocumentCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n\n        ]\n\n    @staticmethod\n    def get_request():\n        return {\n            'multipart/form-data': {\n                'type': 'object',\n                'properties': {\n                    'file': {\n                        'type': 'string',\n                        'format': 'binary'  # Tells Swagger it's a file\n                    }\n                }\n            }\n        }\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass QaDocumentCreateAPI(TableDocumentCreateAPI):\n    pass\n\n\nclass WebDocumentCreateAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return DocumentWebInstanceSerializer\n\n\nclass CancelTaskAPI(DocumentReadAPI):\n    @staticmethod\n    def get_request():\n        return CancelInstanceSerializer\n\n\nclass BatchCancelTaskAPI(DocumentReadAPI):\n    @staticmethod\n    def get_request():\n        return BatchCancelInstanceSerializer\n\n\nclass SyncWebAPI(DocumentReadAPI):\n    pass\n\n\nclass RefreshAPI(DocumentReadAPI):\n    @staticmethod\n    def get_request():\n        return DocumentRefreshSerializer\n\n\nclass BatchEditHitHandlingAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return BatchEditHitHandlingSerializer\n\n\nclass DocumentTreeReadAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"folder_id\",\n                description=\"文件夹id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"user_id\",\n                description=\"用户id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"name\",\n                description=\"名称\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"desc\",\n                description=\"描述\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n        ]\n\n\nclass DocumentSplitPatternAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass BatchRefreshAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return DocumentBatchRefreshSerializer\n\n\nclass BatchGenerateRelatedAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return DocumentBatchGenerateRelatedSerializer\n\n\nclass TemplateExportAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"type\",\n                description=\"Export template type csv|excel\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass DocumentExportAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass DocumentMigrateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"target_knowledge_id\",\n                description=\"目标知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return DocumentMigrateSerializer\n\n\nclass DocumentDownloadSourceAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass DocumentTagsAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return None\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer"
  },
  {
    "path": "apps/knowledge/api/file.py",
    "content": "from drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import DefaultResultSerializer\n\n\nclass FileUploadAPI(APIMixin):\n\n    @staticmethod\n    def get_request():\n        return {\n            'multipart/form-data': {\n                'type': 'object',\n                'properties': {\n                    'file': {\n                        'type': 'string',\n                        'format': 'binary',\n                        'description': '要上传的文件'\n\n                    },\n                    \"source_id\": {\n                        'type': 'string',\n                        'description': '资源id 如果source_type为[TEMPORARY_30_MINUTE,TEMPORARY_120_MINUTE,TEMPORARY_1_DAY,SYSTEM] 其他的需要为对应资源的id'\n                    },\n                    \"source_type\": {\n                        'type': 'string',\n                        'description': '资源类型[KNOWLEDGE,APPLICATION,TOOL,DOCUMENT,CHAT,SYSTEM,TEMPORARY_30_MINUTE,TEMPORARY_120_MINUTE,TEMPORARY_1_DAY]'\n                    }\n                }\n            }\n        }\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass FileGetAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"file_id\",\n                description=\"文件id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n"
  },
  {
    "path": "apps/knowledge/api/knowledge.py",
    "content": "from drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, DefaultResultSerializer\nfrom knowledge.serializers.common import GenerateRelatedSerializer\nfrom knowledge.serializers.knowledge import KnowledgeBaseCreateRequest, KnowledgeModelSerializer, KnowledgeEditRequest, \\\n    KnowledgeWebCreateRequest, HitTestSerializer\n\n\nclass KnowledgeCreateResponse(ResultSerializer):\n    def get_data(self):\n        return KnowledgeModelSerializer()\n\n\nclass KnowledgeReadAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_response():\n        return KnowledgeCreateResponse\n\n\nclass KnowledgeBaseCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_request():\n        return KnowledgeBaseCreateRequest\n\n    @staticmethod\n    def get_response():\n        return KnowledgeCreateResponse\n\n\nclass KnowledgeWebCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_request():\n        return KnowledgeWebCreateRequest\n\n    @staticmethod\n    def get_response():\n        return KnowledgeCreateResponse\n\n\nclass KnowledgeEditAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_request():\n        return KnowledgeEditRequest\n\n    @staticmethod\n    def get_response():\n        return KnowledgeCreateResponse\n\n\nclass KnowledgeTreeReadAPI(KnowledgeReadAPI):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"folder_id\",\n                description=\"文件夹id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"user_id\",\n                description=\"用户id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"name\",\n                description=\"名称\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"desc\",\n                description=\"描述\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n        ]\n\n\nclass KnowledgePageAPI(KnowledgeReadAPI):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"current_page\",\n                description=\"当前页码\",\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"page_size\",\n                description=\"每页条数\",\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"folder_id\",\n                description=\"文件夹id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"name\",\n                description=\"名称\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"desc\",\n                description=\"描述\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n        ]\n\n\nclass SyncWebAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass GenerateRelatedAPI(SyncWebAPI):\n    @staticmethod\n    def get_request():\n        return GenerateRelatedSerializer\n\n\nclass HitTestAPI(SyncWebAPI):\n    @staticmethod\n    def get_request():\n        return HitTestSerializer\n\n\nclass EmbeddingAPI(SyncWebAPI):\n    pass\n\n\nclass GetModelAPI(SyncWebAPI):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\nclass KnowledgeExportAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer"
  },
  {
    "path": "apps/knowledge/api/knowledge_version.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_version.py\n    @date：2025/6/4 17:33\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, ResultPageSerializer\nfrom knowledge.serializers.knowledge_version import KnowledgeVersionModelSerializer\n\n\nclass KnowledgeListVersionResult(ResultSerializer):\n    def get_data(self):\n        return KnowledgeVersionModelSerializer(many=True)\n\n\nclass KnowledgePageVersionResult(ResultPageSerializer):\n    def get_data(self):\n        return KnowledgeVersionModelSerializer(many=True)\n\n\nclass KnowledgeWorkflowVersionResult(ResultSerializer):\n    def get_data(self):\n        return KnowledgeVersionModelSerializer()\n\n\nclass KnowledgeVersionAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"knowledge ID\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n\nclass KnowledgeVersionOperateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"knowledge_version_id\",\n                description=\"工作流版本id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n            , *KnowledgeVersionAPI.get_parameters()\n        ]\n\n    @staticmethod\n    def get_response():\n        return KnowledgeWorkflowVersionResult\n\n\nclass KnowledgeVersionListAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"name\",\n                description=\"Version Name\",\n                type=OpenApiTypes.STR,\n                required=False,\n            )\n            , *KnowledgeVersionOperateAPI.get_parameters()]\n\n    @staticmethod\n    def get_response():\n        return KnowledgeListVersionResult\n\n\nclass KnowledgeVersionPageAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return KnowledgeVersionListAPI.get_parameters()\n\n    @staticmethod\n    def get_response():\n        return KnowledgePageVersionResult\n"
  },
  {
    "path": "apps/knowledge/api/knowledge_workflow.py",
    "content": "# coding=utf-8\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import DefaultResultSerializer\nfrom knowledge.serializers.knowledge_workflow import KnowledgeWorkflowActionRequestSerializer, \\\n    KnowledgeWorkflowImportRequest\nfrom knowledge.serializers.knowledge_workflow import KnowledgeWorkflowActionListQuerySerializer\n\n\nclass KnowledgeWorkflowApi(APIMixin):\n    pass\n\n\nclass KnowledgeWorkflowVersionApi(APIMixin):\n    pass\n\n\nclass KnowledgeWorkflowActionPageApi(APIMixin):\n    @staticmethod\n    def get_request():\n        return KnowledgeWorkflowActionListQuerySerializer\n\n\nclass KnowledgeWorkflowActionApi(APIMixin):\n    @staticmethod\n    def get_request():\n        return KnowledgeWorkflowActionRequestSerializer\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    class Operate(APIMixin):\n        @staticmethod\n        def get_parameters():\n            return [\n                OpenApiParameter(\n                    name=\"workspace_id\",\n                    description=\"工作空间id\",\n                    type=OpenApiTypes.STR,\n                    location='path',\n                    required=True,\n                ),\n                OpenApiParameter(\n                    name=\"knowledge_id\",\n                    description=\"知识库id\",\n                    type=OpenApiTypes.STR,\n                    location='path',\n                    required=True,\n                ),\n                OpenApiParameter(\n                    name=\"knowledge_action_id\",\n                    description=\"知识库执行id\",\n                    type=OpenApiTypes.STR,\n                    location='path',\n                    required=True,\n                )\n            ]\n\nclass KnowledgeWorkflowExportApi(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\nclass KnowledgeWorkflowImportApi(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return KnowledgeWorkflowExportApi.get_parameters()\n\n    @staticmethod\n    def get_request():\n        return KnowledgeWorkflowImportRequest\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n"
  },
  {
    "path": "apps/knowledge/api/paragraph.py",
    "content": "from drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import DefaultResultSerializer, ResultSerializer\nfrom knowledge.serializers.common import BatchSerializer\nfrom knowledge.serializers.paragraph import ParagraphSerializer, ParagraphBatchGenerateRelatedSerializer\nfrom knowledge.serializers.problem import ProblemSerializer\n\n\nclass ParagraphReadResponse(ResultSerializer):\n    @staticmethod\n    def get_data():\n        return ParagraphSerializer(many=True)\n\n\nclass ParagraphReadAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"title\",\n                description=\"标题\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"content\",\n                description=\"内容\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return ParagraphReadResponse\n\n\nclass ParagraphCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return ParagraphSerializer\n\n    @staticmethod\n    def get_response():\n        return ParagraphReadResponse\n\n\nclass ParagraphBatchDeleteAPI(ParagraphCreateAPI):\n    @staticmethod\n    def get_request():\n        return BatchSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ParagraphBatchGenerateRelatedAPI(ParagraphCreateAPI):\n    @staticmethod\n    def get_request():\n        return ParagraphBatchGenerateRelatedSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ParagraphGetAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"paragraph_id\",\n                description=\"段落id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n\nclass ParagraphEditAPI(ParagraphGetAPI):\n\n    @staticmethod\n    def get_request():\n        return ParagraphSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ProblemCreateAPI(ParagraphGetAPI):\n    @staticmethod\n    def get_request():\n        return ProblemSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass UnAssociationAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"paragraph_id\",\n                description=\"段落id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"problem_id\",\n                description=\"问题id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=True,\n            )\n        ]\n\n\nclass AssociationAPI(UnAssociationAPI):\n    pass\n\n\nclass ParagraphPageAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"paragraph_id\",\n                description=\"段落id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"current_page\",\n                description=\"当前页\",\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"page_size\",\n                description=\"每页大小\",\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n        ]\n\n\nclass ParagraphMigrateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"target_knowledge_id\",\n                description=\"目标知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"target_document_id\",\n                description=\"目标文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return BatchSerializer\n\n\nclass ParagraphAdjustOrderAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"document_id\",\n                description=\"文档id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"paragraph_id\",\n                description=\"段落id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"new_position\",\n                description=\"新的顺序\",\n                type=OpenApiTypes.INT,\n                location='query',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n"
  },
  {
    "path": "apps/knowledge/api/problem.py",
    "content": "from django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\nfrom rest_framework import serializers\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import DefaultResultSerializer\nfrom knowledge.serializers.problem import BatchAssociation, ProblemEditSerializer\n\n\nclass ProblemReadAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ProblemBatchCreateAPI(ProblemReadAPI):\n    @staticmethod\n    def get_request():\n        return serializers.ListField(required=True, label=_('problem list'),\n                                     child=serializers.UUIDField(required=True, label=_('problem')))\n\n\nclass BatchAssociationAPI(ProblemReadAPI):\n    @staticmethod\n    def get_request():\n        return BatchAssociation\n\n\nclass BatchDeleteAPI(ProblemReadAPI):\n    @staticmethod\n    def get_request():\n        return serializers.ListField(required=True, label=_('problem list'),\n                                     child=serializers.UUIDField(required=True, label=_('problem')))\n\n\nclass ProblemPageAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"current_page\",\n                description=\"当前页码\",\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"page_size\",\n                description=\"每页条数\",\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ProblemDeleteAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"problem_id\",\n                description=\"问题id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ProblemEditAPI(ProblemDeleteAPI):\n    @staticmethod\n    def get_request():\n        return ProblemEditSerializer\n\n\nclass ProblemParagraphAPI(ProblemDeleteAPI):\n    pass\n"
  },
  {
    "path": "apps/knowledge/api/tag.py",
    "content": "from drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import DefaultResultSerializer\nfrom knowledge.serializers.common import BatchSerializer\nfrom knowledge.serializers.tag import TagCreateSerializer, TagEditSerializer\n\n\nclass TagCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return TagCreateSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass TagDeleteAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"tag_id\",\n                description=\"标签id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return None\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass DocsTagDeleteAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"tag_id\",\n                description=\"标签id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return BatchSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass TagEditAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"knowledge_id\",\n                description=\"知识库id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"tag_id\",\n                description=\"标签id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return TagEditSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n"
  },
  {
    "path": "apps/knowledge/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass KnowledgeConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'knowledge'\n"
  },
  {
    "path": "apps/knowledge/migrations/0001_initial.py",
    "content": "# Generated by Django 5.2.4 on 2025-07-14 03:50\n\nimport django.contrib.postgres.search\nimport django.db.models.deletion\nimport knowledge.models.knowledge\nimport mptt.fields\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\nfrom knowledge.models import KnowledgeFolder\n\n\ndef insert_default_data(apps, schema_editor):\n    # 创建一个根模块（没有父节点）\n    KnowledgeFolder.objects.create(id='default', name='根目录', user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', workspace_id='default')\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('models_provider', '0001_initial'),\n        ('users', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='File',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('file_name', models.CharField(default='', max_length=256, verbose_name='文件名称')),\n                ('file_size', models.IntegerField(default=0, verbose_name='文件大小')),\n                ('sha256_hash', models.CharField(default='', verbose_name='文件sha256_hash标识')),\n                ('source_type', models.CharField(choices=[('KNOWLEDGE', 'Knowledge'), ('APPLICATION', 'Application'), ('TOOL', 'Tool'), ('DOCUMENT', 'Document'), ('CHAT', 'Chat'), ('TEMPORARY_30_MINUTE', 'Temporary 30 Minute'), ('TEMPORARY_120_MINUTE', 'Temporary 120 Minute'), ('TEMPORARY_1_DAY', 'Temporary 1 Day')], db_index=True, default='TEMPORARY_120_MINUTE', verbose_name='资源类型')),\n                ('source_id', models.CharField(db_index=True, default='TEMPORARY_120_MINUTE', verbose_name='资源id')),\n                ('loid', models.IntegerField(verbose_name='loid')),\n                ('meta', models.JSONField(default=dict, verbose_name='文件关联数据')),\n            ],\n            options={\n                'db_table': 'file',\n            },\n        ),\n        migrations.CreateModel(\n            name='Knowledge',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('name', models.CharField(db_index=True, max_length=150, verbose_name='知识库名称')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('desc', models.CharField(max_length=256, verbose_name='描述')),\n                ('type', models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型')], db_index=True, default=0, verbose_name='类型')),\n                ('scope', models.CharField(choices=[('SHARED', '共享'), ('WORKSPACE', '工作空间可用')], db_index=True, default='WORKSPACE', max_length=20, verbose_name='可用范围')),\n                ('file_size_limit', models.IntegerField(default=100, verbose_name='文件大小限制')),\n                ('file_count_limit', models.IntegerField(default=50, verbose_name='文件数量限制')),\n                ('meta', models.JSONField(default=dict, verbose_name='元数据')),\n                ('embedding_model', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='models_provider.model')),\n                ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),\n            ],\n            options={\n                'db_table': 'knowledge',\n            },\n        ),\n        migrations.CreateModel(\n            name='Document',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('name', models.CharField(db_index=True, max_length=150, verbose_name='文档名称')),\n                ('char_length', models.IntegerField(verbose_name='文档字符数 冗余字段')),\n                ('status', models.CharField(db_index=True, default=knowledge.models.knowledge.get_default_status, max_length=20, verbose_name='状态')),\n                ('status_meta', models.JSONField(default=knowledge.models.knowledge.default_status_meta, verbose_name='状态统计数据')),\n                ('is_active', models.BooleanField(db_index=True, default=True)),\n                ('type', models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型')], db_index=True, default=0, verbose_name='类型')),\n                ('hit_handling_method', models.CharField(choices=[('optimization', '模型优化'), ('directly_return', '直接返回')], default='optimization', max_length=20, verbose_name='命中处理方式')),\n                ('directly_return_similarity', models.FloatField(default=0.9, verbose_name='直接回答相似度')),\n                ('meta', models.JSONField(default=dict, verbose_name='元数据')),\n                ('knowledge', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge', verbose_name='知识库id')),\n            ],\n            options={\n                'db_table': 'document',\n            },\n        ),\n        migrations.CreateModel(\n            name='KnowledgeFolder',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('name', models.CharField(db_index=True, max_length=64, verbose_name='文件夹名称')),\n                ('desc', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('lft', models.PositiveIntegerField(editable=False)),\n                ('rght', models.PositiveIntegerField(editable=False)),\n                ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),\n                ('level', models.PositiveIntegerField(editable=False)),\n                ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='knowledge.knowledgefolder')),\n                ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),\n            ],\n            options={\n                'db_table': 'knowledge_folder',\n            },\n        ),\n        migrations.AddField(\n            model_name='knowledge',\n            name='folder',\n            field=models.ForeignKey(default='default', on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledgefolder', verbose_name='文件夹id'),\n        ),\n        migrations.CreateModel(\n            name='Paragraph',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('content', models.CharField(max_length=102400, verbose_name='段落内容')),\n                ('title', models.CharField(db_index=True, default='', max_length=256, verbose_name='标题')),\n                ('status', models.CharField(db_index=True, default=knowledge.models.knowledge.get_default_status, max_length=20, verbose_name='状态')),\n                ('status_meta', models.JSONField(default=knowledge.models.knowledge.default_status_meta, verbose_name='状态数据')),\n                ('hit_num', models.IntegerField(default=0, verbose_name='命中次数')),\n                ('is_active', models.BooleanField(db_index=True, default=True)),\n                ('position', models.IntegerField(db_index=True, default=0, verbose_name='段落顺序')),\n                ('document', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.document')),\n                ('knowledge', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')),\n            ],\n            options={\n                'db_table': 'paragraph',\n            },\n        ),\n        migrations.CreateModel(\n            name='Embedding',\n            fields=[\n                ('id', models.CharField(max_length=128, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('source_id', models.CharField(db_index=True, max_length=128, verbose_name='资源id')),\n                ('source_type', models.CharField(choices=[(0, '问题'), (1, '段落'), (2, '标题')], db_index=True, default=0, max_length=5, verbose_name='资源类型')),\n                ('is_active', models.BooleanField(default=True, max_length=1, verbose_name='是否可用')),\n                ('embedding', knowledge.models.knowledge.VectorField(verbose_name='向量')),\n                ('search_vector', django.contrib.postgres.search.SearchVectorField(default='', verbose_name='分词')),\n                ('meta', models.JSONField(default=dict, verbose_name='元数据')),\n                ('document', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.document', verbose_name='文档关联')),\n                ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge', verbose_name='文档关联')),\n                ('paragraph', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.paragraph', verbose_name='段落关联')),\n            ],\n            options={\n                'db_table': 'embedding',\n            },\n        ),\n        migrations.CreateModel(\n            name='Problem',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('content', models.CharField(db_index=True, max_length=256, verbose_name='问题内容')),\n                ('hit_num', models.IntegerField(default=0, verbose_name='命中次数')),\n                ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')),\n            ],\n            options={\n                'db_table': 'problem',\n            },\n        ),\n        migrations.CreateModel(\n            name='ProblemParagraphMapping',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('document', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.document')),\n                ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge')),\n                ('paragraph', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.paragraph')),\n                ('problem', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.problem')),\n            ],\n            options={\n                'db_table': 'problem_paragraph_mapping',\n            },\n        ),\n        migrations.RunPython(insert_default_data),\n    ]\n"
  },
  {
    "path": "apps/knowledge/migrations/0002_alter_file_source_type.py",
    "content": "# Generated by Django 5.2.4 on 2025-08-11 09:45\n\nfrom django.db import migrations, models, connection\n\n\n\n\ndef add_allow_download_to_existing_documents(apps, schema_editor):\n\n    # 使用原生SQL进行批量更新，避免加载大量对象到内存\n    with connection.cursor() as cursor:\n        # 为meta为null的记录设置初始值\n        cursor.execute(\"\"\"\n                       UPDATE document\n                       SET meta = '{\"allow_download\": true}'::jsonb\n                       WHERE meta IS NULL\n                       \"\"\")\n\n        # 为meta不包含allow_download键的记录添加该字段\n        cursor.execute(\"\"\"\n                       UPDATE document\n                       SET meta = meta || '{\"allow_download\": true}'::jsonb\n                       WHERE meta IS NOT NULL\n                         AND NOT (meta ? 'allow_download')\n                       \"\"\")\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('knowledge', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='file',\n            name='source_type',\n            field=models.CharField(choices=[('KNOWLEDGE', 'Knowledge'), ('APPLICATION', 'Application'), ('TOOL', 'Tool'), ('DOCUMENT', 'Document'), ('CHAT', 'Chat'), ('SYSTEM', 'System'), ('TEMPORARY_30_MINUTE', 'Temporary 30 Minute'), ('TEMPORARY_120_MINUTE', 'Temporary 120 Minute'), ('TEMPORARY_1_DAY', 'Temporary 1 Day')], db_index=True, default='TEMPORARY_120_MINUTE', verbose_name='资源类型'),\n        ),\n        migrations.RunPython(add_allow_download_to_existing_documents)\n    ]\n"
  },
  {
    "path": "apps/knowledge/migrations/0003_tag_documenttag.py",
    "content": "# Generated by Django 5.2.7 on 2025-10-11 07:38\n\nimport django.db.models.deletion\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('knowledge', '0002_alter_file_source_type'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Tag',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('key', models.CharField(db_index=True, max_length=64, verbose_name='标签键')),\n                ('value', models.CharField(db_index=True, max_length=128, verbose_name='标签值')),\n                ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge', verbose_name='知识库')),\n            ],\n            options={\n                'db_table': 'tag',\n            },\n        ),\n        migrations.CreateModel(\n            name='DocumentTag',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('document', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.document', verbose_name='文档')),\n                ('tag', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.tag', verbose_name='标签')),\n            ],\n            options={\n                'db_table': 'document_tag',\n            },\n        ),\n        migrations.AddIndex(\n            model_name='tag',\n            index=models.Index(fields=['knowledge', 'key'], name='tag_knowled_cba590_idx'),\n        ),\n        migrations.AlterUniqueTogether(\n            name='tag',\n            unique_together={('knowledge', 'key', 'value')},\n        ),\n        migrations.AlterUniqueTogether(\n            name='documenttag',\n            unique_together={('document', 'tag')},\n        ),\n    ]\n"
  },
  {
    "path": "apps/knowledge/migrations/0004_alter_document_type_alter_knowledge_type_and_more.py",
    "content": "# Generated by Django 5.2.4 on 2025-11-04 05:54\n\nimport django.db.models.deletion\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('knowledge', '0003_tag_documenttag'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='document',\n            name='type',\n            field=models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型'), (4, '工作流类型')], db_index=True, default=0, verbose_name='类型'),\n        ),\n        migrations.AlterField(\n            model_name='knowledge',\n            name='type',\n            field=models.IntegerField(choices=[(0, '通用类型'), (1, 'web站点类型'), (2, '飞书类型'), (3, '语雀类型'), (4, '工作流类型')], db_index=True, default=0, verbose_name='类型'),\n        ),\n        migrations.CreateModel(\n            name='KnowledgeWorkflow',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),\n                ('is_publish', models.BooleanField(db_index=True, default=False, verbose_name='是否发布')),\n                ('publish_time', models.DateTimeField(blank=True, null=True, verbose_name='发布时间')),\n                ('knowledge', models.OneToOneField(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='workflow', to='knowledge.knowledge', verbose_name='知识库')),\n            ],\n            options={\n                'db_table': 'knowledge_workflow',\n            },\n        ),\n        migrations.CreateModel(\n            name='KnowledgeWorkflowVersion',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),\n                ('publish_user_id', models.UUIDField(default=None, null=True, verbose_name='发布者id')),\n                ('publish_user_name', models.CharField(default='', max_length=128, verbose_name='发布者名称')),\n                ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='knowledge.knowledge', verbose_name='知识库')),\n                ('workflow', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='knowledge.knowledgeworkflow', verbose_name='工作流')),\n            ],\n            options={\n                'db_table': 'knowledge_workflow_version',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "apps/knowledge/migrations/0005_knowledgeaction.py",
    "content": "# Generated by Django 5.2.8 on 2025-11-19 06:06\n\nimport common.encoder.encoder\nimport django.db.models.deletion\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('knowledge', '0004_alter_document_type_alter_knowledge_type_and_more'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='KnowledgeAction',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('state', models.CharField(choices=[('PENDING', 'Pending'), ('STARTED', 'Started'), ('SUCCESS', 'Success'), ('FAILURE', 'Failure'), ('REVOKE', 'Revoke'), ('REVOKED', 'Revoked')], default='STARTED', max_length=20, verbose_name='状态')),\n                ('details', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='执行详情')),\n                ('run_time', models.FloatField(default=0, verbose_name='运行时长')),\n                ('meta', models.JSONField(default=dict, verbose_name='元数据')),\n                ('knowledge', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.DO_NOTHING, to='knowledge.knowledge', verbose_name='知识库')),\n            ],\n            options={\n                'db_table': 'knowledge_action',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "apps/knowledge/migrations/0006_paragraph_chunks.py",
    "content": "# Generated by Django 5.2.8 on 2025-11-24 07:09\n\nimport django.contrib.postgres.fields\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('knowledge', '0005_knowledgeaction'),\n    ]\n\n    operations = [\n        migrations.AddField(\n            model_name='paragraph',\n            name='chunks',\n            field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(), default=list, size=None, verbose_name='块'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/knowledge/migrations/0007_remove_knowledgeworkflowversion_workflow_and_more.py",
    "content": "# Generated by Django 5.2.8 on 2025-12-01 06:28\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('knowledge', '0006_paragraph_chunks'),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name='knowledgeworkflowversion',\n            name='workflow',\n        ),\n        migrations.AddField(\n            model_name='knowledgeworkflowversion',\n            name='name',\n            field=models.CharField(default='', max_length=128, verbose_name='版本名称'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/knowledge/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "apps/knowledge/models/__init__.py",
    "content": "from .knowledge import *\n"
  },
  {
    "path": "apps/knowledge/models/knowledge.py",
    "content": "import io\nimport zipfile\nfrom enum import Enum\n\nimport uuid_utils.compat as uuid\nfrom django.contrib.postgres.fields import ArrayField\nfrom django.contrib.postgres.search import SearchVectorField\nfrom django.db import models\nfrom django.db.models import QuerySet\nfrom django.db.models.signals import pre_delete\nfrom django.dispatch import receiver\nfrom mptt.fields import TreeForeignKey\nfrom mptt.models import MPTTModel\n\nfrom common.db.sql_execute import select_one\nfrom common.mixins.app_model_mixin import AppModelMixin\nfrom common.utils.common import get_sha256_hash\nfrom models_provider.models import Model\nfrom users.models import User\n\n\nclass KnowledgeType(models.IntegerChoices):\n    BASE = 0, '通用类型'\n    WEB = 1, 'web站点类型'\n    LARK = 2, '飞书类型'\n    YUQUE = 3, '语雀类型'\n    WORKFLOW = 4, '工作流类型'\n\n\nclass TaskType(Enum):\n    # 向量\n    EMBEDDING = 1\n    # 生成问题\n    GENERATE_PROBLEM = 2\n    # 同步\n    SYNC = 3\n\n\nclass State(Enum):\n    # 等待\n    PENDING = '0'\n    # 执行中\n    STARTED = '1'\n    # 成功\n    SUCCESS = '2'\n    # 失败\n    FAILURE = '3'\n    # 取消任务\n    REVOKE = '4'\n    # 取消成功\n    REVOKED = '5'\n    # 忽略\n    IGNORED = 'n'\n\n\nclass KnowledgeScope(models.TextChoices):\n    SHARED = \"SHARED\", '共享'\n    WORKSPACE = \"WORKSPACE\", \"工作空间可用\"\n\n\nclass HitHandlingMethod(models.TextChoices):\n    optimization = 'optimization', '模型优化'\n    directly_return = 'directly_return', '直接返回'\n\n\nclass Status:\n    type_cls = TaskType\n    state_cls = State\n\n    def __init__(self, status: str = None):\n        self.task_status = {}\n        status_list = list(status[::-1] if status is not None else '')\n        for _type in self.type_cls:\n            index = _type.value - 1\n            _state = self.state_cls(status_list[index] if len(status_list) > index else 'n')\n            self.task_status[_type] = _state\n\n    @staticmethod\n    def of(status: str):\n        return Status(status)\n\n    def __str__(self):\n        result = []\n        for _type in sorted(self.type_cls, key=lambda item: item.value, reverse=True):\n            result.insert(len(self.type_cls) - _type.value, self.task_status[_type].value)\n        return ''.join(result)\n\n    def __setitem__(self, key, value):\n        self.task_status[key] = value\n\n    def __getitem__(self, item):\n        return self.task_status[item]\n\n    def update_status(self, task_type: TaskType, state: State):\n        self.task_status[task_type] = state\n\n\ndef default_status_meta():\n    return {\"state_time\": {}}\n\n\nclass KnowledgeFolder(MPTTModel, AppModelMixin):\n    id = models.CharField(primary_key=True, max_length=64, editable=False, verbose_name=\"主键id\")\n    name = models.CharField(max_length=64, verbose_name=\"文件夹名称\", db_index=True)\n    desc = models.CharField(max_length=200, null=True, blank=True, verbose_name=\"描述\")\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    parent = TreeForeignKey('self', on_delete=models.DO_NOTHING, null=True, blank=True, related_name='children')\n\n    class Meta:\n        db_table = \"knowledge_folder\"\n\n    class MPTTMeta:\n        order_insertion_by = ['name']\n\n\nclass Knowledge(AppModelMixin):\n    \"\"\"\n    知识库表\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    name = models.CharField(max_length=150, verbose_name=\"知识库名称\", db_index=True)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    desc = models.CharField(max_length=256, verbose_name=\"描述\")\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n    type = models.IntegerField(verbose_name='类型', choices=KnowledgeType.choices, default=KnowledgeType.BASE,\n                               db_index=True)\n    scope = models.CharField(max_length=20, verbose_name='可用范围', choices=KnowledgeScope.choices,\n                             default=KnowledgeScope.WORKSPACE, db_index=True)\n    folder = models.ForeignKey(KnowledgeFolder, on_delete=models.DO_NOTHING, verbose_name=\"文件夹id\", default='default')\n    embedding_model = models.ForeignKey(Model, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n    file_size_limit = models.IntegerField(verbose_name=\"文件大小限制\", default=100)\n    file_count_limit = models.IntegerField(verbose_name=\"文件数量限制\", default=50)\n    meta = models.JSONField(verbose_name=\"元数据\", default=dict)\n\n    class Meta:\n        db_table = \"knowledge\"\n\n\nclass KnowledgeWorkflow(AppModelMixin):\n    \"\"\"\n    知识库工作流表\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    knowledge = models.OneToOneField(Knowledge, on_delete=models.CASCADE, verbose_name=\"知识库\",\n                                     db_constraint=False, related_name='workflow')\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    work_flow = models.JSONField(verbose_name=\"工作流数据\", default=dict)\n    is_publish = models.BooleanField(verbose_name=\"是否发布\", default=False, db_index=True)\n    publish_time = models.DateTimeField(verbose_name=\"发布时间\", null=True, blank=True)\n\n    class Meta:\n        db_table = \"knowledge_workflow\"\n\n\nclass KnowledgeWorkflowVersion(AppModelMixin):\n    \"\"\"\n    知识库工作流版本表 - 记录工作流历史版本\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    knowledge = models.ForeignKey(Knowledge, on_delete=models.CASCADE, verbose_name=\"知识库\", db_constraint=False)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    name = models.CharField(verbose_name=\"版本名称\", max_length=128, default=\"\")\n    work_flow = models.JSONField(verbose_name=\"工作流数据\", default=dict)\n    publish_user_id = models.UUIDField(verbose_name=\"发布者id\", max_length=128, default=None, null=True)\n    publish_user_name = models.CharField(verbose_name=\"发布者名称\", max_length=128, default=\"\")\n\n    class Meta:\n        db_table = \"knowledge_workflow_version\"\n\n\ndef get_default_status():\n    return Status('').__str__()\n\n\nclass Document(AppModelMixin):\n    \"\"\"\n    文档表\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, verbose_name=\"知识库id\")\n    name = models.CharField(max_length=150, verbose_name=\"文档名称\", db_index=True)\n    char_length = models.IntegerField(verbose_name=\"文档字符数 冗余字段\")\n    status = models.CharField(verbose_name='状态', max_length=20, default=get_default_status, db_index=True)\n    status_meta = models.JSONField(verbose_name=\"状态统计数据\", default=default_status_meta)\n    is_active = models.BooleanField(default=True, db_index=True)\n    type = models.IntegerField(verbose_name='类型', choices=KnowledgeType.choices, default=KnowledgeType.BASE,\n                               db_index=True)\n    hit_handling_method = models.CharField(verbose_name='命中处理方式', max_length=20,\n                                           choices=HitHandlingMethod.choices,\n                                           default=HitHandlingMethod.optimization)\n    directly_return_similarity = models.FloatField(verbose_name='直接回答相似度', default=0.9)\n\n    meta = models.JSONField(verbose_name=\"元数据\", default=dict)\n\n    class Meta:\n        db_table = \"document\"\n\n\nclass Tag(AppModelMixin):\n    \"\"\"\n    标签表 - 存储标签的key-value定义\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, verbose_name=\"知识库\", db_constraint=False)\n    key = models.CharField(max_length=64, verbose_name=\"标签键\", db_index=True)\n    value = models.CharField(max_length=128, verbose_name=\"标签值\", db_index=True)\n\n    class Meta:\n        db_table = \"tag\"\n        unique_together = [['knowledge', 'key', 'value']]  # 在同一知识库内key-value组合唯一\n        indexes = [\n            models.Index(fields=['knowledge', 'key']),\n        ]\n\n\nclass DocumentTag(AppModelMixin):\n    \"\"\"\n    文档标签关联表\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    document = models.ForeignKey(Document, on_delete=models.DO_NOTHING, verbose_name=\"文档\", db_constraint=False)\n    tag = models.ForeignKey(Tag, on_delete=models.DO_NOTHING, verbose_name=\"标签\", db_constraint=False)\n\n    class Meta:\n        db_table = \"document_tag\"\n        unique_together = [['document', 'tag']]  # 文档和标签的组合唯一\n\n\nclass Paragraph(AppModelMixin):\n    \"\"\"\n    段落表\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    document = models.ForeignKey(Document, on_delete=models.DO_NOTHING, db_constraint=False)\n    knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING)\n    content = models.CharField(max_length=102400, verbose_name=\"段落内容\")\n    title = models.CharField(max_length=256, verbose_name=\"标题\", default=\"\", db_index=True)\n    status = models.CharField(verbose_name='状态', max_length=20, default=get_default_status, db_index=True)\n    status_meta = models.JSONField(verbose_name=\"状态数据\", default=default_status_meta)\n    hit_num = models.IntegerField(verbose_name=\"命中次数\", default=0)\n    is_active = models.BooleanField(default=True, db_index=True)\n    position = models.IntegerField(verbose_name=\"段落顺序\", default=0, db_index=True)\n    chunks = ArrayField(verbose_name=\"块\", base_field=models.CharField(), default=list)\n\n    class Meta:\n        db_table = \"paragraph\"\n\n\nclass Problem(AppModelMixin):\n    \"\"\"\n    问题表\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, db_constraint=False)\n    content = models.CharField(max_length=256, verbose_name=\"问题内容\", db_index=True)\n    hit_num = models.IntegerField(verbose_name=\"命中次数\", default=0)\n\n    class Meta:\n        db_table = \"problem\"\n\n\nclass ProblemParagraphMapping(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, db_constraint=False)\n    document = models.ForeignKey(Document, on_delete=models.DO_NOTHING, db_constraint=False)\n    problem = models.ForeignKey(Problem, on_delete=models.DO_NOTHING, db_constraint=False)\n    paragraph = models.ForeignKey(Paragraph, on_delete=models.DO_NOTHING, db_constraint=False)\n\n    class Meta:\n        db_table = \"problem_paragraph_mapping\"\n\n\nclass SourceType(models.IntegerChoices):\n    \"\"\"订单类型\"\"\"\n    PROBLEM = 0, '问题'\n    PARAGRAPH = 1, '段落'\n    TITLE = 2, '标题'\n\n\nclass SearchMode(models.TextChoices):\n    embedding = 'embedding'\n    keywords = 'keywords'\n    blend = 'blend'\n\n\nclass FileSourceType(models.TextChoices):\n    # 知识库  跟随知识库被删除而被删除 source_id 为知识库id\n    KNOWLEDGE = \"KNOWLEDGE\"\n    # 应用  跟随应用被删除而被删除 source_id 为应用id\n    APPLICATION = \"APPLICATION\"\n    # 工具  跟随工具被删除而被删除 source_id 为应用id\n    TOOL = \"TOOL\"\n    # 文档\n    DOCUMENT = \"DOCUMENT\"\n    # 对话\n    CHAT = \"CHAT\"\n    SYSTEM = \"SYSTEM\"\n    # 临时30分钟 数据30分钟后被清理 source_id 为TEMPORARY_30_MINUTE\n    TEMPORARY_30_MINUTE = \"TEMPORARY_30_MINUTE\"\n    # 临时120分钟 数据120分钟后被清理 source_id为TEMPORARY_100_MINUTE\n    TEMPORARY_120_MINUTE = \"TEMPORARY_120_MINUTE\"\n    # 临时1天 数据1天后被清理 source_id为TEMPORARY_1_DAY\n    TEMPORARY_1_DAY = \"TEMPORARY_1_DAY\"\n\n\nclass VectorField(models.Field):\n    def db_type(self, connection):\n        return 'vector'\n\n\nclass Embedding(models.Model):\n    id = models.CharField(max_length=128, primary_key=True, verbose_name=\"主键id\")\n    source_id = models.CharField(max_length=128, verbose_name=\"资源id\", db_index=True)\n    source_type = models.CharField(verbose_name='资源类型', max_length=5, choices=SourceType.choices,\n                                   default=SourceType.PROBLEM, db_index=True)\n    is_active = models.BooleanField(verbose_name=\"是否可用\", max_length=1, default=True)\n    knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, verbose_name=\"文档关联\", db_constraint=False)\n    document = models.ForeignKey(Document, on_delete=models.DO_NOTHING, verbose_name=\"文档关联\", db_constraint=False)\n    paragraph = models.ForeignKey(Paragraph, on_delete=models.DO_NOTHING, verbose_name=\"段落关联\", db_constraint=False)\n    embedding = VectorField(verbose_name=\"向量\")\n    search_vector = SearchVectorField(verbose_name=\"分词\", default=\"\")\n    meta = models.JSONField(verbose_name=\"元数据\", default=dict)\n\n    class Meta:\n        db_table = \"embedding\"\n\n\nclass File(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    file_name = models.CharField(max_length=256, verbose_name=\"文件名称\", default=\"\")\n    file_size = models.IntegerField(verbose_name=\"文件大小\", default=0)\n    sha256_hash = models.CharField(verbose_name=\"文件sha256_hash标识\", default=\"\")\n    source_type = models.CharField(verbose_name=\"资源类型\", choices=FileSourceType,\n                                   default=FileSourceType.TEMPORARY_120_MINUTE.value, db_index=True)\n    source_id = models.CharField(verbose_name=\"资源id\", default=FileSourceType.TEMPORARY_120_MINUTE.value,\n                                 db_index=True)\n    loid = models.IntegerField(verbose_name=\"loid\")\n    meta = models.JSONField(verbose_name=\"文件关联数据\", default=dict)\n\n    class Meta:\n        db_table = \"file\"\n\n    def save(self, bytea=None, force_insert=False, force_update=False, using=None, update_fields=None):\n        if bytea is None:\n            raise ValueError(\"bytea参数不能为空\")\n\n        sha256_hash = get_sha256_hash(bytea)\n        self.sha256_hash = sha256_hash\n        existing_file = QuerySet(File).filter(sha256_hash=sha256_hash).first()\n        if existing_file:\n            self.loid = existing_file.loid\n            self.file_size = existing_file.file_size\n            return super().save()\n\n        compressed_data = self._compress_data(bytea)\n        self.file_size = len(compressed_data)\n\n        self.loid = self._create_large_object()\n\n        self._write_compressed_data(compressed_data)\n        # 调用父类保存\n        return super().save()\n\n    def _compress_data(self, data, compression_level=9):\n        \"\"\"压缩数据到内存\"\"\"\n        buffer = io.BytesIO()\n        with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:\n            zipinfo = zipfile.ZipInfo(self.file_name)\n            zipinfo.compress_type = zipfile.ZIP_DEFLATED\n            zip_file.writestr(zipinfo, data, compresslevel=compression_level)\n\n        return buffer.getvalue()\n\n    def _create_large_object(self):\n        result = select_one(\"SELECT lo_creat(-1)::int8 as lo_id;\", [])\n        return result['lo_id']\n\n    def _write_compressed_data(self, data, block_size=64 * 1024):\n        buffer = io.BytesIO(data)\n        offset = 0\n\n        while True:\n            chunk = buffer.read(block_size)\n            if not chunk:\n                break\n\n            offset += len(chunk)\n            select_one(\n                \"SELECT lo_put(%s::oid, %s::bigint, %s::bytea)::VARCHAR;\",\n                [self.loid, offset - len(chunk), chunk]\n            )\n\n    def get_bytes(self):\n        buffer = io.BytesIO()\n        for chunk in self.get_bytes_stream():\n            buffer.write(chunk)\n        try:\n            # 解压数据\n            with zipfile.ZipFile(buffer) as zip_file:\n                # 用 zip 内实际存储的条目名，避免文件名不匹配\n                name = zip_file.namelist()[0]\n                return zip_file.read(name)\n        except Exception as e:\n            # 如果数据不是zip格式，直接返回原始数据\n            return buffer.getvalue()\n\n    def get_bytes_stream(self, start=0, end=None, chunk_size=64 * 1024):\n        def _read_with_offset():\n            offset = start\n            while True:\n                result = select_one(\n                    \"SELECT lo_get(%s::oid, %s, %s) as chunk\",\n                    [self.loid, offset, end - offset if end and (end - offset) < chunk_size else chunk_size]\n                )\n                chunk = result['chunk'] if result else None\n                if not chunk:\n                    break\n                yield chunk\n                offset += len(chunk)\n                if len(chunk) < chunk_size:\n                    break\n                if end and offset > end:\n                    break\n\n        return _read_with_offset()\n\n\n@receiver(pre_delete, sender=File)\ndef on_delete_file(sender, instance, **kwargs):\n    exist = QuerySet(File).filter(loid=instance.loid).exclude(id=instance.id).exists()\n    if not exist:\n        select_one(f'SELECT lo_unlink({instance.loid})', [])\n"
  },
  {
    "path": "apps/knowledge/models/knowledge_action.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： knowledge_action.py\n    @date：2025/11/18 17:59\n    @desc:\n\"\"\"\nimport uuid_utils.compat as uuid\n\nfrom django.db import models\n\nfrom common.encoder.encoder import SystemEncoder\nfrom common.mixins.app_model_mixin import AppModelMixin\nfrom knowledge.models import Knowledge\n\n\nclass State(models.TextChoices):\n    # 等待\n    PENDING = 'PENDING'\n    # 执行中\n    STARTED = 'STARTED'\n    # 成功\n    SUCCESS = 'SUCCESS'\n    # 失败\n    FAILURE = 'FAILURE'\n    # 取消任务\n    REVOKE = 'REVOKE'\n    # 取消成功\n    REVOKED = 'REVOKED'\n\n\nclass KnowledgeAction(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n\n    knowledge = models.ForeignKey(Knowledge, on_delete=models.DO_NOTHING, verbose_name=\"知识库\", db_constraint=False)\n\n    state = models.CharField(verbose_name='状态', max_length=20,\n                             choices=State.choices,\n                             default=State.STARTED)\n\n    details = models.JSONField(verbose_name=\"执行详情\", default=dict, encoder=SystemEncoder)\n\n    run_time = models.FloatField(verbose_name=\"运行时长\", default=0)\n\n    meta = models.JSONField(verbose_name=\"元数据\", default=dict)\n\n    class Meta:\n        db_table = \"knowledge_action\"\n"
  },
  {
    "path": "apps/knowledge/serializers/__init__.py",
    "content": "# coding=utf-8"
  },
  {
    "path": "apps/knowledge/serializers/common.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： common_serializers.py\n    @date：2023/11/17 11:00\n    @desc:\n\"\"\"\nimport os\nimport re\nimport zipfile\nfrom typing import List\n\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.flow.tools import save_workflow_mapping, get_instance_resource, knowledge_instance_field_call_dict\nfrom common.config.embedding_config import ModelManage\nfrom common.db.search import native_search\nfrom common.db.sql_execute import sql_execute, update_execute\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import get_file_content\nfrom common.utils.fork import Fork\nfrom common.utils.logger import maxkb_logger\nfrom knowledge.models import Document, KnowledgeWorkflow, KnowledgeWorkflowVersion, KnowledgeType\nfrom knowledge.models import Paragraph, Problem, ProblemParagraphMapping, Knowledge, File\nfrom maxkb.conf import PROJECT_DIR\nfrom models_provider.tools import get_model, get_model_default_params\nfrom system_manage.models.resource_mapping import ResourceMapping, ResourceType\n\n\nclass MetaSerializer(serializers.Serializer):\n    class WebMeta(serializers.Serializer):\n        source_url = serializers.CharField(required=True, label=_('source url'))\n        selector = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('selector'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            source_url = self.data.get('source_url')\n            response = Fork(source_url, []).fork()\n            if response.status == 500:\n                raise AppApiException(500, _('URL error, cannot parse [{source_url}]').format(source_url=source_url))\n\n    class BaseMeta(serializers.Serializer):\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n\n\nclass BatchSerializer(serializers.Serializer):\n    id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_('id list'))\n\n    def is_valid(self, *, model=None, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        if model is not None:\n            id_list = self.data.get('id_list')\n            model_list = QuerySet(model).filter(id__in=id_list)\n            if len(model_list) != len(id_list):\n                model_id_list = [str(m.id) for m in model_list]\n                error_id_list = list(filter(lambda row_id: not model_id_list.__contains__(row_id), id_list))\n                raise AppApiException(500, _('The following id does not exist: {error_id_list}').format(\n                    error_id_list=error_id_list))\n\n\nclass ProblemParagraphObject:\n    def __init__(self, knowledge_id: str, document_id: str, paragraph_id: str, problem_content: str):\n        self.knowledge_id = knowledge_id\n        self.document_id = document_id\n        self.paragraph_id = paragraph_id\n        self.problem_content = problem_content\n\n\nclass GenerateRelatedSerializer(serializers.Serializer):\n    model_id = serializers.UUIDField(required=True, label=_('Model id'))\n    prompt = serializers.CharField(required=True, label=_('Prompt word'))\n    state_list = serializers.ListField(required=False, child=serializers.CharField(required=True),\n                                       label=_(\"state list\"))\n\n\nclass ProblemParagraphManage:\n    def __init__(self, problem_paragraph_object_list: List[ProblemParagraphObject], knowledge_id):\n        self.knowledge_id = knowledge_id\n        self.problem_paragraph_object_list = problem_paragraph_object_list\n\n    def to_problem_model_list(self):\n        problem_list = [item.problem_content for item in self.problem_paragraph_object_list]\n        exists_problem_list = []\n        if len(self.problem_paragraph_object_list) > 0:\n            # 查询到已存在的问题列表\n            exists_problem_list = QuerySet(Problem).filter(knowledge_id=self.knowledge_id,\n                                                           content__in=problem_list).all()\n        problem_content_dict = {}\n        problem_model_list = [\n            or_get(\n                exists_problem_list,\n                problemParagraphObject.problem_content,\n                problemParagraphObject.knowledge_id,\n                problemParagraphObject.document_id,\n                problemParagraphObject.paragraph_id, problem_content_dict\n            ) for problemParagraphObject in self.problem_paragraph_object_list]\n\n        problem_paragraph_mapping_list = [\n            ProblemParagraphMapping(\n                id=uuid.uuid7(),\n                document_id=document_id,\n                problem_id=problem_model.id,\n                paragraph_id=paragraph_id,\n                knowledge_id=self.knowledge_id\n            ) for problem_model, document_id, paragraph_id in problem_model_list]\n\n        result = [\n            problem_model for problem_model, is_create in problem_content_dict.values() if is_create\n        ], problem_paragraph_mapping_list\n        return result\n\n\ndef get_embedding_model_by_knowledge_id_list(knowledge_id_list: List):\n    knowledge_list = QuerySet(Knowledge).filter(id__in=knowledge_id_list)\n    if len(set([knowledge.embedding_model_id for knowledge in knowledge_list])) > 1:\n        raise Exception(_('The knowledge base is inconsistent with the vector model'))\n    if len(knowledge_list) == 0:\n        raise Exception(_('Knowledge base setting error, please reset the knowledge base'))\n\n    default_params = get_model_default_params(knowledge_list[0].embedding_model)\n\n    return ModelManage.get_model(\n        str(knowledge_list[0].embedding_model_id),\n        lambda _id: get_model(knowledge_list[0].embedding_model, **{**default_params})\n    )\n\n\ndef get_embedding_model_by_knowledge_id(knowledge_id: str):\n    knowledge = QuerySet(Knowledge).select_related('embedding_model').filter(id=knowledge_id).first()\n\n    default_params = get_model_default_params(knowledge.embedding_model)\n\n    return ModelManage.get_model(str(knowledge.embedding_model_id),\n                                 lambda _id: get_model(knowledge.embedding_model, **{**default_params}))\n\n\ndef get_embedding_model_by_knowledge(knowledge):\n    default_params = get_model_default_params(knowledge.embedding_model)\n\n    return ModelManage.get_model(str(knowledge.embedding_model_id),\n                                 lambda _id: get_model(knowledge.embedding_model, **{**default_params}))\n\n\ndef get_embedding_model_id_by_knowledge_id(knowledge_id):\n    knowledge = QuerySet(Knowledge).select_related('embedding_model').filter(id=knowledge_id).first()\n    return str(knowledge.embedding_model_id)\n\n\ndef get_embedding_model_id_by_knowledge_id_list(knowledge_id_list: List):\n    knowledge_list = QuerySet(Knowledge).filter(id__in=knowledge_id_list)\n    if len(set([knowledge.embedding_model_id for knowledge in knowledge_list])) > 1:\n        raise Exception(_('The knowledge base is inconsistent with the vector model'))\n    if len(knowledge_list) == 0:\n        raise Exception(_('Knowledge base setting error, please reset the knowledge base'))\n    return str(knowledge_list[0].embedding_model_id)\n\n\ndef zip_dir(zip_path, output=None):\n    output = output or os.path.basename(zip_path) + '.zip'\n    zip = zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED)\n    for root, dirs, files in os.walk(zip_path):\n        relative_root = '' if root == zip_path else root.replace(zip_path, '') + os.sep\n        for filename in files:\n            zip.write(os.path.join(root, filename), relative_root + filename)\n    zip.close()\n\n\ndef is_valid_uuid(s):\n    try:\n        uuid.UUID(s)\n        return True\n    except ValueError:\n        return False\n\n\ndef write_image(zip_path: str, image_list: List[str]):\n    for image in image_list:\n        search = re.search(\"\\(.*\\)\", image)\n        if search:\n            text = search.group()\n            if text.startswith('(./oss/file/'):\n                r = text.replace('(./oss/file/', '').replace(')', '')\n                r = r.strip().split(\" \")[0]\n                if not is_valid_uuid(r):\n                    break\n                file = QuerySet(File).filter(id=r).first()\n                if file is None:\n                    break\n                zip_inner_path = os.path.join('oss', 'file', r)\n                file_path = os.path.join(zip_path, zip_inner_path)\n                if not os.path.exists(os.path.dirname(file_path)):\n                    os.makedirs(os.path.dirname(file_path))\n                with open(os.path.join(zip_path, file_path), 'wb') as f:\n                    f.write(file.get_bytes())\n\n\ndef update_document_char_length(document_id: str):\n    update_execute(get_file_content(\n        os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'update_document_char_length.sql')),\n        (document_id, document_id))\n\n\ndef list_paragraph(paragraph_list: List[str]):\n    if paragraph_list is None or len(paragraph_list) == 0:\n        return []\n    return native_search(QuerySet(Paragraph).filter(id__in=paragraph_list), get_file_content(\n        os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_paragraph.sql')))\n\n\ndef or_get(exists_problem_list, content, knowledge_id, document_id, paragraph_id, problem_content_dict):\n    if content in problem_content_dict:\n        return problem_content_dict.get(content)[0], document_id, paragraph_id\n    exists = [row for row in exists_problem_list if row.content == content]\n    if len(exists) > 0:\n        problem_content_dict[content] = exists[0], False\n        return exists[0], document_id, paragraph_id\n    else:\n        problem = Problem(id=uuid.uuid7(), content=content, knowledge_id=knowledge_id)\n        problem_content_dict[content] = problem, True\n        return problem, document_id, paragraph_id\n\n\ndef get_knowledge_operation_object(knowledge_id: str):\n    knowledge_model = QuerySet(model=Knowledge).filter(id=knowledge_id).first()\n    if knowledge_model is not None:\n        return {\n            \"name\": knowledge_model.name,\n            \"desc\": knowledge_model.desc,\n            \"type\": knowledge_model.type,\n            \"create_time\": knowledge_model.create_time,\n            \"update_time\": knowledge_model.update_time\n        }\n    return {}\n\n\ndef create_knowledge_index(knowledge_id=None, document_id=None):\n    if knowledge_id is None and document_id is None:\n        raise AppApiException(500, _('Knowledge ID or Document ID must be provided'))\n\n    if knowledge_id is not None:\n        k_id = knowledge_id\n    else:\n        document = QuerySet(Document).filter(id=document_id).first()\n        k_id = document.knowledge_id\n\n    sql = f\"SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'embedding' AND indexname = 'embedding_hnsw_idx_{k_id}'\"\n    index = sql_execute(sql, [])\n    if not index:\n        sql = f\"SELECT vector_dims(embedding) AS dims FROM embedding WHERE knowledge_id = '{k_id}' LIMIT 1\"\n        result = sql_execute(sql, [])\n        if len(result) == 0:\n            return\n        dims = result[0]['dims']\n        # 超过2000维度不创建索引，pgvector hnsw索引不支持超过2000维度\n        if dims < 2000:\n            sql = f\"\"\"CREATE INDEX \"embedding_hnsw_idx_{k_id}\" ON embedding USING hnsw ((embedding::vector({dims})) vector_cosine_ops) WHERE knowledge_id = '{k_id}'\"\"\"\n            update_execute(sql, [])\n            maxkb_logger.info(f'Created index for knowledge ID: {k_id}')\n\n\ndef drop_knowledge_index(knowledge_id=None, document_id=None):\n    if knowledge_id is None and document_id is None:\n        raise AppApiException(500, _('Knowledge ID or Document ID must be provided'))\n\n    if knowledge_id is not None:\n        k_id = knowledge_id\n    else:\n        document = QuerySet(Document).filter(id=document_id).first()\n        k_id = document.knowledge_id\n\n    sql = f\"SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'embedding' AND indexname = 'embedding_hnsw_idx_{k_id}'\"\n    index = sql_execute(sql, [])\n    if index:\n        sql = f'DROP INDEX \"embedding_hnsw_idx_{k_id}\"'\n        update_execute(sql, [])\n        maxkb_logger.info(f'Dropped index for knowledge ID: {k_id}')\n\n\ndef update_resource_mapping_by_knowledge(knowledge_id: str):\n    knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first()\n    instance_mapping = get_instance_resource(knowledge, ResourceType.KNOWLEDGE, str(knowledge.id),\n                                             knowledge_instance_field_call_dict)\n    if knowledge.type == KnowledgeType.WORKFLOW:\n        knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(\n            knowledge_id=knowledge_id).order_by(\n            '-create_time')[0:1].first()\n        if knowledge_workflow:\n            save_workflow_mapping(knowledge_workflow.work_flow, ResourceType.KNOWLEDGE,\n                                  str(knowledge_id), instance_mapping)\n            return\n    else:\n        save_workflow_mapping({}, ResourceType.KNOWLEDGE,\n                              str(knowledge_id), instance_mapping)\n"
  },
  {
    "path": "apps/knowledge/serializers/document.py",
    "content": "import io\nimport json\nimport os\nimport re\nimport traceback\nfrom collections import defaultdict\nfrom functools import reduce\nfrom tempfile import TemporaryDirectory\nfrom typing import Dict, List\n\nimport openpyxl\nimport uuid_utils.compat as uuid\nfrom celery_once import AlreadyQueued\nfrom django.contrib.postgres.fields import JSONField\nfrom django.core import validators\nfrom django.db import transaction, models\nfrom django.db.models import QuerySet, Func, F, Value\nfrom django.db.models.aggregates import Max\nfrom django.db.models.functions import Substr, Reverse\nfrom django.db.models.query_utils import Q\nfrom django.http import HttpResponse\nfrom django.utils.translation import gettext_lazy as _, gettext, get_language, to_locale\nfrom openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE\nfrom rest_framework import serializers\nfrom xlwt import Utils\n\nfrom common.db.search import native_search, get_dynamics_model, native_page_search\nfrom common.event.common import work_thread_pool\nfrom common.event.listener_manage import ListenerManagement\nfrom common.exception.app_exception import AppApiException\nfrom common.field.common import UploadedFileField\nfrom common.handle.impl.qa.csv_parse_qa_handle import CsvParseQAHandle\nfrom common.handle.impl.qa.md_parse_qa_handle import MarkdownParseQAHandle\nfrom common.handle.impl.qa.xls_parse_qa_handle import XlsParseQAHandle\nfrom common.handle.impl.qa.xlsx_parse_qa_handle import XlsxParseQAHandle\nfrom common.handle.impl.qa.zip_parse_qa_handle import ZipParseQAHandle\nfrom common.handle.impl.table.csv_parse_table_handle import CsvParseTableHandle\nfrom common.handle.impl.table.xls_parse_table_handle import XlsParseTableHandle\nfrom common.handle.impl.table.xlsx_parse_table_handle import XlsxParseTableHandle\nfrom common.handle.impl.text.csv_split_handle import CsvSplitHandle\nfrom common.handle.impl.text.doc_split_handle import DocSplitHandle\nfrom common.handle.impl.text.html_split_handle import HTMLSplitHandle\nfrom common.handle.impl.text.pdf_split_handle import PdfSplitHandle\nfrom common.handle.impl.text.text_split_handle import TextSplitHandle\nfrom common.handle.impl.text.xls_split_handle import XlsSplitHandle\nfrom common.handle.impl.text.xlsx_split_handle import XlsxSplitHandle\nfrom common.handle.impl.text.zip_split_handle import ZipSplitHandle\nfrom common.utils.common import post, get_file_content, bulk_create_in_batches, parse_image\nfrom common.utils.fork import Fork\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.split_model import get_split_model, flat_map\nfrom knowledge.models import Knowledge, Paragraph, Problem, Document, KnowledgeType, ProblemParagraphMapping, State, \\\n    TaskType, File, FileSourceType, Tag, DocumentTag\nfrom knowledge.serializers.common import ProblemParagraphManage, BatchSerializer, \\\n    get_embedding_model_id_by_knowledge_id, MetaSerializer, write_image, zip_dir\nfrom knowledge.serializers.paragraph import ParagraphSerializers, ParagraphInstanceSerializer, \\\n    delete_problems_and_mappings\nfrom knowledge.task.embedding import embedding_by_document, delete_embedding_by_document_list, \\\n    delete_embedding_by_document, delete_embedding_by_paragraph_ids, embedding_by_document_list, \\\n    update_embedding_knowledge_id\nfrom knowledge.task.generate import generate_related_by_document_id\nfrom knowledge.task.sync import sync_web_document\nfrom maxkb.const import PROJECT_DIR\nfrom models_provider.models import Model\nfrom oss.serializers.file import FileSerializer\n\ndefault_split_handle = TextSplitHandle()\nsplit_handles = [\n    HTMLSplitHandle(),\n    DocSplitHandle(),\n    PdfSplitHandle(),\n    XlsxSplitHandle(),\n    XlsSplitHandle(),\n    CsvSplitHandle(),\n    ZipSplitHandle(),\n    default_split_handle\n]\n\nmd_qa_split_handle = MarkdownParseQAHandle()\nparse_qa_handle_list = [XlsParseQAHandle(), CsvParseQAHandle(), XlsxParseQAHandle(), ZipParseQAHandle()]\nparse_table_handle_list = [CsvParseTableHandle(), XlsParseTableHandle(), XlsxParseTableHandle()]\n\n\ndef convert_uuid_to_str(obj):\n    if isinstance(obj, dict):\n        return {k: convert_uuid_to_str(v) for k, v in obj.items()}\n    elif isinstance(obj, list):\n        return [convert_uuid_to_str(i) for i in obj]\n    elif isinstance(obj, uuid.UUID):\n        return str(obj)\n    else:\n        return obj\n\n\nclass BatchCancelInstanceSerializer(serializers.Serializer):\n    id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_('id list'))\n    type = serializers.IntegerField(required=True, label=_('task type'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        _type = self.data.get('type')\n        try:\n            TaskType(_type)\n        except Exception as e:\n            raise AppApiException(500, _('task type not support'))\n\n\nclass DocumentInstanceSerializer(serializers.Serializer):\n    name = serializers.CharField(required=True, label=_('document name'), max_length=128, min_length=1,\n                                 source=_('document name'))\n    paragraphs = ParagraphInstanceSerializer(required=False, many=True, allow_null=True)\n    source_file_id = serializers.UUIDField(required=False, allow_null=True, label=_('source file id'))\n\n\nclass CancelInstanceSerializer(serializers.Serializer):\n    type = serializers.IntegerField(required=True, label=_('task type'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        _type = self.data.get('type')\n        try:\n            TaskType(_type)\n        except Exception as e:\n            raise AppApiException(500, _('task type not support'))\n\n\nclass DocumentEditInstanceSerializer(serializers.Serializer):\n    meta = serializers.DictField(required=False)\n    name = serializers.CharField(required=False, max_length=128, min_length=1, label=_('document name'),\n                                 source=_('document name'))\n    hit_handling_method = serializers.CharField(required=False, validators=[\n        validators.RegexValidator(regex=re.compile(\"^optimization|directly_return$\"),\n                                  message=_('The type only supports optimization|directly_return'),\n                                  code=500)\n    ], label=_('hit handling method'))\n\n    directly_return_similarity = serializers.FloatField(required=False, max_value=2, min_value=0,\n                                                        label=_('directly return similarity'))\n\n    is_active = serializers.BooleanField(required=False, label=_('document is active'))\n\n    @staticmethod\n    def get_meta_valid_map():\n        knowledge_meta_valid_map = {\n            KnowledgeType.BASE: MetaSerializer.BaseMeta,\n            KnowledgeType.WEB: MetaSerializer.WebMeta\n        }\n        return knowledge_meta_valid_map\n\n    def is_valid(self, *, document: Document = None):\n        super().is_valid(raise_exception=True)\n        if 'meta' in self.data and self.data.get('meta') is not None and self.data.get('meta') != {}:\n            knowledge_meta_valid_map = self.get_meta_valid_map()\n            valid_class = knowledge_meta_valid_map.get(document.type)\n            if valid_class is not None:\n                valid_class(data=self.data.get('meta')).is_valid(raise_exception=True)\n\n\nclass DocumentSplitRequest(serializers.Serializer):\n    file = serializers.ListField(required=True, label=_('file list'))\n    limit = serializers.IntegerField(required=False, label=_('limit'))\n    patterns = serializers.ListField(\n        required=False,\n        child=serializers.CharField(required=True, label=_('patterns')),\n        label=_('patterns')\n    )\n    with_filter = serializers.BooleanField(required=False, label=_('Auto Clean'))\n\n\nclass DocumentWebInstanceSerializer(serializers.Serializer):\n    source_url_list = serializers.ListField(required=True, label=_('document url list'),\n                                            child=serializers.CharField(required=True, label=_('document url list')))\n    selector = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('selector'))\n\n\nclass DocumentInstanceQASerializer(serializers.Serializer):\n    file_list = serializers.ListSerializer(required=True, label=_('file list'),\n                                           child=serializers.FileField(required=True, label=_('file')))\n\n\nclass DocumentInstanceTableSerializer(serializers.Serializer):\n    file_list = serializers.ListSerializer(required=True, label=_('file list'),\n                                           child=serializers.FileField(required=True, label=_('file')))\n\n\nclass DocumentRefreshSerializer(serializers.Serializer):\n    state_list = serializers.ListField(required=True, label=_('state list'))\n\n\nclass DocumentBatchRefreshSerializer(serializers.Serializer):\n    id_list = serializers.ListField(required=True, label=_('id list'))\n    state_list = serializers.ListField(required=True, label=_('state list'))\n\n\nclass DocumentBatchGenerateRelatedSerializer(serializers.Serializer):\n    document_id_list = serializers.ListField(required=True, label=_('document id list'))\n    model_id = serializers.UUIDField(required=True, label=_('model id'))\n    prompt = serializers.CharField(required=True, label=_('prompt'))\n    state_list = serializers.ListField(required=True, label=_('state list'))\n\n\nclass DocumentMigrateSerializer(serializers.Serializer):\n    document_id_list = serializers.ListField(required=True, label=_('document id list'))\n\n\nclass BatchEditHitHandlingSerializer(serializers.Serializer):\n    id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_('id list'))\n    hit_handling_method = serializers.CharField(required=True, label=_('hit handling method'))\n    directly_return_similarity = serializers.FloatField(required=False, max_value=2, min_value=0,\n                                                        label=_('directly return similarity'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        if self.data.get('hit_handling_method') not in ['optimization', 'directly_return']:\n            raise AppApiException(500, _('The type only supports optimization|directly_return'))\n\n\nclass DocumentSerializers(serializers.Serializer):\n    class Export(serializers.Serializer):\n        type = serializers.CharField(required=True, validators=[\n            validators.RegexValidator(regex=re.compile(\"^csv|excel$\"),\n                                      message=_('The template type only supports excel|csv'),\n                                      code=500)\n        ], label=_('type'))\n\n        def export(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            language = get_language()\n            if self.data.get('type') == 'csv':\n                file = open(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'template',\n                                 f'csv_template_{to_locale(language)}.csv'),\n                    \"rb\")\n                content = file.read()\n                file.close()\n                return HttpResponse(content, status=200, headers={'Content-Type': 'text/csv',\n                                                                  'Content-Disposition': 'attachment; filename=\"csv_template.csv\"'})\n            elif self.data.get('type') == 'excel':\n                file = open(os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'template',\n                                         f'excel_template_{to_locale(language)}.xlsx'), \"rb\")\n                content = file.read()\n                file.close()\n                return HttpResponse(content, status=200, headers={'Content-Type': 'application/vnd.ms-excel',\n                                                                  'Content-Disposition': 'attachment; filename=\"excel_template.xlsx\"'})\n            else:\n                return None\n\n        def table_export(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            language = get_language()\n            if self.data.get('type') == 'csv':\n                file = open(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'template',\n                                 f'table_template_{to_locale(language)}.csv'),\n                    \"rb\")\n                content = file.read()\n                file.close()\n                return HttpResponse(content, status=200, headers={'Content-Type': 'text/cxv',\n                                                                  'Content-Disposition': 'attachment; filename=\"csv_template.csv\"'})\n            elif self.data.get('type') == 'excel':\n                file = open(os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'template',\n                                         f'table_template_{to_locale(language)}.xlsx'),\n                            \"rb\")\n                content = file.read()\n                file.close()\n                return HttpResponse(content, status=200, headers={'Content-Type': 'application/vnd.ms-excel',\n                                                                  'Content-Disposition': 'attachment; filename=\"excel_template.xlsx\"'})\n            else:\n                return None\n\n    class Migrate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        target_knowledge_id = serializers.UUIDField(required=True, label=_('target knowledge id'))\n        document_id_list = serializers.ListField(required=True, label=_('document list'),\n                                                 child=serializers.UUIDField(required=True, label=_('document id')))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('target_knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        @transaction.atomic\n        def migrate(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            knowledge_id = self.data.get('knowledge_id')\n            target_knowledge_id = self.data.get('target_knowledge_id')\n            knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first()\n            target_knowledge = QuerySet(Knowledge).filter(id=target_knowledge_id).first()\n            document_id_list = self.data.get('document_id_list')\n            document_list = QuerySet(Document).filter(knowledge_id=knowledge_id, id__in=document_id_list)\n            paragraph_list = QuerySet(Paragraph).filter(knowledge_id=knowledge_id, document_id__in=document_id_list)\n\n            problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(paragraph__in=paragraph_list)\n            problem_list = QuerySet(Problem).filter(\n                id__in=[problem_paragraph_mapping.problem_id for problem_paragraph_mapping in\n                        problem_paragraph_mapping_list])\n            target_problem_list = list(\n                QuerySet(Problem).filter(content__in=[problem.content for problem in problem_list],\n                                         knowledge_id=target_knowledge_id))\n            target_handle_problem_list = [\n                self.get_target_knowledge_problem(target_knowledge_id, problem_paragraph_mapping,\n                                                  problem_list, target_problem_list) for\n                problem_paragraph_mapping\n                in\n                problem_paragraph_mapping_list]\n\n            create_problem_list = [problem for problem, is_create in target_handle_problem_list if\n                                   is_create is not None and is_create]\n            # 插入问题\n            QuerySet(Problem).bulk_create(create_problem_list)\n            # 修改mapping\n            QuerySet(ProblemParagraphMapping).bulk_update(problem_paragraph_mapping_list,\n                                                          ['problem_id', 'knowledge_id'])\n            # 修改文档\n            if knowledge.type == KnowledgeType.BASE.value and target_knowledge.type == KnowledgeType.WEB.value:\n                document_list.update(knowledge_id=target_knowledge_id, type=KnowledgeType.WEB,\n                                     meta={'source_url': '', 'selector': ''})\n            elif target_knowledge.type == KnowledgeType.BASE.value and knowledge.type == KnowledgeType.WEB.value:\n                document_list.update(knowledge_id=target_knowledge_id, type=KnowledgeType.BASE,\n                                     meta={})\n            else:\n                document_list.update(knowledge_id=target_knowledge_id)\n            model_id = None\n            if knowledge.embedding_model_id != target_knowledge.embedding_model_id:\n                model_id = get_embedding_model_id_by_knowledge_id(target_knowledge_id)\n\n            pid_list = [paragraph.id for paragraph in paragraph_list]\n            # 修改段落信息\n            paragraph_list.update(knowledge_id=target_knowledge_id)\n            # 修改向量信息\n            if model_id:\n                delete_embedding_by_paragraph_ids(pid_list)\n                ListenerManagement.update_status(QuerySet(Document).filter(id__in=document_id_list),\n                                                 TaskType.EMBEDDING,\n                                                 State.PENDING)\n                ListenerManagement.update_status(QuerySet(Paragraph).filter(document_id__in=document_id_list),\n                                                 TaskType.EMBEDDING,\n                                                 State.PENDING)\n                ListenerManagement.get_aggregation_document_status_by_query_set(\n                    QuerySet(Document).filter(id__in=document_id_list))()\n                embedding_by_document_list.delay(document_id_list, model_id)\n            else:\n                update_embedding_knowledge_id(pid_list, target_knowledge_id)\n\n        @staticmethod\n        def get_target_knowledge_problem(target_knowledge_id: str,\n                                         problem_paragraph_mapping,\n                                         source_problem_list,\n                                         target_problem_list):\n            source_problem_list = [source_problem for source_problem in source_problem_list if\n                                   source_problem.id == problem_paragraph_mapping.problem_id]\n            problem_paragraph_mapping.knowledge_id = target_knowledge_id\n            if len(source_problem_list) > 0:\n                problem_content = source_problem_list[-1].content\n                problem_list = [problem for problem in target_problem_list if problem.content == problem_content]\n                if len(problem_list) > 0:\n                    problem = problem_list[-1]\n                    problem_paragraph_mapping.problem_id = problem.id\n                    return problem, False\n                else:\n                    problem = Problem(id=uuid.uuid7(), knowledge_id=target_knowledge_id, content=problem_content)\n                    target_problem_list.append(problem)\n                    problem_paragraph_mapping.problem_id = problem.id\n                    return problem, True\n            return None\n\n    class Query(serializers.Serializer):\n        # 知识库id\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        name = serializers.CharField(\n            required=False, max_length=128, min_length=1, allow_null=True, allow_blank=True, label=_('document name')\n        )\n        hit_handling_method = serializers.CharField(\n            required=False, label=_('hit handling method'), allow_null=True, allow_blank=True\n        )\n        is_active = serializers.BooleanField(required=False, label=_('document is active'), allow_null=True)\n        task_type = serializers.IntegerField(required=False, label=_('task type'))\n        status = serializers.CharField(required=False, label=_('status'), allow_null=True, allow_blank=True)\n        order_by = serializers.CharField(required=False, label=_('order by'), allow_null=True, allow_blank=True)\n        tag = serializers.CharField(required=False, label=_('tag'), allow_null=True, allow_blank=True)\n        tag_ids = serializers.ListField(child=serializers.UUIDField(), allow_null=True, required=False,\n                                        allow_empty=True)\n        no_tag = serializers.BooleanField(required=False, default=False, allow_null=True)\n        tag_exclude = serializers.BooleanField(required=False, default=False, allow_null=True)\n\n        def get_query_set(self):\n            query_set = QuerySet(model=Document)\n            query_set = query_set.filter(**{'knowledge_id': self.data.get(\"knowledge_id\")})\n\n            tag_ids = self.data.get('tag_ids')\n            no_tag = self.data.get('no_tag')\n            tag_exclude = self.data.get('tag_exclude')\n            if 'name' in self.data and self.data.get('name') is not None:\n                query_set = query_set.filter(**{'name__icontains': self.data.get('name')})\n            if 'hit_handling_method' in self.data and self.data.get('hit_handling_method') not in [None, '']:\n                query_set = query_set.filter(**{'hit_handling_method': self.data.get('hit_handling_method')})\n            if 'is_active' in self.data and self.data.get('is_active') is not None:\n                query_set = query_set.filter(**{'is_active': self.data.get('is_active')})\n            if no_tag and tag_ids:\n                matched_doc_ids = QuerySet(DocumentTag).filter(tag_id__in=tag_ids).values_list('document_id', flat=True)\n                tagged_doc_ids = QuerySet(DocumentTag).values_list('document_id', flat=True)\n                query_set = query_set.filter(\n                    Q(id__in=matched_doc_ids) | ~Q(id__in=tagged_doc_ids)\n                )\n            elif no_tag:\n                tagged_doc_ids = QuerySet(DocumentTag).values_list('document_id', flat=True)\n                query_set = query_set.exclude(id__in=tagged_doc_ids)\n            elif tag_ids:\n                matched_doc_ids = QuerySet(DocumentTag).filter(tag_id__in=tag_ids).values_list('document_id', flat=True)\n                if tag_exclude:\n                    query_set = query_set.exclude(id__in=matched_doc_ids)\n                else:\n                    query_set = query_set.filter(id__in=matched_doc_ids)\n\n            if 'status' in self.data and self.data.get('status') is not None:\n                task_type = self.data.get('task_type')\n                status = self.data.get('status')\n                if task_type is not None:\n                    query_set = query_set.annotate(\n                        reversed_status=Reverse('status'),\n                        task_type_status=Substr('reversed_status', TaskType(task_type).value, 1),\n                    ).filter(\n                        task_type_status=State(status).value\n                    ).values('id')\n                else:\n                    if status != State.SUCCESS.value:\n                        query_set = query_set.filter(status__icontains=status)\n                    else:\n                        query_set = query_set.filter(status__iregex='^[2n]*$')\n            if 'tag' in self.data and self.data.get('tag') not in [None, '']:\n                tag_name = self.data.get('tag')\n                document_id_list = QuerySet(DocumentTag).filter(\n                    Q(tag__key__icontains=tag_name) | Q(tag__value__icontains=tag_name)\n                ).values_list('document_id', flat=True)\n                query_set = query_set.filter(id__in=document_id_list)\n            order_by = self.data.get('order_by', '')\n            order_by_query_set = QuerySet(model=get_dynamics_model(\n                {'char_length': models.CharField(), 'paragraph_count': models.IntegerField(),\n                 \"update_time\": models.IntegerField(), 'create_time': models.DateTimeField()}))\n            if order_by:\n                order_by_query_set = order_by_query_set.order_by(order_by)\n            else:\n                order_by_query_set = order_by_query_set.order_by('-create_time', 'id')\n            return {\n                'document_custom_sql': query_set,\n                'order_by_query': order_by_query_set\n            }\n\n        def list(self):\n            self.is_valid(raise_exception=True)\n            query_set = self.get_query_set()\n            return native_search(query_set, select_string=get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_document.sql')))\n\n        def page(self, current_page, page_size):\n            self.is_valid(raise_exception=True)\n            query_set = self.get_query_set()\n            return native_page_search(current_page, page_size, query_set, select_string=get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_document.sql')))\n\n    class Sync(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=False, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            document_id = self.data.get('document_id')\n            first = QuerySet(Document).filter(id=document_id).first()\n            if first is None:\n                raise AppApiException(500, _('document id not exist'))\n            if first.type != KnowledgeType.WEB:\n                raise AppApiException(500, _('Synchronization is only supported for web site types'))\n\n        @transaction.atomic\n        def sync(self, with_valid=True, with_embedding=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            document_id = self.data.get('document_id')\n            document = QuerySet(Document).filter(id=document_id).first()\n            state = State.SUCCESS\n            if document.type != KnowledgeType.WEB:\n                return True\n            try:\n                ListenerManagement.update_status(QuerySet(Document).filter(id=document_id),\n                                                 TaskType.SYNC,\n                                                 State.PENDING)\n                ListenerManagement.get_aggregation_document_status(document_id)()\n                source_url = document.meta.get('source_url')\n                selector_list = document.meta.get('selector').split(\n                    \" \") if 'selector' in document.meta and document.meta.get('selector') is not None else []\n                result = Fork(source_url, selector_list).fork()\n                if result.status == 200:\n                    # 删除段落\n                    QuerySet(model=Paragraph).filter(document_id=document_id).delete()\n                    # 删除问题\n                    QuerySet(model=ProblemParagraphMapping).filter(document_id=document_id).delete()\n                    delete_problems_and_mappings([document_id])\n                    # 删除向量库\n                    delete_embedding_by_document(document_id)\n                    paragraphs = get_split_model('web.md').parse(result.content)\n                    char_length = reduce(lambda x, y: x + y,\n                                         [len(p.get('content')) for p in paragraphs],\n                                         0)\n                    QuerySet(Document).filter(id=document_id).update(char_length=char_length)\n                    document_paragraph_model = DocumentSerializers.Create.get_paragraph_model(document, paragraphs)\n\n                    paragraph_model_list = document_paragraph_model.get('paragraph_model_list')\n                    problem_paragraph_object_list = document_paragraph_model.get('problem_paragraph_object_list')\n                    problem_model_list, problem_paragraph_mapping_list = ProblemParagraphManage(\n                        problem_paragraph_object_list, document.knowledge_id).to_problem_model_list()\n                    # 批量插入段落\n                    if len(paragraph_model_list) > 0:\n                        max_position = Paragraph.objects.filter(document_id=document_id).aggregate(\n                            max_position=Max('position')\n                        )['max_position'] or 0\n                        for i, paragraph in enumerate(paragraph_model_list):\n                            paragraph.position = max_position + i + 1\n                        QuerySet(Paragraph).bulk_create(paragraph_model_list)\n                    # 批量插入问题\n                    QuerySet(Problem).bulk_create(problem_model_list) if len(problem_model_list) > 0 else None\n                    # 插入关联问题\n                    QuerySet(ProblemParagraphMapping).bulk_create(problem_paragraph_mapping_list) if len(\n                        problem_paragraph_mapping_list) > 0 else None\n                    # 向量化\n                    if with_embedding:\n                        embedding_model_id = get_embedding_model_id_by_knowledge_id(document.knowledge_id)\n                        ListenerManagement.update_status(QuerySet(Document).filter(id=document_id),\n                                                         TaskType.EMBEDDING,\n                                                         State.PENDING)\n                        ListenerManagement.update_status(QuerySet(Paragraph).filter(document_id=document_id),\n                                                         TaskType.EMBEDDING,\n                                                         State.PENDING)\n                        ListenerManagement.get_aggregation_document_status(document_id)()\n                        embedding_by_document.delay(document_id, embedding_model_id)\n\n                else:\n                    state = State.FAILURE\n            except Exception as e:\n                maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}')\n                state = State.FAILURE\n            ListenerManagement.update_status(\n                QuerySet(Document).filter(id=document_id),\n                TaskType.SYNC,\n                state\n            )\n            ListenerManagement.update_status(\n                QuerySet(Paragraph).filter(document_id=document_id),\n                TaskType.SYNC,\n                state\n            )\n            ListenerManagement.get_aggregation_document_status(document_id)()\n            return True\n\n    class Operate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'), allow_blank=True)\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id and workspace_id != 'None':\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            document_id = self.data.get('document_id')\n            if not QuerySet(Document).filter(id=document_id).exists():\n                raise AppApiException(500, _('document id not exist'))\n\n        def export(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            document = QuerySet(Document).filter(id=self.data.get(\"document_id\")).first()\n            paragraph_query_set = QuerySet(Paragraph).filter(\n                document_id=self.data.get(\"document_id\")\n            ).order_by('position')\n            paragraph_list = native_search(\n                paragraph_query_set,\n                get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_paragraph_document_name.sql'))\n            )\n            problem_mapping_list = native_search(\n                QuerySet(ProblemParagraphMapping).filter(document_id=self.data.get(\"document_id\")), get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_problem_mapping.sql')),\n                with_table_name=True)\n            data_dict, document_dict = self.merge_problem(paragraph_list, problem_mapping_list, [document])\n            workbook = self.get_workbook(data_dict, document_dict)\n            response = HttpResponse(content_type='application/vnd.ms-excel')\n            response['Content-Disposition'] = f'attachment; filename=\"data.xlsx\"'\n            workbook.save(response)\n            return response\n\n        def export_zip(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            document = QuerySet(Document).filter(id=self.data.get(\"document_id\")).first()\n            paragraph_query_set = QuerySet(Paragraph).filter(\n                document_id=self.data.get(\"document_id\")\n            ).order_by('position')\n            paragraph_list = native_search(\n                paragraph_query_set,\n                get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_paragraph_document_name.sql')\n                )\n            )\n            problem_mapping_list = native_search(\n                QuerySet(ProblemParagraphMapping).filter(document_id=self.data.get(\"document_id\")), get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_problem_mapping.sql')),\n                with_table_name=True)\n            data_dict, document_dict = self.merge_problem(paragraph_list, problem_mapping_list, [document])\n            res = [parse_image(paragraph.get('content')) for paragraph in paragraph_list]\n\n            workbook = DocumentSerializers.Operate.get_workbook(data_dict, document_dict)\n            response = HttpResponse(content_type='application/zip')\n            response['Content-Disposition'] = f'attachment; filename=\"{document.name.strip()}.zip\"'\n            zip_buffer = io.BytesIO()\n            with TemporaryDirectory() as tempdir:\n                knowledge_file = os.path.join(tempdir, 'document.xlsx')\n                workbook.save(knowledge_file)\n                for r in res:\n                    write_image(tempdir, r)\n                zip_dir(tempdir, zip_buffer)\n            response.write(zip_buffer.getvalue())\n            return response\n\n        def download_source_file(self):\n            self.is_valid(raise_exception=True)\n            file = QuerySet(File).filter(source_id=self.data.get('document_id')).first()\n            if not file:\n                raise AppApiException(500, _('File not exist. Only manually uploaded documents are supported'))\n            return FileSerializer.Operate(data={'id': file.id}).get(with_valid=True)\n\n        def one(self, with_valid=False):\n            self.is_valid(raise_exception=True)\n            query_set = QuerySet(model=Document)\n            query_set = query_set.filter(**{'id': self.data.get(\"document_id\")})\n            return native_search({\n                'document_custom_sql': query_set,\n                'order_by_query': QuerySet(Document).order_by('-create_time', 'id')\n            }, select_string=get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_document.sql')), with_search_one=True)\n\n        def edit(self, instance: Dict, with_valid=False):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            _document = QuerySet(Document).get(id=self.data.get(\"document_id\"))\n            if with_valid:\n                DocumentEditInstanceSerializer(data=instance).is_valid(document=_document)\n            update_keys = ['name', 'is_active', 'hit_handling_method', 'directly_return_similarity', 'meta']\n            for update_key in update_keys:\n                if update_key in instance and instance.get(update_key) is not None:\n                    _document.__setattr__(update_key, instance.get(update_key))\n            _document.save()\n            return self.one()\n\n        def cancel(self, instance, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                CancelInstanceSerializer(data=instance).is_valid()\n            document_id = self.data.get(\"document_id\")\n            ListenerManagement.update_status(\n                QuerySet(Paragraph).annotate(\n                    reversed_status=Reverse('status'),\n                    task_type_status=Substr('reversed_status', TaskType(instance.get('type')).value, 1),\n                ).filter(\n                    task_type_status__in=[State.PENDING.value, State.STARTED.value]\n                ).filter(\n                    document_id=document_id\n                ).values('id'),\n                TaskType(instance.get('type')),\n                State.REVOKE\n            )\n            ListenerManagement.update_status(\n                QuerySet(Document).annotate(\n                    reversed_status=Reverse('status'),\n                    task_type_status=Substr('reversed_status', TaskType(instance.get('type')).value,\n                                            1),\n                ).filter(\n                    task_type_status__in=[State.PENDING.value, State.STARTED.value]\n                ).filter(\n                    id=document_id\n                ).values('id'),\n                TaskType(instance.get('type')),\n                State.REVOKE\n            )\n            return True\n\n        @transaction.atomic\n        def delete(self):\n            self.is_valid(raise_exception=True)\n            document_id = self.data.get(\"document_id\")\n            source_file_ids = [\n                doc['meta'].get(\n                    'source_file_id'\n                ) for doc in Document.objects.filter(id=document_id).values(\"meta\")\n            ]\n            QuerySet(File).filter(id__in=source_file_ids).delete()\n            QuerySet(File).filter(source_id=document_id, source_type=FileSourceType.DOCUMENT).delete()\n            paragraph_ids = QuerySet(model=Paragraph).filter(document_id=document_id).values_list(\"id\", flat=True)\n            # 删除问题\n            delete_problems_and_mappings(paragraph_ids)\n            # 删除段落\n            QuerySet(model=Paragraph).filter(document_id=document_id).delete()\n            # 删除向量库\n            delete_embedding_by_document(document_id)\n            QuerySet(model=DocumentTag).filter(document_id=document_id).delete()\n            QuerySet(model=Document).filter(id=document_id).delete()\n            return True\n\n        def refresh(self, state_list=None, with_valid=True):\n            if state_list is None:\n                state_list = [State.PENDING.value, State.STARTED.value, State.SUCCESS.value, State.FAILURE.value,\n                              State.REVOKE.value,\n                              State.REVOKED.value, State.IGNORED.value]\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            knowledge = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')).first()\n            embedding_model_id = knowledge.embedding_model_id\n            knowledge_user_id = knowledge.user_id\n            embedding_model = QuerySet(Model).filter(id=embedding_model_id).first()\n            if embedding_model is None:\n                raise AppApiException(500, _('Model does not exist'))\n            document_id = self.data.get(\"document_id\")\n            ListenerManagement.update_status(\n                QuerySet(Document).filter(id=document_id), TaskType.EMBEDDING, State.PENDING\n            )\n            ListenerManagement.update_status(\n                QuerySet(Paragraph).annotate(\n                    reversed_status=Reverse('status'),\n                    task_type_status=Substr('reversed_status', TaskType.EMBEDDING.value, 1),\n                ).filter(task_type_status__in=state_list, document_id=document_id).values('id'),\n                TaskType.EMBEDDING,\n                State.PENDING\n            )\n            ListenerManagement.get_aggregation_document_status(document_id)()\n\n            try:\n                embedding_by_document.delay(document_id, embedding_model_id, state_list)\n            except AlreadyQueued as e:\n                raise AppApiException(500, _('The task is being executed, please do not send it repeatedly.'))\n\n        @staticmethod\n        def get_workbook(data_dict, document_dict):\n            # 创建工作簿对象\n            workbook = openpyxl.Workbook()\n            workbook.remove(workbook.active)\n            if len(data_dict.keys()) == 0:\n                data_dict['sheet'] = []\n            for sheet_id in data_dict:\n                # 添加工作表\n                worksheet = workbook.create_sheet(document_dict.get(sheet_id))\n                data = [\n                    [gettext('Section title (optional)'),\n                     gettext('Section content (required, question answer, no more than 4096 characters)'),\n                     gettext('Question (optional, one per line in the cell)')],\n                    *data_dict.get(sheet_id, [])\n                ]\n                # 写入数据到工作表\n                for row_idx, row in enumerate(data):\n                    for col_idx, col in enumerate(row):\n                        cell = worksheet.cell(row=row_idx + 1, column=col_idx + 1)\n                        if isinstance(col, str):\n                            col = re.sub(ILLEGAL_CHARACTERS_RE, '', col)\n                            if col.startswith(('=', '+', '-', '@')):\n                                col = '\\ufeff' + col\n                        cell.value = col\n                    # 创建HttpResponse对象返回Excel文件\n            return workbook\n\n        @staticmethod\n        def merge_problem(paragraph_list: List[Dict], problem_mapping_list: List[Dict], document_list):\n            result = {}\n            document_dict = {}\n\n            for paragraph in paragraph_list:\n                problem_list = [problem_mapping.get('content') for problem_mapping in problem_mapping_list if\n                                problem_mapping.get('paragraph_id') == paragraph.get('id')]\n                document_sheet = result.get(paragraph.get('document_id'))\n                document_name = DocumentSerializers.Operate.reset_document_name(paragraph.get('document_name'))\n                d = document_dict.get(document_name)\n                if d is None:\n                    document_dict[document_name] = {paragraph.get('document_id')}\n                else:\n                    d.add(paragraph.get('document_id'))\n\n                if document_sheet is None:\n                    result[paragraph.get('document_id')] = [[paragraph.get('title'), paragraph.get('content'),\n                                                             '\\n'.join(problem_list)]]\n                else:\n                    document_sheet.append([paragraph.get('title'), paragraph.get('content'), '\\n'.join(problem_list)])\n            for document in document_list:\n                if document.id not in result:\n                    document_name = DocumentSerializers.Operate.reset_document_name(document.name)\n                    result[document.id] = [[]]\n                    d = document_dict.get(document_name)\n                    if d is None:\n                        document_dict[document_name] = {document.id}\n                    else:\n                        d.add(document.id)\n            result_document_dict = {}\n            for d_name in document_dict:\n                for index, d_id in enumerate(document_dict.get(d_name)):\n                    result_document_dict[d_id] = d_name if index == 0 else d_name + str(index)\n            return result, result_document_dict\n\n        @staticmethod\n        def reset_document_name(document_name):\n            if document_name is not None:\n                document_name = document_name.strip()[0:29]\n            if document_name is None or not Utils.valid_sheet_name(document_name):\n                return \"Sheet\"\n            return document_name.strip()\n\n    class Create(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'), allow_null=True)\n        knowledge_id = serializers.UUIDField(required=True, label=_('document id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            if not QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')).exists():\n                raise AppApiException(10000, _('knowledge id not exist'))\n            return True\n\n        @staticmethod\n        def post_embedding(result, document_id, knowledge_id):\n            DocumentSerializers.Operate(\n                data={'knowledge_id': knowledge_id, 'document_id': document_id}).refresh()\n            return result\n\n        @post(post_function=post_embedding)\n        @transaction.atomic\n        def save(self, instance: Dict, with_valid=True, **kwargs):\n            if with_valid:\n                DocumentInstanceSerializer(data=instance).is_valid(raise_exception=True)\n                self.is_valid(raise_exception=True)\n            knowledge_id = self.data.get('knowledge_id')\n            document_paragraph_model = self.get_document_paragraph_model(knowledge_id, instance)\n\n            document_model = document_paragraph_model.get('document')\n            paragraph_model_list = document_paragraph_model.get('paragraph_model_list')\n            problem_paragraph_object_list = document_paragraph_model.get('problem_paragraph_object_list')\n            problem_model_list, problem_paragraph_mapping_list = (\n                ProblemParagraphManage(problem_paragraph_object_list, knowledge_id).to_problem_model_list())\n            # 插入文档\n            document_model.save()\n            # 批量插入段落\n            if len(paragraph_model_list) > 0:\n                max_position = Paragraph.objects.filter(document_id=document_model.id).aggregate(\n                    max_position=Max('position')\n                )['max_position'] or 0\n                for i, paragraph in enumerate(paragraph_model_list):\n                    paragraph.position = max_position + i + 1\n                QuerySet(Paragraph).bulk_create(paragraph_model_list)\n            # 批量插入问题\n            QuerySet(Problem).bulk_create(problem_model_list) if len(problem_model_list) > 0 else None\n            # 批量插入关联问题\n            QuerySet(ProblemParagraphMapping).bulk_create(\n                problem_paragraph_mapping_list\n            ) if len(problem_paragraph_mapping_list) > 0 else None\n            document_id = str(document_model.id)\n            return (DocumentSerializers.Operate(\n                data={'knowledge_id': knowledge_id, 'document_id': document_id}\n            ).one(with_valid=True), document_id, knowledge_id)\n\n        @staticmethod\n        def get_paragraph_model(document_model, paragraph_list: List):\n            knowledge_id = document_model.knowledge_id\n            paragraph_model_dict_list = [\n                ParagraphSerializers.Create(\n                    data={\n                        'knowledge_id': knowledge_id, 'document_id': str(document_model.id)\n                    }).get_paragraph_problem_model(knowledge_id, document_model.id, paragraph)\n                for paragraph in paragraph_list]\n\n            paragraph_model_list = []\n            problem_paragraph_object_list = []\n            for paragraphs in paragraph_model_dict_list:\n                paragraph = paragraphs.get('paragraph')\n                for problem_model in paragraphs.get('problem_paragraph_object_list'):\n                    problem_paragraph_object_list.append(problem_model)\n                paragraph_model_list.append(paragraph)\n\n            return {\n                'document': document_model,\n                'paragraph_model_list': paragraph_model_list,\n                'problem_paragraph_object_list': problem_paragraph_object_list\n            }\n\n        @staticmethod\n        def get_document_paragraph_model(knowledge_id, instance: Dict):\n            source_meta = {'source_file_id': instance.get('source_file_id')} if instance.get('source_file_id') else {}\n            meta = {**instance.get('meta'), **source_meta} if instance.get('meta') is not None else source_meta\n            meta = {**convert_uuid_to_str(meta), 'allow_download': True}\n\n            document_model = Document(\n                **{\n                    'knowledge_id': knowledge_id,\n                    'id': uuid.uuid7(),\n                    'name': instance.get('name'),\n                    'char_length': reduce(\n                        lambda x, y: x + y,\n                        [len(p.get('content')) for p in instance.get('paragraphs', [])],\n                        0),\n                    'meta': meta,\n                    'type': instance.get('type') if instance.get('type') is not None else KnowledgeType.BASE\n                })\n\n            return DocumentSerializers.Create.get_paragraph_model(\n                document_model,\n                instance.get('paragraphs') if 'paragraphs' in instance else []\n            )\n\n        def save_web(self, instance: Dict, with_valid=True):\n            if with_valid:\n                DocumentWebInstanceSerializer(data=instance).is_valid(raise_exception=True)\n                self.is_valid(raise_exception=True)\n            knowledge_id = self.data.get('knowledge_id')\n            source_url_list = instance.get('source_url_list')\n            selector = instance.get('selector')\n            sync_web_document.delay(knowledge_id, source_url_list, selector)\n\n        def save_qa(self, instance: Dict, with_valid=True):\n            if with_valid:\n                DocumentInstanceQASerializer(data=instance).is_valid(raise_exception=True)\n                self.is_valid(raise_exception=True)\n            file_list = instance.get('file_list')\n            document_list = flat_map([self.parse_qa_file(file) for file in file_list])\n            return DocumentSerializers.Batch(data={\n                'knowledge_id': self.data.get('knowledge_id'), 'workspace_id': self.data.get('workspace_id')\n            }).batch_save(document_list)\n\n        def save_table(self, instance: Dict, with_valid=True):\n            if with_valid:\n                DocumentInstanceTableSerializer(data=instance).is_valid(raise_exception=True)\n                self.is_valid(raise_exception=True)\n            file_list = instance.get('file_list')\n            document_list = flat_map([self.parse_table_file(file) for file in file_list])\n            return DocumentSerializers.Batch(data={\n                'knowledge_id': self.data.get('knowledge_id'), 'workspace_id': self.data.get('workspace_id')\n            }).batch_save(document_list)\n\n        def parse_qa_file(self, file):\n            #  保存源文件\n            source_file_id = uuid.uuid7()\n            source_file = File(\n                id=source_file_id,\n                file_name=file.name,\n                source_type=FileSourceType.KNOWLEDGE,\n                source_id=self.data.get('knowledge_id'),\n                meta={}\n            )\n            source_file.save(file.read())\n            file.seek(0)\n\n            get_buffer = FileBufferHandle().get_buffer\n            for parse_qa_handle in parse_qa_handle_list:\n                if parse_qa_handle.support(file, get_buffer):\n                    documents = parse_qa_handle.handle(file, get_buffer, self.save_image)\n                    for doc in documents:\n                        doc['source_file_id'] = source_file_id\n                    return documents\n            raise AppApiException(500, _('Unsupported file format'))\n\n        def parse_table_file(self, file):\n            #  保存源文件\n            source_file_id = uuid.uuid7()\n            source_file = File(\n                id=source_file_id,\n                file_name=file.name,\n                source_type=FileSourceType.KNOWLEDGE,\n                source_id=self.data.get('knowledge_id'),\n                meta={}\n            )\n            source_file.save(file.read())\n            file.seek(0)\n\n            get_buffer = FileBufferHandle().get_buffer\n            for parse_table_handle in parse_table_handle_list:\n                if parse_table_handle.support(file, get_buffer):\n                    documents = parse_table_handle.handle(file, get_buffer, self.save_image)\n                    for doc in documents:\n                        doc['source_file_id'] = source_file_id\n                    return documents\n            raise AppApiException(500, _('Unsupported file format'))\n\n        def save_image(self, image_list):\n            if image_list is not None and len(image_list) > 0:\n                exist_image_list = [str(i.get('id')) for i in\n                                    QuerySet(File).filter(id__in=[i.id for i in image_list]).values('id')]\n                save_image_list = [image for image in image_list if not exist_image_list.__contains__(str(image.id))]\n                save_image_list = list({img.id: img for img in save_image_list}.values())\n                # save image\n                for file in save_image_list:\n                    file_bytes = file.meta.pop('content')\n                    file.meta['knowledge_id'] = self.data.get('knowledge_id')\n                    file.source_type = FileSourceType.KNOWLEDGE\n                    file.source_id = self.data.get('knowledge_id')\n                    file.save(file_bytes)\n\n    class Split(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'), allow_null=True)\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        def is_valid(self, *, instance=None, raise_exception=True):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            files = instance.get('file')\n            knowledge = Knowledge.objects.filter(id=self.data.get('knowledge_id')).first()\n            for f in files:\n                if f.size > 1024 * 1024 * knowledge.file_size_limit:\n                    raise AppApiException(500, _(\n                        'The maximum size of the uploaded file cannot exceed {}MB'\n                    ).format(knowledge.file_size_limit))\n\n        def parse(self, instance):\n            self.is_valid(instance=instance, raise_exception=True)\n            DocumentSplitRequest(data=instance).is_valid(raise_exception=True)\n\n            file_list = instance.get(\"file\")\n            return reduce(\n                lambda x, y: [*x, *y],\n                [self.file_to_paragraph(\n                    f,\n                    instance.get(\"patterns\", None),\n                    instance.get(\"with_filter\", None),\n                    instance.get(\"limit\", 4096)\n                ) for f in file_list],\n                []\n            )\n\n        def save_image(self, image_list):\n            if image_list is not None and len(image_list) > 0:\n                exist_image_list = [str(i.get('id')) for i in\n                                    QuerySet(File).filter(id__in=[i.id for i in image_list]).values('id')]\n                save_image_list = [image for image in image_list if not exist_image_list.__contains__(str(image.id))]\n                save_image_list = list({img.id: img for img in save_image_list}.values())\n                # save image\n                for file in save_image_list:\n                    file_bytes = file.meta.pop('content')\n                    file.meta['knowledge_id'] = self.data.get('knowledge_id')\n                    file.source_type = FileSourceType.KNOWLEDGE\n                    file.source_id = self.data.get('knowledge_id')\n                    file.save(file_bytes)\n\n        def file_to_paragraph(self, file, pattern_list: List, with_filter: bool, limit: int):\n            # 保存源文件\n            file_id = uuid.uuid7()\n            raw_file = File(\n                id=file_id,\n                file_name=file.name,\n                file_size=file.size,\n                source_type=FileSourceType.KNOWLEDGE,\n                source_id=self.data.get('knowledge_id'),\n            )\n            raw_file.save(file.read())\n            file.seek(0)\n\n            get_buffer = FileBufferHandle().get_buffer\n            for split_handle in split_handles:\n                if split_handle.support(file, get_buffer):\n                    result = split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, self.save_image)\n                    if isinstance(result, list):\n                        for item in result:\n                            item['source_file_id'] = file_id\n                        return result\n                    result['source_file_id'] = file_id\n                    return [result]\n            result = default_split_handle.handle(file, pattern_list, with_filter, limit, get_buffer, self.save_image)\n            if isinstance(result, list):\n                for item in result:\n                    item['source_file_id'] = file_id\n                return result\n            result['source_file_id'] = file_id\n            return [result]\n\n    class SplitPattern(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'), allow_null=True)\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        @staticmethod\n        def list():\n            return [\n                {'key': \"#\", 'value': '(?<=^)# .*|(?<=\\\\n)# .*'},\n                {'key': '##', 'value': '(?<=\\\\n)(?<!#)## (?!#).*|(?<=^)(?<!#)## (?!#).*'},\n                {'key': '###', 'value': \"(?<=\\\\n)(?<!#)### (?!#).*|(?<=^)(?<!#)### (?!#).*\"},\n                {'key': '####', 'value': \"(?<=\\\\n)(?<!#)#### (?!#).*|(?<=^)(?<!#)#### (?!#).*\"},\n                {'key': '#####', 'value': \"(?<=\\\\n)(?<!#)##### (?!#).*|(?<=^)(?<!#)##### (?!#).*\"},\n                {'key': '######', 'value': \"(?<=\\\\n)(?<!#)###### (?!#).*|(?<=^)(?<!#)###### (?!#).*\"},\n                {'key': '-', 'value': '(?<! )- .*'},\n                {'key': _('space'), 'value': '(?<! ) (?! )'},\n                {'key': _('semicolon'), 'value': '(?<!；)；(?!；)'}, {'key': _('comma'), 'value': '(?<!，)，(?!，)'},\n                {'key': _('period'), 'value': '(?<!。)。(?!。)'}, {'key': _('enter'), 'value': '(?<!\\\\n)\\\\n(?!\\\\n)'},\n                {'key': _('blank line'), 'value': '(?<!\\\\n)\\\\n\\\\n(?!\\\\n)'}\n            ]\n\n    class Batch(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        @staticmethod\n        def link_file(source_file_id, document_id):\n            if source_file_id is None:\n                return\n            source_file = QuerySet(File).filter(id=source_file_id).first()\n            if source_file:\n                # 获取原始文件内容\n                file_content = source_file.get_bytes()\n\n                # 创建新文件对象，复制原始文件的重要属性\n                new_file = File(\n                    id=uuid.uuid7(),\n                    file_name=source_file.file_name,\n                    file_size=source_file.file_size,\n                    source_type=FileSourceType.DOCUMENT,\n                    source_id=document_id,  # 更新为当前知识库ID\n                    meta=source_file.meta.copy() if source_file.meta else {}\n                )\n\n                # 保存文件内容和元数据\n                new_file.save(file_content)\n\n        @staticmethod\n        def post_embedding(document_list, knowledge_id, workspace_id):\n            for document_dict in document_list:\n                DocumentSerializers.Operate(data={\n                    'knowledge_id': knowledge_id,\n                    'document_id': document_dict.get('id'),\n                    'workspace_id': workspace_id\n                }).refresh()\n            return document_list\n\n        @post(post_function=post_embedding)\n        @transaction.atomic\n        def batch_save(self, instance_list: List[Dict], with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            DocumentInstanceSerializer(many=True, data=instance_list).is_valid(raise_exception=True)\n            workspace_id = self.data.get(\"workspace_id\")\n            knowledge_id = self.data.get(\"knowledge_id\")\n            document_model_list = []\n            paragraph_model_list = []\n            problem_paragraph_object_list = []\n            # 插入文档\n            for document in instance_list:\n                document_paragraph_dict_model = DocumentSerializers.Create.get_document_paragraph_model(\n                    knowledge_id,\n                    document\n                )\n                # 保存文档和文件的关系\n                document_instance = document_paragraph_dict_model.get('document')\n                self.link_file(document.get('source_file_id'), document_instance.id)\n                document_model_list.append(document_instance)\n                for paragraph in document_paragraph_dict_model.get('paragraph_model_list'):\n                    paragraph_model_list.append(paragraph)\n                for problem_paragraph_object in document_paragraph_dict_model.get('problem_paragraph_object_list'):\n                    problem_paragraph_object_list.append(problem_paragraph_object)\n\n            problem_model_list, problem_paragraph_mapping_list = (\n                ProblemParagraphManage(problem_paragraph_object_list, knowledge_id).to_problem_model_list()\n            )\n            # 插入文档\n            QuerySet(Document).bulk_create(document_model_list) if len(document_model_list) > 0 else None\n            # 批量插入段落\n            if len(paragraph_model_list) > 0:\n                for document in document_model_list:\n                    max_position = Paragraph.objects.filter(document_id=document.id).aggregate(\n                        max_position=Max('position')\n                    )['max_position'] or 0\n                    sub_list = [p for p in paragraph_model_list if p.document_id == document.id]\n                    for i, paragraph in enumerate(sub_list):\n                        paragraph.position = max_position + i + 1\n                    QuerySet(Paragraph).bulk_create(sub_list if len(sub_list) > 0 else [])\n            # 批量插入问题\n            bulk_create_in_batches(Problem, problem_model_list, batch_size=1000)\n            # 批量插入关联问题\n            bulk_create_in_batches(ProblemParagraphMapping, problem_paragraph_mapping_list, batch_size=1000)\n            # 查询文档\n            query_set = QuerySet(model=Document)\n            if len(document_model_list) == 0:\n                return [], knowledge_id, workspace_id\n            query_set = query_set.filter(**{'id__in': [d.id for d in document_model_list]})\n            return native_search(\n                {\n                    'document_custom_sql': query_set,\n                    'order_by_query': QuerySet(Document).order_by('-create_time', 'id')\n                },\n                select_string=get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_document.sql')\n                ),\n                with_search_one=False\n            ), knowledge_id, workspace_id\n\n        def batch_sync(self, instance: Dict, with_valid=True):\n            if with_valid:\n                BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True)\n                self.is_valid(raise_exception=True)\n            # 异步同步\n            work_thread_pool.submit(\n                lambda doc_ids: [\n                    DocumentSerializers.Sync(data={\n                        'document_id': doc_id,\n                        'knowledge_id': self.data.get('knowledge_id'),\n                        'workspace_id': self.data.get('workspace_id')\n                    }).sync() for doc_id in doc_ids\n                ],\n                instance.get('id_list')\n            )\n            return True\n\n        @transaction.atomic\n        def batch_delete(self, instance: Dict, with_valid=True):\n            if with_valid:\n                BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True)\n                self.is_valid(raise_exception=True)\n            document_id_list = instance.get(\"id_list\")\n            source_file_ids = [doc['meta'].get('source_file_id') for doc in\n                               Document.objects.filter(id__in=document_id_list).values(\"meta\")]\n            QuerySet(File).filter(id__in=source_file_ids).delete()\n            QuerySet(Document).filter(id__in=document_id_list).delete()\n            QuerySet(DocumentTag).filter(document_id__in=document_id_list).delete()\n            paragraph_ids = QuerySet(Paragraph).filter(document_id__in=document_id_list).values_list(\"id\", flat=True)\n            # 删除问题关系\n            delete_problems_and_mappings(paragraph_ids)\n            # 删除段落\n            QuerySet(Paragraph).filter(document_id__in=document_id_list).delete()\n            # 删除向量库\n            delete_embedding_by_document_list(document_id_list)\n            return True\n\n        def batch_cancel(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                BatchCancelInstanceSerializer(data=instance).is_valid(raise_exception=True)\n            document_id_list = instance.get(\"id_list\")\n            ListenerManagement.update_status(\n                QuerySet(Paragraph).annotate(\n                    reversed_status=Reverse('status'),\n                    task_type_status=Substr('reversed_status', TaskType(instance.get('type')).value, 1),\n                ).filter(\n                    task_type_status__in=[State.PENDING.value, State.STARTED.value]\n                ).filter(\n                    document_id__in=document_id_list\n                ).values('id'),\n                TaskType(instance.get('type')),\n                State.REVOKE\n            )\n            ListenerManagement.update_status(\n                QuerySet(Document).annotate(\n                    reversed_status=Reverse('status'),\n                    task_type_status=Substr('reversed_status', TaskType(instance.get('type')).value, 1),\n                ).filter(\n                    task_type_status__in=[State.PENDING.value, State.STARTED.value]\n                ).filter(\n                    id__in=document_id_list\n                ).values('id'),\n                TaskType(instance.get('type')),\n                State.REVOKE\n            )\n\n        def batch_edit_hit_handling(self, instance: Dict, with_valid=True):\n            if with_valid:\n                BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True)\n                hit_handling_method = instance.get('hit_handling_method')\n                if hit_handling_method is None:\n                    raise AppApiException(500, _('Hit handling method is required'))\n                if hit_handling_method != 'optimization' and hit_handling_method != 'directly_return':\n                    raise AppApiException(500, _('The hit processing method must be directly_return|optimization'))\n                self.is_valid(raise_exception=True)\n            document_id_list = instance.get(\"id_list\")\n            hit_handling_method = instance.get('hit_handling_method')\n            directly_return_similarity = instance.get('directly_return_similarity')\n            update_dict = {'hit_handling_method': hit_handling_method}\n            if directly_return_similarity is not None:\n                update_dict['directly_return_similarity'] = directly_return_similarity\n            QuerySet(Document).filter(id__in=document_id_list).update(**update_dict)\n            allow_download = instance.get('allow_download')\n            if allow_download is not None:\n                # 我需要修改meta meta是存在Document的字段 是一个json字段 但是allow_download可能不存在\n                Document.objects.filter(id__in=document_id_list).update(\n                    meta=Func(\n                        F(\"meta\"),\n                        Value([\"allow_download\"]),\n                        Value(json.dumps(allow_download)),  # 转成 \"true\"/\"false\"\n                        Value(True),  # create_missing = true\n                        function=\"jsonb_set\",\n                        output_field=JSONField(),\n                    )\n                )\n\n        def batch_refresh(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            document_id_list = instance.get(\"id_list\")\n            state_list = instance.get(\"state_list\")\n            knowledge_id = self.data.get('knowledge_id')\n            for document_id in document_id_list:\n                try:\n                    DocumentSerializers.Operate(\n                        data={'knowledge_id': knowledge_id, 'document_id': document_id}).refresh(state_list)\n                except AlreadyQueued as e:\n                    pass\n\n        def batch_add_tag(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            document_id_list = instance.get(\"document_ids\")\n            tag_id_list = instance.get(\"tag_ids\")\n            # 批量查询已存在的标签关联关系\n            existing_relations = {\n                (str(doc_id), str(tag_id))\n                for doc_id, tag_id in QuerySet(DocumentTag).filter(\n                    document_id__in=document_id_list,\n                    tag_id__in=tag_id_list\n                ).values_list('document_id', 'tag_id')\n            }\n\n            new_relations = []\n            for doc_id in document_id_list:\n                for tag_id in tag_id_list:\n                    relation_key = (str(doc_id), str(tag_id))\n\n                    # 既检查数据库中已存在的，也检查本次即将创建的\n                    if relation_key not in existing_relations:\n                        new_relations.append(DocumentTag(\n                            id=uuid.uuid7(),\n                            document_id=doc_id,\n                            tag_id=tag_id,\n                        ))\n                        existing_relations.add(relation_key)\n\n            if new_relations:\n                QuerySet(DocumentTag).bulk_create(new_relations)\n\n        def batch_export(self, instance: Dict, with_valid=True):\n            if with_valid:\n                BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True)\n                self.is_valid(raise_exception=True)\n            document_ids = instance.get(\"id_list\")\n            document_list = QuerySet(Document).filter(id__in=document_ids)\n            paragraph_list = native_search(\n                QuerySet(Paragraph).filter(document_id__in=document_ids),\n                get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_paragraph_document_name.sql')\n                )\n            )\n            problem_mapping_list = native_search(\n                QuerySet(ProblemParagraphMapping).filter(document_id__in=document_ids),\n                get_file_content(os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_problem_mapping.sql')),\n                with_table_name=True\n            )\n            data_dict, document_dict = DocumentSerializers.Operate.merge_problem(\n                paragraph_list, problem_mapping_list, document_list\n            )\n            workbook = DocumentSerializers.Operate.get_workbook(data_dict, document_dict)\n            response = HttpResponse(content_type='application/vnd.ms-excel')\n            workbook.save(response)\n            return response\n\n        def batch_export_zip(self, instance: Dict, with_valid=True):\n            if with_valid:\n                BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True)\n                self.is_valid(raise_exception=True)\n            knowledge = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')).first()\n            document_ids = instance.get(\"id_list\")\n            document_list = QuerySet(Document).filter(id__in=document_ids)\n            paragraph_list = native_search(\n                QuerySet(Paragraph).filter(document_id__in=document_ids),\n                get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_paragraph_document_name.sql')\n                )\n            )\n            problem_mapping_list = native_search(\n                QuerySet(ProblemParagraphMapping).filter(document_id__in=document_ids),\n                get_file_content(os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_problem_mapping.sql')),\n                with_table_name=True\n            )\n            data_dict, document_dict = DocumentSerializers.Operate.merge_problem(\n                paragraph_list, problem_mapping_list, document_list\n            )\n            res = [parse_image(paragraph.get('content')) for paragraph in paragraph_list]\n\n            workbook = DocumentSerializers.Operate.get_workbook(data_dict, document_dict)\n            response = HttpResponse(content_type='application/zip')\n            zip_buffer = io.BytesIO()\n            with TemporaryDirectory() as tempdir:\n                knowledge_file = os.path.join(tempdir, f'{knowledge.name}.xlsx')\n                workbook.save(knowledge_file)\n                for r in res:\n                    write_image(tempdir, r)\n                zip_dir(tempdir, zip_buffer)\n            response.write(zip_buffer.getvalue())\n            return response\n\n    class BatchGenerateRelated(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        def batch_generate_related(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            document_id_list = instance.get(\"document_id_list\")\n            model_id = instance.get(\"model_id\")\n            prompt = instance.get(\"prompt\")\n            model_params_setting = instance.get(\"model_params_setting\")\n            state_list = instance.get('state_list')\n            ListenerManagement.update_status(\n                QuerySet(Document).filter(id__in=document_id_list),\n                TaskType.GENERATE_PROBLEM,\n                State.PENDING\n            )\n            ListenerManagement.update_status(\n                QuerySet(Paragraph).annotate(\n                    reversed_status=Reverse('status'),\n                    task_type_status=Substr('reversed_status', TaskType.GENERATE_PROBLEM.value,\n                                            1),\n                ).filter(\n                    task_type_status__in=state_list, document_id__in=document_id_list\n                )\n                .values('id'),\n                TaskType.GENERATE_PROBLEM,\n                State.PENDING\n            )\n            ListenerManagement.get_aggregation_document_status_by_query_set(\n                QuerySet(Document).filter(id__in=document_id_list))()\n            try:\n                for document_id in document_id_list:\n                    generate_related_by_document_id.delay(\n                        document_id, model_id, model_params_setting, prompt, state_list\n                    )\n            except AlreadyQueued as e:\n                pass\n\n    class Tags(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n        name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('search value'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id and workspace_id != 'None':\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            if not QuerySet(Document).filter(\n                    id=self.data.get('document_id'),\n                    knowledge_id=self.data.get('knowledge_id')\n            ).exists():\n                raise AppApiException(500, _('Document id does not exist'))\n\n        def list(self):\n            self.is_valid(raise_exception=True)\n\n            tag_ids = QuerySet(DocumentTag).filter(\n                document_id=self.data.get('document_id')\n            ).values_list('tag_id', flat=True)\n\n            if self.data.get('name'):\n                tag_ids = QuerySet(Tag).filter(\n                    knowledge_id=self.data.get('knowledge_id'),\n                    id__in=tag_ids,\n                ).filter(\n                    Q(key__icontains=self.data.get('name')) | Q(value__icontains=self.data.get('name'))\n                ).values_list('id', flat=True)\n\n            # 获取所有标签，按创建时间排序保持稳定顺序\n            tags = QuerySet(Tag).filter(\n                knowledge_id=self.data.get('knowledge_id'),\n                id__in=tag_ids\n            ).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value')\n\n            # 按key分组\n            grouped_tags = defaultdict(list)\n            for tag in tags:\n                grouped_tags[tag['key']].append({\n                    'id': tag['id'],\n                    'value': tag['value'],\n                    'create_time': tag['create_time'],\n                    'update_time': tag['update_time']\n                })\n\n            # 转换为期望的格式，保持key的顺序\n            result = []\n            # 按key排序以确保结果顺序一致\n            for key in sorted(grouped_tags.keys()):\n                values = grouped_tags[key]\n                # 按创建时间对values进行排序\n                values.sort(key=lambda x: x['create_time'])\n                result.append({\n                    'key': key,\n                    'values': values,\n                })\n\n            return result\n\n    class AddTags(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n        tag_ids = serializers.ListField(\n            required=True, label=_('tag ids'), child=serializers.UUIDField(required=True, label=_('tag id'))\n        )\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id and workspace_id != 'None':\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            if not QuerySet(Document).filter(\n                    id=self.data.get('document_id'),\n                    knowledge_id=self.data.get('knowledge_id')\n            ).exists():\n                raise AppApiException(500, _('Document id does not exist'))\n\n        def add_tags(self):\n            self.is_valid(raise_exception=True)\n            document_id = self.data.get('document_id')\n            tag_ids = self.data.get('tag_ids')\n            existing_tag_ids = set(\n                str(tag_id) for tag_id in QuerySet(DocumentTag).filter(\n                    document_id=document_id, tag_id__in=tag_ids\n                ).values_list('tag_id', flat=True)\n            )\n            new_tags = [\n                DocumentTag(\n                    id=uuid.uuid7(),\n                    document_id=document_id,\n                    tag_id=tag_id\n                ) for tag_id in set(tag_ids) if tag_id not in existing_tag_ids\n            ]\n            if new_tags:\n                QuerySet(DocumentTag).bulk_create(new_tags)\n\n    class DeleteTags(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n        tag_ids = serializers.ListField(\n            required=True, label=_('tag ids'), child=serializers.UUIDField(required=True, label=_('tag id'))\n        )\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id and workspace_id != 'None':\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            if not QuerySet(Document).filter(\n                    id=self.data.get('document_id'),\n                    knowledge_id=self.data.get('knowledge_id')\n            ).exists():\n                raise AppApiException(500, _('Document id does not exist'))\n\n        def delete_tags(self):\n            self.is_valid(raise_exception=True)\n            document_id = self.data.get('document_id')\n            tag_ids = self.data.get('tag_ids')\n            QuerySet(DocumentTag).filter(\n                document_id=document_id,\n                tag_id__in=tag_ids\n            ).delete()\n\n    class DeleteDocsTag(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        tag_id = serializers.UUIDField(required=True, label=_('tag id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id and workspace_id != 'None':\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            if not QuerySet(Tag).filter(\n                    id=self.data.get('tag_id'),\n                    knowledge_id=self.data.get('knowledge_id')\n            ).exists():\n                raise AppApiException(500, _('Tag id does not exist'))\n\n        def batch_delete_docs_tag(self, instance, with_valid=True):\n            if with_valid:\n                BatchSerializer(data=instance).is_valid(model=Document, raise_exception=True)\n                self.is_valid(raise_exception=True)\n            knowledge_id = self.data.get('knowledge_id')\n            tag_id = self.data.get('tag_id')\n            doc_id_list = instance.get(\"id_list\")\n\n            valid_doc_count = Document.objects.filter(id__in=doc_id_list, knowledge_id=knowledge_id).count()\n            if valid_doc_count != len(doc_id_list):\n                raise AppApiException(500, _('Document id does not belong to current knowledge'))\n\n            DocumentTag.objects.filter(document_id__in=doc_id_list, tag_id=tag_id).delete()\n\n            return True\n\n    class ReplaceSourceFile(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n        file = UploadedFileField(required=True, label=_(\"file\"))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id and workspace_id != 'None':\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            if not QuerySet(Document).filter(\n                    id=self.data.get('document_id'),\n                    knowledge_id=self.data.get('knowledge_id')\n            ).exists():\n                raise AppApiException(500, _('Document id does not exist'))\n\n        @transaction.atomic\n        def replace(self):\n            self.is_valid(raise_exception=True)\n            file = self.data.get('file')\n            source_file = QuerySet(File).filter(source_id=self.data.get('document_id')).first()\n\n            if not source_file:\n                # 不存在手动关联一个文档\n                new_source_file_id = uuid.uuid7()\n                new_source_file = File(\n                    id=new_source_file_id,\n                    file_name=file.name,\n                    source_type=FileSourceType.DOCUMENT,\n                    source_id=self.data.get('document_id'),\n                )\n                new_source_file.save(file.read())\n                # 更新Document的meta字段\n                QuerySet(Document).filter(id=self.data.get('document_id')).update(\n                    meta=Func(\n                        F(\"meta\"),\n                        Value([\"source_file_id\"]),\n                        Value(json.dumps(str(new_source_file_id))),\n                        Value(True),  # create_missing = true\n                        function=\"jsonb_set\",\n                        output_field=JSONField(),\n                    )\n                )\n            else:\n                # 获取原文件的sha256_hash\n                original_hash = source_file.sha256_hash\n\n                # 读取新文件内容\n                file_content = file.read()\n\n                QuerySet(File).filter(\n                    sha256_hash=original_hash,\n                    source_id__in=[self.data.get('knowledge_id'), self.data.get('document_id')]\n                ).update(file_name=file.name)\n\n                # 查找所有具有相同sha256_hash的文件\n                files_to_update = QuerySet(File).filter(\n                    sha256_hash=original_hash,\n                    source_id__in=[self.data.get('knowledge_id'), self.data.get('document_id')]\n                )\n\n                # 更新所有相同hash的文件\n                for file_obj in files_to_update:\n                    file_obj.save(file_content)\n\n            return True\n\n\nclass FileBufferHandle:\n    buffer = None\n\n    def get_buffer(self, file):\n        if self.buffer is None:\n            self.buffer = file.read()\n        return self.buffer\n"
  },
  {
    "path": "apps/knowledge/serializers/knowledge.py",
    "content": "import io\nimport json\nimport os\nimport re\nimport tempfile\nimport traceback\nimport zipfile\nfrom collections import defaultdict\nfrom functools import reduce\nfrom tempfile import TemporaryDirectory\nfrom typing import Dict, List\n\nimport requests\nimport uuid_utils.compat as uuid\nfrom celery_once import AlreadyQueued\nfrom django.core import validators\nfrom django.db import transaction, models\nfrom django.db.models import QuerySet\nfrom django.db.models.functions import Reverse, Substr\nfrom django.db.models.query_utils import Q\nfrom django.http import HttpResponse\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom common.config.embedding_config import VectorStore\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.db.search import native_search, get_dynamics_model, native_page_search\nfrom common.db.sql_execute import select_list\nfrom common.event.listener_manage import ListenerManagement\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import post, get_file_content, parse_image\nfrom common.utils.fork import Fork, ChildLink\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.split_model import get_split_model\nfrom knowledge.models import Knowledge, KnowledgeScope, KnowledgeType, Document, Paragraph, Problem, \\\n    ProblemParagraphMapping, TaskType, State, SearchMode, KnowledgeFolder, File, Tag, KnowledgeWorkflow\nfrom knowledge.serializers.common import ProblemParagraphManage, drop_knowledge_index, \\\n    get_embedding_model_id_by_knowledge_id, MetaSerializer, \\\n    GenerateRelatedSerializer, get_embedding_model_by_knowledge_id, list_paragraph, write_image, zip_dir, \\\n    update_resource_mapping_by_knowledge\nfrom knowledge.serializers.document import DocumentSerializers\nfrom knowledge.task.embedding import embedding_by_knowledge, delete_embedding_by_knowledge\nfrom knowledge.task.generate import generate_related_by_knowledge_id\nfrom knowledge.task.sync import sync_web_knowledge, sync_replace_web_knowledge\nfrom maxkb.conf import PROJECT_DIR\nfrom models_provider.models import Model\nfrom system_manage.models import WorkspaceUserResourcePermission, AuthTargetType\nfrom system_manage.models.resource_mapping import ResourceMapping\nfrom system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer\nfrom system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer\nfrom users.serializers.user import is_workspace_manage\n\n\nclass KnowledgeModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Knowledge\n        fields = ['id', 'name', 'desc', 'meta', 'folder_id', 'type', 'workspace_id', 'create_time',\n                  'update_time', 'file_size_limit', 'file_count_limit', 'embedding_model_id']\n\n\nclass KnowledgeBaseCreateRequest(serializers.Serializer):\n    name = serializers.CharField(required=True, label=_('knowledge name'))\n    folder_id = serializers.CharField(required=True, label=_('folder id'))\n    desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('knowledge description'))\n    embedding_model_id = serializers.CharField(required=True, label=_('knowledge embedding'))\n\n\nclass KnowledgeWebCreateRequest(serializers.Serializer):\n    name = serializers.CharField(required=True, label=_('knowledge name'))\n    folder_id = serializers.CharField(required=True, label=_('folder id'))\n    desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('knowledge description'))\n    embedding_model_id = serializers.CharField(required=True, label=_('knowledge embedding'))\n    source_url = serializers.CharField(required=True, label=_('source url'))\n    selector = serializers.CharField(required=False, label=_('knowledge selector'), allow_null=True, allow_blank=True)\n\n\nclass KnowledgeEditRequest(serializers.Serializer):\n    name = serializers.CharField(required=False, max_length=64, min_length=1, label=_('knowledge name'))\n    desc = serializers.CharField(required=False, max_length=256, min_length=1, label=_('knowledge description'))\n    meta = serializers.DictField(required=False)\n    application_id_list = serializers.ListSerializer(\n        required=False,\n        child=serializers.UUIDField(required=True, label=_('application id')),\n        label=_('application id list')\n    )\n    file_size_limit = serializers.IntegerField(required=False, label=_('file size limit'))\n    file_count_limit = serializers.IntegerField(required=False, label=_('file count limit'))\n\n    @staticmethod\n    def get_knowledge_meta_valid_map():\n        knowledge_meta_valid_map = {\n            KnowledgeType.BASE: MetaSerializer.BaseMeta,\n            KnowledgeType.WEB: MetaSerializer.WebMeta\n        }\n        return knowledge_meta_valid_map\n\n    def is_valid(self, *, knowledge: Knowledge = None):\n        super().is_valid(raise_exception=True)\n        if 'meta' in self.data and self.data.get('meta') is not None:\n            knowledge_meta_valid_map = self.get_knowledge_meta_valid_map()\n            valid_class = knowledge_meta_valid_map.get(knowledge.type)\n            valid_class(data=self.data.get('meta')).is_valid(raise_exception=True)\n\n\nclass HitTestSerializer(serializers.Serializer):\n    query_text = serializers.CharField(required=True, label=_('query text'))\n    top_number = serializers.IntegerField(required=True, max_value=10000, min_value=1, label=_(\"top number\"))\n    similarity = serializers.FloatField(required=True, max_value=2, min_value=0, label=_('similarity'))\n    search_mode = serializers.CharField(required=True, label=_('search mode'), validators=[\n        validators.RegexValidator(regex=re.compile(\"^embedding|keywords|blend$\"),\n                                  message=_('The type only supports embedding|keywords|blend'), code=500)\n    ])\n\n\nclass KnowledgeSerializer(serializers.Serializer):\n    class Query(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True)\n        folder_id = serializers.CharField(required=False, label=_('folder id'), allow_null=True)\n        name = serializers.CharField(required=False, label=_('knowledge name'), allow_null=True, allow_blank=True,\n                                     max_length=64, min_length=1)\n        desc = serializers.CharField(required=False, label=_('knowledge description'), allow_null=True,\n                                     allow_blank=True, max_length=256, min_length=1)\n        user_id = serializers.UUIDField(required=False, label=_('user id'), allow_null=True)\n        scope = serializers.CharField(required=False, label=_('knowledge scope'), allow_null=True)\n        create_user = serializers.UUIDField(required=False, label=_('create user'), allow_null=True)\n\n        @staticmethod\n        def is_x_pack_ee():\n            workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n            role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n            return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None\n\n        def get_query_set(self, workspace_manage, is_x_pack_ee):\n            self.is_valid(raise_exception=True)\n            workspace_id = self.data.get(\"workspace_id\")\n            query_set_dict = {}\n            query_set = QuerySet(model=get_dynamics_model({\n                'temp.name': models.CharField(),\n                'temp.desc': models.CharField(),\n                \"document_temp.char_length\": models.IntegerField(),\n                'temp.create_time': models.DateTimeField(),\n                'temp.user_id': models.CharField(),\n                'temp.workspace_id': models.CharField(),\n                'temp.folder_id': models.CharField(),\n                'temp.id': models.CharField(),\n                'temp.scope': models.CharField(),\n            }))\n            folder_query_set = QuerySet(KnowledgeFolder)\n\n            if \"desc\" in self.data and self.data.get('desc') is not None:\n                query_set = query_set.filter(**{'temp.desc__icontains': self.data.get(\"desc\")})\n                folder_query_set = folder_query_set.filter(**{'desc__icontains': self.data.get(\"desc\")})\n            if \"name\" in self.data and self.data.get('name') is not None:\n                query_set = query_set.filter(**{'temp.name__icontains': self.data.get(\"name\")})\n                folder_query_set = folder_query_set.filter(**{'name__icontains': self.data.get(\"name\")})\n            if \"workspace_id\" in self.data and self.data.get('workspace_id') is not None:\n                query_set = query_set.filter(**{'temp.workspace_id': self.data.get(\"workspace_id\")})\n                folder_query_set = folder_query_set.filter(**{'workspace_id': self.data.get(\"workspace_id\")})\n            if \"folder_id\" in self.data and self.data.get('folder_id') is not None and self.data.get(\n                    'workspace_id') != self.data.get('folder_id'):\n                query_set = query_set.filter(**{'temp.folder_id': self.data.get(\"folder_id\")})\n                folder_query_set = folder_query_set.filter(**{'parent_id': self.data.get(\"folder_id\")})\n            if \"scope\" in self.data and self.data.get('scope') is not None:\n                query_set = query_set.filter(**{'temp.scope': self.data.get(\"scope\")})\n            if \"create_user\" in self.data and self.data.get('create_user') is not None:\n                query_set = query_set.filter(**{'temp.user_id': self.data.get(\"create_user\")})\n            query_set = query_set.order_by(\"-temp.create_time\", \"temp.id\")\n            query_set_dict['default_sql'] = query_set\n\n            query_set_dict['knowledge_custom_sql'] = QuerySet(model=get_dynamics_model({\n                'knowledge.workspace_id': models.CharField(),\n            })).filter(**{'knowledge.workspace_id': workspace_id})\n            # query_set_dict['folder_query_set'] = folder_query_set\n            if not workspace_manage:\n                query_set_dict['workspace_user_resource_permission_query_set'] = QuerySet(\n                    WorkspaceUserResourcePermission).filter(\n                    auth_target_type=\"KNOWLEDGE\",\n                    workspace_id=workspace_id,\n                    user_id=self.data.get(\"user_id\"))\n            return query_set_dict\n\n        def page(self, current_page: int, page_size: int):\n            self.is_valid(raise_exception=True)\n            folder_id = self.data.get('folder_id', self.data.get(\"workspace_id\"))\n            root = KnowledgeFolder.objects.filter(id=folder_id).first()\n            if not root:\n                raise serializers.ValidationError(_('Folder not found'))\n            workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id'))\n            is_x_pack_ee = self.is_x_pack_ee()\n            result = native_page_search(\n                current_page,\n                page_size,\n                self.get_query_set(workspace_manage, is_x_pack_ee),\n                select_string=get_file_content(\n                    os.path.join(\n                        PROJECT_DIR,\n                        \"apps\",\n                        \"knowledge\", 'sql',\n                        'list_knowledge.sql' if workspace_manage else (\n                            'list_knowledge_user_ee.sql' if is_x_pack_ee else 'list_knowledge_user.sql'\n                        )\n                    )\n                ),\n                post_records_handler=lambda r: r\n            )\n            return ResourceMappingSerializer().get_resource_count(result)\n\n        def list(self):\n            self.is_valid(raise_exception=True)\n            folder_id = self.data.get('folder_id')\n            if not folder_id:\n                folder_id = self.data.get('workspace_id')\n            root = KnowledgeFolder.objects.filter(id=folder_id).first()\n            if not root:\n                raise serializers.ValidationError(_('Folder not found'))\n            workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id'))\n            is_x_pack_ee = self.is_x_pack_ee()\n            return native_search(\n                self.get_query_set(workspace_manage, is_x_pack_ee),\n                select_string=get_file_content(\n                    os.path.join(\n                        PROJECT_DIR,\n                        \"apps\",\n                        \"knowledge\", 'sql',\n                        'list_knowledge.sql' if workspace_manage else (\n                            'list_knowledge_user_ee.sql' if self.is_x_pack_ee() else 'list_knowledge_user.sql'\n                        )\n                    )\n                ),\n            )\n\n    class Operate(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        @transaction.atomic\n        def embedding(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            knowledge_id = self.data.get('knowledge_id')\n            knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first()\n            embedding_model_id = knowledge.embedding_model_id\n            embedding_model = QuerySet(Model).filter(id=embedding_model_id).first()\n            if embedding_model is None:\n                raise AppApiException(500, _('Model does not exist'))\n            ListenerManagement.update_status(\n                QuerySet(Document).filter(knowledge_id=self.data.get('knowledge_id')),\n                TaskType.EMBEDDING,\n                State.PENDING\n            )\n            ListenerManagement.update_status(\n                QuerySet(Paragraph).filter(knowledge_id=self.data.get('knowledge_id')),\n                TaskType.EMBEDDING,\n                State.PENDING\n            )\n            ListenerManagement.get_aggregation_document_status_by_knowledge_id(self.data.get('knowledge_id'))()\n            embedding_model_id = get_embedding_model_id_by_knowledge_id(self.data.get('knowledge_id'))\n            try:\n                embedding_by_knowledge.delay(knowledge_id, embedding_model_id)\n            except AlreadyQueued as e:\n                raise AppApiException(500, _('Failed to send the vectorization task, please try again later!'))\n\n        def generate_related(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                GenerateRelatedSerializer(data=instance).is_valid(raise_exception=True)\n            knowledge_id = self.data.get('knowledge_id')\n            model_id = instance.get(\"model_id\")\n            prompt = instance.get(\"prompt\")\n            model_params_setting = instance.get(\"model_params_setting\")\n            state_list = instance.get('state_list')\n            ListenerManagement.update_status(\n                QuerySet(Document).filter(knowledge_id=knowledge_id),\n                TaskType.GENERATE_PROBLEM,\n                State.PENDING\n            )\n            ListenerManagement.update_status(\n                QuerySet(Paragraph).annotate(\n                    reversed_status=Reverse('status'),\n                    task_type_status=Substr('reversed_status', TaskType.GENERATE_PROBLEM.value, 1),\n                ).filter(\n                    task_type_status__in=state_list, knowledge_id=knowledge_id\n                ).values('id'),\n                TaskType.GENERATE_PROBLEM,\n                State.PENDING\n            )\n            ListenerManagement.get_aggregation_document_status_by_knowledge_id(knowledge_id)()\n            try:\n                generate_related_by_knowledge_id.delay(knowledge_id, model_id, model_params_setting, prompt, state_list)\n            except AlreadyQueued as e:\n                raise AppApiException(500, _('Failed to send the vectorization task, please try again later!'))\n\n        def list_application(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            # knowledge = QuerySet(Knowledge).get(id=self.data.get(\"knowledge_id\"))\n            return select_list(\n                get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_knowledge_application.sql')\n                ),\n                [\n                    self.data.get('user_id'),\n                ]\n            )\n\n        @staticmethod\n        def is_x_pack_ee():\n            workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n            role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n            return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None\n\n        def one(self):\n            self.is_valid()\n            workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id'))\n            is_x_pack_ee = self.is_x_pack_ee()\n\n            query_set_dict = {\n                'default_sql': QuerySet(\n                    model=get_dynamics_model({'temp.id': models.CharField()})\n                ).filter(**{'temp.id': self.data.get(\"knowledge_id\")}),\n                'knowledge_custom_sql': QuerySet(\n                    model=get_dynamics_model({'knowledge.id': models.CharField()})\n                ).filter(**{'knowledge.id': self.data.get(\"knowledge_id\")}),\n            }\n            if not workspace_manage:\n                query_set_dict['workspace_user_resource_permission_query_set'] = QuerySet(\n                    WorkspaceUserResourcePermission).filter(\n                    auth_target_type=\"KNOWLEDGE\",\n                    workspace_id=self.data.get('workspace_id'),\n                    user_id=self.data.get(\"user_id\")\n                )\n            all_application_list = [str(adm.get('id')) for adm in self.list_application(with_valid=False)]\n            knowledge_dict = native_search(query_set_dict, select_string=get_file_content(\n                os.path.join(\n                    PROJECT_DIR, \"apps\", \"knowledge\", 'sql',\n                    'list_knowledge.sql' if workspace_manage else (\n                        'list_knowledge_user_ee.sql' if is_x_pack_ee else 'list_knowledge_user.sql'\n                    )\n                )\n            ), with_search_one=True)\n            workflow = {}\n\n            if knowledge_dict.get('type') == 4:\n                from knowledge.models import KnowledgeWorkflow\n                k = QuerySet(KnowledgeWorkflow).filter(knowledge_id=knowledge_dict.get('id')).first()\n                if k:\n                    workflow['work_flow'] = k.work_flow\n                    workflow['is_publish'] = k.is_publish\n                    workflow['publish_time'] = k.publish_time\n            return {\n                **knowledge_dict,\n                **workflow,\n                'meta': json.loads(knowledge_dict.get('meta', '{}')),\n                'application_id_list': list(filter(\n                    lambda application_id: all_application_list.__contains__(application_id),\n                    [\n                        str(\n                            application_knowledge_mapping.source_id\n                        ) for application_knowledge_mapping in\n                        QuerySet(ResourceMapping).filter(source_type='APPLICATION',\n                                                         target_type='KNOWLEDGE',\n                                                         target_id=self.data.get('knowledge_id'))\n                    ]\n                ))\n            }\n\n        @transaction.atomic\n        def edit(self, instance: Dict, select_one=True):\n            self.is_valid()\n            knowledge = QuerySet(Knowledge).get(id=self.data.get(\"knowledge_id\"))\n            KnowledgeEditRequest(data=instance).is_valid(knowledge=knowledge)\n            if 'embedding_model_id' in instance:\n                knowledge.embedding_model_id = instance.get('embedding_model_id')\n            if \"name\" in instance:\n                knowledge.name = instance.get(\"name\")\n            if 'desc' in instance:\n                knowledge.desc = instance.get(\"desc\")\n            if 'meta' in instance:\n                knowledge.meta = instance.get('meta')\n            if 'folder_id' in instance:\n                knowledge.folder_id = instance.get('folder_id')\n            if 'file_size_limit' in instance:\n                knowledge.file_size_limit = instance.get('file_size_limit')\n            if 'file_count_limit' in instance:\n                knowledge.file_count_limit = instance.get('file_count_limit')\n            knowledge.save()\n            update_resource_mapping_by_knowledge(str(knowledge.id))\n            if select_one:\n                return self.one()\n            return None\n\n        @transaction.atomic\n        def delete(self):\n            self.is_valid()\n            knowledge = QuerySet(Knowledge).get(id=self.data.get(\"knowledge_id\"))\n            QuerySet(Document).filter(knowledge=knowledge).delete()\n            QuerySet(ProblemParagraphMapping).filter(knowledge=knowledge).delete()\n            QuerySet(Paragraph).filter(knowledge=knowledge).delete()\n            QuerySet(Problem).filter(knowledge=knowledge).delete()\n            QuerySet(WorkspaceUserResourcePermission).filter(target=knowledge.id).delete()\n            drop_knowledge_index(knowledge_id=knowledge.id)\n            knowledge.delete()\n            File.objects.filter(\n                source_id=knowledge.id,\n            ).delete()\n            QuerySet(ResourceMapping).filter(\n                Q(target_id=self.data.get('knowledge_id')) | Q(source_id=self.data.get('knowledge_id'))\n            ).delete()\n            delete_embedding_by_knowledge(self.data.get('knowledge_id'))\n            return True\n\n        def export_excel(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            document_list = QuerySet(Document).filter(knowledge_id=self.data.get('knowledge_id'))\n            paragraph_list = native_search(\n                QuerySet(Paragraph).filter(knowledge_id=self.data.get(\"knowledge_id\")),\n                get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_paragraph_document_name.sql')\n                )\n            )\n            problem_mapping_list = native_search(\n                QuerySet(ProblemParagraphMapping).filter(knowledge_id=self.data.get(\"knowledge_id\")),\n                get_file_content(os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_problem_mapping.sql')),\n                with_table_name=True\n            )\n            data_dict, document_dict = DocumentSerializers.Operate.merge_problem(\n                paragraph_list, problem_mapping_list, document_list\n            )\n            workbook = DocumentSerializers.Operate.get_workbook(data_dict, document_dict)\n            response = HttpResponse(content_type='application/vnd.ms-excel')\n            response['Content-Disposition'] = 'attachment; filename=\"knowledge.xlsx\"'\n            workbook.save(response)\n            return response\n\n        def export_zip(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            knowledge = QuerySet(Knowledge).filter(id=self.data.get(\"knowledge_id\")).first()\n            document_list = QuerySet(Document).filter(knowledge_id=self.data.get('knowledge_id'))\n            paragraph_list = native_search(\n                QuerySet(Paragraph).filter(knowledge_id=self.data.get(\"knowledge_id\")),\n                get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_paragraph_document_name.sql')\n                )\n            )\n            problem_mapping_list = native_search(\n                QuerySet(ProblemParagraphMapping).filter(knowledge_id=self.data.get(\"knowledge_id\")),\n                get_file_content(os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_problem_mapping.sql')),\n                with_table_name=True\n            )\n            data_dict, document_dict = DocumentSerializers.Operate.merge_problem(\n                paragraph_list, problem_mapping_list, document_list\n            )\n            res = [parse_image(paragraph.get('content')) for paragraph in paragraph_list]\n\n            workbook = DocumentSerializers.Operate.get_workbook(data_dict, document_dict)\n            response = HttpResponse(content_type='application/zip')\n            response['Content-Disposition'] = f'attachment; filename=\"{knowledge.name}.zip\"'\n            zip_buffer = io.BytesIO()\n            with TemporaryDirectory() as tempdir:\n                knowledge_file = os.path.join(tempdir, 'knowledge.xlsx')\n                workbook.save(knowledge_file)\n                for r in res:\n                    write_image(tempdir, r)\n                zip_dir(tempdir, zip_buffer)\n            response.write(zip_buffer.getvalue())\n            return response\n\n        @staticmethod\n        def merge_problem(paragraph_list: List[Dict], problem_mapping_list: List[Dict]):\n            result = {}\n            document_dict = {}\n\n            for paragraph in paragraph_list:\n                problem_list = [problem_mapping.get('content') for problem_mapping in problem_mapping_list if\n                                problem_mapping.get('paragraph_id') == paragraph.get('id')]\n                document_sheet = result.get(paragraph.get('document_id'))\n                d = document_dict.get(paragraph.get('document_name'))\n                if d is None:\n                    document_dict[paragraph.get('document_name')] = {paragraph.get('document_id')}\n                else:\n                    d.add(paragraph.get('document_id'))\n\n                if document_sheet is None:\n                    result[paragraph.get('document_id')] = [[paragraph.get('title'), paragraph.get('content'),\n                                                             '\\n'.join(problem_list)]]\n                else:\n                    document_sheet.append([paragraph.get('title'), paragraph.get('content'), '\\n'.join(problem_list)])\n            result_document_dict = {}\n            for d_name in document_dict:\n                for index, d_id in enumerate(document_dict.get(d_name)):\n                    result_document_dict[d_id] = d_name if index == 0 else d_name + str(index)\n            return result, result_document_dict\n\n    class Create(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        scope = serializers.ChoiceField(required=False, label=_('scope'), default=KnowledgeScope.WORKSPACE,\n                                        choices=KnowledgeScope.choices)\n\n        @staticmethod\n        def post_embedding_knowledge(document_list, knowledge_id):\n            model_id = get_embedding_model_id_by_knowledge_id(knowledge_id)\n            embedding_by_knowledge.delay(knowledge_id, model_id)\n            return document_list\n\n        @post(post_function=post_embedding_knowledge)\n        @transaction.atomic\n        def save_base(self, instance, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                KnowledgeBaseCreateRequest(data=instance).is_valid(raise_exception=True)\n            folder_id = instance.get('folder_id', self.data.get('workspace_id'))\n\n            knowledge_id = uuid.uuid7()\n            knowledge = Knowledge(\n                id=knowledge_id,\n                name=instance.get('name'),\n                workspace_id=self.data.get('workspace_id'),\n                desc=instance.get('desc'),\n                type=instance.get('type', KnowledgeType.BASE),\n                user_id=self.data.get('user_id'),\n                scope=self.data.get('scope', KnowledgeScope.WORKSPACE),\n                folder_id=folder_id,\n                embedding_model_id=instance.get('embedding_model_id'),\n                meta=instance.get('meta', {}),\n            )\n\n            document_model_list = []\n            paragraph_model_list = []\n            problem_paragraph_object_list = []\n            # 插入文档\n            for document in instance.get('documents') if 'documents' in instance else []:\n                document_paragraph_dict_model = DocumentSerializers.Create.get_document_paragraph_model(knowledge_id,\n                                                                                                        document)\n                document_model_list.append(document_paragraph_dict_model.get('document'))\n                for paragraph in document_paragraph_dict_model.get('paragraph_model_list'):\n                    paragraph_model_list.append(paragraph)\n                for problem_paragraph_object in document_paragraph_dict_model.get('problem_paragraph_object_list'):\n                    problem_paragraph_object_list.append(problem_paragraph_object)\n\n            problem_model_list, problem_paragraph_mapping_list = (\n                ProblemParagraphManage(problem_paragraph_object_list, knowledge_id)\n                .to_problem_model_list())\n            # 插入知识库\n            knowledge.save()\n            # 插入文档\n            QuerySet(Document).bulk_create(document_model_list) if len(document_model_list) > 0 else None\n            # 批量插入段落\n            QuerySet(Paragraph).bulk_create(paragraph_model_list) if len(paragraph_model_list) > 0 else None\n            # 批量插入问题\n            QuerySet(Problem).bulk_create(problem_model_list) if len(problem_model_list) > 0 else None\n            # 批量插入关联问题\n            QuerySet(ProblemParagraphMapping).bulk_create(\n                problem_paragraph_mapping_list\n            ) if len(problem_paragraph_mapping_list) > 0 else None\n            # 自动资源给授权当前用户\n            UserResourcePermissionSerializer(data={\n                'workspace_id': self.data.get('workspace_id'),\n                'user_id': self.data.get('user_id'),\n                'auth_target_type': AuthTargetType.KNOWLEDGE.value\n            }).auth_resource(str(knowledge_id))\n            update_resource_mapping_by_knowledge(str(knowledge_id))\n            return {\n                **KnowledgeModelSerializer(knowledge).data,\n                'user_id': self.data.get('user_id'),\n                'document_list': document_model_list,\n                \"document_count\": len(document_model_list),\n                \"char_length\": reduce(lambda x, y: x + y, [d.char_length for d in document_model_list], 0)\n            }, knowledge_id\n\n        def save_web(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                KnowledgeWebCreateRequest(data=instance).is_valid(raise_exception=True)\n\n            folder_id = instance.get('folder_id', self.data.get('workspace_id'))\n\n            knowledge_id = uuid.uuid7()\n            knowledge = Knowledge(\n                id=knowledge_id,\n                name=instance.get('name'),\n                desc=instance.get('desc'),\n                user_id=self.data.get('user_id'),\n                type=instance.get('type', KnowledgeType.WEB),\n                scope=self.data.get('scope', KnowledgeScope.WORKSPACE),\n                folder_id=folder_id,\n                workspace_id=self.data.get('workspace_id'),\n                embedding_model_id=instance.get('embedding_model_id'),\n                meta={\n                    'source_url': instance.get('source_url'),\n                    'selector': instance.get('selector', 'body'),\n                    'embedding_model_id': instance.get('embedding_model_id')\n                },\n            )\n            knowledge.save()\n            # 自动资源给授权当前用户\n            UserResourcePermissionSerializer(data={\n                'workspace_id': self.data.get('workspace_id'),\n                'user_id': self.data.get('user_id'),\n                'auth_target_type': AuthTargetType.KNOWLEDGE.value\n            }).auth_resource(str(knowledge_id))\n\n            sync_web_knowledge.delay(str(knowledge_id), instance.get('source_url'), instance.get('selector'))\n            update_resource_mapping_by_knowledge(str(knowledge_id))\n            return {**KnowledgeModelSerializer(knowledge).data, 'document_list': []}\n\n    class SyncWeb(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.CharField(required=True, label=_('knowledge id'))\n        user_id = serializers.UUIDField(required=False, label=_('user id'))\n        sync_type = serializers.CharField(required=True, label=_('sync type'), validators=[\n            validators.RegexValidator(regex=re.compile(\"^replace|complete$\"),\n                                      message=_('The synchronization type only supports:replace|complete'), code=500)])\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            first = QuerySet(Knowledge).filter(id=self.data.get(\"knowledge_id\")).first()\n            if first is None:\n                raise AppApiException(300, _('id does not exist'))\n            if first.type != KnowledgeType.WEB:\n                raise AppApiException(500, _('Synchronization is only supported for web site types'))\n\n        def sync(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            sync_type = self.data.get('sync_type')\n            knowledge_id = self.data.get('knowledge_id')\n            knowledge = QuerySet(Knowledge).get(id=knowledge_id)\n            self.__getattribute__(sync_type + '_sync')(knowledge)\n            return True\n\n        @staticmethod\n        def get_sync_handler(knowledge):\n            def handler(child_link: ChildLink, response: Fork.Response):\n                if response.status == 200:\n                    try:\n                        document_name = child_link.tag.text if child_link.tag is not None and len(\n                            child_link.tag.text.strip()) > 0 else child_link.url\n                        paragraphs = get_split_model('web.md').parse(response.content)\n                        maxkb_logger.info(child_link.url.strip())\n                        first = QuerySet(Document).filter(\n                            meta__source_url=child_link.url.strip(),\n                            knowledge=knowledge\n                        ).first()\n                        if first is not None:\n                            # 如果存在,使用文档同步\n                            DocumentSerializers.Sync(data={'document_id': first.id}).sync()\n                        else:\n                            # 插入\n                            DocumentSerializers.Create(data={'knowledge_id': knowledge.id}).save(\n                                {'name': document_name, 'paragraphs': paragraphs,\n                                 'meta': {'source_url': child_link.url.strip(),\n                                          'selector': knowledge.meta.get('selector')},\n                                 'type': KnowledgeType.WEB}, with_valid=True)\n                    except Exception as e:\n                        maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}')\n\n            return handler\n\n        def replace_sync(self, knowledge):\n            \"\"\"\n            替换同步\n            :return:\n            \"\"\"\n            url = knowledge.meta.get('source_url')\n            selector = knowledge.meta.get('selector') if 'selector' in knowledge.meta else None\n            sync_replace_web_knowledge.delay(str(knowledge.id), url, selector)\n\n        def complete_sync(self, knowledge):\n            \"\"\"\n            完整同步  删掉当前数据集下所有的文档,再进行同步\n            :return:\n            \"\"\"\n            # 删除关联问题\n            QuerySet(ProblemParagraphMapping).filter(knowledge=knowledge).delete()\n            # 删除文档\n            QuerySet(Document).filter(knowledge=knowledge).delete()\n            # 删除段落\n            QuerySet(Paragraph).filter(knowledge=knowledge).delete()\n            # 删除向量\n            delete_embedding_by_knowledge(self.data.get('knowledge_id'))\n            # 同步\n            self.replace_sync(knowledge)\n\n    class HitTest(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_(\"id\"))\n        user_id = serializers.UUIDField(required=False, label=_('user id'))\n        query_text = serializers.CharField(required=True, label=_('query text'))\n        top_number = serializers.IntegerField(required=True, max_value=10000, min_value=1, label=_(\"top number\"))\n        similarity = serializers.FloatField(required=True, max_value=2, min_value=0, label=_('similarity'))\n        search_mode = serializers.CharField(required=True, label=_('search mode'), validators=[\n            validators.RegexValidator(regex=re.compile(\"^embedding|keywords|blend$\"),\n                                      message=_('The type only supports embedding|keywords|blend'), code=500)\n        ])\n\n        def is_valid(self, *, raise_exception=True):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            if not QuerySet(Knowledge).filter(id=self.data.get(\"knowledge_id\")).exists():\n                raise AppApiException(300, _('id does not exist'))\n\n        def hit_test(self):\n            self.is_valid()\n            vector = VectorStore.get_embedding_vector()\n            exclude_document_id_list = [\n                str(\n                    document.id\n                ) for document in QuerySet(Document).filter(knowledge_id=self.data.get('knowledge_id'), is_active=False)\n            ]\n            model = get_embedding_model_by_knowledge_id(self.data.get('knowledge_id'))\n            # 向量库检索\n            hit_list = vector.hit_test(\n                self.data.get('query_text'),\n                [self.data.get('knowledge_id')],\n                exclude_document_id_list,\n                self.data.get('top_number'),\n                self.data.get('similarity'),\n                SearchMode(self.data.get('search_mode')),\n                model\n            )\n            hit_dict = reduce(lambda x, y: {**x, **y}, [{hit.get('paragraph_id'): hit} for hit in hit_list], {})\n            p_list = list_paragraph([h.get('paragraph_id') for h in hit_list])\n            return [\n                {\n                    **p,\n                    'similarity': hit_dict.get(p.get('id')).get('similarity'),\n                    'comprehensive_score': hit_dict.get(p.get('id')).get('comprehensive_score')\n                } for p in p_list\n            ]\n\n    class StoreKnowledge(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n        name = serializers.CharField(required=False, label=_(\"tool name\"), allow_null=True, allow_blank=True)\n\n        def get_appstore_templates(self):\n            self.is_valid(raise_exception=True)\n            # 下载zip文件\n            try:\n                res = requests.get('https://apps-assets.fit2cloud.com/stable/maxkb.json.zip', timeout=5)\n                res.raise_for_status()\n                # 创建临时文件保存zip\n                with tempfile.NamedTemporaryFile(delete=False, suffix='.zip') as temp_zip:\n                    temp_zip.write(res.content)\n                    temp_zip_path = temp_zip.name\n\n                try:\n                    # 解压zip文件\n                    with zipfile.ZipFile(temp_zip_path, 'r') as zip_ref:\n                        # 获取zip中的第一个文件（假设只有一个json文件）\n                        json_filename = zip_ref.namelist()[0]\n                        json_content = zip_ref.read(json_filename)\n\n                    # 将json转换为字典\n                    tool_store = json.loads(json_content.decode('utf-8'))\n                    tag_dict = {tag['name']: tag['key'] for tag in tool_store['additionalProperties']['tags']}\n                    filter_apps = []\n                    for tool in tool_store['apps']:\n                        if self.data.get('name', '') != '':\n                            if self.data.get('name').lower() not in tool.get('name', '').lower():\n                                continue\n                        if not tool['downloadUrl'].endswith('.kbwf'):\n                            continue\n                        versions = tool.get('versions', [])\n                        tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else ''\n                        tool['version'] = next(\n                            (version.get('name') for version in versions if\n                             version.get('downloadUrl') == tool['downloadUrl']),\n                        )\n                        filter_apps.append(tool)\n\n                    tool_store['apps'] = filter_apps\n                    return tool_store\n                finally:\n                    # 清理临时文件\n                    os.unlink(temp_zip_path)\n            except Exception as e:\n                maxkb_logger.error(f\"fetch appstore tools error: {e}\")\n                return {'apps': [], 'additionalProperties': {'tags': []}}\n\n    class TransformWorkflow(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('Workspace ID'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID'))\n        user_id = serializers.UUIDField(required=True, label=_('User ID'))\n\n        def transform(self, instance: Dict):\n            self.is_valid(raise_exception=True)\n            knowledge = QuerySet(Knowledge).filter(\n                id=self.data.get('knowledge_id'),\n                workspace_id=self.data.get('workspace_id')\n            ).first()\n\n            if not knowledge:\n                raise AppApiException(500, _('Knowledge not found'))\n            if knowledge.type == KnowledgeType.WORKFLOW:\n                raise AppApiException(500, _('Knowledge is already a workflow'))\n\n            knowledge.type = KnowledgeType.WORKFLOW\n            knowledge.save()\n\n            workflow_id = uuid.uuid7()\n            knowledge_workflow = KnowledgeWorkflow(\n                id=workflow_id,\n                workspace_id=knowledge.workspace_id,\n                knowledge_id=knowledge.id,\n                work_flow=instance.get('work_flow', {}),\n            )\n            knowledge_workflow.save()\n            return True\n\n    class Tags(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        knowledge_ids = serializers.ListField(\n            required=True, label=_('knowledge ids'),\n            child=serializers.UUIDField(required=True, label=_('id'))\n        )\n\n        def list(self):\n            self.is_valid(raise_exception=True)\n            if self.data.get('name'):\n                name = self.data.get('name')\n                tags = QuerySet(Tag).filter(\n                    knowledge_id__in=self.data.get('knowledge_ids')\n                ).filter(\n                    Q(key__icontains=name) | Q(value__icontains=name)\n                ).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value')\n            else:\n                # 获取所有标签，按创建时间排序保持稳定顺序\n                tags = QuerySet(Tag).filter(\n                    knowledge_id__in=self.data.get('knowledge_ids')\n                ).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value')\n\n            # 按key分组\n            grouped_tags = defaultdict(list)\n            for tag in tags:\n                grouped_tags[tag['key']].append({\n                    'id': tag['id'],\n                    'value': tag['value'],\n                    'create_time': tag['create_time'],\n                    'update_time': tag['update_time']\n                })\n\n            # 转换为期望的格式，保持key的顺序\n            result = []\n            # 按key排序以确保结果顺序一致\n            for key in sorted(grouped_tags.keys()):\n                values = grouped_tags[key]\n                # 按创建时间对values进行排序\n                values.sort(key=lambda x: x['create_time'])\n                result.append({\n                    'key': key,\n                    'values': values,\n                })\n\n            return result\n"
  },
  {
    "path": "apps/knowledge/serializers/knowledge_folder.py",
    "content": "from rest_framework import serializers\n\nfrom knowledge.models import KnowledgeFolder\n\n\nclass KnowledgeFolderTreeSerializer(serializers.ModelSerializer):\n    children = serializers.SerializerMethodField()\n\n    class Meta:\n        model = KnowledgeFolder\n        fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id', 'children', 'create_time','update_time']\n\n    def get_children(self, obj):\n        return KnowledgeFolderTreeSerializer(obj.get_children(), many=True).data\n\n\nclass KnowledgeFolderFlatSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = KnowledgeFolder\n        fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id']\n"
  },
  {
    "path": "apps/knowledge/serializers/knowledge_version.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： KnowledgeVersionSerializer.py\n    @date：2025/11/28 18:00\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.db.models import QuerySet\nfrom rest_framework import serializers\n\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.db.search import page_search\nfrom common.exception.app_exception import AppApiException\nfrom knowledge.models import KnowledgeWorkflowVersion, Knowledge\n\n\nclass KnowledgeWorkflowVersionEditSerializer(serializers.Serializer):\n    name = serializers.CharField(required=False, max_length=128, allow_null=True, allow_blank=True,\n                                 label=_(\"Version Name\"))\n\n\nclass KnowledgeVersionModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = KnowledgeWorkflowVersion\n        fields = ['id', 'name', 'workspace_id', 'knowledge_id', 'work_flow', 'publish_user_id', 'publish_user_name',\n                  'create_time',\n                  'update_time']\n\n\nclass KnowledgeWorkflowVersionQuerySerializer(serializers.Serializer):\n    knowledge_id = serializers.UUIDField(required=True, label=_(\"Knowledge ID\"))\n    name = serializers.CharField(required=False, allow_null=True, allow_blank=True,\n                                 label=_(\"summary\"))\n\n\nclass KnowledgeWorkflowVersionSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, label=_(\"Workspace ID\"))\n\n    class Query(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n\n        def get_query_set(self, query):\n            query_set = QuerySet(KnowledgeWorkflowVersion).filter(knowledge_id=query.get('knowledge_id'))\n            if 'name' in query and query.get('name') is not None:\n                query_set = query_set.filter(name__contains=query.get('name'))\n            if 'workspace_id' in self.data and self.data.get('workspace_id') is not None:\n                query_set = query_set.filter(workspace_id=self.data.get('workspace_id'))\n            return query_set.order_by(\"-create_time\")\n\n        def list(self, query, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                KnowledgeWorkflowVersionQuerySerializer(data=query).is_valid(raise_exception=True)\n            query_set = self.get_query_set(query)\n            return [KnowledgeVersionModelSerializer(v).data for v in query_set]\n\n        def page(self, query, current_page, page_size, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            return page_search(current_page, page_size,\n                               self.get_query_set(query),\n                               post_records_handler=lambda v: KnowledgeVersionModelSerializer(v).data)\n\n    class Operate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n        knowledge_id = serializers.UUIDField(required=True, label=_(\"Knowledge ID\"))\n        knowledge_version_id = serializers.UUIDField(required=True,\n                                                     label=_(\"Knowledge version ID\"))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        def one(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            knowledge_version = QuerySet(KnowledgeWorkflowVersion).filter(knowledge_id=self.data.get('knowledge_id'),\n                                                                          id=self.data.get(\n                                                                              'knowledge_version_id')).first()\n            if knowledge_version is not None:\n                return KnowledgeVersionModelSerializer(knowledge_version).data\n            else:\n                raise AppApiException(500, _('Workflow version does not exist'))\n\n        def edit(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                KnowledgeWorkflowVersionEditSerializer(data=instance).is_valid(raise_exception=True)\n            knowledge_version = QuerySet(KnowledgeWorkflowVersion).filter(knowledge_id=self.data.get('knowledge_id'),\n                                                                          id=self.data.get(\n                                                                              'knowledge_version_id')).first()\n            if knowledge_version is not None:\n                name = instance.get('name', None)\n                if name is not None and len(name) > 0:\n                    knowledge_version.name = name\n                knowledge_version.save()\n                return KnowledgeVersionModelSerializer(knowledge_version).data\n            else:\n                raise AppApiException(500, _('Workflow version does not exist'))\n"
  },
  {
    "path": "apps/knowledge/serializers/knowledge_workflow.py",
    "content": "# coding=utf-8\nimport asyncio\nimport json\nimport pickle\nfrom functools import reduce\nfrom typing import Dict, List\n\nimport requests\nimport uuid_utils.compat as uuid\nfrom django.core.cache import cache\nfrom django.db import transaction\nfrom django.db.models import QuerySet\nfrom django.http import HttpResponse\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers, status\n\nfrom application.flow.common import Workflow, WorkflowMode\nfrom application.flow.i_step_node import KnowledgeWorkflowPostHandler\nfrom application.flow.knowledge_workflow_manage import KnowledgeWorkflowManage\nfrom application.flow.step_node import get_node\nfrom application.flow.tools import save_workflow_mapping\nfrom application.serializers.application import get_mcp_tools\nfrom common.constants.cache_version import Cache_Version\nfrom common.db.search import page_search\nfrom common.exception.app_exception import AppApiException\nfrom common.field.common import UploadedFileField\nfrom common.result import result\nfrom common.utils.common import bytes_to_uploaded_file\nfrom common.utils.common import restricted_loads, generate_uuid\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.rsa_util import rsa_long_decrypt\nfrom common.utils.tool_code import ToolExecutor\nfrom knowledge.models import KnowledgeScope, Knowledge, KnowledgeType, KnowledgeWorkflow, KnowledgeWorkflowVersion\nfrom knowledge.models.knowledge_action import KnowledgeAction, State\nfrom knowledge.serializers.common import update_resource_mapping_by_knowledge\nfrom knowledge.serializers.knowledge import KnowledgeModelSerializer\nfrom system_manage.models import AuthTargetType\nfrom system_manage.models.resource_mapping import ResourceType\nfrom system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer\nfrom tools.models import Tool, ToolScope\nfrom tools.serializers.tool import ToolExportModelSerializer\nfrom users.models import User\n\ntool_executor = ToolExecutor()\n\n\ndef hand_node(node, update_tool_map):\n    if node.get('type') == 'tool-lib-node':\n        tool_lib_id = (node.get('properties', {}).get('node_data', {}).get('tool_lib_id') or '')\n        node.get('properties', {}).get('node_data', {})['tool_lib_id'] = update_tool_map.get(tool_lib_id, tool_lib_id)\n\n    if node.get('type') == 'search-knowledge-node':\n        node.get('properties', {}).get('node_data', {})['knowledge_id_list'] = []\n    if node.get('type') == 'ai-chat-node':\n        node_data = node.get('properties', {}).get('node_data', {})\n        mcp_tool_ids = node_data.get('mcp_tool_ids') or []\n        node_data['mcp_tool_ids'] = [update_tool_map.get(tool_id,\n                                                         tool_id) for tool_id in mcp_tool_ids]\n        tool_ids = node_data.get('tool_ids') or []\n        node_data['tool_ids'] = [update_tool_map.get(tool_id,\n                                                     tool_id) for tool_id in tool_ids]\n    if node.get('type') == 'mcp-node':\n        mcp_tool_id = (node.get('properties', {}).get('node_data', {}).get('mcp_tool_id') or '')\n        node.get('properties', {}).get('node_data', {})['mcp_tool_id'] = update_tool_map.get(mcp_tool_id,\n                                                                                             mcp_tool_id)\n\n\nclass KnowledgeWorkflowModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = KnowledgeWorkflow\n        fields = '__all__'\n\n\nclass KnowledgeWorkflowActionRequestSerializer(serializers.Serializer):\n    data_source = serializers.DictField(required=True, label=_('datasource data'))\n    knowledge_base = serializers.DictField(required=True, label=_('knowledge base data'))\n\n\nclass KnowledgeWorkflowImportRequest(serializers.Serializer):\n    file = UploadedFileField(required=True, label=_(\"file\"))\n\n\nclass KnowledgeWorkflowActionListQuerySerializer(serializers.Serializer):\n    user_name = serializers.CharField(required=False, label=_('Name'), allow_blank=True, allow_null=True)\n    state = serializers.CharField(required=False, label=_(\"State\"), allow_blank=True, allow_null=True)\n\n\nclass KBWFInstance:\n\n    def __init__(self, knowledge_workflow: dict, function_lib_list: List[dict], version: str, tool_list: List[dict]):\n        self.knowledge_workflow = knowledge_workflow\n        self.function_lib_list = function_lib_list\n        self.version = version\n        self.tool_list = tool_list\n\n    def get_tool_list(self):\n        return [*(self.tool_list or []), *(self.function_lib_list or [])]\n\n\nclass KnowledgeWorkflowActionSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n    knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n    def get_query_set(self, instance: Dict):\n        query_set = QuerySet(KnowledgeAction).filter(knowledge_id=self.data.get('knowledge_id')).values('id',\n                                                                                                        'knowledge_id',\n                                                                                                        \"state\", 'meta',\n                                                                                                        'run_time',\n                                                                                                        \"create_time\")\n        if instance.get(\"user_name\"):\n            query_set = query_set.filter(meta__user_name__icontains=instance.get('user_name'))\n        if instance.get('state'):\n            query_set = query_set.filter(state=instance.get('state'))\n        return query_set.order_by('-create_time')\n\n    def list(self, instance: Dict, is_valid=True):\n        if is_valid:\n            self.is_valid(raise_exception=True)\n            KnowledgeWorkflowActionListQuerySerializer(data=instance).is_valid(raise_exception=True)\n        return [{'id': a.get(\"id\"), 'knowledge_id': a.get(\"knowledge_id\"), 'state': a.get(\"state\"),\n                 'meta': a.get(\"meta\"), 'run_time': a.get(\"run_time\"), 'create_time': a.get(\"create_time\")} for a in\n                self.get_query_set(instance)]\n\n    def page(self, current_page, page_size, instance: Dict, is_valid=True):\n        if is_valid:\n            self.is_valid(raise_exception=True)\n            KnowledgeWorkflowActionListQuerySerializer(data=instance).is_valid(raise_exception=True)\n        return page_search(current_page, page_size, self.get_query_set(instance),\n                           lambda a: {'id': a.get(\"id\"), 'knowledge_id': a.get(\"knowledge_id\"), 'state': a.get(\"state\"),\n                                      'meta': a.get(\"meta\"), 'run_time': a.get(\"run_time\"),\n                                      'create_time': a.get(\"create_time\")})\n\n    def action(self, instance: Dict, user, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get(\"knowledge_id\")).first()\n        knowledge_action_id = uuid.uuid7()\n        meta = {'user_id': str(user.id),\n                'user_name': user.username}\n        KnowledgeAction(id=knowledge_action_id,\n                        knowledge_id=self.data.get(\"knowledge_id\"),\n                        state=State.STARTED,\n                        meta=meta).save()\n        knowledge = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')).first()\n        instance['knowledge_base'] = {**(instance.get('knowledge_base') or {}),\n                                      'knowledge': {'id': str(knowledge.id), 'name': knowledge.name,\n                                                    'desc': knowledge.desc,\n                                                    'workspace_id': knowledge.workspace_id}}\n        work_flow_manage = KnowledgeWorkflowManage(\n            Workflow.new_instance(knowledge_workflow.work_flow, WorkflowMode.KNOWLEDGE),\n            {'knowledge_id': self.data.get(\"knowledge_id\"), 'knowledge_action_id': knowledge_action_id, 'stream': True,\n             'workspace_id': self.data.get(\"workspace_id\"),\n             **instance},\n            KnowledgeWorkflowPostHandler(None, knowledge_action_id),\n            is_the_task_interrupted=lambda: cache.get(\n                Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_key(action_id=knowledge_action_id),\n                version=Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_version()) or False)\n        work_flow_manage.run()\n        return {'id': knowledge_action_id, 'knowledge_id': self.data.get(\"knowledge_id\"), 'state': State.STARTED,\n                'details': {}, 'meta': meta}\n\n    def upload_document(self, instance: Dict, user, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get(\"knowledge_id\")).first()\n        if not knowledge_workflow.is_publish:\n            raise AppApiException(500, _(\"The knowledge base workflow has not been published\"))\n        knowledge_workflow_version = QuerySet(KnowledgeWorkflowVersion).filter(\n            knowledge_id=self.data.get(\"knowledge_id\")).order_by(\n            '-create_time')[0:1].first()\n        knowledge_action_id = uuid.uuid7()\n        meta = {'user_id': str(user.id),\n                'user_name': user.username}\n        KnowledgeAction(id=knowledge_action_id, knowledge_id=self.data.get(\"knowledge_id\"), state=State.STARTED,\n                        meta=meta).save()\n        knowledge = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id')).first()\n        instance['knowledge_base'] = {**(instance.get('knowledge_base') or {}),\n                                      'knowledge': {'id': str(knowledge.id), 'name': knowledge.name,\n                                                    'desc': knowledge.desc,\n                                                    'workspace_id': knowledge.workspace_id}}\n        work_flow_manage = KnowledgeWorkflowManage(\n            Workflow.new_instance(knowledge_workflow_version.work_flow, WorkflowMode.KNOWLEDGE),\n            {'knowledge_id': self.data.get(\"knowledge_id\"), 'knowledge_action_id': knowledge_action_id, 'stream': True,\n             'workspace_id': self.data.get(\"workspace_id\"),\n             **instance},\n            KnowledgeWorkflowPostHandler(None, knowledge_action_id),\n            is_the_task_interrupted=lambda: cache.get(\n                Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_key(action_id=knowledge_action_id),\n                version=Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_version()) or False\n        )\n        work_flow_manage.run()\n        return {'id': knowledge_action_id, 'knowledge_id': self.data.get(\"knowledge_id\"), 'state': State.STARTED,\n                'details': {}, 'meta': meta}\n\n    class Operate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        id = serializers.UUIDField(required=True, label=_('knowledge action id'))\n\n        def one(self, is_valid=True):\n            if is_valid:\n                self.is_valid(raise_exception=True)\n            knowledge_action_id = self.data.get(\"id\")\n            knowledge_action = QuerySet(KnowledgeAction).filter(id=knowledge_action_id).first()\n            return {'id': knowledge_action_id, 'knowledge_id': knowledge_action.knowledge_id,\n                    'state': knowledge_action.state,\n                    'details': knowledge_action.details,\n                    'meta': knowledge_action.meta}\n\n        def cancel(self, is_valid=True):\n            if is_valid:\n                self.is_valid(raise_exception=True)\n            knowledge_action_id = self.data.get(\"id\")\n            cache.set(Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_key(action_id=knowledge_action_id), True,\n                      version=Cache_Version.KNOWLEDGE_WORKFLOW_INTERRUPTED.get_version())\n            QuerySet(KnowledgeAction).filter(id=knowledge_action_id, state__in=[State.STARTED, State.PENDING]).update(\n                state=State.REVOKE)\n            return True\n\n\nclass KnowledgeWorkflowSerializer(serializers.Serializer):\n    class Datasource(serializers.Serializer):\n        type = serializers.CharField(required=True, label=_('type'))\n        id = serializers.CharField(required=True, label=_('type'))\n        params = serializers.DictField(required=True, label=\"\")\n        function_name = serializers.CharField(required=True, label=_('function_name'))\n\n        def action(self):\n            self.is_valid(raise_exception=True)\n            if self.data.get('type') == 'local':\n                node = get_node(self.data.get('id'), WorkflowMode.KNOWLEDGE)\n                return node.__getattribute__(node, self.data.get(\"function_name\"))(**self.data.get(\"params\"))\n            elif self.data.get('type') == 'tool':\n                tool = QuerySet(Tool).filter(id=self.data.get(\"id\")).first()\n                init_params = json.loads(rsa_long_decrypt(tool.init_params))\n                return tool_executor.exec_code(tool.code, {**init_params, **self.data.get('params')},\n                                               self.data.get('function_name'))\n\n    class Create(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        scope = serializers.ChoiceField(\n            required=False, label=_('scope'), default=KnowledgeScope.WORKSPACE, choices=KnowledgeScope.choices\n        )\n\n        @transaction.atomic\n        def save_workflow(self, instance: Dict):\n            self.is_valid(raise_exception=True)\n\n            folder_id = instance.get('folder_id', self.data.get('workspace_id'))\n\n            knowledge_id = uuid.uuid7()\n            knowledge = Knowledge(\n                id=knowledge_id,\n                name=instance.get('name'),\n                desc=instance.get('desc'),\n                user_id=self.data.get('user_id'),\n                type=instance.get('type', KnowledgeType.WORKFLOW),\n                scope=self.data.get('scope', KnowledgeScope.WORKSPACE),\n                folder_id=folder_id,\n                workspace_id=self.data.get('workspace_id'),\n                embedding_model_id=instance.get('embedding_model_id'),\n                meta={},\n            )\n            knowledge.save()\n            # 自动资源给授权当前用户\n            UserResourcePermissionSerializer(data={\n                'workspace_id': self.data.get('workspace_id'),\n                'user_id': self.data.get('user_id'),\n                'auth_target_type': AuthTargetType.KNOWLEDGE.value\n            }).auth_resource(str(knowledge_id))\n\n            knowledge_workflow = KnowledgeWorkflow(\n                id=uuid.uuid7(),\n                knowledge_id=knowledge_id,\n                workspace_id=self.data.get('workspace_id'),\n                work_flow=instance.get('work_flow', {}),\n            )\n\n            knowledge_workflow.save()\n            save_workflow_mapping(instance.get('work_flow', {}), ResourceType.KNOWLEDGE, str(knowledge_id))\n\n            # 处理 work_flow_template\n            if instance.get('work_flow_template') is not None:\n                template_instance = instance.get('work_flow_template')\n                download_url = template_instance.get('downloadUrl')\n                # 查找匹配的版本名称\n                res = requests.get(download_url, timeout=5)\n                KnowledgeWorkflowSerializer.Import(data={\n                    'user_id': self.data.get('user_id'),\n                    'workspace_id': self.data.get('workspace_id'),\n                    'knowledge_id': str(knowledge_id),\n                }).import_({'file': bytes_to_uploaded_file(res.content, 'file.kbwf')}, is_import_tool=True)\n\n                try:\n                    requests.get(template_instance.get('downloadCallbackUrl'), timeout=5)\n                except Exception as e:\n                    maxkb_logger.error(f\"callback appstore tool download error: {e}\")\n\n            return {**KnowledgeModelSerializer(knowledge).data, 'document_list': []}\n\n    class Import(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        @transaction.atomic\n        def import_(self, instance: dict, is_import_tool, with_valid=True):\n            if with_valid:\n                self.is_valid()\n                KnowledgeWorkflowImportRequest(data=instance).is_valid(raise_exception=True)\n            user_id = self.data.get('user_id')\n            workspace_id = self.data.get('workspace_id')\n            knowledge_id = self.data.get('knowledge_id')\n            kbwf_instance_bytes = instance.get('file').read()\n            try:\n                kbwf_instance = restricted_loads(kbwf_instance_bytes)\n            except Exception as e:\n                raise AppApiException(1001, _(\"Unsupported file format\"))\n            knowledge_workflow = kbwf_instance.knowledge_workflow\n            tool_list = kbwf_instance.get_tool_list()\n            update_tool_map = {}\n            if len(tool_list) > 0:\n                tool_id_list = reduce(lambda x, y: [*x, *y],\n                                      [[tool.get('id'), generate_uuid((tool.get('id') + workspace_id or ''))]\n                                       for tool\n                                       in\n                                       tool_list], [])\n                # 存在的工具列表\n                exits_tool_id_list = [str(tool.id) for tool in\n                                      QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=workspace_id)]\n                # 需要更新的工具集合\n                update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + workspace_id or '')) for tool\n                                   in\n                                   tool_list if\n                                   not exits_tool_id_list.__contains__(\n                                       tool.get('id'))}\n\n                tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if\n                             not exits_tool_id_list.__contains__(\n                                 tool.get('id')) and not exits_tool_id_list.__contains__(\n                                 generate_uuid((tool.get('id') + workspace_id or '')))]\n\n            work_flow = self.to_knowledge_workflow(\n                knowledge_workflow,\n                update_tool_map,\n            )\n            tool_model_list = [self.to_tool(tool, workspace_id, user_id) for tool in tool_list]\n            KnowledgeWorkflow.objects.filter(workspace_id=workspace_id, knowledge_id=knowledge_id).update_or_create(\n                knowledge_id=knowledge_id,\n                workspace_id=workspace_id,\n                defaults={'work_flow': work_flow}\n            )\n\n            if is_import_tool:\n                if len(tool_model_list) > 0:\n                    QuerySet(Tool).bulk_create(tool_model_list)\n                    UserResourcePermissionSerializer(data={\n                        'workspace_id': self.data.get('workspace_id'),\n                        'user_id': self.data.get('user_id'),\n                        'auth_target_type': AuthTargetType.TOOL.value\n                    }).auth_resource_batch([t.id for t in tool_model_list])\n                return True\n            update_resource_mapping_by_knowledge(knowledge_id)\n\n        @staticmethod\n        def to_knowledge_workflow(knowledge_workflow, update_tool_map):\n            work_flow = knowledge_workflow.get(\"work_flow\")\n            for node in work_flow.get('nodes', []):\n                hand_node(node, update_tool_map)\n                if node.get('type') == 'loop_node':\n                    for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []):\n                        hand_node(n, update_tool_map)\n            return work_flow\n\n        @staticmethod\n        def to_tool(tool, workspace_id, user_id):\n            return Tool(id=tool.get('id'),\n                        user_id=user_id,\n                        name=tool.get('name'),\n                        code=tool.get('code'),\n                        template_id=tool.get('template_id'),\n                        input_field_list=tool.get('input_field_list'),\n                        init_field_list=tool.get('init_field_list'),\n                        is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'),\n                        tool_type=tool.get('tool_type', 'CUSTOM') or 'CUSTOM',\n                        scope=ToolScope.SHARED if workspace_id == 'None' else ToolScope.WORKSPACE,\n                        folder_id='default' if workspace_id == 'None' else workspace_id,\n                        workspace_id=workspace_id)\n\n    class Export(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        def export(self, with_valid=True):\n            try:\n                if with_valid:\n                    self.is_valid()\n                knowledge_id = self.data.get('knowledge_id')\n                knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=knowledge_id).first()\n                knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first()\n                from application.flow.tools import get_tool_id_list\n                tool_id_list = get_tool_id_list(knowledge_workflow.work_flow)\n                tool_list = []\n                if len(tool_id_list) > 0:\n                    tool_list = QuerySet(Tool).filter(id__in=tool_id_list).exclude(scope=ToolScope.SHARED)\n                knowledge_workflow_dict = KnowledgeWorkflowModelSerializer(knowledge_workflow).data\n\n                kbwf_instance = KBWFInstance(\n                    knowledge_workflow_dict,\n                    [],\n                    'v2',\n                    [ToolExportModelSerializer(tool).data for tool in tool_list]\n                )\n                knowledge_workflow_pickle = pickle.dumps(kbwf_instance)\n                response = HttpResponse(content_type='text/plain', content=knowledge_workflow_pickle)\n                response['Content-Disposition'] = f'attachment; filename=\"{knowledge.name}.kbwf\"'\n                return response\n            except Exception as e:\n                return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR)\n\n    class Operate(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        def publish(self, with_valid=True):\n            if with_valid:\n                self.is_valid()\n            user_id = self.data.get('user_id')\n            workspace_id = self.data.get(\"workspace_id\")\n            user = QuerySet(User).filter(id=user_id).first()\n            knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get(\"knowledge_id\"),\n                                                                    workspace_id=workspace_id).first()\n            work_flow_version = KnowledgeWorkflowVersion(work_flow=knowledge_workflow.work_flow,\n                                                         knowledge_id=self.data.get(\"knowledge_id\"),\n                                                         name=timezone.localtime(timezone.now()).strftime(\n                                                             '%Y-%m-%d %H:%M:%S'),\n                                                         publish_user_id=user_id,\n                                                         publish_user_name=user.username,\n                                                         workspace_id=workspace_id)\n            work_flow_version.save()\n            QuerySet(KnowledgeWorkflow).filter(\n                knowledge_id=self.data.get(\"knowledge_id\")\n            ).update(is_publish=True, publish_time=timezone.now())\n            return True\n\n        def edit(self, instance: Dict):\n            self.is_valid(raise_exception=True)\n            if instance.get(\"work_flow\"):\n                QuerySet(KnowledgeWorkflow).update_or_create(knowledge_id=self.data.get(\"knowledge_id\"),\n                                                             create_defaults={'id': uuid.uuid7(),\n                                                                              'knowledge_id': self.data.get(\n                                                                                  \"knowledge_id\"),\n                                                                              \"workspace_id\": self.data.get(\n                                                                                  'workspace_id'),\n                                                                              'work_flow': instance.get('work_flow',\n                                                                                                        {}), },\n                                                             defaults={\n                                                                 'work_flow': instance.get('work_flow')\n                                                             })\n                update_resource_mapping_by_knowledge(self.data.get(\"knowledge_id\"))\n                return self.one()\n            if instance.get(\"work_flow_template\"):\n                template_instance = instance.get('work_flow_template')\n                download_url = template_instance.get('downloadUrl')\n                # 查找匹配的版本名称\n                res = requests.get(download_url, timeout=5)\n                KnowledgeWorkflowSerializer.Import(data={\n                    'user_id': self.data.get('user_id'),\n                    'workspace_id': self.data.get('workspace_id'),\n                    'knowledge_id': str(self.data.get('knowledge_id')),\n                }).import_({'file': bytes_to_uploaded_file(res.content, 'file.kbwf')}, is_import_tool=False)\n\n                try:\n                    requests.get(template_instance.get('downloadCallbackUrl'), timeout=5)\n                except Exception as e:\n                    maxkb_logger.error(f\"callback appstore tool download error: {e}\")\n\n                return self.one()\n\n        def one(self):\n            self.is_valid(raise_exception=True)\n            workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get('knowledge_id')).first()\n            return {**KnowledgeWorkflowModelSerializer(workflow).data}\n\n\nclass McpServersSerializer(serializers.Serializer):\n    mcp_servers = serializers.JSONField(required=True)\n\n\nclass KnowledgeWorkflowMcpSerializer(serializers.Serializer):\n    knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n    user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Knowledge id does not exist'))\n\n    def get_mcp_servers(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            McpServersSerializer(data=instance).is_valid(raise_exception=True)\n        servers = json.loads(instance.get('mcp_servers'))\n        for server, config in servers.items():\n            if config.get('transport') not in ['sse', 'streamable_http']:\n                raise AppApiException(500, _('Only support transport=sse or transport=streamable_http'))\n        tools = []\n        for server in servers:\n            tools += [\n                {\n                    'server': server,\n                    'name': tool.name,\n                    'description': tool.description,\n                    'args_schema': tool.args_schema,\n                }\n                for tool in asyncio.run(get_mcp_tools({server: servers[server]}))]\n        return tools\n"
  },
  {
    "path": "apps/knowledge/serializers/paragraph.py",
    "content": "# coding=utf-8\n\nfrom typing import Dict\n\nimport uuid_utils.compat as uuid\nfrom celery_once import AlreadyQueued\nfrom django.db import transaction\nfrom django.db.models import QuerySet, Count, F\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom common.db.search import page_search\nfrom common.event.listener_manage import ListenerManagement\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import post\nfrom knowledge.models import Paragraph, Problem, Document, ProblemParagraphMapping, SourceType, TaskType, State, \\\n    Knowledge\nfrom knowledge.serializers.common import ProblemParagraphObject, ProblemParagraphManage, \\\n    get_embedding_model_id_by_knowledge_id, update_document_char_length, BatchSerializer\nfrom knowledge.serializers.problem import ProblemInstanceSerializer, ProblemSerializer, ProblemSerializers\nfrom knowledge.task.embedding import embedding_by_paragraph, enable_embedding_by_paragraph, \\\n    disable_embedding_by_paragraph, \\\n    delete_embedding_by_paragraph, embedding_by_problem as embedding_by_problem_task, delete_embedding_by_paragraph_ids, \\\n    embedding_by_problem, delete_embedding_by_source, update_embedding_document_id\nfrom knowledge.task.generate import generate_related_by_paragraph_id_list\n\n\nclass ParagraphSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Paragraph\n        fields = ['id', 'content', 'is_active', 'document_id', 'title', 'create_time', 'update_time', 'position']\n\n\nclass ParagraphInstanceSerializer(serializers.Serializer):\n    \"\"\"\n    段落实例对象\n    \"\"\"\n    content = serializers.CharField(required=True, label=_('content'), max_length=102400, min_length=1, allow_null=True,\n                                    allow_blank=True)\n    title = serializers.CharField(required=False, max_length=256, label=_('section title'), allow_null=True,\n                                  allow_blank=True)\n    problem_list = ProblemInstanceSerializer(required=False, many=True)\n    is_active = serializers.BooleanField(required=False, label=_('Is active'))\n\n\nclass EditParagraphSerializers(serializers.Serializer):\n    title = serializers.CharField(required=False, max_length=256, label=_('section title'), allow_null=True,\n                                  allow_blank=True)\n    content = serializers.CharField(required=False, max_length=102400, allow_null=True, allow_blank=True,\n                                    label=_('section title'))\n    problem_list = ProblemInstanceSerializer(required=False, many=True)\n\n\nclass ParagraphBatchGenerateRelatedSerializer(serializers.Serializer):\n    paragraph_id_list = serializers.ListField(required=True, label=_('paragraph id list'),\n                                              child=serializers.UUIDField(required=True, label=_('paragraph id')))\n    model_id = serializers.UUIDField(required=True, label=_('model id'))\n    prompt = serializers.CharField(required=True, label=_('prompt'), max_length=102400, allow_null=True,\n                                   allow_blank=True)\n    document_id = serializers.UUIDField(required=True, label=_('document id'))\n\n\nclass ParagraphSerializers(serializers.Serializer):\n    title = serializers.CharField(required=False, max_length=256, label=_('section title'), allow_null=True,\n                                  allow_blank=True)\n    content = serializers.CharField(required=True, max_length=102400, label=_('section title'))\n\n    class Problem(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n        paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            if not QuerySet(Paragraph).filter(id=self.data.get('paragraph_id')).exists():\n                raise AppApiException(500, _('Paragraph id does not exist'))\n\n        def list(self, with_valid=False):\n            \"\"\"\n            获取问题列表\n            :param with_valid: 是否校验\n            :return: 问题列表\n            \"\"\"\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter(\n                knowledge_id=self.data.get(\"knowledge_id\"),\n                paragraph_id=self.data.get(\n                    'paragraph_id'))\n            return [ProblemSerializer(row).data for row in\n                    QuerySet(Problem).filter(id__in=[row.problem_id for row in problem_paragraph_mapping])]\n\n        @transaction.atomic\n        def save(self, instance: Dict, with_valid=True, with_embedding=True, embedding_by_problem=None):\n            if with_valid:\n                self.is_valid()\n                ProblemInstanceSerializer(data=instance).is_valid(raise_exception=True)\n            problem = QuerySet(Problem).filter(knowledge_id=self.data.get('knowledge_id'),\n                                               content=instance.get('content')).first()\n            if problem is None:\n                problem = Problem(id=uuid.uuid7(), knowledge_id=self.data.get('knowledge_id'),\n                                  content=instance.get('content'))\n                problem.save()\n            if QuerySet(ProblemParagraphMapping).filter(knowledge_id=self.data.get('knowledge_id'),\n                                                        problem_id=problem.id,\n                                                        paragraph_id=self.data.get('paragraph_id')).exists():\n                raise AppApiException(500, _('Already associated, please do not associate again'))\n            problem_paragraph_mapping = ProblemParagraphMapping(\n                id=uuid.uuid7(),\n                problem_id=problem.id,\n                document_id=self.data.get('document_id'),\n                paragraph_id=self.data.get('paragraph_id'),\n                knowledge_id=self.data.get('knowledge_id')\n            )\n            problem_paragraph_mapping.save()\n            model_id = get_embedding_model_id_by_knowledge_id(self.data.get('knowledge_id'))\n            if with_embedding:\n                embedding_by_problem_task({\n                    'text': problem.content,\n                    'is_active': True,\n                    'source_type': SourceType.PROBLEM,\n                    'source_id': problem_paragraph_mapping.id,\n                    'document_id': self.data.get('document_id'),\n                    'paragraph_id': self.data.get('paragraph_id'),\n                    'knowledge_id': self.data.get('knowledge_id'),\n                }, model_id)\n\n            return ProblemSerializers.Operate(\n                data={\n                    'workspace_id':  self.data.get('workspace_id'),\n                    'knowledge_id': self.data.get('knowledge_id'),\n                    'problem_id': problem.id\n                }\n            ).one(with_valid=True)\n\n    class Operate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        # 段落id\n        paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id'))\n        # 知识库id\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        # 文档id\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n\n        def is_valid(self, *, raise_exception=True):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            if not QuerySet(Paragraph).filter(id=self.data.get('paragraph_id')).exists():\n                raise AppApiException(500, _('Paragraph id does not exist'))\n\n        @staticmethod\n        def post_embedding(paragraph, instance, knowledge_id):\n            if 'is_active' in instance and instance.get('is_active') is not None:\n                (enable_embedding_by_paragraph if instance.get(\n                    'is_active') else disable_embedding_by_paragraph)(paragraph.get('id'))\n\n            else:\n                model_id = get_embedding_model_id_by_knowledge_id(knowledge_id)\n                embedding_by_paragraph(paragraph.get('id'), model_id)\n            return paragraph\n\n        @post(post_embedding)\n        @transaction.atomic\n        def edit(self, instance: Dict):\n            self.is_valid()\n            EditParagraphSerializers(data=instance).is_valid(raise_exception=True)\n            _paragraph = QuerySet(Paragraph).get(id=self.data.get(\"paragraph_id\"))\n            update_keys = ['title', 'content', 'is_active']\n            for update_key in update_keys:\n                if update_key in instance and instance.get(update_key) is not None:\n                    _paragraph.__setattr__(update_key, instance.get(update_key))\n\n            if 'problem_list' in instance:\n                update_problem_list = list(\n                    filter(lambda row: 'id' in row and row.get('id') is not None, instance.get('problem_list')))\n\n                create_problem_list = list(filter(lambda row: row.get('id') is None, instance.get('problem_list')))\n\n                # 问题集合\n                problem_list = QuerySet(Problem).filter(paragraph_id=self.data.get(\"paragraph_id\"))\n\n                # 校验前端 携带过来的id\n                for update_problem in update_problem_list:\n                    if not set([str(row.id) for row in problem_list]).__contains__(update_problem.get('id')):\n                        raise AppApiException(500, _('Problem id does not exist'))\n                # 对比需要删除的问题\n                delete_problem_list = list(filter(\n                    lambda row: not [str(update_row.get('id')) for update_row in update_problem_list].__contains__(\n                        str(row.id)), problem_list)) if len(update_problem_list) > 0 else []\n                # 删除问题\n                QuerySet(Problem).filter(id__in=[row.id for row in delete_problem_list]).delete() if len(\n                    delete_problem_list) > 0 else None\n                # 插入新的问题\n                QuerySet(Problem).bulk_create([\n                    Problem(\n                        id=uuid.uuid7(),\n                        content=p.get('content'),\n                        paragraph_id=self.data.get('paragraph_id'),\n                        knowledge_id=self.data.get('knowledge_id'),\n                        document_id=self.data.get('document_id')\n                    ) for p in create_problem_list\n                ]) if len(create_problem_list) else None\n\n                # 修改问题集合\n                QuerySet(Problem).bulk_update([\n                    Problem(\n                        id=row.get('id'),\n                        content=row.get('content')\n                    ) for row in update_problem_list], ['content']\n                ) if len(update_problem_list) > 0 else None\n\n            _paragraph.save()\n            update_document_char_length(self.data.get('document_id'))\n            return self.one(), instance, self.data.get('knowledge_id')\n\n        def get_problem_list(self):\n            ProblemParagraphMapping(ProblemParagraphMapping)\n            problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter(\n                paragraph_id=self.data.get(\"paragraph_id\"))\n            if len(problem_paragraph_mapping) > 0:\n                return [ProblemSerializer(problem).data for problem in\n                        QuerySet(Problem).filter(id__in=[ppm.problem_id for ppm in problem_paragraph_mapping])]\n            return []\n\n        def one(self, with_valid=False):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            return {**ParagraphSerializer(QuerySet(model=Paragraph).get(id=self.data.get('paragraph_id'))).data,\n                    'problem_list': self.get_problem_list()}\n\n        def delete(self, with_valid=False):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            paragraph_id = self.data.get('paragraph_id')\n            Paragraph.objects.filter(id=paragraph_id).delete()\n            delete_problems_and_mappings([paragraph_id])\n\n            update_document_char_length(self.data.get('document_id'))\n            delete_embedding_by_paragraph(paragraph_id)\n\n    class Create(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label='Workspace ID')\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            if not QuerySet(Document).filter(id=self.data.get('document_id'),\n                                             knowledge_id=self.data.get('knowledge_id')).exists():\n                raise AppApiException(500, _('The document id is incorrect'))\n\n        @transaction.atomic\n        def save(self, instance: Dict, with_valid=True, with_embedding=True):\n            if with_valid:\n                ParagraphSerializers(data=instance).is_valid(raise_exception=True)\n                self.is_valid()\n            knowledge_id = self.data.get(\"knowledge_id\")\n            document_id = self.data.get('document_id')\n\n            # 先将同一文档中的所有段落位置向下移动一位\n            Paragraph.objects.filter(document_id=document_id).update(position=F('position') + 1)\n\n            paragraph_problem_model = self.get_paragraph_problem_model(knowledge_id, document_id, instance)\n            paragraph = paragraph_problem_model.get('paragraph')\n            problem_paragraph_object_list = paragraph_problem_model.get('problem_paragraph_object_list')\n            problem_model_list, problem_paragraph_mapping_list = (\n                ProblemParagraphManage(problem_paragraph_object_list, knowledge_id)\n                .to_problem_model_list())\n            # 新加的在最上面\n            paragraph.position = 1\n            paragraph.save()\n            # 调整位置\n            if 'position' in instance:\n                if type(instance['position']) is not int:\n                    instance['position'] = 1\n            else:\n                instance['position'] = 1\n\n            ParagraphSerializers.AdjustPosition(data={\n                'paragraph_id': str(paragraph.id),\n                'knowledge_id': knowledge_id,\n                'document_id': document_id,\n                'workspace_id': self.data.get('workspace_id')\n            }).adjust_position(instance.get('position'))\n            # 插入問題\n            QuerySet(Problem).bulk_create(problem_model_list) if len(problem_model_list) > 0 else None\n            # 插入问题关联关系\n            QuerySet(ProblemParagraphMapping).bulk_create(\n                problem_paragraph_mapping_list\n            ) if len(problem_paragraph_mapping_list) > 0 else None\n            # 修改长度\n            update_document_char_length(document_id)\n            if with_embedding:\n                model_id = get_embedding_model_id_by_knowledge_id(knowledge_id)\n                embedding_by_paragraph(str(paragraph.id), model_id)\n                ListenerManagement.update_status(\n                    QuerySet(Document).filter(id=document_id), TaskType.EMBEDDING, State.SUCCESS\n                )\n                ListenerManagement.get_aggregation_document_status(document_id)()\n            return ParagraphSerializers.Operate(\n                data={\n                    'paragraph_id': str(paragraph.id),\n                    'knowledge_id': knowledge_id,\n                    'document_id': document_id,\n                    'workspace_id': self.data.get('workspace_id')\n                }\n            ).one(with_valid=True)\n\n        @staticmethod\n        def get_paragraph_problem_model(knowledge_id: str, document_id: str, instance: Dict):\n            paragraph = Paragraph(\n                id=uuid.uuid7(),\n                document_id=document_id,\n                content=instance.get(\"content\"),\n                knowledge_id=knowledge_id,\n                title=instance.get(\"title\") if 'title' in instance else ''\n            )\n            problem_paragraph_object_list = [ProblemParagraphObject(\n                knowledge_id, document_id, str(paragraph.id), problem.get('content')\n            ) for problem in (instance.get('problem_list') if 'problem_list' in instance else [])]\n\n            return {\n                'paragraph': paragraph,\n                'problem_paragraph_object_list': problem_paragraph_object_list\n            }\n\n        @staticmethod\n        def or_get(exists_problem_list, content, knowledge_id):\n            exists = [row for row in exists_problem_list if row.content == content]\n            if len(exists) > 0:\n                return exists[0]\n            else:\n                return Problem(id=uuid.uuid7(), content=content, knowledge_id=knowledge_id)\n\n    class Query(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n        title = serializers.CharField(required=False, label=_('section title'))\n        content = serializers.CharField(required=False)\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        def get_query_set(self):\n            self.is_valid()\n            query_set = QuerySet(model=Paragraph)\n            query_set = query_set.filter(\n                **{'knowledge_id': self.data.get('knowledge_id'), 'document_id': self.data.get(\"document_id\")})\n            if 'title' in self.data:\n                query_set = query_set.filter(\n                    **{'title__icontains': self.data.get('title')})\n            if 'content' in self.data:\n                query_set = query_set.filter(**{'content__icontains': self.data.get('content')})\n            query_set = query_set.order_by('position', 'create_time')\n            return query_set\n\n        def list(self):\n            return list(map(lambda row: ParagraphSerializer(row).data, self.get_query_set()))\n\n        def page(self, current_page, page_size):\n            query_set = self.get_query_set()\n            return page_search(current_page, page_size, query_set, lambda row: ParagraphSerializer(row).data)\n\n    class Association(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        problem_id = serializers.UUIDField(required=True, label=_('problem id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n        paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id'))\n\n        def is_valid(self, *, raise_exception=True):\n            super().is_valid(raise_exception=True)\n            knowledge_id = self.data.get('knowledge_id')\n            paragraph_id = self.data.get('paragraph_id')\n            problem_id = self.data.get(\"problem_id\")\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            if not QuerySet(Paragraph).filter(knowledge_id=knowledge_id, id=paragraph_id).exists():\n                raise AppApiException(500, _('Paragraph does not exist'))\n            if not QuerySet(Problem).filter(knowledge_id=knowledge_id, id=problem_id).exists():\n                raise AppApiException(500, _('Problem does not exist'))\n\n        def association(self, with_valid=True, with_embedding=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            # 已关联则直接返回\n            if QuerySet(ProblemParagraphMapping).filter(\n                knowledge_id=self.data.get('knowledge_id'),\n                document_id=self.data.get('document_id'),\n                paragraph_id=self.data.get('paragraph_id'),\n                problem_id=self.data.get('problem_id')\n            ).exists():\n                return True\n\n            problem = QuerySet(Problem).filter(id=self.data.get(\"problem_id\")).first()\n            problem_paragraph_mapping = ProblemParagraphMapping(id=uuid.uuid7(),\n                                                                document_id=self.data.get('document_id'),\n                                                                paragraph_id=self.data.get('paragraph_id'),\n                                                                knowledge_id=self.data.get('knowledge_id'),\n                                                                problem_id=problem.id)\n            problem_paragraph_mapping.save()\n            if with_embedding:\n                model_id = get_embedding_model_id_by_knowledge_id(self.data.get('knowledge_id'))\n                embedding_by_problem({\n                    'text': problem.content,\n                    'is_active': True,\n                    'source_type': SourceType.PROBLEM,\n                    'source_id': problem_paragraph_mapping.id,\n                    'document_id': self.data.get('document_id'),\n                    'paragraph_id': self.data.get('paragraph_id'),\n                    'knowledge_id': self.data.get('knowledge_id'),\n                }, model_id)\n\n        def un_association(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter(\n                paragraph_id=self.data.get('paragraph_id'),\n                knowledge_id=self.data.get('knowledge_id'),\n                problem_id=self.data.get(\n                    'problem_id')).first()\n            problem_paragraph_mapping_id = problem_paragraph_mapping.id\n            problem_paragraph_mapping.delete()\n            delete_embedding_by_source(problem_paragraph_mapping_id)\n            return True\n\n    class Batch(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        @transaction.atomic\n        def batch_delete(self, instance: Dict, with_valid=True):\n            if with_valid:\n                BatchSerializer(data=instance).is_valid(model=Paragraph, raise_exception=True)\n                self.is_valid(raise_exception=True)\n            paragraph_id_list = instance.get(\"id_list\")\n            QuerySet(Paragraph).filter(id__in=paragraph_id_list).delete()\n            delete_problems_and_mappings(paragraph_id_list)\n            update_document_char_length(self.data.get('document_id'))\n            # 删除向量库\n            delete_embedding_by_paragraph_ids(paragraph_id_list)\n            return True\n\n        def batch_generate_related(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            paragraph_id_list = instance.get(\"paragraph_id_list\")\n            model_id = instance.get(\"model_id\")\n            prompt = instance.get(\"prompt\")\n            model_params_setting = instance.get(\"model_params_setting\")\n            document_id = self.data.get('document_id')\n            ListenerManagement.update_status(\n                QuerySet(Document).filter(id=document_id),\n                TaskType.GENERATE_PROBLEM,\n                State.PENDING\n            )\n            ListenerManagement.update_status(\n                QuerySet(Paragraph).filter(id__in=paragraph_id_list),\n                TaskType.GENERATE_PROBLEM,\n                State.PENDING\n            )\n            ListenerManagement.get_aggregation_document_status(document_id)()\n            try:\n                generate_related_by_paragraph_id_list.delay(document_id, paragraph_id_list, model_id, model_params_setting, prompt)\n            except AlreadyQueued as e:\n                raise AppApiException(500, _('The task is being executed, please do not send it again.'))\n\n    class Migrate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n        target_knowledge_id = serializers.UUIDField(required=True, label=_('target knowledge id'))\n        target_document_id = serializers.UUIDField(required=True, label=_('target document id'))\n        paragraph_id_list = serializers.ListField(required=True, label=_('paragraph id list'),\n                                                  child=serializers.UUIDField(required=True, label=_('paragraph id')))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n            document_list = QuerySet(Document).filter(\n                id__in=[self.data.get('document_id'), self.data.get('target_document_id')])\n            document_id = self.data.get('document_id')\n            target_document_id = self.data.get('target_document_id')\n            if document_id == target_document_id:\n                raise AppApiException(5000, _('The document to be migrated is consistent with the target document'))\n            if len([document for document in document_list if str(document.id) == self.data.get('document_id')]) < 1:\n                raise AppApiException(5000, _('The document id does not exist [{document_id}]').format(\n                    document_id=self.data.get('document_id')))\n            if len([document for document in document_list if\n                    str(document.id) == self.data.get('target_document_id')]) < 1:\n                raise AppApiException(5000, _('The target document id does not exist [{document_id}]').format(\n                    document_id=self.data.get('target_document_id')))\n\n        @transaction.atomic\n        def migrate(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            knowledge_id = self.data.get('knowledge_id')\n            target_knowledge_id = self.data.get('target_knowledge_id')\n            document_id = self.data.get('document_id')\n            target_document_id = self.data.get('target_document_id')\n            paragraph_id_list = self.data.get('paragraph_id_list')\n            paragraph_list = QuerySet(Paragraph).filter(knowledge_id=knowledge_id, document_id=document_id,\n                                                        id__in=paragraph_id_list)\n            problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(paragraph__in=paragraph_list)\n            # 同数据集迁移\n            if target_knowledge_id == knowledge_id:\n                if len(problem_paragraph_mapping_list):\n                    problem_paragraph_mapping_list = [\n                        self.update_problem_paragraph_mapping(target_document_id,\n                                                              problem_paragraph_mapping) for problem_paragraph_mapping\n                        in\n                        problem_paragraph_mapping_list]\n                    # 修改mapping\n                    QuerySet(ProblemParagraphMapping).bulk_update(problem_paragraph_mapping_list,\n                                                                  ['document_id'])\n                update_embedding_document_id([paragraph.id for paragraph in paragraph_list],\n                                             target_document_id, target_knowledge_id, None)\n                # 修改段落信息\n                paragraph_list.update(document_id=target_document_id)\n\n                # 将当前文档中所有段落的位置向下移动，为新段落腾出空间\n                Paragraph.objects.filter(document_id=target_document_id).exclude(\n                    id__in=paragraph_id_list\n                ).update(position=F('position') + len(paragraph_id_list))\n                # 重新查询迁移的段落\n                paragraph_list = Paragraph.objects.filter(\n                    id__in=paragraph_id_list, document_id=target_document_id\n                )\n                # 将迁移的段落位置设置为从1开始的序号\n                for i, paragraph in enumerate(paragraph_list):\n                    paragraph.position = i + 1\n                    paragraph.save()\n            # 不同数据集迁移\n            else:\n                problem_list = QuerySet(Problem).filter(\n                    id__in=[problem_paragraph_mapping.problem_id for problem_paragraph_mapping in\n                            problem_paragraph_mapping_list])\n                # 目标数据集问题\n                target_problem_list = list(\n                    QuerySet(Problem).filter(content__in=[problem.content for problem in problem_list],\n                                             knowledge_id=target_knowledge_id))\n\n                target_handle_problem_list = [\n                    self.get_target_knowledge_problem(target_knowledge_id, target_document_id,\n                                                      problem_paragraph_mapping,\n                                                      problem_list, target_problem_list) for\n                    problem_paragraph_mapping\n                    in\n                    problem_paragraph_mapping_list]\n\n                create_problem_list = [problem for problem, is_create in target_handle_problem_list if\n                                       is_create is not None and is_create]\n                # 插入问题\n                QuerySet(Problem).bulk_create(create_problem_list)\n                # 修改mapping\n                QuerySet(ProblemParagraphMapping).bulk_update(problem_paragraph_mapping_list,\n                                                              ['problem_id', 'knowledge_id', 'document_id'])\n                target_knowledge = QuerySet(Knowledge).filter(id=target_knowledge_id).first()\n                knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first()\n                embedding_model_id = None\n                if target_knowledge.embedding_model_id != knowledge.embedding_model_id:\n                    embedding_model_id = str(target_knowledge.embedding_model_id)\n                pid_list = [paragraph.id for paragraph in paragraph_list]\n                # 修改段落信息\n                paragraph_list.update(knowledge_id=target_knowledge_id, document_id=target_document_id)\n\n                # 将当前文档中所有段落的位置向下移动，为新段落腾出空间\n                Paragraph.objects.filter(document_id=target_document_id).exclude(\n                    id__in=pid_list\n                ).update(position=F('position') + len(pid_list))\n                # 重新查询迁移的段落\n                paragraph_list = Paragraph.objects.filter(\n                    id__in=pid_list, document_id=target_document_id\n                )\n                # 将迁移的段落位置设置为从1开始的序号\n                for i, paragraph in enumerate(paragraph_list):\n                    paragraph.position = i + 1\n                    paragraph.save()\n                # 修改向量段落信息\n                update_embedding_document_id(pid_list, target_document_id, target_knowledge_id, embedding_model_id)\n\n            update_document_char_length(document_id)\n            update_document_char_length(target_document_id)\n\n        @staticmethod\n        def update_problem_paragraph_mapping(target_document_id: str, problem_paragraph_mapping):\n            problem_paragraph_mapping.document_id = target_document_id\n            return problem_paragraph_mapping\n\n        @staticmethod\n        def get_target_knowledge_problem(target_knowledge_id: str,\n                                         target_document_id: str,\n                                         problem_paragraph_mapping,\n                                         source_problem_list,\n                                         target_problem_list):\n            source_problem_list = [source_problem for source_problem in source_problem_list if\n                                   source_problem.id == problem_paragraph_mapping.problem_id]\n            problem_paragraph_mapping.knowledge_id = target_knowledge_id\n            problem_paragraph_mapping.document_id = target_document_id\n            if len(source_problem_list) > 0:\n                problem_content = source_problem_list[-1].content\n                problem_list = [problem for problem in target_problem_list if problem.content == problem_content]\n                if len(problem_list) > 0:\n                    problem = problem_list[-1]\n                    problem_paragraph_mapping.problem_id = problem.id\n                    return problem, False\n                else:\n                    problem = Problem(id=uuid.uuid7(), knowledge_id=target_knowledge_id, content=problem_content)\n                    target_problem_list.append(problem)\n                    problem_paragraph_mapping.problem_id = problem.id\n                    return problem, True\n            return None\n\n    class AdjustPosition(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        document_id = serializers.UUIDField(required=True, label=_('document id'))\n        paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        @transaction.atomic\n        def adjust_position(self, new_position):\n            \"\"\"\n            调整段落顺序\n            :param new_position: 新的顺序值\n            \"\"\"\n            self.is_valid(raise_exception=True)\n            try:\n                new_position = int(new_position)\n            except (TypeError, ValueError):\n                raise serializers.ValidationError(_('new_position must be an integer'))\n            # 获取当前段落\n            paragraph = Paragraph.objects.get(id=self.data.get('paragraph_id'))\n            old_position = paragraph.position\n\n            if old_position < new_position:\n                # 如果新顺序在当前顺序之后，更新受影响段落的顺序\n                Paragraph.objects.filter(\n                    position__gt=old_position, position__lte=new_position\n                ).update(position=F('position') - 1)\n            elif old_position > new_position:\n                # 如果新顺序在当前顺序之前，更新受影响段落的顺序\n                Paragraph.objects.filter(\n                    position__lt=old_position, position__gte=new_position\n                ).update(position=F('position') + 1)\n\n            # 更新当前段落的顺序\n            paragraph.position = new_position\n            paragraph.save()\n\n\ndef delete_problems_and_mappings(paragraph_ids):\n    problem_paragraph_mappings = ProblemParagraphMapping.objects.filter(paragraph_id__in=paragraph_ids)\n    problem_ids = set(problem_paragraph_mappings.values_list('problem_id', flat=True))\n\n    if problem_ids:\n        problem_paragraph_mappings.delete()\n        remaining_problem_counts = ProblemParagraphMapping.objects.filter(problem_id__in=problem_ids).values(\n            'problem_id').annotate(count=Count('problem_id'))\n        remaining_problem_ids = {pc['problem_id'] for pc in remaining_problem_counts}\n        problem_ids_to_delete = problem_ids - remaining_problem_ids\n        Problem.objects.filter(id__in=problem_ids_to_delete).delete()\n    else:\n        problem_paragraph_mappings.delete()\n"
  },
  {
    "path": "apps/knowledge/serializers/problem.py",
    "content": "import os\nfrom functools import reduce\nfrom typing import Dict, List\n\nimport uuid_utils.compat as uuid\nfrom django.db import transaction\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom common.db.search import native_search, native_page_search\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import get_file_content\nfrom knowledge.models import Problem, ProblemParagraphMapping, Paragraph, Knowledge, SourceType\nfrom knowledge.serializers.common import get_embedding_model_id_by_knowledge_id\nfrom knowledge.task.embedding import delete_embedding_by_source_ids, update_problem_embedding, embedding_by_data_list\nfrom maxkb.const import PROJECT_DIR\n\n\nclass ProblemSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Problem\n        fields = ['id', 'content', 'knowledge_id', 'create_time', 'update_time']\n\n\nclass ProblemInstanceSerializer(serializers.Serializer):\n    id = serializers.CharField(required=False, label=_('problem id'))\n    content = serializers.CharField(required=True, max_length=256, label=_('content'))\n\n\nclass ProblemEditSerializer(serializers.Serializer):\n    content = serializers.CharField(required=True, max_length=256, label=_('content'))\n\n\nclass ProblemMappingSerializer(serializers.Serializer):\n    paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id'))\n    document_id = serializers.UUIDField(required=True, label=_('document id'))\n\n\nclass ProblemBatchSerializer(serializers.Serializer):\n    problem_list = serializers.ListField(required=True, label=_('problem list'),\n                                         child=serializers.CharField(required=True, max_length=256, label=_('problem')))\n\n\nclass ProblemBatchDeleteSerializer(serializers.Serializer):\n    problem_id_list = serializers.ListField(required=True, label=_('problem id list'),\n                                            child=serializers.UUIDField(required=True, label=_('problem id')))\n\n\nclass AssociationParagraph(serializers.Serializer):\n    paragraph_id = serializers.UUIDField(required=True, label=_('paragraph id'))\n    document_id = serializers.UUIDField(required=True, label=_('document id'))\n\n\nclass BatchAssociation(serializers.Serializer):\n    problem_id_list = serializers.ListField(required=True, label=_('problem id list'),\n                                            child=serializers.UUIDField(required=True, label=_('problem id')))\n    paragraph_list = AssociationParagraph(many=True)\n\n\nclass ProblemSerializers(serializers.Serializer):\n    class BatchOperate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        def delete(self, problem_id_list: List, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            knowledge_id = self.data.get('knowledge_id')\n            problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(\n                knowledge_id=knowledge_id,\n                problem_id__in=problem_id_list)\n            source_ids = [row.id for row in problem_paragraph_mapping_list]\n            problem_paragraph_mapping_list.delete()\n            QuerySet(Problem).filter(id__in=problem_id_list).delete()\n            delete_embedding_by_source_ids(source_ids)\n            return True\n\n        def association(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                BatchAssociation(data=instance).is_valid(raise_exception=True)\n            knowledge_id = self.data.get('knowledge_id')\n            paragraph_list = instance.get('paragraph_list')\n            problem_id_list = instance.get('problem_id_list')\n            problem_list = QuerySet(Problem).filter(id__in=problem_id_list)\n\n            exits_problem_paragraph_mapping = QuerySet(\n                ProblemParagraphMapping\n            ).filter(problem_id__in=problem_id_list, paragraph_id__in=[p.get('paragraph_id') for p in paragraph_list])\n\n            problem_paragraph_mapping_list = [\n                (problem_paragraph_mapping, problem) for problem_paragraph_mapping, problem in\n                reduce(\n                    lambda x, y: [*x, *y],\n                    [\n                        [\n                            to_problem_paragraph_mapping(\n                                problem, paragraph.get('document_id'),\n                                paragraph.get('paragraph_id'),\n                                knowledge_id\n                            ) for paragraph in paragraph_list\n                        ] for problem in problem_list\n                    ],\n                    []\n                ) if not is_exits(exits_problem_paragraph_mapping, problem_paragraph_mapping)\n            ]\n\n            QuerySet(ProblemParagraphMapping).bulk_create(\n                [problem_paragraph_mapping for problem_paragraph_mapping, problem in problem_paragraph_mapping_list]\n            )\n\n            data_list = [\n                {\n                    'text': problem.content,\n                    'is_active': True,\n                    'source_type': SourceType.PROBLEM,\n                    'source_id': str(problem_paragraph_mapping.id),\n                    'document_id': str(problem_paragraph_mapping.document_id),\n                    'paragraph_id': str(problem_paragraph_mapping.paragraph_id),\n                    'knowledge_id': knowledge_id,\n                } for problem_paragraph_mapping, problem in problem_paragraph_mapping_list\n            ]\n            model_id = get_embedding_model_id_by_knowledge_id(self.data.get('knowledge_id'))\n            embedding_by_data_list(data_list, model_id=model_id)\n\n    class Operate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        problem_id = serializers.UUIDField(required=True, label=_('problem id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        def list_paragraph(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter(\n                knowledge_id=self.data.get(\"knowledge_id\"),\n                problem_id=self.data.get(\"problem_id\")\n            )\n            if problem_paragraph_mapping is None or len(problem_paragraph_mapping) == 0:\n                return []\n            return native_search(\n                QuerySet(Paragraph).filter(id__in=[row.paragraph_id for row in problem_paragraph_mapping]),\n                select_string=get_file_content(\n                    os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_paragraph.sql')))\n\n        def one(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            return ProblemInstanceSerializer(QuerySet(Problem).get(**{'id': self.data.get('problem_id')})).data\n\n        @transaction.atomic\n        def delete(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(\n                knowledge_id=self.data.get('knowledge_id'),\n                problem_id=self.data.get('problem_id'))\n            source_ids = [row.id for row in problem_paragraph_mapping_list]\n            problem_paragraph_mapping_list.delete()\n            QuerySet(Problem).filter(id=self.data.get('problem_id')).delete()\n            delete_embedding_by_source_ids(source_ids)\n            return True\n\n        @transaction.atomic\n        def edit(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            problem_id = self.data.get('problem_id')\n            knowledge_id = self.data.get('knowledge_id')\n            content = instance.get('content')\n            problem = QuerySet(Problem).filter(id=problem_id, knowledge_id=knowledge_id).first()\n            QuerySet(Knowledge).filter(id=knowledge_id)\n            problem.content = content\n            problem.save()\n            model_id = get_embedding_model_id_by_knowledge_id(knowledge_id)\n            update_problem_embedding(problem_id, content, model_id)\n\n    class Create(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        def batch(self, problem_list, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                ProblemBatchSerializer(data={'problem_list': problem_list}).is_valid(raise_exception=True)\n            problem_list = list(set(problem_list))\n            knowledge_id = self.data.get('knowledge_id')\n            exists_problem_content_list = [\n                problem.content for problem in QuerySet(\n                    Problem\n                ).filter(knowledge_id=knowledge_id, content__in=problem_list)\n            ]\n            problem_instance_list = [\n                Problem(\n                    id=uuid.uuid7(), knowledge_id=knowledge_id, content=problem_content\n                ) for problem_content in problem_list if (\n                    not exists_problem_content_list.__contains__(\n                        problem_content\n                    ) if len(exists_problem_content_list) > 0 else True\n                )\n            ]\n\n            QuerySet(Problem).bulk_create(problem_instance_list) if len(problem_instance_list) > 0 else None\n            return [ProblemSerializer(problem_instance).data for problem_instance in problem_instance_list]\n\n    class Query(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n        content = serializers.CharField(required=False, label=_('content'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        def get_query_set(self):\n            self.is_valid()\n            query_set = QuerySet(model=Problem)\n            query_set = query_set.filter(\n                **{'knowledge_id': self.data.get('knowledge_id')})\n            if 'content' in self.data:\n                query_set = query_set.filter(**{'content__icontains': self.data.get('content')})\n            query_set = query_set.order_by(\"-create_time\")\n            return query_set\n\n        def list(self):\n            query_set = self.get_query_set()\n            return native_search(query_set, select_string=get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_problem.sql')))\n\n        def page(self, current_page, page_size):\n            query_set = self.get_query_set()\n            return native_page_search(current_page, page_size, query_set, select_string=get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql', 'list_problem.sql')))\n\n\ndef is_exits(exits_problem_paragraph_mapping_list, new_paragraph_mapping):\n    filter_list = [exits_problem_paragraph_mapping for exits_problem_paragraph_mapping in\n                   exits_problem_paragraph_mapping_list if\n                   str(exits_problem_paragraph_mapping.paragraph_id) == new_paragraph_mapping.paragraph_id\n                   and str(exits_problem_paragraph_mapping.problem_id) == new_paragraph_mapping.problem_id\n                   and str(exits_problem_paragraph_mapping.knowledge_id) == new_paragraph_mapping.knowledge_id]\n    return len(filter_list) > 0\n\n\ndef to_problem_paragraph_mapping(problem, document_id: str, paragraph_id: str, knowledge_id: str):\n    return ProblemParagraphMapping(\n        id=uuid.uuid7(),\n        document_id=document_id,\n        paragraph_id=paragraph_id,\n        knowledge_id=knowledge_id,\n        problem_id=str(problem.id)\n    ), problem\n"
  },
  {
    "path": "apps/knowledge/serializers/tag.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：AI Assistant\n    @file： tag.py\n    @date：2025/10/13\n    @desc: 标签系统相关序列化器\n\"\"\"\nfrom collections import defaultdict\nfrom typing import Dict\n\nimport uuid_utils.compat as uuid\nfrom django.db import transaction\nfrom django.db.models import QuerySet\nfrom django.db.models.aggregates import Count\nfrom django.db.models.query_utils import Q\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom common.exception.app_exception import AppApiException\nfrom knowledge.models import Tag, Knowledge, DocumentTag\n\n\nclass TagModelSerializer(serializers.ModelSerializer):\n    \"\"\"标签模型序列化器\"\"\"\n\n    class Meta:\n        model = Tag\n        fields = ['id', 'knowledge_id', 'key', 'value', 'create_time', 'update_time']\n        read_only_fields = ['id', 'create_time', 'update_time']\n\n\nclass TagCreateSerializer(serializers.Serializer):\n    \"\"\"创建标签序列化器\"\"\"\n    key = serializers.CharField(required=True, max_length=64, label=_('Tag Key'))\n    value = serializers.CharField(required=True, max_length=128, label=_('Tag Value'))\n\n\nclass TagEditSerializer(serializers.Serializer):\n    key = serializers.CharField(required=False, max_length=64, label=_('Tag Key'))\n    value = serializers.CharField(required=False, max_length=128, label=_('Tag Value'))\n\n\nclass TagSerializers(serializers.Serializer):\n    class Create(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('Workspace ID'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID'))\n        tags = serializers.ListField(required=True, label=_('Tags'), child=TagCreateSerializer())\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id and workspace_id != 'None':\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        def insert(self):\n            self.is_valid(raise_exception=True)\n\n            knowledge_id = self.data.get('knowledge_id')\n\n            # 获取数据库中已存在的key-value组合\n            existing_tags = set(\n                QuerySet(Tag).filter(knowledge_id=knowledge_id)\n                .values_list('key', 'value', named=False)\n            )\n\n            # 过滤掉已存在的标签\n            tag_objects = []\n            for tag_data in self.data.get('tags', []):\n                key = tag_data.get('key')\n                value = tag_data.get('value')\n\n                # 检查key-value组合是否已存在\n                if (key, value) not in existing_tags:\n                    tag = Tag(\n                        id=uuid.uuid7(),\n                        knowledge_id=knowledge_id,\n                        key=key,\n                        value=value\n                    )\n                    tag_objects.append(tag)\n                    # 将新标签添加到已存在集合中，避免本次批量插入中的重复\n                    existing_tags.add((key, value))\n\n            # 批量插入未重复的标签\n            if tag_objects:\n                Tag.objects.bulk_create(tag_objects)\n\n    class Operate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('Workspace ID'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID'))\n        tag_id = serializers.UUIDField(required=True, label=_('Tag ID'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id and workspace_id != 'None':\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        @transaction.atomic\n        def edit(self, instance: Dict):\n            self.is_valid(raise_exception=True)\n            tag = QuerySet(Tag).get(id=self.data.get('tag_id'))\n            if tag is None:\n                raise AppApiException(500, _('Tag id does not exist'))\n\n            # 如果key发生变化，更新所有相同key的标签\n            if instance.get('key') and instance.get('key') != tag.key:\n                old_key = tag.key\n                new_key = instance.get('key')\n\n                # 检查新key是否已存在于同一个knowledge中\n                existing_key_exists = QuerySet(Tag).filter(\n                    knowledge_id=tag.knowledge_id,\n                    key=new_key\n                ).exists()\n\n                if existing_key_exists:\n                    raise AppApiException(500, _('Tag key already exists'))\n\n                # 批量更新所有具有相同old_key的标签\n                QuerySet(Tag).filter(\n                    knowledge_id=tag.knowledge_id,\n                    key=old_key\n                ).update(key=new_key)\n\n            # 如果只是value变化，只更新当前标签\n            if instance.get('value') and instance.get('value') != tag.value:\n                # 检查新key是否已存在于同一个knowledge中\n                existing_value_exists = QuerySet(Tag).filter(\n                    knowledge_id=tag.knowledge_id,\n                    key=instance.get('key'),\n                    value=instance.get('value')\n                ).exists()\n\n                if existing_value_exists:\n                    raise AppApiException(500, _('Tag value already exists'))\n                QuerySet(Tag).filter(\n                    id=tag.id\n                ).update(value=instance.get('value'))\n\n        @transaction.atomic\n        def delete(self, delete_type: str):\n            self.is_valid(raise_exception=True)\n            if delete_type == 'key':\n                # 删除同一knowledge_id下相同key的所有标签\n                tag = QuerySet(Tag).get(id=self.data.get('tag_id'))\n                if tag is None:\n                    raise AppApiException(500, _('Tag id does not exist'))\n                QuerySet(Tag).filter(\n                    knowledge_id=tag.knowledge_id,\n                    key=tag.key\n                ).delete()\n                QuerySet(DocumentTag).filter(tag_id=tag.id).delete()\n            else:\n                # 仅删除当前标签\n                QuerySet(Tag).filter(id=self.data.get('tag_id')).delete()\n                QuerySet(DocumentTag).filter(tag_id=self.data.get('tag_id')).delete()\n\n    class BatchDelete(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('Workspace ID'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID'))\n        tag_ids = serializers.ListField(required=True, label=_('Tag IDs'), child=serializers.UUIDField())\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id and workspace_id != 'None':\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        @transaction.atomic\n        def batch_delete(self):\n            self.is_valid(raise_exception=True)\n            tag_ids = self.data.get('tag_ids', [])\n            if not tag_ids:\n                return\n\n            # 获取要删除的标签的key\n            tags_to_delete = QuerySet(Tag).filter(id__in=tag_ids)\n            keys_to_delete = set(tags_to_delete.values_list('key', flat=True))\n\n            # 删除具有相同key的所有标签\n            QuerySet(Tag).filter(\n                knowledge_id=self.data.get('knowledge_id'),\n                key__in=keys_to_delete\n            ).delete()\n\n            # 删除关联的DocumentTag\n            QuerySet(DocumentTag).filter(tag_id__in=tag_ids).delete()\n\n    class Query(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('Workspace ID'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('Knowledge ID'))\n        name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('search value'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Knowledge).filter(id=self.data.get('knowledge_id'))\n            if workspace_id and workspace_id != 'None':\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Knowledge id does not exist'))\n\n        def list(self):\n            self.is_valid(raise_exception=True)\n            if self.data.get('name'):\n                name = self.data.get('name')\n                tags = QuerySet(Tag).filter(\n                    knowledge_id=self.data.get('knowledge_id')\n                ).filter(\n                    Q(key__icontains=name) | Q(value__icontains=name)\n                ).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value')\n            else:\n                # 获取所有标签，按创建时间排序保持稳定顺序\n                tags = QuerySet(Tag).filter(\n                    knowledge_id=self.data.get('knowledge_id')\n                ).values('key', 'value', 'id', 'create_time', 'update_time').order_by('create_time', 'key', 'value')\n\n            tag_ids = [tag['id'] for tag in tags]\n\n            tag_doc_count_map = {row['tag_id']: row['doc_count'] for row in\n                                 QuerySet(DocumentTag).filter(tag_id__in=tag_ids)\n                                 .values('tag_id').annotate(doc_count=Count('document_id'))\n                                 }\n\n            # 按key分组\n            grouped_tags = defaultdict(list)\n            for tag in tags:\n                grouped_tags[tag['key']].append({\n                    'id': tag['id'],\n                    'value': tag['value'],\n                    'doc_count': tag_doc_count_map.get(tag['id'],0),\n                    'create_time': tag['create_time'],\n                    'update_time': tag['update_time']\n                })\n\n            # 转换为期望的格式，保持key的顺序\n            result = []\n            # 按key排序以确保结果顺序一致\n            for key in sorted(grouped_tags.keys()):\n                values = grouped_tags[key]\n                # 按创建时间对values进行排序\n                values.sort(key=lambda x: x['create_time'])\n                result.append({\n                    'key': key,\n                    'values': values,\n                })\n\n            return result\n"
  },
  {
    "path": "apps/knowledge/sql/blend_search.sql",
    "content": "SELECT\n\tparagraph_id,\n\tcomprehensive_score,\n\tcomprehensive_score AS similarity\nFROM\n\t(\n\tSELECT DISTINCT ON\n\t\t( \"paragraph_id\" ) ( 1 - distance + ts_similarity ) as similarity, *,\n\t\t(1 - distance + ts_similarity) AS comprehensive_score\n\tFROM\n\t\t(\n\t\tSELECT\n\t\t\t*,\n\t\t\t(embedding.embedding::vector(%s) <=>  %s) as distance,\n\t\t\t(ts_rank_cd( embedding.search_vector, websearch_to_tsquery('simple', %s ), 32 )) AS ts_similarity\n\t\tFROM\n\t\t\tembedding ${embedding_query}\n\t\t    ORDER BY distance\n\t\t) TEMP\n\tORDER BY\n\t\tparagraph_id,\n\t\tsimilarity DESC\n\t) DISTINCT_TEMP\nWHERE\n\tcomprehensive_score >%s\nORDER BY\n\tcomprehensive_score DESC\n\tLIMIT %s"
  },
  {
    "path": "apps/knowledge/sql/embedding_search.sql",
    "content": "SELECT\n    paragraph_id,\n\tcomprehensive_score,\n\tcomprehensive_score as similarity\nFROM\n\t(\n\tSELECT DISTINCT ON\n\t\t(\"paragraph_id\") ( 1 - distance ),* ,(1 - distance) AS comprehensive_score\n\tFROM\n\t\t( SELECT *, ( embedding.embedding::vector(%s) <=>  %s ) AS distance FROM embedding ${embedding_query} ORDER BY distance) TEMP\n\tORDER BY\n\t\tparagraph_id,\n\t\tdistance\n\t) DISTINCT_TEMP\nWHERE comprehensive_score>%s\nORDER BY comprehensive_score DESC\nLIMIT %s"
  },
  {
    "path": "apps/knowledge/sql/hit_test.sql",
    "content": "SELECT\n\tparagraph_id,\n\tcomprehensive_score,\n\tcomprehensive_score as similarity\nFROM\n\t(\n\tSELECT DISTINCT ON\n\t\t(\"paragraph_id\") ( similarity ),* ,similarity AS comprehensive_score\n\tFROM\n\t\t( SELECT *, ( 1 - ( embedding.embedding <=>  %s ) ) AS similarity FROM embedding ${embedding_query} ) TEMP\n\tORDER BY\n\t\tparagraph_id,\n\t\tsimilarity DESC\n\t) DISTINCT_TEMP\nWHERE comprehensive_score>%s\nORDER BY comprehensive_score DESC\nLIMIT %s"
  },
  {
    "path": "apps/knowledge/sql/keywords_search.sql",
    "content": "SELECT\n    paragraph_id,\n\tcomprehensive_score,\n\tcomprehensive_score as similarity\nFROM\n\t(\n\tSELECT DISTINCT ON\n\t\t(\"paragraph_id\") ( similarity ),* ,similarity AS comprehensive_score\n\tFROM\n\t\t( SELECT *,ts_rank_cd(embedding.search_vector,websearch_to_tsquery('simple',%s),32) AS similarity  FROM embedding ${keywords_query}) TEMP\n\tORDER BY\n\t\tparagraph_id,\n\t\tsimilarity DESC\n\t) DISTINCT_TEMP\nWHERE comprehensive_score>%s\nORDER BY comprehensive_score DESC\nLIMIT %s"
  },
  {
    "path": "apps/knowledge/sql/list_document.sql",
    "content": "SELECT * from (\nSELECT\n    \"document\".*,\n    to_json(\"document\".\"meta\") as meta,\n    to_json(\"document\".\"status_meta\") as status_meta,\n    (SELECT \"count\"(\"id\") FROM \"paragraph\" WHERE document_id = \"document\".\"id\") as \"paragraph_count\",\n    tag_agg.tag_count as \"tag_count\",\n    COALESCE(tag_agg.tags, '[]'::json) as \"tags\"\nFROM\n    \"document\" \"document\"\nLEFT JOIN LATERAL (\n    SELECT\n        COUNT(*)::int as tag_count,\n        json_agg(\n            json_build_object(\n                'id', \"tag\".\"id\",\n                'key', \"tag\".\"key\",\n                'value', \"tag\".\"value\"\n            )\n            ORDER BY \"tag\".\"key\", \"tag\".\"value\"\n        ) as tags\n    FROM \"document_tag\" \"document_tag\"\n    INNER JOIN \"tag\" \"tag\" ON \"tag\".\"id\" = \"document_tag\".\"tag_id\"\n    WHERE \"document_tag\".\"document_id\" = \"document\".\"id\"\n) tag_agg ON TRUE\n${document_custom_sql}\n) temp\n${order_by_query}"
  },
  {
    "path": "apps/knowledge/sql/list_knowledge.sql",
    "content": "SELECT *\nFROM (SELECT \"temp_knowledge\".id::text, \"temp_knowledge\".name,\n             \"temp_knowledge\".desc,\n             \"temp_knowledge\".type,\n             'knowledge'        as resource_type,\n             \"temp_knowledge\".workspace_id,\n             \"temp_knowledge\".folder_id,\n             \"temp_knowledge\".user_id,\n             \"user\".\"nick_name\" as nick_name,\n             \"temp_knowledge\".create_time,\n             \"temp_knowledge\".update_time,\n             \"temp_knowledge\".file_size_limit,\n             \"temp_knowledge\".file_count_limit,\n             \"temp_knowledge\".\"scope\",\n             \"temp_knowledge\".\"embedding_model_id\"::text, \"document_temp\".\"char_length\",\n             to_json(\"temp_knowledge\".meta)::jsonb       as meta, CASE\n                                                                      WHEN\n                                                                          \"app_knowledge_temp\".\"count\" IS NULL THEN 0\n                                                                      ELSE \"app_knowledge_temp\".\"count\" END AS application_mapping_count,\n             \"document_temp\".document_count\n      FROM (SELECT knowledge.*\n            FROM knowledge knowledge ${knowledge_custom_sql}) temp_knowledge\n               LEFT JOIN (SELECT \"count\"(\"id\") AS document_count, \"sum\"(\"char_length\") \"char_length\", knowledge_id\n                          FROM \"document\"\n                          GROUP BY knowledge_id) \"document_temp\" ON temp_knowledge.\"id\" = \"document_temp\".knowledge_id\n               LEFT JOIN (SELECT \"count\"(\"id\"), knowledge_id\n                          FROM application_knowledge_mapping\n                          GROUP BY knowledge_id) app_knowledge_temp\n                         ON temp_knowledge.\"id\" = \"app_knowledge_temp\".knowledge_id\n               left join \"user\" on \"user\".id = temp_knowledge.user_id\n      ) temp\n    ${default_sql}"
  },
  {
    "path": "apps/knowledge/sql/list_knowledge_application.sql",
    "content": "SELECT\n\t*\nFROM\n\tapplication\nWHERE\n\tuser_id = %s\nUNION\nSELECT\n\t*\nFROM\n\tapplication\nWHERE\n\t\"id\"::text in (select target from workspace_user_resource_permission where auth_target_type = 'APPLICATION' and 'VIEW' = any (permission_list))"
  },
  {
    "path": "apps/knowledge/sql/list_knowledge_user.sql",
    "content": "SELECT *\nFROM (SELECT \"temp_knowledge\".id::text, \"temp_knowledge\".name,\n             \"temp_knowledge\".desc,\n             \"temp_knowledge\".type,\n             'knowledge'                               as resource_type,\n             \"temp_knowledge\".workspace_id,\n             \"temp_knowledge\".folder_id,\n             \"temp_knowledge\".user_id,\n             \"user\".\"nick_name\"                        as nick_name,\n             \"temp_knowledge\".create_time,\n             \"temp_knowledge\".update_time,\n             \"temp_knowledge\".file_size_limit,\n             \"temp_knowledge\".file_count_limit,\n             \"temp_knowledge\".\"scope\",\n             \"temp_knowledge\".\"embedding_model_id\"::text,\n             \"document_temp\".\"char_length\",\n             to_json(\"temp_knowledge\".meta)::jsonb       as meta,\n             CASE\n                 WHEN\n                     \"app_knowledge_temp\".\"count\" IS NULL THEN 0\n                 ELSE \"app_knowledge_temp\".\"count\" END AS application_mapping_count,\n             \"document_temp\".document_count\n      FROM (SELECT knowledge.*\n            FROM knowledge knowledge ${knowledge_custom_sql}\n            AND id::text in (select target\n                   from workspace_user_resource_permission ${workspace_user_resource_permission_query_set}\n                and 'VIEW' = any (permission_list))) temp_knowledge\n               LEFT JOIN (SELECT \"count\"(\"id\") AS document_count, \"sum\"(\"char_length\") \"char_length\", knowledge_id\n                          FROM \"document\"\n                          GROUP BY knowledge_id) \"document_temp\" ON temp_knowledge.\"id\" = \"document_temp\".knowledge_id\n               LEFT JOIN (SELECT \"count\"(\"id\"), knowledge_id\n                          FROM application_knowledge_mapping\n                          GROUP BY knowledge_id) app_knowledge_temp\n                         ON temp_knowledge.\"id\" = \"app_knowledge_temp\".knowledge_id\n               left join \"user\" on \"user\".id = temp_knowledge.user_id\n      ) temp\n    ${default_sql}"
  },
  {
    "path": "apps/knowledge/sql/list_knowledge_user_ee.sql",
    "content": "SELECT *\nFROM (SELECT \"temp_knowledge\".id::text, \"temp_knowledge\".name,\n             \"temp_knowledge\".desc,\n             \"temp_knowledge\".type,\n             'knowledge'                               as resource_type,\n             \"temp_knowledge\".workspace_id,\n             \"temp_knowledge\".folder_id,\n             \"temp_knowledge\".user_id,\n             \"user\".\"nick_name\"                        as nick_name,\n             \"temp_knowledge\".create_time,\n             \"temp_knowledge\".update_time,\n             \"temp_knowledge\".file_size_limit,\n             \"temp_knowledge\".file_count_limit,\n             \"temp_knowledge\".\"scope\",\n             \"temp_knowledge\".\"embedding_model_id\"::text,\n             \"document_temp\".\"char_length\",\n             to_json(\"temp_knowledge\".meta)::jsonb       as meta,\n             CASE\n                 WHEN\n                     \"app_knowledge_temp\".\"count\" IS NULL THEN 0\n                 ELSE \"app_knowledge_temp\".\"count\" END AS application_mapping_count,\n             \"document_temp\".document_count\n      FROM (SELECT knowledge.*\n            FROM knowledge knowledge ${knowledge_custom_sql}\n            AND \"knowledge\".id::text in (select target\n                   from workspace_user_resource_permission\n                   ${workspace_user_resource_permission_query_set}\n                     and case\n                             when auth_type = 'ROLE' then\n                                 'ROLE' = any (permission_list)\n                                  and\n                                 'KNOWLEDGE:READ' in (select (case when user_role_relation.role_id = any (array ['USER']) THEN 'KNOWLEDGE:READ' else role_permission.permission_id END)\n                                                        from role_permission role_permission\n                                                        right join user_role_relation user_role_relation\n                                                            on user_role_relation.role_id=role_permission.role_id\n                                                        where user_role_relation.user_id=workspace_user_resource_permission.user_id\n                                                        and  user_role_relation.workspace_id=workspace_user_resource_permission.workspace_id)\n                             else\n                                 'VIEW' = any (permission_list)\n                       end\n            )) temp_knowledge\n               LEFT JOIN (SELECT \"count\"(\"id\") AS document_count, \"sum\"(\"char_length\") \"char_length\", knowledge_id\n                          FROM \"document\"\n                          GROUP BY knowledge_id) \"document_temp\" ON temp_knowledge.\"id\" = \"document_temp\".knowledge_id\n               LEFT JOIN (SELECT \"count\"(\"id\"), knowledge_id\n                          FROM application_knowledge_mapping\n                          GROUP BY knowledge_id) app_knowledge_temp\n                         ON temp_knowledge.\"id\" = \"app_knowledge_temp\".knowledge_id\n               left join \"user\" on \"user\".id = temp_knowledge.user_id\n      ) temp\n    ${default_sql}"
  },
  {
    "path": "apps/knowledge/sql/list_paragraph.sql",
    "content": "SELECT\n    (SELECT \"name\" FROM \"document\" WHERE \"id\"=document_id) as document_name,\n\t(SELECT \"name\" FROM \"knowledge\" WHERE \"id\"=knowledge_id) as knowledge_name,\n\t*\nFROM\n\t\"paragraph\"\n"
  },
  {
    "path": "apps/knowledge/sql/list_paragraph_document_name.sql",
    "content": "SELECT\n\t(SELECT \"name\" FROM \"document\" WHERE \"id\"=document_id) as document_name,\n\t*\nFROM\n\t\"paragraph\"\n"
  },
  {
    "path": "apps/knowledge/sql/list_problem.sql",
    "content": "SELECT problem.*,\n       (SELECT COUNT(ppm.id)\n        FROM problem_paragraph_mapping ppm\n                 INNER JOIN paragraph p ON ppm.paragraph_id = p.id\n        WHERE ppm.problem_id = problem.id) AS \"paragraph_count\"\nFROM problem problem\n"
  },
  {
    "path": "apps/knowledge/sql/list_problem_mapping.sql",
    "content": "SELECT \"problem\".\"content\",problem_paragraph_mapping.paragraph_id FROM problem problem\nLEFT JOIN problem_paragraph_mapping problem_paragraph_mapping ON problem_paragraph_mapping.problem_id=problem.\"id\""
  },
  {
    "path": "apps/knowledge/sql/update_document_char_length.sql",
    "content": "UPDATE \"document\"\nSET \"char_length\" = ( SELECT CASE WHEN\n\t\t\"sum\" ( \"char_length\" ( \"content\" ) ) IS NULL THEN\n\t\t\t0 ELSE \"sum\" ( \"char_length\" ( \"content\" ) )\n\t\tEND FROM paragraph WHERE \"document_id\" = %s )\nWHERE\n\t\"id\" = %s"
  },
  {
    "path": "apps/knowledge/sql/update_document_status_meta.sql",
    "content": "UPDATE \"document\" \"document\"\nSET status_meta = jsonb_set ( \"document\".status_meta, '{aggs}', tmp.status_meta )\nFROM\n\t(\n\tSELECT COALESCE\n\t\t( jsonb_agg ( jsonb_delete ( ( row_to_json ( record ) :: JSONB ), 'document_id' ) ), '[]' :: JSONB ) AS status_meta,\n\t\tdocument_id AS document_id\n\tFROM\n\t\t(\n\t\tSELECT\n\t\t\t\"paragraph\".status,\n\t\t\t\"count\" ( \"paragraph\".\"id\" ),\n\t\t\t\"document\".\"id\" AS document_id\n\t\tFROM\n\t\t\t\"document\" \"document\"\n\t\t\tLEFT JOIN \"paragraph\" \"paragraph\" ON \"document\".\"id\" = paragraph.document_id\n\t\t${document_custom_sql}\n\t\tGROUP BY\n\t\t\t\"paragraph\".status,\n\t\t\t\"document\".\"id\"\n\t\t) record\n\tGROUP BY\n\t\tdocument_id\n\t) tmp\nWHERE \"document\".id=\"tmp\".document_id"
  },
  {
    "path": "apps/knowledge/sql/update_paragraph_status.sql",
    "content": "UPDATE \"${table_name}\"\nSET status = reverse (\n\tSUBSTRING ( reverse ( LPAD( status, ${bit_number}, 'n' ) ) :: TEXT FROM 1 FOR ${up_index} ) || ${status_number} || SUBSTRING ( reverse ( LPAD( status, ${bit_number}, 'n' ) ) :: TEXT FROM ${next_index} )\n),\nstatus_meta = jsonb_set (\n\t\t\"${table_name}\".status_meta,\n\t\t'{state_time,${current_index}}',\n\t\tjsonb_set (\n\t\t\tCOALESCE ( \"${table_name}\".status_meta #> '{state_time,${current_index}}', jsonb_build_object ( '${status_number}', '${current_time}' ) ),\n\t\t\t'{${status_number}}',\n\t\t\tCONCAT ( '\"', '${current_time}', '\"' ) :: JSONB\n\t\t)\n\t)"
  },
  {
    "path": "apps/knowledge/task/__init__.py",
    "content": ""
  },
  {
    "path": "apps/knowledge/task/embedding.py",
    "content": "# coding=utf-8\n\nimport traceback\nfrom typing import List\n\nfrom celery_once import QueueOnce\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.config.embedding_config import ModelManage\nfrom common.event.listener_manage import ListenerManagement, UpdateProblemArgs, UpdateEmbeddingKnowledgeIdArgs, \\\n    UpdateEmbeddingDocumentIdArgs\nfrom common.utils.logger import maxkb_logger\nfrom knowledge.models import Document, TaskType, State\nfrom knowledge.serializers.common import drop_knowledge_index\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model, get_model_default_params\nfrom ops import celery_app\n\n\ndef get_embedding_model(model_id, exception_handler=lambda e: maxkb_logger.error(\n    _('Failed to obtain vector model: {error} {traceback}').format(\n        error=str(e),\n        traceback=traceback.format_exc()\n    ))):\n    try:\n        model = QuerySet(Model).filter(id=model_id).first()\n\n        default_params = get_model_default_params(model)\n\n        embedding_model = ModelManage.get_model(model_id, lambda _id: get_model(model, **{**default_params}))\n    except Exception as e:\n        exception_handler(e)\n        raise e\n    return embedding_model\n\n\n@celery_app.task(base=QueueOnce, once={'keys': ['paragraph_id']}, name='celery:embedding_by_paragraph')\ndef embedding_by_paragraph(paragraph_id, model_id):\n    embedding_model = get_embedding_model(model_id)\n    ListenerManagement.embedding_by_paragraph(paragraph_id, embedding_model)\n\n\n@celery_app.task(base=QueueOnce, once={'keys': ['paragraph_id_list']}, name='celery:embedding_by_paragraph_data_list')\ndef embedding_by_paragraph_data_list(data_list, paragraph_id_list, model_id):\n    embedding_model = get_embedding_model(model_id)\n    ListenerManagement.embedding_by_paragraph_data_list(data_list, paragraph_id_list, embedding_model)\n\n\n@celery_app.task(base=QueueOnce, once={'keys': ['paragraph_id_list']}, name='celery:embedding_by_paragraph_list')\ndef embedding_by_paragraph_list(paragraph_id_list, model_id):\n    embedding_model = get_embedding_model(model_id)\n    ListenerManagement.embedding_by_paragraph_list(paragraph_id_list, embedding_model)\n\n\n@celery_app.task(base=QueueOnce, once={'keys': ['document_id']}, name='celery:embedding_by_document')\ndef embedding_by_document(document_id, model_id, state_list=None):\n    \"\"\"\n    向量化文档\n    @param state_list:\n    @param document_id: 文档id\n    @param model_id 向量模型\n    :return: None\n    \"\"\"\n\n    if state_list is None:\n        state_list = [State.PENDING.value, State.STARTED.value, State.SUCCESS.value, State.FAILURE.value,\n                      State.REVOKE.value,\n                      State.REVOKED.value, State.IGNORED.value]\n\n    def exception_handler(e):\n        ListenerManagement.update_status(QuerySet(Document).filter(id=document_id), TaskType.EMBEDDING,\n                                         State.FAILURE)\n        maxkb_logger.error(\n            _('Failed to obtain vector model: {error} {traceback}').format(\n                error=str(e),\n                traceback=traceback.format_exc()\n            ))\n\n    embedding_model = get_embedding_model(model_id, exception_handler)\n    #\n    ListenerManagement.embedding_by_document(document_id, embedding_model, state_list)\n\n\n@celery_app.task(name='celery:embedding_by_document_list')\ndef embedding_by_document_list(document_id_list, model_id):\n    \"\"\"\n    向量化文档\n    @param document_id_list: 文档id列表\n    @param model_id 向量模型\n    :return: None\n    \"\"\"\n    for document_id in document_id_list:\n        embedding_by_document.delay(document_id, model_id)\n\n\n@celery_app.task(base=QueueOnce, once={'keys': ['knowledge_id']}, name='celery:embedding_by_knowledge')\ndef embedding_by_knowledge(knowledge_id, model_id):\n    \"\"\"\n          向量化知识库\n          @param knowledge_id: 知识库id\n          @param model_id 向量模型\n          :return: None\n          \"\"\"\n    maxkb_logger.info(_('Start--->Vectorized knowledge: {knowledge_id}').format(knowledge_id=knowledge_id))\n    try:\n        ListenerManagement.delete_embedding_by_knowledge(knowledge_id)\n        drop_knowledge_index(knowledge_id=knowledge_id)\n        document_list = QuerySet(Document).filter(knowledge_id=knowledge_id)\n        maxkb_logger.info(_('Knowledge documentation: {document_names}').format(\n            document_names=\", \".join([d.name for d in document_list])))\n        for document in document_list:\n            try:\n                embedding_by_document.delay(document.id, model_id)\n            except Exception as e:\n                pass\n    except Exception as e:\n        maxkb_logger.error(\n            _('Vectorized knowledge: {knowledge_id} error {error} {traceback}'.format(knowledge_id=knowledge_id,\n                                                                                      error=str(e),\n                                                                                      traceback=traceback.format_exc())))\n    finally:\n        maxkb_logger.info(_('End--->Vectorized knowledge: {knowledge_id}').format(knowledge_id=knowledge_id))\n\n\ndef embedding_by_problem(args, model_id):\n    \"\"\"\n    向量话问题\n    @param args:    问题对象\n    @param model_id: 模型id\n    @return:\n    \"\"\"\n    embedding_model = get_embedding_model(model_id)\n    ListenerManagement.embedding_by_problem(args, embedding_model)\n\n\ndef embedding_by_data_list(args: List, model_id):\n    embedding_model = get_embedding_model(model_id)\n    ListenerManagement.embedding_by_data_list(args, embedding_model)\n\n\ndef delete_embedding_by_document(document_id):\n    \"\"\"\n    删除指定文档id的向量\n    @param document_id: 文档id\n    @return: None\n    \"\"\"\n\n    ListenerManagement.delete_embedding_by_document(document_id)\n\n\ndef delete_embedding_by_document_list(document_id_list: List[str]):\n    \"\"\"\n    删除指定文档列表的向量数据\n    @param document_id_list: 文档id列表\n    @return: None\n    \"\"\"\n    ListenerManagement.delete_embedding_by_document_list(document_id_list)\n\n\ndef delete_embedding_by_knowledge(knowledge_id):\n    \"\"\"\n    删除指定数据集向量数据\n    @param knowledge_id: 数据集id\n    @return: None\n    \"\"\"\n    ListenerManagement.delete_embedding_by_knowledge(knowledge_id)\n\n\ndef delete_embedding_by_paragraph(paragraph_id):\n    \"\"\"\n    删除指定段落的向量数据\n    @param paragraph_id: 段落id\n    @return: None\n    \"\"\"\n    ListenerManagement.delete_embedding_by_paragraph(paragraph_id)\n\n\ndef delete_embedding_by_source(source_id):\n    \"\"\"\n    删除指定资源id的向量数据\n    @param source_id: 资源id\n    @return: None\n    \"\"\"\n    ListenerManagement.delete_embedding_by_source(source_id)\n\n\ndef disable_embedding_by_paragraph(paragraph_id):\n    \"\"\"\n    禁用某个段落id的向量\n    @param paragraph_id: 段落id\n    @return: None\n    \"\"\"\n    ListenerManagement.disable_embedding_by_paragraph(paragraph_id)\n\n\ndef enable_embedding_by_paragraph(paragraph_id):\n    \"\"\"\n    开启某个段落id的向量数据\n    @param paragraph_id: 段落id\n    @return: None\n    \"\"\"\n    ListenerManagement.enable_embedding_by_paragraph(paragraph_id)\n\n\ndef delete_embedding_by_source_ids(source_ids: List[str]):\n    \"\"\"\n    删除向量根据source_id_list\n    @param source_ids:\n    @return:\n    \"\"\"\n    ListenerManagement.delete_embedding_by_source_ids(source_ids)\n\n\ndef update_problem_embedding(problem_id: str, problem_content: str, model_id):\n    \"\"\"\n    更新问题\n    @param problem_id:\n    @param problem_content:\n    @param model_id:\n    @return:\n    \"\"\"\n    model = get_embedding_model(model_id)\n    ListenerManagement.update_problem(UpdateProblemArgs(problem_id, problem_content, model))\n\n\ndef update_embedding_knowledge_id(paragraph_id_list, target_knowledge_id):\n    \"\"\"\n    修改向量数据到指定知识库\n    @param paragraph_id_list: 指定段落的向量数据\n    @param target_knowledge_id: 知识库id\n    @return:\n    \"\"\"\n\n    ListenerManagement.update_embedding_knowledge_id(\n        UpdateEmbeddingKnowledgeIdArgs(paragraph_id_list, target_knowledge_id))\n\n\ndef delete_embedding_by_paragraph_ids(paragraph_ids: List[str]):\n    \"\"\"\n    删除指定段落列表的向量数据\n    @param paragraph_ids: 段落列表\n    @return: None\n    \"\"\"\n    ListenerManagement.delete_embedding_by_paragraph_ids(paragraph_ids)\n\n\ndef update_embedding_document_id(paragraph_id_list, target_document_id, target_knowledge_id,\n                                 target_embedding_model_id=None):\n    target_embedding_model = get_embedding_model(\n        target_embedding_model_id) if target_embedding_model_id is not None else None\n    ListenerManagement.update_embedding_document_id(\n        UpdateEmbeddingDocumentIdArgs(paragraph_id_list, target_document_id, target_knowledge_id,\n                                      target_embedding_model))\n\n\ndef delete_embedding_by_knowledge_id_list(knowledge_id_list):\n    ListenerManagement.delete_embedding_by_knowledge_id_list(knowledge_id_list)\n"
  },
  {
    "path": "apps/knowledge/task/generate.py",
    "content": "import traceback\n\nfrom celery_once import QueueOnce\nfrom django.db.models import QuerySet\nfrom django.db.models.functions import Reverse, Substr\nfrom django.utils.translation import gettext_lazy as _\nfrom langchain_core.messages import HumanMessage\n\nfrom common.config.embedding_config import ModelManage\nfrom common.event.listener_manage import ListenerManagement\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.page_utils import page, page_desc\nfrom knowledge.models import Paragraph, Document, Status, TaskType, State\nfrom knowledge.task.handler import save_problem\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model\nfrom ops import celery_app\n\n\ndef get_llm_model(model_id, model_params_setting=None):\n    model = QuerySet(Model).filter(id=model_id).first()\n    return ModelManage.get_model(model_id, lambda _id: get_model(model, **(model_params_setting or {})))\n\n\ndef generate_problem_by_paragraph(paragraph, llm_model, prompt):\n    try:\n        ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph.id), TaskType.GENERATE_PROBLEM,\n                                         State.STARTED)\n        res = llm_model.invoke(\n            [HumanMessage(content=prompt.replace('{data}', paragraph.content).replace('{title}', paragraph.title))])\n        if (res.content is None) or (len(res.content) == 0):\n            return\n        problems = res.content.split('\\n')\n        for problem in problems:\n            save_problem(paragraph.knowledge_id, paragraph.document_id, paragraph.id, problem)\n        ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph.id), TaskType.GENERATE_PROBLEM,\n                                         State.SUCCESS)\n    except Exception as e:\n        ListenerManagement.update_status(QuerySet(Paragraph).filter(id=paragraph.id), TaskType.GENERATE_PROBLEM,\n                                         State.FAILURE)\n\n\ndef get_generate_problem(llm_model, prompt, post_apply=lambda: None, is_the_task_interrupted=lambda: False):\n    def generate_problem(paragraph_list):\n        for paragraph in paragraph_list:\n            if is_the_task_interrupted():\n                return\n            generate_problem_by_paragraph(paragraph, llm_model, prompt)\n            post_apply()\n\n    return generate_problem\n\n\ndef get_is_the_task_interrupted(document_id):\n    def is_the_task_interrupted():\n        document = QuerySet(Document).filter(id=document_id).first()\n        if document is None or Status(document.status)[TaskType.GENERATE_PROBLEM] == State.REVOKE:\n            return True\n        return False\n\n    return is_the_task_interrupted\n\n\n@celery_app.task(base=QueueOnce, once={'keys': ['knowledge_id']},\n                 name='celery:generate_related_by_knowledge')\ndef generate_related_by_knowledge_id(knowledge_id, model_id, model_params_setting, prompt, state_list=None):\n    document_list = QuerySet(Document).filter(knowledge_id=knowledge_id)\n    for document in document_list:\n        try:\n            generate_related_by_document_id.delay(document.id, model_id, model_params_setting, prompt, state_list)\n        except Exception as e:\n            pass\n\n\n@celery_app.task(base=QueueOnce, once={'keys': ['document_id']},\n                 name='celery:generate_related_by_document')\ndef generate_related_by_document_id(document_id, model_id, model_params_setting, prompt, state_list=None):\n    if state_list is None:\n        state_list = [State.PENDING.value, State.STARTED.value, State.SUCCESS.value, State.FAILURE.value,\n                      State.REVOKE.value,\n                      State.REVOKED.value, State.IGNORED.value]\n    try:\n        is_the_task_interrupted = get_is_the_task_interrupted(document_id)\n        if is_the_task_interrupted():\n            return\n        ListenerManagement.update_status(QuerySet(Document).filter(id=document_id),\n                                         TaskType.GENERATE_PROBLEM,\n                                         State.STARTED)\n        llm_model = get_llm_model(model_id, model_params_setting)\n\n        # 生成问题函数\n        generate_problem = get_generate_problem(llm_model, prompt,\n                                                ListenerManagement.get_aggregation_document_status(\n                                                    document_id), is_the_task_interrupted)\n        query_set = QuerySet(Paragraph).annotate(\n            reversed_status=Reverse('status'),\n            task_type_status=Substr('reversed_status', TaskType.GENERATE_PROBLEM.value,\n                                    1),\n        ).filter(task_type_status__in=state_list, document_id=document_id)\n        page_desc(query_set, 10, generate_problem, is_the_task_interrupted)\n    except Exception as e:\n        maxkb_logger.error(f'根据文档生成问题:{document_id}出现错误{str(e)}{traceback.format_exc()}')\n        maxkb_logger.error(_('Generate issue based on document: {document_id} error {error}{traceback}').format(\n            document_id=document_id, error=str(e), traceback=traceback.format_exc()))\n    finally:\n        ListenerManagement.post_update_document_status(document_id, TaskType.GENERATE_PROBLEM)\n        maxkb_logger.info(_('End--->Generate problem: {document_id}').format(document_id=document_id))\n\n\n@celery_app.task(base=QueueOnce, once={'keys': ['paragraph_id_list']},\n                 name='celery:generate_related_by_paragraph_list')\ndef generate_related_by_paragraph_id_list(document_id, paragraph_id_list, model_id, model_params_setting, prompt):\n    try:\n        is_the_task_interrupted = get_is_the_task_interrupted(document_id)\n        if is_the_task_interrupted():\n            ListenerManagement.update_status(QuerySet(Document).filter(id=document_id),\n                                             TaskType.GENERATE_PROBLEM,\n                                             State.REVOKED)\n            return\n        ListenerManagement.update_status(QuerySet(Document).filter(id=document_id),\n                                         TaskType.GENERATE_PROBLEM,\n                                         State.STARTED)\n        llm_model = get_llm_model(model_id, model_params_setting)\n        # 生成问题函数\n        generate_problem = get_generate_problem(llm_model, prompt, ListenerManagement.get_aggregation_document_status(\n            document_id))\n\n        def is_the_task_interrupted():\n            document = QuerySet(Document).filter(id=document_id).first()\n            if document is None or Status(document.status)[TaskType.GENERATE_PROBLEM] == State.REVOKE:\n                return True\n            return False\n\n        page(QuerySet(Paragraph).filter(id__in=paragraph_id_list), 10, generate_problem, is_the_task_interrupted)\n    finally:\n        ListenerManagement.post_update_document_status(document_id, TaskType.GENERATE_PROBLEM)\n"
  },
  {
    "path": "apps/knowledge/task/handler.py",
    "content": "# coding=utf-8\n\n\nimport re\nimport traceback\n\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.utils.fork import ChildLink, Fork\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.split_model import get_split_model\nfrom knowledge.models import State\nfrom knowledge.models.knowledge import KnowledgeType, Document, Knowledge\n\n\ndef get_save_handler(knowledge_id, selector):\n    from knowledge.serializers.document import DocumentSerializers\n\n    def handler(child_link: ChildLink, response: Fork.Response):\n        if response.status == 200:\n            try:\n                document_name = child_link.tag.text if child_link.tag is not None and len(\n                    child_link.tag.text.strip()) > 0 else child_link.url\n                paragraphs = get_split_model('web.md').parse(response.content)\n                DocumentSerializers.Create(\n                    data={'knowledge_id': knowledge_id}\n                ).save({\n                    'name': document_name,\n                    'paragraphs': paragraphs,\n                    'meta': {'source_url': child_link.url, 'selector': selector},\n                    'type': KnowledgeType.WEB\n                }, with_valid=True)\n            except Exception as e:\n                maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}')\n\n    return handler\n\n\ndef get_sync_handler(knowledge_id):\n    from knowledge.serializers.document import DocumentSerializers\n\n    knowledge = QuerySet(Knowledge).filter(id=knowledge_id).first()\n\n    def handler(child_link: ChildLink, response: Fork.Response):\n        if response.status == 200:\n            try:\n\n                document_name = child_link.tag.text if child_link.tag is not None and len(\n                    child_link.tag.text.strip()) > 0 else child_link.url\n                paragraphs = get_split_model('web.md').parse(response.content)\n                first = QuerySet(Document).filter(meta__source_url=child_link.url.strip(), knowledge=knowledge).first()\n                if first is not None:\n                    # 如果存在,使用文档同步\n                    DocumentSerializers.Sync(data={'document_id': first.id}).sync()\n                else:\n                    # 插入\n                    DocumentSerializers.Create(\n                        data={'knowledge_id': knowledge.id}\n                    ).save({\n                        'name': document_name,\n                        'paragraphs': paragraphs,\n                        'meta': {'source_url': child_link.url.strip(), 'selector': knowledge.meta.get('selector')},\n                        'type': KnowledgeType.WEB\n                    }, with_valid=True)\n            except Exception as e:\n                maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}')\n\n    return handler\n\n\ndef get_sync_web_document_handler(knowledge_id):\n    from knowledge.serializers.document import DocumentSerializers\n\n    def handler(source_url: str, selector, response: Fork.Response):\n        if response.status == 200:\n            try:\n                paragraphs = get_split_model('web.md').parse(response.content)\n                # 插入\n                DocumentSerializers.Create(data={'knowledge_id': knowledge_id}).save(\n                    {'name': source_url[0:128], 'paragraphs': paragraphs,\n                     'meta': {'source_url': source_url, 'selector': selector},\n                     'type': KnowledgeType.WEB}, with_valid=True)\n            except Exception as e:\n                maxkb_logger.error(f'{str(e)}:{traceback.format_exc()}')\n        else:\n            Document(name=source_url[0:128],\n                     knowledge_id=knowledge_id,\n                     meta={'source_url': source_url, 'selector': selector, 'allow_download': True},\n                     type=KnowledgeType.WEB,\n                     char_length=0,\n                     status=State.FAILURE).save()\n\n    return handler\n\n\ndef save_problem(knowledge_id, document_id, paragraph_id, problem):\n    from knowledge.serializers.paragraph import ParagraphSerializers\n\n    # print(f\"knowledge_id: {knowledge_id}\")\n    # print(f\"document_id: {document_id}\")\n    # print(f\"paragraph_id: {paragraph_id}\")\n    # print(f\"problem: {problem}\")\n    problem = re.sub(r\"^\\d+\\.\\s*\", \"\", problem)\n    pattern = r\"<question>(.*?)</question>\"\n    match = re.search(pattern, problem)\n    problem = match.group(1) if match else None\n    if problem is None or len(problem) == 0:\n        return\n    try:\n        workspace_id = QuerySet(Knowledge).filter(id=knowledge_id).first().workspace_id\n        ParagraphSerializers.Problem(\n            data={\n                'workspace_id': workspace_id,\n                \"knowledge_id\": knowledge_id,\n                'document_id': document_id,\n                'paragraph_id': paragraph_id\n            }\n        ).save(instance={\"content\": problem}, with_valid=True)\n    except Exception as e:\n        maxkb_logger.error(_('Association problem failed {error}').format(error=str(e)))\n"
  },
  {
    "path": "apps/knowledge/task/sync.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： sync.py\n    @date：2024/8/20 21:37\n    @desc:\n\"\"\"\n\nimport traceback\nfrom typing import List\n\nfrom celery_once import QueueOnce\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.utils.fork import ForkManage, Fork\nfrom common.utils.logger import maxkb_logger\nfrom ops import celery_app\n\n\n\n\n@celery_app.task(base=QueueOnce, once={'keys': ['knowledge_id']}, name='celery:sync_web_knowledge')\ndef sync_web_knowledge(knowledge_id: str, url: str, selector: str):\n    from knowledge.task.handler import get_save_handler\n\n    try:\n        maxkb_logger.info(\n            _('Start--->Start synchronization web knowledge base:{knowledge_id}').format(knowledge_id=knowledge_id))\n        ForkManage(url, selector.split(\" \") if selector is not None else []).fork(2, set(),\n                                                                                  get_save_handler(knowledge_id,\n                                                                                                   selector))\n\n        maxkb_logger.info(_('End--->End synchronization web knowledge base:{knowledge_id}').format(knowledge_id=knowledge_id))\n    except Exception as e:\n        maxkb_logger.error(_('Synchronize web knowledge base:{knowledge_id} error{error}{traceback}').format(\n            knowledge_id=knowledge_id, error=str(e), traceback=traceback.format_exc()))\n\n\n@celery_app.task(base=QueueOnce, once={'keys': ['knowledge_id']}, name='celery:sync_replace_web_knowledge')\ndef sync_replace_web_knowledge(knowledge_id: str, url: str, selector: str):\n    from knowledge.task.handler import get_sync_handler\n\n    try:\n        maxkb_logger.info(\n            _('Start--->Start synchronization web knowledge base:{knowledge_id}').format(knowledge_id=knowledge_id))\n        ForkManage(url, selector.split(\" \") if selector is not None else []).fork(2, set(),\n                                                                                  get_sync_handler(knowledge_id\n                                                                                                   ))\n        maxkb_logger.info(_('End--->End synchronization web knowledge base:{knowledge_id}').format(knowledge_id=knowledge_id))\n    except Exception as e:\n        maxkb_logger.error(_('Synchronize web knowledge base:{knowledge_id} error{error}{traceback}').format(\n            knowledge_id=knowledge_id, error=str(e), traceback=traceback.format_exc()))\n\n\n@celery_app.task(name='celery:sync_web_document')\ndef sync_web_document(knowledge_id, source_url_list: List[str], selector: str):\n    from knowledge.task.handler import get_sync_web_document_handler\n\n    handler = get_sync_web_document_handler(knowledge_id)\n    for source_url in source_url_list:\n        try:\n            result = Fork(base_fork_url=source_url, selector_list=selector.split(' ')).fork()\n            handler(source_url, selector, result)\n        except Exception as e:\n            pass\n"
  },
  {
    "path": "apps/knowledge/template/csv_template_en.csv",
    "content": "Section title (optional), Section content (required，question answer), Question (optional，one per line in the cell)\nMaxKB product introduction,\"MaxKB is a knowledge base question-answering system based on the LLM large language model. MaxKB = Max Knowledge Base，aims to become the most powerful brain of the enterprise。Out-of-the-box: supports direct document upload、automatic crawling of online documents、automatic text splitting and vectorization、and good intelligent question-answering interactive experience；Seamless embedding: supports zero-coding and rapid embedding into third-party business systems；Multi-model support: supports docking with mainstream large models，including Ollama local private large models (such as Llama 2、Llama 3、qwen)、Tongyi Qianwen、OpenAI、Azure OpenAI、Kimi、Zhipu AI、iFlytek Spark and Baidu Qianfan large models、etc.\",\"What is MaxKB?\nMaxKB product introduction\nLarge language model supported by MaxKB\nMaxKB advantages\""
  },
  {
    "path": "apps/knowledge/template/csv_template_zh.csv",
    "content": "分段标题（选填）,分段内容（必填，问题答案））,问题（选填，单元格内一行一个）\nMaxKB产品介绍,\"MaxKB 是一款基于 LLM 大语言模型的知识库问答系统。MaxKB = Max Knowledge Base，旨在成为企业的最强大脑。\n开箱即用：支持直接上传文档、自动爬取在线文档，支持文本自动拆分、向量化，智能问答交互体验好；\n无缝嵌入：支持零编码快速嵌入到第三方业务系统；\n多模型支持：支持对接主流的大模型，包括 Ollama 本地私有大模型（如 Llama 2、Llama 3、qwen）、通义千问、OpenAI、Azure OpenAI、Kimi、智谱 AI、讯飞星火和百度千帆大模型等。\",\"MaxKB是什么？\nMaxKB产品介绍\nMaxKB支持的大语言模型\nMaxKB优势\"\n"
  },
  {
    "path": "apps/knowledge/template/csv_template_zh_Hant.csv",
    "content": "分段標題（選填）,分段內容（必填，問題答案））,問題（選填，單元格內一行一個）\nMaxKB產品介紹,\"MaxKB 是一款基於 LLM 大語言模型的知識庫問答系統。MaxKB = Max Knowledge Base，旨在成為企業的最強大大腦。\n開箱即用：支援直接上傳文檔、自動爬取線上文檔，支援文字自動分割、向量化，智慧問答互動體驗好；\n無縫嵌入：支援零編碼快速嵌入到第三方業務系統；\n多模型支援：支持對接主流的大模型，包括Ollama 本地私有大模型（如Llama 2、Llama 3、qwen）、通義千問、OpenAI、Azure OpenAI、Kimi、智譜AI、訊飛星火和百度千帆大模型等。 \",\"MaxKB是什麼？\nMaxKB產品介紹\nMaxKB支援的大語言模型\nMaxKB優勢\""
  },
  {
    "path": "apps/knowledge/template/table_template_en.csv",
    "content": "Position, Reimbursement type, First-tier city reimbursement standard (yuan), Second-tier city reimbursement standard (yuan), Third-tier city reimbursement standard (yuan)\nOrdinary employees, Accommodation expenses, 500, 400, 300\nDepartment head, Accommodation fee, 600, 500, 400\nDepartment director, Accommodation fee, 700, 600, 500\nRegional general manager, Accommodation fee, 800, 700, 600\nOrdinary employees, Food expenses, 50, 40, 30\nDepartment head, Food expenses, 50, 40, 30\nDepartment director, Food expenses, 50, 40, 30\nRegional general manager, Food expenses, 50, 40, 30\nOrdinary employees, Transportation expenses, 50, 40, 30\nDepartment head, Transportation expenses, 50, 40, 30\nDepartment director, Transportation expenses, 50, 40, 30\nRegional general manager, Transportation expenses, 50, 40, 30"
  },
  {
    "path": "apps/knowledge/template/table_template_zh.csv",
    "content": "职务,报销类型,一线城市报销标准（元）,二线城市报销标准（元）,三线城市报销标准（元）\n普通员工,住宿费,500,400,300\n部门主管,住宿费,600,500,400\n部门总监,住宿费,700,600,500\n区域总经理,住宿费,800,700,600\n普通员工,伙食费,50,40,30\n部门主管,伙食费,50,40,30\n部门总监,伙食费,50,40,30\n区域总经理,伙食费,50,40,30\n普通员工,交通费,50,40,30\n部门主管,交通费,50,40,30\n部门总监,交通费,50,40,30\n区域总经理,交通费,50,40,30\n"
  },
  {
    "path": "apps/knowledge/template/table_template_zh_Hant.csv",
    "content": "職務,報銷類型,一線城市報銷標準（元）,二線城市報銷標準（元）,三線城市報銷標準（元）\n普通員工,住宿費,500,400,300\n部門主管,住宿費,600,500,400\n部門總監,住宿費,700,600,500\n區域總經理,住宿費,800,700,600\n普通員工,伙食費,50,40,30\n部門主管,伙食費,50,40,30\n部門總監,伙食費,50,40,30\n區域總經理,伙食費,50,40,30\n普通員工,交通費,50,40,30\n部門主管,交通費,50,40,30\n部門總監,交通費,50,40,30\n區域總經理,交通費,50,40,30"
  },
  {
    "path": "apps/knowledge/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/knowledge/urls.py",
    "content": "from django.urls import path\n\nfrom . import views\n\napp_name = \"knowledge\"\n# @formatter:off\nurlpatterns = [\n    path('workspace/knowledge/document/template/export', views.Template.as_view()),\n    path('workspace/knowledge/document/table_template/export', views.TableTemplate.as_view()),\n    path('workspace/store/knowledge_template', views.KnowledgeView.StoreKnowledge.as_view()),\n    path('workspace/<str:workspace_id>/knowledge', views.KnowledgeView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/base', views.KnowledgeBaseView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/workflow', views.KnowledgeWorkflowView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/web', views.KnowledgeWebView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/model', views.KnowledgeView.Model.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/embedding_model', views.KnowledgeView.EmbeddingModel.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/tags', views.KnowledgeView.Tags.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>', views.KnowledgeView.Operate.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/sync', views.KnowledgeView.SyncWeb.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/workflow', views.KnowledgeWorkflowView.Operate.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/workflow/export', views.KnowledgeWorkflowView.Export.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/workflow/import', views.KnowledgeWorkflowView.Import.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/generate_related', views.KnowledgeView.GenerateRelated.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/embedding', views.KnowledgeView.Embedding.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/hit_test', views.KnowledgeView.HitTest.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/export', views.KnowledgeView.Export.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/export_zip', views.KnowledgeView.ExportZip.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/transform_workflow', views.KnowledgeView.TransformWorkflow.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/tags', views.KnowledgeTagView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/tags/batch_delete', views.KnowledgeTagView.BatchDelete.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/tags/<str:tag_id>', views.KnowledgeTagView.Operate.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/tags/<str:tag_id>/<str:delete_type>', views.KnowledgeTagView.Delete.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document', views.DocumentView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/split', views.DocumentView.Split.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/split_pattern', views.DocumentView.SplitPattern.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_create', views.DocumentView.BatchCreate.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_sync', views.DocumentView.BatchSync.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_delete', views.DocumentView.BatchDelete.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_refresh', views.DocumentView.BatchRefresh.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_generate_related', views.DocumentView.BatchGenerateRelated.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_export', views.DocumentView.BatchExport.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_export_zip', views.DocumentView.BatchExportZip.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/web', views.WebDocumentView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/qa', views.QaDocumentView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/table', views.TableDocumentView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_hit_handling', views.DocumentView.BatchEditHitHandling.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_cancel_task', views.DocumentView.BatchCancelTask.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/batch_add_tag', views.DocumentView.BatchAddTag.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/migrate/<str:target_knowledge_id>', views.DocumentView.Migrate.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>', views.DocumentView.Operate.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/sync', views.DocumentView.SyncWeb.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/refresh', views.DocumentView.Refresh.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/cancel_task', views.DocumentView.CancelTask.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/export', views.DocumentView.Export.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/export_zip', views.DocumentView.ExportZip.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/download_source_file', views.DocumentView.DownloadSourceFile.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/replace_source_file', views.DocumentView.ReplaceSourceFile.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/tags', views.DocumentView.Tags.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/tags/batch_delete', views.DocumentView.Tags.BatchDelete.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/tag/<str:tag_id>/docs_delete', views.DocumentView.Tags.BatchDeleteDocsTag.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph', views.ParagraphView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/batch_delete', views.ParagraphView.BatchDelete.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/batch_generate_related', views.ParagraphView.BatchGenerateRelated.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/migrate/knowledge/<str:target_knowledge_id>/document/<str:target_document_id>', views.ParagraphView.BatchMigrate.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/association', views.ParagraphView.Association.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/unassociation', views.ParagraphView.UnAssociation.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/adjust_position', views.ParagraphView.AdjustPosition.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/<str:paragraph_id>', views.ParagraphView.Operate.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/<str:paragraph_id>/problem', views.ParagraphView.Problem.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<str:document_id>/paragraph/<int:current_page>/<int:page_size>', views.ParagraphView.Page.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/problem', views.ProblemView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/problem/batch_delete', views.ProblemView.BatchDelete.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/problem/batch_association', views.ProblemView.BatchAssociation.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/problem/<str:problem_id>', views.ProblemView.Operate.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/problem/<str:problem_id>/paragraph', views.ProblemView.Paragraph.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/problem/<int:current_page>/<int:page_size>', views.ProblemView.Page.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/document/<int:current_page>/<int:page_size>', views.DocumentView.Page.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<int:current_page>/<int:page_size>', views.KnowledgeView.Page.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/datasource/<str:type>/<str:id>/form_list', views.KnowledgeDatasourceFormListView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/datasource/<str:type>/<str:id>/<str:function_name>', views.KnowledgeDatasourceView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/publish', views.KnowledgeWorkflowView.Publish.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/debug', views.KnowledgeWorkflowActionView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/action/<int:current_page>/<int:page_size>', views.KnowledgeWorkflowActionView.Page.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/upload_document', views.KnowledgeWorkflowUploadDocumentView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/action/<str:knowledge_action_id>', views.KnowledgeWorkflowActionView.Operate.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/action/<str:knowledge_action_id>/cancel', views.KnowledgeWorkflowActionView.Cancel.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/mcp_tools', views.McpServers.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/knowledge_version', views.KnowledgeWorkflowVersionView.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/knowledge_version/<int:current_page>/<int:page_size>', views.KnowledgeWorkflowVersionView.Page.as_view()),\n    path('workspace/<str:workspace_id>/knowledge/<str:knowledge_id>/knowledge_version/<str:knowledge_version_id>', views.KnowledgeWorkflowVersionView.Operate.as_view()),\n]\n"
  },
  {
    "path": "apps/knowledge/vector/__init__.py",
    "content": ""
  },
  {
    "path": "apps/knowledge/vector/base_vector.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： base_vector.py\n    @date：2023/10/18 19:16\n    @desc:\n\"\"\"\nimport re\nimport threading\nfrom abc import ABC, abstractmethod\nfrom functools import reduce\nfrom typing import List, Dict\n\nfrom langchain_core.embeddings import Embeddings\n\nfrom common.chunk import text_to_chunk\nfrom common.utils.common import sub_array\nfrom knowledge.models import SourceType, SearchMode\n\nlock = threading.Lock()\n\n\ndef chunk_data(data: Dict):\n    if str(data.get('source_type')) == str(SourceType.PARAGRAPH.value):\n        text = data.get('text')\n        chunk_list = data.get('chunks') if data.get('chunks') else text_to_chunk(text)\n        return [{**data, 'text': chunk} for chunk in chunk_list]\n    return [data]\n\n\ndef chunk_data_list(data_list: List[Dict]):\n    result = [chunk_data(data) for data in data_list]\n    return reduce(lambda x, y: [*x, *y], result, [])\n\n\n# 预编译正则，性能更好\nRE_EMOJI = re.compile(\n    r\"[\\U0001F300-\\U0001FAFF]\"  # Emoji\n    r\"|[\\u2600-\\u27BF]\"  # Dingbats / Symbols（⚓ 在这）\n    r\"|[\\uFE0E\\uFE0F]\",  # Variation Selectors\n    flags=re.UNICODE\n)\n\nRE_WHITESPACE = re.compile(r\"\\s+\")\n\n\ndef normalize_for_embedding(text: str) -> str:\n    if not text:\n        return \"\"\n\n    text = RE_EMOJI.sub(\"\", text)\n    text = RE_WHITESPACE.sub(\" \", text)\n    return text.strip()\n\n\nclass BaseVectorStore(ABC):\n    vector_exists = False\n\n    @abstractmethod\n    def vector_is_create(self) -> bool:\n        \"\"\"\n        判断向量库是否创建\n        :return: 是否创建向量库\n        \"\"\"\n        pass\n\n    @abstractmethod\n    def vector_create(self):\n        \"\"\"\n        创建 向量库\n        :return:\n        \"\"\"\n        pass\n\n    def save_pre_handler(self):\n        \"\"\"\n        插入前置处理器 主要是判断向量库是否创建\n        :return: True\n        \"\"\"\n        if not BaseVectorStore.vector_exists:\n            if not self.vector_is_create():\n                self.vector_create()\n                BaseVectorStore.vector_exists = True\n        return True\n\n    def save(self, text, source_type: SourceType, knowledge_id: str, document_id: str, paragraph_id: str,\n             source_id: str,\n             is_active: bool,\n             embedding: Embeddings):\n        \"\"\"\n        插入向量数据\n        :param source_id:  资源id\n        :param knowledge_id: 知识库id\n        :param text: 文本\n        :param source_type: 资源类型\n        :param document_id: 文档id\n        :param is_active:   是否禁用\n        :param embedding:   向量化处理器\n        :param paragraph_id 段落id\n        :return:  bool\n        \"\"\"\n        self.save_pre_handler()\n        data = {'document_id': document_id, 'paragraph_id': paragraph_id, 'knowledge_id': knowledge_id,\n                'is_active': is_active, 'source_id': source_id, 'source_type': source_type, 'text': text}\n        chunk_list = chunk_data(data)\n        result = sub_array(chunk_list)\n        for child_array in result:\n            self._batch_save(child_array, embedding, lambda: False)\n\n    def batch_save(self, data_list: List[Dict], embedding: Embeddings, is_the_task_interrupted):\n        \"\"\"\n        批量插入\n        @param data_list: 数据列表\n        @param embedding: 向量化处理器\n        @param is_the_task_interrupted: 判断是否中断任务\n        :return: bool\n        \"\"\"\n        self.save_pre_handler()\n        chunk_list = chunk_data_list(data_list)\n        result = sub_array(chunk_list)\n        for child_array in result:\n            if not is_the_task_interrupted():\n                self._batch_save(child_array, embedding, is_the_task_interrupted)\n            else:\n                break\n\n    @abstractmethod\n    def _save(self, text, source_type: SourceType, knowledge_id: str, document_id: str, paragraph_id: str,\n              source_id: str,\n              is_active: bool,\n              embedding: Embeddings):\n        pass\n\n    @abstractmethod\n    def _batch_save(self, text_list: List[Dict], embedding: Embeddings, is_the_task_interrupted):\n        pass\n\n    def search(self, query_text, knowledge_id_list: list[str], exclude_document_id_list: list[str],\n               exclude_paragraph_list: list[str],\n               is_active: bool,\n               embedding: Embeddings):\n        if knowledge_id_list is None or len(knowledge_id_list) == 0:\n            return []\n        query_text = normalize_for_embedding(query_text)\n        embedding_query = embedding.embed_query(query_text)\n        result = self.query(embedding_query, knowledge_id_list, exclude_document_id_list, exclude_paragraph_list,\n                            is_active, 1, 3, 0.65)\n        return result[0]\n\n    @abstractmethod\n    def query(self, query_text: str, query_embedding: List[float], knowledge_id_list: list[str],\n              document_id_list: list[str] | None,\n              exclude_document_id_list: list[str],\n              exclude_paragraph_list: list[str], is_active: bool, top_n: int, similarity: float,\n              search_mode: SearchMode):\n        pass\n\n    @abstractmethod\n    def hit_test(self, query_text, knowledge_id: list[str], exclude_document_id_list: list[str], top_number: int,\n                 similarity: float,\n                 search_mode: SearchMode,\n                 embedding: Embeddings):\n        pass\n\n    @abstractmethod\n    def update_by_paragraph_id(self, paragraph_id: str, instance: Dict):\n        pass\n\n    @abstractmethod\n    def update_by_paragraph_ids(self, paragraph_ids: str, instance: Dict):\n        pass\n\n    @abstractmethod\n    def update_by_source_id(self, source_id: str, instance: Dict):\n        pass\n\n    @abstractmethod\n    def update_by_source_ids(self, source_ids: List[str], instance: Dict):\n        pass\n\n    @abstractmethod\n    def delete_by_knowledge_id(self, knowledge_id: str):\n        pass\n\n    @abstractmethod\n    def delete_by_document_id(self, document_id: str):\n        pass\n\n    @abstractmethod\n    def delete_by_document_id_list(self, document_id_list: List[str]):\n        pass\n\n    @abstractmethod\n    def delete_by_knowledge_id_list(self, knowledge_id_list: List[str]):\n        pass\n\n    @abstractmethod\n    def delete_by_source_id(self, source_id: str, source_type: str):\n        pass\n\n    @abstractmethod\n    def delete_by_source_ids(self, source_ids: List[str], source_type: str):\n        pass\n\n    @abstractmethod\n    def delete_by_paragraph_id(self, paragraph_id: str):\n        pass\n\n    @abstractmethod\n    def delete_by_paragraph_ids(self, paragraph_ids: List[str]):\n        pass\n"
  },
  {
    "path": "apps/knowledge/vector/pg_vector.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： pg_vector.py\n    @date：2023/10/19 15:28\n    @desc:\n\"\"\"\nimport json\nimport os\nfrom abc import ABC, abstractmethod\nfrom typing import Dict, List\n\nimport uuid_utils.compat as uuid\nfrom django.contrib.postgres.search import SearchVector\nfrom django.db.models import QuerySet, Value\nfrom langchain_core.embeddings import Embeddings\n\nfrom common.db.search import generate_sql_by_query_dict\nfrom common.db.sql_execute import select_list\nfrom common.utils.common import get_file_content\nfrom common.utils.ts_vecto_util import to_ts_vector, to_query\nfrom knowledge.models import Embedding, SearchMode, SourceType\nfrom knowledge.vector.base_vector import BaseVectorStore, normalize_for_embedding\nfrom maxkb.conf import PROJECT_DIR\n\n\nclass PGVector(BaseVectorStore):\n\n    def delete_by_source_ids(self, source_ids: List[str], source_type: str):\n        if len(source_ids) == 0:\n            return\n        QuerySet(Embedding).filter(source_id__in=source_ids, source_type=source_type).delete()\n\n    def update_by_source_ids(self, source_ids: List[str], instance: Dict):\n        QuerySet(Embedding).filter(source_id__in=source_ids).update(**instance)\n\n    def vector_is_create(self) -> bool:\n        # 项目启动默认是创建好的 不需要再创建\n        return True\n\n    def vector_create(self):\n        return True\n\n    def _save(self, text, source_type: SourceType, knowledge_id: str, document_id: str, paragraph_id: str,\n              source_id: str,\n              is_active: bool,\n              embedding: Embeddings):\n        text = normalize_for_embedding(text)\n        text_embedding = [float(x) for x in embedding.embed_query(text)]\n        embedding = Embedding(\n            id=uuid.uuid7(),\n            knowledge_id=knowledge_id,\n            document_id=document_id,\n            is_active=is_active,\n            paragraph_id=paragraph_id,\n            source_id=source_id,\n            embedding=text_embedding,\n            source_type=source_type,\n            search_vector=to_ts_vector(text)\n        )\n        embedding.save()\n        return True\n\n    def _batch_save(self, text_list: List[Dict], embedding: Embeddings, is_the_task_interrupted):\n        texts = [normalize_for_embedding(row.get('text')) for row in text_list]\n        embeddings = embedding.embed_documents(texts)\n        embedding_list = [\n            Embedding(\n                id=uuid.uuid7(),\n                document_id=text_list[index].get('document_id'),\n                paragraph_id=text_list[index].get('paragraph_id'),\n                knowledge_id=text_list[index].get('knowledge_id'),\n                is_active=text_list[index].get('is_active', True),\n                source_id=text_list[index].get('source_id'),\n                source_type=text_list[index].get('source_type'),\n                embedding=[float(x) for x in embeddings[index]],\n                search_vector=SearchVector(Value(to_ts_vector(text_list[index]['text'])))\n            ) for index in range(0, len(texts))]\n        if not is_the_task_interrupted():\n            QuerySet(Embedding).bulk_create(embedding_list) if len(embedding_list) > 0 else None\n        return True\n\n    def hit_test(self, query_text, knowledge_id_list: list[str], exclude_document_id_list: list[str], top_number: int,\n                 similarity: float,\n                 search_mode: SearchMode,\n                 embedding: Embeddings):\n        if knowledge_id_list is None or len(knowledge_id_list) == 0:\n            return []\n        exclude_dict = {}\n        query_text = normalize_for_embedding(query_text)\n        embedding_query = embedding.embed_query(query_text)\n        query_set = QuerySet(Embedding).filter(knowledge_id__in=knowledge_id_list, is_active=True)\n        if exclude_document_id_list is not None and len(exclude_document_id_list) > 0:\n            exclude_dict.__setitem__('document_id__in', exclude_document_id_list)\n        query_set = query_set.exclude(**exclude_dict)\n        for search_handle in search_handle_list:\n            if search_handle.support(search_mode):\n                return search_handle.handle(query_set, query_text, embedding_query, top_number, similarity, search_mode)\n\n    def query(self, query_text: str, query_embedding: List[float], knowledge_id_list: list[str],\n              document_id_list: list[str],\n              exclude_document_id_list: list[str],\n              exclude_paragraph_list: list[str], is_active: bool, top_n: int, similarity: float,\n              search_mode: SearchMode):\n        exclude_dict = {}\n        if knowledge_id_list is None or len(knowledge_id_list) == 0:\n            return []\n        query_set = QuerySet(Embedding).filter(knowledge_id__in=knowledge_id_list, is_active=is_active)\n        if document_id_list is not None and len(document_id_list) > 0:\n            query_set = query_set.filter(document_id__in=document_id_list)\n        if exclude_document_id_list is not None and len(exclude_document_id_list) > 0:\n            query_set = query_set.exclude(document_id__in=exclude_document_id_list)\n        if exclude_paragraph_list is not None and len(exclude_paragraph_list) > 0:\n            query_set = query_set.exclude(paragraph_id__in=exclude_paragraph_list)\n        query_set = query_set.exclude(**exclude_dict)\n        for search_handle in search_handle_list:\n            if search_handle.support(search_mode):\n                return search_handle.handle(query_set, query_text, query_embedding, top_n, similarity, search_mode)\n\n    def update_by_source_id(self, source_id: str, instance: Dict):\n        QuerySet(Embedding).filter(source_id=source_id).update(**instance)\n\n    def update_by_paragraph_id(self, paragraph_id: str, instance: Dict):\n        QuerySet(Embedding).filter(paragraph_id=paragraph_id).update(**instance)\n\n    def update_by_paragraph_ids(self, paragraph_id: str, instance: Dict):\n        QuerySet(Embedding).filter(paragraph_id__in=paragraph_id).update(**instance)\n\n    def delete_by_knowledge_id(self, knowledge_id: str):\n        QuerySet(Embedding).filter(knowledge_id=knowledge_id).delete()\n\n    def delete_by_knowledge_id_list(self, knowledge_id_list: List[str]):\n        QuerySet(Embedding).filter(knowledge_id__in=knowledge_id_list).delete()\n\n    def delete_by_document_id(self, document_id: str):\n        QuerySet(Embedding).filter(document_id=document_id).delete()\n        return True\n\n    def delete_by_document_id_list(self, document_id_list: List[str]):\n        if len(document_id_list) == 0:\n            return True\n        return QuerySet(Embedding).filter(document_id__in=document_id_list).delete()\n\n    def delete_by_source_id(self, source_id: str, source_type: str):\n        QuerySet(Embedding).filter(source_id=source_id, source_type=source_type).delete()\n        return True\n\n    def delete_by_paragraph_id(self, paragraph_id: str):\n        QuerySet(Embedding).filter(paragraph_id=paragraph_id).delete()\n\n    def delete_by_paragraph_ids(self, paragraph_ids: List[str]):\n        QuerySet(Embedding).filter(paragraph_id__in=paragraph_ids).delete()\n\n\nclass ISearch(ABC):\n    @abstractmethod\n    def support(self, search_mode: SearchMode):\n        pass\n\n    @abstractmethod\n    def handle(self, query_set, query_text, query_embedding, top_number: int,\n               similarity: float, search_mode: SearchMode):\n        pass\n\n\nclass EmbeddingSearch(ISearch):\n    def handle(self,\n               query_set,\n               query_text,\n               query_embedding,\n               top_number: int,\n               similarity: float,\n               search_mode: SearchMode):\n        exec_sql, exec_params = generate_sql_by_query_dict({'embedding_query': query_set},\n                                                           select_string=get_file_content(\n                                                               os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql',\n                                                                            'embedding_search.sql')),\n                                                           with_table_name=True)\n        embedding_model = select_list(exec_sql, [\n            len(query_embedding),\n            json.dumps(query_embedding),\n            *exec_params,\n            similarity,\n            top_number\n        ])\n        return embedding_model\n\n    def support(self, search_mode: SearchMode):\n        return search_mode.value == SearchMode.embedding.value\n\n\nclass KeywordsSearch(ISearch):\n    def handle(self,\n               query_set,\n               query_text,\n               query_embedding,\n               top_number: int,\n               similarity: float,\n               search_mode: SearchMode):\n        exec_sql, exec_params = generate_sql_by_query_dict({'keywords_query': query_set},\n                                                           select_string=get_file_content(\n                                                               os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql',\n                                                                            'keywords_search.sql')),\n                                                           with_table_name=True)\n        embedding_model = select_list(exec_sql, [\n            to_query(query_text),\n            *exec_params,\n            similarity,\n            top_number\n        ])\n        return embedding_model\n\n    def support(self, search_mode: SearchMode):\n        return search_mode.value == SearchMode.keywords.value\n\n\nclass BlendSearch(ISearch):\n    def handle(self,\n               query_set,\n               query_text,\n               query_embedding,\n               top_number: int,\n               similarity: float,\n               search_mode: SearchMode):\n        exec_sql, exec_params = generate_sql_by_query_dict({'embedding_query': query_set},\n                                                           select_string=get_file_content(\n                                                               os.path.join(PROJECT_DIR, \"apps\", \"knowledge\", 'sql',\n                                                                            'blend_search.sql')),\n                                                           with_table_name=True)\n        embedding_model = select_list(exec_sql, [\n            len(query_embedding),\n            json.dumps(query_embedding),\n            to_query(query_text),\n            *exec_params, similarity,\n            top_number\n        ])\n        return embedding_model\n\n    def support(self, search_mode: SearchMode):\n        return search_mode.value == SearchMode.blend.value\n\n\nsearch_handle_list = [EmbeddingSearch(), KeywordsSearch(), BlendSearch()]\n"
  },
  {
    "path": "apps/knowledge/views/__init__.py",
    "content": "from .document import *\nfrom .knowledge import *\nfrom .paragraph import *\nfrom .problem import *\nfrom .tag import *\nfrom .knowledge_workflow import *\nfrom .knowledge_workflow_version import *\n"
  },
  {
    "path": "apps/knowledge/views/common.py",
    "content": "from django.db.models import QuerySet\n\nfrom knowledge.models import Document\n\n\ndef get_document_operation_object(document_id: str):\n    document_model = QuerySet(model=Document).filter(id=document_id).first()\n    if document_model is not None:\n        return {\n            \"name\": document_model.name,\n            \"type\": document_model.type,\n        }\n    return {}\n\n\ndef get_document_operation_object_batch(document_id_list: str):\n    document_model_list = QuerySet(model=Document).filter(id__in=document_id_list)\n    if document_model_list is not None:\n        return {\n            \"name\": f'[{\",\".join([document_model.name for document_model in document_model_list])}]',\n            'document_list': [{'name': document_model.name, 'type': document_model.type} for document_model in\n                              document_model_list]\n        }\n    return {}\n\n\ndef get_knowledge_document_operation_object(knowledge_dict: dict, document_dict: dict):\n    return {\n        'name': f'{knowledge_dict.get(\"name\", \"\")}/{document_dict.get(\"name\", \"\")}',\n        'dataset_name': knowledge_dict.get(\"name\", \"\"),\n        'dataset_desc': knowledge_dict.get(\"desc\", \"\"),\n        'dataset_type': knowledge_dict.get(\"type\", \"\"),\n        'document_name': document_dict.get(\"name\", \"\"),\n        'document_type': document_dict.get(\"type\", \"\"),\n    }\n"
  },
  {
    "path": "apps/knowledge/views/document.py",
    "content": "from django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.parsers import MultiPartParser\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result\nfrom knowledge.api.document import DocumentSplitAPI, DocumentBatchAPI, DocumentBatchCreateAPI, DocumentCreateAPI, \\\n    DocumentReadAPI, DocumentEditAPI, DocumentDeleteAPI, TableDocumentCreateAPI, QaDocumentCreateAPI, \\\n    WebDocumentCreateAPI, CancelTaskAPI, BatchCancelTaskAPI, SyncWebAPI, RefreshAPI, BatchEditHitHandlingAPI, \\\n    DocumentTreeReadAPI, DocumentSplitPatternAPI, BatchRefreshAPI, BatchGenerateRelatedAPI, TemplateExportAPI, \\\n    DocumentExportAPI, DocumentMigrateAPI, DocumentDownloadSourceAPI, DocumentTagsAPI\nfrom knowledge.api.tag import DocsTagDeleteAPI\nfrom knowledge.serializers.common import get_knowledge_operation_object\nfrom knowledge.serializers.document import DocumentSerializers\nfrom knowledge.views.common import get_knowledge_document_operation_object, get_document_operation_object_batch, \\\n    get_document_operation_object\n\n\nclass DocumentView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create document'),\n        summary=_('Create document'),\n        operation_id=_('Create document'),  # type: ignore\n        request=DocumentCreateAPI.get_request(),\n        parameters=DocumentCreateAPI.get_parameters(),\n        responses=DocumentCreateAPI.get_response(),\n        tags=[_('Knowledge Base/Documentation')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    @log(menu='document', operate=\"Create document\",\n         get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n             get_knowledge_operation_object(keywords.get('knowledge_id')),\n             {'name': r.data.get('name')}), )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str):\n        return result.success(\n            DocumentSerializers.Create(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id},\n            ).save(request.data))\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get document'),\n        summary=_('Get document'),\n        operation_id=_('Get document'),  # type: ignore\n        parameters=DocumentTreeReadAPI.get_parameters(),\n        responses=DocumentTreeReadAPI.get_response(),\n        tags=[_('Knowledge Base/Documentation')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    def get(self, request: Request, workspace_id: str, knowledge_id: str):\n        raw_tags = request.query_params.getlist(\"tags[]\")\n        return result.success(DocumentSerializers.Query(\n            data={\n                'workspace_id': workspace_id,\n                'knowledge_id': knowledge_id,\n                'folder_id': request.query_params.get('folder_id'),\n                'name': request.query_params.get('name'),\n                'tag': request.query_params.get('tag'),\n                'tag_exclude': request.query_params.get('tag_exclude'),\n                'tag_ids': [tag for tag in raw_tags if tag != 'NO_TAG'],\n                'no_tag': 'NO_TAG' in raw_tags,\n                'desc': request.query_params.get(\"desc\"),\n                'user_id': request.query_params.get('user_id')\n            }\n        ).list())\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            description=_('Get document details'),\n            summary=_('Get document details'),\n            operation_id=_('Get document details'),  # type: ignore\n            parameters=DocumentReadAPI.get_parameters(),\n            responses=DocumentReadAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            operate = DocumentSerializers.Operate(data={\n                'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id\n            })\n            operate.is_valid(raise_exception=True)\n            return result.success(operate.one())\n\n        @extend_schema(\n            description=_('Modify document'),\n            summary=_('Modify document'),\n            operation_id=_('Modify document'),  # type: ignore\n            parameters=DocumentEditAPI.get_parameters(),\n            request=DocumentEditAPI.get_request(),\n            responses=DocumentEditAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Modify document\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(DocumentSerializers.Operate(data={\n                'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id\n            }).edit(request.data, with_valid=True))\n\n        @extend_schema(\n            description=_('Delete document'),\n            summary=_('Delete document'),\n            operation_id=_('Delete document'),  # type: ignore\n            parameters=DocumentDeleteAPI.get_parameters(),\n            responses=DocumentDeleteAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_DELETE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_DELETE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Delete document\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def delete(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            operate = DocumentSerializers.Operate(data={\n                'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id\n            })\n            operate.is_valid(raise_exception=True)\n            return result.success(operate.delete())\n\n    class Split(APIView):\n        authentication_classes = [TokenAuth]\n        parser_classes = [MultiPartParser]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_('Segmented document'),\n            summary=_('Segmented document'),\n            operation_id=_('Segmented document'),  # type: ignore\n            parameters=DocumentSplitAPI.get_parameters(),\n            request=DocumentSplitAPI.get_request(),\n            responses=DocumentSplitAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def post(self, request: Request, workspace_id: str, knowledge_id: str):\n            split_data = {'file': request.FILES.getlist('file')}\n            request_data = request.data\n            if 'patterns' in request.data and request.data.get('patterns') is not None and len(\n                    request.data.get('patterns')) > 0:\n                split_data.__setitem__('patterns', request_data.getlist('patterns'))\n            if 'limit' in request.data:\n                split_data.__setitem__('limit', request_data.get('limit'))\n            if 'with_filter' in request.data:\n                split_data.__setitem__('with_filter', request_data.get('with_filter'))\n            return result.success(DocumentSerializers.Split(data={\n                'workspace_id': workspace_id,\n                'knowledge_id': knowledge_id,\n            }).parse(split_data))\n\n    class SplitPattern(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Get a list of segment IDs'),\n            description=_('Get a list of segment IDs'),\n            operation_id=_('Get a list of segment IDs'),  # type: ignore\n            parameters=DocumentSplitPatternAPI.get_parameters(),\n            responses=DocumentSplitPatternAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(DocumentSerializers.SplitPattern(\n                data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n            ).list())\n\n    class BatchEditHitHandling(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Modify document hit processing methods in batches'),\n            description=_('Modify document hit processing methods in batches'),\n            operation_id=_('Modify document hit processing methods in batches'),  # type: ignore\n            request=BatchEditHitHandlingAPI.get_request(),\n            parameters=BatchEditHitHandlingAPI.get_parameters(),\n            responses=BatchEditHitHandlingAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Modify document hit processing methods in batches\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object_batch(r.data.get('id_list'))),\n\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(DocumentSerializers.Batch(\n                data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n            ).batch_edit_hit_handling(request.data))\n\n    class SyncWeb(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Synchronize web site types'),\n            summary=_('Synchronize web site types'),\n            operation_id=_('Synchronize web site types'),  # type: ignore\n            parameters=SyncWebAPI.get_parameters(),\n            request=SyncWebAPI.get_request(),\n            responses=SyncWebAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_SYNC.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_SYNC.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Synchronize web site types\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(DocumentSerializers.Sync(\n                data={'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n            ).sync())\n\n    class Refresh(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Refresh document vector library'),\n            description=_('Refresh document vector library'),\n            operation_id=_('Refresh document vector library'),  # type: ignore\n            parameters=RefreshAPI.get_parameters(),\n            request=RefreshAPI.get_request(),\n            responses=RefreshAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_VECTOR.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_VECTOR.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Refresh document vector library\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(DocumentSerializers.Operate(\n                data={'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n            ).refresh(request.data.get('state_list')))\n\n    class CancelTask(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Cancel task'),\n            description=_('Cancel task'),\n            operation_id=_('Cancel task'),  # type: ignore\n            parameters=CancelTaskAPI.get_parameters(),\n            request=CancelTaskAPI.get_request(),\n            responses=CancelTaskAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Cancel task\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(DocumentSerializers.Operate(\n                data={'document_id': document_id, 'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n            ).cancel(request.data))\n\n    class BatchCancelTask(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Cancel tasks in batches'),\n            description=_('Cancel tasks in batches'),\n            operation_id=_('Cancel tasks in batches'),  # type: ignore\n            parameters=BatchCancelTaskAPI.get_parameters(),\n            request=BatchCancelTaskAPI.get_request(),\n            responses=BatchCancelTaskAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Cancel tasks in batches\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object_batch(r.data.get('id_list'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(DocumentSerializers.Batch(data={\n                'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n            ).batch_cancel(request.data))\n\n    class BatchCreate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Create documents in batches'),\n            summary=_('Create documents in batches'),\n            operation_id=_('Create documents in batches'),  # type: ignore\n            request=DocumentBatchCreateAPI.get_request(),\n            parameters=DocumentBatchCreateAPI.get_parameters(),\n            responses=DocumentBatchCreateAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Create documents in batches\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                {'name': f'[{\",\".join([document.get(\"name\") for document in r.data])}]',\n                 'document_list': r.data}),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(DocumentSerializers.Batch(\n                data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n            ).batch_save(request.data))\n\n    class BatchSync(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Batch sync documents'),\n            summary=_('Batch sync documents'),\n            operation_id=_('Batch sync documents'),  # type: ignore\n            request=DocumentBatchAPI.get_request(),\n            parameters=DocumentBatchAPI.get_parameters(),\n            responses=DocumentBatchAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_SYNC.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_SYNC.get_workspace_permission_workspace_manage_role(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Batch sync documents\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object_batch(r.data.get('id_list'))),\n\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(DocumentSerializers.Batch(\n                data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n            ).batch_sync(request.data))\n\n    class BatchDelete(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Delete documents in batches'),\n            summary=_('Delete documents in batches'),\n            operation_id=_('Delete documents in batches'),  # type: ignore\n            request=DocumentBatchAPI.get_request(),\n            parameters=DocumentBatchAPI.get_parameters(),\n            responses=DocumentBatchAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_DELETE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_DELETE.get_workspace_permission_workspace_manage_role(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Delete documents in batches\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object_batch(r.data.get('id_list'))),\n\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(DocumentSerializers.Batch(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n            ).batch_delete(request.data))\n\n    class BatchRefresh(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Batch refresh document vector library'),\n            operation_id=_('Batch refresh document vector library'),  # type: ignore\n            request=BatchRefreshAPI.get_request(),\n            parameters=BatchRefreshAPI.get_parameters(),\n            responses=BatchRefreshAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_VECTOR.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_VECTOR.get_workspace_permission_workspace_manage_role(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Batch refresh document vector library\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object_batch(r.data.get('id_list'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(\n                DocumentSerializers.Batch(\n                    data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n                ).batch_refresh(request.data))\n\n    class BatchAddTag(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            summary=_('Batch add tags to documents'),\n            operation_id=_('Batch add tags to documents'),  # type: ignore\n            request=DocumentTagsAPI.get_request(),\n            parameters=DocumentTagsAPI.get_parameters(),\n            responses=DocumentTagsAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Batch add tags to documents\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object_batch(r.data.get('document_ids'))\n            ),\n        )\n        def post(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(DocumentSerializers.Batch(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n            ).batch_add_tag(request.data))\n\n    class BatchGenerateRelated(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Batch generate related problems'),\n            description=_('Batch generate related problems'),\n            operation_id=_('Batch generate related problems'),  # type: ignore\n            request=BatchGenerateRelatedAPI.get_request(),\n            parameters=BatchGenerateRelatedAPI.get_parameters(),\n            responses=BatchGenerateRelatedAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_GENERATE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_GENERATE.get_workspace_permission_workspace_manage_role(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Batch generate related problems\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object_batch(r.data.get('document_id_list'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(DocumentSerializers.BatchGenerateRelated(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n            ).batch_generate_related(request.data))\n\n    class BatchExport(APIView):\n        authentication_classes = [TokenAuth]\n\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Export multiple document\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def post(self, request: Request, workspace_id: str, knowledge_id: str):\n            return DocumentSerializers.Batch(data={\n                'workspace_id': workspace_id,\n                'knowledge_id': knowledge_id,\n                'user_id': request.user.id\n            }).batch_export({'id_list': request.data})\n\n    class BatchExportZip(APIView):\n        authentication_classes = [TokenAuth]\n\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Export multiple document\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def post(self, request: Request, workspace_id: str, knowledge_id: str):\n            return DocumentSerializers.Batch(data={\n                'workspace_id': workspace_id,\n                'knowledge_id': knowledge_id,\n                'user_id': request.user.id\n            }).batch_export_zip({'id_list': request.data})\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get document by pagination'),\n            summary=_('Get document by pagination'),\n            operation_id=_('Get document by pagination'),  # type: ignore\n            parameters=DocumentTreeReadAPI.get_parameters(),\n            responses=DocumentTreeReadAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, current_page: int, page_size: int):\n            raw_tags = request.query_params.getlist(\"tags[]\")\n\n            return result.success(DocumentSerializers.Query(\n                data={\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'folder_id': request.query_params.get('folder_id'),\n                    'name': request.query_params.get('name'),\n                    'tag': request.query_params.get('tag'),\n                    'tag_exclude': request.query_params.get('tag_exclude'),\n                    'tag_ids': [tag for tag in raw_tags if tag != 'NO_TAG'],\n                    'no_tag': 'NO_TAG' in raw_tags,\n                    'desc': request.query_params.get(\"desc\"),\n                    'user_id': request.query_params.get('user_id'),\n                    'status': request.query_params.get('status'),\n                    'is_active': request.query_params.get('is_active'),\n                    'hit_handling_method': request.query_params.get('hit_handling_method'),\n                    'order_by': request.query_params.get('order_by'),\n                }\n            ).page(current_page, page_size))\n\n    class Export(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Export document'),\n            operation_id=_('Export document'),  # type: ignore\n            parameters=DocumentExportAPI.get_parameters(),\n            responses=DocumentExportAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Export document\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return DocumentSerializers.Operate(data={\n                'workspace_id': workspace_id, 'document_id': document_id, 'knowledge_id': knowledge_id\n            }).export()\n\n    class ExportZip(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Export Zip document'),\n            operation_id=_('Export Zip document'),  # type: ignore\n            parameters=DocumentExportAPI.get_parameters(),\n            responses=DocumentExportAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EXPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Export Zip document\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return DocumentSerializers.Operate(data={\n                'workspace_id': workspace_id, 'document_id': document_id, 'knowledge_id': knowledge_id\n            }).export_zip()\n\n    class DownloadSourceFile(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Download source file'),\n            operation_id=_('Download source file'),  # type: ignore\n            parameters=DocumentDownloadSourceAPI.get_parameters(),\n            responses=DocumentDownloadSourceAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return DocumentSerializers.Operate(data={\n                'workspace_id': workspace_id, 'document_id': document_id, 'knowledge_id': knowledge_id\n            }).download_source_file()\n\n    class ReplaceSourceFile(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Replace source file'),\n            operation_id=_('Replace source file'),  # type: ignore\n            parameters=DocumentDownloadSourceAPI.get_parameters(),\n            responses=DocumentDownloadSourceAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_REPLACE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_REPLACE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def post(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(DocumentSerializers.ReplaceSourceFile(data={\n                'workspace_id': workspace_id,\n                'document_id': document_id,\n                'knowledge_id': knowledge_id,\n                'file': request.FILES.get('file')\n            }).replace())\n\n    class Tags(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Get document tags'),\n            description=_('Get document tags'),\n            operation_id=_('Get document tags'),  # type: ignore\n            parameters=DocumentTagsAPI.get_parameters(),\n            responses=DocumentTagsAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(DocumentSerializers.Tags(data={\n                'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id,\n                'name': request.query_params.get('name')\n            }).list())\n\n        @extend_schema(\n            summary=_('Add document tags'),\n            description=_('Add document tags'),\n            operation_id=_('Add document tags'),  # type: ignore\n            parameters=DocumentTagsAPI.get_parameters(),\n            responses=DocumentTagsAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def post(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(DocumentSerializers.AddTags(data={\n                'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id,\n                'tag_ids': request.data\n            }).add_tags())\n\n        class BatchDelete(APIView):\n            authentication_classes = [TokenAuth]\n\n            @extend_schema(\n                summary=_('Delete document tags'),\n                description=_('Delete document tags'),\n                operation_id=_('Delete document tags'),  # type: ignore\n                parameters=DocumentTagsAPI.get_parameters(),\n                request=DocumentTagsAPI.get_request(),\n                responses=DocumentTagsAPI.get_response(),\n                tags=[_('Knowledge Base/Documentation')]  # type: ignore\n            )\n            @has_permissions(\n                PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_knowledge_permission(),\n                PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_permission_workspace_manage_role(),\n                RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                ViewPermission([RoleConstants.USER.get_workspace_role()],\n                               [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                               CompareConstants.AND),\n            )\n            @log(\n                menu='document', operate=\"Delete document tags\",\n                get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                    get_knowledge_operation_object(keywords.get('knowledge_id')),\n                    get_document_operation_object(keywords.get('document_id'))\n                ),\n            )\n            def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n                return result.success(DocumentSerializers.DeleteTags(data={\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'document_id': document_id,\n                    'tag_ids': request.data\n                }).delete_tags())\n\n        class BatchDeleteDocsTag(APIView):\n            authentication_classes = [TokenAuth]\n\n            @extend_schema(\n                summary=_(\"Batch Delete Documents Tag\"),\n                description=_(\"Batch Delete Documents Tag\"),\n                parameters=DocsTagDeleteAPI.get_parameters(),\n                request=DocsTagDeleteAPI.get_request(),\n                responses=DocsTagDeleteAPI.get_response(),\n                tags=[_('Knowledge Base/Tag')]  # type: ignore\n            )\n            @has_permissions(\n                PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_knowledge_permission(),\n                PermissionConstants.KNOWLEDGE_DOCUMENT_TAG.get_workspace_permission_workspace_manage_role(),\n                RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                ViewPermission([RoleConstants.USER.get_workspace_role()],\n                               [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                               CompareConstants.AND),\n            )\n            @log(\n                menu='tag', operate=\"Batch Delete Documents Tag\",\n                get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                    get_knowledge_operation_object(keywords.get('knowledge_id')),\n                    get_document_operation_object_batch(r.data.get('id_list'))),\n            )\n            def put(self, request: Request, workspace_id: str, knowledge_id: str, tag_id: str):\n                return result.success(DocumentSerializers.DeleteDocsTag(data={\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'tag_id': tag_id,\n                }).batch_delete_docs_tag(request.data))\n\n    class Migrate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Migrate documents in batches'),\n            operation_id=_('Migrate documents in batches'),  # type: ignore\n            parameters=DocumentMigrateAPI.get_parameters(),\n            request=DocumentMigrateAPI.get_request(),\n            responses=DocumentMigrateAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_MIGRATE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_MIGRATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate=\"Migrate documents in batches\",\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object_batch(r.data)\n            ),\n        )\n        def put(self, request: Request, workspace_id, knowledge_id: str, target_knowledge_id: str):\n            return result.success(DocumentSerializers.Migrate(\n                data={\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'target_knowledge_id': target_knowledge_id,\n                    'document_id_list': request.data}\n            ).migrate())\n\n\nclass WebDocumentView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create Web site documents'),\n        summary=_('Create Web site documents'),\n        operation_id=_('Create Web site documents'),  # type: ignore\n        request=WebDocumentCreateAPI.get_request(),\n        parameters=WebDocumentCreateAPI.get_parameters(),\n        responses=WebDocumentCreateAPI.get_response(),\n        tags=[_('Knowledge Base/Documentation')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    @log(\n        menu='document', operate=\"Create Web site documents\",\n        get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n            get_knowledge_operation_object(keywords.get('knowledge_id')),\n            {'name': f'[{\",\".join([url for url in r.data.get(\"source_url_list\", [])])}]',\n             'document_list': [{'name': url} for url in r.data.get(\"source_url_list\", [])]}),\n\n    )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str):\n        return result.success(DocumentSerializers.Create(data={\n            'knowledge_id': knowledge_id, 'workspace_id': workspace_id\n        }).save_web(request.data, with_valid=True))\n\n\nclass QaDocumentView(APIView):\n    authentication_classes = [TokenAuth]\n    parser_classes = [MultiPartParser]\n\n    @extend_schema(\n        summary=_('Import QA and create documentation'),\n        description=_('Import QA and create documentation'),\n        operation_id=_('Import QA and create documentation'),  # type: ignore\n        request=QaDocumentCreateAPI.get_request(),\n        parameters=QaDocumentCreateAPI.get_parameters(),\n        responses=QaDocumentCreateAPI.get_response(),\n        tags=[_('Knowledge Base/Documentation')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    @log(\n        menu='document', operate=\"Import QA and create documentation\",\n        get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n            get_knowledge_operation_object(keywords.get('knowledge_id')),\n            {'name': f'[{\",\".join([file.name for file in r.FILES.getlist(\"file\")])}]',\n             'document_list': [{'name': file.name} for file in r.FILES.getlist(\"file\")]}),\n\n    )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str):\n        return result.success(DocumentSerializers.Create(data={\n            'knowledge_id': knowledge_id, 'workspace_id': workspace_id\n        }).save_qa({'file_list': request.FILES.getlist('file')}, with_valid=True))\n\n\nclass TableDocumentView(APIView):\n    authentication_classes = [TokenAuth]\n    parser_classes = [MultiPartParser]\n\n    @extend_schema(\n        summary=_('Import tables and create documents'),\n        description=_('Import tables and create documents'),\n        operation_id=_('Import tables and create documents'),  # type: ignore\n        request=TableDocumentCreateAPI.get_request(),\n        parameters=TableDocumentCreateAPI.get_parameters(),\n        responses=TableDocumentCreateAPI.get_response(),\n        tags=[_('Knowledge Base/Documentation')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    @log(\n        menu='document', operate=\"Import tables and create documents\",\n        get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n            get_knowledge_operation_object(keywords.get('knowledge_id')),\n            {'name': f'[{\",\".join([file.name for file in r.FILES.getlist(\"file\")])}]',\n             'document_list': [{'name': file.name} for file in r.FILES.getlist(\"file\")]}),\n\n    )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str):\n        return result.success(DocumentSerializers.Create(\n            data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n        ).save_table({'file_list': request.FILES.getlist('file')}, with_valid=True))\n\n\nclass Template(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        summary=_('Get QA template'),\n        operation_id=_('Get QA template'),  # type: ignore\n        parameters=TemplateExportAPI.get_parameters(),\n        responses=TemplateExportAPI.get_response(),\n        tags=[_('Knowledge Base/Documentation')]  # type: ignore\n    )\n    def get(self, request: Request):\n        return DocumentSerializers.Export(data={'type': request.query_params.get('type')}).export(with_valid=True)\n\n\nclass TableTemplate(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        summary=_('Get form template'),\n        operation_id=_('Get form template'),  # type: ignore\n        parameters=TemplateExportAPI.get_parameters(),\n        responses=TemplateExportAPI.get_response(),\n        tags=[_('Knowledge Base/Documentation')])  # type: ignore\n    def get(self, request: Request):\n        return DocumentSerializers.Export(data={'type': request.query_params.get('type')}).table_export(with_valid=True)\n"
  },
  {
    "path": "apps/knowledge/views/knowledge.py",
    "content": "from django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result\nfrom knowledge.api.knowledge import KnowledgeBaseCreateAPI, KnowledgeWebCreateAPI, KnowledgeTreeReadAPI, \\\n    KnowledgeEditAPI, KnowledgeReadAPI, KnowledgePageAPI, SyncWebAPI, GenerateRelatedAPI, HitTestAPI, EmbeddingAPI, \\\n    GetModelAPI, KnowledgeExportAPI\nfrom knowledge.models import KnowledgeScope\nfrom knowledge.serializers.common import get_knowledge_operation_object\nfrom knowledge.serializers.knowledge import KnowledgeSerializer\nfrom models_provider.serializers.model_serializer import ModelSerializer\nfrom tools.api.tool import GetInternalToolAPI\n\n\nclass KnowledgeView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get knowledge by folder'),\n        summary=_('Get knowledge by folder'),\n        operation_id=_('Get knowledge by folder'),  # type: ignore\n        parameters=KnowledgeTreeReadAPI.get_parameters(),\n        responses=KnowledgeTreeReadAPI.get_response(),\n        tags=[_('Knowledge Base')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n    )\n    def get(self, request: Request, workspace_id: str):\n        return result.success(KnowledgeSerializer.Query(\n            data={\n                'workspace_id': workspace_id,\n                'folder_id': request.query_params.get('folder_id'),\n                'name': request.query_params.get('name'),\n                'desc': request.query_params.get(\"desc\"),\n                'scope': KnowledgeScope.WORKSPACE,\n                'user_id': request.user.id\n            }\n        ).list())\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Edit knowledge'),\n            summary=_('Edit knowledge'),\n            operation_id=_('Edit knowledge'),  # type: ignore\n            parameters=KnowledgeEditAPI.get_parameters(),\n            request=KnowledgeEditAPI.get_request(),\n            responses=KnowledgeEditAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Knowledge Base', operate=\"Modify knowledge base information\",\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(KnowledgeSerializer.Operate(\n                data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n            ).edit(request.data))\n\n        @extend_schema(\n            methods=['DELETE'],\n            description=_('Delete knowledge'),\n            summary=_('Delete knowledge'),\n            operation_id=_('Delete knowledge'),  # type: ignore\n            parameters=KnowledgeBaseCreateAPI.get_parameters(),\n            request=KnowledgeBaseCreateAPI.get_request(),\n            responses=KnowledgeBaseCreateAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DELETE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DELETE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Knowledge Base', operate=\"Delete knowledge base\",\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n\n        )\n        def delete(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(KnowledgeSerializer.Operate(\n                data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n            ).delete())\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get knowledge'),\n            summary=_('Get knowledge'),\n            operation_id=_('Get knowledge'),  # type: ignore\n            parameters=KnowledgeReadAPI.get_parameters(),\n            responses=KnowledgeReadAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(KnowledgeSerializer.Operate(\n                data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n            ).one())\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get the knowledge base paginated list'),\n            summary=_('Get the knowledge base paginated list'),\n            operation_id=_('Get the knowledge base paginated list'),  # type: ignore\n            parameters=KnowledgePageAPI.get_parameters(),\n            responses=KnowledgePageAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n        )\n        def get(self, request: Request, workspace_id: str, current_page: int, page_size: int):\n            return result.success(KnowledgeSerializer.Query(\n                data={\n                    'workspace_id': workspace_id,\n                    'folder_id': request.query_params.get('folder_id'),\n                    'name': request.query_params.get('name'),\n                    'desc': request.query_params.get(\"desc\"),\n                    'scope': KnowledgeScope.WORKSPACE,\n                    'user_id': request.user.id,\n                    'create_user': request.query_params.get('create_user'),\n                }\n            ).page(current_page, page_size))\n\n    class SyncWeb(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_(\"Synchronize the knowledge base of the website\"),\n            description=_(\"Synchronize the knowledge base of the website\"),\n            operation_id=_(\"Synchronize the knowledge base of the website\"),  # type: ignore\n            parameters=SyncWebAPI.get_parameters(),\n            request=SyncWebAPI.get_request(),\n            responses=SyncWebAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_SYNC.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_SYNC.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Knowledge Base', operate=\"Synchronize the knowledge base of the website\",\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(KnowledgeSerializer.SyncWeb(\n                data={\n                    'workspace_id': workspace_id,\n                    'sync_type': request.query_params.get('sync_type'),\n                    'knowledge_id': knowledge_id,\n                    'user_id': str(request.user.id)\n                }\n            ).sync())\n\n    class HitTest(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            summary=_('Hit test list'),\n            description=_('Hit test list'),\n            operation_id=_('Hit test list'),  # type: ignore\n            parameters=HitTestAPI.get_parameters(),\n            request=HitTestAPI.get_request(),\n            responses=HitTestAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_HIT_TEST.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_HIT_TEST.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def post(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(KnowledgeSerializer.HitTest(\n                data={\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'user_id': request.user.id,\n                    \"query_text\": request.data.get(\"query_text\"),\n                    \"top_number\": request.data.get(\"top_number\"),\n                    'similarity': request.data.get('similarity'),\n                    'search_mode': request.data.get('search_mode')\n                }\n            ).hit_test())\n\n    class StoreKnowledge(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get Appstore tools\"),\n            summary=_(\"Get Appstore tools\"),\n            operation_id=_(\"Get Appstore tools\"),  # type: ignore\n            responses=GetInternalToolAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        def get(self, request: Request):\n            return result.success(KnowledgeSerializer.StoreKnowledge(data={\n                'user_id': request.user.id,\n                'name': request.query_params.get('name', ''),\n            }).get_appstore_templates())\n\n    class Embedding(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Re-vectorize'),\n            description=_('Re-vectorize'),\n            operation_id=_('Re-vectorize'),  # type: ignore\n            parameters=EmbeddingAPI.get_parameters(),\n            request=EmbeddingAPI.get_request(),\n            responses=EmbeddingAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_VECTOR.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_VECTOR.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Knowledge Base', operate='Re-vectorize',\n            get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')),\n\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(KnowledgeSerializer.Operate(\n                data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id, 'user_id': request.user.id}\n            ).embedding())\n\n    class Export(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Export knowledge base'),\n            operation_id=_('Export knowledge base'),  # type: ignore\n            parameters=KnowledgeExportAPI.get_parameters(),\n            responses=KnowledgeExportAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_EXPORT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_EXPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Knowledge Base', operate=\"Export knowledge base\",\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str):\n            return KnowledgeSerializer.Operate(data={\n                'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'user_id': request.user.id\n            }).export_excel()\n\n    class ExportZip(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Export knowledge base containing images'),\n            operation_id=_('Export knowledge base containing images'),  # type: ignore\n            parameters=KnowledgeExportAPI.get_parameters(),\n            responses=KnowledgeExportAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_EXPORT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_EXPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Knowledge Base', operate=\"Export knowledge base containing images\",\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str):\n            return KnowledgeSerializer.Operate(data={\n                'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'user_id': request.user.id\n            }).export_zip()\n\n    class GenerateRelated(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Generate related'),\n            description=_('Generate related'),\n            operation_id=_('Generate related'),  # type: ignore\n            parameters=GenerateRelatedAPI.get_parameters(),\n            request=GenerateRelatedAPI.get_request(),\n            responses=GenerateRelatedAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_GENERATE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_GENERATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='document', operate='Generate related documents',\n            get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')),\n\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(KnowledgeSerializer.Operate(\n                data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id, 'user_id': request.user.id}\n            ).generate_related(request.data))\n\n    class Model(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            summary=_('Get model for knowledge base'),\n            description=_('Get model for knowledge base'),\n            operation_id=_('Get model for knowledge base'),  # type: ignore\n            parameters=GetModelAPI.get_parameters(),\n            responses=GetModelAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_EDIT.get_workspace_permission(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n        )\n        def get(self, request: Request, workspace_id: str):\n            return result.success(ModelSerializer.Query(\n                data={\n                    'workspace_id': workspace_id,\n                    'model_type': 'LLM'\n                }\n            ).list(workspace_id, True))\n\n    class EmbeddingModel(APIView):\n        authentication_classes = [TokenAuth]\n\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_EDIT.get_workspace_permission(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n        )\n        def get(self, request: Request, workspace_id: str):\n            return result.success(ModelSerializer.Query(\n                data={\n                    'workspace_id': workspace_id,\n                    'model_type': 'EMBEDDING'\n                }\n            ).list(workspace_id, True))\n\n    class TransformWorkflow(APIView):\n        authentication_classes = [TokenAuth]\n\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Knowledge Base', operate=\"Modify knowledge base information\",\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n        )\n        def post(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(KnowledgeSerializer.TransformWorkflow(\n                data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n            ).transform(request.data))\n\n    class Tags(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get all tags of knowledge base'),\n            summary=_('Get all tags of knowledge base'),\n            operation_id=_('Get all tags of knowledge base'),  # type: ignore\n            parameters=KnowledgeReadAPI.get_parameters(),\n            responses=KnowledgeReadAPI.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n        )\n        def get(self, request: Request, workspace_id: str):\n            return result.success(KnowledgeSerializer.Tags(data={\n                'user_id': request.user.id,\n                'workspace_id': workspace_id,\n                'knowledge_ids': request.query_params.getlist('knowledge_ids[]')\n            }).list())\n\n\nclass KnowledgeBaseView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create base knowledge'),\n        summary=_('Create base knowledge'),\n        operation_id=_('Create base knowledge'),  # type: ignore\n        parameters=KnowledgeBaseCreateAPI.get_parameters(),\n        request=KnowledgeBaseCreateAPI.get_request(),\n        responses=KnowledgeBaseCreateAPI.get_response(),\n        tags=[_('Knowledge Base')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_CREATE.get_workspace_permission(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n    )\n    @log(\n        menu='knowledge Base', operate='Create base knowledge',\n        get_operation_object=lambda r, k: {'name': r.data.get('name'), 'desc': r.data.get('desc')},\n\n    )\n    def post(self, request: Request, workspace_id: str):\n        return result.success(KnowledgeSerializer.Create(\n            data={'user_id': request.user.id, 'workspace_id': workspace_id}\n        ).save_base(request.data))\n\n\nclass KnowledgeWebView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create web knowledge'),\n        summary=_('Create web knowledge'),\n        operation_id=_('Create web knowledge'),  # type: ignore\n        parameters=KnowledgeWebCreateAPI.get_parameters(),\n        request=KnowledgeWebCreateAPI.get_request(),\n        responses=KnowledgeWebCreateAPI.get_response(),\n        tags=[_('Knowledge Base')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_CREATE.get_workspace_permission(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n    )\n    @log(\n        menu='Knowledge Base', operate=\"Create a web site knowledge base\",\n        get_operation_object=lambda r, k: {'name': r.data.get('name'), 'desc': r.data.get('desc'),\n                                           'first_list': r.FILES.getlist('file'),\n                                           'meta': {'source_url': r.data.get('source_url'),\n                                                    'selector': r.data.get('selector'),\n                                                    'embedding_model_id': r.data.get('embedding_model_id')}}\n        ,\n    )\n    def post(self, request: Request, workspace_id: str):\n        return result.success(KnowledgeSerializer.Create(\n            data={'user_id': request.user.id, 'workspace_id': workspace_id}\n        ).save_web(request.data))\n"
  },
  {
    "path": "apps/knowledge/views/knowledge_workflow.py",
    "content": "# coding=utf-8\n\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_api import SpeechToTextAPI\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions, get_is_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result, DefaultResultSerializer\nfrom knowledge.api.knowledge_workflow import KnowledgeWorkflowApi, KnowledgeWorkflowActionApi, \\\n    KnowledgeWorkflowActionPageApi, KnowledgeWorkflowExportApi, KnowledgeWorkflowImportApi\nfrom knowledge.serializers.common import get_knowledge_operation_object\nfrom knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer, KnowledgeWorkflowActionSerializer, \\\n    KnowledgeWorkflowMcpSerializer\n\n\nclass KnowledgeDatasourceFormListView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission(\n            [RoleConstants.USER.get_workspace_role()],\n            [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n            CompareConstants.AND\n        ),\n    )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str, type: str, id: str):\n        r = KnowledgeWorkflowSerializer.Datasource(\n            data={'type': type, 'id': id, 'params': request.data, 'function_name': 'get_form_list'}\n        ).action()\n\n        return result.success(r)\n\n\nclass KnowledgeDatasourceView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission(\n            [RoleConstants.USER.get_workspace_role()],\n            [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n            CompareConstants.AND\n        ),\n    )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str, type: str, id: str, function_name: str):\n        return result.success(KnowledgeWorkflowSerializer.Datasource(\n            data={'type': type, 'id': id, 'params': request.data, 'function_name': function_name}).action())\n\n\nclass KnowledgeWorkflowUploadDocumentView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Knowledge workflow upload document'),\n        summary=_('Knowledge workflow upload document'),\n        operation_id=_('Knowledge workflow upload document'),  # type: ignore\n        parameters=KnowledgeWorkflowActionApi.get_parameters(),\n        request=KnowledgeWorkflowActionApi.get_request(),\n        responses=KnowledgeWorkflowActionApi.get_response(),\n        tags=[_('Knowledge Base')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission(\n            [RoleConstants.USER.get_workspace_role()],\n            [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n            CompareConstants.AND\n        ),\n    )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str):\n        return result.success(KnowledgeWorkflowActionSerializer(\n            data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}).upload_document(request.data,\n                                                                                               request.user, True))\n\n\nclass KnowledgeWorkflowActionView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Knowledge workflow debug'),\n        summary=_('Knowledge workflow debug'),\n        operation_id=_('Knowledge workflow debug'),  # type: ignore\n        parameters=KnowledgeWorkflowActionApi.get_parameters(),\n        request=KnowledgeWorkflowActionApi.get_request(),\n        responses=KnowledgeWorkflowActionApi.get_response(),\n        tags=[_('Knowledge Base')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission(\n            [RoleConstants.USER.get_workspace_role()],\n            [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n            CompareConstants.AND\n        ),\n    )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str):\n        return result.success(KnowledgeWorkflowActionSerializer(\n            data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}).action(request.data, request.user, True))\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Page Knowledge workflow action'),\n            summary=_('Page Knowledge workflow action'),\n            operation_id=_('Page Knowledge workflow action'),  # type: ignore\n            parameters=KnowledgeWorkflowActionApi.get_parameters(),\n            request=KnowledgeWorkflowActionPageApi.get_request(),\n            responses=KnowledgeWorkflowActionApi.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                CompareConstants.AND\n            ),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, current_page: int, page_size: int):\n            return result.success(\n                KnowledgeWorkflowActionSerializer(data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id})\n                .page(current_page, page_size, request.query_params))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get knowledge workflow action'),\n            summary=_('Get knowledge workflow action'),\n            operation_id=_('Get knowledge workflow action'),  # type: ignore\n            parameters=KnowledgeWorkflowActionApi.get_parameters(),\n            responses=KnowledgeWorkflowActionApi.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                CompareConstants.AND\n            ),\n        )\n        def get(self, request, workspace_id: str, knowledge_id: str, knowledge_action_id: str):\n            return result.success(KnowledgeWorkflowActionSerializer.Operate(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'id': knowledge_action_id})\n                                  .one())\n\n    class Cancel(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_('Cancel knowledge workflow action'),\n            summary=_('Cancel knowledge workflow action'),\n            operation_id=_('Cancel knowledge workflow action'),  # type: ignore\n            parameters=KnowledgeWorkflowActionApi.get_parameters(),\n            responses=DefaultResultSerializer(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_CREATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                CompareConstants.AND\n            ),\n        )\n        def post(self, request, workspace_id: str, knowledge_id: str, knowledge_action_id: str):\n            return result.success(KnowledgeWorkflowActionSerializer.Operate(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'id': knowledge_action_id})\n                                  .cancel())\n\n\nclass KnowledgeWorkflowView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create knowledge workflow'),\n        summary=_('Create knowledge workflow'),\n        operation_id=_('Create knowledge workflow'),  # type: ignore\n        parameters=KnowledgeWorkflowApi.get_parameters(),\n        responses=KnowledgeWorkflowApi.get_response(),\n        tags=[_('Knowledge Base')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_CREATE.get_workspace_permission(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n    )\n    def post(self, request: Request, workspace_id: str):\n        return result.success(KnowledgeWorkflowSerializer.Create(\n            data={'user_id': request.user.id, 'workspace_id': workspace_id}\n        ).save_workflow(request.data))\n\n    class Publish(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_(\"Publishing an knowledge\"),\n            summary=_(\"Publishing an knowledge\"),\n            operation_id=_(\"Publishing an knowledge\"),  # type: ignore\n            parameters=KnowledgeWorkflowApi.get_parameters(),\n            request=None,\n            responses=DefaultResultSerializer,\n            tags=[_('Knowledge')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(),\n                         PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Knowledge', operate='Publishing an knowledge',\n             get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')))\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(\n                KnowledgeWorkflowSerializer.Operate(\n                    data={'knowledge_id': knowledge_id, 'user_id': request.user.id,\n                          'workspace_id': workspace_id, }).publish())\n\n    class Export(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Export knowledge workflow'),\n            summary=_('Export knowledge workflow'),\n            operation_id=_('Export knowledge workflow'),  # type: ignore\n            parameters=KnowledgeWorkflowExportApi.get_parameters(),\n            request=None,\n            responses=KnowledgeWorkflowExportApi.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_WORKFLOW_EXPORT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_WORKFLOW_EXPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                CompareConstants.AND\n            )\n        )\n        @log(menu='Knowledge', operate=\"Export knowledge workflow\",\n             get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')),\n             )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str):\n            return KnowledgeWorkflowSerializer.Export(\n                data={'knowledge_id': knowledge_id, 'user_id': request.user.id, 'workspace_id': workspace_id}\n            ).export()\n\n    class Import(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_('Import knowledge workflow'),\n            summary=_('Import knowledge workflow'),\n            operation_id=_('Import knowledge workflow'),  # type: ignore\n            parameters=KnowledgeWorkflowImportApi.get_parameters(),\n            request=KnowledgeWorkflowImportApi.get_request(),\n            responses=KnowledgeWorkflowImportApi.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                CompareConstants.AND\n            )\n        )\n        @log(menu='Knowledge', operate=\"Import knowledge workflow\",\n             get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')),\n             )\n        def post(self, request: Request, workspace_id: str, knowledge_id: str):\n            is_import_tool = get_is_permissions(request, workspace_id=workspace_id)(\n                PermissionConstants.TOOL_IMPORT.get_workspace_permission(),\n                PermissionConstants.TOOL_IMPORT.get_workspace_permission_workspace_manage_role(),\n                RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n            )\n            return result.success(KnowledgeWorkflowSerializer.Import(data={\n                'knowledge_id': knowledge_id, 'user_id': request.user.id, 'workspace_id': workspace_id\n            }).import_({'file': request.FILES.get('file')}, is_import_tool))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Edit knowledge workflow'),\n            summary=_('Edit knowledge workflow'),\n            operation_id=_('Edit knowledge workflow'),  # type: ignore\n            parameters=KnowledgeWorkflowApi.get_parameters(),\n            request=KnowledgeWorkflowApi.get_request(),\n            responses=KnowledgeWorkflowApi.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                CompareConstants.AND\n            )\n        )\n        @log(\n            menu='Knowledge Base', operate=\"Modify knowledge workflow\",\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(KnowledgeWorkflowSerializer.Operate(\n                data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n            ).edit(request.data))\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get knowledge workflow'),\n            summary=_('Get knowledge workflow'),\n            operation_id=_('Get knowledge workflow'),  # type: ignore\n            parameters=KnowledgeWorkflowApi.get_parameters(),\n            responses=KnowledgeWorkflowApi.get_response(),\n            tags=[_('Knowledge Base')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                CompareConstants.AND\n            ),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(KnowledgeWorkflowSerializer.Operate(\n                data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n            ).one())\n\n\nclass KnowledgeWorkflowVersionView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get knowledge workflow version list'),\n        summary=_('Get knowledge workflow version list'),\n        operation_id=_('Get knowledge workflow version list'),  # type: ignore\n        parameters=KnowledgeWorkflowApi.get_parameters(),\n        responses=KnowledgeWorkflowApi.get_response(),\n        tags=[_('Knowledge Base')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission(\n            [RoleConstants.USER.get_workspace_role()],\n            [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n            CompareConstants.AND\n        ),\n    )\n    def get(self, request: Request, workspace_id: str, knowledge_id: str):\n        return result.success(KnowledgeWorkflowSerializer.Operate(\n            data={'user_id': request.user.id, 'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n        ).one())\n\n\nclass McpServers(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"speech to text\"),\n        summary=_(\"speech to text\"),\n        operation_id=_(\"speech to text\"),  # type: ignore\n        parameters=SpeechToTextAPI.get_parameters(),\n        request=SpeechToTextAPI.get_request(),\n        responses=SpeechToTextAPI.get_response(),\n        tags=[_('Knowledge Base')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_application_permission(),\n                     PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.KNOWLEDGE.get_workspace_application_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def post(self, request: Request, workspace_id, knowledge_id: str):\n        return result.success(KnowledgeWorkflowMcpSerializer(\n            data={'mcp_servers': request.query_params.get('mcp_servers'), 'workspace_id': workspace_id,\n                  'user_id': request.user.id,\n                  'knowledge_id': knowledge_id}).get_mcp_servers(request.data))\n"
  },
  {
    "path": "apps/knowledge/views/knowledge_workflow_version.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_version.py.py\n    @date：2025/6/3 15:46\n    @desc:\n\"\"\"\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom knowledge.api.knowledge_version import KnowledgeVersionListAPI, KnowledgeVersionPageAPI, \\\n    KnowledgeVersionOperateAPI\nfrom knowledge.models import Knowledge\nfrom knowledge.serializers.knowledge_version import KnowledgeWorkflowVersionSerializer\n\n\ndef get_knowledge_operation_object(knowledge_id):\n    knowledge_model = QuerySet(model=Knowledge).filter(id=knowledge_id).first()\n    if knowledge_model is not None:\n        return {\n            'name': knowledge_model.name\n        }\n    return {}\n\n\nclass KnowledgeWorkflowVersionView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get the knowledge version list\"),\n        summary=_(\"Get the knowledge version list\"),\n        operation_id=_(\"Get the knowledge version list\"),  # type: ignore\n        parameters=KnowledgeVersionListAPI.get_parameters(),\n        responses=KnowledgeVersionListAPI.get_response(),\n        tags=[_('Knowledge/Version')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(),\n                     PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id, knowledge_id: str):\n        return result.success(\n            KnowledgeWorkflowVersionSerializer.Query(\n                data={'workspace_id': workspace_id}).list(\n                {'name': request.query_params.get(\"name\"), 'knowledge_id': knowledge_id}))\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get the list of knowledge versions by page\"),\n            summary=_(\"Get the list of knowledge versions by page\"),\n            operation_id=_(\"Get the list of knowledge versions by page\"),  # type: ignore\n            parameters=KnowledgeVersionPageAPI.get_parameters(),\n            responses=KnowledgeVersionPageAPI.get_response(),\n            tags=[_('Knowledge/Version')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(),\n                         PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, current_page: int, page_size: int):\n            return result.success(\n                KnowledgeWorkflowVersionSerializer.Query(\n                    data={'workspace_id': workspace_id}).page(\n                    {'name': request.query_params.get(\"name\"), 'knowledge_id': knowledge_id},\n                    current_page, page_size))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get knowledge version details\"),\n            summary=_(\"Get knowledge version details\"),\n            operation_id=_(\"Get knowledge version details\"),  # type: ignore\n            parameters=KnowledgeVersionOperateAPI.get_parameters(),\n            responses=KnowledgeVersionOperateAPI.get_response(),\n            tags=[_('Knowledge/Version')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_knowledge_permission(),\n                         PermissionConstants.KNOWLEDGE_WORKFLOW_READ.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, knowledge_version_id: str):\n            return result.success(\n                KnowledgeWorkflowVersionSerializer.Operate(\n                    data={'user_id': request.user, 'workspace_id': workspace_id,\n                          'knowledge_id': knowledge_id, 'knowledge_version_id': knowledge_version_id}).one())\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_(\"Modify knowledge version information\"),\n            summary=_(\"Modify knowledge version information\"),\n            operation_id=_(\"Modify knowledge version information\"),  # type: ignore\n            parameters=KnowledgeVersionOperateAPI.get_parameters(),\n            request=None,\n            responses=KnowledgeVersionOperateAPI.get_response(),\n            tags=[_('Knowledge/Version')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_knowledge_permission(),\n                         PermissionConstants.KNOWLEDGE_WORKFLOW_EDIT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Knowledge', operate=\"Modify knowledge version information\",\n             get_operation_object=lambda r, k: get_knowledge_operation_object(k.get('knowledge_id')),\n             )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, knowledge_version_id: str):\n            return result.success(\n                KnowledgeWorkflowVersionSerializer.Operate(\n                    data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id,\n                          'knowledge_version_id': knowledge_version_id,\n                          'user_id': request.user.id}).edit(\n                    request.data))\n"
  },
  {
    "path": "apps/knowledge/views/paragraph.py",
    "content": "from django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.views import APIView\nfrom rest_framework.views import Request\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result\nfrom common.utils.common import query_params_to_single_dict\nfrom knowledge.api.paragraph import ParagraphReadAPI, ParagraphCreateAPI, ParagraphBatchDeleteAPI, ParagraphEditAPI, \\\n    ParagraphGetAPI, ProblemCreateAPI, UnAssociationAPI, AssociationAPI, ParagraphPageAPI, \\\n    ParagraphBatchGenerateRelatedAPI, ParagraphMigrateAPI, ParagraphAdjustOrderAPI\nfrom knowledge.serializers.common import get_knowledge_operation_object\nfrom knowledge.serializers.paragraph import ParagraphSerializers\nfrom knowledge.views import get_knowledge_document_operation_object, get_document_operation_object\n\n\nclass ParagraphView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        summary=_('Paragraph list'),\n        description=_('Paragraph list'),\n        operation_id=_('Paragraph list'),  # type: ignore\n        parameters=ParagraphReadAPI.get_parameters(),\n        responses=ParagraphReadAPI.get_response(),\n        tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n        q = ParagraphSerializers.Query(\n            data={\n                **query_params_to_single_dict(request.query_params),\n                'workspace_id': workspace_id,\n                'knowledge_id': knowledge_id,\n                'document_id': document_id\n            }\n        )\n        return result.success(q.list())\n\n    @extend_schema(\n        summary=_('Create Paragraph'),\n        operation_id=_('Create Paragraph'),  # type: ignore\n        parameters=ParagraphCreateAPI.get_parameters(),\n        request=ParagraphCreateAPI.get_request(),\n        responses=ParagraphCreateAPI.get_response(),\n        tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    @log(\n        menu='Paragraph', operate='Create Paragraph',\n        get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n            get_knowledge_operation_object(keywords.get('knowledge_id')),\n            get_knowledge_operation_object(keywords.get('knowledge_id')),\n            get_document_operation_object(keywords.get('document_id'))\n        ),\n    )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n        return result.success(ParagraphSerializers.Create(\n            data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id}\n        ).save(request.data))\n\n    class BatchDelete(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Batch Paragraph'),\n            description=_('Batch Paragraph'),\n            operation_id=_('Batch Paragraph'),  # type: ignore\n            parameters=ParagraphBatchDeleteAPI.get_parameters(),\n            request=ParagraphBatchDeleteAPI.get_request(),\n            responses=ParagraphBatchDeleteAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(ParagraphSerializers.Batch(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id}\n            ).batch_delete(request.data))\n\n    class BatchMigrate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Migrate paragraphs in batches'),\n            operation_id=_('Migrate paragraphs in batches'),  # type: ignore\n            parameters=ParagraphMigrateAPI.get_parameters(),\n            request=ParagraphMigrateAPI.get_request(),\n            responses=ParagraphMigrateAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Paragraph', operate='Migrate paragraphs in batches',\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str,\n                target_knowledge_id: str, target_document_id):\n            return result.success(\n                ParagraphSerializers.Migrate(data={\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'target_knowledge_id': target_knowledge_id,\n                    'document_id': document_id,\n                    'target_document_id': target_document_id,\n                    'paragraph_id_list': request.data.get('id_list')\n                }).migrate())\n\n    class BatchGenerateRelated(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Batch Generate Related'),\n            description=_('Batch Generate Related'),\n            operation_id=_('Batch Generate Related'),  # type: ignore\n            parameters=ParagraphBatchGenerateRelatedAPI.get_parameters(),\n            request=ParagraphBatchGenerateRelatedAPI.get_request(),\n            responses=ParagraphBatchGenerateRelatedAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_GENERATE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_GENERATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Paragraph', operate='Batch generate related',\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(ParagraphSerializers.Batch(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'document_id': document_id}\n            ).batch_generate_related(request.data))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Modify paragraph data'),\n            description=_('Modify paragraph data'),\n            operation_id=_('Modify paragraph data'),  # type: ignore\n            parameters=ParagraphEditAPI.get_parameters(),\n            request=ParagraphEditAPI.get_request(),\n            responses=ParagraphEditAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Paragraph', operate='Modify paragraph data',\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, paragraph_id: str):\n            o = ParagraphSerializers.Operate(\n                data={\n                    'workspace_id': workspace_id,\n                    \"paragraph_id\": paragraph_id,\n                    'knowledge_id': knowledge_id,\n                    'document_id': document_id\n                }\n            )\n            o.is_valid(raise_exception=True)\n            return result.success(o.edit(request.data))\n\n        @extend_schema(\n            methods=['GET'],\n            summary=_('Get paragraph details'),\n            description=_('Get paragraph details'),\n            operation_id=_('Get paragraph details'),  # type: ignore\n            parameters=ParagraphGetAPI.get_parameters(),\n            responses=ParagraphGetAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, paragraph_id: str):\n            o = ParagraphSerializers.Operate(\n                data={\n                    'workspace_id': workspace_id,\n                    \"paragraph_id\": paragraph_id,\n                    'knowledge_id': knowledge_id,\n                    'document_id': document_id\n                }\n            )\n            o.is_valid(raise_exception=True)\n            return result.success(o.one())\n\n        @extend_schema(\n            methods=['DELETE'],\n            summary=_('Delete paragraph'),\n            description=_('Delete paragraph'),\n            operation_id=_('Delete paragraph'),  # type: ignore\n            parameters=ParagraphGetAPI.get_parameters(),\n            responses=ParagraphGetAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')])  # type: ignore\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Paragraph', operate='Delete paragraph',\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def delete(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, paragraph_id: str):\n            o = ParagraphSerializers.Operate(\n                data={\n                    'workspace_id': workspace_id,\n                    \"paragraph_id\": paragraph_id,\n                    'knowledge_id': knowledge_id,\n                    'document_id': document_id\n                }\n            )\n            o.is_valid(raise_exception=True)\n            return result.success(o.delete())\n\n    class Problem(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            summary=_('Add associated questions'),\n            description=_('Add associated questions'),\n            operation_id=_('Add associated questions'),  # type: ignore\n            parameters=ProblemCreateAPI.get_parameters(),\n            request=ProblemCreateAPI.get_request(),\n            responses=ProblemCreateAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Paragraph', operate='Add associated questions',\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def post(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, paragraph_id: str):\n            return result.success(ParagraphSerializers.Problem(\n                data={\n                    'workspace_id': workspace_id,\n                    \"knowledge_id\": knowledge_id,\n                    'document_id': document_id,\n                    'paragraph_id': paragraph_id\n                }\n            ).save(request.data, with_valid=True))\n\n        @extend_schema(\n            methods=['GET'],\n            summary=_('Get a list of paragraph questions'),\n            description=_('Get a list of paragraph questions'),\n            operation_id=_('Get a list of paragraph questions'),  # type: ignore\n            parameters=ParagraphGetAPI.get_parameters(),\n            responses=ParagraphGetAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str, paragraph_id: str):\n            return result.success(ParagraphSerializers.Problem(\n                data={\n                    'workspace_id': workspace_id,\n                    \"knowledge_id\": knowledge_id,\n                    'document_id': document_id,\n                    'paragraph_id': paragraph_id\n                }\n            ).list(with_valid=True))\n\n    class UnAssociation(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Disassociation issue'),\n            description=_('Disassociation issue'),\n            operation_id=_('Disassociation issue'),  # type: ignore\n            parameters=UnAssociationAPI.get_parameters(),\n            request=UnAssociationAPI.get_request(),\n            responses=UnAssociationAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_PROBLEM_RELATE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_PROBLEM_RELATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Paragraph', operate='Disassociation issue',\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            )\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(ParagraphSerializers.Association(\n                data={\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'document_id': document_id,\n                    'paragraph_id': request.query_params.get('paragraph_id'),\n                    'problem_id': request.query_params.get('problem_id')\n                }\n            ).un_association())\n\n    class Association(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Related questions'),\n            description=_('Related questions'),\n            operation_id=_('Related questions'),  # type: ignore\n            parameters=AssociationAPI.get_parameters(),\n            request=AssociationAPI.get_request(),\n            responses=AssociationAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_PROBLEM_RELATE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_PROBLEM_RELATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='Paragraph', operate='Related questions',\n            get_operation_object=lambda r, keywords: get_knowledge_document_operation_object(\n                get_knowledge_operation_object(keywords.get('knowledge_id')),\n                get_document_operation_object(keywords.get('document_id'))\n            ),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(ParagraphSerializers.Association(\n                data={\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'document_id': document_id,\n                    'paragraph_id': request.query_params.get('paragraph_id'),\n                    'problem_id': request.query_params.get('problem_id')\n                }\n            ).association())\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            summary=_('Get paragraph list by pagination'),\n            description=_('Get paragraph list by pagination'),\n            operation_id=_('Get paragraph list by pagination'),  # type: ignore\n            parameters=ParagraphPageAPI.get_parameters(),\n            responses=ParagraphPageAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def get(self, request: Request,\n                workspace_id: str, knowledge_id: str, document_id: str, current_page: int, page_size: int):\n            d = ParagraphSerializers.Query(\n                data={\n                    **query_params_to_single_dict(request.query_params),\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'document_id': document_id\n                }\n            )\n            return result.success(d.page(current_page, page_size))\n\n    class AdjustPosition(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Adjust paragraph position'),\n            description=_('Adjust paragraph position'),\n            operation_id=_('Adjust paragraph position'),  # type: ignore\n            parameters=ParagraphAdjustOrderAPI.get_parameters(),\n            request=ParagraphAdjustOrderAPI.get_request(),\n            responses=ParagraphAdjustOrderAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, document_id: str):\n            return result.success(ParagraphSerializers.AdjustPosition(\n                data={\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'document_id': document_id,\n                    'paragraph_id': request.query_params.get('paragraph_id'),\n                }\n            ).adjust_position(request.query_params.get('new_position')))\n"
  },
  {
    "path": "apps/knowledge/views/problem.py",
    "content": "from django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.views import APIView\nfrom rest_framework.views import Request\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result\nfrom common.utils.common import query_params_to_single_dict\nfrom knowledge.api.problem import ProblemReadAPI, ProblemBatchCreateAPI, BatchAssociationAPI, BatchDeleteAPI, \\\n    ProblemPageAPI, ProblemDeleteAPI, ProblemEditAPI, ProblemParagraphAPI\nfrom knowledge.serializers.common import get_knowledge_operation_object\nfrom knowledge.serializers.problem import ProblemSerializers\n\n\nclass ProblemView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        summary=_('Question list'),\n        description=_('Question list'),\n        operation_id=_('Question list'),  # type: ignore\n        parameters=ProblemReadAPI.get_parameters(),\n        responses=ProblemReadAPI.get_response(),\n        tags=[_('Knowledge Base/Documentation/Paragraph/Question')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    def get(self, request: Request, workspace_id: str, knowledge_id: str):\n        q = ProblemSerializers.Query(\n            data={\n                **query_params_to_single_dict(request.query_params),\n                'workspace_id': workspace_id,\n                'knowledge_id': knowledge_id\n            }\n        )\n        q.is_valid(raise_exception=True)\n        return result.success(q.list())\n\n    @extend_schema(\n        methods=['POST'],\n        summary=_('Create question'),\n        description=_('Create question'),\n        operation_id=_('Create question'),  # type: ignore\n        parameters=ProblemBatchCreateAPI.get_parameters(),\n        responses=ProblemBatchCreateAPI.get_response(),\n        request=ProblemBatchCreateAPI.get_request(),\n        tags=[_('Knowledge Base/Documentation/Paragraph/Question')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_PROBLEM_CREATE.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_PROBLEM_CREATE.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    @log(\n        menu='problem', operate='Create question',\n        get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))\n        ,\n    )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str):\n        return result.success(ProblemSerializers.Create(\n            data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id}\n        ).batch(request.data))\n\n    class Paragraph(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Get a list of associated paragraphs'),\n            description=_('Get a list of associated paragraphs'),\n            operation_id=_('Get a list of associated paragraphs'),  # type: ignore\n            parameters=ProblemParagraphAPI.get_parameters(),\n            responses=ProblemParagraphAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph/Question')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, problem_id: str):\n            return result.success(ProblemSerializers.Operate(\n                data={\n                    **query_params_to_single_dict(request.query_params),\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'problem_id': problem_id\n                }\n            ).list_paragraph())\n\n    class BatchAssociation(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Batch associated paragraphs'),\n            description=_('Batch associated paragraphs'),\n            operation_id=_('Batch associated paragraphs'),  # type: ignore\n            request=BatchAssociationAPI.get_request(),\n            parameters=BatchAssociationAPI.get_parameters(),\n            responses=BatchAssociationAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph/Question')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='problem', operate='Batch associated paragraphs',\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(ProblemSerializers.BatchOperate(\n                data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n            ).association(request.data))\n\n    class BatchDelete(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Batch deletion issues'),\n            description=_('Batch deletion issues'),\n            operation_id=_('Batch deletion issues'),  # type: ignore\n            request=BatchDeleteAPI.get_request(),\n            parameters=BatchDeleteAPI.get_parameters(),\n            responses=BatchDeleteAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph/Question')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='problem', operate='Batch deletion issues',\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(ProblemSerializers.BatchOperate(\n                data={'knowledge_id': knowledge_id, 'workspace_id': workspace_id}\n            ).delete(request.data))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['DELETE'],\n            summary=_('Delete question'),\n            description=_('Delete question'),\n            operation_id=_('Delete question'),  # type: ignore\n            parameters=ProblemDeleteAPI.get_parameters(),\n            responses=ProblemDeleteAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph/Question')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_PROBLEM_DELETE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_PROBLEM_DELETE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='problem', operate='Delete question',\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n\n        )\n        def delete(self, request: Request, workspace_id: str, knowledge_id: str, problem_id: str):\n            return result.success(ProblemSerializers.Operate(\n                data={\n                    **query_params_to_single_dict(request.query_params),\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'problem_id': problem_id\n                }\n            ).delete())\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Modify question'),\n            description=_('Modify question'),\n            operation_id=_('Modify question'),  # type: ignore\n            parameters=ProblemEditAPI.get_parameters(),\n            request=ProblemEditAPI.get_request(),\n            responses=ProblemEditAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph/Question')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_PROBLEM_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='problem', operate='Modify question',\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id')),\n\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, problem_id: str):\n            return result.success(ProblemSerializers.Operate(\n                data={\n                    **query_params_to_single_dict(request.query_params),\n                    'workspace_id': workspace_id,\n                    'knowledge_id': knowledge_id,\n                    'problem_id': problem_id\n                }\n            ).edit(request.data))\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_('Get the list of questions by page'),\n            description=_('Get the list of questions by page'),\n            operation_id=_('Get the list of questions by page'),  # type: ignore\n            parameters=ProblemPageAPI.get_parameters(),\n            responses=ProblemPageAPI.get_response(),\n            tags=[_('Knowledge Base/Documentation/Paragraph/Question')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_PROBLEM_READ.get_workspace_permission_workspace_manage_role(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_DOCUMENT_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        def get(self, request: Request, workspace_id: str, knowledge_id: str, current_page, page_size):\n            d = ProblemSerializers.Query(\n                data={\n                    **query_params_to_single_dict(request.query_params),\n                    'knowledge_id': knowledge_id,\n                    'workspace_id': workspace_id\n                }\n            )\n            d.is_valid(raise_exception=True)\n            return result.success(d.page(current_page, page_size))\n"
  },
  {
    "path": "apps/knowledge/views/tag.py",
    "content": "from django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result\nfrom knowledge.api.tag import TagCreateAPI, TagDeleteAPI, TagEditAPI\nfrom knowledge.serializers.common import get_knowledge_operation_object\nfrom knowledge.serializers.tag import TagSerializers\n\n\nclass KnowledgeTagView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        summary=_(\"Create Knowledge Tag\"),\n        description=_(\"Create a new knowledge tag\"),\n        parameters=TagCreateAPI.get_parameters(),\n        request=TagCreateAPI.get_request(),\n        responses=TagCreateAPI.get_response(),\n        tags=[_('Knowledge Base/Tag')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_TAG_CREATE.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_TAG_CREATE.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    @log(\n        menu='tag', operate=\"Create a knowledge tag\",\n        get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))\n    )\n    def post(self, request: Request, workspace_id: str, knowledge_id: str):\n        return result.success(TagSerializers.Create(\n            data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tags': request.data}\n        ).insert())\n\n    @extend_schema(\n        summary=_(\"Get Knowledge Tag\"),\n        description=_(\"Get knowledge tag\"),\n        parameters=TagCreateAPI.get_parameters(),\n        request=TagCreateAPI.get_request(),\n        responses=TagCreateAPI.get_response(),\n        tags=[_('Knowledge Base/Tag')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_knowledge_permission(),\n        PermissionConstants.KNOWLEDGE_TAG_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n    )\n    @log(\n        menu='tag', operate=\"Create a knowledge tag\",\n        get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))\n    )\n    def get(self, request: Request, workspace_id: str, knowledge_id: str):\n        return result.success(TagSerializers.Query(data={\n            'workspace_id': workspace_id,\n            'knowledge_id': knowledge_id,\n            'name': request.query_params.get('name')\n        }).list())\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_(\"Update Knowledge Tag\"),\n            description=_(\"Update a knowledge tag\"),\n            parameters=TagEditAPI.get_parameters(),\n            request=TagEditAPI.get_request(),\n            responses=TagEditAPI.get_response(),\n            tags=[_('Knowledge Base/Tag')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_TAG_EDIT.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_TAG_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='tag', operate=\"Update a knowledge tag\",\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str, tag_id: str):\n            return result.success(TagSerializers.Operate(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tag_id': tag_id}\n            ).edit(request.data))\n\n    class Delete(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_(\"Delete Knowledge Tag\"),\n            description=_(\"Delete a knowledge tag\"),\n            parameters=TagDeleteAPI.get_parameters(),\n            request=TagDeleteAPI.get_request(),\n            responses=TagDeleteAPI.get_response(),\n            tags=[_('Knowledge Base/Tag')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_TAG_DELETE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_TAG_DELETE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='tag', operate=\"Delete a knowledge tag\",\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))\n        )\n        def delete(self, request: Request, workspace_id: str, knowledge_id: str, tag_id: str, delete_type: str):\n            return result.success(TagSerializers.Operate(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tag_id': tag_id}\n            ).delete(delete_type))\n\n    class BatchDelete(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            summary=_(\"Batch Delete Knowledge Tag\"),\n            description=_(\"Batch Delete a knowledge tag\"),\n            parameters=TagDeleteAPI.get_parameters(),\n            request=TagDeleteAPI.get_request(),\n            responses=TagDeleteAPI.get_response(),\n            tags=[_('Knowledge Base/Tag')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.KNOWLEDGE_TAG_DELETE.get_workspace_knowledge_permission(),\n            PermissionConstants.KNOWLEDGE_TAG_DELETE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.KNOWLEDGE.get_workspace_knowledge_permission()], CompareConstants.AND),\n        )\n        @log(\n            menu='tag', operate=\"Batch Delete knowledge tag\",\n            get_operation_object=lambda r, keywords: get_knowledge_operation_object(keywords.get('knowledge_id'))\n        )\n        def put(self, request: Request, workspace_id: str, knowledge_id: str):\n            return result.success(TagSerializers.BatchDelete(\n                data={'workspace_id': workspace_id, 'knowledge_id': knowledge_id, 'tag_ids': request.data}\n            ).batch_delete())\n"
  },
  {
    "path": "apps/local_model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/local_model/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "apps/local_model/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass LocalModelConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'local_model'\n"
  },
  {
    "path": "apps/local_model/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "apps/local_model/models/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2023/9/25 15:04\n    @desc:\n\"\"\"\n\nfrom .model_management import *\n"
  },
  {
    "path": "apps/local_model/models/model_management.py",
    "content": "# coding=utf-8\nimport uuid_utils.compat as uuid\n\nfrom django.db import models\n\nfrom common.mixins.app_model_mixin import AppModelMixin\nfrom local_model.models.user import User\n\n\nclass Status(models.TextChoices):\n    \"\"\"系统设置类型\"\"\"\n    SUCCESS = \"SUCCESS\", '成功'\n\n    ERROR = \"ERROR\", \"失败\"\n\n    DOWNLOAD = \"DOWNLOAD\", '下载中'\n\n    PAUSE_DOWNLOAD = \"PAUSE_DOWNLOAD\", '暂停下载'\n\n\nclass Model(AppModelMixin):\n    \"\"\"\n    模型数据\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n\n    name = models.CharField(max_length=128, verbose_name=\"名称\", db_index=True)\n\n    status = models.CharField(max_length=20, verbose_name='设置类型', choices=Status.choices,\n                              default=Status.SUCCESS, db_index=True)\n\n    model_type = models.CharField(max_length=128, verbose_name=\"模型类型\", db_index=True)\n\n    model_name = models.CharField(max_length=128, verbose_name=\"模型名称\", db_index=True)\n\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n\n    provider = models.CharField(max_length=128, verbose_name='供应商', db_index=True)\n\n    credential = models.CharField(max_length=102400, verbose_name=\"模型认证信息\")\n\n    meta = models.JSONField(verbose_name=\"模型元数据,用于存储下载,或者错误信息\", default=dict)\n\n    model_params_form = models.JSONField(verbose_name=\"模型参数配置\", default=list)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n\n    class Meta:\n        db_table = \"model\"\n        unique_together = ['name', 'workspace_id']\n"
  },
  {
    "path": "apps/local_model/models/system_setting.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： system_management.py\n    @date：2024/3/19 13:47\n    @desc: 邮箱管理\n\"\"\"\n\nfrom django.db import models\n\nfrom common.mixins.app_model_mixin import AppModelMixin\n\n\nclass SettingType(models.IntegerChoices):\n    \"\"\"系统设置类型\"\"\"\n    EMAIL = 0, '邮箱'\n\n    RSA = 1, \"私钥秘钥\"\n\n    LOG = 2, \"日志清理时间\"\n\n\nclass SystemSetting(AppModelMixin):\n    \"\"\"\n     系统设置\n    \"\"\"\n    type = models.IntegerField(primary_key=True, verbose_name='设置类型', choices=SettingType.choices,\n                               default=SettingType.EMAIL)\n\n    meta = models.JSONField(verbose_name=\"配置数据\", default=dict)\n\n    class Meta:\n        db_table = \"system_setting\"\n"
  },
  {
    "path": "apps/local_model/models/user.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： user.py\n    @date：2025/4/14 10:20\n    @desc:\n\"\"\"\nimport uuid_utils.compat as uuid\n\nfrom django.db import models\n\nfrom common.utils.common import password_encrypt\n\n\nclass User(models.Model):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    email = models.EmailField(unique=True, null=True, blank=True, verbose_name=\"邮箱\", db_index=True)\n    phone = models.CharField(max_length=20, verbose_name=\"电话\", default=\"\", db_index=True)\n    nick_name = models.CharField(max_length=150, verbose_name=\"昵称\", unique=True, db_index=True)\n    username = models.CharField(max_length=150, unique=True, verbose_name=\"用户名\", db_index=True)\n    password = models.CharField(max_length=150, verbose_name=\"密码\")\n    role = models.CharField(max_length=150, verbose_name=\"角色\")\n    source = models.CharField(max_length=10, verbose_name=\"来源\", default=\"LOCAL\", db_index=True)\n    is_active = models.BooleanField(default=True, db_index=True)\n    language = models.CharField(max_length=10, verbose_name=\"语言\", null=True, default=None)\n    create_time = models.DateTimeField(verbose_name=\"创建时间\", auto_now_add=True, null=True, db_index=True)\n    update_time = models.DateTimeField(verbose_name=\"修改时间\", auto_now=True, null=True, db_index=True)\n\n    USERNAME_FIELD = 'username'\n    REQUIRED_FIELDS = []\n\n    class Meta:\n        db_table = \"user\"\n\n    def set_password(self, row_password):\n        self.password = password_encrypt(row_password)\n        self._password = row_password\n"
  },
  {
    "path": "apps/local_model/serializers/__init__.py",
    "content": "# coding=utf-8\n"
  },
  {
    "path": "apps/local_model/serializers/model_apply_serializers.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： model_apply_serializers.py\n    @date：2024/8/20 20:39\n    @desc:\n\"\"\"\nimport json\nimport threading\nimport time\n\nfrom django.db import connection\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom langchain_core.documents import Document\nfrom rest_framework import serializers\n\nfrom local_model.models import Model\nfrom local_model.serializers.rsa_util import rsa_long_decrypt\nfrom models_provider.impl.local_model_provider.local_model_provider import LocalModelProvider\n\nfrom common.cache.mem_cache import MemCache\n\n_lock = threading.Lock()\nlocks = {}\n\n\nclass ModelManage:\n    cache = MemCache('model', {})\n    up_clear_time = time.time()\n\n    @staticmethod\n    def _get_lock(_id):\n        lock = locks.get(_id)\n        if lock is None:\n            with _lock:\n                lock = locks.get(_id)\n                if lock is None:\n                    lock = threading.Lock()\n                    locks[_id] = lock\n\n        return lock\n\n    @staticmethod\n    def get_model(_id, get_model):\n        model_instance = ModelManage.cache.get(_id)\n        if model_instance is None:\n            lock = ModelManage._get_lock(_id)\n            with lock:\n                model_instance = ModelManage.cache.get(_id)\n                if model_instance is None:\n                    model_instance = get_model(_id)\n                    ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8)\n        else:\n            if model_instance.is_cache_model():\n                ModelManage.cache.touch(_id, timeout=60 * 60 * 8)\n            else:\n                model_instance = get_model(_id)\n                ModelManage.cache.set(_id, model_instance, timeout=60 * 60 * 8)\n        ModelManage.clear_timeout_cache()\n        return model_instance\n\n    @staticmethod\n    def clear_timeout_cache():\n        if time.time() - ModelManage.up_clear_time > 60 * 60:\n            threading.Thread(target=lambda: ModelManage.cache.clear_timeout_data()).start()\n            ModelManage.up_clear_time = time.time()\n\n    @staticmethod\n    def delete_key(_id):\n        if ModelManage.cache.has_key(_id):\n            ModelManage.cache.delete(_id)\n\n\ndef get_local_model(model, **kwargs):\n    return LocalModelProvider().get_model(model.model_type, model.model_name,\n                                          json.loads(\n                                              rsa_long_decrypt(model.credential)),\n                                          model_id=model.id,\n                                          streaming=True, **kwargs)\n\n\ndef get_embedding_model(model_id):\n    model = QuerySet(Model).filter(id=model_id).first()\n    # 手动关闭数据库连接\n    connection.close()\n    embedding_model = ModelManage.get_model(model_id,\n                                            lambda _id: get_local_model(model, use_local=True))\n    return embedding_model\n\n\nclass EmbedDocuments(serializers.Serializer):\n    texts = serializers.ListField(required=True, child=serializers.CharField(required=True,\n                                                                             label=_('vector text')),\n                                  label=_('vector text list')),\n\n\nclass EmbedQuery(serializers.Serializer):\n    text = serializers.CharField(required=True, label=_('vector text'))\n\n\nclass CompressDocument(serializers.Serializer):\n    page_content = serializers.CharField(required=True, label=_('text'))\n    metadata = serializers.DictField(required=False, label=_('metadata'))\n\n\nclass CompressDocuments(serializers.Serializer):\n    documents = CompressDocument(required=True, many=True)\n    query = serializers.CharField(required=True, label=_('query'))\n\n\nclass ValidateModelSerializers(serializers.Serializer):\n    model_name = serializers.CharField(required=True, label=_('model_name'))\n\n    model_type = serializers.CharField(required=True, label=_('model_type'))\n\n    model_credential = serializers.DictField(required=True, label=\"credential\")\n\n    def validate_model(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        LocalModelProvider().is_valid_credential(self.data.get('model_type'), self.data.get('model_name'),\n                                                 self.data.get('model_credential'), model_params={},\n                                                 raise_exception=True)\n\n\nclass ModelApplySerializers(serializers.Serializer):\n    model_id = serializers.UUIDField(required=True, label=_('model id'))\n\n    def embed_documents(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            EmbedDocuments(data=instance).is_valid(raise_exception=True)\n\n        model = get_embedding_model(self.data.get('model_id'))\n        return model.embed_documents(instance.getlist('texts'))\n\n    def embed_query(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            EmbedQuery(data=instance).is_valid(raise_exception=True)\n\n        model = get_embedding_model(self.data.get('model_id'))\n        return model.embed_query(instance.get('text'))\n\n    def compress_documents(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            CompressDocuments(data=instance).is_valid(raise_exception=True)\n        model = get_embedding_model(self.data.get('model_id'))\n        return [{'page_content': d.page_content, 'metadata': d.metadata} for d in model.compress_documents(\n            [Document(page_content=document.get('page_content'), metadata=document.get('metadata')) for document in\n             instance.get('documents')], instance.get('query'))]\n\n    def unload(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        ModelManage.delete_key(self.data.get('model_id'))\n        return True\n"
  },
  {
    "path": "apps/local_model/serializers/rsa_util.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： rsa_util.py\n    @date：2023/11/3 11:13\n    @desc:\n\"\"\"\nimport base64\nimport threading\n\nfrom Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher\nfrom Crypto.PublicKey import RSA\nfrom django.core import cache\nfrom django.db.models import QuerySet\n\nfrom common.constants.cache_version import Cache_Version\nfrom local_model.models.system_setting import SystemSetting, SettingType\n\nlock = threading.Lock()\nrsa_cache = cache.cache\ncache_key = \"rsa_key\"\n# 对密钥加密的密码\nsecret_code = \"mac_kb_password\"\n\n\ndef generate():\n    \"\"\"\n    生成 私钥秘钥对\n    :return:{key:'公钥',value:'私钥'}\n    \"\"\"\n    # 生成一个 2048 位的密钥\n    key = RSA.generate(2048)\n\n    # 获取私钥\n    encrypted_key = key.export_key(passphrase=secret_code, pkcs=8,\n                                   protection=\"scryptAndAES128-CBC\")\n    return {'key': key.publickey().export_key(), 'value': encrypted_key}\n\n\ndef get_key_pair():\n    rsa_value = rsa_cache.get(cache_key)\n    if rsa_value is None:\n        with lock:\n            rsa_value = rsa_cache.get(cache_key)\n            if rsa_value is not None:\n                return rsa_value\n            rsa_value = get_key_pair_by_sql()\n            version, get_key = Cache_Version.SYSTEM.value\n            rsa_cache.set(get_key(key='rsa_key'), rsa_value, timeout=None, version=version)\n    return rsa_value\n\n\ndef get_key_pair_by_sql():\n    system_setting = QuerySet(SystemSetting).filter(type=SettingType.RSA.value).first()\n    if system_setting is None:\n        kv = generate()\n        system_setting = SystemSetting(type=SettingType.RSA.value,\n                                       meta={'key': kv.get('key').decode(), 'value': kv.get('value').decode()})\n        system_setting.save()\n    return system_setting.meta\n\n\ndef encrypt(msg, public_key: str | None = None):\n    \"\"\"\n    加密\n    :param msg:        加密数据\n    :param public_key: 公钥\n    :return: 加密后的数据\n    \"\"\"\n    if public_key is None:\n        public_key = get_key_pair().get('key')\n    cipher = PKCS1_cipher.new(RSA.importKey(public_key))\n    encrypt_msg = cipher.encrypt(msg.encode(\"utf-8\"))\n    return base64.b64encode(encrypt_msg).decode()\n\n\ndef decrypt(msg, pri_key: str | None = None):\n    \"\"\"\n    解密\n    :param msg: 需要解密的数据\n    :param pri_key: 私钥\n    :return: 解密后数据\n    \"\"\"\n    if pri_key is None:\n        pri_key = get_key_pair().get('value')\n    cipher = PKCS1_cipher.new(RSA.importKey(pri_key, passphrase=secret_code))\n    decrypt_data = cipher.decrypt(base64.b64decode(msg), 0)\n    return decrypt_data.decode(\"utf-8\")\n\n\ndef rsa_long_encrypt(message, public_key: str | None = None, length=200):\n    \"\"\"\n    超长文本加密\n\n    :param message:         需要加密的字符串\n    :param public_key   公钥\n    :param length:      1024bit的证书用100， 2048bit的证书用 200\n    :return: 加密后的数据\n    \"\"\"\n    # 读取公钥\n    if public_key is None:\n        public_key = get_key_pair().get('key')\n    cipher = PKCS1_cipher.new(RSA.importKey(extern_key=public_key,\n                                            passphrase=secret_code))\n    # 处理：Plaintext is too long. 分段加密\n    if len(message) <= length:\n        # 对编码的数据进行加密，并通过base64进行编码\n        result = base64.b64encode(cipher.encrypt(message.encode('utf-8')))\n    else:\n        rsa_text = []\n        # 对编码后的数据进行切片，原因：加密长度不能过长\n        for i in range(0, len(message), length):\n            cont = message[i:i + length]\n            # 对切片后的数据进行加密，并新增到text后面\n            rsa_text.append(cipher.encrypt(cont.encode('utf-8')))\n        # 加密完进行拼接\n        cipher_text = b''.join(rsa_text)\n        # base64进行编码\n        result = base64.b64encode(cipher_text)\n    return result.decode()\n\n\ndef rsa_long_decrypt(message, pri_key: str | None = None, length=256):\n    \"\"\"\n    超长文本解密，默认不加密\n    :param  message:    需要解密的数据\n    :param  pri_key:    秘钥\n    :param  length :     1024bit的证书用128，2048bit证书用256位\n    :return: 解密后的数据\n    \"\"\"\n    if pri_key is None:\n        pri_key = get_key_pair().get('value')\n    cipher = PKCS1_cipher.new(RSA.importKey(pri_key, passphrase=secret_code))\n    base64_de = base64.b64decode(message)\n    res = []\n    for i in range(0, len(base64_de), length):\n        res.append(cipher.decrypt(base64_de[i:i + length], 0))\n    return b\"\".join(res).decode()\n"
  },
  {
    "path": "apps/local_model/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/local_model/urls.py",
    "content": "import os\n\nfrom django.urls import path\n\nfrom . import views\n\napp_name = \"local_model\"\n# @formatter:off\nurlpatterns = [\n    path('model/validate', views.LocalModelApply.Validate.as_view()),\n    path('model/<str:model_id>/embed_documents', views.LocalModelApply.EmbedDocuments.as_view()),\n    path('model/<str:model_id>/embed_query', views.LocalModelApply.EmbedQuery.as_view()),\n    path('model/<str:model_id>/compress_documents', views.LocalModelApply.CompressDocuments.as_view()),\n    path('model/<str:model_id>/unload', views.LocalModelApply.Unload.as_view()),\n    ]\n"
  },
  {
    "path": "apps/local_model/views/__init__.py",
    "content": "# coding=utf-8\nfrom .model_apply import *\n"
  },
  {
    "path": "apps/local_model/views/model_apply.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： model_apply.py\n    @date：2024/8/20 20:38\n    @desc:\n\"\"\"\nfrom urllib.request import Request\n\nfrom rest_framework.views import APIView\n\nfrom common.result import result\nfrom local_model.serializers.model_apply_serializers import ModelApplySerializers, ValidateModelSerializers\n\n\nclass LocalModelApply(APIView):\n    class EmbedDocuments(APIView):\n\n        def post(self, request: Request, model_id):\n            return result.success(\n                ModelApplySerializers(data={'model_id': model_id}).embed_documents(request.data))\n\n    class EmbedQuery(APIView):\n\n        def post(self, request: Request, model_id):\n            return result.success(\n                ModelApplySerializers(data={'model_id': model_id}).embed_query(request.data))\n\n    class CompressDocuments(APIView):\n\n        def post(self, request: Request, model_id):\n            return result.success(\n                ModelApplySerializers(data={'model_id': model_id}).compress_documents(request.data))\n\n    class Unload(APIView):\n        def post(self, request: Request, model_id):\n            return result.success(\n                ModelApplySerializers(data={'model_id': model_id}).compress_documents(request.data))\n\n    class Validate(APIView):\n        def post(self, request: Request):\n            return result.success(ValidateModelSerializers(data=request.data).validate_model())\n"
  },
  {
    "path": "apps/locales/en_US/LC_MESSAGES/django.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\n#, fuzzy\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2025-06-18 17:34+0800\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: apps/application/api/application_api.py:21\n#: apps/application/serializers/application.py:153\nmsgid \"Workflow Objects\"\nmsgstr \"\"\n\n#: apps/application/api/application_api.py:52\n#: apps/application/api/application_chat.py:104\n#: apps/application/api/application_chat_record.py:74\n#: apps/role_setting/api/role_setting.py:171 apps/shared/api/shared_tool.py:79\n#: apps/shared/api/shared_tool.py:119 apps/workspace/api/workspace.py:110\n#: apps/xpack/api/user_group.py:61\nmsgid \"Current page\"\nmsgstr \"\"\n\n#: apps/application/api/application_api.py:59\n#: apps/application/api/application_chat.py:111\n#: apps/application/api/application_chat_record.py:81\n#: apps/role_setting/api/role_setting.py:178 apps/shared/api/shared_tool.py:86\n#: apps/shared/api/shared_tool.py:126 apps/workspace/api/workspace.py:117\n#: apps/xpack/api/user_group.py:68\nmsgid \"Page size\"\nmsgstr \"\"\n\n#: apps/application/api/application_api.py:66\n#: apps/application/serializers/application.py:156\n#: apps/application/serializers/application.py:195\n#: apps/application/serializers/application.py:274\n#: apps/folders/serializers/folder.py:97 apps/folders/serializers/folder.py:139\n#: apps/knowledge/serializers/knowledge.py:52\n#: apps/knowledge/serializers/knowledge.py:59\n#: apps/tools/serializers/tool.py:453\n#: apps/xpack/serializers/dataset_lark_serializer.py:55\nmsgid \"folder id\"\nmsgstr \"\"\n\n#: apps/application/api/application_api.py:73\n#: apps/application/serializers/application.py:149\n#: apps/application/serializers/application.py:275\n#: apps/application/serializers/application.py:282\n#: apps/application/serializers/application.py:368\nmsgid \"Application Name\"\nmsgstr \"Agent Name\"\n\n#: apps/application/api/application_api.py:80\n#: apps/application/serializers/application.py:152\n#: apps/application/serializers/application.py:276\n#: apps/application/serializers/application.py:283\n#: apps/application/serializers/application.py:284\n#: apps/application/serializers/application.py:370\nmsgid \"Application Description\"\nmsgstr \"Agent Description\"\n\n#: apps/application/api/application_api.py:87\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:78\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:30\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:47\n#: apps/application/serializers/application.py:99\n#: apps/application/serializers/application.py:277\n#: apps/application/serializers/application.py:295\n#: apps/application/serializers/application.py:413\n#: apps/application/serializers/application.py:540\n#: apps/role_setting/api/role_setting.py:144 apps/tools/serializers/tool.py:357\n#: apps/tools/serializers/tool.py:390 apps/users/api/user.py:52\n#: apps/users/api/user.py:110 apps/users/api/user.py:126\n#: apps/users/serializers/user.py:325 apps/workspace/api/workspace.py:82\n#: apps/workspace/serializers/workspace_serializers.py:270\n#: apps/workspace/serializers/workspace_serializers.py:306\n#: apps/xpack/api/chat_user.py:49 apps/xpack/api/chat_user.py:92\n#: apps/xpack/serializers/chat_user.py:301\nmsgid \"User ID\"\nmsgstr \"\"\n\n#: apps/application/api/application_chat.py:70\n#: apps/application/serializers/application_chat.py:56\nmsgid \"Minimum number of likes\"\nmsgstr \"\"\n\n#: apps/application/api/application_chat.py:76\n#: apps/application/serializers/application_chat.py:58\nmsgid \"Minimum number of clicks\"\nmsgstr \"\"\n\n#: apps/application/api/application_chat.py:82\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:18\n#: apps/application/serializers/application_chat.py:59\nmsgid \"Comparator\"\nmsgstr \"\"\n\n#: apps/application/api/application_chat_record.py:46\n#: apps/application/api/application_chat_record.py:115\n#: apps/application/serializers/application_chat.py:47\n#: apps/application/serializers/application_chat_record.py:76\nmsgid \"Chat ID\"\nmsgstr \"\"\n\n#: apps/application/api/application_chat_record.py:53\n#: apps/application/serializers/application_chat_record.py:77\nmsgid \"Is it in order\"\nmsgstr \"\"\n\n#: apps/application/api/application_chat_record.py:122\nmsgid \"Chat Record ID\"\nmsgstr \"\"\n\n#: apps/application/api/application_chat_record.py:129\n#: apps/shared/api/shared_knowledge.py:235\n#: apps/shared/api/shared_knowledge.py:256 apps/xpack/api/knowledge_lark.py:47\n#: apps/xpack/api/knowledge_lark.py:79 apps/xpack/api/knowledge_lark.py:111\nmsgid \"Knowledge ID\"\nmsgstr \"\"\n\n#: apps/application/api/application_chat_record.py:136\n#: apps/shared/api/shared_knowledge.py:263 apps/xpack/api/knowledge_lark.py:86\nmsgid \"Document ID\"\nmsgstr \"\"\n\n#: apps/application/api/application_chat_record.py:148\nmsgid \"Paragraph ID\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:26\nmsgid \"Model type error\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:36\n#: apps/common/field/common.py:24 apps/common/field/common.py:37\nmsgid \"Message type error\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:55\nmsgid \"Conversation list\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:56\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:29\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:18\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:12\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:13\n#: apps/application/flow/step_node/question_node/i_question_node.py:18\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:12\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:13\n#: apps/application/serializers/application.py:101\n#: apps/application/serializers/application.py:285\n#: apps/knowledge/serializers/common.py:71 apps/shared/api/shared_model.py:76\n#: apps/shared/api/shared_model.py:98\nmsgid \"Model id\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:58\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:29\nmsgid \"Paragraph List\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:60\n#: apps/application/serializers/application_chat.py:182\n#: apps/application/serializers/application_chat_record.py:41\n#: apps/application/serializers/application_chat_record.py:140\n#: apps/application/serializers/application_chat_record.py:179\n#: apps/application/serializers/application_chat_record.py:247\n#: apps/application/serializers/application_chat_record.py:312\n#: apps/chat/serializers/chat.py:98 apps/chat/serializers/chat.py:114\nmsgid \"Conversation ID\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:62\n#: apps/application/flow/step_node/application_node/i_application_node.py:14\n#: apps/application/serializers/application_chat.py:182\n#: apps/chat/serializers/chat.py:40\nmsgid \"User Questions\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:65\nmsgid \"Post-processor\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:68\nmsgid \"Completion Question\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:70\nmsgid \"Streaming Output\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:71\n#: apps/xpack/serializers/resource_chat_user.py:93\nmsgid \"Chat user id\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:73\nmsgid \"Chat user Type\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:76\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:47\nmsgid \"No reference segment settings\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:81\nmsgid \"Model settings\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:84\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:29\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:29\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:28\n#: apps/application/flow/step_node/question_node/i_question_node.py:29\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:20\nmsgid \"Model parameter settings\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:91\nmsgid \"message type error\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:224\n#: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:270\nmsgid \"\"\n\"Sorry, the AI model is not configured. Please go to the application to set \"\n\"up the AI model first.\"\nmsgstr \"Sorry, the agent's AI model is not configured. Please go to the agent settings to set up the AI model first.\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:26\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:24\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:24\n#: apps/application/serializers/application_chat_record.py:172\nmsgid \"question\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:32\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:27\nmsgid \"History Questions\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:34\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:23\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:20\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:18\n#: apps/application/flow/step_node/question_node/i_question_node.py:24\nmsgid \"Number of multi-round conversations\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:37\nmsgid \"Maximum length of the knowledge base paragraph\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:39\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:21\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:16\n#: apps/application/flow/step_node/question_node/i_question_node.py:21\n#: apps/application/serializers/application.py:79\n#: apps/application/serializers/application.py:124\n#: apps/knowledge/serializers/common.py:72\nmsgid \"Prompt word\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:41\nmsgid \"System prompt words (role)\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:44\nmsgid \"Completion problem\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:32\n#: apps/application/serializers/application.py:215\nmsgid \"Question completion prompt\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py:20\n#: apps/application/serializers/common.py:87\n#, python-brace-format\nmsgid \"\"\n\"() contains the user's question. Answer the guessed user's question based on \"\n\"the context ({question}) Requirement: Output a complete question and put it \"\n\"in the <data></data> tag\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:27\nmsgid \"System completes question text\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:30\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:39\nmsgid \"Dataset id list\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:33\nmsgid \"List of document ids to exclude\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:36\nmsgid \"List of exclusion vector ids\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:39\n#: apps/application/flow/step_node/reranker_node/i_reranker_node.py:21\n#: apps/application/flow/step_node/reranker_node/i_reranker_node.py:24\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:24\n#: apps/application/serializers/application.py:84\n#: apps/application/serializers/application_chat.py:185\nmsgid \"Reference segment number\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:42\nmsgid \"Similarity\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:45\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:30\n#: apps/application/serializers/application.py:91\n#: apps/knowledge/serializers/knowledge.py:100\n#: apps/knowledge/serializers/knowledge.py:643\nmsgid \"The type only supports embedding|keywords|blend\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:46\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:31\n#: apps/application/serializers/application.py:92\nmsgid \"Retrieval Mode\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:31\n#: apps/application/serializers/application.py:113\n#: apps/application/serializers/application.py:657\n#: apps/application/serializers/application.py:664\n#: apps/application/serializers/application.py:671\n#: apps/knowledge/serializers/document.py:643\n#: apps/knowledge/serializers/knowledge.py:220\n#: apps/models_provider/serializers/model_serializer.py:116\n#: apps/models_provider/serializers/model_serializer.py:134\n#: apps/models_provider/serializers/model_serializer.py:370\n#: apps/models_provider/tools.py:111 apps/shared/serializers/shared_model.py:32\n#: apps/shared/serializers/shared_model.py:65\n#: apps/shared/serializers/shared_model.py:82\nmsgid \"Model does not exist\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:33\n#, python-brace-format\nmsgid \"No permission to use this model {model_name}\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:42\nmsgid \"\"\n\"The vector model of the associated knowledge base is inconsistent and the \"\n\"segmentation cannot be recalled.\"\nmsgstr \"\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:44\nmsgid \"The knowledge base setting is wrong, please reset the knowledge base\"\nmsgstr \"\"\n\n#: apps/application/flow/common.py:206\n#, python-brace-format\nmsgid \"The branch {branch} of the {node} node needs to be connected\"\nmsgstr \"\"\n\n#: apps/application/flow/common.py:212\n#, python-brace-format\nmsgid \"{node} Nodes cannot be considered as end nodes\"\nmsgstr \"\"\n\n#: apps/application/flow/common.py:226\nmsgid \"The starting node is required\"\nmsgstr \"\"\n\n#: apps/application/flow/common.py:228\nmsgid \"There can only be one starting node\"\nmsgstr \"\"\n\n#: apps/application/flow/common.py:236\n#, python-brace-format\nmsgid \"The node {node} model does not exist\"\nmsgstr \"\"\n\n#: apps/application/flow/common.py:246\n#, python-brace-format\nmsgid \"Node {node} is unavailable\"\nmsgstr \"\"\n\n#: apps/application/flow/common.py:252\n#, python-brace-format\nmsgid \"The library ID of node {node} cannot be empty\"\nmsgstr \"\"\n\n#: apps/application/flow/common.py:255\n#, python-brace-format\nmsgid \"The function library for node {node} is not available\"\nmsgstr \"\"\n\n#: apps/application/flow/common.py:261\nmsgid \"Basic information node is required\"\nmsgstr \"\"\n\n#: apps/application/flow/common.py:263\nmsgid \"There can only be one basic information node\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:20\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:15\n#: apps/application/flow/step_node/question_node/i_question_node.py:20\n#: apps/users/api/user.py:35 apps/users/api/user.py:102\nmsgid \"Role Setting\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:26\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:25\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:30\n#: apps/application/flow/step_node/function_node/i_function_node.py:48\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:26\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:23\n#: apps/application/flow/step_node/question_node/i_question_node.py:27\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:15\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:16\nmsgid \"Whether to return content\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:33\nmsgid \"Context Type\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:35\nmsgid \"Whether to enable MCP\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:36\nmsgid \"MCP Server\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:12\n#: apps/application/serializers/application.py:539\n#: apps/application/serializers/application_access_token.py:44\n#: apps/application/serializers/application_chat.py:38\n#: apps/application/serializers/application_chat.py:54\n#: apps/application/serializers/application_chat_record.py:43\n#: apps/application/serializers/application_chat_record.py:75\n#: apps/application/serializers/application_stats.py:35\n#: apps/application/serializers/application_version.py:21\n#: apps/application/serializers/application_version.py:67\n#: apps/chat/serializers/chat.py:118\n#: apps/chat/serializers/chat_authentication.py:80\n#: apps/xpack/api/application_setting.py:31 apps/xpack/api/platform.py:29\n#: apps/xpack/api/platform.py:70\nmsgid \"Application ID\"\nmsgstr \"Agent ID\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:15\nmsgid \"API Input Fields\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:17\nmsgid \"User Input Fields\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:18\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:25\n#: apps/chat/serializers/chat.py:57 apps/tools/serializers/tool.py:391\nmsgid \"picture\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:19\n#: apps/application/flow/step_node/document_extract_node/i_document_extract_node.py:12\n#: apps/chat/serializers/chat.py:58\nmsgid \"document\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:20\n#: apps/chat/serializers/chat.py:59\nmsgid \"Audio\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:22\n#: apps/chat/serializers/chat.py:62\nmsgid \"Child Nodes\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:23\n#: apps/application/flow/step_node/form_node/i_form_node.py:21\nmsgid \"Form Data\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:57\nmsgid \"\"\n\"Parameter value error: The uploaded document lacks file_id, and the document \"\n\"upload fails\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:66\nmsgid \"\"\n\"Parameter value error: The uploaded image lacks file_id, and the image \"\n\"upload fails\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:76\nmsgid \"\"\n\"Parameter value error: The uploaded audio lacks file_id, and the audio \"\n\"upload fails.\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:19\n#: apps/models_provider/api/provide.py:24\nmsgid \"value\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:20\nmsgid \"Fields\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:24\nmsgid \"Branch id\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:25\nmsgid \"Branch Type\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:26\nmsgid \"Condition or|and\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:20\nmsgid \"Response Type\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:21\n#: apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py:13\nmsgid \"Reference Field\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:23\nmsgid \"Direct answer content\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:31\nmsgid \"Reference field cannot be empty\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:33\nmsgid \"Reference field error\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:36\nmsgid \"Content cannot be empty\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/form_node/i_form_node.py:19\nmsgid \"Form Configuration\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/form_node/i_form_node.py:20\nmsgid \"Form output content\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:22\n#: apps/application/flow/step_node/function_node/i_function_node.py:24\nmsgid \"Variable Name\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:23\n#: apps/application/flow/step_node/function_node/i_function_node.py:34\nmsgid \"Variable Value\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:27\nmsgid \"Library ID\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:36\nmsgid \"The function has been deleted\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:43\n#, python-brace-format\nmsgid \"Field: {name} No value set\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:59\n#, python-brace-format\nmsgid \"Field: {name} Type: {_type} Value: {value} Unsupported types\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:63\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:100\n#, python-brace-format\nmsgid \"Field: {name} Type: {_type} Value: {value} Type error\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:91\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:96\n#: apps/tools/serializers/tool.py:254 apps/tools/serializers/tool.py:259\nmsgid \"type error\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:106\n#: apps/tools/serializers/tool.py:398\nmsgid \"Function does not exist\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:108\n#, python-brace-format\nmsgid \"No permission to use this function {name}\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:110\n#, python-brace-format\nmsgid \"Function {name} is unavailable\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:25\nmsgid \"Is this field required\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:26\n#: apps/knowledge/serializers/document.py:203\n#: apps/tools/serializers/tool.py:120\nmsgid \"type\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:28\nmsgid \"The field only supports string|int|dict|array|float\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:30\n#: apps/folders/serializers/folder.py:106\n#: apps/folders/serializers/folder.py:141\n#: apps/folders/serializers/folder.py:196 apps/tools/serializers/tool.py:124\nmsgid \"source\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:32\n#: apps/tools/serializers/tool.py:126\nmsgid \"The field only supports custom|reference\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:40\n#, python-brace-format\nmsgid \"{field}, this field is required.\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:46\nmsgid \"function\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:14\nmsgid \"Prompt word (positive)\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:16\nmsgid \"Prompt word (negative)\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:23\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:20\nmsgid \"Conversation storage type\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:13\nmsgid \"Mcp servers\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:16\nmsgid \"Mcp server\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:18\nmsgid \"Mcp tool\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:21\nmsgid \"Tool parameters\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/reranker_node/i_reranker_node.py:26\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:33\nmsgid \"Maximum number of words in a quoted segment\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:27\n#: apps/knowledge/serializers/knowledge.py:97\n#: apps/knowledge/serializers/knowledge.py:640\nmsgid \"similarity\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:18\nmsgid \"The audio file cannot be empty\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:33\nmsgid \"\"\n\"Parameter value error: The uploaded audio lacks file_id, and the audio \"\n\"upload fails\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:18\nmsgid \"Text content\"\nmsgstr \"\"\n\n#: apps/application/models/application_chat.py:79\n#: apps/xpack/serializers/channel/chat_manage.py:94\n#: apps/xpack/serializers/channel/chat_manage.py:152\nmsgid \"\"\n\"Sorry, no relevant content was found. Please re-describe your problem or \"\n\"provide more information. \"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:78\nmsgid \"No reference status\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:86\nmsgid \"Acquaintance\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:88\nmsgid \"Maximum number of quoted characters\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:95\nmsgid \"Segment settings not referenced\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:104\n#: apps/application/serializers/application_chat_record.py:176\n#: apps/application/serializers/application_chat_record.py:252\n#: apps/application/serializers/application_chat_record.py:317\nmsgid \"Knowledge base id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:105\nmsgid \"Knowledge Base List\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:119\nmsgid \"The knowledge base id does not exist\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:126\nmsgid \"Role prompts\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:128\nmsgid \"No citation segmentation prompt\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:130\nmsgid \"Thinking process switch\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:134\nmsgid \"The thinking process begins to mark\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:138\nmsgid \"End of thinking process marker\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:155\n#: apps/application/serializers/application.py:203\n#: apps/application/serializers/application.py:378\nmsgid \"Opening remarks\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:191\nmsgid \"application name\"\nmsgstr \"Agent name\"\n\n#: apps/application/serializers/application.py:194\nmsgid \"application describe\"\nmsgstr \"Agent description\"\n\n#: apps/application/serializers/application.py:197\n#: apps/application/serializers/application.py:372\n#: apps/common/constants/permission_constants.py:226\n#: apps/common/constants/permission_constants.py:259\n#: apps/common/constants/permission_constants.py:264\n#: apps/models_provider/views/model.py:63\n#: apps/models_provider/views/model.py:95\n#: apps/models_provider/views/model.py:113\n#: apps/models_provider/views/model.py:130\n#: apps/models_provider/views/model.py:145\n#: apps/models_provider/views/model.py:160\n#: apps/models_provider/views/model.py:173\n#: apps/models_provider/views/model.py:194\n#: apps/models_provider/views/model.py:210\n#: apps/models_provider/views/model_apply.py:29\n#: apps/models_provider/views/model_apply.py:41\n#: apps/models_provider/views/model_apply.py:53\n#: apps/models_provider/views/provide.py:25\n#: apps/models_provider/views/provide.py:48\n#: apps/models_provider/views/provide.py:62\n#: apps/models_provider/views/provide.py:80\n#: apps/models_provider/views/provide.py:97\nmsgid \"Model\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:201\n#: apps/application/serializers/application.py:376\nmsgid \"Historical chat records\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:206\n#: apps/application/serializers/application.py:380\nmsgid \"Related Knowledge Base\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:213\n#: apps/application/serializers/application.py:390\nmsgid \"Question completion\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:217\nmsgid \"Application Type\"\nmsgstr \"Agent Type\"\n\n#: apps/application/serializers/application.py:221\nmsgid \"Application type only supports SIMPLE|WORK_FLOW\"\nmsgstr \"Agent type only supports SIMPLE|WORK_FLOW\"\n\n#: apps/application/serializers/application.py:226\n#: apps/application/serializers/application.py:394\nmsgid \"Model parameters\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:228\n#: apps/application/serializers/application.py:396\nmsgid \"Voice playback enabled\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:230\n#: apps/application/serializers/application.py:398\nmsgid \"Voice playback model ID\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:232\n#: apps/application/serializers/application.py:400\nmsgid \"Voice playback type\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:234\n#: apps/application/serializers/application.py:402\nmsgid \"Voice playback autoplay\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:236\n#: apps/application/serializers/application.py:404\nmsgid \"Voice recognition enabled\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:238\n#: apps/application/serializers/application.py:406\nmsgid \"Speech recognition model ID\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:240\n#: apps/application/serializers/application.py:408\nmsgid \"Voice recognition automatic transmission\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:281\nmsgid \"Primary key id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:286\nmsgid \"Application type\"\nmsgstr \"Agent type\"\n\n#: apps/application/serializers/application.py:287\n#: apps/xpack/serializers/resource_chat_user.py:34\n#: apps/xpack/serializers/resource_chat_user.py:110\n#: apps/xpack/serializers/resource_chat_user_group.py:17\n#: apps/xpack/serializers/resource_chat_user_group.py:85\nmsgid \"Resource type\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:288\nmsgid \"Affiliation user\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:289\nmsgid \"Creation time\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:290\nmsgid \"Modification time\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:294\n#: apps/application/serializers/application_chat_record.py:42\n#: apps/application/serializers/application_chat_record.py:323\n#: apps/application/serializers/application_version.py:40\n#: apps/chat/serializers/chat.py:318 apps/role_setting/api/role_setting.py:147\n#: apps/users/api/user.py:64 apps/users/api/user.py:170\n#: apps/workspace/api/workspace.py:46 apps/workspace/api/workspace.py:66\n#: apps/workspace/api/workspace.py:85 apps/workspace/api/workspace.py:103\n#: apps/workspace/serializers/workspace_serializers.py:239\n#: apps/xpack/api/application_setting.py:24 apps/xpack/api/knowledge_lark.py:40\n#: apps/xpack/api/knowledge_lark.py:72 apps/xpack/api/knowledge_lark.py:104\n#: apps/xpack/api/platform.py:22 apps/xpack/api/platform.py:63\nmsgid \"Workspace ID\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:363\n#: apps/knowledge/serializers/document.py:157\n#: apps/knowledge/serializers/document.py:162 apps/oss/serializers/file.py:57\n#: apps/tools/serializers/tool.py:356\nmsgid \"file\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:384\nmsgid \"Dataset settings\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:387\nmsgid \"Model setup\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:391\nmsgid \"Icon\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:412\n#: apps/application/serializers/application_api_key.py:33\n#: apps/application/serializers/application_api_key.py:64\n#: apps/folders/serializers/folder.py:101\n#: apps/folders/serializers/folder.py:140\n#: apps/folders/serializers/folder.py:195\n#: apps/knowledge/serializers/document.py:253\n#: apps/knowledge/serializers/document.py:347\n#: apps/knowledge/serializers/document.py:408\n#: apps/knowledge/serializers/document.py:502\n#: apps/knowledge/serializers/document.py:736\n#: apps/knowledge/serializers/document.py:888\n#: apps/knowledge/serializers/document.py:963\n#: apps/knowledge/serializers/document.py:983\n#: apps/knowledge/serializers/document.py:1166\n#: apps/knowledge/serializers/knowledge.py:208\n#: apps/knowledge/serializers/knowledge.py:448\n#: apps/knowledge/serializers/knowledge.py:557\n#: apps/knowledge/serializers/knowledge.py:635\n#: apps/knowledge/serializers/paragraph.py:134\n#: apps/knowledge/serializers/paragraph.py:346\n#: apps/knowledge/serializers/paragraph.py:438\n#: apps/knowledge/serializers/paragraph.py:558\n#: apps/knowledge/serializers/problem.py:176\n#: apps/knowledge/serializers/problem.py:204\n#: apps/models_provider/api/model.py:30 apps/models_provider/api/model.py:86\n#: apps/models_provider/api/model.py:99\n#: apps/models_provider/serializers/model_serializer.py:259\n#: apps/models_provider/serializers/model_serializer.py:323\n#: apps/models_provider/serializers/model_serializer.py:392\n#: apps/shared/api/shared_knowledge.py:131\n#: apps/shared/api/shared_knowledge.py:164 apps/shared/api/shared_tool.py:60\n#: apps/shared/api/shared_tool.py:147\n#: apps/shared/serializers/shared_knowledge.py:61\n#: apps/shared/serializers/shared_knowledge.py:109\n#: apps/shared/serializers/shared_knowledge.py:157\n#: apps/shared/serializers/shared_model.py:110\n#: apps/shared/serializers/shared_tool.py:45\n#: apps/shared/serializers/shared_tool.py:86\n#: apps/system_manage/serializers/user_resource_permission.py:74\n#: apps/tools/serializers/tool.py:188 apps/tools/serializers/tool.py:210\n#: apps/tools/serializers/tool.py:268 apps/tools/serializers/tool.py:358\n#: apps/tools/serializers/tool.py:389 apps/tools/serializers/tool.py:425\n#: apps/tools/serializers/tool.py:452\n#: apps/xpack/serializers/dataset_lark_serializer.py:45\n#: apps/xpack/serializers/resource_chat_user.py:33\n#: apps/xpack/serializers/resource_chat_user.py:109\n#: apps/xpack/serializers/resource_chat_user_group.py:16\n#: apps/xpack/serializers/resource_chat_user_group.py:84\nmsgid \"workspace id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:459\nmsgid \"\"\n\"The community version supports up to 5 applications. If you need more \"\n\"applications, please contact us (https://fit2cloud.com/).\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:471\n#: apps/common/handle/impl/qa/zip_parse_qa_handle.py:56\n#: apps/common/handle/impl/text/zip_split_handle.py:69\n#: apps/knowledge/serializers/document.py:864\n#: apps/knowledge/serializers/document.py:871\n#: apps/tools/serializers/tool.py:370\nmsgid \"Unsupported file format\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:545\nmsgid \"Application id does not exist\"\nmsgstr \"Agent id does not exist\"\n\n#: apps/application/serializers/application.py:591\nmsgid \"work_flow is a required field\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:695\n#, python-brace-format\nmsgid \"Unknown knowledge base id {dataset_id}, unable to associate\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_access_token.py:24\nmsgid \"Reset Token\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_access_token.py:25\nmsgid \"Is it enabled\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_access_token.py:28\nmsgid \"Number of visits\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_access_token.py:30\nmsgid \"Whether to enable whitelist\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_access_token.py:32\n#: apps/application/serializers/application_access_token.py:33\nmsgid \"Whitelist\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_access_token.py:35\nmsgid \"Whether to display knowledge sources\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_access_token.py:37\n#: apps/users/serializers/user.py:665\n#: apps/xpack/serializers/application_setting_serializer.py:37\nmsgid \"language\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_api_key.py:21\nmsgid \"Availability\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_api_key.py:24\nmsgid \"Is cross-domain allowed\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_api_key.py:28\nmsgid \"Cross-domain address\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_api_key.py:29\nmsgid \"Cross-domain list\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_api_key.py:34\n#: apps/application/serializers/application_api_key.py:65\n#: apps/knowledge/serializers/knowledge.py:72\n#: apps/xpack/serializers/application_setting_serializer.py:77\n#: apps/xpack/serializers/dataset_lark_serializer.py:295\nmsgid \"application id\"\nmsgstr \"Agent id\"\n\n#: apps/application/serializers/application_api_key.py:41\n#: apps/chat/serializers/chat.py:277 apps/chat/serializers/chat.py:332\n#: apps/xpack/serializers/application_setting_serializer.py:103\n#: apps/xpack/serializers/platform_serializer.py:81\n#: apps/xpack/serializers/platform_serializer.py:103\n#: apps/xpack/serializers/platform_serializer.py:138\n#: apps/xpack/serializers/platform_serializer.py:149\nmsgid \"Application does not exist\"\nmsgstr \"Agent does not exist\"\n\n#: apps/application/serializers/application_api_key.py:66\nmsgid \"ApiKeyId\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_api_key.py:87\nmsgid \"APIKey does not exist\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:33\nmsgid \"chat id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:34\n#: apps/application/serializers/application_chat.py:51\n#: apps/application/serializers/application_chat.py:182\n#: apps/application/serializers/application_version.py:23\nmsgid \"summary\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:35\nmsgid \"Chat User ID\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:36\nmsgid \"Chat User Type\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:37\nmsgid \"Is delete\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:39\n#: apps/application/serializers/application_stats.py:25\nmsgid \"Number of conversations\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:40\n#: apps/application/serializers/application_stats.py:29\nmsgid \"Number of Likes\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:41\n#: apps/application/serializers/application_stats.py:31\nmsgid \"Number of thumbs-downs\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:42\nmsgid \"Number of tags\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:46\nmsgid \"Chat ID List\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:52\n#: apps/application/serializers/application_stats.py:36\n#: apps/xpack/serializers/operate_log_serializer.py:55\nmsgid \"Start time\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:53\n#: apps/application/serializers/application_stats.py:37\n#: apps/xpack/serializers/operate_log_serializer.py:56\nmsgid \"End time\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:61\nmsgid \"Only supports and|or\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:183\nmsgid \"Problem after optimization\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:184\nmsgid \"answer\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:184\nmsgid \"User feedback\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:186\nmsgid \"Section title + content\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:187\n#: apps/application/views/application_chat_record.py:139\n#: apps/application/views/application_chat_record.py:140\n#: apps/application/views/application_chat_record.py:141\n#: apps/common/constants/permission_constants.py:248\nmsgid \"Annotation\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:187\nmsgid \"Consuming tokens\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:188\nmsgid \"Time consumed (s)\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat.py:189\nmsgid \"Question Time\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:44\n#: apps/application/serializers/application_chat_record.py:143\n#: apps/application/serializers/application_chat_record.py:250\n#: apps/application/serializers/application_chat_record.py:315\n#: apps/chat/serializers/chat.py:45\nmsgid \"Conversation record id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:51\nmsgid \"Application authentication information does not exist\"\nmsgstr \"Agent authentication information does not exist\"\n\n#: apps/application/serializers/application_chat_record.py:53\nmsgid \"Displaying knowledge sources is not enabled\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:70\n#: apps/chat/serializers/chat.py:127 apps/chat/serializers/chat.py:274\nmsgid \"Conversation does not exist\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:152\n#: apps/application/serializers/application_chat_record.py:279\n#: apps/application/serializers/application_chat_record.py:336\n#: apps/chat/serializers/chat.py:205\nmsgid \"Conversation record does not exist\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:168\nmsgid \"Section title\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:169\nmsgid \"Paragraph content\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:177\n#: apps/application/serializers/application_chat_record.py:254\n#: apps/application/serializers/application_chat_record.py:319\nmsgid \"Document id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:184\n#: apps/application/serializers/application_chat_record.py:260\n#: apps/knowledge/serializers/paragraph.py:246\nmsgid \"The document id is incorrect\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:203\nmsgid \"Conversation records that do not exist\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:321\nmsgid \"Paragraph id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_chat_record.py:340\n#, python-brace-format\nmsgid \"\"\n\"The paragraph id is wrong. The current conversation record does not exist. \"\n\"[{paragraph_id}] paragraph id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_stats.py:26\nmsgid \"Number of new users\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_stats.py:27\nmsgid \"Total number of users\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_stats.py:28\nmsgid \"date\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_stats.py:30\nmsgid \"Tokens consumption\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_version.py:36\nmsgid \"Version Name\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_version.py:69\nmsgid \"Workflow version id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application_version.py:79\n#: apps/application/serializers/application_version.py:94\nmsgid \"Workflow version does not exist\"\nmsgstr \"\"\n\n#: apps/application/views/application.py:41\n#: apps/application/views/application.py:42\n#: apps/application/views/application.py:43\nmsgid \"Create an application\"\nmsgstr \"\"\n\n#: apps/application/views/application.py:47\n#: apps/application/views/application.py:65\n#: apps/application/views/application.py:82\n#: apps/application/views/application.py:103\n#: apps/application/views/application.py:123\n#: apps/application/views/application.py:145\n#: apps/application/views/application.py:166\n#: apps/application/views/application.py:187\n#: apps/application/views/application.py:206\n#: apps/application/views/application_access_token.py:32\n#: apps/application/views/application_access_token.py:47\n#: apps/application/views/application_chat.py:102\n#: apps/application/views/application_chat.py:122\n#: apps/application/views/application_stats.py:33\n#: apps/common/constants/permission_constants.py:224\n#: apps/common/constants/permission_constants.py:234\n#: apps/xpack/views/application_setting.py:29\n#: apps/xpack/views/application_setting.py:47\nmsgid \"Application\"\nmsgstr \"\"\n\n#: apps/application/views/application.py:60\n#: apps/application/views/application.py:61\n#: apps/application/views/application.py:62\nmsgid \"Get the application list\"\nmsgstr \"Get the agent list\"\n\n#: apps/application/views/application.py:77\n#: apps/application/views/application.py:78\n#: apps/application/views/application.py:79\nmsgid \"Get the application list by page\"\nmsgstr \"Get the agent list by page\"\n\n#: apps/application/views/application.py:97\n#: apps/application/views/application.py:98\n#: apps/application/views/application.py:99\nmsgid \"Import Application\"\nmsgstr \"\"\n\n#: apps/application/views/application.py:117\n#: apps/application/views/application.py:118\n#: apps/application/views/application.py:119\nmsgid \"Export application\"\nmsgstr \"\"\n\n#: apps/application/views/application.py:140\n#: apps/application/views/application.py:141\n#: apps/application/views/application.py:142\nmsgid \"Deleting application\"\nmsgstr \"\"\n\n#: apps/application/views/application.py:160\n#: apps/application/views/application.py:161\n#: apps/application/views/application.py:162\nmsgid \"Modify the application\"\nmsgstr \"\"\n\n#: apps/application/views/application.py:181\n#: apps/application/views/application.py:182\n#: apps/application/views/application.py:183\nmsgid \"Get application details\"\nmsgstr \"Get agent details\"\n\n#: apps/application/views/application.py:200\n#: apps/application/views/application.py:201\n#: apps/application/views/application.py:202\nmsgid \"Publishing an application\"\nmsgstr \"\"\n\n#: apps/application/views/application_access_token.py:27\n#: apps/application/views/application_access_token.py:28\n#: apps/application/views/application_access_token.py:29\nmsgid \"Modify application access restriction information\"\nmsgstr \"Modify agent access restriction information\"\n\n#: apps/application/views/application_access_token.py:43\n#: apps/application/views/application_access_token.py:44\n#: apps/application/views/application_access_token.py:45\nmsgid \"Get application access restriction information\"\nmsgstr \"Get agent access restriction information\"\n\n#: apps/application/views/application_api_key.py:31\n#: apps/application/views/application_api_key.py:32\n#: apps/application/views/application_api_key.py:33\nmsgid \"Create application ApiKey\"\nmsgstr \"Create agent ApiKey\"\n\n#: apps/application/views/application_api_key.py:37\n#: apps/application/views/application_api_key.py:57\n#: apps/application/views/application_api_key.py:77\n#: apps/application/views/application_api_key.py:99\nmsgid \"Application Api Key\"\nmsgstr \"Agent Api Key\"\n\n#: apps/application/views/application_api_key.py:52\nmsgid \"GET application ApiKey List\"\nmsgstr \"GET agent ApiKey List\"\n\n#: apps/application/views/application_api_key.py:53\n#: apps/application/views/application_api_key.py:54\nmsgid \"Create application ApiKey List\"\nmsgstr \"Create agent ApiKey List\"\n\n#: apps/application/views/application_api_key.py:71\n#: apps/application/views/application_api_key.py:72\n#: apps/application/views/application_api_key.py:73\nmsgid \"Modify application API_KEY\"\nmsgstr \"Modify agent API_KEY\"\n\n#: apps/application/views/application_api_key.py:93\n#: apps/application/views/application_api_key.py:94\n#: apps/application/views/application_api_key.py:95\nmsgid \"Delete Application API_KEY\"\nmsgstr \"Delete Agent API_KEY\"\n\n#: apps/application/views/application_chat.py:35\n#: apps/application/views/application_chat.py:36\n#: apps/application/views/application_chat.py:37\nmsgid \"Get the conversation list\"\nmsgstr \"\"\n\n#: apps/application/views/application_chat.py:41\n#: apps/application/views/application_chat.py:61\n#: apps/application/views/application_chat.py:82\n#: apps/application/views/application_chat_record.py:37\n#: apps/application/views/application_chat_record.py:58\n#: apps/application/views/application_chat_record.py:82\n#: apps/application/views/application_chat_record.py:106\n#: apps/application/views/application_chat_record.py:125\n#: apps/application/views/application_chat_record.py:145\n#: apps/application/views/application_chat_record.py:171\nmsgid \"Application/Conversation Log\"\nmsgstr \"Agent/Conversation Log\"\n\n#: apps/application/views/application_chat.py:55\n#: apps/application/views/application_chat.py:56\n#: apps/application/views/application_chat.py:57\nmsgid \"Get the conversation list by page\"\nmsgstr \"\"\n\n#: apps/application/views/application_chat.py:76\n#: apps/application/views/application_chat.py:77\n#: apps/application/views/application_chat.py:78\nmsgid \"Export conversation\"\nmsgstr \"\"\n\n#: apps/application/views/application_chat.py:97\n#: apps/application/views/application_chat.py:98\n#: apps/application/views/application_chat.py:99\nmsgid \"Get a temporary session id based on the application id\"\nmsgstr \"Get a temporary session id based on the agent id\"\n\n#: apps/application/views/application_chat.py:116\n#: apps/application/views/application_chat.py:117\n#: apps/application/views/application_chat.py:118 apps/chat/views/chat.py:93\n#: apps/chat/views/chat.py:94 apps/chat/views/chat.py:95\nmsgid \"dialogue\"\nmsgstr \"\"\n\n#: apps/application/views/application_chat_record.py:31\n#: apps/application/views/application_chat_record.py:32\n#: apps/application/views/application_chat_record.py:33\nmsgid \"Get the conversation record list\"\nmsgstr \"\"\n\n#: apps/application/views/application_chat_record.py:52\n#: apps/application/views/application_chat_record.py:53\n#: apps/application/views/application_chat_record.py:54\nmsgid \"Get the conversation record list by page\"\nmsgstr \"\"\n\n#: apps/application/views/application_chat_record.py:76\n#: apps/application/views/application_chat_record.py:77\n#: apps/application/views/application_chat_record.py:78\nmsgid \"Get conversation record details\"\nmsgstr \"\"\n\n#: apps/application/views/application_chat_record.py:100\n#: apps/application/views/application_chat_record.py:101\n#: apps/application/views/application_chat_record.py:102\nmsgid \"Add to Knowledge Base\"\nmsgstr \"\"\n\n#: apps/application/views/application_chat_record.py:119\n#: apps/application/views/application_chat_record.py:120\n#: apps/application/views/application_chat_record.py:121\nmsgid \"Get the list of marked paragraphs\"\nmsgstr \"\"\n\n#: apps/application/views/application_chat_record.py:165\n#: apps/application/views/application_chat_record.py:166\n#: apps/application/views/application_chat_record.py:167\nmsgid \"Delete a Annotation\"\nmsgstr \"\"\n\n#: apps/application/views/application_stats.py:28\n#: apps/application/views/application_stats.py:29\n#: apps/application/views/application_stats.py:30\nmsgid \"Dialogue-related statistical trends\"\nmsgstr \"\"\n\n#: apps/application/views/application_version.py:30\n#: apps/application/views/application_version.py:31\n#: apps/application/views/application_version.py:32\nmsgid \"Get the application version list\"\nmsgstr \"Get the agent version list\"\n\n#: apps/application/views/application_version.py:35\n#: apps/application/views/application_version.py:55\n#: apps/application/views/application_version.py:76\n#: apps/application/views/application_version.py:94\nmsgid \"Application/Version\"\nmsgstr \"Agent/Version\"\n\n#: apps/application/views/application_version.py:50\n#: apps/application/views/application_version.py:51\n#: apps/application/views/application_version.py:52\nmsgid \"Get the list of application versions by page\"\nmsgstr \"Get the list of agent versions by page\"\n\n#: apps/application/views/application_version.py:71\n#: apps/application/views/application_version.py:72\n#: apps/application/views/application_version.py:73\nmsgid \"Get application version details\"\nmsgstr \"Get agent version details\"\n\n#: apps/application/views/application_version.py:88\n#: apps/application/views/application_version.py:89\n#: apps/application/views/application_version.py:90\nmsgid \"Modify application version information\"\nmsgstr \"Modify agent version information\"\n\n#: apps/chat/api/chat_authentication_api.py:38\n#: apps/chat/serializers/chat_authentication.py:28\n#: apps/chat/serializers/chat_authentication.py:54\n#: apps/xpack/serializers/chat_auth.py:25\nmsgid \"access_token\"\nmsgstr \"\"\n\n#: apps/chat/api/chat_embed_api.py:24\nmsgid \"host\"\nmsgstr \"\"\n\n#: apps/chat/api/chat_embed_api.py:31\n#: apps/chat/serializers/chat_embed_serializers.py:25\nmsgid \"protocol\"\nmsgstr \"\"\n\n#: apps/chat/api/chat_embed_api.py:38\n#: apps/chat/serializers/chat_embed_serializers.py:26\n#: apps/users/serializers/login.py:36\nmsgid \"token\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:42\nmsgid \"Is the answer in streaming mode\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:43\nmsgid \"Do you want to reply again\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:48\nmsgid \"Node id\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:51\nmsgid \"Runtime node id\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:54\nmsgid \"Node parameters\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:56\nmsgid \"Global variables\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:60\n#: apps/common/constants/permission_constants.py:222\n#: apps/common/constants/permission_constants.py:228\nmsgid \"Other\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:115 apps/chat/serializers/chat.py:320\nmsgid \"Client id\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:116 apps/chat/serializers/chat.py:321\nmsgid \"Client Type\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:119 apps/chat/serializers/chat.py:322\n#: apps/common/constants/permission_constants.py:240\nmsgid \"Debug\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:146\nmsgid \"The number of visits exceeds today's visits\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:157\nmsgid \"The current model is not available\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:159\nmsgid \"The model is downloading, please try again later\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat.py:306 apps/chat/serializers/chat.py:357\nmsgid \"The application has not been published. Please use it after publishing.\"\nmsgstr \"The agent has not been published. Please use it after publishing.\"\n\n#: apps/chat/serializers/chat_authentication.py:50\n#: apps/xpack/serializers/chat_auth.py:53\nmsgid \"Invalid access_token\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat_authentication.py:89\nmsgid \"Illegal User\"\nmsgstr \"\"\n\n#: apps/chat/serializers/chat_embed_serializers.py:24\nmsgid \"Host\"\nmsgstr \"\"\n\n#: apps/chat/views/chat.py:37 apps/chat/views/chat.py:38\n#: apps/chat/views/chat.py:39\nmsgid \"Application Anonymous Certification\"\nmsgstr \"Agent Anonymous Certification\"\n\n#: apps/chat/views/chat.py:42 apps/chat/views/chat.py:64\n#: apps/chat/views/chat.py:81 apps/chat/views/chat.py:99\n#: apps/chat/views/chat.py:120 apps/chat/views/chat_embed.py:27\n#: apps/xpack/views/chat_user_auth.py:419\nmsgid \"Chat\"\nmsgstr \"\"\n\n#: apps/chat/views/chat.py:59 apps/chat/views/chat.py:60\n#: apps/chat/views/chat.py:61\nmsgid \"Get application related information\"\nmsgstr \"Get agent related information\"\n\n#: apps/chat/views/chat.py:76 apps/chat/views/chat.py:77\n#: apps/chat/views/chat.py:78\nmsgid \"Get application authentication information\"\nmsgstr \"Get agent authentication information\"\n\n#: apps/chat/views/chat.py:115 apps/chat/views/chat.py:116\n#: apps/chat/views/chat.py:117\nmsgid \"Get the session id according to the application id\"\nmsgstr \"Get the session id according to the agent id\"\n\n#: apps/chat/views/chat.py:131 apps/chat/views/chat.py:132\n#: apps/chat/views/chat.py:133 apps/users/views/login.py:70\n#: apps/users/views/login.py:71 apps/users/views/login.py:72\nmsgid \"Get captcha\"\nmsgstr \"\"\n\n#: apps/chat/views/chat.py:134\n#: apps/common/constants/permission_constants.py:210\n#: apps/users/views/login.py:41 apps/users/views/login.py:58\n#: apps/users/views/login.py:73 apps/users/views/user.py:63\n#: apps/users/views/user.py:77 apps/users/views/user.py:91\n#: apps/users/views/user.py:108 apps/users/views/user.py:123\n#: apps/users/views/user.py:136 apps/users/views/user.py:150\n#: apps/users/views/user.py:164 apps/users/views/user.py:180\n#: apps/users/views/user.py:193 apps/users/views/user.py:206\n#: apps/users/views/user.py:217 apps/users/views/user.py:235\n#: apps/users/views/user.py:251 apps/users/views/user.py:269\n#: apps/users/views/user.py:286 apps/users/views/user.py:303\n#: apps/users/views/user.py:321 apps/users/views/user.py:338\n#: apps/users/views/user.py:356 apps/xpack/views/chat_user_auth.py:206\nmsgid \"User Management\"\nmsgstr \"\"\n\n#: apps/chat/views/chat_embed.py:22 apps/chat/views/chat_embed.py:23\n#: apps/chat/views/chat_embed.py:24\nmsgid \"Get embedded js\"\nmsgstr \"\"\n\n#: apps/common/auth/authenticate.py:80\nmsgid \"Not logged in, please log in first\"\nmsgstr \"\"\n\n#: apps/common/auth/authenticate.py:82 apps/common/auth/authenticate.py:89\n#: apps/common/auth/authenticate.py:95\nmsgid \"Authentication information is incorrect! illegal user\"\nmsgstr \"\"\n\n#: apps/common/auth/authentication.py:98\nmsgid \"No permission to access\"\nmsgstr \"\"\n\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:39\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:41\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:43\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:49\n#: apps/xpack/auth/chat_user_token.py:41 apps/xpack/auth/chat_user_token.py:43\n#: apps/xpack/auth/chat_user_token.py:45 apps/xpack/auth/chat_user_token.py:49\nmsgid \"Authentication information is incorrect\"\nmsgstr \"\"\n\n#: apps/common/auth/handle/impl/user_token.py:265\nmsgid \"Login expired\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:31\n#: apps/users/serializers/login.py:53\n#: apps/xpack/serializers/chat_user_serializer.py:123\nmsgid \"The username or password is incorrect\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:32\nmsgid \"Please log in first and bring the user Token\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:33\n#: apps/users/serializers/user.py:630\nmsgid \"Email sending failed\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:34\nmsgid \"Email format error\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:35\nmsgid \"The email has been registered, please log in directly\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:36\nmsgid \"The email is not registered, please register first\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:38\nmsgid \"The verification code is incorrect or the verification code has expired\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:39\nmsgid \"The username has been registered, please log in directly\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:41\nmsgid \"\"\n\"The username cannot be empty and must be between 6 and 20 characters long.\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:43\nmsgid \"Password and confirmation password are inconsistent\"\nmsgstr \"\"\n\n#: apps/common/constants/exception_code_constants.py:44\nmsgid \"The nickname is already registered\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:209\nmsgid \"System Setting\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:211\n#: apps/common/constants/permission_constants.py:272\n#: apps/role_setting/views/role_setting.py:44\n#: apps/role_setting/views/role_setting.py:67\n#: apps/role_setting/views/role_setting.py:84\n#: apps/role_setting/views/role_setting.py:103\n#: apps/role_setting/views/role_setting.py:125\n#: apps/role_setting/views/role_setting.py:145\n#: apps/role_setting/views/role_setting.py:167\n#: apps/role_setting/views/role_setting.py:191\n#: apps/role_setting/views/role_setting.py:210\nmsgid \"Role\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:212\n#: apps/common/constants/permission_constants.py:270\n#: apps/workspace/views/workspace.py:37 apps/workspace/views/workspace.py:49\n#: apps/workspace/views/workspace.py:63 apps/workspace/views/workspace.py:80\n#: apps/workspace/views/workspace.py:101 apps/workspace/views/workspace.py:119\n#: apps/workspace/views/workspace.py:138 apps/workspace/views/workspace.py:155\n#: apps/workspace/views/workspace.py:170 apps/workspace/views/workspace.py:188\n#: apps/workspace/views/workspace.py:207 apps/workspace/views/workspace.py:223\n#: apps/workspace/views/workspace.py:236 apps/workspace/views/workspace.py:250\nmsgid \"Workspace\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:213\nmsgid \"Resource Application\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:214\nmsgid \"Resource Knowledge\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:215\nmsgid \"Resource Tool\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:216\nmsgid \"Resource Model\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:217\nmsgid \"Resource Permission\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:218\n#: apps/shared/views/shared_dataset_lark_views.py:30\n#: apps/shared/views/shared_dataset_lark_views.py:50\n#: apps/shared/views/shared_knowledge.py:33\n#: apps/shared/views/shared_knowledge.py:53\n#: apps/shared/views/shared_knowledge.py:76\n#: apps/shared/views/shared_knowledge.py:91\n#: apps/shared/views/shared_knowledge.py:106\n#: apps/shared/views/shared_knowledge.py:125\n#: apps/shared/views/shared_knowledge.py:151\n#: apps/shared/views/shared_knowledge.py:178\n#: apps/shared/views/shared_knowledge.py:196\n#: apps/shared/views/shared_knowledge.py:214\n#: apps/shared/views/shared_knowledge.py:235\n#: apps/shared/views/shared_knowledge.py:256\n#: apps/shared/views/shared_knowledge.py:276\n#: apps/shared/views/shared_knowledge.py:297\n#: apps/shared/views/shared_knowledge.py:312\n#: apps/shared/views/shared_knowledge.py:331\n#: apps/shared/views/shared_knowledge.py:354\n#: apps/shared/views/shared_knowledge.py:386\n#: apps/shared/views/shared_knowledge.py:407\nmsgid \"Shared Knowledge\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:219\n#: apps/models_provider/views/model.py:227 apps/shared/views/shared_model.py:58\n#: apps/shared/views/shared_model.py:89 apps/shared/views/shared_model.py:107\n#: apps/shared/views/shared_model.py:124 apps/shared/views/shared_model.py:138\n#: apps/shared/views/shared_model.py:153 apps/shared/views/shared_model.py:166\n#: apps/shared/views/shared_model.py:186 apps/shared/views/shared_model.py:202\n#: apps/shared/views/shared_model.py:219 apps/shared/views/shared_model.py:234\n#: apps/shared/views/shared_model.py:253 apps/shared/views/shared_model.py:270\nmsgid \"Shared Model\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:220\n#: apps/shared/views/shared_tool.py:30 apps/shared/views/shared_tool.py:49\n#: apps/shared/views/shared_tool.py:68 apps/shared/views/shared_tool.py:83\n#: apps/shared/views/shared_tool.py:98 apps/shared/views/shared_tool.py:116\n#: apps/shared/views/shared_tool.py:139 apps/shared/views/shared_tool.py:157\n#: apps/shared/views/shared_tool.py:175 apps/shared/views/shared_tool.py:194\n#: apps/shared/views/shared_tool.py:218 apps/shared/views/shared_tool.py:239\n#: apps/shared/views/shared_tool.py:254 apps/shared/views/shared_tool.py:273\n#: apps/shared/views/shared_tool.py:294\nmsgid \"Shared Tool\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:221\nmsgid \"Operation Log\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:223\nmsgid \"System Management\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:225\n#: apps/common/constants/permission_constants.py:235\n#: apps/common/constants/permission_constants.py:260\n#: apps/common/constants/permission_constants.py:265\nmsgid \"Knowledge\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:227\n#: apps/common/constants/permission_constants.py:258\n#: apps/common/constants/permission_constants.py:263\n#: apps/tools/views/tool.py:39 apps/tools/views/tool.py:61\n#: apps/tools/views/tool.py:82 apps/tools/views/tool.py:104\n#: apps/tools/views/tool.py:127 apps/tools/views/tool.py:146\n#: apps/tools/views/tool.py:172 apps/tools/views/tool.py:201\n#: apps/tools/views/tool.py:223 apps/tools/views/tool.py:250\n#: apps/tools/views/tool.py:274\nmsgid \"Tool\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:229\nmsgid \"Read\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:230\nmsgid \"Edit\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:231\nmsgid \"Create\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:232\nmsgid \"Delete\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:233\nmsgid \"Email Setting\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:236\n#: apps/common/constants/permission_constants.py:261\n#: apps/common/constants/permission_constants.py:266\nmsgid \"Document\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:237\n#: apps/common/constants/permission_constants.py:262\n#: apps/common/constants/permission_constants.py:267\nmsgid \"Problem\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:238\nmsgid \"Import\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:239\nmsgid \"Export\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:241\nmsgid \"Sync\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:242\nmsgid \"Generate\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:243\nmsgid \"Add Member\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:244\nmsgid \"Remove Member\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:245\nmsgid \"Vector\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:246\nmsgid \"Migrate\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:247\nmsgid \"Relate\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:249\nmsgid \"Clear Policy\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:250\nmsgid \"Login Auth\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:251\nmsgid \"Display Settings\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:252\n#: apps/common/constants/permission_constants.py:720\n#: apps/xpack/views/system_api_key.py:23 apps/xpack/views/system_api_key.py:38\n#: apps/xpack/views/system_api_key.py:56 apps/xpack/views/system_api_key.py:71\nmsgid \"System API Key\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:253\n#: apps/xpack/views/system_params.py:26 apps/xpack/views/system_params.py:42\nmsgid \"Appearance Settings\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:254\n#: apps/common/constants/permission_constants.py:269\n#: apps/xpack/views/system_chat_user.py:339\n#: apps/xpack/views/system_chat_user.py:362\nmsgid \"Chat User\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:255\n#: apps/common/constants/permission_constants.py:268\nmsgid \"User Group\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:256\nmsgid \"Chat User Auth\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:257\nmsgid \"Overview\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:271\n#: apps/common/constants/permission_constants.py:671\n#: apps/common/constants/permission_constants.py:677\n#: apps/common/constants/permission_constants.py:683\n#: apps/common/constants/permission_constants.py:689\nmsgid \"Dialogue log\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:641\nmsgid \"Embed third party\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:647\nmsgid \"Access restrictions\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:653\nmsgid \"Display settings\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:659\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:44\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:16\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:75\nmsgid \"API Key\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:665\nmsgid \"Public settings\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:704\nmsgid \"About\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:709\n#: apps/users/views/user.py:88 apps/users/views/user.py:89\n#: apps/users/views/user.py:90\nmsgid \"Switch Language\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:714\nmsgid \"Change Password\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:734\nmsgid \"Sync users\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:755\n#: apps/common/constants/permission_constants.py:808\nmsgid \"Set up user groups\"\nmsgstr \"\"\n\n#: apps/common/event/__init__.py:27\nmsgid \"The download process was interrupted, please try again\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:90\n#, python-brace-format\nmsgid \"Query vector data: {paragraph_id_list} error {error} {traceback}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:95\n#, python-brace-format\nmsgid \"Start--->Embedding paragraph: {paragraph_id_list}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:107\n#, python-brace-format\nmsgid \"Vectorized paragraph: {paragraph_id_list} error {error} {traceback}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:113\n#, python-brace-format\nmsgid \"End--->Embedding paragraph: {paragraph_id_list}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:122\n#, python-brace-format\nmsgid \"Start--->Embedding paragraph: {paragraph_id}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:147\n#, python-brace-format\nmsgid \"Vectorized paragraph: {paragraph_id} error {error} {traceback}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:152\n#, python-brace-format\nmsgid \"End--->Embedding paragraph: {paragraph_id}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:268\n#, python-brace-format\nmsgid \"Start--->Embedding document: {document_id}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:288\n#, python-brace-format\nmsgid \"Vectorized document: {document_id} error {error} {traceback}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:293\n#, python-brace-format\nmsgid \"End--->Embedding document: {document_id}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:304\n#, python-brace-format\nmsgid \"Start--->Embedding knowledge: {knowledge_id}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:308\n#, python-brace-format\nmsgid \"Start--->Embedding document: {document_list}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:312\n#: apps/knowledge/task/embedding.py:116\n#, python-brace-format\nmsgid \"Vectorized knowledge: {knowledge_id} error {error} {traceback}\"\nmsgstr \"\"\n\n#: apps/common/event/listener_manage.py:315\n#, python-brace-format\nmsgid \"End--->Embedding knowledge: {knowledge_id}\"\nmsgstr \"\"\n\n#: apps/common/exception/handle_exception.py:32\n#: apps/common/handle/handle_exception.py:33\nmsgid \"Unknown exception\"\nmsgstr \"\"\n\n#: apps/common/field/common.py:48\nmsgid \"not a function\"\nmsgstr \"\"\n\n#: apps/common/forms/base_field.py:64\n#, python-brace-format\nmsgid \"The field {field_label} is required\"\nmsgstr \"\"\n\n#: apps/common/forms/slider_field.py:56\n#, python-brace-format\nmsgid \"The {field_label} cannot be less than {min}\"\nmsgstr \"\"\n\n#: apps/common/forms/slider_field.py:62\n#, python-brace-format\nmsgid \"The {field_label} cannot be greater than {max}\"\nmsgstr \"\"\n\n#: apps/common/handle/impl/text/pdf_split_handle.py:281\n#, python-brace-format\nmsgid \"This document has no preface and is treated as ordinary text: {e}\"\nmsgstr \"\"\n\n#: apps/common/job/clean_chat_job.py:23\nmsgid \"start clean chat log\"\nmsgstr \"\"\n\n#: apps/common/job/clean_chat_job.py:69\nmsgid \"end clean chat log\"\nmsgstr \"\"\n\n#: apps/common/job/clean_debug_file_job.py:21\nmsgid \"start clean debug file\"\nmsgstr \"\"\n\n#: apps/common/job/clean_debug_file_job.py:25\nmsgid \"end clean debug file\"\nmsgstr \"\"\n\n#: apps/common/result/api.py:17 apps/common/result/api.py:27\nmsgid \"response code\"\nmsgstr \"\"\n\n#: apps/common/result/api.py:18 apps/common/result/api.py:19\n#: apps/common/result/api.py:28 apps/common/result/api.py:29\nmsgid \"error prompt\"\nmsgstr \"\"\n\n#: apps/common/result/api.py:43\nmsgid \"total number of data\"\nmsgstr \"\"\n\n#: apps/common/result/api.py:44\nmsgid \"current page\"\nmsgstr \"\"\n\n#: apps/common/result/api.py:45\nmsgid \"page size\"\nmsgstr \"\"\n\n#: apps/common/result/result.py:31\n#: apps/xpack/serializers/operate_log_serializer.py:134\nmsgid \"Success\"\nmsgstr \"\"\n\n#: apps/common/utils/common.py:91\nmsgid \"Text-to-speech node, the text content must be of string type\"\nmsgstr \"\"\n\n#: apps/common/utils/common.py:93\nmsgid \"Text-to-speech node, the text content cannot be empty\"\nmsgstr \"\"\n\n#: apps/common/utils/common.py:246\n#, python-brace-format\nmsgid \"Limit {count} exceeded, please contact us (https://fit2cloud.com/).\"\nmsgstr \"\"\n\n#: apps/folders/models/folder.py:6 apps/folders/models/folder.py:17\n#: apps/folders/serializers/folder.py:98\nmsgid \"folder name\"\nmsgstr \"\"\n\n#: apps/folders/models/folder.py:8 apps/folders/models/folder.py:19\n#: apps/folders/serializers/folder.py:99\nmsgid \"folder description\"\nmsgstr \"\"\n\n#: apps/folders/models/folder.py:12 apps/folders/models/folder.py:23\n#: apps/folders/serializers/folder.py:102\nmsgid \"parent id\"\nmsgstr \"\"\n\n#: apps/folders/serializers/folder.py:75\nmsgid \"Folder depth cannot exceed 5 levels\"\nmsgstr \"\"\n\n#: apps/folders/serializers/folder.py:100\nmsgid \"folder user id\"\nmsgstr \"\"\n\n#: apps/folders/serializers/folder.py:105\n#: apps/knowledge/serializers/knowledge.py:112\n#: apps/knowledge/serializers/knowledge.py:207\n#: apps/knowledge/serializers/knowledge.py:447\n#: apps/knowledge/serializers/knowledge.py:559\n#: apps/knowledge/serializers/knowledge.py:637\n#: apps/models_provider/serializers/model_serializer.py:108\n#: apps/models_provider/serializers/model_serializer.py:212\n#: apps/models_provider/serializers/model_serializer.py:252\n#: apps/shared/serializers/shared_knowledge.py:107\n#: apps/shared/serializers/shared_knowledge.py:156\n#: apps/shared/serializers/shared_tool.py:84\n#: apps/system_manage/serializers/user_resource_permission.py:75\n#: apps/tools/serializers/tool.py:187 apps/tools/serializers/tool.py:209\n#: apps/tools/serializers/tool.py:455 apps/users/serializers/user.py:664\n#: apps/xpack/serializers/dataset_lark_serializer.py:46\n#: apps/xpack/serializers/dataset_lark_serializer.py:285\n#: apps/xpack/serializers/system_api_key.py:23\nmsgid \"user id\"\nmsgstr \"\"\n\n#: apps/folders/serializers/folder.py:123\nmsgid \"Folder name already exists\"\nmsgstr \"\"\n\n#: apps/folders/serializers/folder.py:150\n#: apps/folders/serializers/folder.py:182\nmsgid \"Folder does not exist\"\nmsgstr \"\"\n\n#: apps/folders/serializers/folder.py:184\nmsgid \"Cannot delete root folder\"\nmsgstr \"\"\n\n#: apps/folders/views/folder.py:31 apps/folders/views/folder.py:32\n#: apps/folders/views/folder.py:33\nmsgid \"Create folder\"\nmsgstr \"\"\n\n#: apps/folders/views/folder.py:37 apps/folders/views/folder.py:63\n#: apps/folders/views/folder.py:86 apps/folders/views/folder.py:110\n#: apps/folders/views/folder.py:129\nmsgid \"Folder\"\nmsgstr \"\"\n\n#: apps/folders/views/folder.py:58 apps/folders/views/folder.py:59\n#: apps/folders/views/folder.py:60\nmsgid \"Get folder tree\"\nmsgstr \"\"\n\n#: apps/folders/views/folder.py:80 apps/folders/views/folder.py:81\n#: apps/folders/views/folder.py:82\nmsgid \"Update folder\"\nmsgstr \"\"\n\n#: apps/folders/views/folder.py:105 apps/folders/views/folder.py:106\n#: apps/folders/views/folder.py:107\nmsgid \"Get folder\"\nmsgstr \"\"\n\n#: apps/folders/views/folder.py:124 apps/folders/views/folder.py:125\n#: apps/folders/views/folder.py:126\nmsgid \"Delete folder\"\nmsgstr \"\"\n\n#: apps/knowledge/api/problem.py:39 apps/knowledge/api/problem.py:52\n#: apps/knowledge/serializers/problem.py:40\nmsgid \"problem list\"\nmsgstr \"\"\n\n#: apps/knowledge/api/problem.py:40 apps/knowledge/api/problem.py:53\n#: apps/knowledge/serializers/problem.py:41\nmsgid \"problem\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/common.py:32\n#: apps/knowledge/serializers/knowledge.py:62\n#: apps/shared/serializers/shared_knowledge.py:29\nmsgid \"source url\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/common.py:33\n#: apps/knowledge/serializers/document.py:152\nmsgid \"selector\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/common.py:40\n#, python-brace-format\nmsgid \"URL error, cannot parse [{source_url}]\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/common.py:48\n#: apps/knowledge/serializers/document.py:78\n#: apps/knowledge/serializers/document.py:170\n#: apps/knowledge/serializers/document.py:186\nmsgid \"id list\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/common.py:58\n#, python-brace-format\nmsgid \"The following id does not exist: {error_id_list}\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/common.py:74\n#: apps/knowledge/serializers/document.py:166\n#: apps/knowledge/serializers/document.py:171\n#: apps/knowledge/serializers/document.py:178\nmsgid \"state list\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/common.py:117\n#: apps/knowledge/serializers/common.py:141\nmsgid \"The knowledge base is inconsistent with the vector model\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/common.py:119\n#: apps/knowledge/serializers/common.py:143\nmsgid \"Knowledge base setting error, please reset the knowledge base\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:79\n#: apps/knowledge/serializers/document.py:97\n#: apps/knowledge/serializers/document.py:353\nmsgid \"task type\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:87\n#: apps/knowledge/serializers/document.py:105\nmsgid \"task type not support\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:91\n#: apps/knowledge/serializers/document.py:110\n#: apps/knowledge/serializers/document.py:350\nmsgid \"document name\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:93\nmsgid \"source file id\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:113\n#: apps/knowledge/serializers/document.py:194\nmsgid \"The type only supports optimization|directly_return\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:115\n#: apps/knowledge/serializers/document.py:187\n#: apps/knowledge/serializers/document.py:351\nmsgid \"hit handling method\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:118\n#: apps/knowledge/serializers/document.py:189\nmsgid \"directly return similarity\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:120\n#: apps/knowledge/serializers/document.py:352\nmsgid \"document is active\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:139\n#: apps/knowledge/serializers/document.py:156\n#: apps/knowledge/serializers/document.py:161\nmsgid \"file list\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:140\nmsgid \"limit\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:143\n#: apps/knowledge/serializers/document.py:144\nmsgid \"patterns\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:146\nmsgid \"Auto Clean\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:150\n#: apps/knowledge/serializers/document.py:151\nmsgid \"document url list\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:175\n#: apps/knowledge/serializers/document.py:182\nmsgid \"document id list\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:176\n#: apps/knowledge/serializers/paragraph.py:58\n#: apps/models_provider/api/model.py:105\n#: apps/models_provider/serializers/model_apply_serializers.py:51\n#: apps/models_provider/serializers/model_serializer.py:107\n#: apps/models_provider/serializers/model_serializer.py:364\n#: apps/shared/api/shared_model.py:61\n#: apps/shared/serializers/shared_model.py:54\nmsgid \"model id\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:177\n#: apps/knowledge/serializers/paragraph.py:59\nmsgid \"prompt\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:201\nmsgid \"The template type only supports excel|csv\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:254\n#: apps/knowledge/serializers/document.py:348\n#: apps/knowledge/serializers/document.py:409\n#: apps/knowledge/serializers/document.py:504\n#: apps/knowledge/serializers/document.py:889\n#: apps/knowledge/serializers/document.py:964\n#: apps/knowledge/serializers/document.py:984\n#: apps/knowledge/serializers/document.py:1167\n#: apps/knowledge/serializers/knowledge.py:209\n#: apps/knowledge/serializers/knowledge.py:558\n#: apps/knowledge/serializers/paragraph.py:70\n#: apps/knowledge/serializers/paragraph.py:138\n#: apps/knowledge/serializers/paragraph.py:239\n#: apps/knowledge/serializers/paragraph.py:321\n#: apps/knowledge/serializers/paragraph.py:347\n#: apps/knowledge/serializers/paragraph.py:398\n#: apps/knowledge/serializers/paragraph.py:439\n#: apps/knowledge/serializers/paragraph.py:559\n#: apps/knowledge/serializers/problem.py:62\n#: apps/knowledge/serializers/problem.py:126\n#: apps/knowledge/serializers/problem.py:177\n#: apps/knowledge/serializers/problem.py:205\n#: apps/shared/api/shared_knowledge.py:196\n#: apps/shared/api/shared_knowledge.py:218\n#: apps/shared/serializers/shared_knowledge.py:158\n#: apps/shared/serializers/shared_knowledge.py:205\n#: apps/xpack/serializers/dataset_lark_serializer.py:104\n#: apps/xpack/serializers/dataset_lark_serializer.py:263\n#: apps/xpack/serializers/dataset_lark_serializer.py:284\nmsgid \"knowledge id\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:255\n#: apps/knowledge/serializers/paragraph.py:441\nmsgid \"target knowledge id\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:256\nmsgid \"document list\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:257\n#: apps/knowledge/serializers/document.py:410\n#: apps/knowledge/serializers/document.py:503\n#: apps/knowledge/serializers/document.py:737\n#: apps/knowledge/serializers/paragraph.py:61\n#: apps/knowledge/serializers/paragraph.py:71\n#: apps/knowledge/serializers/paragraph.py:140\n#: apps/knowledge/serializers/paragraph.py:240\n#: apps/knowledge/serializers/paragraph.py:322\n#: apps/knowledge/serializers/paragraph.py:349\n#: apps/knowledge/serializers/paragraph.py:399\n#: apps/knowledge/serializers/paragraph.py:440\n#: apps/knowledge/serializers/paragraph.py:560\n#: apps/knowledge/serializers/problem.py:36\n#: apps/knowledge/serializers/problem.py:51\n#: apps/xpack/serializers/dataset_lark_serializer.py:160\nmsgid \"document id\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:354 apps/xpack/api/license.py:25\n#: apps/xpack/serializers/operate_log_serializer.py:60\n#: apps/xpack/serializers/operate_log_serializer.py:174\nmsgid \"status\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:355\nmsgid \"order by\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:417\n#: apps/knowledge/serializers/document.py:510\n#: apps/xpack/serializers/dataset_lark_serializer.py:167\n#: apps/xpack/serializers/dataset_lark_serializer.py:189\nmsgid \"document id not exist\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:419\n#: apps/knowledge/serializers/knowledge.py:570\nmsgid \"Synchronization is only supported for web site types\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:661\nmsgid \"The task is being executed, please do not send it repeatedly.\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:674\nmsgid \"Section title (optional)\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:675\nmsgid \"\"\n\"Section content (required, question answer, no more than 4096 characters)\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:676\nmsgid \"Question (optional, one per line in the cell)\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:742\nmsgid \"knowledge id not exist\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:898\nmsgid \"The maximum size of the uploaded file cannot exceed {}MB\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:976\nmsgid \"space\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:977\nmsgid \"semicolon\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:977\nmsgid \"comma\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:978\nmsgid \"period\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:978\nmsgid \"enter\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:979\nmsgid \"blank line\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:1140\nmsgid \"Hit handling method is required\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/document.py:1142\nmsgid \"The hit processing method must be directly_return|optimization\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:51\n#: apps/knowledge/serializers/knowledge.py:58\n#: apps/knowledge/serializers/knowledge.py:67\n#: apps/knowledge/serializers/knowledge.py:108\n#: apps/shared/api/shared_knowledge.py:117\n#: apps/shared/api/shared_knowledge.py:150\n#: apps/shared/serializers/shared_knowledge.py:20\n#: apps/shared/serializers/shared_knowledge.py:26\n#: apps/shared/serializers/shared_knowledge.py:59\n#: apps/shared/serializers/shared_knowledge.py:106\n#: apps/xpack/serializers/dataset_lark_serializer.py:51\n#: apps/xpack/serializers/dataset_lark_serializer.py:289\nmsgid \"knowledge name\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:53\n#: apps/knowledge/serializers/knowledge.py:60\n#: apps/knowledge/serializers/knowledge.py:68\n#: apps/knowledge/serializers/knowledge.py:110\n#: apps/shared/api/shared_knowledge.py:124\n#: apps/shared/api/shared_knowledge.py:157\n#: apps/shared/serializers/shared_knowledge.py:21\n#: apps/shared/serializers/shared_knowledge.py:27\n#: apps/shared/serializers/shared_knowledge.py:60\n#: apps/shared/serializers/shared_knowledge.py:108\n#: apps/xpack/serializers/dataset_lark_serializer.py:53\n#: apps/xpack/serializers/dataset_lark_serializer.py:291\nmsgid \"knowledge description\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:54\n#: apps/knowledge/serializers/knowledge.py:61\n#: apps/shared/serializers/shared_knowledge.py:22\n#: apps/shared/serializers/shared_knowledge.py:28\nmsgid \"knowledge embedding\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:63\n#: apps/shared/serializers/shared_knowledge.py:30\nmsgid \"knowledge selector\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:73\n#: apps/xpack/serializers/dataset_lark_serializer.py:296\nmsgid \"application id list\"\nmsgstr \"Agent ID list\"\n\n#: apps/knowledge/serializers/knowledge.py:75\nmsgid \"file size limit\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:76\nmsgid \"file count limit\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:95\n#: apps/knowledge/serializers/knowledge.py:638\nmsgid \"query text\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:96\n#: apps/knowledge/serializers/knowledge.py:639\nmsgid \"top number\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:98\n#: apps/knowledge/serializers/knowledge.py:641\nmsgid \"search mode\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:113\nmsgid \"knowledge scope\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:169\n#: apps/tools/serializers/tool.py:434 apps/tools/serializers/tool.py:464\nmsgid \"Folder not found\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:236\n#: apps/knowledge/serializers/knowledge.py:265\nmsgid \"Failed to send the vectorization task, please try again later!\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:315\n#: apps/knowledge/serializers/knowledge.py:471\n#: apps/knowledge/serializers/knowledge.py:533\n#: apps/xpack/serializers/dataset_lark_serializer.py:82\n#: apps/xpack/serializers/dataset_lark_serializer.py:340\nmsgid \"Knowledge base name duplicate!\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:341\n#: apps/xpack/serializers/dataset_lark_serializer.py:359\n#, python-brace-format\nmsgid \"Unknown application id {knowledge_id}, cannot be associated\"\nmsgstr \"Unknown Agent ID {knowledge_id}, cannot be associated\"\n\n#: apps/knowledge/serializers/knowledge.py:449\n#: apps/shared/serializers/shared_knowledge.py:62\n#: apps/shared/serializers/shared_knowledge.py:110\n#: apps/shared/serializers/shared_tool.py:46\n#: apps/shared/serializers/shared_tool.py:87 apps/tools/serializers/tool.py:456\n#: apps/xpack/serializers/dataset_lark_serializer.py:47\nmsgid \"scope\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:460\nmsgid \"\"\n\"The community version supports up to 50 knowledge bases. If you need more \"\n\"knowledge bases, please contact us (https://fit2cloud.com/).\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:560\nmsgid \"sync type\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:562\nmsgid \"The synchronization type only supports:replace|complete\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:568\n#: apps/knowledge/serializers/knowledge.py:649\nmsgid \"id does not exist\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/knowledge.py:636 apps/users/api/user.py:76\nmsgid \"id\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:39\n#: apps/knowledge/serializers/problem.py:27\n#: apps/knowledge/serializers/problem.py:31\n#: apps/knowledge/serializers/problem.py:206\nmsgid \"content\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:41\n#: apps/knowledge/serializers/paragraph.py:48\n#: apps/knowledge/serializers/paragraph.py:51\n#: apps/knowledge/serializers/paragraph.py:65\n#: apps/knowledge/serializers/paragraph.py:67\n#: apps/knowledge/serializers/paragraph.py:323\nmsgid \"section title\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:44\n#: apps/tools/serializers/tool.py:152 apps/tools/serializers/tool.py:164\n#: apps/xpack/serializers/system_api_key.py:11\nmsgid \"Is active\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:56\n#: apps/knowledge/serializers/paragraph.py:443\nmsgid \"paragraph id list\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:57\n#: apps/knowledge/serializers/paragraph.py:72\n#: apps/knowledge/serializers/paragraph.py:136\n#: apps/knowledge/serializers/paragraph.py:350\n#: apps/knowledge/serializers/paragraph.py:444\n#: apps/knowledge/serializers/paragraph.py:561\n#: apps/knowledge/serializers/problem.py:35\n#: apps/knowledge/serializers/problem.py:50\nmsgid \"paragraph id\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:77\n#: apps/knowledge/serializers/paragraph.py:145\nmsgid \"Paragraph id does not exist\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:108\nmsgid \"Already associated, please do not associate again\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:181\nmsgid \"Problem id does not exist\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:348\n#: apps/knowledge/serializers/problem.py:26\n#: apps/knowledge/serializers/problem.py:46\n#: apps/knowledge/serializers/problem.py:56\n#: apps/knowledge/serializers/problem.py:127\nmsgid \"problem id\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:358\nmsgid \"Paragraph does not exist\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:360\nmsgid \"Problem does not exist\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:435\nmsgid \"The task is being executed, please do not send it again.\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:442\nmsgid \"target document id\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:453\nmsgid \"The document to be migrated is consistent with the target document\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:455\n#, python-brace-format\nmsgid \"The document id does not exist [{document_id}]\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:459\n#, python-brace-format\nmsgid \"The target document id does not exist [{document_id}]\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/paragraph.py:573\nmsgid \"new_position must be an integer\"\nmsgstr \"\"\n\n#: apps/knowledge/serializers/problem.py:45\n#: apps/knowledge/serializers/problem.py:55\nmsgid \"problem id list\"\nmsgstr \"\"\n\n#: apps/knowledge/task/embedding.py:24 apps/knowledge/task/embedding.py:74\n#, python-brace-format\nmsgid \"Failed to obtain vector model: {error} {traceback}\"\nmsgstr \"\"\n\n#: apps/knowledge/task/embedding.py:103\n#, python-brace-format\nmsgid \"Start--->Vectorized knowledge: {knowledge_id}\"\nmsgstr \"\"\n\n#: apps/knowledge/task/embedding.py:107\n#, python-brace-format\nmsgid \"Knowledge documentation: {document_names}\"\nmsgstr \"\"\n\n#: apps/knowledge/task/embedding.py:120\n#, python-brace-format\nmsgid \"End--->Vectorized knowledge: {knowledge_id}\"\nmsgstr \"\"\n\n#: apps/knowledge/task/generate.py:106\n#, python-brace-format\nmsgid \"\"\n\"Generate issue based on document: {document_id} error {error}{traceback}\"\nmsgstr \"\"\n\n#: apps/knowledge/task/generate.py:110\n#, python-brace-format\nmsgid \"End--->Generate problem: {document_id}\"\nmsgstr \"\"\n\n#: apps/knowledge/task/handler.py:121\n#, python-brace-format\nmsgid \"Association problem failed {error}\"\nmsgstr \"\"\n\n#: apps/knowledge/task/sync.py:30 apps/knowledge/task/sync.py:47\n#, python-brace-format\nmsgid \"Start--->Start synchronization web knowledge base:{knowledge_id}\"\nmsgstr \"\"\n\n#: apps/knowledge/task/sync.py:35 apps/knowledge/task/sync.py:51\n#, python-brace-format\nmsgid \"End--->End synchronization web knowledge base:{knowledge_id}\"\nmsgstr \"\"\n\n#: apps/knowledge/task/sync.py:37 apps/knowledge/task/sync.py:53\n#, python-brace-format\nmsgid \"Synchronize web knowledge base:{knowledge_id} error{error}{traceback}\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:28 apps/knowledge/views/document.py:29\n#: apps/knowledge/views/document.py:30\nmsgid \"Create document\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:34 apps/knowledge/views/document.py:57\n#: apps/knowledge/views/document.py:84 apps/knowledge/views/document.py:104\n#: apps/knowledge/views/document.py:128 apps/knowledge/views/document.py:160\n#: apps/knowledge/views/document.py:191 apps/knowledge/views/document.py:209\n#: apps/knowledge/views/document.py:238 apps/knowledge/views/document.py:267\n#: apps/knowledge/views/document.py:295 apps/knowledge/views/document.py:323\n#: apps/knowledge/views/document.py:352 apps/knowledge/views/document.py:382\n#: apps/knowledge/views/document.py:412 apps/knowledge/views/document.py:441\n#: apps/knowledge/views/document.py:472 apps/knowledge/views/document.py:501\n#: apps/knowledge/views/document.py:527 apps/knowledge/views/document.py:553\n#: apps/knowledge/views/document.py:579 apps/knowledge/views/document.py:599\n#: apps/knowledge/views/document.py:633 apps/knowledge/views/document.py:664\n#: apps/knowledge/views/document.py:695 apps/knowledge/views/document.py:723\n#: apps/knowledge/views/document.py:737\n#: apps/xpack/views/dataset_lark_views.py:72\n#: apps/xpack/views/dataset_lark_views.py:91\n#: apps/xpack/views/dataset_lark_views.py:111\n#: apps/xpack/views/dataset_lark_views.py:132\nmsgid \"Knowledge Base/Documentation\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:52 apps/knowledge/views/document.py:53\n#: apps/knowledge/views/document.py:54\nmsgid \"Get document\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:79 apps/knowledge/views/document.py:80\n#: apps/knowledge/views/document.py:81\nmsgid \"Get document details\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:98 apps/knowledge/views/document.py:99\n#: apps/knowledge/views/document.py:100\nmsgid \"Modify document\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:123 apps/knowledge/views/document.py:124\n#: apps/knowledge/views/document.py:125\nmsgid \"Delete document\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:154 apps/knowledge/views/document.py:155\n#: apps/knowledge/views/document.py:156\nmsgid \"Segmented document\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:186 apps/knowledge/views/document.py:187\n#: apps/knowledge/views/document.py:188\nmsgid \"Get a list of segment IDs\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:203 apps/knowledge/views/document.py:204\n#: apps/knowledge/views/document.py:205\nmsgid \"Modify document hit processing methods in batches\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:232 apps/knowledge/views/document.py:233\n#: apps/knowledge/views/document.py:234\nmsgid \"Synchronize web site types\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:261 apps/knowledge/views/document.py:262\n#: apps/knowledge/views/document.py:263\nmsgid \"Refresh document vector library\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:289 apps/knowledge/views/document.py:290\n#: apps/knowledge/views/document.py:291\nmsgid \"Cancel task\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:317 apps/knowledge/views/document.py:318\n#: apps/knowledge/views/document.py:319\nmsgid \"Cancel tasks in batches\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:346 apps/knowledge/views/document.py:347\n#: apps/knowledge/views/document.py:348\nmsgid \"Create documents in batches\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:376 apps/knowledge/views/document.py:377\n#: apps/knowledge/views/document.py:378\nmsgid \"Batch sync documents\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:406 apps/knowledge/views/document.py:407\n#: apps/knowledge/views/document.py:408\nmsgid \"Delete documents in batches\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:436 apps/knowledge/views/document.py:437\nmsgid \"Batch refresh document vector library\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:466 apps/knowledge/views/document.py:467\n#: apps/knowledge/views/document.py:468\nmsgid \"Batch generate related problems\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:496 apps/knowledge/views/document.py:497\n#: apps/knowledge/views/document.py:498\nmsgid \"Get document by pagination\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:523 apps/knowledge/views/document.py:524\nmsgid \"Export document\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:549 apps/knowledge/views/document.py:550\nmsgid \"Export Zip document\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:575 apps/knowledge/views/document.py:576\nmsgid \"Download source file\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:594 apps/knowledge/views/document.py:595\nmsgid \"Migrate documents in batches\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:627 apps/knowledge/views/document.py:628\n#: apps/knowledge/views/document.py:629\n#: apps/shared/views/shared_document.py:570\nmsgid \"Create Web site documents\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:658 apps/knowledge/views/document.py:659\n#: apps/knowledge/views/document.py:660\nmsgid \"Import QA and create documentation\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:689 apps/knowledge/views/document.py:690\n#: apps/knowledge/views/document.py:691\nmsgid \"Import tables and create documents\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:719 apps/knowledge/views/document.py:720\nmsgid \"Get QA template\"\nmsgstr \"\"\n\n#: apps/knowledge/views/document.py:733 apps/knowledge/views/document.py:734\nmsgid \"Get form template\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:25 apps/knowledge/views/knowledge.py:26\n#: apps/knowledge/views/knowledge.py:27\nmsgid \"Get knowledge by folder\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:30 apps/knowledge/views/knowledge.py:59\n#: apps/knowledge/views/knowledge.py:83 apps/knowledge/views/knowledge.py:106\n#: apps/knowledge/views/knowledge.py:127 apps/knowledge/views/knowledge.py:156\n#: apps/knowledge/views/knowledge.py:188 apps/knowledge/views/knowledge.py:218\n#: apps/knowledge/views/knowledge.py:242 apps/knowledge/views/knowledge.py:266\n#: apps/knowledge/views/knowledge.py:293 apps/knowledge/views/knowledge.py:319\n#: apps/knowledge/views/knowledge.py:343 apps/knowledge/views/knowledge.py:369\n#: apps/knowledge/views/knowledge.py:397\n#: apps/xpack/views/dataset_lark_views.py:29\n#: apps/xpack/views/dataset_lark_views.py:50\nmsgid \"Knowledge Base\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:53 apps/knowledge/views/knowledge.py:54\n#: apps/knowledge/views/knowledge.py:55\nmsgid \"Edit knowledge\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:77 apps/knowledge/views/knowledge.py:78\n#: apps/knowledge/views/knowledge.py:79\nmsgid \"Delete knowledge\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:101 apps/knowledge/views/knowledge.py:102\n#: apps/knowledge/views/knowledge.py:103\nmsgid \"Get knowledge\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:122 apps/knowledge/views/knowledge.py:123\n#: apps/knowledge/views/knowledge.py:124\nmsgid \"Get the knowledge base paginated list\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:150 apps/knowledge/views/knowledge.py:151\n#: apps/knowledge/views/knowledge.py:152\nmsgid \"Synchronize the knowledge base of the website\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:182 apps/knowledge/views/knowledge.py:183\n#: apps/knowledge/views/knowledge.py:184\nmsgid \"Hit test list\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:212 apps/knowledge/views/knowledge.py:213\n#: apps/knowledge/views/knowledge.py:214\nmsgid \"Re-vectorize\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:238 apps/knowledge/views/knowledge.py:239\nmsgid \"Export knowledge base\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:262 apps/knowledge/views/knowledge.py:263\nmsgid \"Export knowledge base containing images\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:287 apps/knowledge/views/knowledge.py:288\n#: apps/knowledge/views/knowledge.py:289\nmsgid \"Generate related\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:314 apps/knowledge/views/knowledge.py:315\n#: apps/knowledge/views/knowledge.py:316\nmsgid \"Get model for knowledge base\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:338 apps/knowledge/views/knowledge.py:339\n#: apps/knowledge/views/knowledge.py:340\nmsgid \"Get embedding model for knowledge base\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:363 apps/knowledge/views/knowledge.py:364\n#: apps/knowledge/views/knowledge.py:365\nmsgid \"Create base knowledge\"\nmsgstr \"\"\n\n#: apps/knowledge/views/knowledge.py:391 apps/knowledge/views/knowledge.py:392\n#: apps/knowledge/views/knowledge.py:393\nmsgid \"Create web knowledge\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:24 apps/knowledge/views/paragraph.py:25\n#: apps/knowledge/views/paragraph.py:26\nmsgid \"Paragraph list\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:29 apps/knowledge/views/paragraph.py:53\n#: apps/knowledge/views/paragraph.py:82 apps/knowledge/views/paragraph.py:102\n#: apps/knowledge/views/paragraph.py:138 apps/knowledge/views/paragraph.py:167\n#: apps/knowledge/views/paragraph.py:199 apps/knowledge/views/paragraph.py:224\n#: apps/knowledge/views/paragraph.py:259 apps/knowledge/views/paragraph.py:289\n#: apps/knowledge/views/paragraph.py:316 apps/knowledge/views/paragraph.py:351\n#: apps/knowledge/views/paragraph.py:385 apps/knowledge/views/paragraph.py:415\nmsgid \"Knowledge Base/Documentation/Paragraph\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:48 apps/knowledge/views/paragraph.py:49\nmsgid \"Create Paragraph\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:76 apps/knowledge/views/paragraph.py:77\n#: apps/knowledge/views/paragraph.py:78\nmsgid \"Batch Paragraph\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:97 apps/knowledge/views/paragraph.py:98\nmsgid \"Migrate paragraphs in batches\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:132 apps/knowledge/views/paragraph.py:133\n#: apps/knowledge/views/paragraph.py:134\nmsgid \"Batch Generate Related\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:161 apps/knowledge/views/paragraph.py:162\n#: apps/knowledge/views/paragraph.py:163\nmsgid \"Modify paragraph data\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:194 apps/knowledge/views/paragraph.py:195\n#: apps/knowledge/views/paragraph.py:196\nmsgid \"Get paragraph details\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:219 apps/knowledge/views/paragraph.py:220\n#: apps/knowledge/views/paragraph.py:221\nmsgid \"Delete paragraph\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:253 apps/knowledge/views/paragraph.py:254\n#: apps/knowledge/views/paragraph.py:255\nmsgid \"Add associated questions\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:284 apps/knowledge/views/paragraph.py:285\n#: apps/knowledge/views/paragraph.py:286\nmsgid \"Get a list of paragraph questions\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:310 apps/knowledge/views/paragraph.py:311\n#: apps/knowledge/views/paragraph.py:312\nmsgid \"Disassociation issue\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:345 apps/knowledge/views/paragraph.py:346\n#: apps/knowledge/views/paragraph.py:347\nmsgid \"Related questions\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:380 apps/knowledge/views/paragraph.py:381\n#: apps/knowledge/views/paragraph.py:382\nmsgid \"Get paragraph list by pagination\"\nmsgstr \"\"\n\n#: apps/knowledge/views/paragraph.py:409 apps/knowledge/views/paragraph.py:410\n#: apps/knowledge/views/paragraph.py:411\n#: apps/resource_manage/views/paragraph.py:364\n#: apps/resource_manage/views/paragraph.py:365\n#: apps/resource_manage/views/paragraph.py:366\n#: apps/shared/views/shared_paragraph.py:365\n#: apps/shared/views/shared_paragraph.py:366\n#: apps/shared/views/shared_paragraph.py:367\nmsgid \"Adjust paragraph position\"\nmsgstr \"\"\n\n#: apps/knowledge/views/problem.py:23 apps/knowledge/views/problem.py:24\n#: apps/knowledge/views/problem.py:25\nmsgid \"Question list\"\nmsgstr \"\"\n\n#: apps/knowledge/views/problem.py:28 apps/knowledge/views/problem.py:53\n#: apps/knowledge/views/problem.py:78 apps/knowledge/views/problem.py:104\n#: apps/knowledge/views/problem.py:131 apps/knowledge/views/problem.py:157\n#: apps/knowledge/views/problem.py:186 apps/knowledge/views/problem.py:216\nmsgid \"Knowledge Base/Documentation/Paragraph/Question\"\nmsgstr \"\"\n\n#: apps/knowledge/views/problem.py:47 apps/knowledge/views/problem.py:48\n#: apps/knowledge/views/problem.py:49\nmsgid \"Create question\"\nmsgstr \"\"\n\n#: apps/knowledge/views/problem.py:73 apps/knowledge/views/problem.py:74\n#: apps/knowledge/views/problem.py:75\nmsgid \"Get a list of associated paragraphs\"\nmsgstr \"\"\n\n#: apps/knowledge/views/problem.py:98 apps/knowledge/views/problem.py:99\n#: apps/knowledge/views/problem.py:100\nmsgid \"Batch associated paragraphs\"\nmsgstr \"\"\n\n#: apps/knowledge/views/problem.py:125 apps/knowledge/views/problem.py:126\n#: apps/knowledge/views/problem.py:127\nmsgid \"Batch deletion issues\"\nmsgstr \"\"\n\n#: apps/knowledge/views/problem.py:152 apps/knowledge/views/problem.py:153\n#: apps/knowledge/views/problem.py:154\nmsgid \"Delete question\"\nmsgstr \"\"\n\n#: apps/knowledge/views/problem.py:180 apps/knowledge/views/problem.py:181\n#: apps/knowledge/views/problem.py:182\nmsgid \"Modify question\"\nmsgstr \"\"\n\n#: apps/knowledge/views/problem.py:211 apps/knowledge/views/problem.py:212\n#: apps/knowledge/views/problem.py:213\nmsgid \"Get the list of questions by page\"\nmsgstr \"\"\n\n#: apps/maxkb/settings/base.py:101\nmsgid \"Intelligent customer service platform\"\nmsgstr \"\"\n\n#: apps/models_provider/api/model.py:37 apps/models_provider/api/provide.py:17\n#: apps/models_provider/api/provide.py:23\n#: apps/models_provider/api/provide.py:28\n#: apps/models_provider/api/provide.py:30\n#: apps/models_provider/api/provide.py:82\n#: apps/models_provider/serializers/model_serializer.py:40\n#: apps/models_provider/serializers/model_serializer.py:215\n#: apps/models_provider/serializers/model_serializer.py:253\n#: apps/models_provider/serializers/model_serializer.py:318\n#: apps/models_provider/serializers/model_serializer.py:393\n#: apps/shared/api/shared_model.py:18\n#: apps/shared/serializers/shared_model.py:111\nmsgid \"model name\"\nmsgstr \"\"\n\n#: apps/models_provider/api/model.py:44 apps/models_provider/api/provide.py:29\n#: apps/models_provider/api/provide.py:70\n#: apps/models_provider/api/provide.py:98\n#: apps/models_provider/serializers/model_serializer.py:42\n#: apps/models_provider/serializers/model_serializer.py:217\n#: apps/models_provider/serializers/model_serializer.py:255\n#: apps/models_provider/serializers/model_serializer.py:319\n#: apps/models_provider/serializers/model_serializer.py:394\n#: apps/shared/api/shared_model.py:25\n#: apps/shared/serializers/shared_model.py:112\nmsgid \"model type\"\nmsgstr \"\"\n\n#: apps/models_provider/api/model.py:51\n#: apps/models_provider/serializers/model_serializer.py:43\n#: apps/models_provider/serializers/model_serializer.py:219\n#: apps/models_provider/serializers/model_serializer.py:256\n#: apps/models_provider/serializers/model_serializer.py:320\n#: apps/models_provider/serializers/model_serializer.py:395\n#: apps/shared/api/shared_model.py:32\n#: apps/shared/serializers/shared_model.py:113\nmsgid \"base model\"\nmsgstr \"\"\n\n#: apps/models_provider/api/model.py:58 apps/models_provider/api/provide.py:18\n#: apps/models_provider/api/provide.py:38\n#: apps/models_provider/api/provide.py:76\n#: apps/models_provider/api/provide.py:104\n#: apps/models_provider/api/provide.py:126\n#: apps/models_provider/serializers/model_serializer.py:41\n#: apps/models_provider/serializers/model_serializer.py:254\n#: apps/models_provider/serializers/model_serializer.py:321\n#: apps/models_provider/serializers/model_serializer.py:396\n#: apps/shared/api/shared_model.py:39\n#: apps/shared/serializers/shared_model.py:114\nmsgid \"provider\"\nmsgstr \"\"\n\n#: apps/models_provider/api/model.py:65\n#: apps/models_provider/serializers/model_serializer.py:322\n#: apps/models_provider/serializers/model_serializer.py:397\n#: apps/shared/api/shared_model.py:46\n#: apps/shared/serializers/shared_model.py:115\nmsgid \"create user\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:19\n#: apps/xpack/serializers/application_setting_serializer.py:41\n#: apps/xpack/serializers/system_params.py:21\nmsgid \"icon\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:34 apps/tools/serializers/tool.py:134\nmsgid \"input type\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:35\nmsgid \"label\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:36\nmsgid \"text field\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:37\nmsgid \"value field\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:39\nmsgid \"method\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:40 apps/tools/serializers/tool.py:119\n#: apps/tools/serializers/tool.py:133\nmsgid \"required\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:41\nmsgid \"default value\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:42\nmsgid \"relation show field dict\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:43\nmsgid \"relation trigger field dict\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:44\nmsgid \"trigger type\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:45\nmsgid \"attrs\"\nmsgstr \"\"\n\n#: apps/models_provider/api/provide.py:46\nmsgid \"props info\"\nmsgstr \"\"\n\n#: apps/models_provider/base_model_provider.py:60\nmsgid \"Model type cannot be empty\"\nmsgstr \"\"\n\n#: apps/models_provider/base_model_provider.py:85\nmsgid \"The current platform does not support downloading models\"\nmsgstr \"\"\n\n#: apps/models_provider/base_model_provider.py:143\nmsgid \"LLM\"\nmsgstr \"\"\n\n#: apps/models_provider/base_model_provider.py:144\nmsgid \"Embedding Model\"\nmsgstr \"\"\n\n#: apps/models_provider/base_model_provider.py:145\nmsgid \"Speech2Text\"\nmsgstr \"\"\n\n#: apps/models_provider/base_model_provider.py:146\nmsgid \"TTS\"\nmsgstr \"\"\n\n#: apps/models_provider/base_model_provider.py:147\nmsgid \"Vision Model\"\nmsgstr \"\"\n\n#: apps/models_provider/base_model_provider.py:148\nmsgid \"Image Generation\"\nmsgstr \"\"\n\n#: apps/models_provider/base_model_provider.py:149\nmsgid \"Rerank\"\nmsgstr \"\"\n\n#: apps/models_provider/base_model_provider.py:223\nmsgid \"The model does not support\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:42\nmsgid \"\"\n\"With the GTE-Rerank text sorting series model developed by Alibaba Tongyi \"\n\"Lab, developers can integrate high-quality text retrieval and sorting \"\n\"through the LlamaIndex framework.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:45\nmsgid \"\"\n\"Chinese (including various dialects such as Cantonese), English, Japanese, \"\n\"and Korean support free switching between multiple languages.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:48\nmsgid \"\"\n\"CosyVoice is based on a new generation of large generative speech models, \"\n\"which can predict emotions, intonation, rhythm, etc. based on context, and \"\n\"has better anthropomorphic effects.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:51\nmsgid \"\"\n\"Universal text vector is Tongyi Lab's multi-language text unified vector \"\n\"model based on the LLM base. It provides high-level vector services for \"\n\"multiple mainstream languages around the world and helps developers quickly \"\n\"convert text data into high-quality vector data.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:69\nmsgid \"\"\n\"Tongyi Wanxiang - a large image model for text generation, supports \"\n\"bilingual input in Chinese and English, and supports the input of reference \"\n\"pictures for reference content or reference style migration. Key styles \"\n\"include but are not limited to watercolor, oil painting, Chinese painting, \"\n\"sketch, flat illustration, two-dimensional, and 3D. Cartoon.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:95\nmsgid \"Alibaba Cloud Bailian\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py:53\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:50\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:74\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:77\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:61\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py:43\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py:37\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:33\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:34\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:53\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:37\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:40\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:69\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:32\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/gemini_model_provider/model/stt.py:43\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:37\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:37\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:44\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:35\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:35\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:58\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:37\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:58\n#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:23\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:37\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:51\n#: apps/models_provider/impl/tencent_model_provider/model/tti.py:54\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:50\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:32\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/volcanic_engine_model_provider/model/tts.py:77\n#: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:60\n#: apps/models_provider/impl/xf_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:76\n#: apps/models_provider/impl/xf_model_provider/model/tts.py:101\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:32\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:50\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:34\n#: apps/models_provider/impl/xinference_model_provider/model/tts.py:44\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:31\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:56\n#: apps/models_provider/impl/zhipu_model_provider/model/tti.py:49\nmsgid \"Hello\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:36\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:46\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:44\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:96\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:89\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:23\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:21\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:40\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:30\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/azure_model_provider/credential/stt.py:23\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:58\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:41\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:22\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/gemini_model_provider/credential/stt.py:21\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:28\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/ollama_model_provider/credential/image.py:19\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:44\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:27\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:31\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:25\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:48\n#: apps/models_provider/impl/openai_model_provider/credential/stt.py:22\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:61\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:40\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:25\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:28\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:22\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:61\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:22\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:19\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:28\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:78\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/vllm_model_provider/credential/image.py:22\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:39\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:22\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:25\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:41\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:51\n#: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:46\n#: apps/models_provider/impl/xf_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:29\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/xf_model_provider/credential/stt.py:24\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:47\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:19\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:22\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:39\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:25\n#: apps/models_provider/impl/xinference_model_provider/credential/stt.py:21\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:59\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:39\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:21\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:40\n#, python-brace-format\nmsgid \"{model_type} Model type is not supported\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:44\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:67\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:55\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:53\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:105\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:98\n#, python-brace-format\nmsgid \"{key} is required\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:60\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:85\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:69\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:67\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:121\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:113\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:43\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:65\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:42\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:61\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:50\n#: apps/models_provider/impl/azure_model_provider/credential/stt.py:40\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:77\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:58\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:65\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:42\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/gemini_model_provider/credential/stt.py:38\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:64\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:44\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:45\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:51\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:45\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:67\n#: apps/models_provider/impl/openai_model_provider/credential/stt.py:39\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:80\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:58\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:45\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:44\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:39\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:80\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:40\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:30\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:47\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:104\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/vllm_model_provider/credential/image.py:42\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:55\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:42\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:42\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:58\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:68\n#: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:38\n#: apps/models_provider/impl/xf_model_provider/credential/embedding.py:38\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:50\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:84\n#: apps/models_provider/impl/xf_model_provider/credential/stt.py:41\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:65\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:41\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:40\n#: apps/models_provider/impl/xinference_model_provider/credential/stt.py:37\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:77\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:56\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:41\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:64\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:59\n#, python-brace-format\nmsgid \"\"\n\"Verification failed, please check whether the parameters are correct: {error}\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:17\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:14\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:20\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:14\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:41\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:22\nmsgid \"Temperature\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:18\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:21\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:16\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:42\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:16\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:23\nmsgid \"\"\n\"Higher values make the output more random, while lower values make it more \"\n\"focused and deterministic\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:30\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:43\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:29\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:50\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:31\nmsgid \"Output the maximum Tokens\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:31\nmsgid \"Specify the maximum number of tokens that the model can generate.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:43\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:15\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:74\nmsgid \"API URL\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:14\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:15\nmsgid \"Image size\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:15\nmsgid \"Specify the size of the generated image, such as: 1024x1024\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:40\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:43\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:43\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:41\nmsgid \"Number of pictures\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:40\nmsgid \"Specify the number of generated images\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44\nmsgid \"Style\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44\nmsgid \"Specify the style of generated images\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:48\nmsgid \"Default value, the image style is randomly output by the model\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:49\nmsgid \"photography\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:50\nmsgid \"Portraits\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:51\nmsgid \"3D cartoon\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:52\nmsgid \"animation\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:53\nmsgid \"painting\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:54\nmsgid \"watercolor\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:55\nmsgid \"sketch\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:56\nmsgid \"Chinese painting\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:57\nmsgid \"flat illustration\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20\nmsgid \"Timbre\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15\nmsgid \"Chinese sounds can support mixed scenes of Chinese and English\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:26\nmsgid \"Long Xiaochun\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:27\nmsgid \"Long Xiaoxia\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:28\nmsgid \"Long Xiaochen\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:29\nmsgid \"Long Xiaobai\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:30\nmsgid \"Long Laotie\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:31\nmsgid \"Long Shu\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:32\nmsgid \"Long Shuo\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:33\nmsgid \"Long Jing\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:34\nmsgid \"Long Miao\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:35\nmsgid \"Long Yue\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:36\nmsgid \"Long Yuan\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:37\nmsgid \"Long Fei\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:38\nmsgid \"Long Jielidou\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:39\nmsgid \"Long Tong\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:40\nmsgid \"Long Xiang\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47\nmsgid \"Speaking speed\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47\nmsgid \"[0.5, 2], the default is 1, usually one decimal place is enough\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:28\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:32\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:35\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:64\n#: apps/models_provider/impl/azure_model_provider/credential/stt.py:28\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:63\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:46\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:27\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/gemini_model_provider/credential/stt.py:26\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:32\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:46\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:62\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:63\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:30\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:53\n#: apps/models_provider/impl/openai_model_provider/credential/stt.py:27\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:66\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:45\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:30\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:32\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:27\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:66\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:27\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:32\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/vllm_model_provider/credential/image.py:27\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:65\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:27\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:30\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:46\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:56\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:55\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:72\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:34\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:71\n#: apps/models_provider/impl/xf_model_provider/credential/stt.py:29\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:52\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:40\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:27\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:29\n#: apps/models_provider/impl/xinference_model_provider/credential/stt.py:26\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:64\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:44\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:26\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:51\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:45\n#, python-brace-format\nmsgid \"{key}  is required\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:33\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:44\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:30\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:33\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:25\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:51\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:25\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:32\nmsgid \"Specify the maximum number of tokens that the model can generate\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:36\nmsgid \"\"\n\"An update to Claude 2 that doubles the context window and improves \"\n\"reliability, hallucination rates, and evidence-based accuracy in long \"\n\"documents and RAG contexts.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:43\nmsgid \"\"\n\"Anthropic is a powerful model that can handle a variety of tasks, from \"\n\"complex dialogue and creative content generation to detailed command \"\n\"obedience.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:50\nmsgid \"\"\n\"The Claude 3 Haiku is Anthropic's fastest and most compact model, with near-\"\n\"instant responsiveness. The model can answer simple queries and requests \"\n\"quickly. Customers will be able to build seamless AI experiences that mimic \"\n\"human interactions. Claude 3 Haiku can process images and return text \"\n\"output, and provides 200K context windows.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:57\nmsgid \"\"\n\"The Claude 3 Sonnet model from Anthropic strikes the ideal balance between \"\n\"intelligence and speed, especially when it comes to handling enterprise \"\n\"workloads. This model offers maximum utility while being priced lower than \"\n\"competing products, and it's been engineered to be a solid choice for \"\n\"deploying AI at scale.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:64\nmsgid \"\"\n\"The Claude 3.5 Sonnet raises the industry standard for intelligence, \"\n\"outperforming competing models and the Claude 3 Opus in extensive \"\n\"evaluations, with the speed and cost-effectiveness of our mid-range models.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:71\nmsgid \"\"\n\"A faster, more affordable but still very powerful model that can handle a \"\n\"range of tasks including casual conversation, text analysis, summarization \"\n\"and document question answering.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:78\nmsgid \"\"\n\"Titan Text Premier is the most powerful and advanced model in the Titan Text \"\n\"series, designed to deliver exceptional performance for a variety of \"\n\"enterprise applications. With its cutting-edge features, it delivers greater \"\n\"accuracy and outstanding results, making it an excellent choice for \"\n\"organizations looking for a top-notch text processing solution.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:85\nmsgid \"\"\n\"Amazon Titan Text Lite is a lightweight, efficient model ideal for fine-\"\n\"tuning English-language tasks, including summarization and copywriting, \"\n\"where customers require smaller, more cost-effective, and highly \"\n\"customizable models.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:91\nmsgid \"\"\n\"Amazon Titan Text Express has context lengths of up to 8,000 tokens, making \"\n\"it ideal for a variety of high-level general language tasks, such as open-\"\n\"ended text generation and conversational chat, as well as support in \"\n\"retrieval-augmented generation (RAG). At launch, the model is optimized for \"\n\"English, but other languages are supported.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:97\nmsgid \"\"\n\"7B dense converter for rapid deployment and easy customization. Small in \"\n\"size yet powerful in a variety of use cases. Supports English and code, as \"\n\"well as 32k context windows.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:103\nmsgid \"\"\n\"Advanced Mistral AI large-scale language model capable of handling any \"\n\"language task, including complex multilingual reasoning, text understanding, \"\n\"transformation, and code generation.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:109\nmsgid \"\"\n\"Ideal for content creation, conversational AI, language understanding, R&D, \"\n\"and enterprise applications\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:115\nmsgid \"\"\n\"Ideal for limited computing power and resources, edge devices, and faster \"\n\"training times.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:123\nmsgid \"\"\n\"Titan Embed Text is the largest embedding model in the Amazon Titan Embed \"\n\"series and can handle various text embedding tasks, such as text \"\n\"classification, text similarity calculation, etc.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:28\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:47\n#, python-brace-format\nmsgid \"The following fields are required: {keys}\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:44\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:76\nmsgid \"Verification failed, please check whether the parameters are correct\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:28\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:29\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:28\nmsgid \"Picture quality\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:17\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:17\nmsgid \"\"\n\"Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) \"\n\"to find one that suits your desired tone and audience. The current voiceover \"\n\"is optimized for English.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:24\nmsgid \"Good at common conversational tasks, supports 32K contexts\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:29\nmsgid \"Good at handling programming tasks, supports 16K contexts\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:32\nmsgid \"Latest Gemini 1.0 Pro model, updated with Google update\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:36\nmsgid \"Latest Gemini 1.0 Pro Vision model, updated with Google update\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:43\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:47\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:54\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:58\nmsgid \"Latest Gemini 1.5 Flash model, updated with Google updates\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/gemini_model_provider/model/stt.py:53\nmsgid \"convert audio to text\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:53\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:54\nmsgid \"Model catalog\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/local_model_provider/local_model_provider.py:39\nmsgid \"local model\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:30\n#: apps/models_provider/impl/ollama_model_provider/credential/image.py:23\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:48\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:35\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:43\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:24\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:44\nmsgid \"API domain name is invalid\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:35\n#: apps/models_provider/impl/ollama_model_provider/credential/image.py:28\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:53\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:40\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:30\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:48\nmsgid \"The model does not exist, please download the model first\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:56\nmsgid \"\"\n\"Llama 2 is a set of pretrained and fine-tuned generative text models ranging \"\n\"in size from 7 billion to 70 billion. This is a repository of 7B pretrained \"\n\"models. Links to other models can be found in the index at the bottom.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:60\nmsgid \"\"\n\"Llama 2 is a set of pretrained and fine-tuned generative text models ranging \"\n\"in size from 7 billion to 70 billion. This is a repository of 13B pretrained \"\n\"models. Links to other models can be found in the index at the bottom.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:64\nmsgid \"\"\n\"Llama 2 is a set of pretrained and fine-tuned generative text models ranging \"\n\"in size from 7 billion to 70 billion. This is a repository of 70B pretrained \"\n\"models. Links to other models can be found in the index at the bottom.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:68\nmsgid \"\"\n\"Since the Chinese alignment of Llama2 itself is weak, we use the Chinese \"\n\"instruction set to fine-tune meta-llama/Llama-2-13b-chat-hf with LoRA so \"\n\"that it has strong Chinese conversation capabilities.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:72\nmsgid \"\"\n\"Meta Llama 3: The most capable public product LLM to date. 8 billion \"\n\"parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:76\nmsgid \"\"\n\"Meta Llama 3: The most capable public product LLM to date. 70 billion \"\n\"parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:80\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 0.5b has significantly enhanced \"\n\"the model's alignment with human preferences and its multi-language \"\n\"processing capabilities. Models of all sizes support a context length of \"\n\"32768 tokens. 500 million parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:84\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 1.8b has significantly enhanced \"\n\"the model's alignment with human preferences and its multi-language \"\n\"processing capabilities. Models of all sizes support a context length of \"\n\"32768 tokens. 1.8 billion parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:88\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 4b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"4 billion parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:93\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 7b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"7 billion parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:97\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 14b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"14 billion parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:101\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 32b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"32 billion parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:105\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 72b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"72 billion parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:109\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 110b has significantly enhanced \"\n\"the model's alignment with human preferences and its multi-language \"\n\"processing capabilities. Models of all sizes support a context length of \"\n\"32768 tokens. 110 billion parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:153\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:193\nmsgid \"\"\n\"Phi-3 Mini is Microsoft's 3.8B parameter, lightweight, state-of-the-art open \"\n\"model.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:162\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:197\nmsgid \"\"\n\"A high-performance open embedding model with a large token context window.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:16\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:16\nmsgid \"\"\n\"The image generation endpoint allows you to create raw images based on text \"\n\"prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 \"\n\"or 1792x1024 pixels.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:29\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29\nmsgid \"\"\n\"       \\n\"\n\"By default, images are produced in standard quality, but with DALL·E 3 you \"\n\"can set quality: \\\"hd\\\" to enhance detail. Square, standard quality images \"\n\"are generated fastest.\\n\"\n\"        \"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:44\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:44\nmsgid \"\"\n\"You can use DALL·E 3 to request 1 image at a time (requesting more images by \"\n\"issuing parallel requests), or use DALL·E 2 with the n parameter to request \"\n\"up to 10 images at a time.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:35\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:119\n#: apps/models_provider/impl/siliconCloud_model_provider/siliconCloud_model_provider.py:118\nmsgid \"The latest gpt-3.5-turbo, updated with OpenAI adjustments\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:38\nmsgid \"Latest gpt-4, updated with OpenAI adjustments\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:40\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:99\nmsgid \"\"\n\"The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with OpenAI \"\n\"adjustments\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:43\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:102\nmsgid \"\"\n\"The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with OpenAI \"\n\"adjustments\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:46\nmsgid \"The latest gpt-4-turbo, updated with OpenAI adjustments\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:49\nmsgid \"The latest gpt-4-turbo-preview, updated with OpenAI adjustments\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:53\nmsgid \"\"\n\"gpt-3.5-turbo snapshot on January 25, 2024, supporting context length 16,385 \"\n\"tokens\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:57\nmsgid \"\"\n\"gpt-3.5-turbo snapshot on November 6, 2023, supporting context length 16,385 \"\n\"tokens\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:61\nmsgid \"\"\n\"[Legacy] gpt-3.5-turbo snapshot on June 13, 2023, will be deprecated on June \"\n\"13, 2024\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:65\nmsgid \"\"\n\"gpt-4o snapshot on May 13, 2024, supporting context length 128,000 tokens\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:69\nmsgid \"\"\n\"gpt-4-turbo snapshot on April 9, 2024, supporting context length 128,000 \"\n\"tokens\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:72\nmsgid \"\"\n\"gpt-4-turbo snapshot on January 25, 2024, supporting context length 128,000 \"\n\"tokens\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:75\nmsgid \"\"\n\"gpt-4-turbo snapshot on November 6, 2023, supporting context length 128,000 \"\n\"tokens\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_cloud_model_provider/tencent_cloud_model_provider.py:58\nmsgid \"Tencent Cloud\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:41\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:88\n#, python-brace-format\nmsgid \"{keys} is required\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14\nmsgid \"painting style\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14\nmsgid \"If not passed, the default value is 201 (Japanese anime style)\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:18\nmsgid \"Not limited to style\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:19\nmsgid \"ink painting\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:20\nmsgid \"concept art\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:21\nmsgid \"Oil painting 1\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:22\nmsgid \"Oil Painting 2 (Van Gogh)\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:23\nmsgid \"watercolor painting\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:24\nmsgid \"pixel art\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:25\nmsgid \"impasto style\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:26\nmsgid \"illustration\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:27\nmsgid \"paper cut style\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:28\nmsgid \"Impressionism 1 (Monet)\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:29\nmsgid \"Impressionism 2\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:31\nmsgid \"classical portraiture\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:32\nmsgid \"black and white sketch\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:33\nmsgid \"cyberpunk\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:34\nmsgid \"science fiction style\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:35\nmsgid \"dark style\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:37\nmsgid \"vaporwave\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:38\nmsgid \"Japanese animation\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:39\nmsgid \"monster style\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:40\nmsgid \"Beautiful ancient style\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:41\nmsgid \"retro anime\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:42\nmsgid \"Game cartoon hand drawing\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:43\nmsgid \"Universal realistic style\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50\nmsgid \"Generate image resolution\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50\nmsgid \"If not transmitted, the default value is 768:768.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:38\nmsgid \"\"\n\"The most effective version of the current hybrid model, the trillion-level \"\n\"parameter scale MOE-32K long article model. Reaching the absolute leading \"\n\"level on various benchmarks, with complex instructions and reasoning, \"\n\"complex mathematical capabilities, support for function call, and \"\n\"application focus optimization in fields such as multi-language translation, \"\n\"finance, law, and medical care\"\nmsgstr \"\"\n\"The most effective version of the current hybrid model, the trillion-level \"\n\"parameter scale MOE-32K long article model. Reaching the absolute leading \"\n\"level on various benchmarks, with complex instructions and reasoning, \"\n\"complex mathematical capabilities, support for function call, and \"\n\"agent focus optimization in fields such as multi-language translation, \"\n\"finance, law, and medical care\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:45\nmsgid \"\"\n\"A better routing strategy is adopted to simultaneously alleviate the \"\n\"problems of load balancing and expert convergence. For long articles, the \"\n\"needle-in-a-haystack index reaches 99.9%\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:51\nmsgid \"\"\n\"Upgraded to MOE structure, the context window is 256k, leading many open \"\n\"source models in multiple evaluation sets such as NLP, code, mathematics, \"\n\"industry, etc.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:57\nmsgid \"\"\n\"Hunyuan's latest version of the role-playing model, a role-playing model \"\n\"launched by Hunyuan's official fine-tuning training, is based on the Hunyuan \"\n\"model combined with the role-playing scene data set for additional training, \"\n\"and has better basic effects in role-playing scenes.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:63\nmsgid \"\"\n\"Hunyuan's latest MOE architecture FunctionCall model has been trained with \"\n\"high-quality FunctionCall data and has a context window of 32K, leading in \"\n\"multiple dimensions of evaluation indicators.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:69\nmsgid \"\"\n\"Hunyuan's latest code generation model, after training the base model with \"\n\"200B high-quality code data, and iterating on high-quality SFT data for half \"\n\"a year, the context long window length has been increased to 8K, and it \"\n\"ranks among the top in the automatic evaluation indicators of code \"\n\"generation in the five major languages; the five major languages In the \"\n\"manual high-quality evaluation of 10 comprehensive code tasks that consider \"\n\"all aspects, the performance is in the first echelon.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:77\nmsgid \"\"\n\"Tencent's Hunyuan Embedding interface can convert text into high-quality \"\n\"vector data. The vector dimension is 1024 dimensions.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:87\nmsgid \"Mixed element visual model\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:94\nmsgid \"Hunyuan graph model\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:125\nmsgid \"Tencent Hunyuan\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:24\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:42\nmsgid \"Facebook’s 125M parameter model\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:25\nmsgid \"BAAI’s 7B parameter model\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:26\nmsgid \"BAAI’s 13B parameter mode\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:16\nmsgid \"\"\n\"If the gap between width, height and 512 is too large, the picture rendering \"\n\"effect will be poor and the probability of excessive delay will increase \"\n\"significantly. Recommended ratio and corresponding width and height before \"\n\"super score: width*height\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:15\nmsgid \"timbre\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:28\nmsgid \"speaking speed\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31\nmsgid \"[0.2,3], the default is 1, usually one decimal place is enough\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:39\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:44\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:88\nmsgid \"\"\n\"The user goes to the model inference page of Volcano Ark to create an \"\n\"inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call \"\n\"it.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:59\nmsgid \"Universal 2.0-Vincent Diagram\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:64\nmsgid \"Universal 2.0Pro-Vincent Chart\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:69\nmsgid \"Universal 1.4-Vincent Chart\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:74\nmsgid \"Animation 1.3.0-Vincent Picture\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:79\nmsgid \"Animation 1.3.1-Vincent Picture\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:113\nmsgid \"volcano engine\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:51\n#, python-brace-format\nmsgid \"{model_name} The model does not support\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:24\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:53\nmsgid \"\"\n\"ERNIE-Bot-4 is a large language model independently developed by Baidu. It \"\n\"covers massive Chinese data and has stronger capabilities in dialogue Q&A, \"\n\"content creation and generation.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:27\nmsgid \"\"\n\"ERNIE-Bot is a large language model independently developed by Baidu. It \"\n\"covers massive Chinese data and has stronger capabilities in dialogue Q&A, \"\n\"content creation and generation.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:30\nmsgid \"\"\n\"ERNIE-Bot-turbo is a large language model independently developed by Baidu. \"\n\"It covers massive Chinese data, has stronger capabilities in dialogue Q&A, \"\n\"content creation and generation, and has a faster response speed.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:33\nmsgid \"\"\n\"BLOOMZ-7B is a well-known large language model in the industry. It was \"\n\"developed and open sourced by BigScience and can output text in 46 languages \"\n\"and 13 programming languages.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:39\nmsgid \"\"\n\"Llama-2-13b-chat was developed by Meta AI and is open source. It performs \"\n\"well in scenarios such as coding, reasoning and knowledge application. \"\n\"Llama-2-13b-chat is a native open source version with balanced performance \"\n\"and effect, suitable for conversation scenarios.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:42\nmsgid \"\"\n\"Llama-2-70b-chat was developed by Meta AI and is open source. It performs \"\n\"well in scenarios such as coding, reasoning, and knowledge application. \"\n\"Llama-2-70b-chat is a native open source version with high-precision effects.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:45\nmsgid \"\"\n\"The Chinese enhanced version developed by the Qianfan team based on \"\n\"Llama-2-7b has performed well on Chinese knowledge bases such as CMMLU and C-\"\n\"EVAL.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:49\nmsgid \"\"\n\"Embedding-V1 is a text representation model based on Baidu Wenxin large \"\n\"model technology. It can convert text into a vector form represented by \"\n\"numerical values and can be used in text retrieval, information \"\n\"recommendation, knowledge mining and other scenarios. Embedding-V1 provides \"\n\"the Embeddings interface, which can generate corresponding vector \"\n\"representations based on input content. You can call this interface to input \"\n\"text into the model and obtain the corresponding vector representation for \"\n\"subsequent text processing and analysis.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:66\nmsgid \"Thousand sails large model\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:42\nmsgid \"Please outline this picture\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:15\nmsgid \"Speaker\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:16\nmsgid \"\"\n\"Speaker, optional value: Please go to the console to add a trial or purchase \"\n\"speaker. After adding, the speaker parameter value will be displayed.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:21\nmsgid \"iFlytek Xiaoyan\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:22\nmsgid \"iFlytek Xujiu\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:23\nmsgid \"iFlytek Xiaoping\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:24\nmsgid \"iFlytek Xiaojing\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:25\nmsgid \"iFlytek Xuxiaobao\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:28\nmsgid \"Speech speed, optional value: [0-100], default is 50\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:39\n#: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:50\nmsgid \"Chinese and English recognition\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:66\nmsgid \"iFlytek Spark\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:15\nmsgid \"\"\n\"The image generation endpoint allows you to create raw images based on text \"\n\"prompts. The dimensions of the image can be 1024x1024, 1024x1792, or \"\n\"1792x1024 pixels.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:29\nmsgid \"\"\n\"By default, images are generated in standard quality, you can set quality: \"\n\"\\\"hd\\\" to enhance detail. Square, standard quality images are generated \"\n\"fastest.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:42\nmsgid \"\"\n\"You can request 1 image at a time (requesting more images by making parallel \"\n\"requests), or up to 10 images at a time using the n parameter.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:20\nmsgid \"Chinese female\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:21\nmsgid \"Chinese male\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:22\nmsgid \"Japanese male\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:23\nmsgid \"Cantonese female\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:24\nmsgid \"English female\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:25\nmsgid \"English male\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:26\nmsgid \"Korean female\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:37\nmsgid \"\"\n\"Code Llama is a language model specifically designed for code generation.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:44\nmsgid \"\"\n\"       \\n\"\n\"Code Llama Instruct is a fine-tuned version of Code Llama's instructions, \"\n\"designed to perform specific tasks.\\n\"\n\"        \"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:53\nmsgid \"\"\n\"Code Llama Python is a language model specifically designed for Python code \"\n\"generation.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:60\nmsgid \"\"\n\"CodeQwen 1.5 is a language model for code generation with high performance.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:67\nmsgid \"CodeQwen 1.5 Chat is a chat model version of CodeQwen 1.5.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:74\nmsgid \"Deepseek is a large-scale language model with 13 billion parameters.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:16\nmsgid \"\"\n\"Image size, only cogview-3-plus supports this parameter. Optional range: \"\n\"[1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440], the \"\n\"default is 1024x1024.\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:34\nmsgid \"\"\n\"Have strong multi-modal understanding capabilities. Able to understand up to \"\n\"five images simultaneously and supports video content understanding\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:37\nmsgid \"\"\n\"Focus on single picture understanding. Suitable for scenarios requiring \"\n\"efficient image analysis\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:40\nmsgid \"\"\n\"Focus on single picture understanding. Suitable for scenarios requiring \"\n\"efficient image analysis (free)\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:46\nmsgid \"\"\n\"Quickly and accurately generate images based on user text descriptions. \"\n\"Resolution supports 1024x1024\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:49\nmsgid \"\"\n\"Generate high-quality images based on user text descriptions, supporting \"\n\"multiple image sizes\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:52\nmsgid \"\"\n\"Generate high-quality images based on user text descriptions, supporting \"\n\"multiple image sizes (free)\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:75\nmsgid \"zhipu AI\"\nmsgstr \"\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:32\n#: apps/models_provider/serializers/model_apply_serializers.py:37\nmsgid \"vector text\"\nmsgstr \"\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:33\nmsgid \"vector text list\"\nmsgstr \"\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:41\nmsgid \"text\"\nmsgstr \"\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:42\nmsgid \"metadata\"\nmsgstr \"\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:47\nmsgid \"query\"\nmsgstr \"\"\n\n#: apps/models_provider/serializers/model_serializer.py:44\n#: apps/models_provider/serializers/model_serializer.py:257\nmsgid \"parameter configuration\"\nmsgstr \"\"\n\n#: apps/models_provider/serializers/model_serializer.py:45\n#: apps/models_provider/serializers/model_serializer.py:222\n#: apps/models_provider/serializers/model_serializer.py:258\nmsgid \"certification information\"\nmsgstr \"\"\n\n#: apps/models_provider/serializers/model_serializer.py:118\nmsgid \"Shared models cannot be deleted or modified\"\nmsgstr \"\"\n\n#: apps/models_provider/serializers/model_serializer.py:230\n#: apps/models_provider/serializers/model_serializer.py:269\n#, python-brace-format\nmsgid \"base model【{model_name}】already exists\"\nmsgstr \"\"\n\n#: apps/models_provider/serializers/model_serializer.py:309\nmsgid \"Model saving failed\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model.py:60\n#: apps/models_provider/views/model.py:61\n#: apps/models_provider/views/model.py:62 apps/shared/views/shared_model.py:55\n#: apps/shared/views/shared_model.py:56 apps/shared/views/shared_model.py:57\nmsgid \"Create model\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model.py:90\n#: apps/models_provider/views/model.py:91\n#: apps/models_provider/views/model.py:92 apps/shared/views/shared_model.py:84\n#: apps/shared/views/shared_model.py:85 apps/shared/views/shared_model.py:86\nmsgid \"Query model list\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model.py:107\n#: apps/models_provider/views/model.py:108\n#: apps/models_provider/views/model.py:109\n#: apps/shared/views/shared_model.py:101 apps/shared/views/shared_model.py:102\n#: apps/shared/views/shared_model.py:103\nmsgid \"Update model\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model.py:125\n#: apps/models_provider/views/model.py:126\n#: apps/models_provider/views/model.py:127\n#: apps/shared/views/shared_model.py:119 apps/shared/views/shared_model.py:120\n#: apps/shared/views/shared_model.py:121\nmsgid \"Delete model\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model.py:140\n#: apps/models_provider/views/model.py:141\n#: apps/models_provider/views/model.py:142\n#: apps/shared/views/shared_model.py:133 apps/shared/views/shared_model.py:134\n#: apps/shared/views/shared_model.py:135\nmsgid \"Query model details\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model.py:155\n#: apps/models_provider/views/model.py:156\n#: apps/models_provider/views/model.py:157\n#: apps/shared/views/shared_model.py:148 apps/shared/views/shared_model.py:149\n#: apps/shared/views/shared_model.py:150\nmsgid \"Get model parameter form\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model.py:167\n#: apps/models_provider/views/model.py:168\n#: apps/models_provider/views/model.py:169\n#: apps/shared/views/shared_model.py:160 apps/shared/views/shared_model.py:161\n#: apps/shared/views/shared_model.py:162\nmsgid \"Save model parameter form\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model.py:187\n#: apps/models_provider/views/model.py:189\n#: apps/models_provider/views/model.py:191\n#: apps/shared/views/shared_model.py:179 apps/shared/views/shared_model.py:181\n#: apps/shared/views/shared_model.py:183\nmsgid \"\"\n\"Query model meta information, this interface does not carry authentication \"\n\"information\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model.py:204\n#: apps/models_provider/views/model.py:205\n#: apps/models_provider/views/model.py:206\n#: apps/shared/views/shared_model.py:196 apps/shared/views/shared_model.py:197\n#: apps/shared/views/shared_model.py:198\nmsgid \"Pause model download\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model.py:222\n#: apps/models_provider/views/model.py:223\n#: apps/models_provider/views/model.py:224\nmsgid \"Get Share model\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model_apply.py:25\n#: apps/models_provider/views/model_apply.py:26\n#: apps/models_provider/views/model_apply.py:27\n#: apps/models_provider/views/model_apply.py:37\n#: apps/models_provider/views/model_apply.py:38\n#: apps/models_provider/views/model_apply.py:39\nmsgid \"Vectorization documentation\"\nmsgstr \"\"\n\n#: apps/models_provider/views/model_apply.py:49\n#: apps/models_provider/views/model_apply.py:50\n#: apps/models_provider/views/model_apply.py:51\nmsgid \"Reorder documents\"\nmsgstr \"\"\n\n#: apps/models_provider/views/provide.py:21\n#: apps/models_provider/views/provide.py:22\n#: apps/models_provider/views/provide.py:23\nmsgid \"Get a list of model suppliers\"\nmsgstr \"\"\n\n#: apps/models_provider/views/provide.py:43\n#: apps/models_provider/views/provide.py:44\n#: apps/models_provider/views/provide.py:45\nmsgid \"Get a list of model types\"\nmsgstr \"\"\n\n#: apps/models_provider/views/provide.py:57\n#: apps/models_provider/views/provide.py:58\n#: apps/models_provider/views/provide.py:59\nmsgid \"Example of obtaining model list\"\nmsgstr \"\"\n\n#: apps/models_provider/views/provide.py:75\n#: apps/models_provider/views/provide.py:76\n#: apps/models_provider/views/provide.py:77\nmsgid \"Get model default parameters\"\nmsgstr \"\"\n\n#: apps/models_provider/views/provide.py:92\n#: apps/models_provider/views/provide.py:93\n#: apps/models_provider/views/provide.py:94\nmsgid \"Get the model creation form\"\nmsgstr \"\"\n\n#: apps/oss/serializers/file.py:80\nmsgid \"File not found\"\nmsgstr \"\"\n\n#: apps/oss/views/file.py:21 apps/oss/views/file.py:22\n#: apps/oss/views/file.py:23\nmsgid \"Upload file\"\nmsgstr \"\"\n\n#: apps/oss/views/file.py:27 apps/oss/views/file.py:41\n#: apps/oss/views/file.py:53\nmsgid \"File\"\nmsgstr \"\"\n\n#: apps/oss/views/file.py:36 apps/oss/views/file.py:37\n#: apps/oss/views/file.py:38\nmsgid \"Get file\"\nmsgstr \"\"\n\n#: apps/oss/views/file.py:48 apps/oss/views/file.py:49\n#: apps/oss/views/file.py:50\nmsgid \"Delete file\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:30\n#: apps/resource_manage/views/document.py:31\n#: apps/resource_manage/views/document.py:32\nmsgid \"Create system knowledge\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:36\n#: apps/resource_manage/views/document.py:56\n#: apps/resource_manage/views/document.py:83\n#: apps/resource_manage/views/document.py:113\n#: apps/resource_manage/views/document.py:130\n#: apps/resource_manage/views/document.py:155\n#: apps/resource_manage/views/document.py:183\n#: apps/resource_manage/views/document.py:210\n#: apps/resource_manage/views/document.py:237\n#: apps/resource_manage/views/document.py:267\n#: apps/resource_manage/views/document.py:296\n#: apps/resource_manage/views/document.py:317\n#: apps/resource_manage/views/document.py:345\n#: apps/resource_manage/views/document.py:362\n#: apps/resource_manage/views/document.py:383\n#: apps/resource_manage/views/document.py:411\n#: apps/resource_manage/views/document.py:435\n#: apps/resource_manage/views/document.py:460\n#: apps/resource_manage/views/document.py:485\n#: apps/resource_manage/views/document.py:507\n#: apps/resource_manage/views/document.py:530\n#: apps/resource_manage/views/document.py:553\n#: apps/resource_manage/views/document.py:571\n#: apps/resource_manage/views/document.py:585\nmsgid \"System Knowledge/Documentation\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:51\n#: apps/resource_manage/views/document.py:52\n#: apps/resource_manage/views/document.py:53\nmsgid \"Get system document\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:77\n#: apps/resource_manage/views/document.py:78\n#: apps/resource_manage/views/document.py:79\nmsgid \"Segmented system document\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:108\n#: apps/resource_manage/views/document.py:109\n#: apps/resource_manage/views/document.py:110\nmsgid \"Get a list of system segment IDs\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:124\n#: apps/resource_manage/views/document.py:125\n#: apps/resource_manage/views/document.py:126\nmsgid \"Cancel system tasks in batches\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:149\n#: apps/resource_manage/views/document.py:150\n#: apps/resource_manage/views/document.py:151\nmsgid \"Create system knowledges in batches\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:177\n#: apps/resource_manage/views/document.py:178\n#: apps/resource_manage/views/document.py:179\nmsgid \"Batch sync system knowledges\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:204\n#: apps/resource_manage/views/document.py:206\nmsgid \"Delete system document in batches\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:205\nmsgid \"Delete system knowledge in batches\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:232\n#: apps/resource_manage/views/document.py:233\nmsgid \"Batch refresh system document vector library\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:261\n#: apps/resource_manage/views/document.py:262\n#: apps/resource_manage/views/document.py:263\nmsgid \"Batch generate related system problems\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:290\n#: apps/resource_manage/views/document.py:291\n#: apps/resource_manage/views/document.py:292\nmsgid \"Modify system document hit processing methods in batches\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:312\n#: apps/resource_manage/views/document.py:313\nmsgid \"Migrate system knowledges in batches\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:340\n#: apps/resource_manage/views/document.py:341\n#: apps/resource_manage/views/document.py:342\nmsgid \"Get system document details\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:356\n#: apps/resource_manage/views/document.py:357\n#: apps/resource_manage/views/document.py:358\nmsgid \"Modify system document\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:378\n#: apps/resource_manage/views/document.py:379\n#: apps/resource_manage/views/document.py:380\nmsgid \"Delete system document\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:405\n#: apps/resource_manage/views/document.py:406\n#: apps/resource_manage/views/document.py:407\nmsgid \"Synchronize system web site types\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:429\n#: apps/resource_manage/views/document.py:430\n#: apps/resource_manage/views/document.py:431\nmsgid \"Refresh system knowledge vector library\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:454\n#: apps/resource_manage/views/document.py:455\n#: apps/resource_manage/views/document.py:456\nmsgid \"Cancel system task\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:480\n#: apps/resource_manage/views/document.py:481\n#: apps/resource_manage/views/document.py:482\nmsgid \"Get system document by pagination\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:503\n#: apps/resource_manage/views/document.py:504\nmsgid \"Export system knowledge\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:526\n#: apps/resource_manage/views/document.py:527\nmsgid \"Export Zip system knowledge\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:549\n#: apps/resource_manage/views/document.py:550\nmsgid \"Download system source file\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:567\n#: apps/resource_manage/views/document.py:568\nmsgid \"Get system QA template\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/document.py:581\n#: apps/resource_manage/views/document.py:582\nmsgid \"Get system form template\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:26\n#: apps/resource_manage/views/knowledge.py:27\n#: apps/resource_manage/views/knowledge.py:28\nmsgid \"Get system knowledge list\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:31\n#: apps/resource_manage/views/knowledge.py:50\n#: apps/resource_manage/views/knowledge.py:72\n#: apps/resource_manage/views/knowledge.py:87\n#: apps/resource_manage/views/knowledge.py:102\n#: apps/resource_manage/views/knowledge.py:121\n#: apps/resource_manage/views/knowledge.py:147\n#: apps/resource_manage/views/knowledge.py:174\n#: apps/resource_manage/views/knowledge.py:192\n#: apps/resource_manage/views/knowledge.py:210\n#: apps/resource_manage/views/knowledge.py:231\n#: apps/resource_manage/views/knowledge.py:252\n#: apps/resource_manage/views/knowledge.py:272\nmsgid \"System Knowledge\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:45\n#: apps/resource_manage/views/knowledge.py:46\n#: apps/resource_manage/views/knowledge.py:47\nmsgid \"Get system knowledge list by pagination\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:66\n#: apps/resource_manage/views/knowledge.py:67\n#: apps/resource_manage/views/knowledge.py:68\nmsgid \"Update system knowledge\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:82\n#: apps/resource_manage/views/knowledge.py:83\n#: apps/resource_manage/views/knowledge.py:84\nmsgid \"Get system knowledge\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:97\n#: apps/resource_manage/views/knowledge.py:98\n#: apps/resource_manage/views/knowledge.py:99\nmsgid \"Delete system knowledge\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:115\n#: apps/resource_manage/views/knowledge.py:116\n#: apps/resource_manage/views/knowledge.py:117\nmsgid \"Synchronize the system knowledge base of the website\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:141\n#: apps/resource_manage/views/knowledge.py:142\n#: apps/resource_manage/views/knowledge.py:143\nmsgid \"System Hit test list\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:168\n#: apps/resource_manage/views/knowledge.py:169\n#: apps/resource_manage/views/knowledge.py:170\nmsgid \"System Re-vectorize\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:188\n#: apps/resource_manage/views/knowledge.py:189\nmsgid \"Export system knowledge base\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:206\n#: apps/resource_manage/views/knowledge.py:207\nmsgid \"Export system knowledge base containing images\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:225\n#: apps/resource_manage/views/knowledge.py:226\n#: apps/resource_manage/views/knowledge.py:227\nmsgid \"System generate related\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:247\n#: apps/resource_manage/views/knowledge.py:248\n#: apps/resource_manage/views/knowledge.py:249\nmsgid \"Get model for system knowledge base\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/knowledge.py:267\n#: apps/resource_manage/views/knowledge.py:268\n#: apps/resource_manage/views/knowledge.py:269\nmsgid \"Get embedding model for system knowledge base\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:24\n#: apps/resource_manage/views/paragraph.py:25\n#: apps/resource_manage/views/paragraph.py:26\nmsgid \"System paragraph list\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:29\n#: apps/resource_manage/views/paragraph.py:50\n#: apps/resource_manage/views/paragraph.py:76\n#: apps/resource_manage/views/paragraph.py:93\n#: apps/resource_manage/views/paragraph.py:125\n#: apps/resource_manage/views/paragraph.py:151\n#: apps/resource_manage/views/paragraph.py:179\n#: apps/resource_manage/views/paragraph.py:201\n#: apps/resource_manage/views/paragraph.py:233\n#: apps/resource_manage/views/paragraph.py:259\n#: apps/resource_manage/views/paragraph.py:283\n#: apps/resource_manage/views/paragraph.py:314\n#: apps/resource_manage/views/paragraph.py:344\n#: apps/resource_manage/views/paragraph.py:370\nmsgid \"System Knowledge/Documentation/Paragraph\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:45\n#: apps/resource_manage/views/paragraph.py:46\nmsgid \"Create system paragraph\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:70\n#: apps/resource_manage/views/paragraph.py:71\n#: apps/resource_manage/views/paragraph.py:72\nmsgid \"Batch system paragraph\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:88\n#: apps/resource_manage/views/paragraph.py:89\nmsgid \"Migrate system paragraphs in batches\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:119\n#: apps/resource_manage/views/paragraph.py:120\n#: apps/resource_manage/views/paragraph.py:121\nmsgid \"Batch generate system related\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:145\n#: apps/resource_manage/views/paragraph.py:146\n#: apps/resource_manage/views/paragraph.py:147\nmsgid \"Modify system paragraph data\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:174\n#: apps/resource_manage/views/paragraph.py:175\n#: apps/resource_manage/views/paragraph.py:176\nmsgid \"Get system paragraph details\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:196\n#: apps/resource_manage/views/paragraph.py:197\n#: apps/resource_manage/views/paragraph.py:198\nmsgid \"Delete system paragraph\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:227\n#: apps/resource_manage/views/paragraph.py:228\n#: apps/resource_manage/views/paragraph.py:229\nmsgid \"Add system associated questions\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:254\n#: apps/resource_manage/views/paragraph.py:255\n#: apps/resource_manage/views/paragraph.py:256\nmsgid \"Get a list of system paragraph questions\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:277\n#: apps/resource_manage/views/paragraph.py:278\n#: apps/resource_manage/views/paragraph.py:279\nmsgid \"Disassociation system issue\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:308\n#: apps/resource_manage/views/paragraph.py:309\n#: apps/resource_manage/views/paragraph.py:310\nmsgid \"Related system questions\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/paragraph.py:339\n#: apps/resource_manage/views/paragraph.py:340\n#: apps/resource_manage/views/paragraph.py:341\nmsgid \"Get system paragraph list by pagination\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/problem.py:23\n#: apps/resource_manage/views/problem.py:24\n#: apps/resource_manage/views/problem.py:25\nmsgid \"System question list\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/problem.py:28\n#: apps/resource_manage/views/problem.py:50\n#: apps/resource_manage/views/problem.py:71\n#: apps/resource_manage/views/problem.py:94\n#: apps/resource_manage/views/problem.py:115\n#: apps/resource_manage/views/problem.py:135\n#: apps/resource_manage/views/problem.py:158\n#: apps/resource_manage/views/problem.py:182\nmsgid \"System Knowledge/Documentation/Paragraph/Question\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/problem.py:44\n#: apps/resource_manage/views/problem.py:45\n#: apps/resource_manage/views/problem.py:46\nmsgid \"Create system question\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/problem.py:66\n#: apps/resource_manage/views/problem.py:67\n#: apps/resource_manage/views/problem.py:68\nmsgid \"Get a list of associated system paragraphs\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/problem.py:88\n#: apps/resource_manage/views/problem.py:89\n#: apps/resource_manage/views/problem.py:90\nmsgid \"Batch associated system paragraphs\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/problem.py:109\n#: apps/resource_manage/views/problem.py:110\n#: apps/resource_manage/views/problem.py:111\nmsgid \"Batch deletion system issues\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/problem.py:130\n#: apps/resource_manage/views/problem.py:131\n#: apps/resource_manage/views/problem.py:132\nmsgid \"Delete system question\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/problem.py:152\n#: apps/resource_manage/views/problem.py:153\n#: apps/resource_manage/views/problem.py:154\nmsgid \"Modify system question\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/problem.py:177\n#: apps/resource_manage/views/problem.py:178\n#: apps/resource_manage/views/problem.py:179\nmsgid \"Get the list of system questions by page\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/tool.py:24 apps/resource_manage/views/tool.py:25\n#: apps/resource_manage/views/tool.py:26\nmsgid \"Get system tool\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/tool.py:29 apps/resource_manage/views/tool.py:50\n#: apps/resource_manage/views/tool.py:65 apps/resource_manage/views/tool.py:80\n#: apps/resource_manage/views/tool.py:98 apps/resource_manage/views/tool.py:119\n#: apps/resource_manage/views/tool.py:137\n#: apps/resource_manage/views/tool.py:156\n#: apps/resource_manage/views/tool.py:179\nmsgid \"System Tool\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/tool.py:44 apps/resource_manage/views/tool.py:45\n#: apps/resource_manage/views/tool.py:46\nmsgid \"Update system tool\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/tool.py:60 apps/resource_manage/views/tool.py:61\n#: apps/resource_manage/views/tool.py:62\nmsgid \"Get system tool by id\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/tool.py:75 apps/resource_manage/views/tool.py:76\n#: apps/resource_manage/views/tool.py:77\nmsgid \"Delete system tool\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/tool.py:93 apps/resource_manage/views/tool.py:94\n#: apps/resource_manage/views/tool.py:95\nmsgid \"Get system tool list by pagination\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/tool.py:114\n#: apps/resource_manage/views/tool.py:115\n#: apps/resource_manage/views/tool.py:116\nmsgid \"Export system tool\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/tool.py:132\n#: apps/resource_manage/views/tool.py:133\n#: apps/resource_manage/views/tool.py:134\nmsgid \"Debug system tool\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/tool.py:150\n#: apps/resource_manage/views/tool.py:151\n#: apps/resource_manage/views/tool.py:152\nmsgid \"Check system code\"\nmsgstr \"\"\n\n#: apps/resource_manage/views/tool.py:173\n#: apps/resource_manage/views/tool.py:174\n#: apps/resource_manage/views/tool.py:175\nmsgid \"Edit system tool icon\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:16\n#: apps/role_setting/api/role_setting.py:22\n#: apps/role_setting/api/role_setting.py:33\n#: apps/role_setting/api/role_setting.py:143\n#: apps/role_setting/serializers/role_setting_serializers.py:193\n#: apps/workspace/api/workspace.py:81 apps/xpack/api/chat_user.py:104\nmsgid \"ID\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:17\n#: apps/role_setting/api/role_setting.py:23\n#: apps/role_setting/api/role_setting.py:34 apps/users/serializers/user.py:258\n#: apps/xpack/api/knowledge_lark.py:24 apps/xpack/serializers/chat_user.py:235\nmsgid \"Name\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:18\n#: apps/role_setting/api/role_setting.py:29\n#: apps/role_setting/serializers/role_setting_serializers.py:194\nmsgid \"Enable\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:26\nmsgid \"Permission\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:37\nmsgid \"Children\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:55\n#: apps/role_setting/serializers/role_setting_serializers.py:107\nmsgid \"Role type\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:76\nmsgid \"Internal role\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:80\nmsgid \"Custom role\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:108\n#: apps/role_setting/api/role_setting.py:128\n#: apps/role_setting/api/role_setting.py:164\n#: apps/role_setting/serializers/role_setting_serializers.py:110\n#: apps/role_setting/serializers/role_setting_serializers.py:329\n#: apps/users/api/user.py:26 apps/workspace/api/workspace.py:86\nmsgid \"Role ID\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:135\nmsgid \"User relation ID\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:145\n#: apps/role_setting/api/role_setting.py:185\n#: apps/role_setting/serializers/role_setting_serializers.py:330\n#: apps/users/api/user.py:77 apps/users/serializers/login.py:27\n#: apps/users/serializers/user.py:56 apps/users/serializers/user.py:114\n#: apps/workspace/api/workspace.py:83 apps/workspace/api/workspace.py:124\n#: apps/workspace/serializers/workspace_serializers.py:240\n#: apps/xpack/api/auth_config.py:24 apps/xpack/api/chat_user.py:105\n#: apps/xpack/api/user_group.py:75 apps/xpack/serializers/chat_user.py:62\n#: apps/xpack/serializers/chat_user.py:564\nmsgid \"Username\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:146 apps/workspace/api/workspace.py:84\n#: apps/xpack/api/chat_user.py:106\nmsgid \"Nickname\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:148\nmsgid \"Workspace Name\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:104\nmsgid \"Role name\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:122\n#: apps/role_setting/serializers/role_setting_serializers.py:200\n#: apps/role_setting/serializers/role_setting_serializers.py:325\n#: apps/role_setting/serializers/role_setting_serializers.py:337\nmsgid \"Role does not exist\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:124\n#: apps/role_setting/serializers/role_setting_serializers.py:202\nmsgid \"Cannot modify built-in role\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:132\nmsgid \"Role name already exists\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:152\nmsgid \"Invalid role type\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:204\nmsgid \"Cannot delete built-in role\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:262\n#: apps/users/api/user.py:135 apps/users/serializers/user.py:471\n#: apps/workspace/serializers/workspace_serializers.py:161\n#: apps/xpack/api/user_group.py:90 apps/xpack/serializers/chat_user.py:158\n#: apps/xpack/serializers/chat_user.py:172\n#: apps/xpack/serializers/chat_user.py:502\nmsgid \"User IDs\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:267\n#: apps/users/api/user.py:30\nmsgid \"Workspace IDs\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:272\n#: apps/workspace/serializers/workspace_serializers.py:172\nmsgid \"Members\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:312\n#: apps/workspace/serializers/workspace_serializers.py:223\nmsgid \"User relation does not exist\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:316\n#: apps/workspace/serializers/workspace_serializers.py:226\nmsgid \"Cannot remove member from built-in role\"\nmsgstr \"\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:370\nmsgid \"Only update members to normal users\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:39\n#: apps/role_setting/views/role_setting.py:40\n#: apps/role_setting/views/role_setting.py:41\nmsgid \"Get role permission template\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:62\n#: apps/role_setting/views/role_setting.py:63\n#: apps/role_setting/views/role_setting.py:64\nmsgid \"Create or update role\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:80\n#: apps/role_setting/views/role_setting.py:81\n#: apps/role_setting/views/role_setting.py:82\nmsgid \"Get role list\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:98\n#: apps/role_setting/views/role_setting.py:99\n#: apps/role_setting/views/role_setting.py:100\nmsgid \"Delete role\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:120\n#: apps/role_setting/views/role_setting.py:121\n#: apps/role_setting/views/role_setting.py:122\nmsgid \"Create or update role permission\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:140\n#: apps/role_setting/views/role_setting.py:141\n#: apps/role_setting/views/role_setting.py:142\nmsgid \"Get role permission\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:161\n#: apps/role_setting/views/role_setting.py:162\n#: apps/role_setting/views/role_setting.py:163\nmsgid \"Add member to system role\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:186\n#: apps/role_setting/views/role_setting.py:187\n#: apps/role_setting/views/role_setting.py:188\nmsgid \"Remove member from system role\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:205\n#: apps/role_setting/views/role_setting.py:206\n#: apps/role_setting/views/role_setting.py:207\nmsgid \"Get system role member list\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:223\n#: apps/role_setting/views/role_setting.py:224\n#: apps/role_setting/views/role_setting.py:225\nmsgid \"Get Workspace role list\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:227\n#: apps/role_setting/views/role_setting.py:248\n#: apps/role_setting/views/role_setting.py:273\n#: apps/role_setting/views/role_setting.py:292\nmsgid \"Workspace Role\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:242\n#: apps/role_setting/views/role_setting.py:243\n#: apps/role_setting/views/role_setting.py:244\nmsgid \"Add member to workspace role\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:268\n#: apps/role_setting/views/role_setting.py:269\n#: apps/role_setting/views/role_setting.py:270\nmsgid \"Remove member from workspace role\"\nmsgstr \"\"\n\n#: apps/role_setting/views/role_setting.py:287\n#: apps/role_setting/views/role_setting.py:288\n#: apps/role_setting/views/role_setting.py:289\nmsgid \"Get workspace role member list\"\nmsgstr \"\"\n\n#: apps/shared/api/shared_knowledge.py:242 apps/xpack/api/knowledge_lark.py:54\nmsgid \"Folder token\"\nmsgstr \"\"\n\n#: apps/shared/api/shared_tool.py:19 apps/shared/api/shared_tool.py:46\n#: apps/shared/api/shared_tool.py:93 apps/shared/api/shared_tool.py:133\n#: apps/shared/serializers/shared_tool.py:43\n#: apps/shared/serializers/shared_tool.py:83 apps/tools/serializers/tool.py:142\n#: apps/tools/serializers/tool.py:158 apps/tools/serializers/tool.py:454\nmsgid \"tool name\"\nmsgstr \"\"\n\n#: apps/shared/api/shared_tool.py:26 apps/shared/api/shared_tool.py:53\n#: apps/shared/api/shared_tool.py:100 apps/shared/api/shared_tool.py:140\n#: apps/shared/serializers/shared_tool.py:44\n#: apps/shared/serializers/shared_tool.py:85 apps/tools/serializers/tool.py:144\n#: apps/tools/serializers/tool.py:159\nmsgid \"tool description\"\nmsgstr \"\"\n\n#: apps/shared/api/shared_tool.py:166 apps/shared/api/shared_tool.py:184\n#: apps/shared/api/shared_tool.py:256 apps/shared/api/shared_tool.py:288\n#: apps/shared/api/shared_tool.py:310 apps/shared/serializers/shared_tool.py:66\n#: apps/shared/serializers/shared_tool.py:107\n#: apps/tools/serializers/tool.py:267\nmsgid \"tool id\"\nmsgstr \"\"\n\n#: apps/shared/serializers/shared_knowledge.py:35\n#: apps/shared/serializers/shared_model.py:20\n#: apps/shared/serializers/shared_tool.py:19\nmsgid \"workspace id list\"\nmsgstr \"\"\n\n#: apps/shared/serializers/shared_knowledge.py:36\n#: apps/shared/serializers/shared_model.py:21\n#: apps/shared/serializers/shared_tool.py:20\nmsgid \"authentication type\"\nmsgstr \"\"\n\n#: apps/shared/serializers/shared_knowledge.py:196\n#: apps/shared/serializers/shared_knowledge.py:216\n#: apps/shared/serializers/shared_knowledge.py:236\nmsgid \"Knowledge does not exist\"\nmsgstr \"\"\n\n#: apps/shared/serializers/shared_knowledge.py:218\n#: apps/shared/serializers/shared_knowledge.py:238\nmsgid \"Only shared knowledge can be authorized\"\nmsgstr \"\"\n\n#: apps/shared/serializers/shared_tool.py:74\nmsgid \"Only shared tools can be deleted\"\nmsgstr \"\"\n\n#: apps/shared/serializers/shared_tool.py:76\nmsgid \"System tools cannot be deleted\"\nmsgstr \"\"\n\n#: apps/shared/serializers/shared_tool.py:118\n#: apps/shared/serializers/shared_tool.py:138\nmsgid \"Tool does not exist\"\nmsgstr \"\"\n\n#: apps/shared/serializers/shared_tool.py:120\n#: apps/shared/serializers/shared_tool.py:140\nmsgid \"Only shared tools can be authorized\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:25\n#: apps/shared/views/shared_dataset_lark_views.py:26\n#: apps/shared/views/shared_dataset_lark_views.py:27\n#: apps/xpack/views/dataset_lark_views.py:23\n#: apps/xpack/views/dataset_lark_views.py:24\n#: apps/xpack/views/dataset_lark_views.py:25\nmsgid \"Create a lark knowledge base\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:44\n#: apps/shared/views/shared_dataset_lark_views.py:45\n#: apps/shared/views/shared_dataset_lark_views.py:46\n#: apps/xpack/views/dataset_lark_views.py:44\n#: apps/xpack/views/dataset_lark_views.py:45\n#: apps/xpack/views/dataset_lark_views.py:46\nmsgid \"Update a lark knowledge base\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:67\n#: apps/shared/views/shared_dataset_lark_views.py:68\n#: apps/shared/views/shared_dataset_lark_views.py:69\n#: apps/xpack/views/dataset_lark_views.py:67\n#: apps/xpack/views/dataset_lark_views.py:68\n#: apps/xpack/views/dataset_lark_views.py:69\nmsgid \"Get document list from lark\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:72\n#: apps/shared/views/shared_dataset_lark_views.py:90\n#: apps/shared/views/shared_dataset_lark_views.py:109\n#: apps/shared/views/shared_dataset_lark_views.py:129\n#: apps/shared/views/shared_document.py:36\n#: apps/shared/views/shared_document.py:56\n#: apps/shared/views/shared_document.py:83\n#: apps/shared/views/shared_document.py:114\n#: apps/shared/views/shared_document.py:131\n#: apps/shared/views/shared_document.py:156\n#: apps/shared/views/shared_document.py:184\n#: apps/shared/views/shared_document.py:211\n#: apps/shared/views/shared_document.py:238\n#: apps/shared/views/shared_document.py:268\n#: apps/shared/views/shared_document.py:297\n#: apps/shared/views/shared_document.py:318\n#: apps/shared/views/shared_document.py:346\n#: apps/shared/views/shared_document.py:363\n#: apps/shared/views/shared_document.py:384\n#: apps/shared/views/shared_document.py:412\n#: apps/shared/views/shared_document.py:436\n#: apps/shared/views/shared_document.py:461\n#: apps/shared/views/shared_document.py:486\n#: apps/shared/views/shared_document.py:508\n#: apps/shared/views/shared_document.py:531\n#: apps/shared/views/shared_document.py:554\n#: apps/shared/views/shared_document.py:575\n#: apps/shared/views/shared_document.py:602\n#: apps/shared/views/shared_document.py:629\n#: apps/shared/views/shared_document.py:651\n#: apps/shared/views/shared_document.py:665\nmsgid \"Shared Knowledge/Documentation\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:85\n#: apps/shared/views/shared_dataset_lark_views.py:86\n#: apps/shared/views/shared_dataset_lark_views.py:87\n#: apps/xpack/views/dataset_lark_views.py:86\n#: apps/xpack/views/dataset_lark_views.py:87\n#: apps/xpack/views/dataset_lark_views.py:88\nmsgid \"Import documents to the lark knowledge base\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:104\n#: apps/shared/views/shared_dataset_lark_views.py:105\n#: apps/shared/views/shared_dataset_lark_views.py:106\n#: apps/xpack/views/dataset_lark_views.py:106\n#: apps/xpack/views/dataset_lark_views.py:107\n#: apps/xpack/views/dataset_lark_views.py:108\nmsgid \"Synchronize lark document\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:123\n#: apps/shared/views/shared_dataset_lark_views.py:124\n#: apps/shared/views/shared_dataset_lark_views.py:125\n#: apps/xpack/views/dataset_lark_views.py:126\n#: apps/xpack/views/dataset_lark_views.py:127\n#: apps/xpack/views/dataset_lark_views.py:128\nmsgid \"Batch synchronize lark document\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:30\n#: apps/shared/views/shared_document.py:31\n#: apps/shared/views/shared_document.py:32\nmsgid \"Create shared document\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:51\n#: apps/shared/views/shared_document.py:52\n#: apps/shared/views/shared_document.py:53\nmsgid \"Get shared document\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:77\n#: apps/shared/views/shared_document.py:78\n#: apps/shared/views/shared_document.py:79\nmsgid \"Segmented shared document\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:109\n#: apps/shared/views/shared_document.py:110\n#: apps/shared/views/shared_document.py:111\nmsgid \"Get a list of shared segment IDs\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:125\n#: apps/shared/views/shared_document.py:126\n#: apps/shared/views/shared_document.py:127\nmsgid \"Cancel shared tasks in batches\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:150\n#: apps/shared/views/shared_document.py:151\n#: apps/shared/views/shared_document.py:152\nmsgid \"Create shared documents in batches\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:178\n#: apps/shared/views/shared_document.py:179\n#: apps/shared/views/shared_document.py:180\nmsgid \"Batch sync shared documents\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:205\n#: apps/shared/views/shared_document.py:206\n#: apps/shared/views/shared_document.py:207\nmsgid \"Delete shared documents in batches\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:233\n#: apps/shared/views/shared_document.py:234\nmsgid \"Batch refresh shared document vector library\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:262\n#: apps/shared/views/shared_document.py:263\n#: apps/shared/views/shared_document.py:264\nmsgid \"Batch generate related shared problems\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:291\n#: apps/shared/views/shared_document.py:292\n#: apps/shared/views/shared_document.py:293\nmsgid \"Modify shared document hit processing methods in batches\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:313\n#: apps/shared/views/shared_document.py:314\nmsgid \"Migrate shared documents in batches\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:341\n#: apps/shared/views/shared_document.py:342\n#: apps/shared/views/shared_document.py:343\nmsgid \"Get shared document details\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:357\n#: apps/shared/views/shared_document.py:358\n#: apps/shared/views/shared_document.py:359\nmsgid \"Modify shared document\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:379\n#: apps/shared/views/shared_document.py:380\n#: apps/shared/views/shared_document.py:381\nmsgid \"Delete shared document\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:406\n#: apps/shared/views/shared_document.py:407\n#: apps/shared/views/shared_document.py:408\nmsgid \"Synchronize shared web site types\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:430\n#: apps/shared/views/shared_document.py:431\n#: apps/shared/views/shared_document.py:432\nmsgid \"Refresh shared document vector library\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:455\n#: apps/shared/views/shared_document.py:456\n#: apps/shared/views/shared_document.py:457\nmsgid \"Cancel shared task\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:481\n#: apps/shared/views/shared_document.py:482\n#: apps/shared/views/shared_document.py:483\nmsgid \"Get shared document by pagination\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:504\n#: apps/shared/views/shared_document.py:505\nmsgid \"Export shared document\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:527\n#: apps/shared/views/shared_document.py:528\nmsgid \"Export Zip shared document\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:550\n#: apps/shared/views/shared_document.py:551\nmsgid \"Download shared source file\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:569\n#: apps/shared/views/shared_document.py:571\nmsgid \"Create Web site shared documents\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:596\n#: apps/shared/views/shared_document.py:597\n#: apps/shared/views/shared_document.py:598\nmsgid \"Import QA and create shared documentation\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:623\n#: apps/shared/views/shared_document.py:624\n#: apps/shared/views/shared_document.py:625\nmsgid \"Import tables and create shared documents\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:647\n#: apps/shared/views/shared_document.py:648\nmsgid \"Get shared QA template\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_document.py:661\n#: apps/shared/views/shared_document.py:662\nmsgid \"Get shared form template\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:28\n#: apps/shared/views/shared_knowledge.py:29\n#: apps/shared/views/shared_knowledge.py:30\nmsgid \"Get share resource knowledge\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:48\n#: apps/shared/views/shared_knowledge.py:49\n#: apps/shared/views/shared_knowledge.py:50\nmsgid \"Get shared knowledge list by pagination\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:70\n#: apps/shared/views/shared_knowledge.py:71\n#: apps/shared/views/shared_knowledge.py:72\nmsgid \"Update shared knowledge\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:86\n#: apps/shared/views/shared_knowledge.py:87\n#: apps/shared/views/shared_knowledge.py:88\nmsgid \"Get shared knowledge\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:101\n#: apps/shared/views/shared_knowledge.py:102\n#: apps/shared/views/shared_knowledge.py:103\nmsgid \"Delete shared knowledge\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:119\n#: apps/shared/views/shared_knowledge.py:120\n#: apps/shared/views/shared_knowledge.py:121\nmsgid \"Synchronize the shared knowledge base of the website\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:145\n#: apps/shared/views/shared_knowledge.py:146\n#: apps/shared/views/shared_knowledge.py:147\nmsgid \"Shared Hit test list\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:172\n#: apps/shared/views/shared_knowledge.py:173\n#: apps/shared/views/shared_knowledge.py:174\nmsgid \"Shared Re-vectorize\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:192\n#: apps/shared/views/shared_knowledge.py:193\nmsgid \"Export shared knowledge base\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:210\n#: apps/shared/views/shared_knowledge.py:211\nmsgid \"Export shared knowledge base containing images\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:229\n#: apps/shared/views/shared_knowledge.py:230\n#: apps/shared/views/shared_knowledge.py:231\nmsgid \"Shared generate related\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:251\n#: apps/shared/views/shared_knowledge.py:252\n#: apps/shared/views/shared_knowledge.py:253\nmsgid \"Get model for shared knowledge base\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:271\n#: apps/shared/views/shared_knowledge.py:272\n#: apps/shared/views/shared_knowledge.py:273\nmsgid \"Get embedding model for shared knowledge base\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:291\n#: apps/shared/views/shared_knowledge.py:293\nmsgid \"Authorization knowledge workspace\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:292\nmsgid \"Authorization knowledge workspace \"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:307\n#: apps/shared/views/shared_knowledge.py:308\n#: apps/shared/views/shared_knowledge.py:309\nmsgid \"Get Authorization knowledge workspace\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:326\n#: apps/shared/views/shared_knowledge.py:327\n#: apps/shared/views/shared_knowledge.py:328\nmsgid \"Create shared base knowledge\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:349\n#: apps/shared/views/shared_knowledge.py:350\n#: apps/shared/views/shared_knowledge.py:351\nmsgid \"Create shared web knowledge\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:381\n#: apps/shared/views/shared_knowledge.py:382\n#: apps/shared/views/shared_knowledge.py:383\nmsgid \"Get shared workspace knowledge\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_knowledge.py:402\n#: apps/shared/views/shared_knowledge.py:403\n#: apps/shared/views/shared_knowledge.py:404\nmsgid \"Get shared workspace knowledge list by pagination\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_model.py:213 apps/shared/views/shared_model.py:215\nmsgid \"Authorization model workspace\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_model.py:214\nmsgid \"Authorization model workspace \"\nmsgstr \"\"\n\n#: apps/shared/views/shared_model.py:229 apps/shared/views/shared_model.py:230\n#: apps/shared/views/shared_model.py:231\nmsgid \"Get Authorization model workspace\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_model.py:248 apps/shared/views/shared_model.py:249\n#: apps/shared/views/shared_model.py:250\nmsgid \"Get Share model by workspace id\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_model.py:265 apps/shared/views/shared_model.py:266\n#: apps/shared/views/shared_model.py:267\nmsgid \"Get Share model by workspace id with pagination\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:24\n#: apps/shared/views/shared_paragraph.py:25\n#: apps/shared/views/shared_paragraph.py:26\nmsgid \"Shared paragraph list\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:29\n#: apps/shared/views/shared_paragraph.py:50\n#: apps/shared/views/shared_paragraph.py:76\n#: apps/shared/views/shared_paragraph.py:93\n#: apps/shared/views/shared_paragraph.py:125\n#: apps/shared/views/shared_paragraph.py:151\n#: apps/shared/views/shared_paragraph.py:179\n#: apps/shared/views/shared_paragraph.py:201\n#: apps/shared/views/shared_paragraph.py:233\n#: apps/shared/views/shared_paragraph.py:259\n#: apps/shared/views/shared_paragraph.py:283\n#: apps/shared/views/shared_paragraph.py:314\n#: apps/shared/views/shared_paragraph.py:345\n#: apps/shared/views/shared_paragraph.py:371\nmsgid \"Shared Knowledge/Documentation/Paragraph\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:45\n#: apps/shared/views/shared_paragraph.py:46\nmsgid \"Create shared paragraph\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:70\n#: apps/shared/views/shared_paragraph.py:71\n#: apps/shared/views/shared_paragraph.py:72\nmsgid \"Batch shared paragraph\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:88\n#: apps/shared/views/shared_paragraph.py:89\nmsgid \"Migrate shared paragraphs in batches\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:119\n#: apps/shared/views/shared_paragraph.py:120\n#: apps/shared/views/shared_paragraph.py:121\nmsgid \"Batch generate shared related\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:145\n#: apps/shared/views/shared_paragraph.py:146\n#: apps/shared/views/shared_paragraph.py:147\nmsgid \"Modify shared paragraph data\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:174\n#: apps/shared/views/shared_paragraph.py:175\n#: apps/shared/views/shared_paragraph.py:176\nmsgid \"Get shared paragraph details\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:196\n#: apps/shared/views/shared_paragraph.py:197\n#: apps/shared/views/shared_paragraph.py:198\nmsgid \"Delete shared paragraph\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:227\n#: apps/shared/views/shared_paragraph.py:228\n#: apps/shared/views/shared_paragraph.py:229\nmsgid \"Add shared associated questions\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:254\n#: apps/shared/views/shared_paragraph.py:255\n#: apps/shared/views/shared_paragraph.py:256\nmsgid \"Get a list of shared paragraph questions\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:277\n#: apps/shared/views/shared_paragraph.py:278\n#: apps/shared/views/shared_paragraph.py:279\nmsgid \"Disassociation shared issue\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:308\n#: apps/shared/views/shared_paragraph.py:309\n#: apps/shared/views/shared_paragraph.py:310\nmsgid \"Related shared questions\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_paragraph.py:340\n#: apps/shared/views/shared_paragraph.py:341\n#: apps/shared/views/shared_paragraph.py:342\nmsgid \"Get shared paragraph list by pagination\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_problem.py:23\n#: apps/shared/views/shared_problem.py:24\n#: apps/shared/views/shared_problem.py:25\nmsgid \"Shared question list\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_problem.py:28\n#: apps/shared/views/shared_problem.py:50\n#: apps/shared/views/shared_problem.py:71\n#: apps/shared/views/shared_problem.py:94\n#: apps/shared/views/shared_problem.py:115\n#: apps/shared/views/shared_problem.py:135\n#: apps/shared/views/shared_problem.py:158\n#: apps/shared/views/shared_problem.py:182\nmsgid \"Shared Knowledge/Documentation/Paragraph/Question\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_problem.py:44\n#: apps/shared/views/shared_problem.py:45\n#: apps/shared/views/shared_problem.py:46\nmsgid \"Create shared question\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_problem.py:66\n#: apps/shared/views/shared_problem.py:67\n#: apps/shared/views/shared_problem.py:68\nmsgid \"Get a list of associated shared paragraphs\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_problem.py:88\n#: apps/shared/views/shared_problem.py:89\n#: apps/shared/views/shared_problem.py:90\nmsgid \"Batch associated shared paragraphs\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_problem.py:109\n#: apps/shared/views/shared_problem.py:110\n#: apps/shared/views/shared_problem.py:111\nmsgid \"Batch deletion shared issues\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_problem.py:130\n#: apps/shared/views/shared_problem.py:131\n#: apps/shared/views/shared_problem.py:132\nmsgid \"Delete shared question\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_problem.py:152\n#: apps/shared/views/shared_problem.py:153\n#: apps/shared/views/shared_problem.py:154\nmsgid \"Modify shared question\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_problem.py:177\n#: apps/shared/views/shared_problem.py:178\n#: apps/shared/views/shared_problem.py:179\nmsgid \"Get the list of shared questions by page\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:25 apps/shared/views/shared_tool.py:26\n#: apps/shared/views/shared_tool.py:27\nmsgid \"Get share resource tool\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:43 apps/shared/views/shared_tool.py:44\n#: apps/shared/views/shared_tool.py:45\nmsgid \"Create shared tool\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:62 apps/shared/views/shared_tool.py:63\n#: apps/shared/views/shared_tool.py:64\nmsgid \"Update shared tool\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:78 apps/shared/views/shared_tool.py:79\n#: apps/shared/views/shared_tool.py:80\nmsgid \"Get shared tool\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:93 apps/shared/views/shared_tool.py:94\n#: apps/shared/views/shared_tool.py:95\nmsgid \"Delete shared tool\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:111 apps/shared/views/shared_tool.py:112\n#: apps/shared/views/shared_tool.py:113\nmsgid \"Get shared tool list by pagination\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:134 apps/shared/views/shared_tool.py:135\n#: apps/shared/views/shared_tool.py:136\nmsgid \"Import shared tool\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:152 apps/shared/views/shared_tool.py:153\n#: apps/shared/views/shared_tool.py:154\nmsgid \"Export shared tool\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:170 apps/shared/views/shared_tool.py:171\n#: apps/shared/views/shared_tool.py:172\nmsgid \"Debug shared Tool\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:188 apps/shared/views/shared_tool.py:189\n#: apps/shared/views/shared_tool.py:190\nmsgid \"Check shared code\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:212 apps/shared/views/shared_tool.py:213\n#: apps/shared/views/shared_tool.py:214\nmsgid \"Edit shared tool icon\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:233 apps/shared/views/shared_tool.py:235\nmsgid \"Authorization tool workspace\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:234\nmsgid \"Authorization tool workspace \"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:249 apps/shared/views/shared_tool.py:250\n#: apps/shared/views/shared_tool.py:251\nmsgid \"Get Authorization tool workspace\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:268 apps/shared/views/shared_tool.py:269\n#: apps/shared/views/shared_tool.py:270\nmsgid \"Get shared workspace tool\"\nmsgstr \"\"\n\n#: apps/shared/views/shared_tool.py:289 apps/shared/views/shared_tool.py:290\n#: apps/shared/views/shared_tool.py:291\nmsgid \"Get shared workspace tool list by pagination\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:28\nmsgid \"SMTP host\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:29\nmsgid \"SMTP port\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:30\n#: apps/system_manage/serializers/email_setting.py:34\nmsgid \"Sender's email\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:31 apps/users/api/user.py:93\n#: apps/users/serializers/login.py:28 apps/users/serializers/user.py:57\n#: apps/users/serializers/user.py:126 apps/users/serializers/user.py:291\n#: apps/users/serializers/user.py:517 apps/xpack/api/auth_config.py:25\n#: apps/xpack/api/chat_user.py:71 apps/xpack/serializers/chat_auth.py:24\n#: apps/xpack/serializers/chat_user.py:74\n#: apps/xpack/serializers/chat_user.py:267\n#: apps/xpack/serializers/chat_user.py:601\nmsgid \"Password\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:32\nmsgid \"Whether to enable TLS\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:33\nmsgid \"Whether to enable SSL\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:49\nmsgid \"Email verification failed\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/user_resource_permission.py:53\nmsgid \"target id\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/user_resource_permission.py:70\nmsgid \"Non-existent application|knowledge base id[\"\nmsgstr \"\"\n\n#: apps/system_manage/views/email_setting.py:50\n#: apps/system_manage/views/email_setting.py:51\n#: apps/system_manage/views/email_setting.py:52\nmsgid \"Create or update email settings\"\nmsgstr \"\"\n\n#: apps/system_manage/views/email_setting.py:55\n#: apps/system_manage/views/email_setting.py:70\n#: apps/system_manage/views/email_setting.py:86\nmsgid \"Email Settings\"\nmsgstr \"\"\n\n#: apps/system_manage/views/email_setting.py:66\n#: apps/system_manage/views/email_setting.py:67\nmsgid \"Test email settings\"\nmsgstr \"\"\n\n#: apps/system_manage/views/email_setting.py:82\n#: apps/system_manage/views/email_setting.py:83\n#: apps/system_manage/views/email_setting.py:84\nmsgid \"Get email settings\"\nmsgstr \"\"\n\n#: apps/system_manage/views/system_profile.py:22\n#: apps/system_manage/views/system_profile.py:23\nmsgid \"Get MaxKB related information\"\nmsgstr \"\"\n\n#: apps/system_manage/views/system_profile.py:25\nmsgid \"System parameters\"\nmsgstr \"\"\n\n#: apps/system_manage/views/user_resource_permission.py:40\n#: apps/system_manage/views/user_resource_permission.py:41\nmsgid \"Obtain resource authorization list\"\nmsgstr \"\"\n\n#: apps/system_manage/views/user_resource_permission.py:44\n#: apps/system_manage/views/user_resource_permission.py:60\nmsgid \"Resources authorization\"\nmsgstr \"\"\n\n#: apps/system_manage/views/user_resource_permission.py:55\n#: apps/system_manage/views/user_resource_permission.py:56\nmsgid \"Modify the resource authorization list\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:118 apps/tools/serializers/tool.py:169\nmsgid \"variable name\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:122\nmsgid \"fields only support string|int|dict|array|float\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:131\nmsgid \"field name\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:132\nmsgid \"field label\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:146 apps/tools/serializers/tool.py:160\n#: apps/tools/serializers/tool.py:174\nmsgid \"tool content\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:148 apps/tools/serializers/tool.py:161\n#: apps/tools/serializers/tool.py:175\nmsgid \"input field list\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:150 apps/tools/serializers/tool.py:162\n#: apps/tools/serializers/tool.py:176\nmsgid \"init field list\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:163 apps/tools/serializers/tool.py:177\nmsgid \"init params\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:170\nmsgid \"variable value\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:182\nmsgid \"function content\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:238\nmsgid \"field has no value set\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:262\n#, python-brace-format\nmsgid \"Field: {name} Type: {_type} Value: {value} Type conversion error\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:275\nmsgid \"Tool not found\"\nmsgstr \"\"\n\n#: apps/tools/serializers/tool.py:388\nmsgid \"function ID\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:33 apps/tools/views/tool.py:34\n#: apps/tools/views/tool.py:35\nmsgid \"Create tool\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:56 apps/tools/views/tool.py:57\n#: apps/tools/views/tool.py:58\nmsgid \"Get tool by folder\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:77 apps/tools/views/tool.py:78\n#: apps/tools/views/tool.py:79\nmsgid \"Debug Tool\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:98 apps/tools/views/tool.py:99\n#: apps/tools/views/tool.py:100\nmsgid \"Update tool\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:122 apps/tools/views/tool.py:123\n#: apps/tools/views/tool.py:124\nmsgid \"Get tool\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:141 apps/tools/views/tool.py:142\n#: apps/tools/views/tool.py:143\nmsgid \"Delete tool\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:167 apps/tools/views/tool.py:168\n#: apps/tools/views/tool.py:169\nmsgid \"Get tool list by pagination\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:195 apps/tools/views/tool.py:196\n#: apps/tools/views/tool.py:197\nmsgid \"Import tool\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:218 apps/tools/views/tool.py:219\n#: apps/tools/views/tool.py:220\nmsgid \"Export tool\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:244 apps/tools/views/tool.py:245\n#: apps/tools/views/tool.py:246\nmsgid \"Check code\"\nmsgstr \"\"\n\n#: apps/tools/views/tool.py:268 apps/tools/views/tool.py:269\n#: apps/tools/views/tool.py:270\nmsgid \"Edit tool icon\"\nmsgstr \"\"\n\n#: apps/users/api/user.py:154\nmsgid \"Email or Username\"\nmsgstr \"\"\n\n#: apps/users/api/user.py:224\nmsgid \"Language\"\nmsgstr \"\"\n\n#: apps/users/serializers/login.py:29 apps/users/serializers/login.py:69\nmsgid \"captcha\"\nmsgstr \"\"\n\n#: apps/users/serializers/login.py:50\n#: apps/xpack/serializers/chat_user_serializer.py:120\nmsgid \"Captcha code error or expiration\"\nmsgstr \"\"\n\n#: apps/users/serializers/login.py:55\n#: apps/xpack/serializers/auth_config_serializer.py:192\n#: apps/xpack/serializers/chat_user_serializer.py:125\n#: apps/xpack/serializers/qr_login/qr_login.py:37\nmsgid \"The user has been disabled, please contact the administrator!\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:47\nmsgid \"Is Edit Password\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:48\nmsgid \"permissions\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:58 apps/users/serializers/user.py:106\n#: apps/users/serializers/user.py:250 apps/users/serializers/user.py:557\n#: apps/users/serializers/user.py:641 apps/xpack/api/chat_user.py:107\n#: apps/xpack/serializers/chat_user.py:54\n#: apps/xpack/serializers/chat_user.py:227\nmsgid \"Email\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:59 apps/users/serializers/user.py:140\n#: apps/xpack/serializers/chat_user.py:88\nmsgid \"Nick name\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:60 apps/users/serializers/user.py:145\n#: apps/users/serializers/user.py:263 apps/xpack/api/chat_user.py:108\n#: apps/xpack/serializers/chat_user.py:93\n#: apps/xpack/serializers/chat_user.py:240\nmsgid \"Phone\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:120 apps/xpack/serializers/chat_user.py:68\nmsgid \"Username must be 4-64 characters long\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:133 apps/users/serializers/user.py:298\n#: apps/xpack/serializers/chat_user.py:81\n#: apps/xpack/serializers/chat_user.py:274\nmsgid \"\"\n\"The password must be 6-20 characters long and must be a combination of \"\n\"letters, numbers, and special characters.\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:170\nmsgid \"Email or username\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:226\nmsgid \"\"\n\"The community version supports up to 2 users. If you need more users, please \"\n\"contact us (https://fit2cloud.com/).\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:270 apps/xpack/api/auth_config.py:31\n#: apps/xpack/api/chat_user.py:109 apps/xpack/serializers/chat_user.py:247\nmsgid \"Is Active\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:281 apps/xpack/serializers/chat_user.py:262\nmsgid \"Nickname is already in use\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:286\nmsgid \"Email is already in use\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:305 apps/xpack/serializers/chat_user.py:281\nmsgid \"Re Password\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:310 apps/users/serializers/user.py:522\n#: apps/users/serializers/user.py:529 apps/xpack/serializers/chat_user.py:286\n#: apps/xpack/serializers/chat_user.py:606\n#: apps/xpack/serializers/chat_user.py:613\nmsgid \"\"\n\"The confirmation password must be 6-20 characters long and must be a \"\n\"combination of letters, numbers, and special characters.\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:333 apps/xpack/serializers/chat_user.py:309\nmsgid \"User does not exist\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:348\nmsgid \"Unable to delete administrator\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:366\nmsgid \"Cannot modify administrator status\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:478 apps/xpack/serializers/chat_user.py:164\n#: apps/xpack/serializers/chat_user.py:192\n#: apps/xpack/serializers/chat_user.py:512\nmsgid \"User IDs cannot be empty\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:524 apps/xpack/serializers/chat_user.py:608\nmsgid \"Confirm Password\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:561 apps/users/serializers/user.py:647\n#: apps/xpack/api/knowledge_lark.py:26\nmsgid \"Type\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:563 apps/users/serializers/user.py:651\nmsgid \"The type only supports register|reset_password\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:581\n#, python-brace-format\nmsgid \"Do not send emails again within {seconds} seconds\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:611\nmsgid \"\"\n\"The email service has not been set up. Please contact the administrator to \"\n\"set up the email service in [Email Settings].\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:622\n#, python-brace-format\nmsgid \"【Intelligent knowledge base question and answer system-{action}】\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:623\nmsgid \"User registration\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:623 apps/users/views/user.py:248\n#: apps/users/views/user.py:249 apps/users/views/user.py:250\n#: apps/users/views/user.py:283 apps/users/views/user.py:284\n#: apps/users/views/user.py:285\nmsgid \"Change password\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:644\nmsgid \"Verification code\"\nmsgstr \"\"\n\n#: apps/users/serializers/user.py:672\nmsgid \"language only support:\"\nmsgstr \"\"\n\n#: apps/users/views/login.py:38 apps/users/views/login.py:39\n#: apps/users/views/login.py:40 apps/xpack/views/chat_user_auth.py:184\n#: apps/xpack/views/chat_user_auth.py:185\n#: apps/xpack/views/chat_user_auth.py:186\nmsgid \"Log in\"\nmsgstr \"\"\n\n#: apps/users/views/login.py:55 apps/users/views/login.py:56\n#: apps/users/views/login.py:57 apps/xpack/views/chat_user_auth.py:203\n#: apps/xpack/views/chat_user_auth.py:204\n#: apps/xpack/views/chat_user_auth.py:205\nmsgid \"Sign out\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:60 apps/users/views/user.py:61\n#: apps/users/views/user.py:62 apps/users/views/user.py:74\n#: apps/users/views/user.py:75 apps/xpack/views/system_chat_user.py:359\n#: apps/xpack/views/system_chat_user.py:360\n#: apps/xpack/views/system_chat_user.py:361\nmsgid \"Get current user information\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:120 apps/users/views/user.py:121\n#: apps/users/views/user.py:122\nmsgid \"Get all user\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:133 apps/users/views/user.py:134\n#: apps/users/views/user.py:135\nmsgid \"Get user list under workspace\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:147 apps/users/views/user.py:148\n#: apps/users/views/user.py:149\nmsgid \"Get user member under workspace\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:161 apps/users/views/user.py:162\n#: apps/users/views/user.py:163\nmsgid \"Create user\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:177 apps/users/views/user.py:178\n#: apps/users/views/user.py:179\nmsgid \"Get default password\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:190 apps/users/views/user.py:191\n#: apps/users/views/user.py:192\nmsgid \"Delete user\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:203 apps/users/views/user.py:204\n#: apps/users/views/user.py:205\nmsgid \"Get user information\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:214 apps/users/views/user.py:215\n#: apps/users/views/user.py:216\nmsgid \"Update user information\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:232 apps/users/views/user.py:233\n#: apps/users/views/user.py:234\nmsgid \"Batch delete user\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:266 apps/users/views/user.py:267\n#: apps/users/views/user.py:268 apps/workspace/views/workspace_chat_user.py:168\n#: apps/workspace/views/workspace_chat_user.py:169\n#: apps/workspace/views/workspace_chat_user.py:170\n#: apps/xpack/views/system_chat_user.py:197\n#: apps/xpack/views/system_chat_user.py:198\n#: apps/xpack/views/system_chat_user.py:199\nmsgid \"Get user paginated list\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:300 apps/users/views/user.py:301\n#: apps/users/views/user.py:302\nmsgid \"Send email\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:318 apps/users/views/user.py:319\n#: apps/users/views/user.py:320\nmsgid \"Check whether the verification code is correct\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:335 apps/users/views/user.py:336\n#: apps/users/views/user.py:337\nmsgid \"Send email to current user\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:353 apps/users/views/user.py:354\n#: apps/users/views/user.py:355 apps/xpack/views/system_chat_user.py:336\n#: apps/xpack/views/system_chat_user.py:337\n#: apps/xpack/views/system_chat_user.py:338\nmsgid \"Modify current user password\"\nmsgstr \"\"\n\n#: apps/users/views/user.py:368 apps/xpack/views/system_chat_user.py:352\nmsgid \"Failed to change password\"\nmsgstr \"\"\n\n#: apps/workspace/api/workspace.py:73\n#: apps/workspace/serializers/workspace_serializers.py:213\nmsgid \"User Relation ID\"\nmsgstr \"\"\n\n#: apps/workspace/api/workspace.py:87\nmsgid \"Role Name\"\nmsgstr \"\"\n\n#: apps/workspace/serializers/workspace_serializers.py:42\n#: apps/workspace/serializers/workspace_serializers.py:95\n#: apps/workspace/serializers/workspace_serializers.py:110\n#: apps/workspace/serializers/workspace_serializers.py:177\n#: apps/workspace/serializers/workspace_serializers.py:219\n#: apps/workspace/serializers/workspace_serializers.py:246\nmsgid \"Workspace does not exist\"\nmsgstr \"\"\n\n#: apps/workspace/serializers/workspace_serializers.py:49\nmsgid \"Workspace name already exists\"\nmsgstr \"\"\n\n#: apps/workspace/serializers/workspace_serializers.py:97\n#: apps/workspace/serializers/workspace_serializers.py:112\nmsgid \"Default workspace cannot be deleted\"\nmsgstr \"\"\n\n#: apps/workspace/serializers/workspace_serializers.py:122\nmsgid \"Applications Resource\"\nmsgstr \"\"\n\n#: apps/workspace/serializers/workspace_serializers.py:124\nmsgid \"Knowledge Resource\"\nmsgstr \"\"\n\n#: apps/workspace/serializers/workspace_serializers.py:130\n#, python-format\nmsgid \"This workspace contains %s, cannot be deleted.\"\nmsgstr \"\"\n\n#: apps/workspace/serializers/workspace_serializers.py:166\nmsgid \"Role IDs\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:32 apps/workspace/views/workspace.py:33\n#: apps/workspace/views/workspace.py:34\nmsgid \"Create or update workspace\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:45 apps/workspace/views/workspace.py:46\n#: apps/workspace/views/workspace.py:47\nmsgid \"Get system workspace list\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:58 apps/workspace/views/workspace.py:59\n#: apps/workspace/views/workspace.py:60\nmsgid \"Delete workspace\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:75 apps/workspace/views/workspace.py:76\n#: apps/workspace/views/workspace.py:77\nmsgid \"Check workspace can it be deleted\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:95 apps/workspace/views/workspace.py:96\n#: apps/workspace/views/workspace.py:97\nmsgid \"Add member to system workspace\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:114 apps/workspace/views/workspace.py:115\n#: apps/workspace/views/workspace.py:116\nmsgid \"Remove member from system workspace\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:133 apps/workspace/views/workspace.py:134\n#: apps/workspace/views/workspace.py:135\nmsgid \"Get system workspace member list\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:151 apps/workspace/views/workspace.py:152\n#: apps/workspace/views/workspace.py:153\nmsgid \"Get workspace list\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:164 apps/workspace/views/workspace.py:165\n#: apps/workspace/views/workspace.py:166\nmsgid \"Add member to workspace\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:183 apps/workspace/views/workspace.py:184\n#: apps/workspace/views/workspace.py:185\nmsgid \"Remove member from workspace\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:202 apps/workspace/views/workspace.py:203\n#: apps/workspace/views/workspace.py:204\nmsgid \"Get workspace member list\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:219 apps/workspace/views/workspace.py:220\n#: apps/workspace/views/workspace.py:221\nmsgid \"Get workspace list by current user\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:232 apps/workspace/views/workspace.py:233\n#: apps/workspace/views/workspace.py:234\nmsgid \"Get workspace list by user\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace.py:246 apps/workspace/views/workspace.py:247\n#: apps/workspace/views/workspace.py:248\nmsgid \"Get current user role list\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:44\n#: apps/workspace/views/workspace_chat_user.py:45\n#: apps/workspace/views/workspace_chat_user.py:46\n#: apps/workspace/views/workspace_chat_user.py:51\n#: apps/xpack/views/system_chat_user.py:57\n#: apps/xpack/views/system_chat_user.py:58\n#: apps/xpack/views/system_chat_user.py:59\nmsgid \"Create chat user\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:47\n#: apps/workspace/views/workspace_chat_user.py:51\n#: apps/workspace/views/workspace_chat_user.py:63\n#: apps/workspace/views/workspace_chat_user.py:67\n#: apps/workspace/views/workspace_chat_user.py:76\n#: apps/workspace/views/workspace_chat_user.py:87\n#: apps/workspace/views/workspace_chat_user.py:92\n#: apps/workspace/views/workspace_chat_user.py:105\n#: apps/workspace/views/workspace_chat_user.py:120\n#: apps/workspace/views/workspace_chat_user.py:124\n#: apps/workspace/views/workspace_chat_user.py:136\n#: apps/workspace/views/workspace_chat_user.py:140\n#: apps/workspace/views/workspace_chat_user.py:152\n#: apps/workspace/views/workspace_chat_user.py:171\nmsgid \"Workspace/Chat user\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:60\n#: apps/workspace/views/workspace_chat_user.py:61\n#: apps/workspace/views/workspace_chat_user.py:62\n#: apps/workspace/views/workspace_chat_user.py:67\n#: apps/xpack/views/system_chat_user.py:86\n#: apps/xpack/views/system_chat_user.py:87\n#: apps/xpack/views/system_chat_user.py:88\nmsgid \"Delete chat user\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:73\n#: apps/workspace/views/workspace_chat_user.py:74\n#: apps/workspace/views/workspace_chat_user.py:75\n#: apps/xpack/views/system_chat_user.py:99\n#: apps/xpack/views/system_chat_user.py:100\n#: apps/xpack/views/system_chat_user.py:101\nmsgid \"Get chat user information\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:84\n#: apps/workspace/views/workspace_chat_user.py:85\n#: apps/workspace/views/workspace_chat_user.py:86\n#: apps/workspace/views/workspace_chat_user.py:92\n#: apps/xpack/views/system_chat_user.py:110\n#: apps/xpack/views/system_chat_user.py:111\n#: apps/xpack/views/system_chat_user.py:112\nmsgid \"Update chat user information\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:102\n#: apps/workspace/views/workspace_chat_user.py:103\n#: apps/workspace/views/workspace_chat_user.py:104\n#: apps/workspace/views/workspace_chat_user.py:261\n#: apps/workspace/views/workspace_chat_user.py:262\n#: apps/workspace/views/workspace_chat_user.py:263\n#: apps/xpack/views/system_chat_user.py:128\n#: apps/xpack/views/system_chat_user.py:129\n#: apps/xpack/views/system_chat_user.py:130\n#: apps/xpack/views/system_chat_user.py:318\n#: apps/xpack/views/system_chat_user.py:319\n#: apps/xpack/views/system_chat_user.py:320\nmsgid \"Get user list by group\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:117\n#: apps/workspace/views/workspace_chat_user.py:118\n#: apps/workspace/views/workspace_chat_user.py:119\n#: apps/workspace/views/workspace_chat_user.py:124\n#: apps/xpack/views/system_chat_user.py:143\n#: apps/xpack/views/system_chat_user.py:144\n#: apps/xpack/views/system_chat_user.py:145\nmsgid \"Batch delete chat user\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:133\n#: apps/workspace/views/workspace_chat_user.py:134\n#: apps/workspace/views/workspace_chat_user.py:135\n#: apps/workspace/views/workspace_chat_user.py:140\n#: apps/xpack/views/system_chat_user.py:160\n#: apps/xpack/views/system_chat_user.py:161\n#: apps/xpack/views/system_chat_user.py:162\nmsgid \"Batch add chat user to group\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:149\n#: apps/workspace/views/workspace_chat_user.py:150\n#: apps/workspace/views/workspace_chat_user.py:151\n#: apps/xpack/views/system_chat_user.py:177\n#: apps/xpack/views/system_chat_user.py:178\n#: apps/xpack/views/system_chat_user.py:179\nmsgid \"Change chat user password\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:186\n#: apps/workspace/views/workspace_chat_user.py:187\n#: apps/workspace/views/workspace_chat_user.py:188\n#: apps/xpack/views/system_chat_user.py:230\n#: apps/xpack/views/system_chat_user.py:231\n#: apps/xpack/views/system_chat_user.py:232\nmsgid \"Create or update Chat User Group\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:191\n#: apps/workspace/views/workspace_chat_user.py:202\n#: apps/workspace/views/workspace_chat_user.py:216\n#: apps/workspace/views/workspace_chat_user.py:232\n#: apps/workspace/views/workspace_chat_user.py:249\n#: apps/workspace/views/workspace_chat_user.py:264\nmsgid \"Workspace/User Group\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:198\n#: apps/workspace/views/workspace_chat_user.py:199\n#: apps/workspace/views/workspace_chat_user.py:200\n#: apps/xpack/views/system_chat_user.py:243\n#: apps/xpack/views/system_chat_user.py:244\n#: apps/xpack/views/system_chat_user.py:245\nmsgid \"Get user group list\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:211\n#: apps/workspace/views/workspace_chat_user.py:212\n#: apps/workspace/views/workspace_chat_user.py:213\n#: apps/xpack/views/system_chat_user.py:256\n#: apps/xpack/views/system_chat_user.py:257\n#: apps/xpack/views/system_chat_user.py:258\nmsgid \"Delete chat user group\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:226\n#: apps/workspace/views/workspace_chat_user.py:227\n#: apps/workspace/views/workspace_chat_user.py:228\n#: apps/xpack/views/system_chat_user.py:273\n#: apps/xpack/views/system_chat_user.py:274\n#: apps/xpack/views/system_chat_user.py:275\nmsgid \"Add member to chat user group\"\nmsgstr \"\"\n\n#: apps/workspace/views/workspace_chat_user.py:243\n#: apps/workspace/views/workspace_chat_user.py:244\n#: apps/workspace/views/workspace_chat_user.py:245\n#: apps/xpack/views/system_chat_user.py:295\n#: apps/xpack/views/system_chat_user.py:296\n#: apps/xpack/views/system_chat_user.py:297\nmsgid \"Remove member from chat user group\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:29\nmsgid \"Auth Type\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:30\nmsgid \"Config\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:77\nmsgid \"Corp ID\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:78\nmsgid \"Agent ID\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:79\nmsgid \"App Secret\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:80\nmsgid \"Callback URL\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:84\nmsgid \"Key\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:106\nmsgid \"Access Token\"\nmsgstr \"\"\n\n#: apps/xpack/api/chat_user.py:31 apps/xpack/api/chat_user.py:83\n#: apps/xpack/api/chat_user.py:113 apps/xpack/serializers/chat_user.py:101\n#: apps/xpack/serializers/chat_user.py:177\n#: apps/xpack/serializers/chat_user.py:252\nmsgid \"User Group IDs\"\nmsgstr \"\"\n\n#: apps/xpack/api/chat_user.py:118\nmsgid \"User Group Names\"\nmsgstr \"\"\n\n#: apps/xpack/api/chat_user.py:132 apps/xpack/serializers/chat_user.py:120\n#: apps/xpack/serializers/resource_chat_user.py:37\nmsgid \"Username or Nickname\"\nmsgstr \"\"\n\n#: apps/xpack/api/chat_user.py:148 apps/xpack/serializers/chat_user.py:360\nmsgid \"Sync Type\"\nmsgstr \"\"\n\n#: apps/xpack/api/knowledge_lark.py:25\nmsgid \"Token\"\nmsgstr \"\"\n\n#: apps/xpack/api/knowledge_lark.py:27\nmsgid \"Is Exist\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:13\nmsgid \"corporation\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:14\nmsgid \"isv\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:15\nmsgid \"expired\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:16\nmsgid \"product\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:17\nmsgid \"edition\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:18\nmsgid \"license version\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:19\nmsgid \"count\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:20\nmsgid \"serial number\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:21\nmsgid \"remark\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:26\nmsgid \"message\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:27\nmsgid \"license details\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:36\n#: apps/xpack/serializers/license/license_serializers.py:56\nmsgid \"license file\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:37\nmsgid \"License file is required\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:38\nmsgid \"Invalid license file format\"\nmsgstr \"\"\n\n#: apps/xpack/api/operate_log.py:12\n#: apps/xpack/serializers/operate_log_serializer.py:57\nmsgid \"menu\"\nmsgstr \"\"\n\n#: apps/xpack/api/operate_log.py:13\n#: apps/xpack/serializers/operate_log_serializer.py:58\nmsgid \"operate\"\nmsgstr \"\"\n\n#: apps/xpack/api/operate_log.py:14\nmsgid \"menu_label\"\nmsgstr \"\"\n\n#: apps/xpack/api/operate_log.py:15\nmsgid \"operate_label\"\nmsgstr \"\"\n\n#: apps/xpack/api/platform.py:35\nmsgid \"Platform type\"\nmsgstr \"\"\n\n#: apps/xpack/api/platform.py:50\nmsgid \"Platform configuration\"\nmsgstr \"\"\n\n#: apps/xpack/api/resource_chat_user_group.py:40\n#: apps/xpack/serializers/resource_chat_user.py:25\n#: apps/xpack/serializers/resource_chat_user_group.py:69\nmsgid \"is auth\"\nmsgstr \"\"\n\n#: apps/xpack/api/user_group.py:31 apps/xpack/api/user_group.py:99\nmsgid \"User Group ID\"\nmsgstr \"\"\n\n#: apps/xpack/api/user_group.py:54 apps/xpack/serializers/chat_user.py:406\n#: apps/xpack/serializers/chat_user.py:563\nmsgid \"Group ID\"\nmsgstr \"\"\n\n#: apps/xpack/api/user_group.py:114 apps/xpack/serializers/chat_user.py:541\nmsgid \"User group relation IDs\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:19\nmsgid \"theme color\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:21\nmsgid \"header font color\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:25\nmsgid \"float location type\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:26\nmsgid \"float location value\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:30\nmsgid \"float location x\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:31\nmsgid \"float location y\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:35\nmsgid \"show source\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:36\nmsgid \"show exec\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:38\nmsgid \"show history\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:39\nmsgid \"draggable\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:40\nmsgid \"show guide\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:42\nmsgid \"icon url\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:43\nmsgid \"chat background\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:44\nmsgid \"chat background url\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:45\nmsgid \"avatar\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:46\nmsgid \"avatar url\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:47\nmsgid \"user avatar\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:48\nmsgid \"user avatar url\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:49\nmsgid \"float icon\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:50\nmsgid \"float icon url\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:51\nmsgid \"disclaimer\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:52\nmsgid \"disclaimer value\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:55\nmsgid \"show avatar\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:56\nmsgid \"show user avatar\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:124\nmsgid \"Float location field type error\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:130\nmsgid \"Custom theme field type error\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:27\n#: apps/xpack/serializers/platform_serializer.py:31\nmsgid \"App Secret is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:28\n#: apps/xpack/serializers/platform_serializer.py:26\n#: apps/xpack/serializers/platform_serializer.py:34\n#: apps/xpack/serializers/platform_serializer.py:40\n#: apps/xpack/serializers/platform_serializer.py:46\nmsgid \"Callback URL is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:32\n#: apps/xpack/serializers/auth_config_serializer.py:41\nmsgid \"Corp ID is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:33\n#: apps/xpack/serializers/platform_serializer.py:22\nmsgid \"Agent ID is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:37\n#: apps/xpack/serializers/auth_config_serializer.py:42\nmsgid \"App Key is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:53\nmsgid \"LDAP server cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:54\nmsgid \"Base DN cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:55\nmsgid \"Password cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:56\nmsgid \"OU cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:57\nmsgid \"LDAP filter cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:58\nmsgid \"LDAP mapping cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:62\nmsgid \"Authorization address cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:63\nmsgid \"Token address cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:64\nmsgid \"User information address cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:65\nmsgid \"Scope cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:66\nmsgid \"Client ID cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:67\nmsgid \"Client secret cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:68\nmsgid \"Redirect address cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:69\nmsgid \"Field mapping cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:262\nmsgid \"Configuration information is wrong and failed to save\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:288\nmsgid \"Connection failed\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:306\nmsgid \"Platform does not exist\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:316\nmsgid \"Unsupported platform type\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/chat_manage.py:100\nmsgid \"Think: \"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/chat_manage.py:103\n#: apps/xpack/serializers/channel/chat_manage.py:105\nmsgid \"AI reply: \"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/chat_manage.py:318\nmsgid \"Thinking, please wait a moment!\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:19\n#: apps/xpack/serializers/channel/wechat.py:91\n#: apps/xpack/serializers/channel/wechat.py:132\n#: apps/xpack/serializers/channel/wecom.py:78\n#: apps/xpack/serializers/channel/wecom.py:259\nmsgid \"The corresponding platform configuration was not found\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:27\n#: apps/xpack/serializers/channel/lark.py:117\nmsgid \"Currently only text messages are supported\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:91\n#: apps/xpack/serializers/channel/wechat.py:163\n#: apps/xpack/serializers/channel/wecom.py:189\nmsgid \"Image download failed, check network\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:92\n#: apps/xpack/serializers/channel/wechat.py:161\n#: apps/xpack/serializers/channel/wecom.py:185\nmsgid \"Please analyze the content of the image.\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:95\n#, python-brace-format\nmsgid \"DingTalk application: {user}\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:106\n#: apps/xpack/serializers/channel/ding_talk.py:151\nmsgid \"Content generated by AI\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/lark.py:92\nmsgid \"Lark application: \"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/slack.py:116\nmsgid \"The corresponding platform configuration for Slack was not found\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/slack.py:206\nmsgid \"Thinking...\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/slack.py:333\nmsgid \"Invalid json format.\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/slack.py:339\nmsgid \"Invalid Slack request\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/slack.py:347\n#, python-brace-format\nmsgid \"Slack application: {user}\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/slack.py:480\nmsgid \"Stop\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/tools.py:58\n#, python-brace-format\nmsgid \"\"\n\"Thinking about 【{question}】...If you want me to continue answering, please \"\n\"reply {trigger_message}\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/tools.py:158\nmsgid \"\"\n\"\\n\"\n\" ------------\\n\"\n\"[To be continued, reply \\\"Continue to answer the question]\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/tools.py:238\n#, python-brace-format\nmsgid \"\"\n\"To be continued, reply \\\"{trigger_message}\\\" to continue answering the \"\n\"question\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/wechat.py:143\n#, python-brace-format\nmsgid \"WeChat Official Account: {account}\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/wechat.py:150\n#: apps/xpack/serializers/channel/wecom.py:171\n#: apps/xpack/serializers/channel/wecom.py:175\nmsgid \"\"\n\"The app does not enable the speech-to-text function or the speech-to-text \"\n\"function fails.\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/wechat.py:189\nmsgid \"Message types not supported yet\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/wechat.py:196\nmsgid \"Welcome to subscribe\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/wecom.py:86\nmsgid \"Enterprise WeChat user: \"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/wecom.py:97\nmsgid \"Enterprise WeChat customer service: \"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/wecom.py:134\n#: apps/xpack/serializers/channel/wecom.py:150\nmsgid \"This type of message is not supported yet\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/wecom.py:254\nmsgid \"Signature missing\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/channel/wecom.py:266\n#: apps/xpack/serializers/channel/wecom.py:273\n#, python-brace-format\nmsgid \"An error occurred while processing the GET request {e}\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_auth.py:51\nmsgid \"The password is incorrect\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user.py:42\nmsgid \"Some user groups do not exist\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user.py:181\nmsgid \"Is Append\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user.py:194\nmsgid \"User Group IDs cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user.py:198\nmsgid \"Some users do not exist\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user.py:361\nmsgid \"Sync Type: LOCAL or LDAP\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user.py:403\nmsgid \"Unsupported sync type\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user.py:412\n#: apps/xpack/serializers/chat_user.py:444\n#: apps/xpack/serializers/chat_user.py:483\n#: apps/xpack/serializers/chat_user.py:510\n#: apps/xpack/serializers/chat_user.py:548\n#: apps/xpack/serializers/chat_user.py:570\nmsgid \"User group does not exist\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user.py:451\nmsgid \"User group name already exists\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user.py:485\nmsgid \"Default user group cannot be deleted\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user.py:550\nmsgid \"User group relation IDs cannot be empty\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user_serializer.py:75\nmsgid \"Invalid access token\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/chat_user_serializer.py:102\nmsgid \"The user does not have permission to access the application\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:56\n#: apps/xpack/serializers/dataset_lark_serializer.py:299\nmsgid \"app id\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:57\n#: apps/xpack/serializers/dataset_lark_serializer.py:300\nmsgid \"app secret\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:58\n#: apps/xpack/serializers/dataset_lark_serializer.py:105\n#: apps/xpack/serializers/dataset_lark_serializer.py:301\nmsgid \"folder token\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:60\nmsgid \"embedding model\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:71\n#: apps/xpack/serializers/dataset_lark_serializer.py:311\nmsgid \"Network error or folder token error!\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:113\n#: apps/xpack/serializers/dataset_lark_serializer.py:155\n#: apps/xpack/task/sync.py:308\nmsgid \"Knowledge base not found!\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:125\n#: apps/xpack/task/sync.py:240\nmsgid \"Failed to get lark document list!\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:147\nmsgid \"Knowledge id\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:169\nmsgid \"Synchronization is only supported for lark documents\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/license/license_serializers.py:102\n#: apps/xpack/serializers/license/license_serializers.py:123\n#: apps/xpack/serializers/license/license_tools.py:111\nmsgid \"The license is invalid\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/license/license_tools.py:136\nmsgid \"License usage limit exceeded.\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/license/license_tools.py:160\nmsgid \"The network is busy, try again later.\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:59\nmsgid \"user\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:61\nmsgid \"ip_address\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:62\nmsgid \"workspace_id\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:134\nmsgid \"Fail\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:171\nmsgid \"Menu\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:172\nmsgid \"Operate\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:173\nmsgid \"Operate user\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:175\nmsgid \"Ip Address\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:176\nmsgid \"API Details\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:177\nmsgid \"Operate Time\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:12\nmsgid \"app_id is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:13\nmsgid \"app_secret is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:14\nmsgid \"token is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:15\nmsgid \"callback_url is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:21\n#: apps/xpack/serializers/platform_serializer.py:30\nmsgid \"App ID is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:23\nmsgid \"Secret is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:24\nmsgid \"Token is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:33\nmsgid \"Verification Token is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:38\nmsgid \"Client ID is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:39\nmsgid \"Client Secret is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:44\nmsgid \"Signing Secret is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:45\nmsgid \"Bot User Token is required\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:66\nmsgid \"Check if the fields are correct\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/platform_serializer.py:155\n#, python-brace-format\nmsgid \"The platform configuration corresponding to {type} was not found\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/resource_chat_user.py:35\n#: apps/xpack/serializers/resource_chat_user.py:111\n#: apps/xpack/serializers/resource_chat_user_group.py:18\n#: apps/xpack/serializers/resource_chat_user_group.py:86\nmsgid \"Resource id\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/resource_chat_user.py:38\n#: apps/xpack/serializers/resource_chat_user.py:112\nmsgid \"User group id\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/resource_chat_user.py:94\nmsgid \"Is auth\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/resource_chat_user_group.py:20\nmsgid \"User group name\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/resource_chat_user_group.py:68\nmsgid \"user_group_id\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/sso_auth/cas.py:32\nmsgid \"HttpClient query failed: \"\nmsgstr \"\"\n\n#: apps/xpack/serializers/sso_auth/cas.py:58\nmsgid \"CAS authentication failed\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/sso_auth/oauth2.py:165\n#: apps/xpack/serializers/sso_auth/oauth2.py:184\n#: apps/xpack/serializers/sso_auth/oauth2.py:187\nmsgid \"Failed to obtain user information\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_api_key.py:12\nmsgid \"Allow cross domain\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_api_key.py:13\nmsgid \"Cross domain list\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_api_key.py:44\nmsgid \"system API key id\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:20\nmsgid \"theme\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:22\nmsgid \"login logo\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:23\nmsgid \"login image\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:24\nmsgid \"title\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:25\nmsgid \"slogan\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:26\n#: apps/xpack/serializers/system_params.py:27\nmsgid \"show user manual\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:28\nmsgid \"user manual url\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:29\nmsgid \"show forum\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:30\nmsgid \"forum url\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:31\nmsgid \"show project\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/system_params.py:32\nmsgid \"project url\"\nmsgstr \"\"\n\n#: apps/xpack/views/application_setting.py:24\n#: apps/xpack/views/application_setting.py:25\n#: apps/xpack/views/application_setting.py:26\nmsgid \"Modify Application Settings\"\nmsgstr \"Modify Agent Settings\"\n\n#: apps/xpack/views/application_setting.py:42\n#: apps/xpack/views/application_setting.py:43\n#: apps/xpack/views/application_setting.py:44\nmsgid \"Get Application Settings\"\nmsgstr \"Get Agent Settings\"\n\n#: apps/xpack/views/auth.py:52 apps/xpack/views/auth.py:53\n#: apps/xpack/views/auth.py:54 apps/xpack/views/chat_user_auth.py:46\n#: apps/xpack/views/chat_user_auth.py:47 apps/xpack/views/chat_user_auth.py:48\nmsgid \"Get authentication types\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:55 apps/xpack/views/auth.py:70\n#: apps/xpack/views/auth.py:90 apps/xpack/views/auth.py:108\n#: apps/xpack/views/auth.py:223 apps/xpack/views/auth.py:235\n#: apps/xpack/views/auth.py:249\nmsgid \"Authentication Configuration\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:67 apps/xpack/views/auth.py:68\n#: apps/xpack/views/auth.py:69 apps/xpack/views/chat_user_auth.py:62\n#: apps/xpack/views/chat_user_auth.py:63 apps/xpack/views/chat_user_auth.py:64\nmsgid \"Test LDAP connection\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:87 apps/xpack/views/auth.py:88\n#: apps/xpack/views/auth.py:89\nmsgid \"Add or modify authentication configuration\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:105 apps/xpack/views/auth.py:106\n#: apps/xpack/views/auth.py:107\nmsgid \"Get authentication configuration\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:118 apps/xpack/views/auth.py:119\n#: apps/xpack/views/auth.py:120 apps/xpack/views/chat_user_auth.py:112\n#: apps/xpack/views/chat_user_auth.py:113\n#: apps/xpack/views/chat_user_auth.py:114\nmsgid \"Ldap Log in\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:121 apps/xpack/views/auth.py:138\n#: apps/xpack/views/auth.py:157 apps/xpack/views/auth.py:176\n#: apps/xpack/views/auth.py:194 apps/xpack/views/auth.py:208\n#: apps/xpack/views/auth.py:266 apps/xpack/views/auth.py:286\n#: apps/xpack/views/auth.py:307 apps/xpack/views/auth.py:327\n#: apps/xpack/views/auth.py:348 apps/xpack/views/auth.py:368\nmsgid \"Three-party login\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:135 apps/xpack/views/auth.py:136\n#: apps/xpack/views/auth.py:137 apps/xpack/views/chat_user_auth.py:129\n#: apps/xpack/views/chat_user_auth.py:130\n#: apps/xpack/views/chat_user_auth.py:131\nmsgid \"CAS Log in\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:154 apps/xpack/views/auth.py:155\n#: apps/xpack/views/auth.py:156 apps/xpack/views/chat_user_auth.py:148\n#: apps/xpack/views/chat_user_auth.py:149\n#: apps/xpack/views/chat_user_auth.py:150\nmsgid \"OIDC Log in\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:173 apps/xpack/views/auth.py:174\n#: apps/xpack/views/auth.py:175 apps/xpack/views/chat_user_auth.py:167\n#: apps/xpack/views/chat_user_auth.py:168\n#: apps/xpack/views/chat_user_auth.py:169\nmsgid \"OAuth2 Log in\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:191 apps/xpack/views/auth.py:192\n#: apps/xpack/views/auth.py:193 apps/xpack/views/chat_user_auth.py:220\n#: apps/xpack/views/chat_user_auth.py:221\n#: apps/xpack/views/chat_user_auth.py:222\nmsgid \"Scan code login type\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:205 apps/xpack/views/auth.py:206\n#: apps/xpack/views/auth.py:207 apps/xpack/views/auth.py:220\n#: apps/xpack/views/auth.py:221 apps/xpack/views/auth.py:222\n#: apps/xpack/views/chat_user_auth.py:234\n#: apps/xpack/views/chat_user_auth.py:235\n#: apps/xpack/views/chat_user_auth.py:236\n#: apps/xpack/views/chat_user_auth.py:249\n#: apps/xpack/views/chat_user_auth.py:250\n#: apps/xpack/views/chat_user_auth.py:251\nmsgid \"Get platform information\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:232 apps/xpack/views/auth.py:233\n#: apps/xpack/views/auth.py:234 apps/xpack/views/chat_user_auth.py:261\n#: apps/xpack/views/chat_user_auth.py:262\n#: apps/xpack/views/chat_user_auth.py:263\nmsgid \"Modify platform information\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:246 apps/xpack/views/auth.py:247\n#: apps/xpack/views/auth.py:248 apps/xpack/views/chat_user_auth.py:275\n#: apps/xpack/views/chat_user_auth.py:276\n#: apps/xpack/views/chat_user_auth.py:277\nmsgid \"Test platform connection\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:263 apps/xpack/views/auth.py:264\n#: apps/xpack/views/auth.py:265 apps/xpack/views/chat_user_auth.py:292\n#: apps/xpack/views/chat_user_auth.py:293\n#: apps/xpack/views/chat_user_auth.py:294\nmsgid \"DingTalk callback\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:283 apps/xpack/views/auth.py:284\n#: apps/xpack/views/auth.py:285 apps/xpack/views/chat_user_auth.py:312\n#: apps/xpack/views/chat_user_auth.py:313\n#: apps/xpack/views/chat_user_auth.py:314\nmsgid \"DingTalk OAuth2 callback\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:304 apps/xpack/views/auth.py:305\n#: apps/xpack/views/auth.py:306 apps/xpack/views/chat_user_auth.py:333\n#: apps/xpack/views/chat_user_auth.py:334\n#: apps/xpack/views/chat_user_auth.py:335\nmsgid \"WeCom callback\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:324 apps/xpack/views/auth.py:325\n#: apps/xpack/views/auth.py:326 apps/xpack/views/chat_user_auth.py:353\n#: apps/xpack/views/chat_user_auth.py:354\n#: apps/xpack/views/chat_user_auth.py:355\nmsgid \"WeCom OAuth2 callback\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:345 apps/xpack/views/auth.py:346\n#: apps/xpack/views/auth.py:347 apps/xpack/views/chat_user_auth.py:374\n#: apps/xpack/views/chat_user_auth.py:375\n#: apps/xpack/views/chat_user_auth.py:376\nmsgid \"Lark callback\"\nmsgstr \"\"\n\n#: apps/xpack/views/auth.py:365 apps/xpack/views/auth.py:366\n#: apps/xpack/views/auth.py:367 apps/xpack/views/chat_user_auth.py:394\n#: apps/xpack/views/chat_user_auth.py:395\n#: apps/xpack/views/chat_user_auth.py:396\nmsgid \"Lark OAuth2 callback\"\nmsgstr \"\"\n\n#: apps/xpack/views/chat_user_auth.py:49 apps/xpack/views/chat_user_auth.py:65\n#: apps/xpack/views/chat_user_auth.py:85 apps/xpack/views/chat_user_auth.py:252\n#: apps/xpack/views/chat_user_auth.py:264\n#: apps/xpack/views/chat_user_auth.py:278\nmsgid \"Chat User/Authentication Configuration\"\nmsgstr \"\"\n\n#: apps/xpack/views/chat_user_auth.py:82 apps/xpack/views/chat_user_auth.py:83\n#: apps/xpack/views/chat_user_auth.py:84\nmsgid \"Add or modify Chat/Authentication Configuration\"\nmsgstr \"\"\n\n#: apps/xpack/views/chat_user_auth.py:99 apps/xpack/views/chat_user_auth.py:100\n#: apps/xpack/views/chat_user_auth.py:101\nmsgid \"Get Authentication Configuration\"\nmsgstr \"\"\n\n#: apps/xpack/views/chat_user_auth.py:102\nmsgid \"Chat User/login authentication\"\nmsgstr \"\"\n\n#: apps/xpack/views/chat_user_auth.py:115\n#: apps/xpack/views/chat_user_auth.py:132\n#: apps/xpack/views/chat_user_auth.py:151\n#: apps/xpack/views/chat_user_auth.py:170\n#: apps/xpack/views/chat_user_auth.py:223\n#: apps/xpack/views/chat_user_auth.py:237\n#: apps/xpack/views/chat_user_auth.py:295\n#: apps/xpack/views/chat_user_auth.py:315\n#: apps/xpack/views/chat_user_auth.py:336\n#: apps/xpack/views/chat_user_auth.py:356\n#: apps/xpack/views/chat_user_auth.py:377\n#: apps/xpack/views/chat_user_auth.py:397\nmsgid \"Chat User/Three-party login\"\nmsgstr \"\"\n\n#: apps/xpack/views/chat_user_auth.py:187\nmsgid \"Chat User/login\"\nmsgstr \"\"\n\n#: apps/xpack/views/chat_user_auth.py:414\n#: apps/xpack/views/chat_user_auth.py:415\n#: apps/xpack/views/chat_user_auth.py:416\nmsgid \"Application Password Certification\"\nmsgstr \"Agent Password Certification\"\n\n#: apps/xpack/views/license.py:31 apps/xpack/views/license.py:32\n#: apps/xpack/views/license.py:33\nmsgid \"Get license information\"\nmsgstr \"\"\n\n#: apps/xpack/views/license.py:42 apps/xpack/views/license.py:44\nmsgid \"Update license information\"\nmsgstr \"\"\n\n#: apps/xpack/views/license.py:43\nmsgid \"Update license information by uploading a new license file\"\nmsgstr \"\"\n\n#: apps/xpack/views/operate_log.py:21 apps/xpack/views/operate_log.py:22\n#: apps/xpack/views/operate_log.py:23\nmsgid \"Get menu operate log\"\nmsgstr \"\"\n\n#: apps/xpack/views/operate_log.py:25 apps/xpack/views/operate_log.py:41\n#: apps/xpack/views/operate_log.py:57\nmsgid \"System operate log\"\nmsgstr \"\"\n\n#: apps/xpack/views/operate_log.py:36 apps/xpack/views/operate_log.py:37\n#: apps/xpack/views/operate_log.py:38\nmsgid \"Get paginated operate log\"\nmsgstr \"\"\n\n#: apps/xpack/views/operate_log.py:54 apps/xpack/views/operate_log.py:55\n#: apps/xpack/views/operate_log.py:56\nmsgid \"Export operate log\"\nmsgstr \"\"\n\n#: apps/xpack/views/platform.py:60 apps/xpack/views/platform.py:61\n#: apps/xpack/views/platform.py:62\nmsgid \"Get platform configuration\"\nmsgstr \"\"\n\n#: apps/xpack/views/platform.py:65 apps/xpack/views/platform.py:79\nmsgid \"Application/application access\"\nmsgstr \"Agent/agent access\"\n\n#: apps/xpack/views/platform.py:73 apps/xpack/views/platform.py:74\n#: apps/xpack/views/platform.py:75\nmsgid \"Update platform configuration\"\nmsgstr \"\"\n\n#: apps/xpack/views/platform.py:94 apps/xpack/views/platform.py:95\n#: apps/xpack/views/platform.py:96\nmsgid \"Get platform status\"\nmsgstr \"\"\n\n#: apps/xpack/views/platform.py:98 apps/xpack/views/platform.py:118\nmsgid \"Application/Get platform status\"\nmsgstr \"Agent/Get platform status\"\n\n#: apps/xpack/views/platform.py:113 apps/xpack/views/platform.py:114\n#: apps/xpack/views/platform.py:115\nmsgid \"Update platform status\"\nmsgstr \"\"\n\n#: apps/xpack/views/resource_chat_user.py:27\n#: apps/xpack/views/resource_chat_user.py:28\n#: apps/xpack/views/resource_chat_user.py:29\nmsgid \"Get Resource chat user List\"\nmsgstr \"\"\n\n#: apps/xpack/views/resource_chat_user.py:32\n#: apps/xpack/views/resource_chat_user.py:54\n#: apps/xpack/views/resource_chat_user.py:77\n#: apps/xpack/views/system_chat_user_group.py:24\n#: apps/xpack/views/system_chat_user_group.py:45\n#: apps/xpack/views/system_chat_user_group.py:67\nmsgid \"Chat user\"\nmsgstr \"\"\n\n#: apps/xpack/views/resource_chat_user.py:48\n#: apps/xpack/views/resource_chat_user.py:49\n#: apps/xpack/views/resource_chat_user.py:50\nmsgid \"Edit Resource chat user List\"\nmsgstr \"\"\n\n#: apps/xpack/views/resource_chat_user.py:72\n#: apps/xpack/views/resource_chat_user.py:73\n#: apps/xpack/views/resource_chat_user.py:74\nmsgid \"Get Resource chat user page List\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_api_key.py:19 apps/xpack/views/system_api_key.py:20\n#: apps/xpack/views/system_api_key.py:21\nmsgid \"Create SystemAPIKey\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_api_key.py:34 apps/xpack/views/system_api_key.py:35\n#: apps/xpack/views/system_api_key.py:36\nmsgid \"Get SystemAPIKey List\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_api_key.py:50 apps/xpack/views/system_api_key.py:51\n#: apps/xpack/views/system_api_key.py:52\nmsgid \"Update SystemAPIKey\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_api_key.py:66 apps/xpack/views/system_api_key.py:67\n#: apps/xpack/views/system_api_key.py:68\nmsgid \"Delete SystemAPIKey\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_chat_user.py:60\n#: apps/xpack/views/system_chat_user.py:76\n#: apps/xpack/views/system_chat_user.py:89\n#: apps/xpack/views/system_chat_user.py:102\n#: apps/xpack/views/system_chat_user.py:113\n#: apps/xpack/views/system_chat_user.py:131\n#: apps/xpack/views/system_chat_user.py:146\n#: apps/xpack/views/system_chat_user.py:163\n#: apps/xpack/views/system_chat_user.py:180\n#: apps/xpack/views/system_chat_user.py:200\n#: apps/xpack/views/system_chat_user.py:217\nmsgid \"System/Chat user\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_chat_user.py:73\n#: apps/xpack/views/system_chat_user.py:74\n#: apps/xpack/views/system_chat_user.py:75\nmsgid \"Get chat user list\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_chat_user.py:214\n#: apps/xpack/views/system_chat_user.py:216\nmsgid \"Sync chat users\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_chat_user.py:215\nmsgid \"Sync chat users from external source\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_chat_user.py:235\n#: apps/xpack/views/system_chat_user.py:247\n#: apps/xpack/views/system_chat_user.py:261\n#: apps/xpack/views/system_chat_user.py:279\n#: apps/xpack/views/system_chat_user.py:301\n#: apps/xpack/views/system_chat_user.py:321\nmsgid \"System/User Group\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_chat_user_group.py:19\n#: apps/xpack/views/system_chat_user_group.py:20\n#: apps/xpack/views/system_chat_user_group.py:21\nmsgid \"Get Resource chat user group List\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_chat_user_group.py:39\n#: apps/xpack/views/system_chat_user_group.py:40\n#: apps/xpack/views/system_chat_user_group.py:41\nmsgid \"Edit Resource chat user group List\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_chat_user_group.py:62\n#: apps/xpack/views/system_chat_user_group.py:64\nmsgid \"Get Resource chat user group page List\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_chat_user_group.py:63\nmsgid \"Get Resource chat user page group List\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_params.py:22 apps/xpack/views/system_params.py:23\n#: apps/xpack/views/system_params.py:24\nmsgid \"View appearance settings\"\nmsgstr \"\"\n\n#: apps/xpack/views/system_params.py:39 apps/xpack/views/system_params.py:40\n#: apps/xpack/views/system_params.py:41\nmsgid \"Update appearance settings\"\nmsgstr \"\"\n\nmsgid \"Application Access\"\nmsgstr \"Agent Access\"\n\nmsgid \"Display execution details\"\nmsgstr \"\"\n\nmsgid \"LOCAL\"\nmsgstr \"Account login\"\n\nmsgid \"CAS\"\nmsgstr \"CAS\"\n\nmsgid \"LDAP\"\nmsgstr \"LDAP\"\n\nmsgid \"OIDC\"\nmsgstr \"OIDC\"\n\nmsgid \"OAuth2\"\nmsgstr \"OAuth2\"\n\nmsgid \"dingtalk\"\nmsgstr \"DingTalk\"\n\nmsgid \"wecom\"\nmsgstr \"WeCom\"\n\nmsgid \"lark\"\nmsgstr \"Lark\"\n\nmsgid \"Get tool list\"\nmsgstr \"\"\n\nmsgid \"Setting\"\nmsgstr \"\"\n\nmsgid \"Get verification results\"\nmsgstr \"\"\n\nmsgid \"Validation\"\nmsgstr \"\"\n\nmsgid \"Models Resource\"\nmsgstr \"\"\n\nmsgid \"Tools Resource\"\nmsgstr \"\"\n\nmsgid \"Get resource model list\"\nmsgstr \"\"\n\nmsgid \"System Model\"\nmsgstr \"\"\n\nmsgid \"Dialogue users\"\nmsgstr \"\"\n\nmsgid \"Conversation log\"\nmsgstr \"\"\n\nmsgid \"Public access link\"\nmsgstr \"\"\n\nmsgid \"User management\"\nmsgstr \"User Management\"\n\nmsgid \"Chat User/logout\"\nmsgstr \"\"\n\nmsgid \"Paragraph\"\nmsgstr \"\"\n\nmsgid \"User group\"\nmsgstr \"\"\n\nmsgid \"Remove member from user group\"\nmsgstr \"\"\n\nmsgid \"Create a web site knowledge base\"\nmsgstr \"\"\n\nmsgid \"Modify knowledge base information\"\nmsgstr \"\"\n\nmsgid \"Delete knowledge base\"\nmsgstr \"\"\n\nmsgid \"model\"\nmsgstr \"Model\"\n\nmsgid \"Batch add user to group\"\nmsgstr \"\"\n\nmsgid \"Add internal tool\"\nmsgstr \"\"\n\nmsgid \"Batch generate related\"\nmsgstr \"\"\n\nmsgid \"Update personal system API_KEY\"\nmsgstr \"\"\n\nmsgid \"Delete user group\"\nmsgstr \"\"\n\nmsgid \"Add user\"\nmsgstr \"\"\n\nmsgid \"folder\"\nmsgstr \"Folder\"\n\nmsgid \"Create or update user group\"\nmsgstr \"\"\n\nmsgid \"Edit folder\"\nmsgstr \"\"\n\nmsgid \"Email settings\"\nmsgstr \"\"\n\nmsgid \"trial listening\"\nmsgstr \"\"\n\nmsgid \"Add member to user group\"\nmsgstr \"\"\n\nmsgid \"System\"\nmsgstr \"\"\n\nmsgid \"Shared Knowledge/Document\"\nmsgstr \"\"\n\nmsgid \"System Application\"\nmsgstr \"\"\n\nmsgid \"Hit-Test\"\nmsgstr \"\"\n\nmsgid \"Export Application\"\nmsgstr \"\"\n\nmsgid \"Add ApiKey\"\nmsgstr \"\"\n\nmsgid \"Delete application API_KEY\"\nmsgstr \"Delete agent API_KEY\"\n\nmsgid \"knowledge Base\"\nmsgstr \"\"\n\nmsgid \"API KEY\"\nmsgstr \"\"\n\nmsgid \"Download\"\nmsgstr \"\"\n\nmsgid \"Delete personal system API_KEY\"\nmsgstr \"\"\n\nmsgid \"Add personal system API_KEY\"\nmsgstr \"\"\n\nmsgid \"Generate related documents\"\nmsgstr \"\"\n\nmsgid \"Modify application access token\"\nmsgstr \"Modify agent access token\"\n\nmsgid \"File not exist. Only manually uploaded documents are supported\"\nmsgstr \"\"\n\nmsgid \"Resource\"\nmsgstr \"Resource Management\"\n\nmsgid \"LDAP configuration not found or not active\"\nmsgstr \"\"\n\nmsgid \"Lark configuration not found or not active\"\nmsgstr \"\"\n\nmsgid \"Failed to get Lark collaborators\"\nmsgstr \"\"\n\nmsgid \"Failed to get Lark user details\"\nmsgstr \"\"\n\nmsgid \"WeCom configuration not found or not active\"\nmsgstr \"\"\n\nmsgid \"Failed to get WeCom access token\"\nmsgstr \"\"\n\nmsgid \"Failed to get WeCom agent info\"\nmsgstr \"\"\n\nmsgid \"Failed to get WeCom department users\"\nmsgstr \"\"\n\nmsgid \"Failed to get WeCom user info\"\nmsgstr \"\"\n\nmsgid \"Publish status\"\nmsgstr \"\"\n\nmsgid \"Unpublished\"\nmsgstr \"\"\n\nmsgid \"Published\"\nmsgstr \"\"\n\nmsgid \"users_permission\"\nmsgstr \"\"\n\nmsgid \"Get user authorization status of resource\"\nmsgstr \"\"\n\nmsgid \"Edit user authorization status of resource\"\nmsgstr \"\"\n\nmsgid \"Get user authorization status of resource by page\"\nmsgstr \"\"\n\nmsgid \"Obtain resource authorization list by page\"\nmsgstr \"\"\n\nmsgid \"Engine model type\"\nmsgstr \"\"\n\nmsgid \"If not passed, the default value is 16k_zh (Chinese universal)\"\nmsgstr \"\"\n\nmsgid \"Chinese telephone universal\"\nmsgstr \"\"\n\nmsgid \"English telephone universal\"\nmsgstr \"\"\n\nmsgid \"Commonly used in Chinese\"\nmsgstr \"\"\n\nmsgid \"Chinese, English, and Guangdong\"\nmsgstr \"\"\n\nmsgid \"Chinese medical\"\nmsgstr \"\"\n\nmsgid \"English\"\nmsgstr \"\"\n\nmsgid \"Cantonese\"\nmsgstr \"\"\n\nmsgid \"Japanese\"\nmsgstr \"\"\n\nmsgid \"Korean\"\nmsgstr \"\"\n\nmsgid \"Vietnamese\"\nmsgstr \"\"\n\nmsgid \"Malay language\"\nmsgstr \"\"\n\nmsgid \"Indonesian language\"\nmsgstr \"\"\n\nmsgid \"Filipino language\"\nmsgstr \"\"\n\nmsgid \"Thai\"\nmsgstr \"\"\n\nmsgid \"Portuguese\"\nmsgstr \"\"\n\nmsgid \"Turkish\"\nmsgstr \"\"\n\nmsgid \"Arabic\"\nmsgstr \"\"\n\nmsgid \"Spanish\"\nmsgstr \"\"\n\nmsgid \"Hindi\"\nmsgstr \"\"\n\nmsgid \"French\"\nmsgstr \"\"\n\nmsgid \"German\"\nmsgstr \"\"\n\nmsgid \"Multiple dialects, supporting 23 dialects\"\nmsgstr \"\"\n\nmsgid \"This interface is used to recognize short audio files within 60 seconds. Supports Mandarin Chinese, English, Cantonese, Japanese, Vietnamese, Malay, Indonesian, Filipino, Thai, Portuguese, Turkish, Arabic, Hindi, French, German, and 23 Chinese dialects.\"\nmsgstr \"\"\n\nmsgid \"CueWord\"\nmsgstr \"\"\n\nmsgid \"If not passed, the default value is What is this audio saying? Only answer the audio content\"\nmsgstr \"\"\n\nmsgid \"The Qwen Omni series model supports inputting multiple modalities of data, including video, audio, images, and text, and outputting audio and text.\"\nmsgstr \"\"\n\nmsgid \"resource authorization\"\nmsgstr \"\"\n\nmsgid \"The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition.\"\nmsgstr \"\"\n\nmsgid \"If not passed, the default value is 'zh'\"\nmsgstr \"\"\n\nmsgid \"System resources authorization\"\nmsgstr \"\"\n\nmsgid \"This folder contains resources that you dont have permission\"\nmsgstr \"\"\n\nmsgid \"Text to Video\"\nmsgstr \"\"\n\nmsgid \"Image to Video\"\nmsgstr \"\"\n\nmsgid \"Authentication failed. Please verify that the parameters are correct\"\nmsgstr \"\"\n\nmsgid \"Chat context\"\nmsgstr \"\"\n\nmsgid \"Prompt template\"\nmsgstr \"\"\n\nmsgid \"generate prompt\"\nmsgstr \"\"\n\n\nmsgid \"Watermark\"\nmsgstr \"\"\n\nmsgid \"Whether to add watermark\"\nmsgstr \"\"\n\nmsgid \"Resolution\"\nmsgstr \"\"\n\nmsgid \"Ratio\"\nmsgstr \"\"\n\nmsgid \"Duration\"\nmsgstr \"\"\n\nmsgid \"Failed to generate video\"\nmsgstr \"\"\n\nmsgid \"password\"\nmsgstr \"Password login\"\n\nmsgid \"Failed to obtain the image\"\nmsgstr \"\"\n\nmsgid \"Update auth setting\"\nmsgstr \"\"\n\nmsgid \"If not passed, the default value is streaming_asr_demo\"\nmsgstr \"\"\n\nmsgid \"If not passed, the default value is 16000\"\nmsgstr \"\"\n\nmsgid \"Sample Rate\"\nmsgstr \"\"\n\nmsgid \"Captcha is required\"\nmsgstr \"\"\n\nmsgid \"Tag\"\nmsgstr \"\"\n\nmsgid \"Tag Setting\"\nmsgstr \"\"\n\nmsgid \"Download Original Document\"\nmsgstr \"\"\n\nmsgid \"Replace Original Document\"\nmsgstr \"\"\n\nmsgid \"Update License\"\nmsgstr \"\"\n\nmsgid \"Tag Key\"\nmsgstr \"\"\n\nmsgid \"Tag Value\"\nmsgstr \"\"\n\nmsgid \"Tag id does not exist\"\nmsgstr \"\"\n\nmsgid \"Tag key already exists\"\nmsgstr \"\"\n\nmsgid \"Tag value already exists\"\nmsgstr \"\"\n\nmsgid \"Non-existent id\"\nmsgstr \"\"\n\nmsgid \"No permission for the target folder\"\nmsgstr \"\"\n\nmsgid \"Application token usage statistics\"\nmsgstr \"Agent token usage statistics\"\n\nmsgid \"Application top question statistics\"\nmsgstr \"Agent top question statistics\"\n\nmsgid \"SAML2 Metadata\"\nmsgstr \"\"\n\nmsgid \"SAML2 Log in\"\nmsgstr \"\"\n\nmsgid \"SAML2 SSO\"\nmsgstr \"\"\n\nmsgid \"Workflow\"\nmsgstr \"\"\n\nmsgid \"Web source url\"\nmsgstr \"\"\n\nmsgid \"Web knowledge selector\"\nmsgstr \"\"\n\nmsgid \"The default is body, you can enter .classname/#idname/tagname\"\nmsgstr \"\"\n\nmsgid \"Please enter the Web root address\"\nmsgstr \"\"\n\nmsgid \"File size exceeds limit\"\nmsgstr \"\"\n\nmsgid \"File upload is not enabled\"\nmsgstr \"\"\n\nmsgid \"Blocked unsafe redirect to internal host\"\nmsgstr \"\"\n\nmsgid \"Audio file recognition - Tongyi Qwen\"\nmsgstr \"\"\n\nmsgid \"Real-time speech recognition - Fun-ASR/Paraformer\"\nmsgstr \"\"\n\nmsgid \"Qwen-Omni\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingxiaoxuan Flow\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingyuyan Flow\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingfeiyi Flow\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingxiaoyue Flow\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Sun Dasheng Flow\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingyuzhao Flow\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingxiaotang Flow\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingxiaorong Flow\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Xinyun Flow\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Grant (EN)\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lila (EN)\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingwanwan Pro\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Yiyi Pro\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Huifangnv Pro\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingxiaoying Pro\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingfeibo Pro\"\nmsgstr \"\"\n\nmsgid \"Super-humanoid: Lingyuyan Pro\"\nmsgstr \"\"\n\nmsgid \"Login failed %s times, account will be locked, you have %s more chances !\"\nmsgstr \"\"\n\nmsgid \"This account has been locked for %s minutes, please try again later\"\nmsgstr \"\"\n\nmsgid \"User does not have permission to use API Key\"\nmsgstr \"\"\n\nmsgid \"Import knowledge workflow\"\nmsgstr \"\"\n\nmsgid \"Export knowledge workflow\"\nmsgstr \"\"\n\nmsgid \"Role IDs cannot be empty\"\nmsgstr \"\"\n\nmsgid \"Some roles do not exist\"\nmsgstr \"\"\n\nmsgid \"Authorized pagination list for obtaining resources\"\nmsgstr \"\"\n\nmsgid \"Resources mapping\"\nmsgstr \"\"\n\nmsgid \"Batch set user roles\"\nmsgstr \"\"\n\nmsgid \"Role Setting cannot be empty\"\nmsgstr \"\"\n\nmsgid \"View related resources\"\nmsgstr \"\"\n\nmsgid \"Feedback reason\"\nmsgstr \"\"\n\nmsgid \"Other reason content\"\nmsgstr \"\"\n\nmsgid \"accurate\"\nmsgstr \"accurate\"\n\nmsgid \"complete\"\nmsgstr \"complete\"\n\nmsgid \"inaccurate\"\nmsgstr \"inaccurate\"\n\nmsgid \"incomplete\"\nmsgstr \"incomplete\"\n\nmsgid \"Secret key is invalid\"\nmsgstr \"\"\n\nmsgid \"Secret key is expired\"\nmsgstr \"\"\n\nmsgid \"Online Usage\"\nmsgstr \"\"\n\nmsgid \"API Call\"\nmsgstr \"\"\n\nmsgid \"Enterprise WeChat\"\nmsgstr \"\"\n\nmsgid \"WeChat Public Account\"\nmsgstr \"\"\n\nmsgid \"Lark\"\nmsgstr \"Lark App\"\n\nmsgid \"DingTalk\"\nmsgstr \"DingTalk App\"\n\nmsgid \"Enterprise WeChat Robot\"\nmsgstr \"\"\n\nmsgid \"Trigger\"\nmsgstr \"\"\n\nmsgid \"Slack\"\nmsgstr \"Slack App\"\n\nmsgid \"Root Directory\"\nmsgstr \"\"\n\nmsgid \"Create trigger\"\nmsgstr \"\"\n\nmsgid \"Get the trigger list\"\nmsgstr \"\"\n\nmsgid \"Get trigger details\"\nmsgstr \"\"\n\nmsgid \"Modify the trigger\"\nmsgstr \"\"\n\nmsgid \"Delete the trigger\"\nmsgstr \"\"\n\nmsgid \"Delete trigger in batches\"\nmsgstr \"\"\n\nmsgid \"Activate trigger in batches\"\nmsgstr \"\"\n\nmsgid \"Get the trigger list by page\"\nmsgstr \"\"\n\nmsgid \"Create trigger in source\"\nmsgstr \"\"\n\nmsgid \"Get the trigger list of source\"\nmsgstr \"\"\n\nmsgid \"Modify the task source trigger\"\nmsgstr \"\"\n\nmsgid \"Get Task source trigger details\"\nmsgstr \"\"\n\nmsgid \"Delete the task source trigger\"\nmsgstr \"\"\n\nmsgid \"Get the task list of triggers\"\nmsgstr \"\"\n\nmsgid \"Retrieve detailed records of tasks executed by the trigger.\"\nmsgstr \"\"\n\nmsgid \"Get a paginated list of execution records for trigger tasks.\"\nmsgstr \"\"\n\nmsgid \"%s must be an array\"\nmsgstr \"\"\n\nmsgid \"%s must not be empty\"\nmsgstr \"\"\n\nmsgid \"%s values must be between %s and %s\"\nmsgstr \"\"\n\nmsgid \"Invalid time format: %s, must be HH:MM (e.g., 09:00)\"\nmsgstr \"\"\n\nmsgid \"schedule_type must be one of %s\"\nmsgstr \"\"\n\nmsgid \"interval_value must be an integer greater than or equal to 1\"\nmsgstr \"\"\n\nmsgid \"interval_unit must be one of %s\"\nmsgstr \"\"\n\nmsgid \"body must be an array\"\nmsgstr \"\"\n\nmsgid \"Error trigger type\"\nmsgstr \"\"\n\nmsgid \"The following id does not exist: %s\"\nmsgstr \"\"\n\nmsgid \"%s must be a dict\"\nmsgstr \"\"\n\nmsgid \"input_field_list must be a dict\"\nmsgstr \"\"\n\nmsgid \"%s type requires %s field\"\nmsgstr \"\"\n\nmsgid \"trigger name\"\nmsgstr \"\"\n\nmsgid \"trigger description\"\nmsgstr \"\"\n\nmsgid \"trigger setting\"\nmsgstr \"\"\n\nmsgid \"Trigger ID\"\nmsgstr \"\"\n\nmsgid \"Trigger task can not be empty\"\nmsgstr \"\"\n\nmsgid \"%s id does not exist\"\nmsgstr \"\"\n\nmsgid \"Trigger id does not exist\"\nmsgstr \"\"\n\nmsgid \"Trigger not found\"\nmsgstr \"\"\n\nmsgid \"Trigger must have at least one task\"\nmsgstr \"\"\n\nmsgid \"Trigger task number must be one\"\nmsgstr \"\"\n\nmsgid \"Incorrect trigger task\"\nmsgstr \"\"\n\nmsgid \"Trigger task ID\"\nmsgstr \"\"\n\nmsgid \"Trigger task record ID\"\nmsgstr \"\"\n\nmsgid \"Trigger task record id does not exist\"\nmsgstr \"\"\n\nmsgid \"Order field\"\nmsgstr \"\"\n\nmsgid \"System Trigger\"\nmsgstr \"\"\n\nmsgid \"Get the System trigger list of source\"\nmsgstr \"\"\n\nmsgid \"Get System Task source trigger details\"\nmsgstr \"\"\n\nmsgid \"Modify the System task source trigger\"\nmsgstr \"\"\n\nmsgid \"Delete the System task source trigger\"\nmsgstr \"\"\n\nmsgid \"Invalid source type\"\nmsgstr \"\"\n\nmsgid \"Shared tool is not supported\"\nmsgstr \"\"\n\nmsgid \"Read Trigger\"\nmsgstr \"\"\n\nmsgid \"Create Trigger\"\nmsgstr \"\"\n\nmsgid \"Edit Trigger\"\nmsgstr \"\"\n\nmsgid \"Delete Trigger\"\nmsgstr \"\"\n\nmsgid \"Read execute record\"\nmsgstr \"\"\n\nmsgid \"ADMIN\"\nmsgstr \"System Administrator\"\n\nmsgid \"WORKSPACE_MANAGE\"\nmsgstr \"Workspace Manager\"\n\nmsgid \"USER\"\nmsgstr \"Regular User\"\n\nmsgid \"Generate share link\"\nmsgstr \"Generate share link\"\n\nmsgid \"Chat record link\"\nmsgstr \"Chat record link\"\n\nmsgid \"Get chat record by share link\"\nmsgstr \"Get chat record by share link\"\n\nmsgid \"Invalid chat record ids\"\nmsgstr \"Invalid chat record ids\"\n\nmsgid \"Share link does not exist\"\nmsgstr \"Share link does not exist\"\n\nmsgid \"Chat has been deleted\"\nmsgstr \"Chat has been deleted\"\n\nmsgid \"cron type requires cron_expression field\"\nmsgstr \"cron type requires cron_expression field\"\n\nmsgid \"Invalid cron expression: %s\"\nmsgstr \"Invalid cron expression: %s\"\n\nmsgid \"Batch Remove Documents from Tag\"\nmsgstr \"Batch Remove Documents from Tag\"\n\nmsgid \"Document does not belong to current knowledge\"\nmsgstr \"Document does not belong to current knowledge\"\n\nmsgid \"Move an application\"\nmsgstr \"Move an application\""
  },
  {
    "path": "apps/locales/zh_CN/LC_MESSAGES/django.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2025-06-18 17:33+0800\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: apps/application/api/application_api.py:21\n#: apps/application/serializers/application.py:153\nmsgid \"Workflow Objects\"\nmsgstr \"工作流对象\"\n\n#: apps/application/api/application_api.py:52\n#: apps/application/api/application_chat.py:104\n#: apps/application/api/application_chat_record.py:74\n#: apps/role_setting/api/role_setting.py:171 apps/shared/api/shared_tool.py:79\n#: apps/shared/api/shared_tool.py:119 apps/workspace/api/workspace.py:110\n#: apps/xpack/api/user_group.py:61\nmsgid \"Current page\"\nmsgstr \"当前页\"\n\n#: apps/application/api/application_api.py:59\n#: apps/application/api/application_chat.py:111\n#: apps/application/api/application_chat_record.py:81\n#: apps/role_setting/api/role_setting.py:178 apps/shared/api/shared_tool.py:86\n#: apps/shared/api/shared_tool.py:126 apps/workspace/api/workspace.py:117\n#: apps/xpack/api/user_group.py:68\nmsgid \"Page size\"\nmsgstr \"每页大小\"\n\n#: apps/application/api/application_api.py:66\n#: apps/application/serializers/application.py:156\n#: apps/application/serializers/application.py:195\n#: apps/application/serializers/application.py:274\n#: apps/folders/serializers/folder.py:97 apps/folders/serializers/folder.py:139\n#: apps/knowledge/serializers/knowledge.py:52\n#: apps/knowledge/serializers/knowledge.py:59\n#: apps/tools/serializers/tool.py:453\n#: apps/xpack/serializers/dataset_lark_serializer.py:55\nmsgid \"folder id\"\nmsgstr \"文件夹 ID\"\n\n#: apps/application/api/application_api.py:73\n#: apps/application/serializers/application.py:149\n#: apps/application/serializers/application.py:275\n#: apps/application/serializers/application.py:282\n#: apps/application/serializers/application.py:368\n#| msgid \"Application\"\nmsgid \"Application Name\"\nmsgstr \"智能体名称\"\n\n#: apps/application/api/application_api.py:80\n#: apps/application/serializers/application.py:152\n#: apps/application/serializers/application.py:276\n#: apps/application/serializers/application.py:283\n#: apps/application/serializers/application.py:284\n#: apps/application/serializers/application.py:370\n#| msgid \"Application/Version\"\nmsgid \"Application Description\"\nmsgstr \"智能体描述\"\n\n#: apps/application/api/application_api.py:87\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:78\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:30\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:47\n#: apps/application/serializers/application.py:99\n#: apps/application/serializers/application.py:277\n#: apps/application/serializers/application.py:295\n#: apps/application/serializers/application.py:413\n#: apps/application/serializers/application.py:540\n#: apps/role_setting/api/role_setting.py:144 apps/tools/serializers/tool.py:357\n#: apps/tools/serializers/tool.py:390 apps/users/api/user.py:52\n#: apps/users/api/user.py:110 apps/users/api/user.py:126\n#: apps/users/serializers/user.py:325 apps/workspace/api/workspace.py:82\n#: apps/workspace/serializers/workspace_serializers.py:270\n#: apps/workspace/serializers/workspace_serializers.py:306\n#: apps/xpack/api/chat_user.py:49 apps/xpack/api/chat_user.py:92\n#: apps/xpack/serializers/chat_user.py:301\nmsgid \"User ID\"\nmsgstr \"用户 ID\"\n\n#: apps/application/api/application_chat.py:70\n#: apps/application/serializers/application_chat.py:56\nmsgid \"Minimum number of likes\"\nmsgstr \"最小点赞数\"\n\n#: apps/application/api/application_chat.py:76\n#: apps/application/serializers/application_chat.py:58\nmsgid \"Minimum number of clicks\"\nmsgstr \"最小点击数\"\n\n#: apps/application/api/application_chat.py:82\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:18\n#: apps/application/serializers/application_chat.py:59\nmsgid \"Comparator\"\nmsgstr \"比较器\"\n\n#: apps/application/api/application_chat_record.py:46\n#: apps/application/api/application_chat_record.py:115\n#: apps/application/serializers/application_chat.py:47\n#: apps/application/serializers/application_chat_record.py:76\n#| msgid \"Chat\"\nmsgid \"Chat ID\"\nmsgstr \"对话 ID\"\n\n#: apps/application/api/application_chat_record.py:53\n#: apps/application/serializers/application_chat_record.py:77\nmsgid \"Is it in order\"\nmsgstr \"是否有序\"\n\n#: apps/application/api/application_chat_record.py:122\nmsgid \"Chat Record ID\"\nmsgstr \"对话记录 ID\"\n\n#: apps/application/api/application_chat_record.py:129\n#: apps/shared/api/shared_knowledge.py:235\n#: apps/shared/api/shared_knowledge.py:256 apps/xpack/api/knowledge_lark.py:47\n#: apps/xpack/api/knowledge_lark.py:79 apps/xpack/api/knowledge_lark.py:111\n#| msgid \"Knowledge\"\nmsgid \"Knowledge ID\"\nmsgstr \"知识库 ID\"\n\n#: apps/application/api/application_chat_record.py:136\n#: apps/shared/api/shared_knowledge.py:263 apps/xpack/api/knowledge_lark.py:86\n#| msgid \"Document\"\nmsgid \"Document ID\"\nmsgstr \"文档 ID\"\n\n#: apps/application/api/application_chat_record.py:148\n#| msgid \"Paragraph list\"\nmsgid \"Paragraph ID\"\nmsgstr \"段落 ID\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:26\n#| msgid \"type error\"\nmsgid \"Model type error\"\nmsgstr \"模型类型错误\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:36\n#: apps/common/field/common.py:24 apps/common/field/common.py:37\n#| msgid \"type error\"\nmsgid \"Message type error\"\nmsgstr \"消息类型错误\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:55\n#| msgid \"Question list\"\nmsgid \"Conversation list\"\nmsgstr \"对话列表\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:56\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:29\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:18\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:12\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:13\n#: apps/application/flow/step_node/question_node/i_question_node.py:18\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:12\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:13\n#: apps/application/serializers/application.py:101\n#: apps/application/serializers/application.py:285\n#: apps/knowledge/serializers/common.py:71 apps/shared/api/shared_model.py:76\n#: apps/shared/api/shared_model.py:98\nmsgid \"Model id\"\nmsgstr \"模型ID\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:58\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:29\n#| msgid \"Paragraph list\"\nmsgid \"Paragraph List\"\nmsgstr \"段落列表\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:60\n#: apps/application/serializers/application_chat.py:182\n#: apps/application/serializers/application_chat_record.py:41\n#: apps/application/serializers/application_chat_record.py:140\n#: apps/application/serializers/application_chat_record.py:179\n#: apps/application/serializers/application_chat_record.py:247\n#: apps/application/serializers/application_chat_record.py:312\n#: apps/chat/serializers/chat.py:98 apps/chat/serializers/chat.py:114\n#| msgid \"User relation ID\"\nmsgid \"Conversation ID\"\nmsgstr \"对话 ID\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:62\n#: apps/application/flow/step_node/application_node/i_application_node.py:14\n#: apps/application/serializers/application_chat.py:182\n#: apps/chat/serializers/chat.py:40\n#| msgid \"Question list\"\nmsgid \"User Questions\"\nmsgstr \"用户问题\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:65\nmsgid \"Post-processor\"\nmsgstr \"后置处理器\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:68\n#| msgid \"Create question\"\nmsgid \"Completion Question\"\nmsgstr \"完成问题\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:70\nmsgid \"Streaming Output\"\nmsgstr \"流式输出\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:71\n#: apps/xpack/serializers/resource_chat_user.py:93\n#| msgid \"user id\"\nmsgid \"Chat user id\"\nmsgstr \"对话用户 ID\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:73\n#| msgid \"Create user\"\nmsgid \"Chat user Type\"\nmsgstr \"对话用户类型\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:76\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:47\nmsgid \"No reference segment settings\"\nmsgstr \"无参考段设置\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:81\nmsgid \"Model settings\"\nmsgstr \"模型设置\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:84\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:29\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:29\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:28\n#: apps/application/flow/step_node/question_node/i_question_node.py:29\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:20\nmsgid \"Model parameter settings\"\nmsgstr \"模型参数设置\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:91\nmsgid \"message type error\"\nmsgstr \"消息类型错误\"\n\n#: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:224\n#: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:270\nmsgid \"\"\n\"Sorry, the AI model is not configured. Please go to the application to set \"\n\"up the AI model first.\"\nmsgstr \"抱歉，AI 模型未配置，请先前往智能体设置 AI 模型。\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:26\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:24\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:24\n#: apps/application/serializers/application_chat_record.py:172\nmsgid \"question\"\nmsgstr \"问题\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:32\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:27\nmsgid \"History Questions\"\nmsgstr \"历史问题\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:34\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:23\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:20\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:18\n#: apps/application/flow/step_node/question_node/i_question_node.py:24\nmsgid \"Number of multi-round conversations\"\nmsgstr \"多轮对话的数量\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:37\nmsgid \"Maximum length of the knowledge base paragraph\"\nmsgstr \"知识库段落的最大长度\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:39\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:21\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:16\n#: apps/application/flow/step_node/question_node/i_question_node.py:21\n#: apps/application/serializers/application.py:79\n#: apps/application/serializers/application.py:124\n#: apps/knowledge/serializers/common.py:72\nmsgid \"Prompt word\"\nmsgstr \"提示词\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:41\nmsgid \"System prompt words (role)\"\nmsgstr \"系统提示词（角色）\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:44\nmsgid \"Completion problem\"\nmsgstr \"完成问题\"\n\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:32\n#: apps/application/serializers/application.py:215\nmsgid \"Question completion prompt\"\nmsgstr \"问题完成提示\"\n\n#: apps/application/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py:20\n#: apps/application/serializers/common.py:87\n#, python-brace-format\nmsgid \"\"\n\"() contains the user's question. Answer the guessed user's question based on \"\n\"the context ({question}) Requirement: Output a complete question and put it \"\n\"in the <data></data> tag\"\nmsgstr \" () 包含用户的问题。根据上下文（{question}）要求：输出一个完整的问题并提出在<data></data>标签中\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:27\nmsgid \"System completes question text\"\nmsgstr \"系统完成问题文本\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:30\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:39\nmsgid \"Dataset id list\"\nmsgstr \"知识库 ID 列表\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:33\nmsgid \"List of document ids to exclude\"\nmsgstr \"排除的文档 ID 列表\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:36\nmsgid \"List of exclusion vector ids\"\nmsgstr \"排除的向量 ID 列表\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:39\n#: apps/application/flow/step_node/reranker_node/i_reranker_node.py:21\n#: apps/application/flow/step_node/reranker_node/i_reranker_node.py:24\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:24\n#: apps/application/serializers/application.py:84\n#: apps/application/serializers/application_chat.py:185\nmsgid \"Reference segment number\"\nmsgstr \"引用分段数\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:42\nmsgid \"Similarity\"\nmsgstr \"相似度\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:45\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:30\n#: apps/application/serializers/application.py:91\n#: apps/knowledge/serializers/knowledge.py:100\n#: apps/knowledge/serializers/knowledge.py:643\nmsgid \"The type only supports embedding|keywords|blend\"\nmsgstr \"类型仅支持嵌入|关键字|混合\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:46\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:31\n#: apps/application/serializers/application.py:92\nmsgid \"Retrieval Mode\"\nmsgstr \"检索模式\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:31\n#: apps/application/serializers/application.py:113\n#: apps/application/serializers/application.py:657\n#: apps/application/serializers/application.py:664\n#: apps/application/serializers/application.py:671\n#: apps/knowledge/serializers/document.py:643\n#: apps/knowledge/serializers/knowledge.py:220\n#: apps/models_provider/serializers/model_serializer.py:116\n#: apps/models_provider/serializers/model_serializer.py:134\n#: apps/models_provider/serializers/model_serializer.py:370\n#: apps/models_provider/tools.py:111 apps/shared/serializers/shared_model.py:32\n#: apps/shared/serializers/shared_model.py:65\n#: apps/shared/serializers/shared_model.py:82\nmsgid \"Model does not exist\"\nmsgstr \"模型不存在\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:33\nmsgid \"No permission to use this model {model_name}\"\nmsgstr \"无权限使用此模型{model_name}\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:42\nmsgid \"\"\n\"The vector model of the associated knowledge base is inconsistent and the \"\n\"segmentation cannot be recalled.\"\nmsgstr \"关联的知识库的向量模型不一致，无法回调分段。\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:44\nmsgid \"The knowledge base setting is wrong, please reset the knowledge base\"\nmsgstr \"知识库设置错误，请重置知识库\"\n\n#: apps/application/flow/common.py:206\n#, python-brace-format\nmsgid \"The branch {branch} of the {node} node needs to be connected\"\nmsgstr \"需要连接 {node} 节点的 {branch} 分支\"\n\n#: apps/application/flow/common.py:212\n#, python-brace-format\nmsgid \"{node} Nodes cannot be considered as end nodes\"\nmsgstr \"{node} 节点不能被视为结束节点\"\n\n#: apps/application/flow/common.py:226\nmsgid \"The starting node is required\"\nmsgstr \"开始节点是必需的\"\n\n#: apps/application/flow/common.py:228\nmsgid \"There can only be one starting node\"\nmsgstr \"只能有一个开始节点\"\n\n#: apps/application/flow/common.py:236\nmsgid \"The node {node} model does not exist\"\nmsgstr \"节点 {node} 模型不存在\"\n\n#: apps/application/flow/common.py:246\n#, python-brace-format\nmsgid \"Node {node} is unavailable\"\nmsgstr \"节点 {node} 不可用\"\n\n#: apps/application/flow/common.py:252\n#, python-brace-format\nmsgid \"The library ID of node {node} cannot be empty\"\nmsgstr \"工具库 ID {node} 不能为空\"\n\n#: apps/application/flow/common.py:255\n#, python-brace-format\nmsgid \"The function library for node {node} is not available\"\nmsgstr \"工具库 {node} 不可用\"\n\n#: apps/application/flow/common.py:261\nmsgid \"Basic information node is required\"\nmsgstr \"基本信息节点是必需的\"\n\n#: apps/application/flow/common.py:263\nmsgid \"There can only be one basic information node\"\nmsgstr \"只能有一个基本信息节点\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:20\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:15\n#: apps/application/flow/step_node/question_node/i_question_node.py:20\n#: apps/users/api/user.py:35 apps/users/api/user.py:102\nmsgid \"Role Setting\"\nmsgstr \"角色设置\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:26\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:25\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:30\n#: apps/application/flow/step_node/function_node/i_function_node.py:48\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:26\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:23\n#: apps/application/flow/step_node/question_node/i_question_node.py:27\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:15\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:16\nmsgid \"Whether to return content\"\nmsgstr \"是否返回内容\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:33\nmsgid \"Context Type\"\nmsgstr \"上下文类型\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:35\nmsgid \"Whether to enable MCP\"\nmsgstr \"是否启用 MCP\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:36\nmsgid \"MCP Server\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:12\n#: apps/application/serializers/application.py:539\n#: apps/application/serializers/application_access_token.py:44\n#: apps/application/serializers/application_chat.py:38\n#: apps/application/serializers/application_chat.py:54\n#: apps/application/serializers/application_chat_record.py:43\n#: apps/application/serializers/application_chat_record.py:75\n#: apps/application/serializers/application_stats.py:35\n#: apps/application/serializers/application_version.py:21\n#: apps/application/serializers/application_version.py:67\n#: apps/chat/serializers/chat.py:118\n#: apps/chat/serializers/chat_authentication.py:80\n#: apps/xpack/api/application_setting.py:31 apps/xpack/api/platform.py:29\n#: apps/xpack/api/platform.py:70\nmsgid \"Application ID\"\nmsgstr \"智能体 ID\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:15\nmsgid \"API Input Fields\"\nmsgstr \"API 输入字段\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:17\nmsgid \"User Input Fields\"\nmsgstr \"用户输入字段\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:18\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:25\n#: apps/chat/serializers/chat.py:57 apps/tools/serializers/tool.py:391\nmsgid \"picture\"\nmsgstr \"图片\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:19\n#: apps/application/flow/step_node/document_extract_node/i_document_extract_node.py:12\n#: apps/chat/serializers/chat.py:58\nmsgid \"document\"\nmsgstr \"文档\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:20\n#: apps/chat/serializers/chat.py:59\nmsgid \"Audio\"\nmsgstr \"音频\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:22\n#: apps/chat/serializers/chat.py:62\nmsgid \"Child Nodes\"\nmsgstr \"子节点\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:23\n#: apps/application/flow/step_node/form_node/i_form_node.py:21\nmsgid \"Form Data\"\nmsgstr \"表单数据\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:57\nmsgid \"\"\n\"Parameter value error: The uploaded document lacks file_id, and the document \"\n\"upload fails\"\nmsgstr \"参数值错误：上传的文档缺少 file_id，文档上传失败\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:66\nmsgid \"\"\n\"Parameter value error: The uploaded image lacks file_id, and the image \"\n\"upload fails\"\nmsgstr \"参数值错误：上传的图片缺少 file_id，图片上传失败\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:76\nmsgid \"\"\n\"Parameter value error: The uploaded audio lacks file_id, and the audio \"\n\"upload fails.\"\nmsgstr \"参数值错误：上传的音频缺少 file_id，音频上传失败\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:19\n#: apps/models_provider/api/provide.py:24\nmsgid \"value\"\nmsgstr \"值\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:20\nmsgid \"Fields\"\nmsgstr \"字段\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:24\nmsgid \"Branch id\"\nmsgstr \"分支 ID\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:25\nmsgid \"Branch Type\"\nmsgstr \"分支类型\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:26\nmsgid \"Condition or|and\"\nmsgstr \"条件 或|与\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:20\nmsgid \"Response Type\"\nmsgstr \"响应类型\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:21\n#: apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py:13\nmsgid \"Reference Field\"\nmsgstr \"引用字段\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:23\nmsgid \"Direct answer content\"\nmsgstr \"直接回答内容\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:31\nmsgid \"Reference field cannot be empty\"\nmsgstr \"引用字段不能为空\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:33\nmsgid \"Reference field error\"\nmsgstr \"引用字段错误\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:36\nmsgid \"Content cannot be empty\"\nmsgstr \"内容不能为空\"\n\n#: apps/application/flow/step_node/form_node/i_form_node.py:19\nmsgid \"Form Configuration\"\nmsgstr \"表单配置\"\n\n#: apps/application/flow/step_node/form_node/i_form_node.py:20\nmsgid \"Form output content\"\nmsgstr \"表单输出内容\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:22\n#: apps/application/flow/step_node/function_node/i_function_node.py:24\nmsgid \"Variable Name\"\nmsgstr \"变量名称\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:23\n#: apps/application/flow/step_node/function_node/i_function_node.py:34\nmsgid \"Variable Value\"\nmsgstr \"变量名称\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:27\nmsgid \"Library ID\"\nmsgstr \"工具 ID\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:36\nmsgid \"The function has been deleted\"\nmsgstr \"工具已被删除\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:43\nmsgid \"Field: {name} No value set\"\nmsgstr \"字段：{name} 未设置值\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:59\nmsgid \"Field: {name} Type: {_type} Value: {value} Unsupported types\"\nmsgstr \"字段：{name} 类型：{_type} 值：{value} 类型转换错误\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:63\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:100\nmsgid \"Field: {name} Type: {_type} Value: {value} Type error\"\nmsgstr \"字段：{name} 类型：{_type} 值：{value} 类型转换错误\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:91\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:96\n#: apps/tools/serializers/tool.py:254 apps/tools/serializers/tool.py:259\nmsgid \"type error\"\nmsgstr \"类型错误\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:106\n#: apps/tools/serializers/tool.py:398\nmsgid \"Function does not exist\"\nmsgstr \"工具不存在\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:108\nmsgid \"No permission to use this function {name}\"\nmsgstr \"无权限使用此工具 {name}\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:110\n#, python-brace-format\nmsgid \"Function {name} is unavailable\"\nmsgstr \"工具 {name} 不可用\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:25\nmsgid \"Is this field required\"\nmsgstr \"{keys} 是必填项\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:26\n#: apps/knowledge/serializers/document.py:203\n#: apps/tools/serializers/tool.py:120\nmsgid \"type\"\nmsgstr \"类型\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:28\nmsgid \"The field only supports string|int|dict|array|float\"\nmsgstr \"字段仅支持字符串|整数|字典|数组|浮点数\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:30\n#: apps/folders/serializers/folder.py:106\n#: apps/folders/serializers/folder.py:141\n#: apps/folders/serializers/folder.py:196 apps/tools/serializers/tool.py:124\nmsgid \"source\"\nmsgstr \"来源\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:32\n#: apps/tools/serializers/tool.py:126\nmsgid \"The field only supports custom|reference\"\nmsgstr \"字段仅支持自定义|引用\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:40\nmsgid \"{field}, this field is required.\"\nmsgstr \"{field_label} 字段是必填项\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:46\nmsgid \"function\"\nmsgstr \"工具内容\"\n\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:14\nmsgid \"Prompt word (positive)\"\nmsgstr \"提示词 (正向)\"\n\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:16\nmsgid \"Prompt word (negative)\"\nmsgstr \"提示词 (负向)\"\n\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:23\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:20\nmsgid \"Conversation storage type\"\nmsgstr \"对话存储类型\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:13\nmsgid \"Mcp servers\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:16\nmsgid \"Mcp server\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:18\nmsgid \"Mcp tool\"\nmsgstr \"Mcp 工具\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:21\nmsgid \"Tool parameters\"\nmsgstr \"工具参数\"\n\n#: apps/application/flow/step_node/reranker_node/i_reranker_node.py:26\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:33\nmsgid \"Maximum number of words in a quoted segment\"\nmsgstr \"引用段落的最大字数\"\n\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:27\n#: apps/knowledge/serializers/knowledge.py:97\n#: apps/knowledge/serializers/knowledge.py:640\nmsgid \"similarity\"\nmsgstr \"相似度\"\n\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:18\nmsgid \"The audio file cannot be empty\"\nmsgstr \"音频文件不能为空\"\n\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:33\nmsgid \"\"\n\"Parameter value error: The uploaded audio lacks file_id, and the audio \"\n\"upload fails\"\nmsgstr \"参数错误：上传的音频缺少 file_id，音频上传失败\"\n\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:18\nmsgid \"Text content\"\nmsgstr \"文本内容\"\n\n#: apps/application/models/application_chat.py:79\n#: apps/xpack/serializers/channel/chat_manage.py:94\n#: apps/xpack/serializers/channel/chat_manage.py:152\nmsgid \"\"\n\"Sorry, no relevant content was found. Please re-describe your problem or \"\n\"provide more information. \"\nmsgstr \"不好意思，没有找到相关内容。请重新描述您的问题或提供更多信息。\"\n\n#: apps/application/serializers/application.py:78\nmsgid \"No reference status\"\nmsgstr \"无参考状态\"\n\n#: apps/application/serializers/application.py:86\nmsgid \"Acquaintance\"\nmsgstr \"相似度\"\n\n#: apps/application/serializers/application.py:88\nmsgid \"Maximum number of quoted characters\"\nmsgstr \"引用字符的最大数量\"\n\n#: apps/application/serializers/application.py:95\nmsgid \"Segment settings not referenced\"\nmsgstr \"引用段设置未引用\"\n\n#: apps/application/serializers/application.py:104\n#: apps/application/serializers/application_chat_record.py:176\n#: apps/application/serializers/application_chat_record.py:252\n#: apps/application/serializers/application_chat_record.py:317\nmsgid \"Knowledge base id\"\nmsgstr \"知识库\"\n\n#: apps/application/serializers/application.py:105\nmsgid \"Knowledge Base List\"\nmsgstr \"知识库列表\"\n\n#: apps/application/serializers/application.py:119\nmsgid \"The knowledge base id does not exist\"\nmsgstr \"知识库 ID 不存在\"\n\n#: apps/application/serializers/application.py:126\nmsgid \"Role prompts\"\nmsgstr \"角色提示\"\n\n#: apps/application/serializers/application.py:128\nmsgid \"No citation segmentation prompt\"\nmsgstr \"无引用段落提示\"\n\n#: apps/application/serializers/application.py:130\nmsgid \"Thinking process switch\"\nmsgstr \"思考过程开关\"\n\n#: apps/application/serializers/application.py:134\nmsgid \"The thinking process begins to mark\"\nmsgstr \"思考过程开始标记\"\n\n#: apps/application/serializers/application.py:138\nmsgid \"End of thinking process marker\"\nmsgstr \"思考过程结束标记\"\n\n#: apps/application/serializers/application.py:155\n#: apps/application/serializers/application.py:203\n#: apps/application/serializers/application.py:378\nmsgid \"Opening remarks\"\nmsgstr \"开始提示\"\n\n#: apps/application/serializers/application.py:191\nmsgid \"application name\"\nmsgstr \"智能体名称\"\n\n#: apps/application/serializers/application.py:194\nmsgid \"application describe\"\nmsgstr \"智能体描述\"\n\n#: apps/application/serializers/application.py:197\n#: apps/application/serializers/application.py:372\n#: apps/common/constants/permission_constants.py:226\n#: apps/common/constants/permission_constants.py:259\n#: apps/common/constants/permission_constants.py:264\n#: apps/models_provider/views/model.py:63\n#: apps/models_provider/views/model.py:95\n#: apps/models_provider/views/model.py:113\n#: apps/models_provider/views/model.py:130\n#: apps/models_provider/views/model.py:145\n#: apps/models_provider/views/model.py:160\n#: apps/models_provider/views/model.py:173\n#: apps/models_provider/views/model.py:194\n#: apps/models_provider/views/model.py:210\n#: apps/models_provider/views/model_apply.py:29\n#: apps/models_provider/views/model_apply.py:41\n#: apps/models_provider/views/model_apply.py:53\n#: apps/models_provider/views/provide.py:25\n#: apps/models_provider/views/provide.py:48\n#: apps/models_provider/views/provide.py:62\n#: apps/models_provider/views/provide.py:80\n#: apps/models_provider/views/provide.py:97\nmsgid \"Model\"\nmsgstr \"模型\"\n\n#: apps/application/serializers/application.py:201\n#: apps/application/serializers/application.py:376\nmsgid \"Historical chat records\"\nmsgstr \"历史聊天记录\"\n\n#: apps/application/serializers/application.py:206\n#: apps/application/serializers/application.py:380\nmsgid \"Related Knowledge Base\"\nmsgstr \"相关知识库\"\n\n#: apps/application/serializers/application.py:213\n#: apps/application/serializers/application.py:390\nmsgid \"Question completion\"\nmsgstr \"问题完成\"\n\n#: apps/application/serializers/application.py:217\nmsgid \"Application Type\"\nmsgstr \"智能体类型\"\n\n#: apps/application/serializers/application.py:221\nmsgid \"Application type only supports SIMPLE|WORK_FLOW\"\nmsgstr \"智能体类型仅支持 SIMPLE|WORK_FLOW\"\n\n#: apps/application/serializers/application.py:226\n#: apps/application/serializers/application.py:394\nmsgid \"Model parameters\"\nmsgstr \"模型参数\"\n\n#: apps/application/serializers/application.py:228\n#: apps/application/serializers/application.py:396\nmsgid \"Voice playback enabled\"\nmsgstr \"开启语音播放\"\n\n#: apps/application/serializers/application.py:230\n#: apps/application/serializers/application.py:398\nmsgid \"Voice playback model ID\"\nmsgstr \"语音播放模型 ID\"\n\n#: apps/application/serializers/application.py:232\n#: apps/application/serializers/application.py:400\nmsgid \"Voice playback type\"\nmsgstr \"语音播放类型\"\n\n#: apps/application/serializers/application.py:234\n#: apps/application/serializers/application.py:402\nmsgid \"Voice playback autoplay\"\nmsgstr \"自动播放语音\"\n\n#: apps/application/serializers/application.py:236\n#: apps/application/serializers/application.py:404\nmsgid \"Voice recognition enabled\"\nmsgstr \"开启语音识别\"\n\n#: apps/application/serializers/application.py:238\n#: apps/application/serializers/application.py:406\nmsgid \"Speech recognition model ID\"\nmsgstr \"语音识别模型 ID\"\n\n#: apps/application/serializers/application.py:240\n#: apps/application/serializers/application.py:408\nmsgid \"Voice recognition automatic transmission\"\nmsgstr \"语音识别自动播放\"\n\n#: apps/application/serializers/application.py:281\nmsgid \"Primary key id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:286\nmsgid \"Application type\"\nmsgstr \"智能体类型\"\n\n#: apps/application/serializers/application.py:287\n#: apps/xpack/serializers/resource_chat_user.py:34\n#: apps/xpack/serializers/resource_chat_user.py:110\n#: apps/xpack/serializers/resource_chat_user_group.py:17\n#: apps/xpack/serializers/resource_chat_user_group.py:85\nmsgid \"Resource type\"\nmsgstr \"资源类型\"\n\n#: apps/application/serializers/application.py:288\nmsgid \"Affiliation user\"\nmsgstr \"关联用户\"\n\n#: apps/application/serializers/application.py:289\nmsgid \"Creation time\"\nmsgstr \"创建时间\"\n\n#: apps/application/serializers/application.py:290\nmsgid \"Modification time\"\nmsgstr \"修改时间\"\n\n#: apps/application/serializers/application.py:294\n#: apps/application/serializers/application_chat_record.py:42\n#: apps/application/serializers/application_chat_record.py:323\n#: apps/application/serializers/application_version.py:40\n#: apps/chat/serializers/chat.py:318 apps/role_setting/api/role_setting.py:147\n#: apps/users/api/user.py:64 apps/users/api/user.py:170\n#: apps/workspace/api/workspace.py:46 apps/workspace/api/workspace.py:66\n#: apps/workspace/api/workspace.py:85 apps/workspace/api/workspace.py:103\n#: apps/workspace/serializers/workspace_serializers.py:239\n#: apps/xpack/api/application_setting.py:24 apps/xpack/api/knowledge_lark.py:40\n#: apps/xpack/api/knowledge_lark.py:72 apps/xpack/api/knowledge_lark.py:104\n#: apps/xpack/api/platform.py:22 apps/xpack/api/platform.py:63\nmsgid \"Workspace ID\"\nmsgstr \"工作空间 ID\"\n\n#: apps/application/serializers/application.py:363\n#: apps/knowledge/serializers/document.py:157\n#: apps/knowledge/serializers/document.py:162 apps/oss/serializers/file.py:57\n#: apps/tools/serializers/tool.py:356\nmsgid \"file\"\nmsgstr \"文件\"\n\n#: apps/application/serializers/application.py:384\nmsgid \"Dataset settings\"\nmsgstr \"知识库设置\"\n\n#: apps/application/serializers/application.py:387\nmsgid \"Model setup\"\nmsgstr \"模型设置\"\n\n#: apps/application/serializers/application.py:391\nmsgid \"Icon\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:412\n#: apps/application/serializers/application_api_key.py:33\n#: apps/application/serializers/application_api_key.py:64\n#: apps/folders/serializers/folder.py:101\n#: apps/folders/serializers/folder.py:140\n#: apps/folders/serializers/folder.py:195\n#: apps/knowledge/serializers/document.py:253\n#: apps/knowledge/serializers/document.py:347\n#: apps/knowledge/serializers/document.py:408\n#: apps/knowledge/serializers/document.py:502\n#: apps/knowledge/serializers/document.py:736\n#: apps/knowledge/serializers/document.py:888\n#: apps/knowledge/serializers/document.py:963\n#: apps/knowledge/serializers/document.py:983\n#: apps/knowledge/serializers/document.py:1166\n#: apps/knowledge/serializers/knowledge.py:208\n#: apps/knowledge/serializers/knowledge.py:448\n#: apps/knowledge/serializers/knowledge.py:557\n#: apps/knowledge/serializers/knowledge.py:635\n#: apps/knowledge/serializers/paragraph.py:134\n#: apps/knowledge/serializers/paragraph.py:346\n#: apps/knowledge/serializers/paragraph.py:438\n#: apps/knowledge/serializers/paragraph.py:558\n#: apps/knowledge/serializers/problem.py:176\n#: apps/knowledge/serializers/problem.py:204\n#: apps/models_provider/api/model.py:30 apps/models_provider/api/model.py:86\n#: apps/models_provider/api/model.py:99\n#: apps/models_provider/serializers/model_serializer.py:259\n#: apps/models_provider/serializers/model_serializer.py:323\n#: apps/models_provider/serializers/model_serializer.py:392\n#: apps/shared/api/shared_knowledge.py:131\n#: apps/shared/api/shared_knowledge.py:164 apps/shared/api/shared_tool.py:60\n#: apps/shared/api/shared_tool.py:147\n#: apps/shared/serializers/shared_knowledge.py:61\n#: apps/shared/serializers/shared_knowledge.py:109\n#: apps/shared/serializers/shared_knowledge.py:157\n#: apps/shared/serializers/shared_model.py:110\n#: apps/shared/serializers/shared_tool.py:45\n#: apps/shared/serializers/shared_tool.py:86\n#: apps/system_manage/serializers/user_resource_permission.py:74\n#: apps/tools/serializers/tool.py:188 apps/tools/serializers/tool.py:210\n#: apps/tools/serializers/tool.py:268 apps/tools/serializers/tool.py:358\n#: apps/tools/serializers/tool.py:389 apps/tools/serializers/tool.py:425\n#: apps/tools/serializers/tool.py:452\n#: apps/xpack/serializers/dataset_lark_serializer.py:45\n#: apps/xpack/serializers/resource_chat_user.py:33\n#: apps/xpack/serializers/resource_chat_user.py:109\n#: apps/xpack/serializers/resource_chat_user_group.py:16\n#: apps/xpack/serializers/resource_chat_user_group.py:84\nmsgid \"workspace id\"\nmsgstr \"工作空间ID\"\n\n#: apps/application/serializers/application.py:459\nmsgid \"\"\n\"The community version supports up to 5 applications. If you need more \"\n\"applications, please contact us (https://fit2cloud.com/).\"\nmsgstr \"\"\n\"社区版支持最多5个智能体，如需更多智能体，请联系我们（https://fit2cloud.com/）。\"\n\n#: apps/application/serializers/application.py:471\n#: apps/common/handle/impl/qa/zip_parse_qa_handle.py:56\n#: apps/common/handle/impl/text/zip_split_handle.py:69\n#: apps/knowledge/serializers/document.py:864\n#: apps/knowledge/serializers/document.py:871\n#: apps/tools/serializers/tool.py:370\nmsgid \"Unsupported file format\"\nmsgstr \"不支持的文件格式\"\n\n#: apps/application/serializers/application.py:545\nmsgid \"Application id does not exist\"\nmsgstr \"智能体 ID 不存在\"\n\n#: apps/application/serializers/application.py:591\nmsgid \"work_flow is a required field\"\nmsgstr \"工作流是必填字段\"\n\n#: apps/application/serializers/application.py:695\nmsgid \"Unknown knowledge base id {dataset_id}, unable to associate\"\nmsgstr \"未知知识库 ID {dataset_id}，无法关联\"\n\n#: apps/application/serializers/application_access_token.py:24\nmsgid \"Reset Token\"\nmsgstr \"重置令牌\"\n\n#: apps/application/serializers/application_access_token.py:25\nmsgid \"Is it enabled\"\nmsgstr \"是否开启\"\n\n#: apps/application/serializers/application_access_token.py:28\nmsgid \"Number of visits\"\nmsgstr \"访问次数\"\n\n#: apps/application/serializers/application_access_token.py:30\nmsgid \"Whether to enable whitelist\"\nmsgstr \"是否启用白名单\"\n\n#: apps/application/serializers/application_access_token.py:32\n#: apps/application/serializers/application_access_token.py:33\nmsgid \"Whitelist\"\nmsgstr \"白名单\"\n\n#: apps/application/serializers/application_access_token.py:35\nmsgid \"Whether to display knowledge sources\"\nmsgstr \"是否展示知识来源\"\n\n#: apps/application/serializers/application_access_token.py:37\n#: apps/users/serializers/user.py:665\n#: apps/xpack/serializers/application_setting_serializer.py:37\nmsgid \"language\"\nmsgstr \"语言\"\n\n#: apps/application/serializers/application_api_key.py:21\nmsgid \"Availability\"\nmsgstr \"可用\"\n\n#: apps/application/serializers/application_api_key.py:24\nmsgid \"Is cross-domain allowed\"\nmsgstr \"是否允许跨域\"\n\n#: apps/application/serializers/application_api_key.py:28\nmsgid \"Cross-domain address\"\nmsgstr \"跨域地址\"\n\n#: apps/application/serializers/application_api_key.py:29\nmsgid \"Cross-domain list\"\nmsgstr \"跨域列表\"\n\n#: apps/application/serializers/application_api_key.py:34\n#: apps/application/serializers/application_api_key.py:65\n#: apps/knowledge/serializers/knowledge.py:72\n#: apps/xpack/serializers/application_setting_serializer.py:77\n#: apps/xpack/serializers/dataset_lark_serializer.py:295\nmsgid \"application id\"\nmsgstr \"智能体 ID\"\n\n#: apps/application/serializers/application_api_key.py:41\n#: apps/chat/serializers/chat.py:277 apps/chat/serializers/chat.py:332\n#: apps/xpack/serializers/application_setting_serializer.py:103\n#: apps/xpack/serializers/platform_serializer.py:81\n#: apps/xpack/serializers/platform_serializer.py:103\n#: apps/xpack/serializers/platform_serializer.py:138\n#: apps/xpack/serializers/platform_serializer.py:149\nmsgid \"Application does not exist\"\nmsgstr \"智能体不存在\"\n\n#: apps/application/serializers/application_api_key.py:66\nmsgid \"ApiKeyId\"\nmsgstr \"ApiKey ID\"\n\n#: apps/application/serializers/application_api_key.py:87\nmsgid \"APIKey does not exist\"\nmsgstr \"APIKey 不存在\"\n\n#: apps/application/serializers/application_chat.py:33\nmsgid \"chat id\"\nmsgstr \"对话 ID\"\n\n#: apps/application/serializers/application_chat.py:34\n#: apps/application/serializers/application_chat.py:51\n#: apps/application/serializers/application_chat.py:182\n#: apps/application/serializers/application_version.py:23\nmsgid \"summary\"\nmsgstr \"摘要\"\n\n#: apps/application/serializers/application_chat.py:35\nmsgid \"Chat User ID\"\nmsgstr \"对话用户 ID\"\n\n#: apps/application/serializers/application_chat.py:36\nmsgid \"Chat User Type\"\nmsgstr \"对话用户类型\"\n\n#: apps/application/serializers/application_chat.py:37\nmsgid \"Is delete\"\nmsgstr \"删除\"\n\n#: apps/application/serializers/application_chat.py:39\n#: apps/application/serializers/application_stats.py:25\nmsgid \"Number of conversations\"\nmsgstr \"对话数量\"\n\n#: apps/application/serializers/application_chat.py:40\n#: apps/application/serializers/application_stats.py:29\nmsgid \"Number of Likes\"\nmsgstr \"点赞数量\"\n\n#: apps/application/serializers/application_chat.py:41\n#: apps/application/serializers/application_stats.py:31\nmsgid \"Number of thumbs-downs\"\nmsgstr \"点踩数量\"\n\n#: apps/application/serializers/application_chat.py:42\nmsgid \"Number of tags\"\nmsgstr \"标签数量\"\n\n#: apps/application/serializers/application_chat.py:46\nmsgid \"Chat ID List\"\nmsgstr \"对话 ID 列表\"\n\n#: apps/application/serializers/application_chat.py:52\n#: apps/application/serializers/application_stats.py:36\n#: apps/xpack/serializers/operate_log_serializer.py:55\nmsgid \"Start time\"\nmsgstr \"开始时间\"\n\n#: apps/application/serializers/application_chat.py:53\n#: apps/application/serializers/application_stats.py:37\n#: apps/xpack/serializers/operate_log_serializer.py:56\nmsgid \"End time\"\nmsgstr \"结束时间\"\n\n#: apps/application/serializers/application_chat.py:61\nmsgid \"Only supports and|or\"\nmsgstr \"只支持 与|或\"\n\n#: apps/application/serializers/application_chat.py:183\nmsgid \"Problem after optimization\"\nmsgstr \"优化后的问题\"\n\n#: apps/application/serializers/application_chat.py:184\nmsgid \"answer\"\nmsgstr \"回答\"\n\n#: apps/application/serializers/application_chat.py:184\nmsgid \"User feedback\"\nmsgstr \"用户反馈\"\n\n#: apps/application/serializers/application_chat.py:186\nmsgid \"Section title + content\"\nmsgstr \"分段标题 + 内容\"\n\n#: apps/application/serializers/application_chat.py:187\n#: apps/application/views/application_chat_record.py:139\n#: apps/application/views/application_chat_record.py:140\n#: apps/application/views/application_chat_record.py:141\n#: apps/common/constants/permission_constants.py:248\nmsgid \"Annotation\"\nmsgstr \"标注\"\n\n#: apps/application/serializers/application_chat.py:187\nmsgid \"Consuming tokens\"\nmsgstr \"消耗的令牌\"\n\n#: apps/application/serializers/application_chat.py:188\nmsgid \"Time consumed (s)\"\nmsgstr \"耗时 (s)\"\n\n#: apps/application/serializers/application_chat.py:189\nmsgid \"Question Time\"\nmsgstr \"提问时间\"\n\n#: apps/application/serializers/application_chat_record.py:44\n#: apps/application/serializers/application_chat_record.py:143\n#: apps/application/serializers/application_chat_record.py:250\n#: apps/application/serializers/application_chat_record.py:315\n#: apps/chat/serializers/chat.py:45\nmsgid \"Conversation record id\"\nmsgstr \"对话记录 ID\"\n\n#: apps/application/serializers/application_chat_record.py:51\nmsgid \"Application authentication information does not exist\"\nmsgstr \"智能体认证信息不存在\"\n\n#: apps/application/serializers/application_chat_record.py:53\nmsgid \"Displaying knowledge sources is not enabled\"\nmsgstr \"知识库来源展示未开启\"\n\n#: apps/application/serializers/application_chat_record.py:70\n#: apps/chat/serializers/chat.py:127 apps/chat/serializers/chat.py:274\nmsgid \"Conversation does not exist\"\nmsgstr \"对话不存在\"\n\n#: apps/application/serializers/application_chat_record.py:152\n#: apps/application/serializers/application_chat_record.py:279\n#: apps/application/serializers/application_chat_record.py:336\n#: apps/chat/serializers/chat.py:205\nmsgid \"Conversation record does not exist\"\nmsgstr \"对话记录不存在\"\n\n#: apps/application/serializers/application_chat_record.py:168\nmsgid \"Section title\"\nmsgstr \"章节标题\"\n\n#: apps/application/serializers/application_chat_record.py:169\nmsgid \"Paragraph content\"\nmsgstr \"段落内容\"\n\n#: apps/application/serializers/application_chat_record.py:177\n#: apps/application/serializers/application_chat_record.py:254\n#: apps/application/serializers/application_chat_record.py:319\nmsgid \"Document id\"\nmsgstr \"文档 ID\"\n\n#: apps/application/serializers/application_chat_record.py:184\n#: apps/application/serializers/application_chat_record.py:260\n#: apps/knowledge/serializers/paragraph.py:246\nmsgid \"The document id is incorrect\"\nmsgstr \"文档 ID 不正确\"\n\n#: apps/application/serializers/application_chat_record.py:203\nmsgid \"Conversation records that do not exist\"\nmsgstr \"对话记录不存在\"\n\n#: apps/application/serializers/application_chat_record.py:321\nmsgid \"Paragraph id\"\nmsgstr \"段落 ID\"\n\n#: apps/application/serializers/application_chat_record.py:340\n#, python-brace-format\nmsgid \"\"\n\"The paragraph id is wrong. The current conversation record does not exist. \"\n\"[{paragraph_id}] paragraph id\"\nmsgstr \"段落 ID 错误。当前对话记录不存在。[{paragraph_id}] 段落 ID\"\n\n#: apps/application/serializers/application_stats.py:26\nmsgid \"Number of new users\"\nmsgstr \"新用户数量\"\n\n#: apps/application/serializers/application_stats.py:27\nmsgid \"Total number of users\"\nmsgstr \"总用户数\"\n\n#: apps/application/serializers/application_stats.py:28\nmsgid \"date\"\nmsgstr \"日期\"\n\n#: apps/application/serializers/application_stats.py:30\nmsgid \"Tokens consumption\"\nmsgstr \"消耗的令牌\"\n\n#: apps/application/serializers/application_version.py:36\nmsgid \"Version Name\"\nmsgstr \"版本名称\"\n\n#: apps/application/serializers/application_version.py:69\nmsgid \"Workflow version id\"\nmsgstr \"工作流版本 ID\"\n\n#: apps/application/serializers/application_version.py:79\n#: apps/application/serializers/application_version.py:94\nmsgid \"Workflow version does not exist\"\nmsgstr \"工作流版本不存在\"\n\n#: apps/application/views/application.py:41\n#: apps/application/views/application.py:42\n#: apps/application/views/application.py:43\nmsgid \"Create an application\"\nmsgstr \"创建一个智能体程序\"\n\n#: apps/application/views/application.py:47\n#: apps/application/views/application.py:65\n#: apps/application/views/application.py:82\n#: apps/application/views/application.py:103\n#: apps/application/views/application.py:123\n#: apps/application/views/application.py:145\n#: apps/application/views/application.py:166\n#: apps/application/views/application.py:187\n#: apps/application/views/application.py:206\n#: apps/application/views/application_access_token.py:32\n#: apps/application/views/application_access_token.py:47\n#: apps/application/views/application_chat.py:102\n#: apps/application/views/application_chat.py:122\n#: apps/application/views/application_stats.py:33\n#: apps/common/constants/permission_constants.py:224\n#: apps/common/constants/permission_constants.py:234\n#: apps/xpack/views/application_setting.py:29\n#: apps/xpack/views/application_setting.py:47\nmsgid \"Application\"\nmsgstr \"智能体\"\n\n#: apps/application/views/application.py:60\n#: apps/application/views/application.py:61\n#: apps/application/views/application.py:62\nmsgid \"Get the application list\"\nmsgstr \"获取智能体列表\"\n\n#: apps/application/views/application.py:77\n#: apps/application/views/application.py:78\n#: apps/application/views/application.py:79\nmsgid \"Get the application list by page\"\nmsgstr \"分页获取智能体列表\"\n\n#: apps/application/views/application.py:97\n#: apps/application/views/application.py:98\n#: apps/application/views/application.py:99\nmsgid \"Import Application\"\nmsgstr \"导入智能体\"\n\n#: apps/application/views/application.py:117\n#: apps/application/views/application.py:118\n#: apps/application/views/application.py:119\nmsgid \"Export application\"\nmsgstr \"导出智能体\"\n\n#: apps/application/views/application.py:140\n#: apps/application/views/application.py:141\n#: apps/application/views/application.py:142\nmsgid \"Deleting application\"\nmsgstr \"删除智能体\"\n\n#: apps/application/views/application.py:160\n#: apps/application/views/application.py:161\n#: apps/application/views/application.py:162\nmsgid \"Modify the application\"\nmsgstr \"修改智能体\"\n\n#: apps/application/views/application.py:181\n#: apps/application/views/application.py:182\n#: apps/application/views/application.py:183\nmsgid \"Get application details\"\nmsgstr \"获取智能体详情\"\n\n#: apps/application/views/application.py:200\n#: apps/application/views/application.py:201\n#: apps/application/views/application.py:202\nmsgid \"Publishing an application\"\nmsgstr \"发布智能体\"\n\n#: apps/application/views/application_access_token.py:27\n#: apps/application/views/application_access_token.py:28\n#: apps/application/views/application_access_token.py:29\nmsgid \"Modify application access restriction information\"\nmsgstr \"修改智能体访问限制信息\"\n\n#: apps/application/views/application_access_token.py:43\n#: apps/application/views/application_access_token.py:44\n#: apps/application/views/application_access_token.py:45\nmsgid \"Get application access restriction information\"\nmsgstr \"获取智能体访问限制信息\"\n\n#: apps/application/views/application_api_key.py:31\n#: apps/application/views/application_api_key.py:32\n#: apps/application/views/application_api_key.py:33\nmsgid \"Create application ApiKey\"\nmsgstr \"创建智能体 API 密钥\"\n\n#: apps/application/views/application_api_key.py:37\n#: apps/application/views/application_api_key.py:57\n#: apps/application/views/application_api_key.py:77\n#: apps/application/views/application_api_key.py:99\nmsgid \"Application Api Key\"\nmsgstr \"智能体 API 密钥\"\n\n#: apps/application/views/application_api_key.py:52\nmsgid \"GET application ApiKey List\"\nmsgstr \"获取智能体的 API 密钥列表\"\n\n#: apps/application/views/application_api_key.py:53\n#: apps/application/views/application_api_key.py:54\nmsgid \"Create application ApiKey List\"\nmsgstr \"创建智能体 API 密钥列表\"\n\n#: apps/application/views/application_api_key.py:71\n#: apps/application/views/application_api_key.py:72\n#: apps/application/views/application_api_key.py:73\nmsgid \"Modify application API_KEY\"\nmsgstr \"修改智能体 API 密钥\"\n\n#: apps/application/views/application_api_key.py:93\n#: apps/application/views/application_api_key.py:94\n#: apps/application/views/application_api_key.py:95\nmsgid \"Delete Application API_KEY\"\nmsgstr \"删除智能体 API 密钥\"\n\n#: apps/application/views/application_chat.py:35\n#: apps/application/views/application_chat.py:36\n#: apps/application/views/application_chat.py:37\nmsgid \"Get the conversation list\"\nmsgstr \"获取对话列表\"\n\n#: apps/application/views/application_chat.py:41\n#: apps/application/views/application_chat.py:61\n#: apps/application/views/application_chat.py:82\n#: apps/application/views/application_chat_record.py:37\n#: apps/application/views/application_chat_record.py:58\n#: apps/application/views/application_chat_record.py:82\n#: apps/application/views/application_chat_record.py:106\n#: apps/application/views/application_chat_record.py:125\n#: apps/application/views/application_chat_record.py:145\n#: apps/application/views/application_chat_record.py:171\nmsgid \"Application/Conversation Log\"\nmsgstr \"智能体/对话日志\"\n\n#: apps/application/views/application_chat.py:55\n#: apps/application/views/application_chat.py:56\n#: apps/application/views/application_chat.py:57\nmsgid \"Get the conversation list by page\"\nmsgstr \"分页获取对话列表\"\n\n#: apps/application/views/application_chat.py:76\n#: apps/application/views/application_chat.py:77\n#: apps/application/views/application_chat.py:78\nmsgid \"Export conversation\"\nmsgstr \"导出对话\"\n\n#: apps/application/views/application_chat.py:97\n#: apps/application/views/application_chat.py:98\n#: apps/application/views/application_chat.py:99\nmsgid \"Get a temporary session id based on the application id\"\nmsgstr \"获取智能体的临时会话 ID\"\n\n#: apps/application/views/application_chat.py:116\n#: apps/application/views/application_chat.py:117\n#: apps/application/views/application_chat.py:118 apps/chat/views/chat.py:93\n#: apps/chat/views/chat.py:94 apps/chat/views/chat.py:95\nmsgid \"dialogue\"\nmsgstr \"对话\"\n\n#: apps/application/views/application_chat_record.py:31\n#: apps/application/views/application_chat_record.py:32\n#: apps/application/views/application_chat_record.py:33\nmsgid \"Get the conversation record list\"\nmsgstr \"获取对话记录列表\"\n\n#: apps/application/views/application_chat_record.py:52\n#: apps/application/views/application_chat_record.py:53\n#: apps/application/views/application_chat_record.py:54\nmsgid \"Get the conversation record list by page\"\nmsgstr \"分页获取对话记录列表\"\n\n#: apps/application/views/application_chat_record.py:76\n#: apps/application/views/application_chat_record.py:77\n#: apps/application/views/application_chat_record.py:78\nmsgid \"Get conversation record details\"\nmsgstr \"获取对话记录详情\"\n\n#: apps/application/views/application_chat_record.py:100\n#: apps/application/views/application_chat_record.py:101\n#: apps/application/views/application_chat_record.py:102\nmsgid \"Add to Knowledge Base\"\nmsgstr \"添加到知识库\"\n\n#: apps/application/views/application_chat_record.py:119\n#: apps/application/views/application_chat_record.py:120\n#: apps/application/views/application_chat_record.py:121\nmsgid \"Get the list of marked paragraphs\"\nmsgstr \"获取标记段落列表\"\n\n#: apps/application/views/application_chat_record.py:165\n#: apps/application/views/application_chat_record.py:166\n#: apps/application/views/application_chat_record.py:167\nmsgid \"Delete a Annotation\"\nmsgstr \"删除注释\"\n\n#: apps/application/views/application_stats.py:28\n#: apps/application/views/application_stats.py:29\n#: apps/application/views/application_stats.py:30\nmsgid \"Dialogue-related statistical trends\"\nmsgstr \"与对话有关的统计趋势\"\n\n#: apps/application/views/application_version.py:30\n#: apps/application/views/application_version.py:31\n#: apps/application/views/application_version.py:32\nmsgid \"Get the application version list\"\nmsgstr \"获取智能体版本列表\"\n\n#: apps/application/views/application_version.py:35\n#: apps/application/views/application_version.py:55\n#: apps/application/views/application_version.py:76\n#: apps/application/views/application_version.py:94\nmsgid \"Application/Version\"\nmsgstr \"智能体/ 版本\"\n\n#: apps/application/views/application_version.py:50\n#: apps/application/views/application_version.py:51\n#: apps/application/views/application_version.py:52\nmsgid \"Get the list of application versions by page\"\nmsgstr \"分页获取智能体版本列表\"\n\n#: apps/application/views/application_version.py:71\n#: apps/application/views/application_version.py:72\n#: apps/application/views/application_version.py:73\nmsgid \"Get application version details\"\nmsgstr \"获取智能体版本详情\"\n\n#: apps/application/views/application_version.py:88\n#: apps/application/views/application_version.py:89\n#: apps/application/views/application_version.py:90\nmsgid \"Modify application version information\"\nmsgstr \"修改智能体版本信息\"\n\n#: apps/chat/api/chat_authentication_api.py:38\n#: apps/chat/serializers/chat_authentication.py:28\n#: apps/chat/serializers/chat_authentication.py:54\n#: apps/xpack/serializers/chat_auth.py:25\nmsgid \"access_token\"\nmsgstr \"\"\n\n#: apps/chat/api/chat_embed_api.py:24\nmsgid \"host\"\nmsgstr \"\"\n\n#: apps/chat/api/chat_embed_api.py:31\n#: apps/chat/serializers/chat_embed_serializers.py:25\nmsgid \"protocol\"\nmsgstr \"协议\"\n\n#: apps/chat/api/chat_embed_api.py:38\n#: apps/chat/serializers/chat_embed_serializers.py:26\n#: apps/users/serializers/login.py:36\nmsgid \"token\"\nmsgstr \"令牌\"\n\n#: apps/chat/serializers/chat.py:42\nmsgid \"Is the answer in streaming mode\"\nmsgstr \"是否流式回答\"\n\n#: apps/chat/serializers/chat.py:43\nmsgid \"Do you want to reply again\"\nmsgstr \"是否重新回复\"\n\n#: apps/chat/serializers/chat.py:48\nmsgid \"Node id\"\nmsgstr \"节点 ID\"\n\n#: apps/chat/serializers/chat.py:51\nmsgid \"Runtime node id\"\nmsgstr \"运行时节点 ID\"\n\n#: apps/chat/serializers/chat.py:54\nmsgid \"Node parameters\"\nmsgstr \"节点参数\"\n\n#: apps/chat/serializers/chat.py:56\nmsgid \"Global variables\"\nmsgstr \"全局变量\"\n\n#: apps/chat/serializers/chat.py:60\n#: apps/common/constants/permission_constants.py:222\n#: apps/common/constants/permission_constants.py:228\nmsgid \"Other\"\nmsgstr \"其他\"\n\n#: apps/chat/serializers/chat.py:115 apps/chat/serializers/chat.py:320\nmsgid \"Client id\"\nmsgstr \"客户端 ID\"\n\n#: apps/chat/serializers/chat.py:116 apps/chat/serializers/chat.py:321\nmsgid \"Client Type\"\nmsgstr \"客户端类型\"\n\n#: apps/chat/serializers/chat.py:119 apps/chat/serializers/chat.py:322\n#: apps/common/constants/permission_constants.py:240\nmsgid \"Debug\"\nmsgstr \"调试\"\n\n#: apps/chat/serializers/chat.py:146\nmsgid \"The number of visits exceeds today's visits\"\nmsgstr \"今天的访问次数超过限制\"\n\n#: apps/chat/serializers/chat.py:157\nmsgid \"The current model is not available\"\nmsgstr \"当前模型不可用\"\n\n#: apps/chat/serializers/chat.py:159\nmsgid \"The model is downloading, please try again later\"\nmsgstr \"下载过程被中断，请重试\"\n\n#: apps/chat/serializers/chat.py:306 apps/chat/serializers/chat.py:357\nmsgid \"The application has not been published. Please use it after publishing.\"\nmsgstr \"智能体未发布，请发布后使用。\"\n\n#: apps/chat/serializers/chat_authentication.py:50\n#: apps/xpack/serializers/chat_auth.py:53\nmsgid \"Invalid access_token\"\nmsgstr \"access_token 无效\"\n\n#: apps/chat/serializers/chat_authentication.py:89\nmsgid \"Illegal User\"\nmsgstr \"非法用户\"\n\n#: apps/chat/serializers/chat_embed_serializers.py:24\nmsgid \"Host\"\nmsgstr \"\"\n\n#: apps/chat/views/chat.py:37 apps/chat/views/chat.py:38\n#: apps/chat/views/chat.py:39\nmsgid \"Application Anonymous Certification\"\nmsgstr \"智能体匿名认证\"\n\n#: apps/chat/views/chat.py:42 apps/chat/views/chat.py:64\n#: apps/chat/views/chat.py:81 apps/chat/views/chat.py:99\n#: apps/chat/views/chat.py:120 apps/chat/views/chat_embed.py:27\n#: apps/xpack/views/chat_user_auth.py:419\nmsgid \"Chat\"\nmsgstr \"对话\"\n\n#: apps/chat/views/chat.py:59 apps/chat/views/chat.py:60\n#: apps/chat/views/chat.py:61\nmsgid \"Get application related information\"\nmsgstr \"获取智能体相关信息\"\n\n#: apps/chat/views/chat.py:76 apps/chat/views/chat.py:77\n#: apps/chat/views/chat.py:78\nmsgid \"Get application authentication information\"\nmsgstr \"获取智能体认证信息\"\n\n#: apps/chat/views/chat.py:115 apps/chat/views/chat.py:116\n#: apps/chat/views/chat.py:117\nmsgid \"Get the session id according to the application id\"\nmsgstr \"根据智能体 ID 获取会话 ID\"\n\n#: apps/chat/views/chat.py:131 apps/chat/views/chat.py:132\n#: apps/chat/views/chat.py:133 apps/users/views/login.py:70\n#: apps/users/views/login.py:71 apps/users/views/login.py:72\nmsgid \"Get captcha\"\nmsgstr \"获取验证码\"\n\n#: apps/chat/views/chat.py:134\n#: apps/common/constants/permission_constants.py:210\n#: apps/users/views/login.py:41 apps/users/views/login.py:58\n#: apps/users/views/login.py:73 apps/users/views/user.py:63\n#: apps/users/views/user.py:77 apps/users/views/user.py:91\n#: apps/users/views/user.py:108 apps/users/views/user.py:123\n#: apps/users/views/user.py:136 apps/users/views/user.py:150\n#: apps/users/views/user.py:164 apps/users/views/user.py:180\n#: apps/users/views/user.py:193 apps/users/views/user.py:206\n#: apps/users/views/user.py:217 apps/users/views/user.py:235\n#: apps/users/views/user.py:251 apps/users/views/user.py:269\n#: apps/users/views/user.py:286 apps/users/views/user.py:303\n#: apps/users/views/user.py:321 apps/users/views/user.py:338\n#: apps/users/views/user.py:356 apps/xpack/views/chat_user_auth.py:206\nmsgid \"User Management\"\nmsgstr \"用户管理\"\n\n#: apps/chat/views/chat_embed.py:22 apps/chat/views/chat_embed.py:23\n#: apps/chat/views/chat_embed.py:24\nmsgid \"Get embedded js\"\nmsgstr \"获取嵌入式 JavaScript\"\n\n#: apps/common/auth/authenticate.py:80\nmsgid \"Not logged in, please log in first\"\nmsgstr \"未登录，请先登录\"\n\n#: apps/common/auth/authenticate.py:82 apps/common/auth/authenticate.py:89\n#: apps/common/auth/authenticate.py:95\nmsgid \"Authentication information is incorrect! illegal user\"\nmsgstr \"身份验证信息不正确！非法用户\"\n\n#: apps/common/auth/authentication.py:98\nmsgid \"No permission to access\"\nmsgstr \"无权限访问\"\n\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:39\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:41\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:43\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:49\n#: apps/xpack/auth/chat_user_token.py:41 apps/xpack/auth/chat_user_token.py:43\n#: apps/xpack/auth/chat_user_token.py:45 apps/xpack/auth/chat_user_token.py:49\nmsgid \"Authentication information is incorrect\"\nmsgstr \"身份验证信息不正确\"\n\n#: apps/common/auth/handle/impl/user_token.py:265\nmsgid \"Login expired\"\nmsgstr \"登录已过期\"\n\n#: apps/common/constants/exception_code_constants.py:31\n#: apps/users/serializers/login.py:53\n#: apps/xpack/serializers/chat_user_serializer.py:123\nmsgid \"The username or password is incorrect\"\nmsgstr \"用户名或密码不正确\"\n\n#: apps/common/constants/exception_code_constants.py:32\nmsgid \"Please log in first and bring the user Token\"\nmsgstr \"请先登录并携带用户 Token\"\n\n#: apps/common/constants/exception_code_constants.py:33\n#: apps/users/serializers/user.py:630\nmsgid \"Email sending failed\"\nmsgstr \"邮件发送失败\"\n\n#: apps/common/constants/exception_code_constants.py:34\nmsgid \"Email format error\"\nmsgstr \"邮箱格式错误\"\n\n#: apps/common/constants/exception_code_constants.py:35\nmsgid \"The email has been registered, please log in directly\"\nmsgstr \"该邮箱已注册，请直接登录\"\n\n#: apps/common/constants/exception_code_constants.py:36\nmsgid \"The email is not registered, please register first\"\nmsgstr \"该邮箱未注册，请先注册\"\n\n#: apps/common/constants/exception_code_constants.py:38\nmsgid \"The verification code is incorrect or the verification code has expired\"\nmsgstr \"验证码不正确或已过期\"\n\n#: apps/common/constants/exception_code_constants.py:39\nmsgid \"The username has been registered, please log in directly\"\nmsgstr \"用户名已注册，请直接登录\"\n\n#: apps/common/constants/exception_code_constants.py:41\nmsgid \"\"\n\"The username cannot be empty and must be between 6 and 20 characters long.\"\nmsgstr \"用户名不能为空，且长度在6到20个字符之间。\"\n\n#: apps/common/constants/exception_code_constants.py:43\nmsgid \"Password and confirmation password are inconsistent\"\nmsgstr \"密码和确认密码不一致\"\n\n#: apps/common/constants/exception_code_constants.py:44\nmsgid \"The nickname is already registered\"\nmsgstr \"姓名已注册\"\n\n#: apps/common/constants/permission_constants.py:209\nmsgid \"System Setting\"\nmsgstr \"系统设置\"\n\n#: apps/common/constants/permission_constants.py:211\n#: apps/common/constants/permission_constants.py:272\n#: apps/role_setting/views/role_setting.py:44\n#: apps/role_setting/views/role_setting.py:67\n#: apps/role_setting/views/role_setting.py:84\n#: apps/role_setting/views/role_setting.py:103\n#: apps/role_setting/views/role_setting.py:125\n#: apps/role_setting/views/role_setting.py:145\n#: apps/role_setting/views/role_setting.py:167\n#: apps/role_setting/views/role_setting.py:191\n#: apps/role_setting/views/role_setting.py:210\nmsgid \"Role\"\nmsgstr \"角色\"\n\n#: apps/common/constants/permission_constants.py:212\n#: apps/common/constants/permission_constants.py:270\n#: apps/workspace/views/workspace.py:37 apps/workspace/views/workspace.py:49\n#: apps/workspace/views/workspace.py:63 apps/workspace/views/workspace.py:80\n#: apps/workspace/views/workspace.py:101 apps/workspace/views/workspace.py:119\n#: apps/workspace/views/workspace.py:138 apps/workspace/views/workspace.py:155\n#: apps/workspace/views/workspace.py:170 apps/workspace/views/workspace.py:188\n#: apps/workspace/views/workspace.py:207 apps/workspace/views/workspace.py:223\n#: apps/workspace/views/workspace.py:236 apps/workspace/views/workspace.py:250\nmsgid \"Workspace\"\nmsgstr \"工作空间\"\n\n#: apps/common/constants/permission_constants.py:213\nmsgid \"Resource Application\"\nmsgstr \"资源管理-智能体\"\n\n#: apps/common/constants/permission_constants.py:214\nmsgid \"Resource Knowledge\"\nmsgstr \"资源管理-知识库\"\n\n#: apps/common/constants/permission_constants.py:215\nmsgid \"Resource Tool\"\nmsgstr \"资源管理-工具\"\n\n#: apps/common/constants/permission_constants.py:216\nmsgid \"Resource Model\"\nmsgstr \"资源管理-模型\"\n\n#: apps/common/constants/permission_constants.py:217\nmsgid \"Resource Permission\"\nmsgstr \"资源授权\"\n\n#: apps/common/constants/permission_constants.py:218\n#: apps/shared/views/shared_dataset_lark_views.py:30\n#: apps/shared/views/shared_dataset_lark_views.py:50\n#: apps/shared/views/shared_knowledge.py:33\n#: apps/shared/views/shared_knowledge.py:53\n#: apps/shared/views/shared_knowledge.py:76\n#: apps/shared/views/shared_knowledge.py:91\n#: apps/shared/views/shared_knowledge.py:106\n#: apps/shared/views/shared_knowledge.py:125\n#: apps/shared/views/shared_knowledge.py:151\n#: apps/shared/views/shared_knowledge.py:178\n#: apps/shared/views/shared_knowledge.py:196\n#: apps/shared/views/shared_knowledge.py:214\n#: apps/shared/views/shared_knowledge.py:235\n#: apps/shared/views/shared_knowledge.py:256\n#: apps/shared/views/shared_knowledge.py:276\n#: apps/shared/views/shared_knowledge.py:297\n#: apps/shared/views/shared_knowledge.py:312\n#: apps/shared/views/shared_knowledge.py:331\n#: apps/shared/views/shared_knowledge.py:354\n#: apps/shared/views/shared_knowledge.py:386\n#: apps/shared/views/shared_knowledge.py:407\nmsgid \"Shared Knowledge\"\nmsgstr \"共享资源-知识库\"\n\n#: apps/common/constants/permission_constants.py:219\n#: apps/models_provider/views/model.py:227 apps/shared/views/shared_model.py:58\n#: apps/shared/views/shared_model.py:89 apps/shared/views/shared_model.py:107\n#: apps/shared/views/shared_model.py:124 apps/shared/views/shared_model.py:138\n#: apps/shared/views/shared_model.py:153 apps/shared/views/shared_model.py:166\n#: apps/shared/views/shared_model.py:186 apps/shared/views/shared_model.py:202\n#: apps/shared/views/shared_model.py:219 apps/shared/views/shared_model.py:234\n#: apps/shared/views/shared_model.py:253 apps/shared/views/shared_model.py:270\nmsgid \"Shared Model\"\nmsgstr \"共享资源-模型\"\n\n#: apps/common/constants/permission_constants.py:220\n#: apps/shared/views/shared_tool.py:30 apps/shared/views/shared_tool.py:49\n#: apps/shared/views/shared_tool.py:68 apps/shared/views/shared_tool.py:83\n#: apps/shared/views/shared_tool.py:98 apps/shared/views/shared_tool.py:116\n#: apps/shared/views/shared_tool.py:139 apps/shared/views/shared_tool.py:157\n#: apps/shared/views/shared_tool.py:175 apps/shared/views/shared_tool.py:194\n#: apps/shared/views/shared_tool.py:218 apps/shared/views/shared_tool.py:239\n#: apps/shared/views/shared_tool.py:254 apps/shared/views/shared_tool.py:273\n#: apps/shared/views/shared_tool.py:294\nmsgid \"Shared Tool\"\nmsgstr \"共享资源-工具\"\n\n#: apps/common/constants/permission_constants.py:221\nmsgid \"Operation Log\"\nmsgstr \"操作日志\"\n\n#: apps/common/constants/permission_constants.py:223\nmsgid \"System Management\"\nmsgstr \"系统管理\"\n\n#: apps/common/constants/permission_constants.py:225\n#: apps/common/constants/permission_constants.py:235\n#: apps/common/constants/permission_constants.py:260\n#: apps/common/constants/permission_constants.py:265\nmsgid \"Knowledge\"\nmsgstr \"知识库\"\n\n#: apps/common/constants/permission_constants.py:227\n#: apps/common/constants/permission_constants.py:258\n#: apps/common/constants/permission_constants.py:263\n#: apps/tools/views/tool.py:39 apps/tools/views/tool.py:61\n#: apps/tools/views/tool.py:82 apps/tools/views/tool.py:104\n#: apps/tools/views/tool.py:127 apps/tools/views/tool.py:146\n#: apps/tools/views/tool.py:172 apps/tools/views/tool.py:201\n#: apps/tools/views/tool.py:223 apps/tools/views/tool.py:250\n#: apps/tools/views/tool.py:274\nmsgid \"Tool\"\nmsgstr \"工具\"\n\n#: apps/common/constants/permission_constants.py:229\nmsgid \"Read\"\nmsgstr \"查看\"\n\n#: apps/common/constants/permission_constants.py:230\nmsgid \"Edit\"\nmsgstr \"编辑\"\n\n#: apps/common/constants/permission_constants.py:231\nmsgid \"Create\"\nmsgstr \"创建\"\n\n#: apps/common/constants/permission_constants.py:232\nmsgid \"Delete\"\nmsgstr \"删除\"\n\n#: apps/common/constants/permission_constants.py:233\nmsgid \"Email Setting\"\nmsgstr \"邮箱设置\"\n\n#: apps/common/constants/permission_constants.py:236\n#: apps/common/constants/permission_constants.py:261\n#: apps/common/constants/permission_constants.py:266\nmsgid \"Document\"\nmsgstr \"文档\"\n\n#: apps/common/constants/permission_constants.py:237\n#: apps/common/constants/permission_constants.py:262\n#: apps/common/constants/permission_constants.py:267\nmsgid \"Problem\"\nmsgstr \"问题\"\n\n#: apps/common/constants/permission_constants.py:238\nmsgid \"Import\"\nmsgstr \"导入\"\n\n#: apps/common/constants/permission_constants.py:239\nmsgid \"Export\"\nmsgstr \"导出\"\n\n#: apps/common/constants/permission_constants.py:241\nmsgid \"Sync\"\nmsgstr \"同步\"\n\n#: apps/common/constants/permission_constants.py:242\nmsgid \"Generate\"\nmsgstr \"生成问题\"\n\n#: apps/common/constants/permission_constants.py:243\nmsgid \"Add Member\"\nmsgstr \"添加成员\"\n\n#: apps/common/constants/permission_constants.py:244\nmsgid \"Remove Member\"\nmsgstr \"移除成员\"\n\n#: apps/common/constants/permission_constants.py:245\nmsgid \"Vector\"\nmsgstr \"向量化\"\n\n#: apps/common/constants/permission_constants.py:246\nmsgid \"Migrate\"\nmsgstr \"迁移\"\n\n#: apps/common/constants/permission_constants.py:247\nmsgid \"Relate\"\nmsgstr \"关联分段\"\n\n#: apps/common/constants/permission_constants.py:249\nmsgid \"Clear Policy\"\nmsgstr \"清除策略\"\n\n#: apps/common/constants/permission_constants.py:250\nmsgid \"Login Auth\"\nmsgstr \"登录认证\"\n\n#: apps/common/constants/permission_constants.py:251\nmsgid \"Display Settings\"\nmsgstr \"显示设置\"\n\n#: apps/common/constants/permission_constants.py:252\n#: apps/common/constants/permission_constants.py:720\n#: apps/xpack/views/system_api_key.py:23 apps/xpack/views/system_api_key.py:38\n#: apps/xpack/views/system_api_key.py:56 apps/xpack/views/system_api_key.py:71\nmsgid \"System API Key\"\nmsgstr \"系统 API Key\"\n\n#: apps/common/constants/permission_constants.py:253\n#: apps/xpack/views/system_params.py:26 apps/xpack/views/system_params.py:42\nmsgid \"Appearance Settings\"\nmsgstr \"外观设置\"\n\n#: apps/common/constants/permission_constants.py:254\n#: apps/common/constants/permission_constants.py:269\n#: apps/xpack/views/system_chat_user.py:339\n#: apps/xpack/views/system_chat_user.py:362\nmsgid \"Chat User\"\nmsgstr \"对话用户\"\n\n#: apps/common/constants/permission_constants.py:255\n#: apps/common/constants/permission_constants.py:268\nmsgid \"User Group\"\nmsgstr \"用户组\"\n\n#: apps/common/constants/permission_constants.py:256\nmsgid \"Chat User Auth\"\nmsgstr \"对话用户认证\"\n\n#: apps/common/constants/permission_constants.py:257\nmsgid \"Overview\"\nmsgstr \"概览\"\n\n#: apps/common/constants/permission_constants.py:271\n#: apps/common/constants/permission_constants.py:671\n#: apps/common/constants/permission_constants.py:677\n#: apps/common/constants/permission_constants.py:683\n#: apps/common/constants/permission_constants.py:689\nmsgid \"Dialogue log\"\nmsgstr \"对话日志\"\n\n#: apps/common/constants/permission_constants.py:641\nmsgid \"Embed third party\"\nmsgstr \"嵌入第三方\"\n\n#: apps/common/constants/permission_constants.py:647\nmsgid \"Access restrictions\"\nmsgstr \"访问限制\"\n\n#: apps/common/constants/permission_constants.py:653\nmsgid \"Display settings\"\nmsgstr \"显示设置\"\n\n#: apps/common/constants/permission_constants.py:659\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:44\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:16\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:75\nmsgid \"API Key\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:665\nmsgid \"Public settings\"\nmsgstr \"公共访问连接\"\n\n#: apps/common/constants/permission_constants.py:704\nmsgid \"About\"\nmsgstr \"关于\"\n\n#: apps/common/constants/permission_constants.py:709\n#: apps/users/views/user.py:88 apps/users/views/user.py:89\n#: apps/users/views/user.py:90\nmsgid \"Switch Language\"\nmsgstr \"切换语言\"\n\n#: apps/common/constants/permission_constants.py:714\nmsgid \"Change Password\"\nmsgstr \"修改密码\"\n\n#: apps/common/constants/permission_constants.py:734\nmsgid \"Sync users\"\nmsgstr \"同步用户\"\n\n#: apps/common/constants/permission_constants.py:755\n#: apps/common/constants/permission_constants.py:808\nmsgid \"Set up user groups\"\nmsgstr \"设置用户组\"\n\n#: apps/common/event/__init__.py:27\nmsgid \"The download process was interrupted, please try again\"\nmsgstr \"下载过程被中断，请重试\"\n\n#: apps/common/event/listener_manage.py:90\n#, python-brace-format\nmsgid \"Query vector data: {paragraph_id_list} error {error} {traceback}\"\nmsgstr \"查询向量数据：{paragraph_id_list} 错误：{error} {traceback}\"\n\n#: apps/common/event/listener_manage.py:95\n#, python-brace-format\nmsgid \"Start--->Embedding paragraph: {paragraph_id_list}\"\nmsgstr \"开始--->向量段落: {paragraph_id_list}\"\n\n#: apps/common/event/listener_manage.py:107\n#, python-brace-format\nmsgid \"Vectorized paragraph: {paragraph_id_list} error {error} {traceback}\"\nmsgstr \"向量段落: {paragraph_id_list} 错误：{error} {traceback}\"\n\n#: apps/common/event/listener_manage.py:113\n#, python-brace-format\nmsgid \"End--->Embedding paragraph: {paragraph_id_list}\"\nmsgstr \"结束--->向量段落: {paragraph_id_list}\"\n\n#: apps/common/event/listener_manage.py:122\n#, python-brace-format\nmsgid \"Start--->Embedding paragraph: {paragraph_id}\"\nmsgstr \"开始--->向量段落: {paragraph_id}\"\n\n#: apps/common/event/listener_manage.py:147\n#, python-brace-format\nmsgid \"Vectorized paragraph: {paragraph_id} error {error} {traceback}\"\nmsgstr \"向量段落: {paragraph_id} 错误：{error} {traceback}\"\n\n#: apps/common/event/listener_manage.py:152\n#, python-brace-format\nmsgid \"End--->Embedding paragraph: {paragraph_id}\"\nmsgstr \"结束--->向量段落: {paragraph_id}\"\n\n#: apps/common/event/listener_manage.py:268\n#, python-brace-format\nmsgid \"Start--->Embedding document: {document_id}\"\nmsgstr \"开始--->向量文档: {document_id}\"\n\n#: apps/common/event/listener_manage.py:288\n#, python-brace-format\nmsgid \"Vectorized document: {document_id} error {error} {traceback}\"\nmsgstr \"向量文档: {document_id} 错误：{error} {traceback}\"\n\n#: apps/common/event/listener_manage.py:293\n#, python-brace-format\nmsgid \"End--->Embedding document: {document_id}\"\nmsgstr \"结束--->向量文档: {document_id}\"\n\n#: apps/common/event/listener_manage.py:304\n#, python-brace-format\nmsgid \"Start--->Embedding knowledge: {knowledge_id}\"\nmsgstr \"开始--->向量知识库: {knowledge_id}\"\n\n#: apps/common/event/listener_manage.py:308\n#, python-brace-format\nmsgid \"Start--->Embedding document: {document_list}\"\nmsgstr \"开始--->向量文档: {document_list}\"\n\n#: apps/common/event/listener_manage.py:312\n#: apps/knowledge/task/embedding.py:116\n#, python-brace-format\nmsgid \"Vectorized knowledge: {knowledge_id} error {error} {traceback}\"\nmsgstr \"向量知识库: {knowledge_id} 错误：{error} {traceback}\"\n\n#: apps/common/event/listener_manage.py:315\n#, python-brace-format\nmsgid \"End--->Embedding knowledge: {knowledge_id}\"\nmsgstr \"结束--->向量知识库: {knowledge_id}\"\n\n#: apps/common/exception/handle_exception.py:32\n#: apps/common/handle/handle_exception.py:33\nmsgid \"Unknown exception\"\nmsgstr \"未知错误\"\n\n#: apps/common/field/common.py:48\nmsgid \"not a function\"\nmsgstr \"不是函数\"\n\n#: apps/common/forms/base_field.py:64\n#, python-brace-format\nmsgid \"The field {field_label} is required\"\nmsgstr \"{field_label} 字段是必填项\"\n\n#: apps/common/forms/slider_field.py:56\n#, python-brace-format\nmsgid \"The {field_label} cannot be less than {min}\"\nmsgstr \"{field_label} 不能小于{min}\"\n\n#: apps/common/forms/slider_field.py:62\n#, python-brace-format\nmsgid \"The {field_label} cannot be greater than {max}\"\nmsgstr \"{field_label} 不能大于{max}\"\n\n#: apps/common/handle/impl/text/pdf_split_handle.py:281\n#, python-brace-format\nmsgid \"This document has no preface and is treated as ordinary text: {e}\"\nmsgstr \"该文档没有前言，视为普通文本: {e}\"\n\n#: apps/common/job/clean_chat_job.py:23\nmsgid \"start clean chat log\"\nmsgstr \"开始清理聊天日志\"\n\n#: apps/common/job/clean_chat_job.py:69\nmsgid \"end clean chat log\"\nmsgstr \"结束清理聊天日志\"\n\n#: apps/common/job/clean_debug_file_job.py:21\nmsgid \"start clean debug file\"\nmsgstr \"开始清理调试文件\"\n\n#: apps/common/job/clean_debug_file_job.py:25\nmsgid \"end clean debug file\"\nmsgstr \"结束清理调试文件\"\n\n#: apps/common/result/api.py:17 apps/common/result/api.py:27\nmsgid \"response code\"\nmsgstr \"响应码\"\n\n#: apps/common/result/api.py:18 apps/common/result/api.py:19\n#: apps/common/result/api.py:28 apps/common/result/api.py:29\nmsgid \"error prompt\"\nmsgstr \"错误提示\"\n\n#: apps/common/result/api.py:43\nmsgid \"total number of data\"\nmsgstr \"总数据\"\n\n#: apps/common/result/api.py:44\nmsgid \"current page\"\nmsgstr \"当前页\"\n\n#: apps/common/result/api.py:45\nmsgid \"page size\"\nmsgstr \"每页大小\"\n\n#: apps/common/result/result.py:31\n#: apps/xpack/serializers/operate_log_serializer.py:134\nmsgid \"Success\"\nmsgstr \"成功\"\n\n#: apps/common/utils/common.py:91\nmsgid \"Text-to-speech node, the text content must be of string type\"\nmsgstr \"文本转语音节点，文本内容必须是字符串类型\"\n\n#: apps/common/utils/common.py:93\nmsgid \"Text-to-speech node, the text content cannot be empty\"\nmsgstr \"文本转语音节点，文本内容不能为空\"\n\n#: apps/common/utils/common.py:246\n#, python-brace-format\nmsgid \"Limit {count} exceeded, please contact us (https://fit2cloud.com/).\"\nmsgstr \"超过限制 {count}，请联系我们 (https://fit2cloud.com/).\"\n\n#: apps/folders/models/folder.py:6 apps/folders/models/folder.py:17\n#: apps/folders/serializers/folder.py:98\nmsgid \"folder name\"\nmsgstr \"文件夹名称\"\n\n#: apps/folders/models/folder.py:8 apps/folders/models/folder.py:19\n#: apps/folders/serializers/folder.py:99\nmsgid \"folder description\"\nmsgstr \"文件夹描述\"\n\n#: apps/folders/models/folder.py:12 apps/folders/models/folder.py:23\n#: apps/folders/serializers/folder.py:102\nmsgid \"parent id\"\nmsgstr \"父级 ID\"\n\n#: apps/folders/serializers/folder.py:75\nmsgid \"Folder depth cannot exceed 5 levels\"\nmsgstr \"文件夹深度不能超过5级\"\n\n#: apps/folders/serializers/folder.py:100\nmsgid \"folder user id\"\nmsgstr \"文件夹用户 ID\"\n\n#: apps/folders/serializers/folder.py:105\n#: apps/knowledge/serializers/knowledge.py:112\n#: apps/knowledge/serializers/knowledge.py:207\n#: apps/knowledge/serializers/knowledge.py:447\n#: apps/knowledge/serializers/knowledge.py:559\n#: apps/knowledge/serializers/knowledge.py:637\n#: apps/models_provider/serializers/model_serializer.py:108\n#: apps/models_provider/serializers/model_serializer.py:212\n#: apps/models_provider/serializers/model_serializer.py:252\n#: apps/shared/serializers/shared_knowledge.py:107\n#: apps/shared/serializers/shared_knowledge.py:156\n#: apps/shared/serializers/shared_tool.py:84\n#: apps/system_manage/serializers/user_resource_permission.py:75\n#: apps/tools/serializers/tool.py:187 apps/tools/serializers/tool.py:209\n#: apps/tools/serializers/tool.py:455 apps/users/serializers/user.py:664\n#: apps/xpack/serializers/dataset_lark_serializer.py:46\n#: apps/xpack/serializers/dataset_lark_serializer.py:285\n#: apps/xpack/serializers/system_api_key.py:23\nmsgid \"user id\"\nmsgstr \"用户ID\"\n\n#: apps/folders/serializers/folder.py:123\nmsgid \"Folder name already exists\"\nmsgstr \"文件夹名称已存在\"\n\n#: apps/folders/serializers/folder.py:150\n#: apps/folders/serializers/folder.py:182\nmsgid \"Folder does not exist\"\nmsgstr \"文件夹不存在\"\n\n#: apps/folders/serializers/folder.py:184\nmsgid \"Cannot delete root folder\"\nmsgstr \"无法删除根文件夹\"\n\n#: apps/folders/views/folder.py:31 apps/folders/views/folder.py:32\n#: apps/folders/views/folder.py:33\nmsgid \"Create folder\"\nmsgstr \"创建文件夹\"\n\n#: apps/folders/views/folder.py:37 apps/folders/views/folder.py:63\n#: apps/folders/views/folder.py:86 apps/folders/views/folder.py:110\n#: apps/folders/views/folder.py:129\nmsgid \"Folder\"\nmsgstr \"文件夹\"\n\n#: apps/folders/views/folder.py:58 apps/folders/views/folder.py:59\n#: apps/folders/views/folder.py:60\nmsgid \"Get folder tree\"\nmsgstr \"获取文件夹树\"\n\n#: apps/folders/views/folder.py:80 apps/folders/views/folder.py:81\n#: apps/folders/views/folder.py:82\nmsgid \"Update folder\"\nmsgstr \"更新文件夹\"\n\n#: apps/folders/views/folder.py:105 apps/folders/views/folder.py:106\n#: apps/folders/views/folder.py:107\nmsgid \"Get folder\"\nmsgstr \"获取文件夹\"\n\n#: apps/folders/views/folder.py:124 apps/folders/views/folder.py:125\n#: apps/folders/views/folder.py:126\nmsgid \"Delete folder\"\nmsgstr \"删除文件夹\"\n\n#: apps/knowledge/api/problem.py:39 apps/knowledge/api/problem.py:52\n#: apps/knowledge/serializers/problem.py:40\nmsgid \"problem list\"\nmsgstr \"问题列表\"\n\n#: apps/knowledge/api/problem.py:40 apps/knowledge/api/problem.py:53\n#: apps/knowledge/serializers/problem.py:41\nmsgid \"problem\"\nmsgstr \"问题 ID\"\n\n#: apps/knowledge/serializers/common.py:32\n#: apps/knowledge/serializers/knowledge.py:62\n#: apps/shared/serializers/shared_knowledge.py:29\nmsgid \"source url\"\nmsgstr \"来源\"\n\n#: apps/knowledge/serializers/common.py:33\n#: apps/knowledge/serializers/document.py:152\nmsgid \"selector\"\nmsgstr \"选择器\"\n\n#: apps/knowledge/serializers/common.py:40\n#, python-brace-format\nmsgid \"URL error, cannot parse [{source_url}]\"\nmsgstr \"URL 错误，无法解析 [{source_url}]\"\n\n#: apps/knowledge/serializers/common.py:48\n#: apps/knowledge/serializers/document.py:78\n#: apps/knowledge/serializers/document.py:170\n#: apps/knowledge/serializers/document.py:186\nmsgid \"id list\"\nmsgstr \"ID 列表\"\n\n#: apps/knowledge/serializers/common.py:58\n#, python-brace-format\nmsgid \"The following id does not exist: {error_id_list}\"\nmsgstr \"以下ID不存在: {error_id_list}\"\n\n#: apps/knowledge/serializers/common.py:74\n#: apps/knowledge/serializers/document.py:166\n#: apps/knowledge/serializers/document.py:171\n#: apps/knowledge/serializers/document.py:178\nmsgid \"state list\"\nmsgstr \"状态列表\"\n\n#: apps/knowledge/serializers/common.py:117\n#: apps/knowledge/serializers/common.py:141\nmsgid \"The knowledge base is inconsistent with the vector model\"\nmsgstr \"知识库与向量模型不一致\"\n\n#: apps/knowledge/serializers/common.py:119\n#: apps/knowledge/serializers/common.py:143\nmsgid \"Knowledge base setting error, please reset the knowledge base\"\nmsgstr \"知识库设置错误，请重置知识库\"\n\n#: apps/knowledge/serializers/document.py:79\n#: apps/knowledge/serializers/document.py:97\n#: apps/knowledge/serializers/document.py:353\nmsgid \"task type\"\nmsgstr \"任务类型\"\n\n#: apps/knowledge/serializers/document.py:87\n#: apps/knowledge/serializers/document.py:105\nmsgid \"task type not support\"\nmsgstr \"任务类型不支持\"\n\n#: apps/knowledge/serializers/document.py:91\n#: apps/knowledge/serializers/document.py:110\n#: apps/knowledge/serializers/document.py:350\nmsgid \"document name\"\nmsgstr \"文档名称\"\n\n#: apps/knowledge/serializers/document.py:93\nmsgid \"source file id\"\nmsgstr \"源文件 ID\"\n\n#: apps/knowledge/serializers/document.py:113\n#: apps/knowledge/serializers/document.py:194\nmsgid \"The type only supports optimization|directly_return\"\nmsgstr \"该类型仅支持优化|直接返回\"\n\n#: apps/knowledge/serializers/document.py:115\n#: apps/knowledge/serializers/document.py:187\n#: apps/knowledge/serializers/document.py:351\nmsgid \"hit handling method\"\nmsgstr \"命中处理方法\"\n\n#: apps/knowledge/serializers/document.py:118\n#: apps/knowledge/serializers/document.py:189\nmsgid \"directly return similarity\"\nmsgstr \"直接返回相似度\"\n\n#: apps/knowledge/serializers/document.py:120\n#: apps/knowledge/serializers/document.py:352\nmsgid \"document is active\"\nmsgstr \"文档已激活\"\n\n#: apps/knowledge/serializers/document.py:139\n#: apps/knowledge/serializers/document.py:156\n#: apps/knowledge/serializers/document.py:161\nmsgid \"file list\"\nmsgstr \"文件 列表\"\n\n#: apps/knowledge/serializers/document.py:140\nmsgid \"limit\"\nmsgstr \"限制\"\n\n#: apps/knowledge/serializers/document.py:143\n#: apps/knowledge/serializers/document.py:144\nmsgid \"patterns\"\nmsgstr \"分割符\"\n\n#: apps/knowledge/serializers/document.py:146\nmsgid \"Auto Clean\"\nmsgstr \"自动清理\"\n\n#: apps/knowledge/serializers/document.py:150\n#: apps/knowledge/serializers/document.py:151\nmsgid \"document url list\"\nmsgstr \"文档 URL 列表\"\n\n#: apps/knowledge/serializers/document.py:175\n#: apps/knowledge/serializers/document.py:182\nmsgid \"document id list\"\nmsgstr \"文档 ID 列表\"\n\n#: apps/knowledge/serializers/document.py:176\n#: apps/knowledge/serializers/paragraph.py:58\n#: apps/models_provider/api/model.py:105\n#: apps/models_provider/serializers/model_apply_serializers.py:51\n#: apps/models_provider/serializers/model_serializer.py:107\n#: apps/models_provider/serializers/model_serializer.py:364\n#: apps/shared/api/shared_model.py:61\n#: apps/shared/serializers/shared_model.py:54\nmsgid \"model id\"\nmsgstr \"模型ID\"\n\n#: apps/knowledge/serializers/document.py:177\n#: apps/knowledge/serializers/paragraph.py:59\nmsgid \"prompt\"\nmsgstr \"提示词\"\n\n#: apps/knowledge/serializers/document.py:201\nmsgid \"The template type only supports excel|csv\"\nmsgstr \"模板类型仅支持 excel|csv\"\n\n#: apps/knowledge/serializers/document.py:254\n#: apps/knowledge/serializers/document.py:348\n#: apps/knowledge/serializers/document.py:409\n#: apps/knowledge/serializers/document.py:504\n#: apps/knowledge/serializers/document.py:889\n#: apps/knowledge/serializers/document.py:964\n#: apps/knowledge/serializers/document.py:984\n#: apps/knowledge/serializers/document.py:1167\n#: apps/knowledge/serializers/knowledge.py:209\n#: apps/knowledge/serializers/knowledge.py:558\n#: apps/knowledge/serializers/paragraph.py:70\n#: apps/knowledge/serializers/paragraph.py:138\n#: apps/knowledge/serializers/paragraph.py:239\n#: apps/knowledge/serializers/paragraph.py:321\n#: apps/knowledge/serializers/paragraph.py:347\n#: apps/knowledge/serializers/paragraph.py:398\n#: apps/knowledge/serializers/paragraph.py:439\n#: apps/knowledge/serializers/paragraph.py:559\n#: apps/knowledge/serializers/problem.py:62\n#: apps/knowledge/serializers/problem.py:126\n#: apps/knowledge/serializers/problem.py:177\n#: apps/knowledge/serializers/problem.py:205\n#: apps/shared/api/shared_knowledge.py:196\n#: apps/shared/api/shared_knowledge.py:218\n#: apps/shared/serializers/shared_knowledge.py:158\n#: apps/shared/serializers/shared_knowledge.py:205\n#: apps/xpack/serializers/dataset_lark_serializer.py:104\n#: apps/xpack/serializers/dataset_lark_serializer.py:263\n#: apps/xpack/serializers/dataset_lark_serializer.py:284\nmsgid \"knowledge id\"\nmsgstr \"知识库 ID\"\n\n#: apps/knowledge/serializers/document.py:255\n#: apps/knowledge/serializers/paragraph.py:441\nmsgid \"target knowledge id\"\nmsgstr \"当前知识库 ID\"\n\n#: apps/knowledge/serializers/document.py:256\nmsgid \"document list\"\nmsgstr \"文档列表\"\n\n#: apps/knowledge/serializers/document.py:257\n#: apps/knowledge/serializers/document.py:410\n#: apps/knowledge/serializers/document.py:503\n#: apps/knowledge/serializers/document.py:737\n#: apps/knowledge/serializers/paragraph.py:61\n#: apps/knowledge/serializers/paragraph.py:71\n#: apps/knowledge/serializers/paragraph.py:140\n#: apps/knowledge/serializers/paragraph.py:240\n#: apps/knowledge/serializers/paragraph.py:322\n#: apps/knowledge/serializers/paragraph.py:349\n#: apps/knowledge/serializers/paragraph.py:399\n#: apps/knowledge/serializers/paragraph.py:440\n#: apps/knowledge/serializers/paragraph.py:560\n#: apps/knowledge/serializers/problem.py:36\n#: apps/knowledge/serializers/problem.py:51\n#: apps/xpack/serializers/dataset_lark_serializer.py:160\nmsgid \"document id\"\nmsgstr \"文档 ID\"\n\n#: apps/knowledge/serializers/document.py:354 apps/xpack/api/license.py:25\n#: apps/xpack/serializers/operate_log_serializer.py:60\n#: apps/xpack/serializers/operate_log_serializer.py:174\nmsgid \"status\"\nmsgstr \"状态\"\n\n#: apps/knowledge/serializers/document.py:355\nmsgid \"order by\"\nmsgstr \"排序\"\n\n#: apps/knowledge/serializers/document.py:417\n#: apps/knowledge/serializers/document.py:510\n#: apps/xpack/serializers/dataset_lark_serializer.py:167\n#: apps/xpack/serializers/dataset_lark_serializer.py:189\nmsgid \"document id not exist\"\nmsgstr \"文档 ID 不存在\"\n\n#: apps/knowledge/serializers/document.py:419\n#: apps/knowledge/serializers/knowledge.py:570\nmsgid \"Synchronization is only supported for web site types\"\nmsgstr \"仅支持网站类型的同步\"\n\n#: apps/knowledge/serializers/document.py:661\nmsgid \"The task is being executed, please do not send it repeatedly.\"\nmsgstr \"任务正在执行，请勿重复发送。\"\n\n#: apps/knowledge/serializers/document.py:674\nmsgid \"Section title (optional)\"\nmsgstr \"章节标题\"\n\n#: apps/knowledge/serializers/document.py:675\nmsgid \"\"\n\"Section content (required, question answer, no more than 4096 characters)\"\nmsgstr \"章节内容（必填，问答，不超过4096个字符）\"\n\n#: apps/knowledge/serializers/document.py:676\nmsgid \"Question (optional, one per line in the cell)\"\nmsgstr \"问题（可选，每个单元格一行）\"\n\n#: apps/knowledge/serializers/document.py:742\nmsgid \"knowledge id not exist\"\nmsgstr \"知识库 ID 不存在\"\n\n#: apps/knowledge/serializers/document.py:898\nmsgid \"The maximum size of the uploaded file cannot exceed {}MB\"\nmsgstr \"上传文件的最大大小不能超过 {}MB\"\n\n#: apps/knowledge/serializers/document.py:976\nmsgid \"space\"\nmsgstr \"空格\"\n\n#: apps/knowledge/serializers/document.py:977\nmsgid \"semicolon\"\nmsgstr \"分号\"\n\n#: apps/knowledge/serializers/document.py:977\nmsgid \"comma\"\nmsgstr \"逗号\"\n\n#: apps/knowledge/serializers/document.py:978\nmsgid \"period\"\nmsgstr \"句号\"\n\n#: apps/knowledge/serializers/document.py:978\nmsgid \"enter\"\nmsgstr \"回车\"\n\n#: apps/knowledge/serializers/document.py:979\nmsgid \"blank line\"\nmsgstr \"空行\"\n\n#: apps/knowledge/serializers/document.py:1140\nmsgid \"Hit handling method is required\"\nmsgstr \"命中处理方法是必需的\"\n\n#: apps/knowledge/serializers/document.py:1142\nmsgid \"The hit processing method must be directly_return|optimization\"\nmsgstr \"命中处理方法必须是直接返回|优化\"\n\n#: apps/knowledge/serializers/knowledge.py:51\n#: apps/knowledge/serializers/knowledge.py:58\n#: apps/knowledge/serializers/knowledge.py:67\n#: apps/knowledge/serializers/knowledge.py:108\n#: apps/shared/api/shared_knowledge.py:117\n#: apps/shared/api/shared_knowledge.py:150\n#: apps/shared/serializers/shared_knowledge.py:20\n#: apps/shared/serializers/shared_knowledge.py:26\n#: apps/shared/serializers/shared_knowledge.py:59\n#: apps/shared/serializers/shared_knowledge.py:106\n#: apps/xpack/serializers/dataset_lark_serializer.py:51\n#: apps/xpack/serializers/dataset_lark_serializer.py:289\nmsgid \"knowledge name\"\nmsgstr \"知识库名称\"\n\n#: apps/knowledge/serializers/knowledge.py:53\n#: apps/knowledge/serializers/knowledge.py:60\n#: apps/knowledge/serializers/knowledge.py:68\n#: apps/knowledge/serializers/knowledge.py:110\n#: apps/shared/api/shared_knowledge.py:124\n#: apps/shared/api/shared_knowledge.py:157\n#: apps/shared/serializers/shared_knowledge.py:21\n#: apps/shared/serializers/shared_knowledge.py:27\n#: apps/shared/serializers/shared_knowledge.py:60\n#: apps/shared/serializers/shared_knowledge.py:108\n#: apps/xpack/serializers/dataset_lark_serializer.py:53\n#: apps/xpack/serializers/dataset_lark_serializer.py:291\nmsgid \"knowledge description\"\nmsgstr \"知识库描述\"\n\n#: apps/knowledge/serializers/knowledge.py:54\n#: apps/knowledge/serializers/knowledge.py:61\n#: apps/shared/serializers/shared_knowledge.py:22\n#: apps/shared/serializers/shared_knowledge.py:28\nmsgid \"knowledge embedding\"\nmsgstr \"知识库向量\"\n\n#: apps/knowledge/serializers/knowledge.py:63\n#: apps/shared/serializers/shared_knowledge.py:30\nmsgid \"knowledge selector\"\nmsgstr \"知识库选择器\"\n\n#: apps/knowledge/serializers/knowledge.py:73\n#: apps/xpack/serializers/dataset_lark_serializer.py:296\nmsgid \"application id list\"\nmsgstr \"智能体 ID 列表\"\n\n#: apps/knowledge/serializers/knowledge.py:75\nmsgid \"file size limit\"\nmsgstr \"文件大小限制\"\n\n#: apps/knowledge/serializers/knowledge.py:76\nmsgid \"file count limit\"\nmsgstr \"文件数量限制\"\n\n#: apps/knowledge/serializers/knowledge.py:95\n#: apps/knowledge/serializers/knowledge.py:638\nmsgid \"query text\"\nmsgstr \"查询文本\"\n\n#: apps/knowledge/serializers/knowledge.py:96\n#: apps/knowledge/serializers/knowledge.py:639\nmsgid \"top number\"\nmsgstr \"Top 数量\"\n\n#: apps/knowledge/serializers/knowledge.py:98\n#: apps/knowledge/serializers/knowledge.py:641\nmsgid \"search mode\"\nmsgstr \"搜索模式\"\n\n#: apps/knowledge/serializers/knowledge.py:113\nmsgid \"knowledge scope\"\nmsgstr \"知识库范围\"\n\n#: apps/knowledge/serializers/knowledge.py:169\n#: apps/tools/serializers/tool.py:434 apps/tools/serializers/tool.py:464\nmsgid \"Folder not found\"\nmsgstr \"文件夹不存在\"\n\n#: apps/knowledge/serializers/knowledge.py:236\n#: apps/knowledge/serializers/knowledge.py:265\nmsgid \"Failed to send the vectorization task, please try again later!\"\nmsgstr \"发送向量化任务失败，请稍后再试！\"\n\n#: apps/knowledge/serializers/knowledge.py:315\n#: apps/knowledge/serializers/knowledge.py:471\n#: apps/knowledge/serializers/knowledge.py:533\n#: apps/xpack/serializers/dataset_lark_serializer.py:82\n#: apps/xpack/serializers/dataset_lark_serializer.py:340\nmsgid \"Knowledge base name duplicate!\"\nmsgstr \"知识库名称重复！\"\n\n#: apps/knowledge/serializers/knowledge.py:341\n#: apps/xpack/serializers/dataset_lark_serializer.py:359\n#, python-brace-format\nmsgid \"Unknown application id {knowledge_id}, cannot be associated\"\nmsgstr \"未知智能体 ID {knowledge_id}，无法关联\"\n\n#: apps/knowledge/serializers/knowledge.py:449\n#: apps/shared/serializers/shared_knowledge.py:62\n#: apps/shared/serializers/shared_knowledge.py:110\n#: apps/shared/serializers/shared_tool.py:46\n#: apps/shared/serializers/shared_tool.py:87 apps/tools/serializers/tool.py:456\n#: apps/xpack/serializers/dataset_lark_serializer.py:47\nmsgid \"scope\"\nmsgstr \"范围\"\n\n#: apps/knowledge/serializers/knowledge.py:460\nmsgid \"\"\n\"The community version supports up to 50 knowledge bases. If you need more \"\n\"knowledge bases, please contact us (https://fit2cloud.com/).\"\nmsgstr \"\"\n\"社区版支持最多50个知识库，如需更多知识库，请联系我们 (https://\"\n\"fit2cloud.com/).\"\n\n#: apps/knowledge/serializers/knowledge.py:560\nmsgid \"sync type\"\nmsgstr \"同步类型\"\n\n#: apps/knowledge/serializers/knowledge.py:562\nmsgid \"The synchronization type only supports:replace|complete\"\nmsgstr \"同步类型仅支持:replace|complete\"\n\n#: apps/knowledge/serializers/knowledge.py:568\n#: apps/knowledge/serializers/knowledge.py:649\nmsgid \"id does not exist\"\nmsgstr \"知识库 ID 不存在\"\n\n#: apps/knowledge/serializers/knowledge.py:636 apps/users/api/user.py:76\nmsgid \"id\"\nmsgstr \"ID\"\n\n#: apps/knowledge/serializers/paragraph.py:39\n#: apps/knowledge/serializers/problem.py:27\n#: apps/knowledge/serializers/problem.py:31\n#: apps/knowledge/serializers/problem.py:206\nmsgid \"content\"\nmsgstr \"内容\"\n\n#: apps/knowledge/serializers/paragraph.py:41\n#: apps/knowledge/serializers/paragraph.py:48\n#: apps/knowledge/serializers/paragraph.py:51\n#: apps/knowledge/serializers/paragraph.py:65\n#: apps/knowledge/serializers/paragraph.py:67\n#: apps/knowledge/serializers/paragraph.py:323\nmsgid \"section title\"\nmsgstr \"章节标题\"\n\n#: apps/knowledge/serializers/paragraph.py:44\n#: apps/tools/serializers/tool.py:152 apps/tools/serializers/tool.py:164\n#: apps/xpack/serializers/system_api_key.py:11\nmsgid \"Is active\"\nmsgstr \"是否启用\"\n\n#: apps/knowledge/serializers/paragraph.py:56\n#: apps/knowledge/serializers/paragraph.py:443\nmsgid \"paragraph id list\"\nmsgstr \"段落 ID 列表\"\n\n#: apps/knowledge/serializers/paragraph.py:57\n#: apps/knowledge/serializers/paragraph.py:72\n#: apps/knowledge/serializers/paragraph.py:136\n#: apps/knowledge/serializers/paragraph.py:350\n#: apps/knowledge/serializers/paragraph.py:444\n#: apps/knowledge/serializers/paragraph.py:561\n#: apps/knowledge/serializers/problem.py:35\n#: apps/knowledge/serializers/problem.py:50\nmsgid \"paragraph id\"\nmsgstr \"段落 ID\"\n\n#: apps/knowledge/serializers/paragraph.py:77\n#: apps/knowledge/serializers/paragraph.py:145\nmsgid \"Paragraph id does not exist\"\nmsgstr \"段落 ID 不存在\"\n\n#: apps/knowledge/serializers/paragraph.py:108\nmsgid \"Already associated, please do not associate again\"\nmsgstr \"已关联，请勿再次关联\"\n\n#: apps/knowledge/serializers/paragraph.py:181\nmsgid \"Problem id does not exist\"\nmsgstr \"问题 ID 不存在\"\n\n#: apps/knowledge/serializers/paragraph.py:348\n#: apps/knowledge/serializers/problem.py:26\n#: apps/knowledge/serializers/problem.py:46\n#: apps/knowledge/serializers/problem.py:56\n#: apps/knowledge/serializers/problem.py:127\nmsgid \"problem id\"\nmsgstr \"问题 ID\"\n\n#: apps/knowledge/serializers/paragraph.py:358\nmsgid \"Paragraph does not exist\"\nmsgstr \"段落不存在\"\n\n#: apps/knowledge/serializers/paragraph.py:360\nmsgid \"Problem does not exist\"\nmsgstr \"问题不存在\"\n\n#: apps/knowledge/serializers/paragraph.py:435\nmsgid \"The task is being executed, please do not send it again.\"\nmsgstr \"任务正在执行，请勿重复发送。\"\n\n#: apps/knowledge/serializers/paragraph.py:442\nmsgid \"target document id\"\nmsgstr \"目标文档 ID\"\n\n#: apps/knowledge/serializers/paragraph.py:453\nmsgid \"The document to be migrated is consistent with the target document\"\nmsgstr \"迁移的文档与目标文档一致\"\n\n#: apps/knowledge/serializers/paragraph.py:455\nmsgid \"The document id does not exist [{document_id}]\"\nmsgstr \"以下文档ID不存在: {error_id_list}\"\n\n#: apps/knowledge/serializers/paragraph.py:459\nmsgid \"The target document id does not exist [{document_id}]\"\nmsgstr \"以下目标文档ID不存在: {error_id_list}\"\n\n#: apps/knowledge/serializers/paragraph.py:573\nmsgid \"new_position must be an integer\"\nmsgstr \"new_position 必须是整数\"\n\n#: apps/knowledge/serializers/problem.py:45\n#: apps/knowledge/serializers/problem.py:55\nmsgid \"problem id list\"\nmsgstr \"问题 ID 列表\"\n\n#: apps/knowledge/task/embedding.py:24 apps/knowledge/task/embedding.py:74\n#, python-brace-format\nmsgid \"Failed to obtain vector model: {error} {traceback}\"\nmsgstr \"向量模型获取失败: {error} {traceback}\"\n\n#: apps/knowledge/task/embedding.py:103\n#, python-brace-format\nmsgid \"Start--->Vectorized knowledge: {knowledge_id}\"\nmsgstr \"开始--->向量知识库: {knowledge_id}\"\n\n#: apps/knowledge/task/embedding.py:107\n#, python-brace-format\nmsgid \"Knowledge documentation: {document_names}\"\nmsgstr \"知识库文档: {document_names}\"\n\n#: apps/knowledge/task/embedding.py:120\n#, python-brace-format\nmsgid \"End--->Vectorized knowledge: {knowledge_id}\"\nmsgstr \"结束--->向量知识库: {knowledge_id}\"\n\n#: apps/knowledge/task/generate.py:106\n#, python-brace-format\nmsgid \"\"\n\"Generate issue based on document: {document_id} error {error}{traceback}\"\nmsgstr \"生成问题基于文档: {document_id} 错误 {error}{traceback}\"\n\n#: apps/knowledge/task/generate.py:110\n#, python-brace-format\nmsgid \"End--->Generate problem: {document_id}\"\nmsgstr \"结束--->生成问题: {document_id}\"\n\n#: apps/knowledge/task/handler.py:121\n#, python-brace-format\nmsgid \"Association problem failed {error}\"\nmsgstr \"关联问题失败 {error}\"\n\n#: apps/knowledge/task/sync.py:30 apps/knowledge/task/sync.py:47\n#, python-brace-format\nmsgid \"Start--->Start synchronization web knowledge base:{knowledge_id}\"\nmsgstr \"开始--->开始同步 web 知识库:{knowledge_id}\"\n\n#: apps/knowledge/task/sync.py:35 apps/knowledge/task/sync.py:51\n#, python-brace-format\nmsgid \"End--->End synchronization web knowledge base:{knowledge_id}\"\nmsgstr \"结束--->结束同步 web 知识库:{knowledge_id}\"\n\n#: apps/knowledge/task/sync.py:37 apps/knowledge/task/sync.py:53\n#, python-brace-format\nmsgid \"Synchronize web knowledge base:{knowledge_id} error{error}{traceback}\"\nmsgstr \"同步 web 知识库:{knowledge_id} 错误{error}{traceback}\"\n\n#: apps/knowledge/views/document.py:28 apps/knowledge/views/document.py:29\n#: apps/knowledge/views/document.py:30\nmsgid \"Create document\"\nmsgstr \"创建文档\"\n\n#: apps/knowledge/views/document.py:34 apps/knowledge/views/document.py:57\n#: apps/knowledge/views/document.py:84 apps/knowledge/views/document.py:104\n#: apps/knowledge/views/document.py:128 apps/knowledge/views/document.py:160\n#: apps/knowledge/views/document.py:191 apps/knowledge/views/document.py:209\n#: apps/knowledge/views/document.py:238 apps/knowledge/views/document.py:267\n#: apps/knowledge/views/document.py:295 apps/knowledge/views/document.py:323\n#: apps/knowledge/views/document.py:352 apps/knowledge/views/document.py:382\n#: apps/knowledge/views/document.py:412 apps/knowledge/views/document.py:441\n#: apps/knowledge/views/document.py:472 apps/knowledge/views/document.py:501\n#: apps/knowledge/views/document.py:527 apps/knowledge/views/document.py:553\n#: apps/knowledge/views/document.py:579 apps/knowledge/views/document.py:599\n#: apps/knowledge/views/document.py:633 apps/knowledge/views/document.py:664\n#: apps/knowledge/views/document.py:695 apps/knowledge/views/document.py:723\n#: apps/knowledge/views/document.py:737\n#: apps/xpack/views/dataset_lark_views.py:72\n#: apps/xpack/views/dataset_lark_views.py:91\n#: apps/xpack/views/dataset_lark_views.py:111\n#: apps/xpack/views/dataset_lark_views.py:132\nmsgid \"Knowledge Base/Documentation\"\nmsgstr \"知识库/文档\"\n\n#: apps/knowledge/views/document.py:52 apps/knowledge/views/document.py:53\n#: apps/knowledge/views/document.py:54\nmsgid \"Get document\"\nmsgstr \"获取文档\"\n\n#: apps/knowledge/views/document.py:79 apps/knowledge/views/document.py:80\n#: apps/knowledge/views/document.py:81\nmsgid \"Get document details\"\nmsgstr \"文档文档详情\"\n\n#: apps/knowledge/views/document.py:98 apps/knowledge/views/document.py:99\n#: apps/knowledge/views/document.py:100\nmsgid \"Modify document\"\nmsgstr \"修改文档\"\n\n#: apps/knowledge/views/document.py:123 apps/knowledge/views/document.py:124\n#: apps/knowledge/views/document.py:125\nmsgid \"Delete document\"\nmsgstr \"删除文档\"\n\n#: apps/knowledge/views/document.py:154 apps/knowledge/views/document.py:155\n#: apps/knowledge/views/document.py:156\nmsgid \"Segmented document\"\nmsgstr \"分段文档\"\n\n#: apps/knowledge/views/document.py:186 apps/knowledge/views/document.py:187\n#: apps/knowledge/views/document.py:188\nmsgid \"Get a list of segment IDs\"\nmsgstr \"获取分段列表\"\n\n#: apps/knowledge/views/document.py:203 apps/knowledge/views/document.py:204\n#: apps/knowledge/views/document.py:205\nmsgid \"Modify document hit processing methods in batches\"\nmsgstr \"批量修改文档命中处理方法\"\n\n#: apps/knowledge/views/document.py:232 apps/knowledge/views/document.py:233\n#: apps/knowledge/views/document.py:234\nmsgid \"Synchronize web site types\"\nmsgstr \"同步网站类型\"\n\n#: apps/knowledge/views/document.py:261 apps/knowledge/views/document.py:262\n#: apps/knowledge/views/document.py:263\nmsgid \"Refresh document vector library\"\nmsgstr \"刷新文档向量库\"\n\n#: apps/knowledge/views/document.py:289 apps/knowledge/views/document.py:290\n#: apps/knowledge/views/document.py:291\nmsgid \"Cancel task\"\nmsgstr \"取消任务\"\n\n#: apps/knowledge/views/document.py:317 apps/knowledge/views/document.py:318\n#: apps/knowledge/views/document.py:319\nmsgid \"Cancel tasks in batches\"\nmsgstr \"批量取消任务\"\n\n#: apps/knowledge/views/document.py:346 apps/knowledge/views/document.py:347\n#: apps/knowledge/views/document.py:348\nmsgid \"Create documents in batches\"\nmsgstr \"批量创建文档\"\n\n#: apps/knowledge/views/document.py:376 apps/knowledge/views/document.py:377\n#: apps/knowledge/views/document.py:378\nmsgid \"Batch sync documents\"\nmsgstr \"批量同步文档\"\n\n#: apps/knowledge/views/document.py:406 apps/knowledge/views/document.py:407\n#: apps/knowledge/views/document.py:408\nmsgid \"Delete documents in batches\"\nmsgstr \"批量删除文档\"\n\n#: apps/knowledge/views/document.py:436 apps/knowledge/views/document.py:437\nmsgid \"Batch refresh document vector library\"\nmsgstr \"批量刷新文档向量库\"\n\n#: apps/knowledge/views/document.py:466 apps/knowledge/views/document.py:467\n#: apps/knowledge/views/document.py:468\nmsgid \"Batch generate related problems\"\nmsgstr \"批量生成相关问题\"\n\n#: apps/knowledge/views/document.py:496 apps/knowledge/views/document.py:497\n#: apps/knowledge/views/document.py:498\nmsgid \"Get document by pagination\"\nmsgstr \"分页获取文档\"\n\n#: apps/knowledge/views/document.py:523 apps/knowledge/views/document.py:524\nmsgid \"Export document\"\nmsgstr \"导出文档\"\n\n#: apps/knowledge/views/document.py:549 apps/knowledge/views/document.py:550\nmsgid \"Export Zip document\"\nmsgstr \"导出 Zip 文档\"\n\n#: apps/knowledge/views/document.py:575 apps/knowledge/views/document.py:576\nmsgid \"Download source file\"\nmsgstr \"下载源文件\"\n\n#: apps/knowledge/views/document.py:594 apps/knowledge/views/document.py:595\nmsgid \"Migrate documents in batches\"\nmsgstr \"批量迁移文档\"\n\n#: apps/knowledge/views/document.py:627 apps/knowledge/views/document.py:628\n#: apps/knowledge/views/document.py:629\n#: apps/shared/views/shared_document.py:570\nmsgid \"Create Web site documents\"\nmsgstr \"创建网站文档\"\n\n#: apps/knowledge/views/document.py:658 apps/knowledge/views/document.py:659\n#: apps/knowledge/views/document.py:660\nmsgid \"Import QA and create documentation\"\nmsgstr \"导入问答并创建文档\"\n\n#: apps/knowledge/views/document.py:689 apps/knowledge/views/document.py:690\n#: apps/knowledge/views/document.py:691\nmsgid \"Import tables and create documents\"\nmsgstr \"导入表格并创建文档\"\n\n#: apps/knowledge/views/document.py:719 apps/knowledge/views/document.py:720\nmsgid \"Get QA template\"\nmsgstr \"获取问答模板\"\n\n#: apps/knowledge/views/document.py:733 apps/knowledge/views/document.py:734\nmsgid \"Get form template\"\nmsgstr \"获取表格模板\"\n\n#: apps/knowledge/views/knowledge.py:25 apps/knowledge/views/knowledge.py:26\n#: apps/knowledge/views/knowledge.py:27\nmsgid \"Get knowledge by folder\"\nmsgstr \"根据文件夹获取知识库\"\n\n#: apps/knowledge/views/knowledge.py:30 apps/knowledge/views/knowledge.py:59\n#: apps/knowledge/views/knowledge.py:83 apps/knowledge/views/knowledge.py:106\n#: apps/knowledge/views/knowledge.py:127 apps/knowledge/views/knowledge.py:156\n#: apps/knowledge/views/knowledge.py:188 apps/knowledge/views/knowledge.py:218\n#: apps/knowledge/views/knowledge.py:242 apps/knowledge/views/knowledge.py:266\n#: apps/knowledge/views/knowledge.py:293 apps/knowledge/views/knowledge.py:319\n#: apps/knowledge/views/knowledge.py:343 apps/knowledge/views/knowledge.py:369\n#: apps/knowledge/views/knowledge.py:397\n#: apps/xpack/views/dataset_lark_views.py:29\n#: apps/xpack/views/dataset_lark_views.py:50\nmsgid \"Knowledge Base\"\nmsgstr \"知识库\"\n\n#: apps/knowledge/views/knowledge.py:53 apps/knowledge/views/knowledge.py:54\n#: apps/knowledge/views/knowledge.py:55\nmsgid \"Edit knowledge\"\nmsgstr \"修改知识库\"\n\n#: apps/knowledge/views/knowledge.py:77 apps/knowledge/views/knowledge.py:78\n#: apps/knowledge/views/knowledge.py:79\nmsgid \"Delete knowledge\"\nmsgstr \"删除知识库\"\n\n#: apps/knowledge/views/knowledge.py:101 apps/knowledge/views/knowledge.py:102\n#: apps/knowledge/views/knowledge.py:103\nmsgid \"Get knowledge\"\nmsgstr \"获取知识库\"\n\n#: apps/knowledge/views/knowledge.py:122 apps/knowledge/views/knowledge.py:123\n#: apps/knowledge/views/knowledge.py:124\nmsgid \"Get the knowledge base paginated list\"\nmsgstr \"获取知识库分页列表\"\n\n#: apps/knowledge/views/knowledge.py:150 apps/knowledge/views/knowledge.py:151\n#: apps/knowledge/views/knowledge.py:152\nmsgid \"Synchronize the knowledge base of the website\"\nmsgstr \"同步网站知识库\"\n\n#: apps/knowledge/views/knowledge.py:182 apps/knowledge/views/knowledge.py:183\n#: apps/knowledge/views/knowledge.py:184\nmsgid \"Hit test list\"\nmsgstr \"命中测试列表\"\n\n#: apps/knowledge/views/knowledge.py:212 apps/knowledge/views/knowledge.py:213\n#: apps/knowledge/views/knowledge.py:214\nmsgid \"Re-vectorize\"\nmsgstr \"重新向量化\"\n\n#: apps/knowledge/views/knowledge.py:238 apps/knowledge/views/knowledge.py:239\nmsgid \"Export knowledge base\"\nmsgstr \"导出知识库\"\n\n#: apps/knowledge/views/knowledge.py:262 apps/knowledge/views/knowledge.py:263\nmsgid \"Export knowledge base containing images\"\nmsgstr \"导出包含图片的知识库\"\n\n#: apps/knowledge/views/knowledge.py:287 apps/knowledge/views/knowledge.py:288\n#: apps/knowledge/views/knowledge.py:289\nmsgid \"Generate related\"\nmsgstr \"生成相关\"\n\n#: apps/knowledge/views/knowledge.py:314 apps/knowledge/views/knowledge.py:315\n#: apps/knowledge/views/knowledge.py:316\nmsgid \"Get model for knowledge base\"\nmsgstr \"获取知识库模型\"\n\n#: apps/knowledge/views/knowledge.py:338 apps/knowledge/views/knowledge.py:339\n#: apps/knowledge/views/knowledge.py:340\nmsgid \"Get embedding model for knowledge base\"\nmsgstr \"获取知识库向量模型\"\n\n#: apps/knowledge/views/knowledge.py:363 apps/knowledge/views/knowledge.py:364\n#: apps/knowledge/views/knowledge.py:365\nmsgid \"Create base knowledge\"\nmsgstr \"创建知识库\"\n\n#: apps/knowledge/views/knowledge.py:391 apps/knowledge/views/knowledge.py:392\n#: apps/knowledge/views/knowledge.py:393\nmsgid \"Create web knowledge\"\nmsgstr \"创建 web 知识库\"\n\n#: apps/knowledge/views/paragraph.py:24 apps/knowledge/views/paragraph.py:25\n#: apps/knowledge/views/paragraph.py:26\nmsgid \"Paragraph list\"\nmsgstr \"段落列表\"\n\n#: apps/knowledge/views/paragraph.py:29 apps/knowledge/views/paragraph.py:53\n#: apps/knowledge/views/paragraph.py:82 apps/knowledge/views/paragraph.py:102\n#: apps/knowledge/views/paragraph.py:138 apps/knowledge/views/paragraph.py:167\n#: apps/knowledge/views/paragraph.py:199 apps/knowledge/views/paragraph.py:224\n#: apps/knowledge/views/paragraph.py:259 apps/knowledge/views/paragraph.py:289\n#: apps/knowledge/views/paragraph.py:316 apps/knowledge/views/paragraph.py:351\n#: apps/knowledge/views/paragraph.py:385 apps/knowledge/views/paragraph.py:415\nmsgid \"Knowledge Base/Documentation/Paragraph\"\nmsgstr \"知识库/文档/段落\"\n\n#: apps/knowledge/views/paragraph.py:48 apps/knowledge/views/paragraph.py:49\nmsgid \"Create Paragraph\"\nmsgstr \"创建段落\"\n\n#: apps/knowledge/views/paragraph.py:76 apps/knowledge/views/paragraph.py:77\n#: apps/knowledge/views/paragraph.py:78\nmsgid \"Batch Paragraph\"\nmsgstr \"批量段落\"\n\n#: apps/knowledge/views/paragraph.py:97 apps/knowledge/views/paragraph.py:98\nmsgid \"Migrate paragraphs in batches\"\nmsgstr \"批量迁移段落\"\n\n#: apps/knowledge/views/paragraph.py:132 apps/knowledge/views/paragraph.py:133\n#: apps/knowledge/views/paragraph.py:134\nmsgid \"Batch Generate Related\"\nmsgstr \"批量生成相关\"\n\n#: apps/knowledge/views/paragraph.py:161 apps/knowledge/views/paragraph.py:162\n#: apps/knowledge/views/paragraph.py:163\nmsgid \"Modify paragraph data\"\nmsgstr \"修改段落数据\"\n\n#: apps/knowledge/views/paragraph.py:194 apps/knowledge/views/paragraph.py:195\n#: apps/knowledge/views/paragraph.py:196\nmsgid \"Get paragraph details\"\nmsgstr \"获取段落详情\"\n\n#: apps/knowledge/views/paragraph.py:219 apps/knowledge/views/paragraph.py:220\n#: apps/knowledge/views/paragraph.py:221\nmsgid \"Delete paragraph\"\nmsgstr \"删除段落\"\n\n#: apps/knowledge/views/paragraph.py:253 apps/knowledge/views/paragraph.py:254\n#: apps/knowledge/views/paragraph.py:255\nmsgid \"Add associated questions\"\nmsgstr \"添加关联问题\"\n\n#: apps/knowledge/views/paragraph.py:284 apps/knowledge/views/paragraph.py:285\n#: apps/knowledge/views/paragraph.py:286\nmsgid \"Get a list of paragraph questions\"\nmsgstr \"获取段落问题列表\"\n\n#: apps/knowledge/views/paragraph.py:310 apps/knowledge/views/paragraph.py:311\n#: apps/knowledge/views/paragraph.py:312\nmsgid \"Disassociation issue\"\nmsgstr \"取消关联问题\"\n\n#: apps/knowledge/views/paragraph.py:345 apps/knowledge/views/paragraph.py:346\n#: apps/knowledge/views/paragraph.py:347\nmsgid \"Related questions\"\nmsgstr \"关联问题\"\n\n#: apps/knowledge/views/paragraph.py:380 apps/knowledge/views/paragraph.py:381\n#: apps/knowledge/views/paragraph.py:382\nmsgid \"Get paragraph list by pagination\"\nmsgstr \"获取段落列表\"\n\n#: apps/knowledge/views/paragraph.py:409 apps/knowledge/views/paragraph.py:410\n#: apps/knowledge/views/paragraph.py:411\n#: apps/resource_manage/views/paragraph.py:364\n#: apps/resource_manage/views/paragraph.py:365\n#: apps/resource_manage/views/paragraph.py:366\n#: apps/shared/views/shared_paragraph.py:365\n#: apps/shared/views/shared_paragraph.py:366\n#: apps/shared/views/shared_paragraph.py:367\nmsgid \"Adjust paragraph position\"\nmsgstr \"调整段落位置\"\n\n#: apps/knowledge/views/problem.py:23 apps/knowledge/views/problem.py:24\n#: apps/knowledge/views/problem.py:25\nmsgid \"Question list\"\nmsgstr \"问题列表\"\n\n#: apps/knowledge/views/problem.py:28 apps/knowledge/views/problem.py:53\n#: apps/knowledge/views/problem.py:78 apps/knowledge/views/problem.py:104\n#: apps/knowledge/views/problem.py:131 apps/knowledge/views/problem.py:157\n#: apps/knowledge/views/problem.py:186 apps/knowledge/views/problem.py:216\nmsgid \"Knowledge Base/Documentation/Paragraph/Question\"\nmsgstr \"知识库/文档/段落/问题\"\n\n#: apps/knowledge/views/problem.py:47 apps/knowledge/views/problem.py:48\n#: apps/knowledge/views/problem.py:49\nmsgid \"Create question\"\nmsgstr \"创建问题\"\n\n#: apps/knowledge/views/problem.py:73 apps/knowledge/views/problem.py:74\n#: apps/knowledge/views/problem.py:75\nmsgid \"Get a list of associated paragraphs\"\nmsgstr \"获取关联段落列表\"\n\n#: apps/knowledge/views/problem.py:98 apps/knowledge/views/problem.py:99\n#: apps/knowledge/views/problem.py:100\nmsgid \"Batch associated paragraphs\"\nmsgstr \"批量关联段落\"\n\n#: apps/knowledge/views/problem.py:125 apps/knowledge/views/problem.py:126\n#: apps/knowledge/views/problem.py:127\nmsgid \"Batch deletion issues\"\nmsgstr \"批量删除问题\"\n\n#: apps/knowledge/views/problem.py:152 apps/knowledge/views/problem.py:153\n#: apps/knowledge/views/problem.py:154\nmsgid \"Delete question\"\nmsgstr \"删除问题\"\n\n#: apps/knowledge/views/problem.py:180 apps/knowledge/views/problem.py:181\n#: apps/knowledge/views/problem.py:182\nmsgid \"Modify question\"\nmsgstr \"修改问题\"\n\n#: apps/knowledge/views/problem.py:211 apps/knowledge/views/problem.py:212\n#: apps/knowledge/views/problem.py:213\nmsgid \"Get the list of questions by page\"\nmsgstr \"分页获取问题列表\"\n\n#: apps/maxkb/settings/base.py:101\nmsgid \"Intelligent customer service platform\"\nmsgstr \"强大易用的企业级智能体平台\"\n\n#: apps/models_provider/api/model.py:37 apps/models_provider/api/provide.py:17\n#: apps/models_provider/api/provide.py:23\n#: apps/models_provider/api/provide.py:28\n#: apps/models_provider/api/provide.py:30\n#: apps/models_provider/api/provide.py:82\n#: apps/models_provider/serializers/model_serializer.py:40\n#: apps/models_provider/serializers/model_serializer.py:215\n#: apps/models_provider/serializers/model_serializer.py:253\n#: apps/models_provider/serializers/model_serializer.py:318\n#: apps/models_provider/serializers/model_serializer.py:393\n#: apps/shared/api/shared_model.py:18\n#: apps/shared/serializers/shared_model.py:111\nmsgid \"model name\"\nmsgstr \"模型名称\"\n\n#: apps/models_provider/api/model.py:44 apps/models_provider/api/provide.py:29\n#: apps/models_provider/api/provide.py:70\n#: apps/models_provider/api/provide.py:98\n#: apps/models_provider/serializers/model_serializer.py:42\n#: apps/models_provider/serializers/model_serializer.py:217\n#: apps/models_provider/serializers/model_serializer.py:255\n#: apps/models_provider/serializers/model_serializer.py:319\n#: apps/models_provider/serializers/model_serializer.py:394\n#: apps/shared/api/shared_model.py:25\n#: apps/shared/serializers/shared_model.py:112\nmsgid \"model type\"\nmsgstr \"模型类型\"\n\n#: apps/models_provider/api/model.py:51\n#: apps/models_provider/serializers/model_serializer.py:43\n#: apps/models_provider/serializers/model_serializer.py:219\n#: apps/models_provider/serializers/model_serializer.py:256\n#: apps/models_provider/serializers/model_serializer.py:320\n#: apps/models_provider/serializers/model_serializer.py:395\n#: apps/shared/api/shared_model.py:32\n#: apps/shared/serializers/shared_model.py:113\nmsgid \"base model\"\nmsgstr \"基础模型\"\n\n#: apps/models_provider/api/model.py:58 apps/models_provider/api/provide.py:18\n#: apps/models_provider/api/provide.py:38\n#: apps/models_provider/api/provide.py:76\n#: apps/models_provider/api/provide.py:104\n#: apps/models_provider/api/provide.py:126\n#: apps/models_provider/serializers/model_serializer.py:41\n#: apps/models_provider/serializers/model_serializer.py:254\n#: apps/models_provider/serializers/model_serializer.py:321\n#: apps/models_provider/serializers/model_serializer.py:396\n#: apps/shared/api/shared_model.py:39\n#: apps/shared/serializers/shared_model.py:114\nmsgid \"provider\"\nmsgstr \"供应商\"\n\n#: apps/models_provider/api/model.py:65\n#: apps/models_provider/serializers/model_serializer.py:322\n#: apps/models_provider/serializers/model_serializer.py:397\n#: apps/shared/api/shared_model.py:46\n#: apps/shared/serializers/shared_model.py:115\nmsgid \"create user\"\nmsgstr \"创建用户\"\n\n#: apps/models_provider/api/provide.py:19\n#: apps/xpack/serializers/application_setting_serializer.py:41\n#: apps/xpack/serializers/system_params.py:21\nmsgid \"icon\"\nmsgstr \"图标\"\n\n#: apps/models_provider/api/provide.py:34 apps/tools/serializers/tool.py:134\nmsgid \"input type\"\nmsgstr \"输入类型\"\n\n#: apps/models_provider/api/provide.py:35\nmsgid \"label\"\nmsgstr \"标签\"\n\n#: apps/models_provider/api/provide.py:36\nmsgid \"text field\"\nmsgstr \"文本字段\"\n\n#: apps/models_provider/api/provide.py:37\nmsgid \"value field\"\nmsgstr \"值\"\n\n#: apps/models_provider/api/provide.py:39\nmsgid \"method\"\nmsgstr \"方法\"\n\n#: apps/models_provider/api/provide.py:40 apps/tools/serializers/tool.py:119\n#: apps/tools/serializers/tool.py:133\nmsgid \"required\"\nmsgstr \"必填\"\n\n#: apps/models_provider/api/provide.py:41\nmsgid \"default value\"\nmsgstr \"默认值\"\n\n#: apps/models_provider/api/provide.py:42\nmsgid \"relation show field dict\"\nmsgstr \"关系显示字段\"\n\n#: apps/models_provider/api/provide.py:43\nmsgid \"relation trigger field dict\"\nmsgstr \"关系触发字段\"\n\n#: apps/models_provider/api/provide.py:44\nmsgid \"trigger type\"\nmsgstr \"触发类型\"\n\n#: apps/models_provider/api/provide.py:45\nmsgid \"attrs\"\nmsgstr \"属性\"\n\n#: apps/models_provider/api/provide.py:46\nmsgid \"props info\"\nmsgstr \"props 信息\"\n\n#: apps/models_provider/base_model_provider.py:60\nmsgid \"Model type cannot be empty\"\nmsgstr \"模型类型不能为空\"\n\n#: apps/models_provider/base_model_provider.py:85\nmsgid \"The current platform does not support downloading models\"\nmsgstr \"当前平台不支持下载模型\"\n\n#: apps/models_provider/base_model_provider.py:143\nmsgid \"LLM\"\nmsgstr \"大语言模型\"\n\n#: apps/models_provider/base_model_provider.py:144\nmsgid \"Embedding Model\"\nmsgstr \"向量模型\"\n\n#: apps/models_provider/base_model_provider.py:145\nmsgid \"Speech2Text\"\nmsgstr \"语音识别\"\n\n#: apps/models_provider/base_model_provider.py:146\nmsgid \"TTS\"\nmsgstr \"语音合成\"\n\n#: apps/models_provider/base_model_provider.py:147\nmsgid \"Vision Model\"\nmsgstr \"视觉模型\"\n\n#: apps/models_provider/base_model_provider.py:148\nmsgid \"Image Generation\"\nmsgstr \"图片生成\"\n\n#: apps/models_provider/base_model_provider.py:149\nmsgid \"Rerank\"\nmsgstr \"重排模型\"\n\n#: apps/models_provider/base_model_provider.py:223\nmsgid \"The model does not support\"\nmsgstr \"模型不支持\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:42\nmsgid \"\"\n\"With the GTE-Rerank text sorting series model developed by Alibaba Tongyi \"\n\"Lab, developers can integrate high-quality text retrieval and sorting \"\n\"through the LlamaIndex framework.\"\nmsgstr \"\"\n\"阿里巴巴通义实验室开发的GTE-Rerank文本排序系列模型，开发者可以通过LlamaIndex\"\n\"框架进行集成高质量文本检索、排序。\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:45\nmsgid \"\"\n\"Chinese (including various dialects such as Cantonese), English, Japanese, \"\n\"and Korean support free switching between multiple languages.\"\nmsgstr \"中文（含粤语等各种方言）、英文、日语、韩语支持多个语种自由切换\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:48\nmsgid \"\"\n\"CosyVoice is based on a new generation of large generative speech models, \"\n\"which can predict emotions, intonation, rhythm, etc. based on context, and \"\n\"has better anthropomorphic effects.\"\nmsgstr \"\"\n\"CosyVoice基于新一代生成式语音大模型，能根据上下文预测情绪、语调、韵律等，具有\"\n\"更好的拟人效果\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:51\nmsgid \"\"\n\"Universal text vector is Tongyi Lab's multi-language text unified vector \"\n\"model based on the LLM base. It provides high-level vector services for \"\n\"multiple mainstream languages around the world and helps developers quickly \"\n\"convert text data into high-quality vector data.\"\nmsgstr \"\"\n\"通用文本向量，是通义实验室基于LLM底座的多语言文本统一向量模型，面向全球多个主\"\n\"流语种，提供高水准的向量服务，帮助开发者将文本数据快速转换为高质量的向量数\"\n\"据。\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:69\nmsgid \"\"\n\"Tongyi Wanxiang - a large image model for text generation, supports \"\n\"bilingual input in Chinese and English, and supports the input of reference \"\n\"pictures for reference content or reference style migration. Key styles \"\n\"include but are not limited to watercolor, oil painting, Chinese painting, \"\n\"sketch, flat illustration, two-dimensional, and 3D. Cartoon.\"\nmsgstr \"\"\n\"通义万相-文本生成图像大模型，支持中英文双语输入，支持输入参考图片进行参考内容\"\n\"或者参考风格迁移，重点风格包括但不限于水彩、油画、中国画、素描、扁平插画、二\"\n\"次元、3D卡通。\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:95\nmsgid \"Alibaba Cloud Bailian\"\nmsgstr \"阿里云百炼\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py:53\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:50\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:74\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:77\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:61\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py:43\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py:37\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:33\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:34\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:53\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:37\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:40\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:69\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:32\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/gemini_model_provider/model/stt.py:43\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:37\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:37\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:44\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:35\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:35\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:58\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:37\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:58\n#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:23\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:37\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:51\n#: apps/models_provider/impl/tencent_model_provider/model/tti.py:54\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:50\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:32\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/volcanic_engine_model_provider/model/tts.py:77\n#: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:60\n#: apps/models_provider/impl/xf_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:76\n#: apps/models_provider/impl/xf_model_provider/model/tts.py:101\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:32\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:50\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:34\n#: apps/models_provider/impl/xinference_model_provider/model/tts.py:44\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:31\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:56\n#: apps/models_provider/impl/zhipu_model_provider/model/tti.py:49\nmsgid \"Hello\"\nmsgstr \"你好\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:36\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:46\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:44\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:96\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:89\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:23\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:21\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:40\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:30\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/azure_model_provider/credential/stt.py:23\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:58\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:41\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:22\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/gemini_model_provider/credential/stt.py:21\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:28\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/ollama_model_provider/credential/image.py:19\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:44\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:27\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:31\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:25\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:48\n#: apps/models_provider/impl/openai_model_provider/credential/stt.py:22\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:61\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:40\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:25\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:28\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:22\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:61\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:22\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:19\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:28\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:78\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/vllm_model_provider/credential/image.py:22\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:39\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:22\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:25\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:41\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:51\n#: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:46\n#: apps/models_provider/impl/xf_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:29\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/xf_model_provider/credential/stt.py:24\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:47\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:19\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:22\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:39\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:25\n#: apps/models_provider/impl/xinference_model_provider/credential/stt.py:21\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:59\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:39\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:21\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:40\n#, python-brace-format\nmsgid \"{model_type} Model type is not supported\"\nmsgstr \"{model_type} 模型类型不支持\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:44\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:67\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:55\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:53\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:105\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:98\n#, python-brace-format\nmsgid \"{key} is required\"\nmsgstr \"{key} 是必填项\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:60\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:85\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:69\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:67\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:121\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:113\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:43\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:65\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:42\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:61\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:50\n#: apps/models_provider/impl/azure_model_provider/credential/stt.py:40\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:77\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:58\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:65\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:42\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/gemini_model_provider/credential/stt.py:38\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:64\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:44\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:45\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:51\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:45\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:67\n#: apps/models_provider/impl/openai_model_provider/credential/stt.py:39\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:80\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:58\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:45\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:44\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:39\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:80\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:40\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:30\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:47\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:104\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/vllm_model_provider/credential/image.py:42\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:55\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:42\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:42\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:58\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:68\n#: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:38\n#: apps/models_provider/impl/xf_model_provider/credential/embedding.py:38\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:50\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:84\n#: apps/models_provider/impl/xf_model_provider/credential/stt.py:41\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:65\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:41\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:40\n#: apps/models_provider/impl/xinference_model_provider/credential/stt.py:37\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:77\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:56\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:41\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:64\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:59\n#, python-brace-format\nmsgid \"\"\n\"Verification failed, please check whether the parameters are correct: {error}\"\nmsgstr \"认证失败，请检查参数是否正确：{error}\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:17\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:14\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:20\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:14\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:41\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:22\nmsgid \"Temperature\"\nmsgstr \"温度\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:18\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:21\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:16\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:42\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:16\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:23\nmsgid \"\"\n\"Higher values make the output more random, while lower values make it more \"\n\"focused and deterministic\"\nmsgstr \"较高的数值会使输出更加随机，而较低的数值会使其更加集中和确定\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:30\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:43\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:29\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:50\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:31\nmsgid \"Output the maximum Tokens\"\nmsgstr \"输出最大Token数\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:31\nmsgid \"Specify the maximum number of tokens that the model can generate.\"\nmsgstr \"指定模型可以生成的最大 tokens 数\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:43\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:15\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:74\nmsgid \"API URL\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:14\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:15\nmsgid \"Image size\"\nmsgstr \"图片尺寸\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:15\nmsgid \"Specify the size of the generated image, such as: 1024x1024\"\nmsgstr \"指定生成图片的尺寸, 如: 1024x1024\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:40\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:43\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:43\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:41\nmsgid \"Number of pictures\"\nmsgstr \"图片数量\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:40\nmsgid \"Specify the number of generated images\"\nmsgstr \"指定生成图片的数量\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44\nmsgid \"Style\"\nmsgstr \"风格\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44\nmsgid \"Specify the style of generated images\"\nmsgstr \"指定生成图片的风格\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:48\nmsgid \"Default value, the image style is randomly output by the model\"\nmsgstr \"默认值，图片风格由模型随机输出\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:49\nmsgid \"photography\"\nmsgstr \"摄影\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:50\nmsgid \"Portraits\"\nmsgstr \"人像写真\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:51\nmsgid \"3D cartoon\"\nmsgstr \"3D卡通\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:52\nmsgid \"animation\"\nmsgstr \"动画\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:53\nmsgid \"painting\"\nmsgstr \"油画\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:54\nmsgid \"watercolor\"\nmsgstr \"水彩\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:55\nmsgid \"sketch\"\nmsgstr \"素描\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:56\nmsgid \"Chinese painting\"\nmsgstr \"中国画\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:57\nmsgid \"flat illustration\"\nmsgstr \"扁平插画\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20\nmsgid \"Timbre\"\nmsgstr \"音色\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15\nmsgid \"Chinese sounds can support mixed scenes of Chinese and English\"\nmsgstr \"中文音色支持中英文混合场景\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:26\nmsgid \"Long Xiaochun\"\nmsgstr \"龙小淳\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:27\nmsgid \"Long Xiaoxia\"\nmsgstr \"龙小夏\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:28\nmsgid \"Long Xiaochen\"\nmsgstr \"龙小诚\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:29\nmsgid \"Long Xiaobai\"\nmsgstr \"龙小白\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:30\nmsgid \"Long Laotie\"\nmsgstr \"龙老铁\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:31\nmsgid \"Long Shu\"\nmsgstr \"龙书\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:32\nmsgid \"Long Shuo\"\nmsgstr \"龙硕\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:33\nmsgid \"Long Jing\"\nmsgstr \"龙婧\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:34\nmsgid \"Long Miao\"\nmsgstr \"龙妙\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:35\nmsgid \"Long Yue\"\nmsgstr \"龙悦\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:36\nmsgid \"Long Yuan\"\nmsgstr \"龙媛\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:37\nmsgid \"Long Fei\"\nmsgstr \"龙飞\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:38\nmsgid \"Long Jielidou\"\nmsgstr \"龙杰力豆\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:39\nmsgid \"Long Tong\"\nmsgstr \"龙彤\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:40\nmsgid \"Long Xiang\"\nmsgstr \"龙祥\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47\nmsgid \"Speaking speed\"\nmsgstr \"语速\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47\nmsgid \"[0.5, 2], the default is 1, usually one decimal place is enough\"\nmsgstr \"[0.5,2]，默认为1，通常一位小数就足够了\"\n\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:28\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:32\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:35\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:64\n#: apps/models_provider/impl/azure_model_provider/credential/stt.py:28\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:63\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:46\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:27\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/gemini_model_provider/credential/stt.py:26\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:32\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:46\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:62\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:63\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:30\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:53\n#: apps/models_provider/impl/openai_model_provider/credential/stt.py:27\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:66\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:45\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:30\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:32\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:27\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:66\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:27\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:32\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/vllm_model_provider/credential/image.py:27\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:65\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:27\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:30\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:46\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:56\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:55\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:72\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:34\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:71\n#: apps/models_provider/impl/xf_model_provider/credential/stt.py:29\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:52\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:40\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:27\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:29\n#: apps/models_provider/impl/xinference_model_provider/credential/stt.py:26\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:64\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:44\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:26\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:51\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:45\n#, python-brace-format\nmsgid \"{key}  is required\"\nmsgstr \"{key} 是必填项\"\n\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:33\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:44\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:30\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:33\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:25\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:51\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:25\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:32\nmsgid \"Specify the maximum number of tokens that the model can generate\"\nmsgstr \"指定模型可以生成的最大 tokens 数\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:36\nmsgid \"\"\n\"An update to Claude 2 that doubles the context window and improves \"\n\"reliability, hallucination rates, and evidence-based accuracy in long \"\n\"documents and RAG contexts.\"\nmsgstr \"\"\n\"Claude 2 的更新，采用双倍的上下文窗口，并在长文档和 RAG 上下文中提高可靠性、\"\n\"幻觉率和循证准确性。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:43\nmsgid \"\"\n\"Anthropic is a powerful model that can handle a variety of tasks, from \"\n\"complex dialogue and creative content generation to detailed command \"\n\"obedience.\"\nmsgstr \"\"\n\"Anthropic 功能强大的模型，可处理各种任务，从复杂的对话和创意内容生成到详细的\"\n\"指令服从。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:50\nmsgid \"\"\n\"The Claude 3 Haiku is Anthropic's fastest and most compact model, with near-\"\n\"instant responsiveness. The model can answer simple queries and requests \"\n\"quickly. Customers will be able to build seamless AI experiences that mimic \"\n\"human interactions. Claude 3 Haiku can process images and return text \"\n\"output, and provides 200K context windows.\"\nmsgstr \"\"\n\"Claude 3 Haiku 是 Anthropic 最快速、最紧凑的模型，具有近乎即时的响应能力。该\"\n\"模型可以快速回答简单的查询和请求。客户将能够构建模仿人类交互的无缝人工智能体\"\n\"验。 Claude 3 Haiku 可以处理图像和返回文本输出，并且提供 200K 上下文窗口。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:57\nmsgid \"\"\n\"The Claude 3 Sonnet model from Anthropic strikes the ideal balance between \"\n\"intelligence and speed, especially when it comes to handling enterprise \"\n\"workloads. This model offers maximum utility while being priced lower than \"\n\"competing products, and it's been engineered to be a solid choice for \"\n\"deploying AI at scale.\"\nmsgstr \"\"\n\"Anthropic 推出的 Claude 3 Sonnet 模型在智能和速度之间取得理想的平衡，尤其是在\"\n\"处理企业工作负载方面。该模型提供最大的效用，同时价格低于竞争产品，并且其经过\"\n\"精心设计，是大规模部署人工智能的可靠选择。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:64\nmsgid \"\"\n\"The Claude 3.5 Sonnet raises the industry standard for intelligence, \"\n\"outperforming competing models and the Claude 3 Opus in extensive \"\n\"evaluations, with the speed and cost-effectiveness of our mid-range models.\"\nmsgstr \"\"\n\"Claude 3.5 Sonnet提高了智能的行业标准，在广泛的评估中超越了竞争对手的型号和\"\n\"Claude 3 Opus，具有我们中端型号的速度和成本效益。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:71\nmsgid \"\"\n\"A faster, more affordable but still very powerful model that can handle a \"\n\"range of tasks including casual conversation, text analysis, summarization \"\n\"and document question answering.\"\nmsgstr \"\"\n\"一种更快速、更实惠但仍然非常强大的模型，它可以处理一系列任务，包括随意对话、\"\n\"文本分析、摘要和文档问题回答。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:78\nmsgid \"\"\n\"Titan Text Premier is the most powerful and advanced model in the Titan Text \"\n\"series, designed to deliver exceptional performance for a variety of \"\n\"enterprise applications. With its cutting-edge features, it delivers greater \"\n\"accuracy and outstanding results, making it an excellent choice for \"\n\"organizations looking for a top-notch text processing solution.\"\nmsgstr \"\"\n\"Titan Text Premier 是 Titan Text 系列中功能强大且先进的型号，旨在为各种企业应\"\n\"用程序提供卓越的性能。凭借其尖端功能，它提供了更高的准确性和出色的结果，使其\"\n\"成为寻求一流文本处理解决方案的组织的绝佳选择。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:85\nmsgid \"\"\n\"Amazon Titan Text Lite is a lightweight, efficient model ideal for fine-\"\n\"tuning English-language tasks, including summarization and copywriting, \"\n\"where customers require smaller, more cost-effective, and highly \"\n\"customizable models.\"\nmsgstr \"\"\n\"Amazon Titan Text Lite 是一种轻量级的高效模型，非常适合英语任务的微调，包括摘\"\n\"要和文案写作等，在这种场景下，客户需要更小、更经济高效且高度可定制的模型\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:91\nmsgid \"\"\n\"Amazon Titan Text Express has context lengths of up to 8,000 tokens, making \"\n\"it ideal for a variety of high-level general language tasks, such as open-\"\n\"ended text generation and conversational chat, as well as support in \"\n\"retrieval-augmented generation (RAG). At launch, the model is optimized for \"\n\"English, but other languages are supported.\"\nmsgstr \"\"\n\"Amazon Titan Text Express 的上下文长度长达 8000 个 tokens，因而非常适合各种高\"\n\"级常规语言任务，例如开放式文本生成和对话式聊天，以及检索增强生成（RAG）中的支\"\n\"持。在发布时，该模型针对英语进行了优化，但也支持其他语言。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:97\nmsgid \"\"\n\"7B dense converter for rapid deployment and easy customization. Small in \"\n\"size yet powerful in a variety of use cases. Supports English and code, as \"\n\"well as 32k context windows.\"\nmsgstr \"\"\n\"7B 密集型转换器，可快速部署，易于定制。体积虽小，但功能强大，适用于各种用例。\"\n\"支持英语和代码，以及 32k 的上下文窗口。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:103\nmsgid \"\"\n\"Advanced Mistral AI large-scale language model capable of handling any \"\n\"language task, including complex multilingual reasoning, text understanding, \"\n\"transformation, and code generation.\"\nmsgstr \"\"\n\"先进的 Mistral AI 大型语言模型，能够处理任何语言任务，包括复杂的多语言推理、\"\n\"文本理解、转换和代码生成。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:109\nmsgid \"\"\n\"Ideal for content creation, conversational AI, language understanding, R&D, \"\n\"and enterprise applications\"\nmsgstr \"非常适合内容创作、对话式人工智能、语言理解、研发和企业智能体\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:115\nmsgid \"\"\n\"Ideal for limited computing power and resources, edge devices, and faster \"\n\"training times.\"\nmsgstr \"非常适合有限的计算能力和资源、边缘设备和更快的训练时间。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:123\nmsgid \"\"\n\"Titan Embed Text is the largest embedding model in the Amazon Titan Embed \"\n\"series and can handle various text embedding tasks, such as text \"\n\"classification, text similarity calculation, etc.\"\nmsgstr \"\"\n\"Titan Embed Text 是 Amazon Titan Embed 系列中最大的嵌入模型，可以处理各种文本\"\n\"嵌入任务，如文本分类、文本相似度计算等。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:28\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:47\n#, python-brace-format\nmsgid \"The following fields are required: {keys}\"\nmsgstr \"以下字段是必填项: {keys}\"\n\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:44\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:76\nmsgid \"Verification failed, please check whether the parameters are correct\"\nmsgstr \"认证失败，请检查参数是否正确\"\n\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:28\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:29\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:28\nmsgid \"Picture quality\"\nmsgstr \"图片质量\"\n\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:17\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:17\nmsgid \"\"\n\"Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) \"\n\"to find one that suits your desired tone and audience. The current voiceover \"\n\"is optimized for English.\"\nmsgstr \"\"\n\"尝试不同的声音（合金、回声、寓言、缟玛瑙、新星和闪光），找到一种适合您所需的\"\n\"音调和听众的声音。当前的语音针对英语进行了优化。\"\n\n#: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:24\nmsgid \"Good at common conversational tasks, supports 32K contexts\"\nmsgstr \"擅长通用对话任务，支持 32K 上下文\"\n\n#: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:29\nmsgid \"Good at handling programming tasks, supports 16K contexts\"\nmsgstr \"擅长处理编程任务，支持 16K 上下文\"\n\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:32\nmsgid \"Latest Gemini 1.0 Pro model, updated with Google update\"\nmsgstr \"最新的 Gemini 1.0 Pro 模型，更新了 Google 更新\"\n\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:36\nmsgid \"Latest Gemini 1.0 Pro Vision model, updated with Google update\"\nmsgstr \"最新的Gemini 1.0 Pro Vision模型，随Google更新而更新\"\n\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:43\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:47\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:54\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:58\nmsgid \"Latest Gemini 1.5 Flash model, updated with Google updates\"\nmsgstr \"最新的Gemini 1.5 Flash模型，随Google更新而更新\"\n\n#: apps/models_provider/impl/gemini_model_provider/model/stt.py:53\nmsgid \"convert audio to text\"\nmsgstr \"将音频转换为文本\"\n\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:53\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:54\nmsgid \"Model catalog\"\nmsgstr \"模型目录\"\n\n#: apps/models_provider/impl/local_model_provider/local_model_provider.py:39\nmsgid \"local model\"\nmsgstr \"本地模型\"\n\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:30\n#: apps/models_provider/impl/ollama_model_provider/credential/image.py:23\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:48\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:35\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:43\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:24\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:44\nmsgid \"API domain name is invalid\"\nmsgstr \"API 域名无效\"\n\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:35\n#: apps/models_provider/impl/ollama_model_provider/credential/image.py:28\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:53\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:40\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:30\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:48\nmsgid \"The model does not exist, please download the model first\"\nmsgstr \"模型不存在，请先下载模型\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:56\nmsgid \"\"\n\"Llama 2 is a set of pretrained and fine-tuned generative text models ranging \"\n\"in size from 7 billion to 70 billion. This is a repository of 7B pretrained \"\n\"models. Links to other models can be found in the index at the bottom.\"\nmsgstr \"\"\n\"Llama 2 是一组经过预训练和微调的生成文本模型，其规模从 70 亿到 700 亿个不等。\"\n\"这是 7B 预训练模型的存储库。其他模型的链接可以在底部的索引中找到。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:60\nmsgid \"\"\n\"Llama 2 is a set of pretrained and fine-tuned generative text models ranging \"\n\"in size from 7 billion to 70 billion. This is a repository of 13B pretrained \"\n\"models. Links to other models can be found in the index at the bottom.\"\nmsgstr \"\"\n\"Llama 2 是一组经过预训练和微调的生成文本模型，其规模从 70 亿到 700 亿个不等。\"\n\"这是 13B 预训练模型的存储库。其他模型的链接可以在底部的索引中找到。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:64\nmsgid \"\"\n\"Llama 2 is a set of pretrained and fine-tuned generative text models ranging \"\n\"in size from 7 billion to 70 billion. This is a repository of 70B pretrained \"\n\"models. Links to other models can be found in the index at the bottom.\"\nmsgstr \"\"\n\"Llama 2 是一组经过预训练和微调的生成文本模型，其规模从 70 亿到 700 亿个不等。\"\n\"这是 70B 预训练模型的存储库。其他模型的链接可以在底部的索引中找到。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:68\nmsgid \"\"\n\"Since the Chinese alignment of Llama2 itself is weak, we use the Chinese \"\n\"instruction set to fine-tune meta-llama/Llama-2-13b-chat-hf with LoRA so \"\n\"that it has strong Chinese conversation capabilities.\"\nmsgstr \"\"\n\"由于Llama2本身的中文对齐较弱，我们采用中文指令集，对meta-llama/Llama-2-13b-\"\n\"chat-hf进行LoRA微调，使其具备较强的中文对话能力。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:72\nmsgid \"\"\n\"Meta Llama 3: The most capable public product LLM to date. 8 billion \"\n\"parameters.\"\nmsgstr \"Meta Llama 3：迄今为止最有能力的公开产品LLM。80亿参数。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:76\nmsgid \"\"\n\"Meta Llama 3: The most capable public product LLM to date. 70 billion \"\n\"parameters.\"\nmsgstr \"Meta Llama 3：迄今为止最有能力的公开产品LLM。700亿参数。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:80\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 0.5b has significantly enhanced \"\n\"the model's alignment with human preferences and its multi-language \"\n\"processing capabilities. Models of all sizes support a context length of \"\n\"32768 tokens. 500 million parameters.\"\nmsgstr \"\"\n\"qwen 1.5 0.5b 相较于以往版本，模型与人类偏好的对齐程度以及多语言处理能力上有\"\n\"显著增强。所有规模的模型都支持32768个tokens的上下文长度。5亿参数。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:84\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 1.8b has significantly enhanced \"\n\"the model's alignment with human preferences and its multi-language \"\n\"processing capabilities. Models of all sizes support a context length of \"\n\"32768 tokens. 1.8 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 1.8b 相较于以往版本，模型与人类偏好的对齐程度以及多语言处理能力上有\"\n\"显著增强。所有规模的模型都支持32768个tokens的上下文长度。18亿参数。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:88\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 4b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"4 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 4b 相较于以往版本，模型与人类偏好的对齐程度以及多语言处理能力上有显\"\n\"著增强。所有规模的模型都支持32768个tokens的上下文长度。40亿参数。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:93\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 7b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"7 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 7b 相较于以往版本，模型与人类偏好的对齐程度以及多语言处理能力上有显\"\n\"著增强。所有规模的模型都支持32768个tokens的上下文长度。70亿参数。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:97\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 14b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"14 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 14b 相较于以往版本，模型与人类偏好的对齐程度以及多语言处理能力上有显\"\n\"著增强。所有规模的模型都支持32768个tokens的上下文长度。140亿参数。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:101\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 32b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"32 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 32b 相较于以往版本，模型与人类偏好的对齐程度以及多语言处理能力上有显\"\n\"著增强。所有规模的模型都支持32768个tokens的上下文长度。320亿参数。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:105\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 72b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"72 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 72b 相较于以往版本，模型与人类偏好的对齐程度以及多语言处理能力上有显\"\n\"著增强。所有规模的模型都支持32768个tokens的上下文长度。720亿参数。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:109\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 110b has significantly enhanced \"\n\"the model's alignment with human preferences and its multi-language \"\n\"processing capabilities. Models of all sizes support a context length of \"\n\"32768 tokens. 110 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 110b 相较于以往版本，模型与人类偏好的对齐程度以及多语言处理能力上有\"\n\"显著增强。所有规模的模型都支持32768个tokens的上下文长度。1100亿参数。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:153\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:193\nmsgid \"\"\n\"Phi-3 Mini is Microsoft's 3.8B parameter, lightweight, state-of-the-art open \"\n\"model.\"\nmsgstr \"Phi-3 Mini是Microsoft的3.8B参数，轻量级，最先进的开放模型。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:162\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:197\nmsgid \"\"\n\"A high-performance open embedding model with a large token context window.\"\nmsgstr \"一个具有大 tokens上下文窗口的高性能开放嵌入模型。\"\n\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:16\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:16\nmsgid \"\"\n\"The image generation endpoint allows you to create raw images based on text \"\n\"prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 \"\n\"or 1792x1024 pixels.\"\nmsgstr \"\"\n\"图像生成端点允许您根据文本提示创建原始图像。使用 DALL·E 3 时，图像的尺寸可以\"\n\"为 1024x1024、1024x1792 或 1792x1024 像素。\"\n\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:29\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29\nmsgid \"\"\n\"       \\n\"\n\"By default, images are produced in standard quality, but with DALL·E 3 you \"\n\"can set quality: \\\"hd\\\" to enhance detail. Square, standard quality images \"\n\"are generated fastest.\\n\"\n\"        \"\nmsgstr \"\"\n\"默认情况下，图像以标准质量生成，但使用 DALL·E 3 时，您可以设置质量：“hd”以增\"\n\"强细节。方形、标准质量的图像生成速度最快。\"\n\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:44\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:44\nmsgid \"\"\n\"You can use DALL·E 3 to request 1 image at a time (requesting more images by \"\n\"issuing parallel requests), or use DALL·E 2 with the n parameter to request \"\n\"up to 10 images at a time.\"\nmsgstr \"\"\n\"您可以使用 DALL·E 3 一次请求 1 个图像（通过发出并行请求来请求更多图像），或者\"\n\"使用带有 n 参数的 DALL·E 2 一次最多请求 10 个图像。\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:35\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:119\n#: apps/models_provider/impl/siliconCloud_model_provider/siliconCloud_model_provider.py:118\nmsgid \"The latest gpt-3.5-turbo, updated with OpenAI adjustments\"\nmsgstr \"最新的gpt-3.5-turbo，随OpenAI调整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:38\nmsgid \"Latest gpt-4, updated with OpenAI adjustments\"\nmsgstr \"最新的gpt-4，随OpenAI调整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:40\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:99\nmsgid \"\"\n\"The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with OpenAI \"\n\"adjustments\"\nmsgstr \"最新的GPT-4o，比gpt-4-turbo更便宜、更快，随OpenAI调整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:43\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:102\nmsgid \"\"\n\"The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with OpenAI \"\n\"adjustments\"\nmsgstr \"最新的gpt-4o-mini，比gpt-4o更便宜、更快，随OpenAI调整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:46\nmsgid \"The latest gpt-4-turbo, updated with OpenAI adjustments\"\nmsgstr \"最新的gpt-4-turbo，随OpenAI调整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:49\nmsgid \"The latest gpt-4-turbo-preview, updated with OpenAI adjustments\"\nmsgstr \"最新的gpt-4-turbo-preview，随OpenAI调整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:53\nmsgid \"\"\n\"gpt-3.5-turbo snapshot on January 25, 2024, supporting context length 16,385 \"\n\"tokens\"\nmsgstr \"2024年1月25日的gpt-3.5-turbo快照，支持上下文长度16,385 tokens\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:57\nmsgid \"\"\n\"gpt-3.5-turbo snapshot on November 6, 2023, supporting context length 16,385 \"\n\"tokens\"\nmsgstr \"2023年11月6日的gpt-3.5-turbo快照，支持上下文长度16,385 tokens\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:61\nmsgid \"\"\n\"[Legacy] gpt-3.5-turbo snapshot on June 13, 2023, will be deprecated on June \"\n\"13, 2024\"\nmsgstr \"[Legacy] 2023年6月13日的gpt-3.5-turbo快照，将于2024年6月13日弃用\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:65\nmsgid \"\"\n\"gpt-4o snapshot on May 13, 2024, supporting context length 128,000 tokens\"\nmsgstr \"2024年5月13日的gpt-4o快照，支持上下文长度128,000 tokens\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:69\nmsgid \"\"\n\"gpt-4-turbo snapshot on April 9, 2024, supporting context length 128,000 \"\n\"tokens\"\nmsgstr \"2024年4月9日的gpt-4-turbo快照，支持上下文长度128,000 tokens\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:72\nmsgid \"\"\n\"gpt-4-turbo snapshot on January 25, 2024, supporting context length 128,000 \"\n\"tokens\"\nmsgstr \"2024年1月25日的gpt-4-turbo快照，支持上下文长度128,000 tokens\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:75\nmsgid \"\"\n\"gpt-4-turbo snapshot on November 6, 2023, supporting context length 128,000 \"\n\"tokens\"\nmsgstr \"2023年11月6日的gpt-4-turbo快照，支持上下文长度128,000 tokens\"\n\n#: apps/models_provider/impl/tencent_cloud_model_provider/tencent_cloud_model_provider.py:58\nmsgid \"Tencent Cloud\"\nmsgstr \"腾讯云\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:41\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:88\n#, python-brace-format\nmsgid \"{keys} is required\"\nmsgstr \"{keys} 是必填项\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14\nmsgid \"painting style\"\nmsgstr \"绘画风格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14\nmsgid \"If not passed, the default value is 201 (Japanese anime style)\"\nmsgstr \"如果未传递，则默认值为201（日本动漫风格）\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:18\nmsgid \"Not limited to style\"\nmsgstr \"不限于风格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:19\nmsgid \"ink painting\"\nmsgstr \"水墨画\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:20\nmsgid \"concept art\"\nmsgstr \"概念艺术\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:21\nmsgid \"Oil painting 1\"\nmsgstr \"油画1\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:22\nmsgid \"Oil Painting 2 (Van Gogh)\"\nmsgstr \"油画2（梵高）\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:23\nmsgid \"watercolor painting\"\nmsgstr \"水彩画\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:24\nmsgid \"pixel art\"\nmsgstr \"像素画\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:25\nmsgid \"impasto style\"\nmsgstr \"厚涂风格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:26\nmsgid \"illustration\"\nmsgstr \"插图\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:27\nmsgid \"paper cut style\"\nmsgstr \"剪纸风格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:28\nmsgid \"Impressionism 1 (Monet)\"\nmsgstr \"印象派1（莫奈）\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:29\nmsgid \"Impressionism 2\"\nmsgstr \"印象派2\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:31\nmsgid \"classical portraiture\"\nmsgstr \"古典肖像画\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:32\nmsgid \"black and white sketch\"\nmsgstr \"黑白素描画\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:33\nmsgid \"cyberpunk\"\nmsgstr \"赛博朋克\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:34\nmsgid \"science fiction style\"\nmsgstr \"科幻风格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:35\nmsgid \"dark style\"\nmsgstr \"暗黑风格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:37\nmsgid \"vaporwave\"\nmsgstr \"蒸汽波\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:38\nmsgid \"Japanese animation\"\nmsgstr \"日系动漫\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:39\nmsgid \"monster style\"\nmsgstr \"怪兽风格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:40\nmsgid \"Beautiful ancient style\"\nmsgstr \"唯美古风\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:41\nmsgid \"retro anime\"\nmsgstr \"复古动漫\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:42\nmsgid \"Game cartoon hand drawing\"\nmsgstr \"游戏卡通手绘\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:43\nmsgid \"Universal realistic style\"\nmsgstr \"通用写实风格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50\nmsgid \"Generate image resolution\"\nmsgstr \"生成图像分辨率\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50\nmsgid \"If not transmitted, the default value is 768:768.\"\nmsgstr \"不传默认使用768:768。\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:38\nmsgid \"\"\n\"The most effective version of the current hybrid model, the trillion-level \"\n\"parameter scale MOE-32K long article model. Reaching the absolute leading \"\n\"level on various benchmarks, with complex instructions and reasoning, \"\n\"complex mathematical capabilities, support for function call, and \"\n\"application focus optimization in fields such as multi-language translation, \"\n\"finance, law, and medical care\"\nmsgstr \"\"\n\"当前混元模型中效果最优版本，万亿级参数规模 MOE-32K 长文模型。在各种 \"\n\"benchmark 上达到绝对领先的水平，复杂指令和推理，具备复杂数学能力，支持 \"\n\"functioncall，在多语言翻译、金融法律医疗等领域智能体重点优化\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:45\nmsgid \"\"\n\"A better routing strategy is adopted to simultaneously alleviate the \"\n\"problems of load balancing and expert convergence. For long articles, the \"\n\"needle-in-a-haystack index reaches 99.9%\"\nmsgstr \"\"\n\"采用更优的路由策略，同时缓解了负载均衡和专家趋同的问题。长文方面，大海捞针指\"\n\"标达到99.9%\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:51\nmsgid \"\"\n\"Upgraded to MOE structure, the context window is 256k, leading many open \"\n\"source models in multiple evaluation sets such as NLP, code, mathematics, \"\n\"industry, etc.\"\nmsgstr \"\"\n\"升级为 MOE 结构，上下文窗口为 256k ，在 NLP，代码，数学，行业等多项评测集上领\"\n\"先众多开源模型\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:57\nmsgid \"\"\n\"Hunyuan's latest version of the role-playing model, a role-playing model \"\n\"launched by Hunyuan's official fine-tuning training, is based on the Hunyuan \"\n\"model combined with the role-playing scene data set for additional training, \"\n\"and has better basic effects in role-playing scenes.\"\nmsgstr \"\"\n\"混元最新版角色扮演模型，混元官方精调训练推出的角色扮演模型，基于混元模型结合\"\n\"角色扮演场景数据集进行增训，在角色扮演场景具有更好的基础效果\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:63\nmsgid \"\"\n\"Hunyuan's latest MOE architecture FunctionCall model has been trained with \"\n\"high-quality FunctionCall data and has a context window of 32K, leading in \"\n\"multiple dimensions of evaluation indicators.\"\nmsgstr \"\"\n\"混元最新 MOE 架构 FunctionCall 模型，经过高质量的 FunctionCall 数据训练，上下\"\n\"文窗口达 32K，在多个维度的评测指标上处于领先。\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:69\nmsgid \"\"\n\"Hunyuan's latest code generation model, after training the base model with \"\n\"200B high-quality code data, and iterating on high-quality SFT data for half \"\n\"a year, the context long window length has been increased to 8K, and it \"\n\"ranks among the top in the automatic evaluation indicators of code \"\n\"generation in the five major languages; the five major languages In the \"\n\"manual high-quality evaluation of 10 comprehensive code tasks that consider \"\n\"all aspects, the performance is in the first echelon.\"\nmsgstr \"\"\n\"混元最新代码生成模型，经过 200B 高质量代码数据增训基座模型，迭代半年高质量 \"\n\"SFT 数据训练，上下文长窗口长度增大到 8K，五大语言代码生成自动评测指标上位居前\"\n\"列；五大语言10项考量各方面综合代码任务人工高质量评测上，性能处于第一梯队\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:77\nmsgid \"\"\n\"Tencent's Hunyuan Embedding interface can convert text into high-quality \"\n\"vector data. The vector dimension is 1024 dimensions.\"\nmsgstr \"\"\n\"腾讯混元 Embedding 接口，可以将文本转化为高质量的向量数据。向量维度为1024维。\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:87\nmsgid \"Mixed element visual model\"\nmsgstr \"混元视觉模型\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:94\nmsgid \"Hunyuan graph model\"\nmsgstr \"混元生图模型\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:125\nmsgid \"Tencent Hunyuan\"\nmsgstr \"腾讯混元\"\n\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:24\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:42\nmsgid \"Facebook’s 125M parameter model\"\nmsgstr \"Facebook的125M参数模型\"\n\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:25\nmsgid \"BAAI’s 7B parameter model\"\nmsgstr \"BAAI的7B参数模型\"\n\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:26\nmsgid \"BAAI’s 13B parameter mode\"\nmsgstr \"BAAI的13B参数模型\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:16\nmsgid \"\"\n\"If the gap between width, height and 512 is too large, the picture rendering \"\n\"effect will be poor and the probability of excessive delay will increase \"\n\"significantly. Recommended ratio and corresponding width and height before \"\n\"super score: width*height\"\nmsgstr \"\"\n\"宽、高与512差距过大，则出图效果不佳、延迟过长概率显著增加。超分前建议比例及对\"\n\"应宽高：width*height\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:15\nmsgid \"timbre\"\nmsgstr \"音色\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:28\nmsgid \"speaking speed\"\nmsgstr \"语速\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31\nmsgid \"[0.2,3], the default is 1, usually one decimal place is enough\"\nmsgstr \"[0.2,3]，默认为1，通常保留一位小数即可\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:39\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:44\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:88\nmsgid \"\"\n\"The user goes to the model inference page of Volcano Ark to create an \"\n\"inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call \"\n\"it.\"\nmsgstr \"\"\n\"用户前往火山方舟的模型推理页面创建推理接入点，这里需要输入ep-xxxxxxxxxx-yyyy\"\n\"进行调用\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:59\nmsgid \"Universal 2.0-Vincent Diagram\"\nmsgstr \"通用2.0-文生图\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:64\nmsgid \"Universal 2.0Pro-Vincent Chart\"\nmsgstr \"通用2.0Pro-文生图\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:69\nmsgid \"Universal 1.4-Vincent Chart\"\nmsgstr \"通用1.4-文生图\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:74\nmsgid \"Animation 1.3.0-Vincent Picture\"\nmsgstr \"动漫1.3.0-文生图\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:79\nmsgid \"Animation 1.3.1-Vincent Picture\"\nmsgstr \"动漫1.3.1-文生图\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:113\nmsgid \"volcano engine\"\nmsgstr \"火山引擎\"\n\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:51\n#, python-brace-format\nmsgid \"{model_name} The model does not support\"\nmsgstr \"{model_name} 模型不支持\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:24\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:53\nmsgid \"\"\n\"ERNIE-Bot-4 is a large language model independently developed by Baidu. It \"\n\"covers massive Chinese data and has stronger capabilities in dialogue Q&A, \"\n\"content creation and generation.\"\nmsgstr \"\"\n\"ERNIE-Bot-4是百度自行研发的大语言模型，覆盖海量中文数据，具有更强的对话问答、\"\n\"内容创作生成等能力。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:27\nmsgid \"\"\n\"ERNIE-Bot is a large language model independently developed by Baidu. It \"\n\"covers massive Chinese data and has stronger capabilities in dialogue Q&A, \"\n\"content creation and generation.\"\nmsgstr \"\"\n\"ERNIE-Bot是百度自行研发的大语言模型，覆盖海量中文数据，具有更强的对话问答、内\"\n\"容创作生成等能力。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:30\nmsgid \"\"\n\"ERNIE-Bot-turbo is a large language model independently developed by Baidu. \"\n\"It covers massive Chinese data, has stronger capabilities in dialogue Q&A, \"\n\"content creation and generation, and has a faster response speed.\"\nmsgstr \"\"\n\"ERNIE-Bot-turbo是百度自行研发的大语言模型，覆盖海量中文数据，具有更强的对话问\"\n\"答、内容创作生成等能力，响应速度更快。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:33\nmsgid \"\"\n\"BLOOMZ-7B is a well-known large language model in the industry. It was \"\n\"developed and open sourced by BigScience and can output text in 46 languages \"\n\"and 13 programming languages.\"\nmsgstr \"\"\n\"BLOOMZ-7B是业内知名的大语言模型，由BigScience研发并开源，能够以46种语言和13种\"\n\"编程语言输出文本。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:39\nmsgid \"\"\n\"Llama-2-13b-chat was developed by Meta AI and is open source. It performs \"\n\"well in scenarios such as coding, reasoning and knowledge application. \"\n\"Llama-2-13b-chat is a native open source version with balanced performance \"\n\"and effect, suitable for conversation scenarios.\"\nmsgstr \"\"\n\"Llama-2-13b-chat由Meta AI研发并开源，在编码、推理及知识智能体等场景表现优秀，\"\n\"Llama-2-13b-chat是性能与效果均衡的原生开源版本，适用于对话场景。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:42\nmsgid \"\"\n\"Llama-2-70b-chat was developed by Meta AI and is open source. It performs \"\n\"well in scenarios such as coding, reasoning, and knowledge application. \"\n\"Llama-2-70b-chat is a native open source version with high-precision effects.\"\nmsgstr \"\"\n\"Llama-2-70b-chat由Meta AI研发并开源，在编码、推理及知识智能体等场景表现优秀，\"\n\"Llama-2-70b-chat是高精度效果的原生开源版本。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:45\nmsgid \"\"\n\"The Chinese enhanced version developed by the Qianfan team based on \"\n\"Llama-2-7b has performed well on Chinese knowledge bases such as CMMLU and C-\"\n\"EVAL.\"\nmsgstr \"\"\n\"千帆团队在Llama-2-7b基础上的中文增强版本，在CMMLU、C-EVAL等中文知识库上表现优\"\n\"异。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:49\nmsgid \"\"\n\"Embedding-V1 is a text representation model based on Baidu Wenxin large \"\n\"model technology. It can convert text into a vector form represented by \"\n\"numerical values and can be used in text retrieval, information \"\n\"recommendation, knowledge mining and other scenarios. Embedding-V1 provides \"\n\"the Embeddings interface, which can generate corresponding vector \"\n\"representations based on input content. You can call this interface to input \"\n\"text into the model and obtain the corresponding vector representation for \"\n\"subsequent text processing and analysis.\"\nmsgstr \"\"\n\"Embedding-V1是一个基于百度文心大模型技术的文本表示模型，可以将文本转化为用数\"\n\"值表示的向量形式，用于文本检索、信息推荐、知识挖掘等场景。 Embedding-V1提供了\"\n\"Embeddings接口，可以根据输入内容生成对应的向量表示。您可以通过调用该接口，将\"\n\"文本输入到模型中，获取到对应的向量表示，从而进行后续的文本处理和分析。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:66\nmsgid \"Thousand sails large model\"\nmsgstr \"千帆大模型\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:42\nmsgid \"Please outline this picture\"\nmsgstr \"请描述这张图片\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:15\nmsgid \"Speaker\"\nmsgstr \"发音人\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:16\nmsgid \"\"\n\"Speaker, optional value: Please go to the console to add a trial or purchase \"\n\"speaker. After adding, the speaker parameter value will be displayed.\"\nmsgstr \"\"\n\"发音人，可选值：请到控制台添加试用或购买发音人，添加后即显示发音人参数值\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:21\nmsgid \"iFlytek Xiaoyan\"\nmsgstr \"讯飞小燕\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:22\nmsgid \"iFlytek Xujiu\"\nmsgstr \"讯飞许久\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:23\nmsgid \"iFlytek Xiaoping\"\nmsgstr \"讯飞小萍\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:24\nmsgid \"iFlytek Xiaojing\"\nmsgstr \"讯飞小婧\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:25\nmsgid \"iFlytek Xuxiaobao\"\nmsgstr \"讯飞许小宝\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:28\nmsgid \"Speech speed, optional value: [0-100], default is 50\"\nmsgstr \"语速，可选值：[0-100]，默认为50\"\n\n#: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:39\n#: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:50\nmsgid \"Chinese and English recognition\"\nmsgstr \"中英文识别\"\n\n#: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:66\nmsgid \"iFlytek Spark\"\nmsgstr \"讯飞星火\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:15\nmsgid \"\"\n\"The image generation endpoint allows you to create raw images based on text \"\n\"prompts. The dimensions of the image can be 1024x1024, 1024x1792, or \"\n\"1792x1024 pixels.\"\nmsgstr \"\"\n\"图像生成端点允许您根据文本提示创建原始图像。图像的尺寸可以为 1024x1024、\"\n\"1024x1792 或 1792x1024 像素。\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:29\nmsgid \"\"\n\"By default, images are generated in standard quality, you can set quality: \"\n\"\\\"hd\\\" to enhance detail. Square, standard quality images are generated \"\n\"fastest.\"\nmsgstr \"\"\n\"默认情况下，图像以标准质量生成，您可以设置质量：“hd”以增强细节。方形、标准质\"\n\"量的图像生成速度最快。\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:42\nmsgid \"\"\n\"You can request 1 image at a time (requesting more images by making parallel \"\n\"requests), or up to 10 images at a time using the n parameter.\"\nmsgstr \"\"\n\"您可以一次请求 1 个图像（通过发出并行请求来请求更多图像），或者使用 n 参数一\"\n\"次最多请求 10 个图像。\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:20\nmsgid \"Chinese female\"\nmsgstr \"中文女\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:21\nmsgid \"Chinese male\"\nmsgstr \"中文男\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:22\nmsgid \"Japanese male\"\nmsgstr \"日语男\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:23\nmsgid \"Cantonese female\"\nmsgstr \"粤语女\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:24\nmsgid \"English female\"\nmsgstr \"英文女\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:25\nmsgid \"English male\"\nmsgstr \"英文男\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:26\nmsgid \"Korean female\"\nmsgstr \"韩语女\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:37\nmsgid \"\"\n\"Code Llama is a language model specifically designed for code generation.\"\nmsgstr \"Code Llama 是一个专门用于代码生成的语言模型。\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:44\nmsgid \"\"\n\"       \\n\"\n\"Code Llama Instruct is a fine-tuned version of Code Llama's instructions, \"\n\"designed to perform specific tasks.\\n\"\n\"        \"\nmsgstr \"\"\n\"Code Llama Instruct 是 Code Llama 的指令微调版本，专为执行特定任务而设计。\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:53\nmsgid \"\"\n\"Code Llama Python is a language model specifically designed for Python code \"\n\"generation.\"\nmsgstr \"Code Llama Python 是一个专门用于 Python 代码生成的语言模型。\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:60\nmsgid \"\"\n\"CodeQwen 1.5 is a language model for code generation with high performance.\"\nmsgstr \"CodeQwen 1.5 是一个用于代码生成的语言模型，具有较高的性能。\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:67\nmsgid \"CodeQwen 1.5 Chat is a chat model version of CodeQwen 1.5.\"\nmsgstr \"CodeQwen 1.5 Chat 是一个聊天模型版本的 CodeQwen 1.5。\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:74\nmsgid \"Deepseek is a large-scale language model with 13 billion parameters.\"\nmsgstr \"Deepseek Chat 是一个聊天模型版本的 Deepseek。\"\n\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:16\nmsgid \"\"\n\"Image size, only cogview-3-plus supports this parameter. Optional range: \"\n\"[1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440], the \"\n\"default is 1024x1024.\"\nmsgstr \"\"\n\"图片尺寸，仅 cogview-3-plus 支持该参数。可选范围：\"\n\"[1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440]，默认是\"\n\"1024x1024。\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:34\nmsgid \"\"\n\"Have strong multi-modal understanding capabilities. Able to understand up to \"\n\"five images simultaneously and supports video content understanding\"\nmsgstr \"具有强大的多模态理解能力。能够同时理解多达五张图像，并支持视频内容理解\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:37\nmsgid \"\"\n\"Focus on single picture understanding. Suitable for scenarios requiring \"\n\"efficient image analysis\"\nmsgstr \"专注于单图理解。适用于需要高效图像解析的场景\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:40\nmsgid \"\"\n\"Focus on single picture understanding. Suitable for scenarios requiring \"\n\"efficient image analysis (free)\"\nmsgstr \"专注于单图理解。适用于需要高效图像解析的场景(免费)\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:46\nmsgid \"\"\n\"Quickly and accurately generate images based on user text descriptions. \"\n\"Resolution supports 1024x1024\"\nmsgstr \"根据用户文字描述快速、精准生成图像。分辨率支持1024x1024\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:49\nmsgid \"\"\n\"Generate high-quality images based on user text descriptions, supporting \"\n\"multiple image sizes\"\nmsgstr \"根据用户文字描述生成高质量图像，支持多图片尺寸\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:52\nmsgid \"\"\n\"Generate high-quality images based on user text descriptions, supporting \"\n\"multiple image sizes (free)\"\nmsgstr \"根据用户文字描述生成高质量图像，支持多图片尺寸(免费)\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:75\nmsgid \"zhipu AI\"\nmsgstr \"智谱 AI\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:32\n#: apps/models_provider/serializers/model_apply_serializers.py:37\nmsgid \"vector text\"\nmsgstr \"向量文本\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:33\nmsgid \"vector text list\"\nmsgstr \"向量文本列表\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:41\nmsgid \"text\"\nmsgstr \"文本\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:42\nmsgid \"metadata\"\nmsgstr \"元数据\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:47\nmsgid \"query\"\nmsgstr \"查询\"\n\n#: apps/models_provider/serializers/model_serializer.py:44\n#: apps/models_provider/serializers/model_serializer.py:257\nmsgid \"parameter configuration\"\nmsgstr \"参数配置\"\n\n#: apps/models_provider/serializers/model_serializer.py:45\n#: apps/models_provider/serializers/model_serializer.py:222\n#: apps/models_provider/serializers/model_serializer.py:258\nmsgid \"certification information\"\nmsgstr \"认证信息\"\n\n#: apps/models_provider/serializers/model_serializer.py:118\nmsgid \"Shared models cannot be deleted or modified\"\nmsgstr \"共享模型不能被删除或修改\"\n\n#: apps/models_provider/serializers/model_serializer.py:230\n#: apps/models_provider/serializers/model_serializer.py:269\n#, python-brace-format\nmsgid \"base model【{model_name}】already exists\"\nmsgstr \"模型【{model_name}】已存在\"\n\n#: apps/models_provider/serializers/model_serializer.py:309\nmsgid \"Model saving failed\"\nmsgstr \"模型保存失败\"\n\n#: apps/models_provider/views/model.py:60\n#: apps/models_provider/views/model.py:61\n#: apps/models_provider/views/model.py:62 apps/shared/views/shared_model.py:55\n#: apps/shared/views/shared_model.py:56 apps/shared/views/shared_model.py:57\nmsgid \"Create model\"\nmsgstr \"创建模型\"\n\n#: apps/models_provider/views/model.py:90\n#: apps/models_provider/views/model.py:91\n#: apps/models_provider/views/model.py:92 apps/shared/views/shared_model.py:84\n#: apps/shared/views/shared_model.py:85 apps/shared/views/shared_model.py:86\nmsgid \"Query model list\"\nmsgstr \"查询模型列表\"\n\n#: apps/models_provider/views/model.py:107\n#: apps/models_provider/views/model.py:108\n#: apps/models_provider/views/model.py:109\n#: apps/shared/views/shared_model.py:101 apps/shared/views/shared_model.py:102\n#: apps/shared/views/shared_model.py:103\nmsgid \"Update model\"\nmsgstr \"更新模型\"\n\n#: apps/models_provider/views/model.py:125\n#: apps/models_provider/views/model.py:126\n#: apps/models_provider/views/model.py:127\n#: apps/shared/views/shared_model.py:119 apps/shared/views/shared_model.py:120\n#: apps/shared/views/shared_model.py:121\nmsgid \"Delete model\"\nmsgstr \"删除模型\"\n\n#: apps/models_provider/views/model.py:140\n#: apps/models_provider/views/model.py:141\n#: apps/models_provider/views/model.py:142\n#: apps/shared/views/shared_model.py:133 apps/shared/views/shared_model.py:134\n#: apps/shared/views/shared_model.py:135\nmsgid \"Query model details\"\nmsgstr \"查询模型详情\"\n\n#: apps/models_provider/views/model.py:155\n#: apps/models_provider/views/model.py:156\n#: apps/models_provider/views/model.py:157\n#: apps/shared/views/shared_model.py:148 apps/shared/views/shared_model.py:149\n#: apps/shared/views/shared_model.py:150\nmsgid \"Get model parameter form\"\nmsgstr \"获取模型参数表单\"\n\n#: apps/models_provider/views/model.py:167\n#: apps/models_provider/views/model.py:168\n#: apps/models_provider/views/model.py:169\n#: apps/shared/views/shared_model.py:160 apps/shared/views/shared_model.py:161\n#: apps/shared/views/shared_model.py:162\nmsgid \"Save model parameter form\"\nmsgstr \"保存模型参数表单\"\n\n#: apps/models_provider/views/model.py:187\n#: apps/models_provider/views/model.py:189\n#: apps/models_provider/views/model.py:191\n#: apps/shared/views/shared_model.py:179 apps/shared/views/shared_model.py:181\n#: apps/shared/views/shared_model.py:183\nmsgid \"\"\n\"Query model meta information, this interface does not carry authentication \"\n\"information\"\nmsgstr \"查询模型元信息，该接口不携带认证信息\"\n\n#: apps/models_provider/views/model.py:204\n#: apps/models_provider/views/model.py:205\n#: apps/models_provider/views/model.py:206\n#: apps/shared/views/shared_model.py:196 apps/shared/views/shared_model.py:197\n#: apps/shared/views/shared_model.py:198\nmsgid \"Pause model download\"\nmsgstr \"下载模型暂停\"\n\n#: apps/models_provider/views/model.py:222\n#: apps/models_provider/views/model.py:223\n#: apps/models_provider/views/model.py:224\nmsgid \"Get Share model\"\nmsgstr \"获取共享模型\"\n\n#: apps/models_provider/views/model_apply.py:25\n#: apps/models_provider/views/model_apply.py:26\n#: apps/models_provider/views/model_apply.py:27\n#: apps/models_provider/views/model_apply.py:37\n#: apps/models_provider/views/model_apply.py:38\n#: apps/models_provider/views/model_apply.py:39\nmsgid \"Vectorization documentation\"\nmsgstr \"向量化文档\"\n\n#: apps/models_provider/views/model_apply.py:49\n#: apps/models_provider/views/model_apply.py:50\n#: apps/models_provider/views/model_apply.py:51\nmsgid \"Reorder documents\"\nmsgstr \"重新排序文档\"\n\n#: apps/models_provider/views/provide.py:21\n#: apps/models_provider/views/provide.py:22\n#: apps/models_provider/views/provide.py:23\nmsgid \"Get a list of model suppliers\"\nmsgstr \"获取模型供应商列表\"\n\n#: apps/models_provider/views/provide.py:43\n#: apps/models_provider/views/provide.py:44\n#: apps/models_provider/views/provide.py:45\nmsgid \"Get a list of model types\"\nmsgstr \"获取模型类型列表\"\n\n#: apps/models_provider/views/provide.py:57\n#: apps/models_provider/views/provide.py:58\n#: apps/models_provider/views/provide.py:59\nmsgid \"Example of obtaining model list\"\nmsgstr \"获取模型列表示例\"\n\n#: apps/models_provider/views/provide.py:75\n#: apps/models_provider/views/provide.py:76\n#: apps/models_provider/views/provide.py:77\nmsgid \"Get model default parameters\"\nmsgstr \"获取模型默认参数\"\n\n#: apps/models_provider/views/provide.py:92\n#: apps/models_provider/views/provide.py:93\n#: apps/models_provider/views/provide.py:94\nmsgid \"Get the model creation form\"\nmsgstr \"获取模型创建表单\"\n\n#: apps/oss/serializers/file.py:80\nmsgid \"File not found\"\nmsgstr \"文件未找到\"\n\n#: apps/oss/views/file.py:21 apps/oss/views/file.py:22\n#: apps/oss/views/file.py:23\nmsgid \"Upload file\"\nmsgstr \"上传文件\"\n\n#: apps/oss/views/file.py:27 apps/oss/views/file.py:41\n#: apps/oss/views/file.py:53\nmsgid \"File\"\nmsgstr \"文件\"\n\n#: apps/oss/views/file.py:36 apps/oss/views/file.py:37\n#: apps/oss/views/file.py:38\nmsgid \"Get file\"\nmsgstr \"获取文件\"\n\n#: apps/oss/views/file.py:48 apps/oss/views/file.py:49\n#: apps/oss/views/file.py:50\nmsgid \"Delete file\"\nmsgstr \"删除文件\"\n\n#: apps/resource_manage/views/document.py:30\n#: apps/resource_manage/views/document.py:31\n#: apps/resource_manage/views/document.py:32\nmsgid \"Create system knowledge\"\nmsgstr \"创建系统知识库\"\n\n#: apps/resource_manage/views/document.py:36\n#: apps/resource_manage/views/document.py:56\n#: apps/resource_manage/views/document.py:83\n#: apps/resource_manage/views/document.py:113\n#: apps/resource_manage/views/document.py:130\n#: apps/resource_manage/views/document.py:155\n#: apps/resource_manage/views/document.py:183\n#: apps/resource_manage/views/document.py:210\n#: apps/resource_manage/views/document.py:237\n#: apps/resource_manage/views/document.py:267\n#: apps/resource_manage/views/document.py:296\n#: apps/resource_manage/views/document.py:317\n#: apps/resource_manage/views/document.py:345\n#: apps/resource_manage/views/document.py:362\n#: apps/resource_manage/views/document.py:383\n#: apps/resource_manage/views/document.py:411\n#: apps/resource_manage/views/document.py:435\n#: apps/resource_manage/views/document.py:460\n#: apps/resource_manage/views/document.py:485\n#: apps/resource_manage/views/document.py:507\n#: apps/resource_manage/views/document.py:530\n#: apps/resource_manage/views/document.py:553\n#: apps/resource_manage/views/document.py:571\n#: apps/resource_manage/views/document.py:585\nmsgid \"System Knowledge/Documentation\"\nmsgstr \"系统知识库/文档\"\n\n#: apps/resource_manage/views/document.py:51\n#: apps/resource_manage/views/document.py:52\n#: apps/resource_manage/views/document.py:53\nmsgid \"Get system document\"\nmsgstr \"获取文档\"\n\n#: apps/resource_manage/views/document.py:77\n#: apps/resource_manage/views/document.py:78\n#: apps/resource_manage/views/document.py:79\nmsgid \"Segmented system document\"\nmsgstr \"分段文档\"\n\n#: apps/resource_manage/views/document.py:108\n#: apps/resource_manage/views/document.py:109\n#: apps/resource_manage/views/document.py:110\nmsgid \"Get a list of system segment IDs\"\nmsgstr \"获取分段ID列表\"\n\n#: apps/resource_manage/views/document.py:124\n#: apps/resource_manage/views/document.py:125\n#: apps/resource_manage/views/document.py:126\nmsgid \"Cancel system tasks in batches\"\nmsgstr \"批量取消任务\"\n\n#: apps/resource_manage/views/document.py:149\n#: apps/resource_manage/views/document.py:150\n#: apps/resource_manage/views/document.py:151\nmsgid \"Create system knowledges in batches\"\nmsgstr \"批量创建知识库\"\n\n#: apps/resource_manage/views/document.py:177\n#: apps/resource_manage/views/document.py:178\n#: apps/resource_manage/views/document.py:179\nmsgid \"Batch sync system knowledges\"\nmsgstr \"批量同步知识库\"\n\n#: apps/resource_manage/views/document.py:204\n#: apps/resource_manage/views/document.py:206\nmsgid \"Delete system document in batches\"\nmsgstr \"批量删除文档\"\n\n#: apps/resource_manage/views/document.py:205\nmsgid \"Delete system knowledge in batches\"\nmsgstr \"批量删除知识库\"\n\n#: apps/resource_manage/views/document.py:232\n#: apps/resource_manage/views/document.py:233\nmsgid \"Batch refresh system document vector library\"\nmsgstr \"批量刷新文档向量库\"\n\n#: apps/resource_manage/views/document.py:261\n#: apps/resource_manage/views/document.py:262\n#: apps/resource_manage/views/document.py:263\nmsgid \"Batch generate related system problems\"\nmsgstr \"批量生成相关问题\"\n\n#: apps/resource_manage/views/document.py:290\n#: apps/resource_manage/views/document.py:291\n#: apps/resource_manage/views/document.py:292\nmsgid \"Modify system document hit processing methods in batches\"\nmsgstr \"批量修改文档命中处理方式\"\n\n#: apps/resource_manage/views/document.py:312\n#: apps/resource_manage/views/document.py:313\nmsgid \"Migrate system knowledges in batches\"\nmsgstr \"批量迁移知识库\"\n\n#: apps/resource_manage/views/document.py:340\n#: apps/resource_manage/views/document.py:341\n#: apps/resource_manage/views/document.py:342\nmsgid \"Get system document details\"\nmsgstr \"获取文档详情\"\n\n#: apps/resource_manage/views/document.py:356\n#: apps/resource_manage/views/document.py:357\n#: apps/resource_manage/views/document.py:358\nmsgid \"Modify system document\"\nmsgstr \"修改文档\"\n\n#: apps/resource_manage/views/document.py:378\n#: apps/resource_manage/views/document.py:379\n#: apps/resource_manage/views/document.py:380\nmsgid \"Delete system document\"\nmsgstr \"删除文档\"\n\n#: apps/resource_manage/views/document.py:405\n#: apps/resource_manage/views/document.py:406\n#: apps/resource_manage/views/document.py:407\nmsgid \"Synchronize system web site types\"\nmsgstr \"同步网站类型\"\n\n#: apps/resource_manage/views/document.py:429\n#: apps/resource_manage/views/document.py:430\n#: apps/resource_manage/views/document.py:431\nmsgid \"Refresh system knowledge vector library\"\nmsgstr \"刷新文档向量库\"\n\n#: apps/resource_manage/views/document.py:454\n#: apps/resource_manage/views/document.py:455\n#: apps/resource_manage/views/document.py:456\nmsgid \"Cancel system task\"\nmsgstr \"取消任务\"\n\n#: apps/resource_manage/views/document.py:480\n#: apps/resource_manage/views/document.py:481\n#: apps/resource_manage/views/document.py:482\nmsgid \"Get system document by pagination\"\nmsgstr \"分页获取文档\"\n\n#: apps/resource_manage/views/document.py:503\n#: apps/resource_manage/views/document.py:504\nmsgid \"Export system knowledge\"\nmsgstr \"导出知识库\"\n\n#: apps/resource_manage/views/document.py:526\n#: apps/resource_manage/views/document.py:527\nmsgid \"Export Zip system knowledge\"\nmsgstr \"导出Zip知识库\"\n\n#: apps/resource_manage/views/document.py:549\n#: apps/resource_manage/views/document.py:550\nmsgid \"Download system source file\"\nmsgstr \"下载系统源文件\"\n\n#: apps/resource_manage/views/document.py:567\n#: apps/resource_manage/views/document.py:568\nmsgid \"Get system QA template\"\nmsgstr \"获取系统问答模板\"\n\n#: apps/resource_manage/views/document.py:581\n#: apps/resource_manage/views/document.py:582\nmsgid \"Get system form template\"\nmsgstr \"获取系统表单模板\"\n\n#: apps/resource_manage/views/knowledge.py:26\n#: apps/resource_manage/views/knowledge.py:27\n#: apps/resource_manage/views/knowledge.py:28\nmsgid \"Get system knowledge list\"\nmsgstr \"获取系统知识库列表\"\n\n#: apps/resource_manage/views/knowledge.py:31\n#: apps/resource_manage/views/knowledge.py:50\n#: apps/resource_manage/views/knowledge.py:72\n#: apps/resource_manage/views/knowledge.py:87\n#: apps/resource_manage/views/knowledge.py:102\n#: apps/resource_manage/views/knowledge.py:121\n#: apps/resource_manage/views/knowledge.py:147\n#: apps/resource_manage/views/knowledge.py:174\n#: apps/resource_manage/views/knowledge.py:192\n#: apps/resource_manage/views/knowledge.py:210\n#: apps/resource_manage/views/knowledge.py:231\n#: apps/resource_manage/views/knowledge.py:252\n#: apps/resource_manage/views/knowledge.py:272\nmsgid \"System Knowledge\"\nmsgstr \"系统知识库\"\n\n#: apps/resource_manage/views/knowledge.py:45\n#: apps/resource_manage/views/knowledge.py:46\n#: apps/resource_manage/views/knowledge.py:47\nmsgid \"Get system knowledge list by pagination\"\nmsgstr \"获取系统知识库分页列表\"\n\n#: apps/resource_manage/views/knowledge.py:66\n#: apps/resource_manage/views/knowledge.py:67\n#: apps/resource_manage/views/knowledge.py:68\nmsgid \"Update system knowledge\"\nmsgstr \"更新系统知识库\"\n\n#: apps/resource_manage/views/knowledge.py:82\n#: apps/resource_manage/views/knowledge.py:83\n#: apps/resource_manage/views/knowledge.py:84\nmsgid \"Get system knowledge\"\nmsgstr \"获取知识库\"\n\n#: apps/resource_manage/views/knowledge.py:97\n#: apps/resource_manage/views/knowledge.py:98\n#: apps/resource_manage/views/knowledge.py:99\nmsgid \"Delete system knowledge\"\nmsgstr \"删除知识库\"\n\n#: apps/resource_manage/views/knowledge.py:115\n#: apps/resource_manage/views/knowledge.py:116\n#: apps/resource_manage/views/knowledge.py:117\nmsgid \"Synchronize the system knowledge base of the website\"\nmsgstr \"同步网站知识库\"\n\n#: apps/resource_manage/views/knowledge.py:141\n#: apps/resource_manage/views/knowledge.py:142\n#: apps/resource_manage/views/knowledge.py:143\nmsgid \"System Hit test list\"\nmsgstr \"命中测试列表\"\n\n#: apps/resource_manage/views/knowledge.py:168\n#: apps/resource_manage/views/knowledge.py:169\n#: apps/resource_manage/views/knowledge.py:170\nmsgid \"System Re-vectorize\"\nmsgstr \"重新向量化\"\n\n#: apps/resource_manage/views/knowledge.py:188\n#: apps/resource_manage/views/knowledge.py:189\nmsgid \"Export system knowledge base\"\nmsgstr \"导出系统知识库\"\n\n#: apps/resource_manage/views/knowledge.py:206\n#: apps/resource_manage/views/knowledge.py:207\nmsgid \"Export system knowledge base containing images\"\nmsgstr \"导出包含图片的系统知识库\"\n\n#: apps/resource_manage/views/knowledge.py:225\n#: apps/resource_manage/views/knowledge.py:226\n#: apps/resource_manage/views/knowledge.py:227\nmsgid \"System generate related\"\nmsgstr \"生成相关\"\n\n#: apps/resource_manage/views/knowledge.py:247\n#: apps/resource_manage/views/knowledge.py:248\n#: apps/resource_manage/views/knowledge.py:249\nmsgid \"Get model for system knowledge base\"\nmsgstr \"获取系统知识库模型\"\n\n#: apps/resource_manage/views/knowledge.py:267\n#: apps/resource_manage/views/knowledge.py:268\n#: apps/resource_manage/views/knowledge.py:269\nmsgid \"Get embedding model for system knowledge base\"\nmsgstr \"获取系统知识库嵌入模型\"\n\n#: apps/resource_manage/views/paragraph.py:24\n#: apps/resource_manage/views/paragraph.py:25\n#: apps/resource_manage/views/paragraph.py:26\nmsgid \"System paragraph list\"\nmsgstr \"段落列表\"\n\n#: apps/resource_manage/views/paragraph.py:29\n#: apps/resource_manage/views/paragraph.py:50\n#: apps/resource_manage/views/paragraph.py:76\n#: apps/resource_manage/views/paragraph.py:93\n#: apps/resource_manage/views/paragraph.py:125\n#: apps/resource_manage/views/paragraph.py:151\n#: apps/resource_manage/views/paragraph.py:179\n#: apps/resource_manage/views/paragraph.py:201\n#: apps/resource_manage/views/paragraph.py:233\n#: apps/resource_manage/views/paragraph.py:259\n#: apps/resource_manage/views/paragraph.py:283\n#: apps/resource_manage/views/paragraph.py:314\n#: apps/resource_manage/views/paragraph.py:344\n#: apps/resource_manage/views/paragraph.py:370\nmsgid \"System Knowledge/Documentation/Paragraph\"\nmsgstr \"系统知识库/文档/段落\"\n\n#: apps/resource_manage/views/paragraph.py:45\n#: apps/resource_manage/views/paragraph.py:46\nmsgid \"Create system paragraph\"\nmsgstr \"创建段落\"\n\n#: apps/resource_manage/views/paragraph.py:70\n#: apps/resource_manage/views/paragraph.py:71\n#: apps/resource_manage/views/paragraph.py:72\nmsgid \"Batch system paragraph\"\nmsgstr \"批量关联段落\"\n\n#: apps/resource_manage/views/paragraph.py:88\n#: apps/resource_manage/views/paragraph.py:89\nmsgid \"Migrate system paragraphs in batches\"\nmsgstr \"批量迁移段落\"\n\n#: apps/resource_manage/views/paragraph.py:119\n#: apps/resource_manage/views/paragraph.py:120\n#: apps/resource_manage/views/paragraph.py:121\nmsgid \"Batch generate system related\"\nmsgstr \"批量生成相关\"\n\n#: apps/resource_manage/views/paragraph.py:145\n#: apps/resource_manage/views/paragraph.py:146\n#: apps/resource_manage/views/paragraph.py:147\nmsgid \"Modify system paragraph data\"\nmsgstr \"修改段落数据\"\n\n#: apps/resource_manage/views/paragraph.py:174\n#: apps/resource_manage/views/paragraph.py:175\n#: apps/resource_manage/views/paragraph.py:176\nmsgid \"Get system paragraph details\"\nmsgstr \"获取段落详情\"\n\n#: apps/resource_manage/views/paragraph.py:196\n#: apps/resource_manage/views/paragraph.py:197\n#: apps/resource_manage/views/paragraph.py:198\nmsgid \"Delete system paragraph\"\nmsgstr \"删除段落\"\n\n#: apps/resource_manage/views/paragraph.py:227\n#: apps/resource_manage/views/paragraph.py:228\n#: apps/resource_manage/views/paragraph.py:229\nmsgid \"Add system associated questions\"\nmsgstr \"添加关联问题\"\n\n#: apps/resource_manage/views/paragraph.py:254\n#: apps/resource_manage/views/paragraph.py:255\n#: apps/resource_manage/views/paragraph.py:256\nmsgid \"Get a list of system paragraph questions\"\nmsgstr \"获取段落问题列表\"\n\n#: apps/resource_manage/views/paragraph.py:277\n#: apps/resource_manage/views/paragraph.py:278\n#: apps/resource_manage/views/paragraph.py:279\nmsgid \"Disassociation system issue\"\nmsgstr \"取消关联问题\"\n\n#: apps/resource_manage/views/paragraph.py:308\n#: apps/resource_manage/views/paragraph.py:309\n#: apps/resource_manage/views/paragraph.py:310\nmsgid \"Related system questions\"\nmsgstr \"关联问题\"\n\n#: apps/resource_manage/views/paragraph.py:339\n#: apps/resource_manage/views/paragraph.py:340\n#: apps/resource_manage/views/paragraph.py:341\nmsgid \"Get system paragraph list by pagination\"\nmsgstr \"获取段落列表\"\n\n#: apps/resource_manage/views/problem.py:23\n#: apps/resource_manage/views/problem.py:24\n#: apps/resource_manage/views/problem.py:25\nmsgid \"System question list\"\nmsgstr \"问题列表\"\n\n#: apps/resource_manage/views/problem.py:28\n#: apps/resource_manage/views/problem.py:50\n#: apps/resource_manage/views/problem.py:71\n#: apps/resource_manage/views/problem.py:94\n#: apps/resource_manage/views/problem.py:115\n#: apps/resource_manage/views/problem.py:135\n#: apps/resource_manage/views/problem.py:158\n#: apps/resource_manage/views/problem.py:182\nmsgid \"System Knowledge/Documentation/Paragraph/Question\"\nmsgstr \"系统知识库/文档/段落/问题\"\n\n#: apps/resource_manage/views/problem.py:44\n#: apps/resource_manage/views/problem.py:45\n#: apps/resource_manage/views/problem.py:46\nmsgid \"Create system question\"\nmsgstr \"创建问题\"\n\n#: apps/resource_manage/views/problem.py:66\n#: apps/resource_manage/views/problem.py:67\n#: apps/resource_manage/views/problem.py:68\nmsgid \"Get a list of associated system paragraphs\"\nmsgstr \"获取关联段落列表\"\n\n#: apps/resource_manage/views/problem.py:88\n#: apps/resource_manage/views/problem.py:89\n#: apps/resource_manage/views/problem.py:90\nmsgid \"Batch associated system paragraphs\"\nmsgstr \"批量关联段落\"\n\n#: apps/resource_manage/views/problem.py:109\n#: apps/resource_manage/views/problem.py:110\n#: apps/resource_manage/views/problem.py:111\nmsgid \"Batch deletion system issues\"\nmsgstr \"批量删除问题\"\n\n#: apps/resource_manage/views/problem.py:130\n#: apps/resource_manage/views/problem.py:131\n#: apps/resource_manage/views/problem.py:132\nmsgid \"Delete system question\"\nmsgstr \"删除问题\"\n\n#: apps/resource_manage/views/problem.py:152\n#: apps/resource_manage/views/problem.py:153\n#: apps/resource_manage/views/problem.py:154\nmsgid \"Modify system question\"\nmsgstr \"修改问题\"\n\n#: apps/resource_manage/views/problem.py:177\n#: apps/resource_manage/views/problem.py:178\n#: apps/resource_manage/views/problem.py:179\nmsgid \"Get the list of system questions by page\"\nmsgstr \"分页获取问题列表\"\n\n#: apps/resource_manage/views/tool.py:24 apps/resource_manage/views/tool.py:25\n#: apps/resource_manage/views/tool.py:26\nmsgid \"Get system tool\"\nmsgstr \"获取工具\"\n\n#: apps/resource_manage/views/tool.py:29 apps/resource_manage/views/tool.py:50\n#: apps/resource_manage/views/tool.py:65 apps/resource_manage/views/tool.py:80\n#: apps/resource_manage/views/tool.py:98 apps/resource_manage/views/tool.py:119\n#: apps/resource_manage/views/tool.py:137\n#: apps/resource_manage/views/tool.py:156\n#: apps/resource_manage/views/tool.py:179\nmsgid \"System Tool\"\nmsgstr \"工具\"\n\n#: apps/resource_manage/views/tool.py:44 apps/resource_manage/views/tool.py:45\n#: apps/resource_manage/views/tool.py:46\nmsgid \"Update system tool\"\nmsgstr \"更新工具\"\n\n#: apps/resource_manage/views/tool.py:60 apps/resource_manage/views/tool.py:61\n#: apps/resource_manage/views/tool.py:62\nmsgid \"Get system tool by id\"\nmsgstr \"获取工具\"\n\n#: apps/resource_manage/views/tool.py:75 apps/resource_manage/views/tool.py:76\n#: apps/resource_manage/views/tool.py:77\nmsgid \"Delete system tool\"\nmsgstr \"删除工具\"\n\n#: apps/resource_manage/views/tool.py:93 apps/resource_manage/views/tool.py:94\n#: apps/resource_manage/views/tool.py:95\nmsgid \"Get system tool list by pagination\"\nmsgstr \"获取工具列表\"\n\n#: apps/resource_manage/views/tool.py:114\n#: apps/resource_manage/views/tool.py:115\n#: apps/resource_manage/views/tool.py:116\nmsgid \"Export system tool\"\nmsgstr \"导出工具\"\n\n#: apps/resource_manage/views/tool.py:132\n#: apps/resource_manage/views/tool.py:133\n#: apps/resource_manage/views/tool.py:134\nmsgid \"Debug system tool\"\nmsgstr \"调试工具\"\n\n#: apps/resource_manage/views/tool.py:150\n#: apps/resource_manage/views/tool.py:151\n#: apps/resource_manage/views/tool.py:152\nmsgid \"Check system code\"\nmsgstr \"检查代码\"\n\n#: apps/resource_manage/views/tool.py:173\n#: apps/resource_manage/views/tool.py:174\n#: apps/resource_manage/views/tool.py:175\nmsgid \"Edit system tool icon\"\nmsgstr \"修改工具图标\"\n\n#: apps/role_setting/api/role_setting.py:16\n#: apps/role_setting/api/role_setting.py:22\n#: apps/role_setting/api/role_setting.py:33\n#: apps/role_setting/api/role_setting.py:143\n#: apps/role_setting/serializers/role_setting_serializers.py:193\n#: apps/workspace/api/workspace.py:81 apps/xpack/api/chat_user.py:104\nmsgid \"ID\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:17\n#: apps/role_setting/api/role_setting.py:23\n#: apps/role_setting/api/role_setting.py:34 apps/users/serializers/user.py:258\n#: apps/xpack/api/knowledge_lark.py:24 apps/xpack/serializers/chat_user.py:235\nmsgid \"Name\"\nmsgstr \"用户名\"\n\n#: apps/role_setting/api/role_setting.py:18\n#: apps/role_setting/api/role_setting.py:29\n#: apps/role_setting/serializers/role_setting_serializers.py:194\nmsgid \"Enable\"\nmsgstr \"启用\"\n\n#: apps/role_setting/api/role_setting.py:26\nmsgid \"Permission\"\nmsgstr \"权限\"\n\n#: apps/role_setting/api/role_setting.py:37\nmsgid \"Children\"\nmsgstr \"子级\"\n\n#: apps/role_setting/api/role_setting.py:55\n#: apps/role_setting/serializers/role_setting_serializers.py:107\nmsgid \"Role type\"\nmsgstr \"角色类型\"\n\n#: apps/role_setting/api/role_setting.py:76\nmsgid \"Internal role\"\nmsgstr \"内置角色\"\n\n#: apps/role_setting/api/role_setting.py:80\nmsgid \"Custom role\"\nmsgstr \"自定义角色\"\n\n#: apps/role_setting/api/role_setting.py:108\n#: apps/role_setting/api/role_setting.py:128\n#: apps/role_setting/api/role_setting.py:164\n#: apps/role_setting/serializers/role_setting_serializers.py:110\n#: apps/role_setting/serializers/role_setting_serializers.py:329\n#: apps/users/api/user.py:26 apps/workspace/api/workspace.py:86\nmsgid \"Role ID\"\nmsgstr \"角色 ID\"\n\n#: apps/role_setting/api/role_setting.py:135\nmsgid \"User relation ID\"\nmsgstr \"用户关系 ID\"\n\n#: apps/role_setting/api/role_setting.py:145\n#: apps/role_setting/api/role_setting.py:185\n#: apps/role_setting/serializers/role_setting_serializers.py:330\n#: apps/users/api/user.py:77 apps/users/serializers/login.py:27\n#: apps/users/serializers/user.py:56 apps/users/serializers/user.py:114\n#: apps/workspace/api/workspace.py:83 apps/workspace/api/workspace.py:124\n#: apps/workspace/serializers/workspace_serializers.py:240\n#: apps/xpack/api/auth_config.py:24 apps/xpack/api/chat_user.py:105\n#: apps/xpack/api/user_group.py:75 apps/xpack/serializers/chat_user.py:62\n#: apps/xpack/serializers/chat_user.py:564\nmsgid \"Username\"\nmsgstr \"用户名\"\n\n#: apps/role_setting/api/role_setting.py:146 apps/workspace/api/workspace.py:84\n#: apps/xpack/api/chat_user.py:106\nmsgid \"Nickname\"\nmsgstr \"姓名\"\n\n#: apps/role_setting/api/role_setting.py:148\nmsgid \"Workspace Name\"\nmsgstr \"工作空间\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:104\nmsgid \"Role name\"\nmsgstr \"角色名称\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:122\n#: apps/role_setting/serializers/role_setting_serializers.py:200\n#: apps/role_setting/serializers/role_setting_serializers.py:325\n#: apps/role_setting/serializers/role_setting_serializers.py:337\nmsgid \"Role does not exist\"\nmsgstr \"角色不存在\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:124\n#: apps/role_setting/serializers/role_setting_serializers.py:202\nmsgid \"Cannot modify built-in role\"\nmsgstr \"不能修改内置角色\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:132\nmsgid \"Role name already exists\"\nmsgstr \"角色名称已存在\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:152\nmsgid \"Invalid role type\"\nmsgstr \"无效的角色类型\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:204\nmsgid \"Cannot delete built-in role\"\nmsgstr \"无法删除内置角色\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:262\n#: apps/users/api/user.py:135 apps/users/serializers/user.py:471\n#: apps/workspace/serializers/workspace_serializers.py:161\n#: apps/xpack/api/user_group.py:90 apps/xpack/serializers/chat_user.py:158\n#: apps/xpack/serializers/chat_user.py:172\n#: apps/xpack/serializers/chat_user.py:502\nmsgid \"User IDs\"\nmsgstr \"用户 ID\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:267\n#: apps/users/api/user.py:30\nmsgid \"Workspace IDs\"\nmsgstr \"工作空间 ID\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:272\n#: apps/workspace/serializers/workspace_serializers.py:172\nmsgid \"Members\"\nmsgstr \"成员集合\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:312\n#: apps/workspace/serializers/workspace_serializers.py:223\nmsgid \"User relation does not exist\"\nmsgstr \"用户关系不存在\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:316\n#: apps/workspace/serializers/workspace_serializers.py:226\nmsgid \"Cannot remove member from built-in role\"\nmsgstr \"不能从内置角色中移除成员\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:370\nmsgid \"Only update members to normal users\"\nmsgstr \"只能为普通用户更新成员\"\n\n#: apps/role_setting/views/role_setting.py:39\n#: apps/role_setting/views/role_setting.py:40\n#: apps/role_setting/views/role_setting.py:41\nmsgid \"Get role permission template\"\nmsgstr \"获取角色权限模板\"\n\n#: apps/role_setting/views/role_setting.py:62\n#: apps/role_setting/views/role_setting.py:63\n#: apps/role_setting/views/role_setting.py:64\nmsgid \"Create or update role\"\nmsgstr \"创建或更新角色\"\n\n#: apps/role_setting/views/role_setting.py:80\n#: apps/role_setting/views/role_setting.py:81\n#: apps/role_setting/views/role_setting.py:82\nmsgid \"Get role list\"\nmsgstr \"获取角色列表\"\n\n#: apps/role_setting/views/role_setting.py:98\n#: apps/role_setting/views/role_setting.py:99\n#: apps/role_setting/views/role_setting.py:100\nmsgid \"Delete role\"\nmsgstr \"删除角色\"\n\n#: apps/role_setting/views/role_setting.py:120\n#: apps/role_setting/views/role_setting.py:121\n#: apps/role_setting/views/role_setting.py:122\nmsgid \"Create or update role permission\"\nmsgstr \"创建或更新角色权限\"\n\n#: apps/role_setting/views/role_setting.py:140\n#: apps/role_setting/views/role_setting.py:141\n#: apps/role_setting/views/role_setting.py:142\nmsgid \"Get role permission\"\nmsgstr \"获取角色权限\"\n\n#: apps/role_setting/views/role_setting.py:161\n#: apps/role_setting/views/role_setting.py:162\n#: apps/role_setting/views/role_setting.py:163\nmsgid \"Add member to system role\"\nmsgstr \"系统角色添加成员\"\n\n#: apps/role_setting/views/role_setting.py:186\n#: apps/role_setting/views/role_setting.py:187\n#: apps/role_setting/views/role_setting.py:188\nmsgid \"Remove member from system role\"\nmsgstr \"系统角色移除成员\"\n\n#: apps/role_setting/views/role_setting.py:205\n#: apps/role_setting/views/role_setting.py:206\n#: apps/role_setting/views/role_setting.py:207\nmsgid \"Get system role member list\"\nmsgstr \"获取系统角色成员列表\"\n\n#: apps/role_setting/views/role_setting.py:223\n#: apps/role_setting/views/role_setting.py:224\n#: apps/role_setting/views/role_setting.py:225\nmsgid \"Get Workspace role list\"\nmsgstr \"获取工作空间角色列表\"\n\n#: apps/role_setting/views/role_setting.py:227\n#: apps/role_setting/views/role_setting.py:248\n#: apps/role_setting/views/role_setting.py:273\n#: apps/role_setting/views/role_setting.py:292\nmsgid \"Workspace Role\"\nmsgstr \"工作空间角色\"\n\n#: apps/role_setting/views/role_setting.py:242\n#: apps/role_setting/views/role_setting.py:243\n#: apps/role_setting/views/role_setting.py:244\nmsgid \"Add member to workspace role\"\nmsgstr \"工作空间角色添加成员\"\n\n#: apps/role_setting/views/role_setting.py:268\n#: apps/role_setting/views/role_setting.py:269\n#: apps/role_setting/views/role_setting.py:270\nmsgid \"Remove member from workspace role\"\nmsgstr \"工作空间角色移除成员\"\n\n#: apps/role_setting/views/role_setting.py:287\n#: apps/role_setting/views/role_setting.py:288\n#: apps/role_setting/views/role_setting.py:289\nmsgid \"Get workspace role member list\"\nmsgstr \"获取工作空间角色成员列表\"\n\n#: apps/shared/api/shared_knowledge.py:242 apps/xpack/api/knowledge_lark.py:54\nmsgid \"Folder token\"\nmsgstr \"文件夹 Token\"\n\n#: apps/shared/api/shared_tool.py:19 apps/shared/api/shared_tool.py:46\n#: apps/shared/api/shared_tool.py:93 apps/shared/api/shared_tool.py:133\n#: apps/shared/serializers/shared_tool.py:43\n#: apps/shared/serializers/shared_tool.py:83 apps/tools/serializers/tool.py:142\n#: apps/tools/serializers/tool.py:158 apps/tools/serializers/tool.py:454\nmsgid \"tool name\"\nmsgstr \"工具名称\"\n\n#: apps/shared/api/shared_tool.py:26 apps/shared/api/shared_tool.py:53\n#: apps/shared/api/shared_tool.py:100 apps/shared/api/shared_tool.py:140\n#: apps/shared/serializers/shared_tool.py:44\n#: apps/shared/serializers/shared_tool.py:85 apps/tools/serializers/tool.py:144\n#: apps/tools/serializers/tool.py:159\nmsgid \"tool description\"\nmsgstr \"工具描述\"\n\n#: apps/shared/api/shared_tool.py:166 apps/shared/api/shared_tool.py:184\n#: apps/shared/api/shared_tool.py:256 apps/shared/api/shared_tool.py:288\n#: apps/shared/api/shared_tool.py:310 apps/shared/serializers/shared_tool.py:66\n#: apps/shared/serializers/shared_tool.py:107\n#: apps/tools/serializers/tool.py:267\nmsgid \"tool id\"\nmsgstr \"工具 ID\"\n\n#: apps/shared/serializers/shared_knowledge.py:35\n#: apps/shared/serializers/shared_model.py:20\n#: apps/shared/serializers/shared_tool.py:19\nmsgid \"workspace id list\"\nmsgstr \"工作空间ID\"\n\n#: apps/shared/serializers/shared_knowledge.py:36\n#: apps/shared/serializers/shared_model.py:21\n#: apps/shared/serializers/shared_tool.py:20\nmsgid \"authentication type\"\nmsgstr \"认证类型\"\n\n#: apps/shared/serializers/shared_knowledge.py:196\n#: apps/shared/serializers/shared_knowledge.py:216\n#: apps/shared/serializers/shared_knowledge.py:236\nmsgid \"Knowledge does not exist\"\nmsgstr \"知识库不存在\"\n\n#: apps/shared/serializers/shared_knowledge.py:218\n#: apps/shared/serializers/shared_knowledge.py:238\nmsgid \"Only shared knowledge can be authorized\"\nmsgstr \"仅限共享知识库可以被授权\"\n\n#: apps/shared/serializers/shared_tool.py:74\nmsgid \"Only shared tools can be deleted\"\nmsgstr \"仅限共享工具可以被删除\"\n\n#: apps/shared/serializers/shared_tool.py:76\nmsgid \"System tools cannot be deleted\"\nmsgstr \"系统工具不能被删除\"\n\n#: apps/shared/serializers/shared_tool.py:118\n#: apps/shared/serializers/shared_tool.py:138\nmsgid \"Tool does not exist\"\nmsgstr \"工具不存在\"\n\n#: apps/shared/serializers/shared_tool.py:120\n#: apps/shared/serializers/shared_tool.py:140\nmsgid \"Only shared tools can be authorized\"\nmsgstr \"仅限共享工具可以被授权\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:25\n#: apps/shared/views/shared_dataset_lark_views.py:26\n#: apps/shared/views/shared_dataset_lark_views.py:27\n#: apps/xpack/views/dataset_lark_views.py:23\n#: apps/xpack/views/dataset_lark_views.py:24\n#: apps/xpack/views/dataset_lark_views.py:25\nmsgid \"Create a lark knowledge base\"\nmsgstr \"创建知识库\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:44\n#: apps/shared/views/shared_dataset_lark_views.py:45\n#: apps/shared/views/shared_dataset_lark_views.py:46\n#: apps/xpack/views/dataset_lark_views.py:44\n#: apps/xpack/views/dataset_lark_views.py:45\n#: apps/xpack/views/dataset_lark_views.py:46\nmsgid \"Update a lark knowledge base\"\nmsgstr \"更新飞书知识库\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:67\n#: apps/shared/views/shared_dataset_lark_views.py:68\n#: apps/shared/views/shared_dataset_lark_views.py:69\n#: apps/xpack/views/dataset_lark_views.py:67\n#: apps/xpack/views/dataset_lark_views.py:68\n#: apps/xpack/views/dataset_lark_views.py:69\nmsgid \"Get document list from lark\"\nmsgstr \"获取飞书文档列表\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:72\n#: apps/shared/views/shared_dataset_lark_views.py:90\n#: apps/shared/views/shared_dataset_lark_views.py:109\n#: apps/shared/views/shared_dataset_lark_views.py:129\n#: apps/shared/views/shared_document.py:36\n#: apps/shared/views/shared_document.py:56\n#: apps/shared/views/shared_document.py:83\n#: apps/shared/views/shared_document.py:114\n#: apps/shared/views/shared_document.py:131\n#: apps/shared/views/shared_document.py:156\n#: apps/shared/views/shared_document.py:184\n#: apps/shared/views/shared_document.py:211\n#: apps/shared/views/shared_document.py:238\n#: apps/shared/views/shared_document.py:268\n#: apps/shared/views/shared_document.py:297\n#: apps/shared/views/shared_document.py:318\n#: apps/shared/views/shared_document.py:346\n#: apps/shared/views/shared_document.py:363\n#: apps/shared/views/shared_document.py:384\n#: apps/shared/views/shared_document.py:412\n#: apps/shared/views/shared_document.py:436\n#: apps/shared/views/shared_document.py:461\n#: apps/shared/views/shared_document.py:486\n#: apps/shared/views/shared_document.py:508\n#: apps/shared/views/shared_document.py:531\n#: apps/shared/views/shared_document.py:554\n#: apps/shared/views/shared_document.py:575\n#: apps/shared/views/shared_document.py:602\n#: apps/shared/views/shared_document.py:629\n#: apps/shared/views/shared_document.py:651\n#: apps/shared/views/shared_document.py:665\nmsgid \"Shared Knowledge/Documentation\"\nmsgstr \"共享知识库/文档\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:85\n#: apps/shared/views/shared_dataset_lark_views.py:86\n#: apps/shared/views/shared_dataset_lark_views.py:87\n#: apps/xpack/views/dataset_lark_views.py:86\n#: apps/xpack/views/dataset_lark_views.py:87\n#: apps/xpack/views/dataset_lark_views.py:88\nmsgid \"Import documents to the lark knowledge base\"\nmsgstr \"导入文档到飞书知识库\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:104\n#: apps/shared/views/shared_dataset_lark_views.py:105\n#: apps/shared/views/shared_dataset_lark_views.py:106\n#: apps/xpack/views/dataset_lark_views.py:106\n#: apps/xpack/views/dataset_lark_views.py:107\n#: apps/xpack/views/dataset_lark_views.py:108\nmsgid \"Synchronize lark document\"\nmsgstr \"同步飞书文档\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:123\n#: apps/shared/views/shared_dataset_lark_views.py:124\n#: apps/shared/views/shared_dataset_lark_views.py:125\n#: apps/xpack/views/dataset_lark_views.py:126\n#: apps/xpack/views/dataset_lark_views.py:127\n#: apps/xpack/views/dataset_lark_views.py:128\nmsgid \"Batch synchronize lark document\"\nmsgstr \"批量同步飞书文档\"\n\n#: apps/shared/views/shared_document.py:30\n#: apps/shared/views/shared_document.py:31\n#: apps/shared/views/shared_document.py:32\nmsgid \"Create shared document\"\nmsgstr \"创建共享文档\"\n\n#: apps/shared/views/shared_document.py:51\n#: apps/shared/views/shared_document.py:52\n#: apps/shared/views/shared_document.py:53\nmsgid \"Get shared document\"\nmsgstr \"获取共享文档\"\n\n#: apps/shared/views/shared_document.py:77\n#: apps/shared/views/shared_document.py:78\n#: apps/shared/views/shared_document.py:79\nmsgid \"Segmented shared document\"\nmsgstr \"分段共享文档\"\n\n#: apps/shared/views/shared_document.py:109\n#: apps/shared/views/shared_document.py:110\n#: apps/shared/views/shared_document.py:111\nmsgid \"Get a list of shared segment IDs\"\nmsgstr \"获取共享分段 ID 列表\"\n\n#: apps/shared/views/shared_document.py:125\n#: apps/shared/views/shared_document.py:126\n#: apps/shared/views/shared_document.py:127\nmsgid \"Cancel shared tasks in batches\"\nmsgstr \"批量取消任务\"\n\n#: apps/shared/views/shared_document.py:150\n#: apps/shared/views/shared_document.py:151\n#: apps/shared/views/shared_document.py:152\nmsgid \"Create shared documents in batches\"\nmsgstr \"批量创建共享文档\"\n\n#: apps/shared/views/shared_document.py:178\n#: apps/shared/views/shared_document.py:179\n#: apps/shared/views/shared_document.py:180\nmsgid \"Batch sync shared documents\"\nmsgstr \"批量同步共享文档\"\n\n#: apps/shared/views/shared_document.py:205\n#: apps/shared/views/shared_document.py:206\n#: apps/shared/views/shared_document.py:207\nmsgid \"Delete shared documents in batches\"\nmsgstr \"批量删除共享文档\"\n\n#: apps/shared/views/shared_document.py:233\n#: apps/shared/views/shared_document.py:234\nmsgid \"Batch refresh shared document vector library\"\nmsgstr \"批量刷新共享文档向量库\"\n\n#: apps/shared/views/shared_document.py:262\n#: apps/shared/views/shared_document.py:263\n#: apps/shared/views/shared_document.py:264\nmsgid \"Batch generate related shared problems\"\nmsgstr \"批量生成相关问题\"\n\n#: apps/shared/views/shared_document.py:291\n#: apps/shared/views/shared_document.py:292\n#: apps/shared/views/shared_document.py:293\nmsgid \"Modify shared document hit processing methods in batches\"\nmsgstr \"批量修改文档命中处理方法\"\n\n#: apps/shared/views/shared_document.py:313\n#: apps/shared/views/shared_document.py:314\nmsgid \"Migrate shared documents in batches\"\nmsgstr \"批量迁移共享文档\"\n\n#: apps/shared/views/shared_document.py:341\n#: apps/shared/views/shared_document.py:342\n#: apps/shared/views/shared_document.py:343\nmsgid \"Get shared document details\"\nmsgstr \"文档文档详情\"\n\n#: apps/shared/views/shared_document.py:357\n#: apps/shared/views/shared_document.py:358\n#: apps/shared/views/shared_document.py:359\nmsgid \"Modify shared document\"\nmsgstr \"修改共享文档\"\n\n#: apps/shared/views/shared_document.py:379\n#: apps/shared/views/shared_document.py:380\n#: apps/shared/views/shared_document.py:381\nmsgid \"Delete shared document\"\nmsgstr \"删除共享文档\"\n\n#: apps/shared/views/shared_document.py:406\n#: apps/shared/views/shared_document.py:407\n#: apps/shared/views/shared_document.py:408\nmsgid \"Synchronize shared web site types\"\nmsgstr \"同步共享网站类型\"\n\n#: apps/shared/views/shared_document.py:430\n#: apps/shared/views/shared_document.py:431\n#: apps/shared/views/shared_document.py:432\nmsgid \"Refresh shared document vector library\"\nmsgstr \"刷新共享文档向量库\"\n\n#: apps/shared/views/shared_document.py:455\n#: apps/shared/views/shared_document.py:456\n#: apps/shared/views/shared_document.py:457\nmsgid \"Cancel shared task\"\nmsgstr \"取消任务\"\n\n#: apps/shared/views/shared_document.py:481\n#: apps/shared/views/shared_document.py:482\n#: apps/shared/views/shared_document.py:483\nmsgid \"Get shared document by pagination\"\nmsgstr \"获取共享文档分页列表\"\n\n#: apps/shared/views/shared_document.py:504\n#: apps/shared/views/shared_document.py:505\nmsgid \"Export shared document\"\nmsgstr \"导出共享文档\"\n\n#: apps/shared/views/shared_document.py:527\n#: apps/shared/views/shared_document.py:528\nmsgid \"Export Zip shared document\"\nmsgstr \"导出共享文档 Zip\"\n\n#: apps/shared/views/shared_document.py:550\n#: apps/shared/views/shared_document.py:551\nmsgid \"Download shared source file\"\nmsgstr \"下载共享源文件\"\n\n#: apps/shared/views/shared_document.py:569\n#: apps/shared/views/shared_document.py:571\nmsgid \"Create Web site shared documents\"\nmsgstr \"创建网站文档\"\n\n#: apps/shared/views/shared_document.py:596\n#: apps/shared/views/shared_document.py:597\n#: apps/shared/views/shared_document.py:598\nmsgid \"Import QA and create shared documentation\"\nmsgstr \"导入问答并创建文档\"\n\n#: apps/shared/views/shared_document.py:623\n#: apps/shared/views/shared_document.py:624\n#: apps/shared/views/shared_document.py:625\nmsgid \"Import tables and create shared documents\"\nmsgstr \"导入表格并创建文档\"\n\n#: apps/shared/views/shared_document.py:647\n#: apps/shared/views/shared_document.py:648\nmsgid \"Get shared QA template\"\nmsgstr \"获取共享问答模板\"\n\n#: apps/shared/views/shared_document.py:661\n#: apps/shared/views/shared_document.py:662\nmsgid \"Get shared form template\"\nmsgstr \"获取共享表格模板\"\n\n#: apps/shared/views/shared_knowledge.py:28\n#: apps/shared/views/shared_knowledge.py:29\n#: apps/shared/views/shared_knowledge.py:30\nmsgid \"Get share resource knowledge\"\nmsgstr \"获取共享资源知识库\"\n\n#: apps/shared/views/shared_knowledge.py:48\n#: apps/shared/views/shared_knowledge.py:49\n#: apps/shared/views/shared_knowledge.py:50\nmsgid \"Get shared knowledge list by pagination\"\nmsgstr \"获取共享知识库分页列表\"\n\n#: apps/shared/views/shared_knowledge.py:70\n#: apps/shared/views/shared_knowledge.py:71\n#: apps/shared/views/shared_knowledge.py:72\nmsgid \"Update shared knowledge\"\nmsgstr \"更新共享知识库\"\n\n#: apps/shared/views/shared_knowledge.py:86\n#: apps/shared/views/shared_knowledge.py:87\n#: apps/shared/views/shared_knowledge.py:88\nmsgid \"Get shared knowledge\"\nmsgstr \"获取共享知识库\"\n\n#: apps/shared/views/shared_knowledge.py:101\n#: apps/shared/views/shared_knowledge.py:102\n#: apps/shared/views/shared_knowledge.py:103\nmsgid \"Delete shared knowledge\"\nmsgstr \"删除共享知识库\"\n\n#: apps/shared/views/shared_knowledge.py:119\n#: apps/shared/views/shared_knowledge.py:120\n#: apps/shared/views/shared_knowledge.py:121\nmsgid \"Synchronize the shared knowledge base of the website\"\nmsgstr \"同步共享知识库网站\"\n\n#: apps/shared/views/shared_knowledge.py:145\n#: apps/shared/views/shared_knowledge.py:146\n#: apps/shared/views/shared_knowledge.py:147\nmsgid \"Shared Hit test list\"\nmsgstr \"命中测试列表\"\n\n#: apps/shared/views/shared_knowledge.py:172\n#: apps/shared/views/shared_knowledge.py:173\n#: apps/shared/views/shared_knowledge.py:174\nmsgid \"Shared Re-vectorize\"\nmsgstr \"重新向量化\"\n\n#: apps/shared/views/shared_knowledge.py:192\n#: apps/shared/views/shared_knowledge.py:193\nmsgid \"Export shared knowledge base\"\nmsgstr \"导出共享知识库\"\n\n#: apps/shared/views/shared_knowledge.py:210\n#: apps/shared/views/shared_knowledge.py:211\nmsgid \"Export shared knowledge base containing images\"\nmsgstr \"导出包含图片的共享知识库\"\n\n#: apps/shared/views/shared_knowledge.py:229\n#: apps/shared/views/shared_knowledge.py:230\n#: apps/shared/views/shared_knowledge.py:231\nmsgid \"Shared generate related\"\nmsgstr \"生成相关\"\n\n#: apps/shared/views/shared_knowledge.py:251\n#: apps/shared/views/shared_knowledge.py:252\n#: apps/shared/views/shared_knowledge.py:253\nmsgid \"Get model for shared knowledge base\"\nmsgstr \"获取共享知识库模型\"\n\n#: apps/shared/views/shared_knowledge.py:271\n#: apps/shared/views/shared_knowledge.py:272\n#: apps/shared/views/shared_knowledge.py:273\nmsgid \"Get embedding model for shared knowledge base\"\nmsgstr \"获取共享知识库嵌入模型\"\n\n#: apps/shared/views/shared_knowledge.py:291\n#: apps/shared/views/shared_knowledge.py:293\nmsgid \"Authorization knowledge workspace\"\nmsgstr \"授权知识工作空间\"\n\n#: apps/shared/views/shared_knowledge.py:292\nmsgid \"Authorization knowledge workspace \"\nmsgstr \"授权知识工作空间\"\n\n#: apps/shared/views/shared_knowledge.py:307\n#: apps/shared/views/shared_knowledge.py:308\n#: apps/shared/views/shared_knowledge.py:309\nmsgid \"Get Authorization knowledge workspace\"\nmsgstr \"获取授权知识工作空间\"\n\n#: apps/shared/views/shared_knowledge.py:326\n#: apps/shared/views/shared_knowledge.py:327\n#: apps/shared/views/shared_knowledge.py:328\nmsgid \"Create shared base knowledge\"\nmsgstr \"创建共享知识库\"\n\n#: apps/shared/views/shared_knowledge.py:349\n#: apps/shared/views/shared_knowledge.py:350\n#: apps/shared/views/shared_knowledge.py:351\nmsgid \"Create shared web knowledge\"\nmsgstr \"创建 web 知识库\"\n\n#: apps/shared/views/shared_knowledge.py:381\n#: apps/shared/views/shared_knowledge.py:382\n#: apps/shared/views/shared_knowledge.py:383\nmsgid \"Get shared workspace knowledge\"\nmsgstr \"获取共享工作空间知识库\"\n\n#: apps/shared/views/shared_knowledge.py:402\n#: apps/shared/views/shared_knowledge.py:403\n#: apps/shared/views/shared_knowledge.py:404\nmsgid \"Get shared workspace knowledge list by pagination\"\nmsgstr \"获取共享工作空间知识库分页列表\"\n\n#: apps/shared/views/shared_model.py:213 apps/shared/views/shared_model.py:215\nmsgid \"Authorization model workspace\"\nmsgstr \"授权模型工作空间\"\n\n#: apps/shared/views/shared_model.py:214\nmsgid \"Authorization model workspace \"\nmsgstr \"授权模型工作空间\"\n\n#: apps/shared/views/shared_model.py:229 apps/shared/views/shared_model.py:230\n#: apps/shared/views/shared_model.py:231\nmsgid \"Get Authorization model workspace\"\nmsgstr \"获取授权模型工作空间\"\n\n#: apps/shared/views/shared_model.py:248 apps/shared/views/shared_model.py:249\n#: apps/shared/views/shared_model.py:250\nmsgid \"Get Share model by workspace id\"\nmsgstr \"获取共享模型工作空间 ID\"\n\n#: apps/shared/views/shared_model.py:265 apps/shared/views/shared_model.py:266\n#: apps/shared/views/shared_model.py:267\nmsgid \"Get Share model by workspace id with pagination\"\nmsgstr \"获取共享模型工作空间 ID 分页列表\"\n\n#: apps/shared/views/shared_paragraph.py:24\n#: apps/shared/views/shared_paragraph.py:25\n#: apps/shared/views/shared_paragraph.py:26\nmsgid \"Shared paragraph list\"\nmsgstr \"共享段落列表\"\n\n#: apps/shared/views/shared_paragraph.py:29\n#: apps/shared/views/shared_paragraph.py:50\n#: apps/shared/views/shared_paragraph.py:76\n#: apps/shared/views/shared_paragraph.py:93\n#: apps/shared/views/shared_paragraph.py:125\n#: apps/shared/views/shared_paragraph.py:151\n#: apps/shared/views/shared_paragraph.py:179\n#: apps/shared/views/shared_paragraph.py:201\n#: apps/shared/views/shared_paragraph.py:233\n#: apps/shared/views/shared_paragraph.py:259\n#: apps/shared/views/shared_paragraph.py:283\n#: apps/shared/views/shared_paragraph.py:314\n#: apps/shared/views/shared_paragraph.py:345\n#: apps/shared/views/shared_paragraph.py:371\nmsgid \"Shared Knowledge/Documentation/Paragraph\"\nmsgstr \"共享知识库/文档/段落\"\n\n#: apps/shared/views/shared_paragraph.py:45\n#: apps/shared/views/shared_paragraph.py:46\nmsgid \"Create shared paragraph\"\nmsgstr \"创建段落\"\n\n#: apps/shared/views/shared_paragraph.py:70\n#: apps/shared/views/shared_paragraph.py:71\n#: apps/shared/views/shared_paragraph.py:72\nmsgid \"Batch shared paragraph\"\nmsgstr \"批量关联段落\"\n\n#: apps/shared/views/shared_paragraph.py:88\n#: apps/shared/views/shared_paragraph.py:89\nmsgid \"Migrate shared paragraphs in batches\"\nmsgstr \"批量迁移共享段落\"\n\n#: apps/shared/views/shared_paragraph.py:119\n#: apps/shared/views/shared_paragraph.py:120\n#: apps/shared/views/shared_paragraph.py:121\nmsgid \"Batch generate shared related\"\nmsgstr \"批量生成相关\"\n\n#: apps/shared/views/shared_paragraph.py:145\n#: apps/shared/views/shared_paragraph.py:146\n#: apps/shared/views/shared_paragraph.py:147\nmsgid \"Modify shared paragraph data\"\nmsgstr \"修改段落数据\"\n\n#: apps/shared/views/shared_paragraph.py:174\n#: apps/shared/views/shared_paragraph.py:175\n#: apps/shared/views/shared_paragraph.py:176\nmsgid \"Get shared paragraph details\"\nmsgstr \"获取段落详情\"\n\n#: apps/shared/views/shared_paragraph.py:196\n#: apps/shared/views/shared_paragraph.py:197\n#: apps/shared/views/shared_paragraph.py:198\nmsgid \"Delete shared paragraph\"\nmsgstr \"删除段落\"\n\n#: apps/shared/views/shared_paragraph.py:227\n#: apps/shared/views/shared_paragraph.py:228\n#: apps/shared/views/shared_paragraph.py:229\nmsgid \"Add shared associated questions\"\nmsgstr \"添加关联问题\"\n\n#: apps/shared/views/shared_paragraph.py:254\n#: apps/shared/views/shared_paragraph.py:255\n#: apps/shared/views/shared_paragraph.py:256\nmsgid \"Get a list of shared paragraph questions\"\nmsgstr \"获取共享段落问题列表\"\n\n#: apps/shared/views/shared_paragraph.py:277\n#: apps/shared/views/shared_paragraph.py:278\n#: apps/shared/views/shared_paragraph.py:279\nmsgid \"Disassociation shared issue\"\nmsgstr \"取消关联问题\"\n\n#: apps/shared/views/shared_paragraph.py:308\n#: apps/shared/views/shared_paragraph.py:309\n#: apps/shared/views/shared_paragraph.py:310\nmsgid \"Related shared questions\"\nmsgstr \"关联问题\"\n\n#: apps/shared/views/shared_paragraph.py:340\n#: apps/shared/views/shared_paragraph.py:341\n#: apps/shared/views/shared_paragraph.py:342\nmsgid \"Get shared paragraph list by pagination\"\nmsgstr \"获取段落列表\"\n\n#: apps/shared/views/shared_problem.py:23\n#: apps/shared/views/shared_problem.py:24\n#: apps/shared/views/shared_problem.py:25\nmsgid \"Shared question list\"\nmsgstr \"问题列表\"\n\n#: apps/shared/views/shared_problem.py:28\n#: apps/shared/views/shared_problem.py:50\n#: apps/shared/views/shared_problem.py:71\n#: apps/shared/views/shared_problem.py:94\n#: apps/shared/views/shared_problem.py:115\n#: apps/shared/views/shared_problem.py:135\n#: apps/shared/views/shared_problem.py:158\n#: apps/shared/views/shared_problem.py:182\nmsgid \"Shared Knowledge/Documentation/Paragraph/Question\"\nmsgstr \"知识库/文档/段落/问题\"\n\n#: apps/shared/views/shared_problem.py:44\n#: apps/shared/views/shared_problem.py:45\n#: apps/shared/views/shared_problem.py:46\nmsgid \"Create shared question\"\nmsgstr \"创建问题\"\n\n#: apps/shared/views/shared_problem.py:66\n#: apps/shared/views/shared_problem.py:67\n#: apps/shared/views/shared_problem.py:68\nmsgid \"Get a list of associated shared paragraphs\"\nmsgstr \"获取关联段落列表\"\n\n#: apps/shared/views/shared_problem.py:88\n#: apps/shared/views/shared_problem.py:89\n#: apps/shared/views/shared_problem.py:90\nmsgid \"Batch associated shared paragraphs\"\nmsgstr \"批量关联段落\"\n\n#: apps/shared/views/shared_problem.py:109\n#: apps/shared/views/shared_problem.py:110\n#: apps/shared/views/shared_problem.py:111\nmsgid \"Batch deletion shared issues\"\nmsgstr \"批量删除问题\"\n\n#: apps/shared/views/shared_problem.py:130\n#: apps/shared/views/shared_problem.py:131\n#: apps/shared/views/shared_problem.py:132\nmsgid \"Delete shared question\"\nmsgstr \"删除问题\"\n\n#: apps/shared/views/shared_problem.py:152\n#: apps/shared/views/shared_problem.py:153\n#: apps/shared/views/shared_problem.py:154\nmsgid \"Modify shared question\"\nmsgstr \"修改问题\"\n\n#: apps/shared/views/shared_problem.py:177\n#: apps/shared/views/shared_problem.py:178\n#: apps/shared/views/shared_problem.py:179\nmsgid \"Get the list of shared questions by page\"\nmsgstr \"分页获取问题列表\"\n\n#: apps/shared/views/shared_tool.py:25 apps/shared/views/shared_tool.py:26\n#: apps/shared/views/shared_tool.py:27\nmsgid \"Get share resource tool\"\nmsgstr \"获取共享资源工具\"\n\n#: apps/shared/views/shared_tool.py:43 apps/shared/views/shared_tool.py:44\n#: apps/shared/views/shared_tool.py:45\nmsgid \"Create shared tool\"\nmsgstr \"创建共享工具\"\n\n#: apps/shared/views/shared_tool.py:62 apps/shared/views/shared_tool.py:63\n#: apps/shared/views/shared_tool.py:64\nmsgid \"Update shared tool\"\nmsgstr \"更新共享工具\"\n\n#: apps/shared/views/shared_tool.py:78 apps/shared/views/shared_tool.py:79\n#: apps/shared/views/shared_tool.py:80\nmsgid \"Get shared tool\"\nmsgstr \"获取共享工具\"\n\n#: apps/shared/views/shared_tool.py:93 apps/shared/views/shared_tool.py:94\n#: apps/shared/views/shared_tool.py:95\nmsgid \"Delete shared tool\"\nmsgstr \"删除共享工具\"\n\n#: apps/shared/views/shared_tool.py:111 apps/shared/views/shared_tool.py:112\n#: apps/shared/views/shared_tool.py:113\nmsgid \"Get shared tool list by pagination\"\nmsgstr \"获取共享工具列表\"\n\n#: apps/shared/views/shared_tool.py:134 apps/shared/views/shared_tool.py:135\n#: apps/shared/views/shared_tool.py:136\nmsgid \"Import shared tool\"\nmsgstr \"导入共享工具\"\n\n#: apps/shared/views/shared_tool.py:152 apps/shared/views/shared_tool.py:153\n#: apps/shared/views/shared_tool.py:154\nmsgid \"Export shared tool\"\nmsgstr \"导出共享工具\"\n\n#: apps/shared/views/shared_tool.py:170 apps/shared/views/shared_tool.py:171\n#: apps/shared/views/shared_tool.py:172\nmsgid \"Debug shared Tool\"\nmsgstr \"调试共享工具\"\n\n#: apps/shared/views/shared_tool.py:188 apps/shared/views/shared_tool.py:189\n#: apps/shared/views/shared_tool.py:190\nmsgid \"Check shared code\"\nmsgstr \"检查代码\"\n\n#: apps/shared/views/shared_tool.py:212 apps/shared/views/shared_tool.py:213\n#: apps/shared/views/shared_tool.py:214\nmsgid \"Edit shared tool icon\"\nmsgstr \"修改共享工具图标\"\n\n#: apps/shared/views/shared_tool.py:233 apps/shared/views/shared_tool.py:235\nmsgid \"Authorization tool workspace\"\nmsgstr \"授权工作空间工具\"\n\n#: apps/shared/views/shared_tool.py:234\nmsgid \"Authorization tool workspace \"\nmsgstr \"授权工作空间工具\"\n\n#: apps/shared/views/shared_tool.py:249 apps/shared/views/shared_tool.py:250\n#: apps/shared/views/shared_tool.py:251\nmsgid \"Get Authorization tool workspace\"\nmsgstr \"获取授权工作空间工具\"\n\n#: apps/shared/views/shared_tool.py:268 apps/shared/views/shared_tool.py:269\n#: apps/shared/views/shared_tool.py:270\nmsgid \"Get shared workspace tool\"\nmsgstr \"获取共享工作空间工具\"\n\n#: apps/shared/views/shared_tool.py:289 apps/shared/views/shared_tool.py:290\n#: apps/shared/views/shared_tool.py:291\nmsgid \"Get shared workspace tool list by pagination\"\nmsgstr \"获取共享工作空间工具分页列表\"\n\n#: apps/system_manage/serializers/email_setting.py:28\nmsgid \"SMTP host\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:29\nmsgid \"SMTP port\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:30\n#: apps/system_manage/serializers/email_setting.py:34\nmsgid \"Sender's email\"\nmsgstr \"发送者邮箱\"\n\n#: apps/system_manage/serializers/email_setting.py:31 apps/users/api/user.py:93\n#: apps/users/serializers/login.py:28 apps/users/serializers/user.py:57\n#: apps/users/serializers/user.py:126 apps/users/serializers/user.py:291\n#: apps/users/serializers/user.py:517 apps/xpack/api/auth_config.py:25\n#: apps/xpack/api/chat_user.py:71 apps/xpack/serializers/chat_auth.py:24\n#: apps/xpack/serializers/chat_user.py:74\n#: apps/xpack/serializers/chat_user.py:267\n#: apps/xpack/serializers/chat_user.py:601\nmsgid \"Password\"\nmsgstr \"密码\"\n\n#: apps/system_manage/serializers/email_setting.py:32\nmsgid \"Whether to enable TLS\"\nmsgstr \"是否启用 TLS\"\n\n#: apps/system_manage/serializers/email_setting.py:33\nmsgid \"Whether to enable SSL\"\nmsgstr \"是否启用 SSL\"\n\n#: apps/system_manage/serializers/email_setting.py:49\nmsgid \"Email verification failed\"\nmsgstr \"邮件认证失败\"\n\n#: apps/system_manage/serializers/user_resource_permission.py:53\nmsgid \"target id\"\nmsgstr \"当前 ID\"\n\n#: apps/system_manage/serializers/user_resource_permission.py:70\nmsgid \"Non-existent application|knowledge base id[\"\nmsgstr \"不存在的智能体|知识库 ID[\"\n\n#: apps/system_manage/views/email_setting.py:50\n#: apps/system_manage/views/email_setting.py:51\n#: apps/system_manage/views/email_setting.py:52\nmsgid \"Create or update email settings\"\nmsgstr \"创建或更新邮件设置\"\n\n#: apps/system_manage/views/email_setting.py:55\n#: apps/system_manage/views/email_setting.py:70\n#: apps/system_manage/views/email_setting.py:86\nmsgid \"Email Settings\"\nmsgstr \"邮箱设置\"\n\n#: apps/system_manage/views/email_setting.py:66\n#: apps/system_manage/views/email_setting.py:67\nmsgid \"Test email settings\"\nmsgstr \"测试邮箱设置\"\n\n#: apps/system_manage/views/email_setting.py:82\n#: apps/system_manage/views/email_setting.py:83\n#: apps/system_manage/views/email_setting.py:84\nmsgid \"Get email settings\"\nmsgstr \"获取邮箱设置\"\n\n#: apps/system_manage/views/system_profile.py:22\n#: apps/system_manage/views/system_profile.py:23\nmsgid \"Get MaxKB related information\"\nmsgstr \"获取 MaxKB 相关信息\"\n\n#: apps/system_manage/views/system_profile.py:25\nmsgid \"System parameters\"\nmsgstr \"系统参数\"\n\n#: apps/system_manage/views/user_resource_permission.py:40\n#: apps/system_manage/views/user_resource_permission.py:41\nmsgid \"Obtain resource authorization list\"\nmsgstr \"获取资源授权列表\"\n\n#: apps/system_manage/views/user_resource_permission.py:44\n#: apps/system_manage/views/user_resource_permission.py:60\nmsgid \"Resources authorization\"\nmsgstr \"资源授权\"\n\n#: apps/system_manage/views/user_resource_permission.py:55\n#: apps/system_manage/views/user_resource_permission.py:56\nmsgid \"Modify the resource authorization list\"\nmsgstr \"修改资源授权列表\"\n\n#: apps/tools/serializers/tool.py:118 apps/tools/serializers/tool.py:169\nmsgid \"variable name\"\nmsgstr \"变量名称\"\n\n#: apps/tools/serializers/tool.py:122\nmsgid \"fields only support string|int|dict|array|float\"\nmsgstr \"字段仅支持字符串|整数|字典|数组|浮点数\"\n\n#: apps/tools/serializers/tool.py:131\nmsgid \"field name\"\nmsgstr \"字段名称\"\n\n#: apps/tools/serializers/tool.py:132\nmsgid \"field label\"\nmsgstr \"标签\"\n\n#: apps/tools/serializers/tool.py:146 apps/tools/serializers/tool.py:160\n#: apps/tools/serializers/tool.py:174\nmsgid \"tool content\"\nmsgstr \"工具内容\"\n\n#: apps/tools/serializers/tool.py:148 apps/tools/serializers/tool.py:161\n#: apps/tools/serializers/tool.py:175\nmsgid \"input field list\"\nmsgstr \"输入字段列表\"\n\n#: apps/tools/serializers/tool.py:150 apps/tools/serializers/tool.py:162\n#: apps/tools/serializers/tool.py:176\nmsgid \"init field list\"\nmsgstr \"内置字段列表\"\n\n#: apps/tools/serializers/tool.py:163 apps/tools/serializers/tool.py:177\nmsgid \"init params\"\nmsgstr \"内置参数\"\n\n#: apps/tools/serializers/tool.py:170\nmsgid \"variable value\"\nmsgstr \"变量名称\"\n\n#: apps/tools/serializers/tool.py:182\nmsgid \"function content\"\nmsgstr \"工具内容\"\n\n#: apps/tools/serializers/tool.py:238\nmsgid \"field has no value set\"\nmsgstr \"字段未设置值\"\n\n#: apps/tools/serializers/tool.py:262\n#, python-brace-format\nmsgid \"Field: {name} Type: {_type} Value: {value} Type conversion error\"\nmsgstr \"字段：{name} 类型：{_type} 值：{value} 类型转换错误\"\n\n#: apps/tools/serializers/tool.py:275\nmsgid \"Tool not found\"\nmsgstr \"工具不存在\"\n\n#: apps/tools/serializers/tool.py:388\nmsgid \"function ID\"\nmsgstr \"工具 ID\"\n\n#: apps/tools/views/tool.py:33 apps/tools/views/tool.py:34\n#: apps/tools/views/tool.py:35\nmsgid \"Create tool\"\nmsgstr \"创建工具\"\n\n#: apps/tools/views/tool.py:56 apps/tools/views/tool.py:57\n#: apps/tools/views/tool.py:58\nmsgid \"Get tool by folder\"\nmsgstr \"通过文件夹获取工具\"\n\n#: apps/tools/views/tool.py:77 apps/tools/views/tool.py:78\n#: apps/tools/views/tool.py:79\nmsgid \"Debug Tool\"\nmsgstr \"调试工具\"\n\n#: apps/tools/views/tool.py:98 apps/tools/views/tool.py:99\n#: apps/tools/views/tool.py:100\nmsgid \"Update tool\"\nmsgstr \"更新工具\"\n\n#: apps/tools/views/tool.py:122 apps/tools/views/tool.py:123\n#: apps/tools/views/tool.py:124\nmsgid \"Get tool\"\nmsgstr \"获取工具\"\n\n#: apps/tools/views/tool.py:141 apps/tools/views/tool.py:142\n#: apps/tools/views/tool.py:143\nmsgid \"Delete tool\"\nmsgstr \"删除工具\"\n\n#: apps/tools/views/tool.py:167 apps/tools/views/tool.py:168\n#: apps/tools/views/tool.py:169\nmsgid \"Get tool list by pagination\"\nmsgstr \"获取工具列表\"\n\n#: apps/tools/views/tool.py:195 apps/tools/views/tool.py:196\n#: apps/tools/views/tool.py:197\nmsgid \"Import tool\"\nmsgstr \"导入工具\"\n\n#: apps/tools/views/tool.py:218 apps/tools/views/tool.py:219\n#: apps/tools/views/tool.py:220\nmsgid \"Export tool\"\nmsgstr \"导出工具\"\n\n#: apps/tools/views/tool.py:244 apps/tools/views/tool.py:245\n#: apps/tools/views/tool.py:246\nmsgid \"Check code\"\nmsgstr \"检查代码\"\n\n#: apps/tools/views/tool.py:268 apps/tools/views/tool.py:269\n#: apps/tools/views/tool.py:270\nmsgid \"Edit tool icon\"\nmsgstr \"修改工具图标\"\n\n#: apps/users/api/user.py:154\nmsgid \"Email or Username\"\nmsgstr \"邮箱或用户名\"\n\n#: apps/users/api/user.py:224\nmsgid \"Language\"\nmsgstr \"语言\"\n\n#: apps/users/serializers/login.py:29 apps/users/serializers/login.py:69\nmsgid \"captcha\"\nmsgstr \"验证码\"\n\n#: apps/users/serializers/login.py:50\n#: apps/xpack/serializers/chat_user_serializer.py:120\nmsgid \"Captcha code error or expiration\"\nmsgstr \"验证码错误或过期\"\n\n#: apps/users/serializers/login.py:55\n#: apps/xpack/serializers/auth_config_serializer.py:192\n#: apps/xpack/serializers/chat_user_serializer.py:125\n#: apps/xpack/serializers/qr_login/qr_login.py:37\nmsgid \"The user has been disabled, please contact the administrator!\"\nmsgstr \"用户已被禁用，请联系管理员！\"\n\n#: apps/users/serializers/user.py:47\nmsgid \"Is Edit Password\"\nmsgstr \"是否编辑密码\"\n\n#: apps/users/serializers/user.py:48\nmsgid \"permissions\"\nmsgstr \"无权限访问\"\n\n#: apps/users/serializers/user.py:58 apps/users/serializers/user.py:106\n#: apps/users/serializers/user.py:250 apps/users/serializers/user.py:557\n#: apps/users/serializers/user.py:641 apps/xpack/api/chat_user.py:107\n#: apps/xpack/serializers/chat_user.py:54\n#: apps/xpack/serializers/chat_user.py:227\nmsgid \"Email\"\nmsgstr \"邮箱\"\n\n#: apps/users/serializers/user.py:59 apps/users/serializers/user.py:140\n#: apps/xpack/serializers/chat_user.py:88\nmsgid \"Nick name\"\nmsgstr \"姓名\"\n\n#: apps/users/serializers/user.py:60 apps/users/serializers/user.py:145\n#: apps/users/serializers/user.py:263 apps/xpack/api/chat_user.py:108\n#: apps/xpack/serializers/chat_user.py:93\n#: apps/xpack/serializers/chat_user.py:240\nmsgid \"Phone\"\nmsgstr \"手机\"\n\n#: apps/users/serializers/user.py:120 apps/xpack/serializers/chat_user.py:68\nmsgid \"Username must be 4-64 characters long\"\nmsgstr \"用户名必须为4-64个字符\"\n\n#: apps/users/serializers/user.py:133 apps/users/serializers/user.py:298\n#: apps/xpack/serializers/chat_user.py:81\n#: apps/xpack/serializers/chat_user.py:274\nmsgid \"\"\n\"The password must be 6-20 characters long and must be a combination of \"\n\"letters, numbers, and special characters.\"\nmsgstr \"密码必须为6-20个字符，且必须包含大小写字母、数字和特殊字符。\"\n\n#: apps/users/serializers/user.py:170\nmsgid \"Email or username\"\nmsgstr \"邮箱或用户名\"\n\n#: apps/users/serializers/user.py:226\nmsgid \"\"\n\"The community version supports up to 2 users. If you need more users, please \"\n\"contact us (https://fit2cloud.com/).\"\nmsgstr \"\"\n\"社区版支持最多2个用户，如需更多用户，请联系我们（https://fit2cloud.com/）。\"\n\n#: apps/users/serializers/user.py:270 apps/xpack/api/auth_config.py:31\n#: apps/xpack/api/chat_user.py:109 apps/xpack/serializers/chat_user.py:247\nmsgid \"Is Active\"\nmsgstr \"是否启用\"\n\n#: apps/users/serializers/user.py:281 apps/xpack/serializers/chat_user.py:262\nmsgid \"Nickname is already in use\"\nmsgstr \"Nickname已被使用\"\n\n#: apps/users/serializers/user.py:286\nmsgid \"Email is already in use\"\nmsgstr \"邮箱已被使用\"\n\n#: apps/users/serializers/user.py:305 apps/xpack/serializers/chat_user.py:281\nmsgid \"Re Password\"\nmsgstr \"确认密码\"\n\n#: apps/users/serializers/user.py:310 apps/users/serializers/user.py:522\n#: apps/users/serializers/user.py:529 apps/xpack/serializers/chat_user.py:286\n#: apps/xpack/serializers/chat_user.py:606\n#: apps/xpack/serializers/chat_user.py:613\nmsgid \"\"\n\"The confirmation password must be 6-20 characters long and must be a \"\n\"combination of letters, numbers, and special characters.\"\nmsgstr \"确认密码必须为6-20个字符，且必须包含字母、数字和特殊字符。\"\n\n#: apps/users/serializers/user.py:333 apps/xpack/serializers/chat_user.py:309\nmsgid \"User does not exist\"\nmsgstr \"用户不存在\"\n\n#: apps/users/serializers/user.py:348\nmsgid \"Unable to delete administrator\"\nmsgstr \"无法删除管理员\"\n\n#: apps/users/serializers/user.py:366\nmsgid \"Cannot modify administrator status\"\nmsgstr \"不能修改管理员状态\"\n\n#: apps/users/serializers/user.py:478 apps/xpack/serializers/chat_user.py:164\n#: apps/xpack/serializers/chat_user.py:192\n#: apps/xpack/serializers/chat_user.py:512\nmsgid \"User IDs cannot be empty\"\nmsgstr \"用户 ID 不能为空\"\n\n#: apps/users/serializers/user.py:524 apps/xpack/serializers/chat_user.py:608\nmsgid \"Confirm Password\"\nmsgstr \"确认密码\"\n\n#: apps/users/serializers/user.py:561 apps/users/serializers/user.py:647\n#: apps/xpack/api/knowledge_lark.py:26\nmsgid \"Type\"\nmsgstr \"类型\"\n\n#: apps/users/serializers/user.py:563 apps/users/serializers/user.py:651\nmsgid \"The type only supports register|reset_password\"\nmsgstr \"类型仅支持 register|reset_password\"\n\n#: apps/users/serializers/user.py:581\n#, python-brace-format\nmsgid \"Do not send emails again within {seconds} seconds\"\nmsgstr \"不要在 {seconds} 秒内再次发送邮件\"\n\n#: apps/users/serializers/user.py:611\nmsgid \"\"\n\"The email service has not been set up. Please contact the administrator to \"\n\"set up the email service in [Email Settings].\"\nmsgstr \"邮箱服务尚未设置，请联系管理员在 [邮箱设置] 中设置邮箱服务。\"\n\n#: apps/users/serializers/user.py:622\n#, python-brace-format\nmsgid \"【Intelligent knowledge base question and answer system-{action}】\"\nmsgstr \"【智能知识库问答系统-{action}】\"\n\n#: apps/users/serializers/user.py:623\nmsgid \"User registration\"\nmsgstr \"用户注册\"\n\n#: apps/users/serializers/user.py:623 apps/users/views/user.py:248\n#: apps/users/views/user.py:249 apps/users/views/user.py:250\n#: apps/users/views/user.py:283 apps/users/views/user.py:284\n#: apps/users/views/user.py:285\nmsgid \"Change password\"\nmsgstr \"修改密码\"\n\n#: apps/users/serializers/user.py:644\nmsgid \"Verification code\"\nmsgstr \"验证码\"\n\n#: apps/users/serializers/user.py:672\nmsgid \"language only support:\"\nmsgstr \"语言仅支持：\"\n\n#: apps/users/views/login.py:38 apps/users/views/login.py:39\n#: apps/users/views/login.py:40 apps/xpack/views/chat_user_auth.py:184\n#: apps/xpack/views/chat_user_auth.py:185\n#: apps/xpack/views/chat_user_auth.py:186\nmsgid \"Log in\"\nmsgstr \"登录\"\n\n#: apps/users/views/login.py:55 apps/users/views/login.py:56\n#: apps/users/views/login.py:57 apps/xpack/views/chat_user_auth.py:203\n#: apps/xpack/views/chat_user_auth.py:204\n#: apps/xpack/views/chat_user_auth.py:205\nmsgid \"Sign out\"\nmsgstr \"登出\"\n\n#: apps/users/views/user.py:60 apps/users/views/user.py:61\n#: apps/users/views/user.py:62 apps/users/views/user.py:74\n#: apps/users/views/user.py:75 apps/xpack/views/system_chat_user.py:359\n#: apps/xpack/views/system_chat_user.py:360\n#: apps/xpack/views/system_chat_user.py:361\nmsgid \"Get current user information\"\nmsgstr \"获取当前用户信息\"\n\n#: apps/users/views/user.py:120 apps/users/views/user.py:121\n#: apps/users/views/user.py:122\nmsgid \"Get all user\"\nmsgstr \"获取所有用户\"\n\n#: apps/users/views/user.py:133 apps/users/views/user.py:134\n#: apps/users/views/user.py:135\nmsgid \"Get user list under workspace\"\nmsgstr \"获取工作空间下用户列表\"\n\n#: apps/users/views/user.py:147 apps/users/views/user.py:148\n#: apps/users/views/user.py:149\nmsgid \"Get user member under workspace\"\nmsgstr \"获取工作空间下用户成员\"\n\n#: apps/users/views/user.py:161 apps/users/views/user.py:162\n#: apps/users/views/user.py:163\nmsgid \"Create user\"\nmsgstr \"创建用户\"\n\n#: apps/users/views/user.py:177 apps/users/views/user.py:178\n#: apps/users/views/user.py:179\nmsgid \"Get default password\"\nmsgstr \"获取默认密码\"\n\n#: apps/users/views/user.py:190 apps/users/views/user.py:191\n#: apps/users/views/user.py:192\nmsgid \"Delete user\"\nmsgstr \"删除用户\"\n\n#: apps/users/views/user.py:203 apps/users/views/user.py:204\n#: apps/users/views/user.py:205\nmsgid \"Get user information\"\nmsgstr \"获取用户信息\"\n\n#: apps/users/views/user.py:214 apps/users/views/user.py:215\n#: apps/users/views/user.py:216\nmsgid \"Update user information\"\nmsgstr \"更新当前用户信息\"\n\n#: apps/users/views/user.py:232 apps/users/views/user.py:233\n#: apps/users/views/user.py:234\nmsgid \"Batch delete user\"\nmsgstr \"批量删除用户\"\n\n#: apps/users/views/user.py:266 apps/users/views/user.py:267\n#: apps/users/views/user.py:268 apps/workspace/views/workspace_chat_user.py:168\n#: apps/workspace/views/workspace_chat_user.py:169\n#: apps/workspace/views/workspace_chat_user.py:170\n#: apps/xpack/views/system_chat_user.py:197\n#: apps/xpack/views/system_chat_user.py:198\n#: apps/xpack/views/system_chat_user.py:199\nmsgid \"Get user paginated list\"\nmsgstr \"获取用户分页列表\"\n\n#: apps/users/views/user.py:300 apps/users/views/user.py:301\n#: apps/users/views/user.py:302\nmsgid \"Send email\"\nmsgstr \"发送邮件\"\n\n#: apps/users/views/user.py:318 apps/users/views/user.py:319\n#: apps/users/views/user.py:320\nmsgid \"Check whether the verification code is correct\"\nmsgstr \"检查验证码是否正确\"\n\n#: apps/users/views/user.py:335 apps/users/views/user.py:336\n#: apps/users/views/user.py:337\nmsgid \"Send email to current user\"\nmsgstr \"发送邮件给当前用户\"\n\n#: apps/users/views/user.py:353 apps/users/views/user.py:354\n#: apps/users/views/user.py:355 apps/xpack/views/system_chat_user.py:336\n#: apps/xpack/views/system_chat_user.py:337\n#: apps/xpack/views/system_chat_user.py:338\nmsgid \"Modify current user password\"\nmsgstr \"修改当前用户密码\"\n\n#: apps/users/views/user.py:368 apps/xpack/views/system_chat_user.py:352\nmsgid \"Failed to change password\"\nmsgstr \"修改密码失败\"\n\n#: apps/workspace/api/workspace.py:73\n#: apps/workspace/serializers/workspace_serializers.py:213\nmsgid \"User Relation ID\"\nmsgstr \"用户关系 ID\"\n\n#: apps/workspace/api/workspace.py:87\nmsgid \"Role Name\"\nmsgstr \"角色名称\"\n\n#: apps/workspace/serializers/workspace_serializers.py:42\n#: apps/workspace/serializers/workspace_serializers.py:95\n#: apps/workspace/serializers/workspace_serializers.py:110\n#: apps/workspace/serializers/workspace_serializers.py:177\n#: apps/workspace/serializers/workspace_serializers.py:219\n#: apps/workspace/serializers/workspace_serializers.py:246\nmsgid \"Workspace does not exist\"\nmsgstr \"工作空间不存在\"\n\n#: apps/workspace/serializers/workspace_serializers.py:49\nmsgid \"Workspace name already exists\"\nmsgstr \"工作空间名称已存在\"\n\n#: apps/workspace/serializers/workspace_serializers.py:97\n#: apps/workspace/serializers/workspace_serializers.py:112\nmsgid \"Default workspace cannot be deleted\"\nmsgstr \"默认工作空间不能被删除\"\n\n#: apps/workspace/serializers/workspace_serializers.py:122\nmsgid \"Applications Resource\"\nmsgstr \"智能体资源\"\n\n#: apps/workspace/serializers/workspace_serializers.py:124\nmsgid \"Knowledge Resource\"\nmsgstr \"知识库资源\"\n\n#: apps/workspace/serializers/workspace_serializers.py:130\nmsgid \"This workspace contains %s, cannot be deleted.\"\nmsgstr \"该工作空间下存在 %s，不能被删除。\"\n\n#: apps/workspace/serializers/workspace_serializers.py:166\nmsgid \"Role IDs\"\nmsgstr \"角色 IDs\"\n\n#: apps/workspace/views/workspace.py:32 apps/workspace/views/workspace.py:33\n#: apps/workspace/views/workspace.py:34\nmsgid \"Create or update workspace\"\nmsgstr \"创建或更新工作空间\"\n\n#: apps/workspace/views/workspace.py:45 apps/workspace/views/workspace.py:46\n#: apps/workspace/views/workspace.py:47\nmsgid \"Get system workspace list\"\nmsgstr \"获取系统工作空间列表\"\n\n#: apps/workspace/views/workspace.py:58 apps/workspace/views/workspace.py:59\n#: apps/workspace/views/workspace.py:60\nmsgid \"Delete workspace\"\nmsgstr \"删除工作空间\"\n\n#: apps/workspace/views/workspace.py:75 apps/workspace/views/workspace.py:76\n#: apps/workspace/views/workspace.py:77\nmsgid \"Check workspace can it be deleted\"\nmsgstr \"检查工作空间是否可以被删除\"\n\n#: apps/workspace/views/workspace.py:95 apps/workspace/views/workspace.py:96\n#: apps/workspace/views/workspace.py:97\nmsgid \"Add member to system workspace\"\nmsgstr \"系统工作空间添加成员\"\n\n#: apps/workspace/views/workspace.py:114 apps/workspace/views/workspace.py:115\n#: apps/workspace/views/workspace.py:116\nmsgid \"Remove member from system workspace\"\nmsgstr \"系统工作空间移除成员\"\n\n#: apps/workspace/views/workspace.py:133 apps/workspace/views/workspace.py:134\n#: apps/workspace/views/workspace.py:135\nmsgid \"Get system workspace member list\"\nmsgstr \"获取系统工作空间成员列表\"\n\n#: apps/workspace/views/workspace.py:151 apps/workspace/views/workspace.py:152\n#: apps/workspace/views/workspace.py:153\nmsgid \"Get workspace list\"\nmsgstr \"获取工作空间列表\"\n\n#: apps/workspace/views/workspace.py:164 apps/workspace/views/workspace.py:165\n#: apps/workspace/views/workspace.py:166\nmsgid \"Add member to workspace\"\nmsgstr \"工作空间添加成员\"\n\n#: apps/workspace/views/workspace.py:183 apps/workspace/views/workspace.py:184\n#: apps/workspace/views/workspace.py:185\nmsgid \"Remove member from workspace\"\nmsgstr \"工作空间移除成员\"\n\n#: apps/workspace/views/workspace.py:202 apps/workspace/views/workspace.py:203\n#: apps/workspace/views/workspace.py:204\nmsgid \"Get workspace member list\"\nmsgstr \"获取工作空间成员列表\"\n\n#: apps/workspace/views/workspace.py:219 apps/workspace/views/workspace.py:220\n#: apps/workspace/views/workspace.py:221\nmsgid \"Get workspace list by current user\"\nmsgstr \"获取当前用户工作空间列表\"\n\n#: apps/workspace/views/workspace.py:232 apps/workspace/views/workspace.py:233\n#: apps/workspace/views/workspace.py:234\nmsgid \"Get workspace list by user\"\nmsgstr \"根据用户获取工作空间列表\"\n\n#: apps/workspace/views/workspace.py:246 apps/workspace/views/workspace.py:247\n#: apps/workspace/views/workspace.py:248\nmsgid \"Get current user role list\"\nmsgstr \"获取当前用户角色列表\"\n\n#: apps/workspace/views/workspace_chat_user.py:44\n#: apps/workspace/views/workspace_chat_user.py:45\n#: apps/workspace/views/workspace_chat_user.py:46\n#: apps/workspace/views/workspace_chat_user.py:51\n#: apps/xpack/views/system_chat_user.py:57\n#: apps/xpack/views/system_chat_user.py:58\n#: apps/xpack/views/system_chat_user.py:59\nmsgid \"Create chat user\"\nmsgstr \"创建对话用户\"\n\n#: apps/workspace/views/workspace_chat_user.py:47\n#: apps/workspace/views/workspace_chat_user.py:51\n#: apps/workspace/views/workspace_chat_user.py:63\n#: apps/workspace/views/workspace_chat_user.py:67\n#: apps/workspace/views/workspace_chat_user.py:76\n#: apps/workspace/views/workspace_chat_user.py:87\n#: apps/workspace/views/workspace_chat_user.py:92\n#: apps/workspace/views/workspace_chat_user.py:105\n#: apps/workspace/views/workspace_chat_user.py:120\n#: apps/workspace/views/workspace_chat_user.py:124\n#: apps/workspace/views/workspace_chat_user.py:136\n#: apps/workspace/views/workspace_chat_user.py:140\n#: apps/workspace/views/workspace_chat_user.py:152\n#: apps/workspace/views/workspace_chat_user.py:171\nmsgid \"Workspace/Chat user\"\nmsgstr \"工作空间/对话用户\"\n\n#: apps/workspace/views/workspace_chat_user.py:60\n#: apps/workspace/views/workspace_chat_user.py:61\n#: apps/workspace/views/workspace_chat_user.py:62\n#: apps/workspace/views/workspace_chat_user.py:67\n#: apps/xpack/views/system_chat_user.py:86\n#: apps/xpack/views/system_chat_user.py:87\n#: apps/xpack/views/system_chat_user.py:88\nmsgid \"Delete chat user\"\nmsgstr \"删除对话用户\"\n\n#: apps/workspace/views/workspace_chat_user.py:73\n#: apps/workspace/views/workspace_chat_user.py:74\n#: apps/workspace/views/workspace_chat_user.py:75\n#: apps/xpack/views/system_chat_user.py:99\n#: apps/xpack/views/system_chat_user.py:100\n#: apps/xpack/views/system_chat_user.py:101\nmsgid \"Get chat user information\"\nmsgstr \"获取对话用户信息\"\n\n#: apps/workspace/views/workspace_chat_user.py:84\n#: apps/workspace/views/workspace_chat_user.py:85\n#: apps/workspace/views/workspace_chat_user.py:86\n#: apps/workspace/views/workspace_chat_user.py:92\n#: apps/xpack/views/system_chat_user.py:110\n#: apps/xpack/views/system_chat_user.py:111\n#: apps/xpack/views/system_chat_user.py:112\nmsgid \"Update chat user information\"\nmsgstr \"更新对话用户信息\"\n\n#: apps/workspace/views/workspace_chat_user.py:102\n#: apps/workspace/views/workspace_chat_user.py:103\n#: apps/workspace/views/workspace_chat_user.py:104\n#: apps/workspace/views/workspace_chat_user.py:261\n#: apps/workspace/views/workspace_chat_user.py:262\n#: apps/workspace/views/workspace_chat_user.py:263\n#: apps/xpack/views/system_chat_user.py:128\n#: apps/xpack/views/system_chat_user.py:129\n#: apps/xpack/views/system_chat_user.py:130\n#: apps/xpack/views/system_chat_user.py:318\n#: apps/xpack/views/system_chat_user.py:319\n#: apps/xpack/views/system_chat_user.py:320\nmsgid \"Get user list by group\"\nmsgstr \"获取用户组列表\"\n\n#: apps/workspace/views/workspace_chat_user.py:117\n#: apps/workspace/views/workspace_chat_user.py:118\n#: apps/workspace/views/workspace_chat_user.py:119\n#: apps/workspace/views/workspace_chat_user.py:124\n#: apps/xpack/views/system_chat_user.py:143\n#: apps/xpack/views/system_chat_user.py:144\n#: apps/xpack/views/system_chat_user.py:145\nmsgid \"Batch delete chat user\"\nmsgstr \"批量删除对话用户\"\n\n#: apps/workspace/views/workspace_chat_user.py:133\n#: apps/workspace/views/workspace_chat_user.py:134\n#: apps/workspace/views/workspace_chat_user.py:135\n#: apps/workspace/views/workspace_chat_user.py:140\n#: apps/xpack/views/system_chat_user.py:160\n#: apps/xpack/views/system_chat_user.py:161\n#: apps/xpack/views/system_chat_user.py:162\nmsgid \"Batch add chat user to group\"\nmsgstr \"批量添加对话用户到用户组\"\n\n#: apps/workspace/views/workspace_chat_user.py:149\n#: apps/workspace/views/workspace_chat_user.py:150\n#: apps/workspace/views/workspace_chat_user.py:151\n#: apps/xpack/views/system_chat_user.py:177\n#: apps/xpack/views/system_chat_user.py:178\n#: apps/xpack/views/system_chat_user.py:179\nmsgid \"Change chat user password\"\nmsgstr \"修改对话用户密码\"\n\n#: apps/workspace/views/workspace_chat_user.py:186\n#: apps/workspace/views/workspace_chat_user.py:187\n#: apps/workspace/views/workspace_chat_user.py:188\n#: apps/xpack/views/system_chat_user.py:230\n#: apps/xpack/views/system_chat_user.py:231\n#: apps/xpack/views/system_chat_user.py:232\nmsgid \"Create or update Chat User Group\"\nmsgstr \"创建或更新对话用户组\"\n\n#: apps/workspace/views/workspace_chat_user.py:191\n#: apps/workspace/views/workspace_chat_user.py:202\n#: apps/workspace/views/workspace_chat_user.py:216\n#: apps/workspace/views/workspace_chat_user.py:232\n#: apps/workspace/views/workspace_chat_user.py:249\n#: apps/workspace/views/workspace_chat_user.py:264\nmsgid \"Workspace/User Group\"\nmsgstr \"工作空间/用户组\"\n\n#: apps/workspace/views/workspace_chat_user.py:198\n#: apps/workspace/views/workspace_chat_user.py:199\n#: apps/workspace/views/workspace_chat_user.py:200\n#: apps/xpack/views/system_chat_user.py:243\n#: apps/xpack/views/system_chat_user.py:244\n#: apps/xpack/views/system_chat_user.py:245\nmsgid \"Get user group list\"\nmsgstr \"获取用户组列表\"\n\n#: apps/workspace/views/workspace_chat_user.py:211\n#: apps/workspace/views/workspace_chat_user.py:212\n#: apps/workspace/views/workspace_chat_user.py:213\n#: apps/xpack/views/system_chat_user.py:256\n#: apps/xpack/views/system_chat_user.py:257\n#: apps/xpack/views/system_chat_user.py:258\nmsgid \"Delete chat user group\"\nmsgstr \"删除对话用户组\"\n\n#: apps/workspace/views/workspace_chat_user.py:226\n#: apps/workspace/views/workspace_chat_user.py:227\n#: apps/workspace/views/workspace_chat_user.py:228\n#: apps/xpack/views/system_chat_user.py:273\n#: apps/xpack/views/system_chat_user.py:274\n#: apps/xpack/views/system_chat_user.py:275\nmsgid \"Add member to chat user group\"\nmsgstr \"添加成员到对话用户组\"\n\n#: apps/workspace/views/workspace_chat_user.py:243\n#: apps/workspace/views/workspace_chat_user.py:244\n#: apps/workspace/views/workspace_chat_user.py:245\n#: apps/xpack/views/system_chat_user.py:295\n#: apps/xpack/views/system_chat_user.py:296\n#: apps/xpack/views/system_chat_user.py:297\nmsgid \"Remove member from chat user group\"\nmsgstr \"从对话用户组移除成员\"\n\n#: apps/xpack/api/auth_config.py:29\nmsgid \"Auth Type\"\nmsgstr \"认证类型\"\n\n#: apps/xpack/api/auth_config.py:30\nmsgid \"Config\"\nmsgstr \"配置\"\n\n#: apps/xpack/api/auth_config.py:77\nmsgid \"Corp ID\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:78\nmsgid \"Agent ID\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:79\nmsgid \"App Secret\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:80\nmsgid \"Callback URL\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:84\nmsgid \"Key\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:106\nmsgid \"Access Token\"\nmsgstr \"\"\n\n#: apps/xpack/api/chat_user.py:31 apps/xpack/api/chat_user.py:83\n#: apps/xpack/api/chat_user.py:113 apps/xpack/serializers/chat_user.py:101\n#: apps/xpack/serializers/chat_user.py:177\n#: apps/xpack/serializers/chat_user.py:252\nmsgid \"User Group IDs\"\nmsgstr \"用户组 IDs\"\n\n#: apps/xpack/api/chat_user.py:118\nmsgid \"User Group Names\"\nmsgstr \"用户组名称\"\n\n#: apps/xpack/api/chat_user.py:132 apps/xpack/serializers/chat_user.py:120\n#: apps/xpack/serializers/resource_chat_user.py:37\nmsgid \"Username or Nickname\"\nmsgstr \"用户名或姓名\"\n\n#: apps/xpack/api/chat_user.py:148 apps/xpack/serializers/chat_user.py:360\nmsgid \"Sync Type\"\nmsgstr \"同步类型\"\n\n#: apps/xpack/api/knowledge_lark.py:25\nmsgid \"Token\"\nmsgstr \"令牌\"\n\n#: apps/xpack/api/knowledge_lark.py:27\nmsgid \"Is Exist\"\nmsgstr \"是否存在\"\n\n#: apps/xpack/api/license.py:13\nmsgid \"corporation\"\nmsgstr \"公司\"\n\n#: apps/xpack/api/license.py:14\nmsgid \"isv\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:15\nmsgid \"expired\"\nmsgstr \"过期时间\"\n\n#: apps/xpack/api/license.py:16\nmsgid \"product\"\nmsgstr \"产品\"\n\n#: apps/xpack/api/license.py:17\nmsgid \"edition\"\nmsgstr \"版本\"\n\n#: apps/xpack/api/license.py:18\nmsgid \"license version\"\nmsgstr \"license 版本\"\n\n#: apps/xpack/api/license.py:19\nmsgid \"count\"\nmsgstr \"数量\"\n\n#: apps/xpack/api/license.py:20\nmsgid \"serial number\"\nmsgstr \"序列号\"\n\n#: apps/xpack/api/license.py:21\nmsgid \"remark\"\nmsgstr \"备注\"\n\n#: apps/xpack/api/license.py:26\nmsgid \"message\"\nmsgstr \"消息\"\n\n#: apps/xpack/api/license.py:27\nmsgid \"license details\"\nmsgstr \"license 详情\"\n\n#: apps/xpack/api/license.py:36\n#: apps/xpack/serializers/license/license_serializers.py:56\nmsgid \"license file\"\nmsgstr \"license 文件\"\n\n#: apps/xpack/api/license.py:37\nmsgid \"License file is required\"\nmsgstr \"license 文件是必需的\"\n\n#: apps/xpack/api/license.py:38\nmsgid \"Invalid license file format\"\nmsgstr \"无效的 license 文件格式\"\n\n#: apps/xpack/api/operate_log.py:12\n#: apps/xpack/serializers/operate_log_serializer.py:57\nmsgid \"menu\"\nmsgstr \"菜单\"\n\n#: apps/xpack/api/operate_log.py:13\n#: apps/xpack/serializers/operate_log_serializer.py:58\nmsgid \"operate\"\nmsgstr \"操作\"\n\n#: apps/xpack/api/operate_log.py:14\nmsgid \"menu_label\"\nmsgstr \"菜单标签\"\n\n#: apps/xpack/api/operate_log.py:15\nmsgid \"operate_label\"\nmsgstr \"操作标签\"\n\n#: apps/xpack/api/platform.py:35\nmsgid \"Platform type\"\nmsgstr \"平台类型\"\n\n#: apps/xpack/api/platform.py:50\nmsgid \"Platform configuration\"\nmsgstr \"平台配置\"\n\n#: apps/xpack/api/resource_chat_user_group.py:40\n#: apps/xpack/serializers/resource_chat_user.py:25\n#: apps/xpack/serializers/resource_chat_user_group.py:69\nmsgid \"is auth\"\nmsgstr \"是否认证\"\n\n#: apps/xpack/api/user_group.py:31 apps/xpack/api/user_group.py:99\nmsgid \"User Group ID\"\nmsgstr \"用户组 ID\"\n\n#: apps/xpack/api/user_group.py:54 apps/xpack/serializers/chat_user.py:406\n#: apps/xpack/serializers/chat_user.py:563\nmsgid \"Group ID\"\nmsgstr \"用户组 ID\"\n\n#: apps/xpack/api/user_group.py:114 apps/xpack/serializers/chat_user.py:541\nmsgid \"User group relation IDs\"\nmsgstr \"用户组关系 ID\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:19\nmsgid \"theme color\"\nmsgstr \"主题颜色\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:21\nmsgid \"header font color\"\nmsgstr \"标题字体颜色\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:25\nmsgid \"float location type\"\nmsgstr \"浮动位置类型\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:26\nmsgid \"float location value\"\nmsgstr \"浮动位置值\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:30\nmsgid \"float location x\"\nmsgstr \"浮动位置 X\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:31\nmsgid \"float location y\"\nmsgstr \"浮动位置 Y\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:35\nmsgid \"show source\"\nmsgstr \"显示来源\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:36\nmsgid \"show exec\"\nmsgstr \"显示执行\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:38\nmsgid \"show history\"\nmsgstr \"显示历史\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:39\nmsgid \"draggable\"\nmsgstr \"是否可拖动\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:40\nmsgid \"show guide\"\nmsgstr \"显示论坛\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:42\nmsgid \"icon url\"\nmsgstr \"图标\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:43\nmsgid \"chat background\"\nmsgstr \"聊天背景\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:44\nmsgid \"chat background url\"\nmsgstr \"聊天背景地址\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:45\nmsgid \"avatar\"\nmsgstr \"头像\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:46\nmsgid \"avatar url\"\nmsgstr \"头像地址\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:47\nmsgid \"user avatar\"\nmsgstr \"用户头像\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:48\nmsgid \"user avatar url\"\nmsgstr \"用户头像地址\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:49\nmsgid \"float icon\"\nmsgstr \"浮动图标\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:50\nmsgid \"float icon url\"\nmsgstr \"浮动图标地址\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:51\nmsgid \"disclaimer\"\nmsgstr \"免责申明\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:52\nmsgid \"disclaimer value\"\nmsgstr \"免责申明内容\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:55\nmsgid \"show avatar\"\nmsgstr \"显示头像\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:56\nmsgid \"show user avatar\"\nmsgstr \"显示用户头像\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:124\nmsgid \"Float location field type error\"\nmsgstr \"浮动位置字段类型错误\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:130\nmsgid \"Custom theme field type error\"\nmsgstr \"自定义主题字段类型错误\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:27\n#: apps/xpack/serializers/platform_serializer.py:31\nmsgid \"App Secret is required\"\nmsgstr \"App Secret 是必填项\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:28\n#: apps/xpack/serializers/platform_serializer.py:26\n#: apps/xpack/serializers/platform_serializer.py:34\n#: apps/xpack/serializers/platform_serializer.py:40\n#: apps/xpack/serializers/platform_serializer.py:46\nmsgid \"Callback URL is required\"\nmsgstr \"Callback URL 是必填项\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:32\n#: apps/xpack/serializers/auth_config_serializer.py:41\nmsgid \"Corp ID is required\"\nmsgstr \"Corp ID 是必填项\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:33\n#: apps/xpack/serializers/platform_serializer.py:22\nmsgid \"Agent ID is required\"\nmsgstr \"Agent ID 是必填项\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:37\n#: apps/xpack/serializers/auth_config_serializer.py:42\nmsgid \"App Key is required\"\nmsgstr \"App Key 是必填项\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:53\nmsgid \"LDAP server cannot be empty\"\nmsgstr \"LDAP server不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:54\nmsgid \"Base DN cannot be empty\"\nmsgstr \"Base DN不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:55\nmsgid \"Password cannot be empty\"\nmsgstr \"密码不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:56\nmsgid \"OU cannot be empty\"\nmsgstr \"OU不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:57\nmsgid \"LDAP filter cannot be empty\"\nmsgstr \"LDAP过滤器不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:58\nmsgid \"LDAP mapping cannot be empty\"\nmsgstr \"LDAP映射不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:62\nmsgid \"Authorization address cannot be empty\"\nmsgstr \"认证地址不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:63\nmsgid \"Token address cannot be empty\"\nmsgstr \"令牌地址不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:64\nmsgid \"User information address cannot be empty\"\nmsgstr \"用户信息地址不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:65\nmsgid \"Scope cannot be empty\"\nmsgstr \"范围不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:66\nmsgid \"Client ID cannot be empty\"\nmsgstr \"客户端 ID 不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:67\nmsgid \"Client secret cannot be empty\"\nmsgstr \"客户端密钥不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:68\nmsgid \"Redirect address cannot be empty\"\nmsgstr \"重定向地址不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:69\nmsgid \"Field mapping cannot be empty\"\nmsgstr \"字段映射不能为空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:262\nmsgid \"Configuration information is wrong and failed to save\"\nmsgstr \"配置信息错误，保存失败\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:288\nmsgid \"Connection failed\"\nmsgstr \"连接失败\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:306\nmsgid \"Platform does not exist\"\nmsgstr \"平台不存在\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:316\nmsgid \"Unsupported platform type\"\nmsgstr \"不支持的平台类型\"\n\n#: apps/xpack/serializers/channel/chat_manage.py:100\nmsgid \"Think: \"\nmsgstr \"思考内容: \"\n\n#: apps/xpack/serializers/channel/chat_manage.py:103\n#: apps/xpack/serializers/channel/chat_manage.py:105\nmsgid \"AI reply: \"\nmsgstr \"AI 回复: \"\n\n#: apps/xpack/serializers/channel/chat_manage.py:318\nmsgid \"Thinking, please wait a moment!\"\nmsgstr \"思考中，请稍等！\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:19\n#: apps/xpack/serializers/channel/wechat.py:91\n#: apps/xpack/serializers/channel/wechat.py:132\n#: apps/xpack/serializers/channel/wecom.py:78\n#: apps/xpack/serializers/channel/wecom.py:259\nmsgid \"The corresponding platform configuration was not found\"\nmsgstr \"未找到对应的平台配置\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:27\n#: apps/xpack/serializers/channel/lark.py:117\nmsgid \"Currently only text messages are supported\"\nmsgstr \"目前仅支持文本消息\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:91\n#: apps/xpack/serializers/channel/wechat.py:163\n#: apps/xpack/serializers/channel/wecom.py:189\nmsgid \"Image download failed, check network\"\nmsgstr \"图片下载失败，请检查网络\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:92\n#: apps/xpack/serializers/channel/wechat.py:161\n#: apps/xpack/serializers/channel/wecom.py:185\nmsgid \"Please analyze the content of the image.\"\nmsgstr \"请分析图片内容。\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:95\nmsgid \"DingTalk application: {user}\"\nmsgstr \"钉钉智能体: {user}\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:106\n#: apps/xpack/serializers/channel/ding_talk.py:151\nmsgid \"Content generated by AI\"\nmsgstr \"AI 生成的内容\"\n\n#: apps/xpack/serializers/channel/lark.py:92\nmsgid \"Lark application: \"\nmsgstr \"飞书智能体: \"\n\n#: apps/xpack/serializers/channel/slack.py:116\nmsgid \"The corresponding platform configuration for Slack was not found\"\nmsgstr \"Slack 的对应平台配置未找到\"\n\n#: apps/xpack/serializers/channel/slack.py:206\nmsgid \"Thinking...\"\nmsgstr \"思考中...\"\n\n#: apps/xpack/serializers/channel/slack.py:333\nmsgid \"Invalid json format.\"\nmsgstr \"json 格式无效。\"\n\n#: apps/xpack/serializers/channel/slack.py:339\nmsgid \"Invalid Slack request\"\nmsgstr \"Slack 请求无效\"\n\n#: apps/xpack/serializers/channel/slack.py:347\nmsgid \"Slack application: {user}\"\nmsgstr \"Slack 智能体: {user}\"\n\n#: apps/xpack/serializers/channel/slack.py:480\nmsgid \"Stop\"\nmsgstr \"停止\"\n\n#: apps/xpack/serializers/channel/tools.py:58\n#, python-brace-format\nmsgid \"\"\n\"Thinking about 【{question}】...If you want me to continue answering, please \"\n\"reply {trigger_message}\"\nmsgstr \"思考【{question}】...如果你想让我继续回答，请回复 {trigger_message}\"\n\n#: apps/xpack/serializers/channel/tools.py:158\nmsgid \"\"\n\"\\n\"\n\" ------------\\n\"\n\"[To be continued, reply \\\"Continue to answer the question]\"\nmsgstr \"\"\n\"\\n\"\n\"------------\\n\"\n\"[待续，回复 \\\"继续回答问题]\"\n\n#: apps/xpack/serializers/channel/tools.py:238\n#, python-brace-format\nmsgid \"\"\n\"To be continued, reply \\\"{trigger_message}\\\" to continue answering the \"\n\"question\"\nmsgstr \"待续，回复 \\\"{trigger_message}\\\" 继续回答问题\"\n\n#: apps/xpack/serializers/channel/wechat.py:143\n#, python-brace-format\nmsgid \"WeChat Official Account: {account}\"\nmsgstr \"微信公众账号: {account}\"\n\n#: apps/xpack/serializers/channel/wechat.py:150\n#: apps/xpack/serializers/channel/wecom.py:171\n#: apps/xpack/serializers/channel/wecom.py:175\nmsgid \"\"\n\"The app does not enable the speech-to-text function or the speech-to-text \"\n\"function fails.\"\nmsgstr \"智能体未开启语音转文字功能或语音转文字功能失败。\"\n\n#: apps/xpack/serializers/channel/wechat.py:189\nmsgid \"Message types not supported yet\"\nmsgstr \"消息类型暂不支持\"\n\n#: apps/xpack/serializers/channel/wechat.py:196\nmsgid \"Welcome to subscribe\"\nmsgstr \"欢迎订阅\"\n\n#: apps/xpack/serializers/channel/wecom.py:86\nmsgid \"Enterprise WeChat user: \"\nmsgstr \"企业微信用户: \"\n\n#: apps/xpack/serializers/channel/wecom.py:97\nmsgid \"Enterprise WeChat customer service: \"\nmsgstr \"企业微信客服: \"\n\n#: apps/xpack/serializers/channel/wecom.py:134\n#: apps/xpack/serializers/channel/wecom.py:150\nmsgid \"This type of message is not supported yet\"\nmsgstr \"此类型消息暂不支持\"\n\n#: apps/xpack/serializers/channel/wecom.py:254\nmsgid \"Signature missing\"\nmsgstr \"签名缺失\"\n\n#: apps/xpack/serializers/channel/wecom.py:266\n#: apps/xpack/serializers/channel/wecom.py:273\n#, python-brace-format\nmsgid \"An error occurred while processing the GET request {e}\"\nmsgstr \"get 请求处理时发生错误 {e}\"\n\n#: apps/xpack/serializers/chat_auth.py:51\nmsgid \"The password is incorrect\"\nmsgstr \"密码不正确\"\n\n#: apps/xpack/serializers/chat_user.py:42\nmsgid \"Some user groups do not exist\"\nmsgstr \"某些用户组不存在\"\n\n#: apps/xpack/serializers/chat_user.py:181\nmsgid \"Is Append\"\nmsgstr \"是否追加\"\n\n#: apps/xpack/serializers/chat_user.py:194\nmsgid \"User Group IDs cannot be empty\"\nmsgstr \"用户组 IDs 不能为空\"\n\n#: apps/xpack/serializers/chat_user.py:198\nmsgid \"Some users do not exist\"\nmsgstr \"某些用户不存在\"\n\n#: apps/xpack/serializers/chat_user.py:361\nmsgid \"Sync Type: LOCAL or LDAP\"\nmsgstr \"同步类型: LOCAL 或 LDAP\"\n\n#: apps/xpack/serializers/chat_user.py:403\nmsgid \"Unsupported sync type\"\nmsgstr \"不支持的同步类型\"\n\n#: apps/xpack/serializers/chat_user.py:412\n#: apps/xpack/serializers/chat_user.py:444\n#: apps/xpack/serializers/chat_user.py:483\n#: apps/xpack/serializers/chat_user.py:510\n#: apps/xpack/serializers/chat_user.py:548\n#: apps/xpack/serializers/chat_user.py:570\nmsgid \"User group does not exist\"\nmsgstr \"用户组不存在\"\n\n#: apps/xpack/serializers/chat_user.py:451\nmsgid \"User group name already exists\"\nmsgstr \"用户组名称已存在\"\n\n#: apps/xpack/serializers/chat_user.py:485\nmsgid \"Default user group cannot be deleted\"\nmsgstr \"默认用户组不能被删除\"\n\n#: apps/xpack/serializers/chat_user.py:550\nmsgid \"User group relation IDs cannot be empty\"\nmsgstr \"用户组关系 IDs 不能为空\"\n\n#: apps/xpack/serializers/chat_user_serializer.py:75\nmsgid \"Invalid access token\"\nmsgstr \"无效的访问令牌\"\n\n#: apps/xpack/serializers/chat_user_serializer.py:102\nmsgid \"The user does not have permission to access the application\"\nmsgstr \"用户没有访问智能体的权限\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:56\n#: apps/xpack/serializers/dataset_lark_serializer.py:299\nmsgid \"app id\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:57\n#: apps/xpack/serializers/dataset_lark_serializer.py:300\nmsgid \"app secret\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:58\n#: apps/xpack/serializers/dataset_lark_serializer.py:105\n#: apps/xpack/serializers/dataset_lark_serializer.py:301\nmsgid \"folder token\"\nmsgstr \"文件夹令牌\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:60\nmsgid \"embedding model\"\nmsgstr \"向量模型\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:71\n#: apps/xpack/serializers/dataset_lark_serializer.py:311\nmsgid \"Network error or folder token error!\"\nmsgstr \"网络错误或文件夹令牌错误！\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:113\n#: apps/xpack/serializers/dataset_lark_serializer.py:155\n#: apps/xpack/task/sync.py:308\nmsgid \"Knowledge base not found!\"\nmsgstr \"知识库未找到！\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:125\n#: apps/xpack/task/sync.py:240\nmsgid \"Failed to get lark document list!\"\nmsgstr \"获取飞书文档列表失败！\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:147\nmsgid \"Knowledge id\"\nmsgstr \"知识库 ID\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:169\nmsgid \"Synchronization is only supported for lark documents\"\nmsgstr \"仅支持飞书文档的同步\"\n\n#: apps/xpack/serializers/license/license_serializers.py:102\n#: apps/xpack/serializers/license/license_serializers.py:123\n#: apps/xpack/serializers/license/license_tools.py:111\nmsgid \"The license is invalid\"\nmsgstr \"许可证无效\"\n\n#: apps/xpack/serializers/license/license_tools.py:136\nmsgid \"License usage limit exceeded.\"\nmsgstr \"License 使用限制已超出。\"\n\n#: apps/xpack/serializers/license/license_tools.py:160\nmsgid \"The network is busy, try again later.\"\nmsgstr \"网络繁忙，请稍后再试。\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:59\nmsgid \"user\"\nmsgstr \"用户\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:61\nmsgid \"ip_address\"\nmsgstr \"IP 地址\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:62\nmsgid \"workspace_id\"\nmsgstr \"工作空间ID\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:134\nmsgid \"Fail\"\nmsgstr \"失败\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:171\nmsgid \"Menu\"\nmsgstr \"菜单\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:172\nmsgid \"Operate\"\nmsgstr \"操作\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:173\nmsgid \"Operate user\"\nmsgstr \"操作用户\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:175\nmsgid \"Ip Address\"\nmsgstr \"IP地址\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:176\nmsgid \"API Details\"\nmsgstr \"API详情\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:177\nmsgid \"Operate Time\"\nmsgstr \"操作时间\"\n\n#: apps/xpack/serializers/platform_serializer.py:12\nmsgid \"app_id is required\"\nmsgstr \"app_id 是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:13\nmsgid \"app_secret is required\"\nmsgstr \"app_secret 是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:14\nmsgid \"token is required\"\nmsgstr \"token 是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:15\nmsgid \"callback_url is required\"\nmsgstr \"callback_url 是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:21\n#: apps/xpack/serializers/platform_serializer.py:30\nmsgid \"App ID is required\"\nmsgstr \"App ID 是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:23\nmsgid \"Secret is required\"\nmsgstr \"Secret 是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:24\nmsgid \"Token is required\"\nmsgstr \"Token 是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:33\nmsgid \"Verification Token is required\"\nmsgstr \"验证令牌是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:38\nmsgid \"Client ID is required\"\nmsgstr \"Client ID 是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:39\nmsgid \"Client Secret is required\"\nmsgstr \"客户端密钥是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:44\nmsgid \"Signing Secret is required\"\nmsgstr \"签名密钥是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:45\nmsgid \"Bot User Token is required\"\nmsgstr \"机器人用户令牌是必填项\"\n\n#: apps/xpack/serializers/platform_serializer.py:66\nmsgid \"Check if the fields are correct\"\nmsgstr \"检查字段是否正确\"\n\n#: apps/xpack/serializers/platform_serializer.py:155\n#, python-brace-format\nmsgid \"The platform configuration corresponding to {type} was not found\"\nmsgstr \"未找到对应 {type} 的平台配置\"\n\n#: apps/xpack/serializers/resource_chat_user.py:35\n#: apps/xpack/serializers/resource_chat_user.py:111\n#: apps/xpack/serializers/resource_chat_user_group.py:18\n#: apps/xpack/serializers/resource_chat_user_group.py:86\nmsgid \"Resource id\"\nmsgstr \"资源ID\"\n\n#: apps/xpack/serializers/resource_chat_user.py:38\n#: apps/xpack/serializers/resource_chat_user.py:112\nmsgid \"User group id\"\nmsgstr \"用户组ID\"\n\n#: apps/xpack/serializers/resource_chat_user.py:94\nmsgid \"Is auth\"\nmsgstr \"是否授权\"\n\n#: apps/xpack/serializers/resource_chat_user_group.py:20\nmsgid \"User group name\"\nmsgstr \"用户名\"\n\n#: apps/xpack/serializers/resource_chat_user_group.py:68\nmsgid \"user_group_id\"\nmsgstr \"用户组ID\"\n\n#: apps/xpack/serializers/sso_auth/cas.py:32\nmsgid \"HttpClient query failed: \"\nmsgstr \"HttpClient 查询失败: \"\n\n#: apps/xpack/serializers/sso_auth/cas.py:58\nmsgid \"CAS authentication failed\"\nmsgstr \"CAS 认证失败\"\n\n#: apps/xpack/serializers/sso_auth/oauth2.py:165\n#: apps/xpack/serializers/sso_auth/oauth2.py:184\n#: apps/xpack/serializers/sso_auth/oauth2.py:187\nmsgid \"Failed to obtain user information\"\nmsgstr \"获取用户信息失败\"\n\n#: apps/xpack/serializers/system_api_key.py:12\nmsgid \"Allow cross domain\"\nmsgstr \"允许跨域\"\n\n#: apps/xpack/serializers/system_api_key.py:13\nmsgid \"Cross domain list\"\nmsgstr \"跨域列表\"\n\n#: apps/xpack/serializers/system_api_key.py:44\nmsgid \"system API key id\"\nmsgstr \"系统 API 密钥 ID\"\n\n#: apps/xpack/serializers/system_params.py:20\nmsgid \"theme\"\nmsgstr \"主题\"\n\n#: apps/xpack/serializers/system_params.py:22\nmsgid \"login logo\"\nmsgstr \"登录 logo\"\n\n#: apps/xpack/serializers/system_params.py:23\nmsgid \"login image\"\nmsgstr \"登录图片\"\n\n#: apps/xpack/serializers/system_params.py:24\nmsgid \"title\"\nmsgstr \"标题\"\n\n#: apps/xpack/serializers/system_params.py:25\nmsgid \"slogan\"\nmsgstr \"标语\"\n\n#: apps/xpack/serializers/system_params.py:26\n#: apps/xpack/serializers/system_params.py:27\nmsgid \"show user manual\"\nmsgstr \"显示用户手册\"\n\n#: apps/xpack/serializers/system_params.py:28\nmsgid \"user manual url\"\nmsgstr \"用户手册网址\"\n\n#: apps/xpack/serializers/system_params.py:29\nmsgid \"show forum\"\nmsgstr \"显示论坛\"\n\n#: apps/xpack/serializers/system_params.py:30\nmsgid \"forum url\"\nmsgstr \"论坛网址\"\n\n#: apps/xpack/serializers/system_params.py:31\nmsgid \"show project\"\nmsgstr \"显示项目\"\n\n#: apps/xpack/serializers/system_params.py:32\nmsgid \"project url\"\nmsgstr \"项目网址\"\n\n#: apps/xpack/views/application_setting.py:24\n#: apps/xpack/views/application_setting.py:25\n#: apps/xpack/views/application_setting.py:26\nmsgid \"Modify Application Settings\"\nmsgstr \"修改智能体设置\"\n\n#: apps/xpack/views/application_setting.py:42\n#: apps/xpack/views/application_setting.py:43\n#: apps/xpack/views/application_setting.py:44\nmsgid \"Get Application Settings\"\nmsgstr \"获取智能体设置\"\n\n#: apps/xpack/views/auth.py:52 apps/xpack/views/auth.py:53\n#: apps/xpack/views/auth.py:54 apps/xpack/views/chat_user_auth.py:46\n#: apps/xpack/views/chat_user_auth.py:47 apps/xpack/views/chat_user_auth.py:48\nmsgid \"Get authentication types\"\nmsgstr \"获取认证类型\"\n\n#: apps/xpack/views/auth.py:55 apps/xpack/views/auth.py:70\n#: apps/xpack/views/auth.py:90 apps/xpack/views/auth.py:108\n#: apps/xpack/views/auth.py:223 apps/xpack/views/auth.py:235\n#: apps/xpack/views/auth.py:249\nmsgid \"Authentication Configuration\"\nmsgstr \"认证配置\"\n\n#: apps/xpack/views/auth.py:67 apps/xpack/views/auth.py:68\n#: apps/xpack/views/auth.py:69 apps/xpack/views/chat_user_auth.py:62\n#: apps/xpack/views/chat_user_auth.py:63 apps/xpack/views/chat_user_auth.py:64\nmsgid \"Test LDAP connection\"\nmsgstr \"测试 LDAP 连接\"\n\n#: apps/xpack/views/auth.py:87 apps/xpack/views/auth.py:88\n#: apps/xpack/views/auth.py:89\nmsgid \"Add or modify authentication configuration\"\nmsgstr \"添加或修改认证配置\"\n\n#: apps/xpack/views/auth.py:105 apps/xpack/views/auth.py:106\n#: apps/xpack/views/auth.py:107\nmsgid \"Get authentication configuration\"\nmsgstr \"获取认证配置\"\n\n#: apps/xpack/views/auth.py:118 apps/xpack/views/auth.py:119\n#: apps/xpack/views/auth.py:120 apps/xpack/views/chat_user_auth.py:112\n#: apps/xpack/views/chat_user_auth.py:113\n#: apps/xpack/views/chat_user_auth.py:114\nmsgid \"Ldap Log in\"\nmsgstr \"LDAP 登录\"\n\n#: apps/xpack/views/auth.py:121 apps/xpack/views/auth.py:138\n#: apps/xpack/views/auth.py:157 apps/xpack/views/auth.py:176\n#: apps/xpack/views/auth.py:194 apps/xpack/views/auth.py:208\n#: apps/xpack/views/auth.py:266 apps/xpack/views/auth.py:286\n#: apps/xpack/views/auth.py:307 apps/xpack/views/auth.py:327\n#: apps/xpack/views/auth.py:348 apps/xpack/views/auth.py:368\nmsgid \"Three-party login\"\nmsgstr \"三方登录\"\n\n#: apps/xpack/views/auth.py:135 apps/xpack/views/auth.py:136\n#: apps/xpack/views/auth.py:137 apps/xpack/views/chat_user_auth.py:129\n#: apps/xpack/views/chat_user_auth.py:130\n#: apps/xpack/views/chat_user_auth.py:131\nmsgid \"CAS Log in\"\nmsgstr \"CAS 登录\"\n\n#: apps/xpack/views/auth.py:154 apps/xpack/views/auth.py:155\n#: apps/xpack/views/auth.py:156 apps/xpack/views/chat_user_auth.py:148\n#: apps/xpack/views/chat_user_auth.py:149\n#: apps/xpack/views/chat_user_auth.py:150\nmsgid \"OIDC Log in\"\nmsgstr \"OIDC 登录\"\n\n#: apps/xpack/views/auth.py:173 apps/xpack/views/auth.py:174\n#: apps/xpack/views/auth.py:175 apps/xpack/views/chat_user_auth.py:167\n#: apps/xpack/views/chat_user_auth.py:168\n#: apps/xpack/views/chat_user_auth.py:169\nmsgid \"OAuth2 Log in\"\nmsgstr \"OAuth2 登录\"\n\n#: apps/xpack/views/auth.py:191 apps/xpack/views/auth.py:192\n#: apps/xpack/views/auth.py:193 apps/xpack/views/chat_user_auth.py:220\n#: apps/xpack/views/chat_user_auth.py:221\n#: apps/xpack/views/chat_user_auth.py:222\nmsgid \"Scan code login type\"\nmsgstr \"扫码登录类型\"\n\n#: apps/xpack/views/auth.py:205 apps/xpack/views/auth.py:206\n#: apps/xpack/views/auth.py:207 apps/xpack/views/auth.py:220\n#: apps/xpack/views/auth.py:221 apps/xpack/views/auth.py:222\n#: apps/xpack/views/chat_user_auth.py:234\n#: apps/xpack/views/chat_user_auth.py:235\n#: apps/xpack/views/chat_user_auth.py:236\n#: apps/xpack/views/chat_user_auth.py:249\n#: apps/xpack/views/chat_user_auth.py:250\n#: apps/xpack/views/chat_user_auth.py:251\nmsgid \"Get platform information\"\nmsgstr \"获取平台信息\"\n\n#: apps/xpack/views/auth.py:232 apps/xpack/views/auth.py:233\n#: apps/xpack/views/auth.py:234 apps/xpack/views/chat_user_auth.py:261\n#: apps/xpack/views/chat_user_auth.py:262\n#: apps/xpack/views/chat_user_auth.py:263\nmsgid \"Modify platform information\"\nmsgstr \"修改平台信息\"\n\n#: apps/xpack/views/auth.py:246 apps/xpack/views/auth.py:247\n#: apps/xpack/views/auth.py:248 apps/xpack/views/chat_user_auth.py:275\n#: apps/xpack/views/chat_user_auth.py:276\n#: apps/xpack/views/chat_user_auth.py:277\nmsgid \"Test platform connection\"\nmsgstr \"测试平台连接\"\n\n#: apps/xpack/views/auth.py:263 apps/xpack/views/auth.py:264\n#: apps/xpack/views/auth.py:265 apps/xpack/views/chat_user_auth.py:292\n#: apps/xpack/views/chat_user_auth.py:293\n#: apps/xpack/views/chat_user_auth.py:294\nmsgid \"DingTalk callback\"\nmsgstr \"钉钉回调\"\n\n#: apps/xpack/views/auth.py:283 apps/xpack/views/auth.py:284\n#: apps/xpack/views/auth.py:285 apps/xpack/views/chat_user_auth.py:312\n#: apps/xpack/views/chat_user_auth.py:313\n#: apps/xpack/views/chat_user_auth.py:314\nmsgid \"DingTalk OAuth2 callback\"\nmsgstr \"钉钉 OAuth2 回调\"\n\n#: apps/xpack/views/auth.py:304 apps/xpack/views/auth.py:305\n#: apps/xpack/views/auth.py:306 apps/xpack/views/chat_user_auth.py:333\n#: apps/xpack/views/chat_user_auth.py:334\n#: apps/xpack/views/chat_user_auth.py:335\nmsgid \"WeCom callback\"\nmsgstr \"企业微信回调\"\n\n#: apps/xpack/views/auth.py:324 apps/xpack/views/auth.py:325\n#: apps/xpack/views/auth.py:326 apps/xpack/views/chat_user_auth.py:353\n#: apps/xpack/views/chat_user_auth.py:354\n#: apps/xpack/views/chat_user_auth.py:355\nmsgid \"WeCom OAuth2 callback\"\nmsgstr \"企业微信 OAuth2 回调\"\n\n#: apps/xpack/views/auth.py:345 apps/xpack/views/auth.py:346\n#: apps/xpack/views/auth.py:347 apps/xpack/views/chat_user_auth.py:374\n#: apps/xpack/views/chat_user_auth.py:375\n#: apps/xpack/views/chat_user_auth.py:376\nmsgid \"Lark callback\"\nmsgstr \"飞书回调\"\n\n#: apps/xpack/views/auth.py:365 apps/xpack/views/auth.py:366\n#: apps/xpack/views/auth.py:367 apps/xpack/views/chat_user_auth.py:394\n#: apps/xpack/views/chat_user_auth.py:395\n#: apps/xpack/views/chat_user_auth.py:396\nmsgid \"Lark OAuth2 callback\"\nmsgstr \"飞书 OAuth2 回调\"\n\n#: apps/xpack/views/chat_user_auth.py:49 apps/xpack/views/chat_user_auth.py:65\n#: apps/xpack/views/chat_user_auth.py:85 apps/xpack/views/chat_user_auth.py:252\n#: apps/xpack/views/chat_user_auth.py:264\n#: apps/xpack/views/chat_user_auth.py:278\nmsgid \"Chat User/Authentication Configuration\"\nmsgstr \"对话用户/认证配置\"\n\n#: apps/xpack/views/chat_user_auth.py:82 apps/xpack/views/chat_user_auth.py:83\n#: apps/xpack/views/chat_user_auth.py:84\nmsgid \"Add or modify Chat/Authentication Configuration\"\nmsgstr \"添加或修改对话/认证配置\"\n\n#: apps/xpack/views/chat_user_auth.py:99 apps/xpack/views/chat_user_auth.py:100\n#: apps/xpack/views/chat_user_auth.py:101\nmsgid \"Get Authentication Configuration\"\nmsgstr \"获取认证配置\"\n\n#: apps/xpack/views/chat_user_auth.py:102\nmsgid \"Chat User/login authentication\"\nmsgstr \"对话用户/登录认证\"\n\n#: apps/xpack/views/chat_user_auth.py:115\n#: apps/xpack/views/chat_user_auth.py:132\n#: apps/xpack/views/chat_user_auth.py:151\n#: apps/xpack/views/chat_user_auth.py:170\n#: apps/xpack/views/chat_user_auth.py:223\n#: apps/xpack/views/chat_user_auth.py:237\n#: apps/xpack/views/chat_user_auth.py:295\n#: apps/xpack/views/chat_user_auth.py:315\n#: apps/xpack/views/chat_user_auth.py:336\n#: apps/xpack/views/chat_user_auth.py:356\n#: apps/xpack/views/chat_user_auth.py:377\n#: apps/xpack/views/chat_user_auth.py:397\nmsgid \"Chat User/Three-party login\"\nmsgstr \"对话用户/三方登录\"\n\n#: apps/xpack/views/chat_user_auth.py:187\nmsgid \"Chat User/login\"\nmsgstr \"对话用户/登录\"\n\n#: apps/xpack/views/chat_user_auth.py:414\n#: apps/xpack/views/chat_user_auth.py:415\n#: apps/xpack/views/chat_user_auth.py:416\nmsgid \"Application Password Certification\"\nmsgstr \"智能体密码认证\"\n\n#: apps/xpack/views/license.py:31 apps/xpack/views/license.py:32\n#: apps/xpack/views/license.py:33\nmsgid \"Get license information\"\nmsgstr \"获取许可证信息\"\n\n#: apps/xpack/views/license.py:42 apps/xpack/views/license.py:44\nmsgid \"Update license information\"\nmsgstr \"更新许可证信息\"\n\n#: apps/xpack/views/license.py:43\nmsgid \"Update license information by uploading a new license file\"\nmsgstr \"通过上传新许可证文件更新许可证信息\"\n\n#: apps/xpack/views/operate_log.py:21 apps/xpack/views/operate_log.py:22\n#: apps/xpack/views/operate_log.py:23\nmsgid \"Get menu operate log\"\nmsgstr \"获取菜单操作日志\"\n\n#: apps/xpack/views/operate_log.py:25 apps/xpack/views/operate_log.py:41\n#: apps/xpack/views/operate_log.py:57\nmsgid \"System operate log\"\nmsgstr \"系统操作日志\"\n\n#: apps/xpack/views/operate_log.py:36 apps/xpack/views/operate_log.py:37\n#: apps/xpack/views/operate_log.py:38\nmsgid \"Get paginated operate log\"\nmsgstr \"获取分页操作日志\"\n\n#: apps/xpack/views/operate_log.py:54 apps/xpack/views/operate_log.py:55\n#: apps/xpack/views/operate_log.py:56\nmsgid \"Export operate log\"\nmsgstr \"导出操作日志\"\n\n#: apps/xpack/views/platform.py:60 apps/xpack/views/platform.py:61\n#: apps/xpack/views/platform.py:62\nmsgid \"Get platform configuration\"\nmsgstr \"获取平台配置\"\n\n#: apps/xpack/views/platform.py:65 apps/xpack/views/platform.py:79\nmsgid \"Application/application access\"\nmsgstr \"智能体/智能体访问\"\n\n#: apps/xpack/views/platform.py:73 apps/xpack/views/platform.py:74\n#: apps/xpack/views/platform.py:75\nmsgid \"Update platform configuration\"\nmsgstr \"更新平台配置\"\n\n#: apps/xpack/views/platform.py:94 apps/xpack/views/platform.py:95\n#: apps/xpack/views/platform.py:96\nmsgid \"Get platform status\"\nmsgstr \"获取平台状态\"\n\n#: apps/xpack/views/platform.py:98 apps/xpack/views/platform.py:118\nmsgid \"Application/Get platform status\"\nmsgstr \"智能体/获取平台状态\"\n\n#: apps/xpack/views/platform.py:113 apps/xpack/views/platform.py:114\n#: apps/xpack/views/platform.py:115\nmsgid \"Update platform status\"\nmsgstr \"更新平台状态\"\n\n#: apps/xpack/views/resource_chat_user.py:27\n#: apps/xpack/views/resource_chat_user.py:28\n#: apps/xpack/views/resource_chat_user.py:29\nmsgid \"Get Resource chat user List\"\nmsgstr \"获取资源对话用户列表\"\n\n#: apps/xpack/views/resource_chat_user.py:32\n#: apps/xpack/views/resource_chat_user.py:54\n#: apps/xpack/views/resource_chat_user.py:77\n#: apps/xpack/views/system_chat_user_group.py:24\n#: apps/xpack/views/system_chat_user_group.py:45\n#: apps/xpack/views/system_chat_user_group.py:67\nmsgid \"Chat user\"\nmsgstr \"对话用户\"\n\n#: apps/xpack/views/resource_chat_user.py:48\n#: apps/xpack/views/resource_chat_user.py:49\n#: apps/xpack/views/resource_chat_user.py:50\nmsgid \"Edit Resource chat user List\"\nmsgstr \"编辑资源对话用户列表\"\n\n#: apps/xpack/views/resource_chat_user.py:72\n#: apps/xpack/views/resource_chat_user.py:73\n#: apps/xpack/views/resource_chat_user.py:74\nmsgid \"Get Resource chat user page List\"\nmsgstr \"获取资源对话用户分页列表\"\n\n#: apps/xpack/views/system_api_key.py:19 apps/xpack/views/system_api_key.py:20\n#: apps/xpack/views/system_api_key.py:21\nmsgid \"Create SystemAPIKey\"\nmsgstr \"创建系统 API 密钥\"\n\n#: apps/xpack/views/system_api_key.py:34 apps/xpack/views/system_api_key.py:35\n#: apps/xpack/views/system_api_key.py:36\nmsgid \"Get SystemAPIKey List\"\nmsgstr \"获取系统 API 密钥列表\"\n\n#: apps/xpack/views/system_api_key.py:50 apps/xpack/views/system_api_key.py:51\n#: apps/xpack/views/system_api_key.py:52\nmsgid \"Update SystemAPIKey\"\nmsgstr \"更新系统 API 密钥\"\n\n#: apps/xpack/views/system_api_key.py:66 apps/xpack/views/system_api_key.py:67\n#: apps/xpack/views/system_api_key.py:68\nmsgid \"Delete SystemAPIKey\"\nmsgstr \"删除系统 API 密钥\"\n\n#: apps/xpack/views/system_chat_user.py:60\n#: apps/xpack/views/system_chat_user.py:76\n#: apps/xpack/views/system_chat_user.py:89\n#: apps/xpack/views/system_chat_user.py:102\n#: apps/xpack/views/system_chat_user.py:113\n#: apps/xpack/views/system_chat_user.py:131\n#: apps/xpack/views/system_chat_user.py:146\n#: apps/xpack/views/system_chat_user.py:163\n#: apps/xpack/views/system_chat_user.py:180\n#: apps/xpack/views/system_chat_user.py:200\n#: apps/xpack/views/system_chat_user.py:217\nmsgid \"System/Chat user\"\nmsgstr \"系统/对话用户\"\n\n#: apps/xpack/views/system_chat_user.py:73\n#: apps/xpack/views/system_chat_user.py:74\n#: apps/xpack/views/system_chat_user.py:75\nmsgid \"Get chat user list\"\nmsgstr \"获取对话用户列表\"\n\n#: apps/xpack/views/system_chat_user.py:214\n#: apps/xpack/views/system_chat_user.py:216\nmsgid \"Sync chat users\"\nmsgstr \"同步对话用户\"\n\n#: apps/xpack/views/system_chat_user.py:215\nmsgid \"Sync chat users from external source\"\nmsgstr \"从外部源同步对话用户\"\n\n#: apps/xpack/views/system_chat_user.py:235\n#: apps/xpack/views/system_chat_user.py:247\n#: apps/xpack/views/system_chat_user.py:261\n#: apps/xpack/views/system_chat_user.py:279\n#: apps/xpack/views/system_chat_user.py:301\n#: apps/xpack/views/system_chat_user.py:321\nmsgid \"System/User Group\"\nmsgstr \"系统/用户组\"\n\n#: apps/xpack/views/system_chat_user_group.py:19\n#: apps/xpack/views/system_chat_user_group.py:20\n#: apps/xpack/views/system_chat_user_group.py:21\nmsgid \"Get Resource chat user group List\"\nmsgstr \"获取资源对话用户组列表\"\n\n#: apps/xpack/views/system_chat_user_group.py:39\n#: apps/xpack/views/system_chat_user_group.py:40\n#: apps/xpack/views/system_chat_user_group.py:41\nmsgid \"Edit Resource chat user group List\"\nmsgstr \"编辑资源对话用户组列表\"\n\n#: apps/xpack/views/system_chat_user_group.py:62\n#: apps/xpack/views/system_chat_user_group.py:64\nmsgid \"Get Resource chat user group page List\"\nmsgstr \"获取资源对话用户组分页列表\"\n\n#: apps/xpack/views/system_chat_user_group.py:63\nmsgid \"Get Resource chat user page group List\"\nmsgstr \"获取资源对话用户分页组列表\"\n\n#: apps/xpack/views/system_params.py:22 apps/xpack/views/system_params.py:23\n#: apps/xpack/views/system_params.py:24\nmsgid \"View appearance settings\"\nmsgstr \"查看外观设置\"\n\n#: apps/xpack/views/system_params.py:39 apps/xpack/views/system_params.py:40\n#: apps/xpack/views/system_params.py:41\nmsgid \"Update appearance settings\"\nmsgstr \"更新外观设置\"\n\nmsgid \"Application Access\"\nmsgstr \"智能体接入\"\n\nmsgid \"Display execution details\"\nmsgstr \"是否显示执行详情\"\n\nmsgid \"LOCAL\"\nmsgstr \"账号登录\"\n\nmsgid \"CAS\"\nmsgstr \"CAS\"\n\nmsgid \"LDAP\"\nmsgstr \"LDAP\"\n\nmsgid \"OIDC\"\nmsgstr \"OIDC\"\n\nmsgid \"OAuth2\"\nmsgstr \"OAuth2\"\n\nmsgid \"dingtalk\"\nmsgstr \"钉钉\"\n\nmsgid \"wecom\"\nmsgstr \"企业微信\"\n\nmsgid \"lark\"\nmsgstr \"飞书\"\n\nmsgid \"Get tool list\"\nmsgstr \"获取工具列表\"\n\nmsgid \"Setting\"\nmsgstr \"设置\"\n\nmsgid \"Get verification results\"\nmsgstr \"获取验证结果\"\n\nmsgid \"Validation\"\nmsgstr \"验证\"\n\nmsgid \"Models Resource\"\nmsgstr \"模型资源\"\n\nmsgid \"Tools Resource\"\nmsgstr \"工具资源\"\n\nmsgid \"Get resource model list\"\nmsgstr \"获取资源管理模型列表\"\n\nmsgid \"System Model\"\nmsgstr \"系统模型\"\n\nmsgid \"Dialogue users\"\nmsgstr \"对话用户\"\n\nmsgid \"Conversation log\"\nmsgstr \"对话日志\"\n\nmsgid \"Public access link\"\nmsgstr \"公共访问链接\"\n\nmsgid \"User management\"\nmsgstr \"用户管理\"\n\nmsgid \"Chat User/logout\"\nmsgstr \"对话用户/登出\"\n\nmsgid \"Paragraph\"\nmsgstr \"段落\"\n\nmsgid \"User group\"\nmsgstr \"用户组\"\n\nmsgid \"Remove member from user group\"\nmsgstr \"从用户组中移除成员\"\n\nmsgid \"Create a web site knowledge base\"\nmsgstr \"创建一个web知识库\"\n\nmsgid \"Modify knowledge base information\"\nmsgstr \"修改知识库信息\"\n\nmsgid \"Delete knowledge base\"\nmsgstr \"删除知识库\"\n\nmsgid \"model\"\nmsgstr \"模型\"\n\nmsgid \"Batch add user to group\"\nmsgstr \"批量添加用户到组\"\n\nmsgid \"Add internal tool\"\nmsgstr \"添加内置工具\"\n\nmsgid \"Batch generate related\"\nmsgstr \"批量生成相关\"\n\nmsgid \"Update personal system API_KEY\"\nmsgstr \"更新个人系统 API KEY\"\n\nmsgid \"Delete user group\"\nmsgstr \"删除用户组\"\n\nmsgid \"Add user\"\nmsgstr \"添加用户\"\n\nmsgid \"folder\"\nmsgstr \"文件夹\"\n\nmsgid \"Create or update user group\"\nmsgstr \"创建或更新用户组\"\n\nmsgid \"Edit folder\"\nmsgstr \"编辑文件夹\"\n\nmsgid \"Email settings\"\nmsgstr \"邮箱设置\"\n\nmsgid \"trial listening\"\nmsgstr \"试听\"\n\nmsgid \"Add member to user group\"\nmsgstr \"添加成员到用户组\"\n\nmsgid \"System\"\nmsgstr \"系统\"\n\nmsgid \"Shared Knowledge/Document\"\nmsgstr \"共享知识库/文档\"\n\nmsgid \"System Application\"\nmsgstr \"系统智能体\"\n\nmsgid \"Hit-Test\"\nmsgstr \"命中测试\"\n\nmsgid \"Export Application\"\nmsgstr \"导出智能体\"\n\nmsgid \"Add ApiKey\"\nmsgstr \"添加 API KEY\"\n\nmsgid \"Delete application API_KEY\"\nmsgstr \"删除智能体 API KEY\"\n\nmsgid \"knowledge Base\"\nmsgstr \"知识库\"\n\nmsgid \"API KEY\"\nmsgstr \"API KEY\"\n\nmsgid \"Download\"\nmsgstr \"下载\"\n\nmsgid \"User\"\nmsgstr \"用户\"\n\nmsgid \"Delete personal system API_KEY\"\nmsgstr \"删除个人系统API KEY\"\n\nmsgid \"Add personal system API_KEY\"\nmsgstr \"添加个人系统API KEY\"\n\nmsgid \"Generate related documents\"\nmsgstr \"生成相关文档\"\n\nmsgid \"Modify application access token\"\nmsgstr \"修改智能体程序访问令牌\"\n\nmsgid \"File not exist. Only manually uploaded documents are supported\"\nmsgstr \"文件不存在, 仅支持手动上传的文档\"\n\nmsgid \"Resource\"\nmsgstr \"资源管理\"\n\nmsgid \"LDAP configuration not found or not active\"\nmsgstr \"LDAP 配置未找到或未激活\"\n\nmsgid \"Lark configuration not found or not active\"\nmsgstr \"飞书配置未找到或未激活\"\n\nmsgid \"Failed to get Lark collaborators\"\nmsgstr \"获取飞书协作者失败\"\n\nmsgid \"Failed to get Lark user details\"\nmsgstr \"获取飞书用户详情失败\"\n\nmsgid \"WeCom configuration not found or not active\"\nmsgstr \"企业微信配置未找到或未激活\"\n\nmsgid \"Failed to get WeCom access token\"\nmsgstr \"获取企业微信访问令牌失败\"\n\nmsgid \"Failed to get WeCom agent info\"\nmsgstr \"获取企业微信代理信息失败\"\n\nmsgid \"Failed to get WeCom department users\"\nmsgstr \"获取企业微信部门用户失败\"\n\nmsgid \"Failed to get WeCom user info\"\nmsgstr \"获取企业微信用户信息失败\"\n\nmsgid \"Publish status\"\nmsgstr \"发布状态\"\n\nmsgid \"Unpublished\"\nmsgstr \"未发布\"\n\nmsgid \"Published\"\nmsgstr \"已发布\"\n\nmsgid \"users_permission\"\nmsgstr \"用户权限\"\n\nmsgid \"Get user authorization status of resource\"\nmsgstr \"获取资源对用户的授权状态\"\n\nmsgid \"Edit user authorization status of resource\"\nmsgstr \"修改资源对用户的授权状态\"\n\nmsgid \"Get user authorization status of resource by page\"\nmsgstr \"分页获取资源对用户的授权状态\"\n\nmsgid \"Obtain resource authorization list by page\"\nmsgstr \"分页获取资源授权列表\"\n\nmsgid \"Engine model type\"\nmsgstr \"引擎模型类型\"\n\nmsgid \"If not passed, the default value is 16k_zh (Chinese universal)\"\nmsgstr \"如果未传递，默认值为 16k_zh（中文通用）\"\n\nmsgid \"Chinese telephone universal\"\nmsgstr \"中文电话通用\"\n\nmsgid \"English telephone universal\"\nmsgstr \"英文电话通用\"\n\nmsgid \"Commonly used in Chinese\"\nmsgstr \"中文常用\"\n\nmsgid \"Chinese, English, and Guangdong\"\nmsgstr \"中文、英文和广东话\"\n\nmsgid \"Chinese medical\"\nmsgstr \"中文医疗\"\n\nmsgid \"English\"\nmsgstr \"英文\"\n\nmsgid \"Cantonese\"\nmsgstr \"粤语\"\n\nmsgid \"Japanese\"\nmsgstr \"日语\"\n\nmsgid \"Korean\"\nmsgstr \"韩语\"\n\nmsgid \"Vietnamese\"\nmsgstr \"越南语\"\n\nmsgid \"Malay language\"\nmsgstr \"马来语\"\n\nmsgid \"Indonesian language\"\nmsgstr \"印尼语\"\n\nmsgid \"Filipino language\"\nmsgstr \"菲律宾语\"\n\nmsgid \"Thai\"\nmsgstr \"泰语\"\n\nmsgid \"Portuguese\"\nmsgstr \"葡萄牙语\"\n\nmsgid \"Turkish\"\nmsgstr \"土耳其语\"\n\nmsgid \"Arabic\"\nmsgstr \"阿拉伯语\"\n\nmsgid \"Spanish\"\nmsgstr \"西班牙语\"\n\nmsgid \"Hindi\"\nmsgstr \"印地语\"\n\nmsgid \"French\"\nmsgstr \"法语\"\n\nmsgid \"German\"\nmsgstr \"德语\"\n\nmsgid \"Multiple dialects, supporting 23 dialects\"\nmsgstr \"多种方言，支持 23 种方言\"\n\nmsgid \"This interface is used to recognize short audio files within 60 seconds. Supports Mandarin Chinese, English, Cantonese, Japanese, Vietnamese, Malay, Indonesian, Filipino, Thai, Portuguese, Turkish, Arabic, Hindi, French, German, and 23 Chinese dialects.\"\nmsgstr \"本接口用于识别 60 秒之内的短音频文件。支持中文普通话、英语、粤语、日语、越南语、马来语、印度尼西亚语、菲律宾语、泰语、葡萄牙语、土耳其语、阿拉伯语、印地语、法语、德语及 23 种汉语方言。\"\n\nmsgid \"CueWord\"\nmsgstr \"提示词\"\n\nmsgid \"If not passed, the default value is What is this audio saying? Only answer the audio content\"\nmsgstr \"如果未传递，默认值为 这段音频在说什么，只回答音频的内容\"\n\nmsgid \"The Qwen Omni series model supports inputting multiple modalities of data, including video, audio, images, and text, and outputting audio and text.\"\nmsgstr \"Qwen-Omni 系列模型支持输入多种模态的数据，包括视频、音频、图片、文本，并输出音频与文本\"\n\nmsgid \"resource authorization\"\nmsgstr \"资源授权\"\n\nmsgid \"The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition.\"\nmsgstr \"基于Qwen-Audio的端到端语音识别大模型，支持3分钟以内的音频识别，目前主要支持中英文识别。\"\n\nmsgid \"If not passed, the default value is 'zh'\"\nmsgstr \"如果未传递，则默认值为'zh'\"\n\nmsgid \"System resources authorization\"\nmsgstr \"系统资源授权\"\n\nmsgid \"This folder contains resources that you dont have permission\"\nmsgstr \"此文件夹包含您没有权限的资源\"\n\n\nmsgid \"Text to Video\"\nmsgstr \"文生视频\"\n\nmsgid \"Image to Video\"\nmsgstr \"图生视频\"\n\nmsgid \"Authentication failed. Please verify that the parameters are correct\"\nmsgstr \"认证失败，请检查参数是否正确\"\n\nmsgid \"Chat context\"\nmsgstr \"聊天上下文\"\n\nmsgid \"Prompt template\"\nmsgstr \"提示词模板\"\n\nmsgid \"generate prompt\"\nmsgstr \"生成提示词\"\n\nmsgid \"Watermark\"\nmsgstr \"水印\"\n\nmsgid \"Whether to add watermark\"\nmsgstr \"是否添加水印\"\n\nmsgid \"Resolution\"\nmsgstr \"分辨率\"\n\nmsgid \"Ratio\"\nmsgstr \"比例\"\n\nmsgid \"Duration\"\nmsgstr \"时长\"\n\nmsgid \"Failed to generate video\"\nmsgstr \"生成视频失败\"\n\nmsgid \"password\"\nmsgstr \"密码登录\"\n\nmsgid \"Failed to obtain the image\"\nmsgstr \"获取图片失败\"\n\nmsgid \"Update auth setting\"\nmsgstr \"更新认证设置\"\n\nmsgid \"If not passed, the default value is streaming_asr_demo\"\nmsgstr \"如果未传入，则默认值为 streaming_asr_demo\"\n\nmsgid \"If not passed, the default value is 16000\"\nmsgstr \"如果未传入，则默认值为 16000\"\n\nmsgid \"Sample Rate\"\nmsgstr \"采样率\"\n\nmsgid \"Captcha is required\"\nmsgstr \"验证码是必填项\"\n\nmsgid \"Tag\"\nmsgstr \"标签管理\"\n\nmsgid \"Tag Setting\"\nmsgstr \"标签设置\"\n\nmsgid \"Download Original Document\"\nmsgstr \"下载原文档\"\n\nmsgid \"Replace Original Document\"\nmsgstr \"替换原文档\"\n\nmsgid \"Update License\"\nmsgstr \"更新许可证\"\n\nmsgid \"Tag Key\"\nmsgstr \"标签\"\n\nmsgid \"Tag Value\"\nmsgstr \"标签值\"\n\nmsgid \"Tag id does not exist\"\nmsgstr \"标签 ID 不存在\"\n\nmsgid \"Tag key already exists\"\nmsgstr \"标签已存在\"\n\nmsgid \"Tag value already exists\"\nmsgstr \"标签值已存在\"\n\nmsgid \"Non-existent id\"\nmsgstr \"不存在的ID\"\n\nmsgid \"No permission for the target folder\"\nmsgstr \"没有目标文件夹的权限\"\n\nmsgid \"Application token usage statistics\"\nmsgstr \"智能体令牌使用统计\"\n\nmsgid \"Application top question statistics\"\nmsgstr \"智能体提问次数统计\"\n\nmsgid \"SAML2 Metadata\"\nmsgstr \"SAML2 元数据\"\n\nmsgid \"SAML2 Log in\"\nmsgstr \"SAML2 登录\"\n\nmsgid \"SAML2 SSO\"\nmsgstr \"SAML2 单点登录\"\n\nmsgid \"Workflow\"\nmsgstr \"工作流\"\n\nmsgid \"Web source url\"\nmsgstr \"Web 根地址\"\n\nmsgid \"Web knowledge selector\"\nmsgstr \"选择器\"\n\nmsgid \"The default is body, you can enter .classname/#idname/tagname\"\nmsgstr \"默认为 body，可输入 .classname/#idname/tagname\"\n\nmsgid \"Please enter the Web root address\"\nmsgstr \"请输入 Web 根地址\"\n\nmsgid \"File size exceeds limit\"\nmsgstr \"文件大小超出限制\"\n\nmsgid \"File upload is not enabled\"\nmsgstr \"文件上传未启用\"\n\nmsgid \"Blocked unsafe redirect to internal host\"\nmsgstr \"阻止不安全的重定向到内部主机\"\n\nmsgid \"Audio file recognition - Tongyi Qwen\"\nmsgstr \"录音文件识别-通义千问\"\n\nmsgid \"Real-time speech recognition - Fun-ASR/Paraformer\"\nmsgstr \"实时语音识别-Fun-ASR/Paraformer\"\n\nmsgid \"Qwen-Omni\"\nmsgstr \"多模态\"\n\nmsgid \"Super-humanoid: Lingxiaoxuan Flow\"\nmsgstr \"聆小璇\"\n\nmsgid \"Super-humanoid: Lingyuyan Flow\"\nmsgstr \"聆玉言\"\n\nmsgid \"Super-humanoid: Lingfeiyi Flow\"\nmsgstr \"聆飞逸\"\n\nmsgid \"Super-humanoid: Lingxiaoyue Flow\"\nmsgstr \"聆小玥\"\n\nmsgid \"Super-humanoid: Sun Dasheng Flow\"\nmsgstr \"孙大圣\"\n\nmsgid \"Super-humanoid: Lingyuzhao Flow\"\nmsgstr \"聆玉昭\"\n\nmsgid \"Super-humanoid: Lingxiaotang Flow\"\nmsgstr \"聆小糖\"\n\nmsgid \"Super-humanoid: Lingxiaorong Flow\"\nmsgstr \"聆小蓉\"\n\nmsgid \"Super-humanoid: Xinyun Flow\"\nmsgstr \"心云\"\n\nmsgid \"Super-humanoid: Grant (EN)\"\nmsgstr \"Grant\"\n\nmsgid \"Super-humanoid: Lila (EN)\"\nmsgstr \"Lila\"\n\nmsgid \"Super-humanoid: Lingwanwan Pro\"\nmsgstr \"聆万万\"\n\nmsgid \"Super-humanoid: Yiyi Pro\"\nmsgstr \"依依\"\n\nmsgid \"Super-humanoid: Huifangnv Pro\"\nmsgstr \"惠芳女\"\n\nmsgid \"Super-humanoid: Lingxiaoying Pro\"\nmsgstr \"聆小颖\"\n\nmsgid \"Super-humanoid: Lingfeibo Pro\"\nmsgstr \"聆飞博\"\n\nmsgid \"Super-humanoid: Lingyuyan Pro\"\nmsgstr \"聆玉言\"\n\nmsgid \"Login failed %s times, account will be locked, you have %s more chances !\"\nmsgstr \"登录失败 %s 次，账号将被锁定，您还有 %s 次机会！\"\n\nmsgid \"This account has been locked for %s minutes, please try again later\"\nmsgstr \"该账号已被锁定 %s 分钟，请稍后再试\"\n\nmsgid \"User does not have permission to use API Key\"\nmsgstr \"用户没有使用 API Key 的权限\"\n\nmsgid \"Import knowledge workflow\"\nmsgstr \"导入知识工作流\"\n\nmsgid \"Export knowledge workflow\"\nmsgstr \"导出知识工作流\"\n\nmsgid \"Role IDs cannot be empty\"\nmsgstr \"角色 ID 不能为空\"\n\nmsgid \"Some roles do not exist\"\nmsgstr \"部分角色不存在\"\n\nmsgid \"Authorized pagination list for obtaining resources\"\nmsgstr \"获取资源的关系分页列表\"\n\nmsgid \"Resources mapping\"\nmsgstr \"资源映射\"\n\nmsgid \"Batch set user roles\"\nmsgstr \"批量设置用户角色\"\n\nmsgid \"Role Setting cannot be empty\"\nmsgstr \"角色设置不能为空\"\n\nmsgid \"View related resources\"\nmsgstr \"查看关联资源\"\n\nmsgid \"Feedback reason\"\nmsgstr \"反馈理由\"\n\nmsgid \"Other reason content\"\nmsgstr \"其他反馈理由内容\"\n\nmsgid \"accurate\"\nmsgstr \"内容准确\"\n\nmsgid \"complete\"\nmsgstr \"内容完善\"\n\nmsgid \"inaccurate\"\nmsgstr \"内容不准确\"\n\nmsgid \"incomplete\"\nmsgstr \"内容不完善\"\n\nmsgid \"Secret key is invalid\"\nmsgstr \"密钥无效\"\n\nmsgid \"Secret key is expired\"\nmsgstr \"密钥已过期\"\n\nmsgid \"Online Usage\"\nmsgstr \"线上使用\"\n\nmsgid \"API Call\"\nmsgstr \"API 调用\"\n\nmsgid \"Enterprise WeChat\"\nmsgstr \"企业微信应用\"\n\nmsgid \"WeChat Public Account\"\nmsgstr \"微信公众号\"\n\nmsgid \"Lark\"\nmsgstr \"飞书应用\"\n\nmsgid \"DingTalk\"\nmsgstr \"钉钉应用\"\n\nmsgid \"Enterprise WeChat Robot\"\nmsgstr \"企业微信机器人\"\n\nmsgid \"Trigger\"\nmsgstr \"触发器\"\n\nmsgid \"Slack\"\nmsgstr \"Slack 应用\"\n\nmsgid \"Root Directory\"\nmsgstr \"根目录\"\n\nmsgid \"Create trigger\"\nmsgstr \"创建触发器\"\n\nmsgid \"Get the trigger list\"\nmsgstr \"获取触发器列表\"\n\nmsgid \"Get trigger details\"\nmsgstr \"获取触发器详情\"\n\nmsgid \"Modify the trigger\"\nmsgstr \"修改触发器\"\n\nmsgid \"Delete the trigger\"\nmsgstr \"删除触发器\"\n\nmsgid \"Activate trigger in batches\"\nmsgstr \"批量启用/禁用触发器\"\n\nmsgid \"Get the trigger list by page\"\nmsgstr \"分页获取触发器列表\"\n\nmsgid \"Create trigger in source\"\nmsgstr \"资源端创建触发器\"\n\nmsgid \"Delete trigger in batches\"\nmsgstr \"批量删除触发器\"\n\nmsgid \"Get the trigger list of source\"\nmsgstr \"获取资源端触发器列表\"\n\nmsgid \"Get Task source trigger details\"\nmsgstr \"获取资源端触发器详情\"\n\nmsgid \"Delete the task source trigger\"\nmsgstr \"删除资源端触发器\"\n\nmsgid \"Get the task list of triggers\"\nmsgstr \"获取触发器任务列表\"\n\nmsgid \"Retrieve detailed records of tasks executed by the trigger.\"\nmsgstr \"获取由该触发器执行的任务详细记录。\"\n\nmsgid \"Get a paginated list of execution records for trigger tasks.\"\nmsgstr \"获取触发器任务执行记录的分页列表。\"\n\nmsgid \"%s must be an array\"\nmsgstr \"%s 必须是数组类型\"\n\nmsgid \"%s must not be empty\"\nmsgstr \"%s 不能为空\"\n\nmsgid \"%s values must be between %s and %s\"\nmsgstr \"%s 的值必须在 %s 到 %s 之间\"\n\nmsgid \"Invalid time format: %s, must be HH:MM (e.g., 09:00)\"\nmsgstr \"时间格式无效: %s，必须是 HH:MM 格式 (例如: 09:00)\"\n\nmsgid \"schedule_type must be one of %s\"\nmsgstr \"schedule_type 必须是以下值之一: %s\"\n\nmsgid \"interval_value must be an integer greater than or equal to 1\"\nmsgstr \"interval_value 必须是大于或等于 1 的整数\"\n\nmsgid \"interval_unit must be one of %s\"\nmsgstr \"interval_unit 必须是以下值之一: %s\"\n\nmsgid \"body must be an array\"\nmsgstr \"body 必须是数组类型\"\n\nmsgid \"Error trigger type\"\nmsgstr \"触发器类型错误\"\n\nmsgid \"The following id does not exist: %s\"\nmsgstr \"以下 id 不存在: %s\"\n\nmsgid \"%s must be a dict\"\nmsgstr \"%s 必须是字典类型\"\n\nmsgid \"input_field_list must be a dict\"\nmsgstr \"input_field_list 必须是字典类型\"\n\nmsgid \"%s type requires %s field\"\nmsgstr \"%s 类型需要 %s 字段\"\n\nmsgid \"trigger name\"\nmsgstr \"触发器名称\"\n\nmsgid \"trigger description\"\nmsgstr \"触发器描述\"\n\nmsgid \"trigger setting\"\nmsgstr \"触发器设置\"\n\nmsgid \"Trigger ID\"\nmsgstr \"触发器ID\"\n\nmsgid \"Trigger task can not be empty\"\nmsgstr \"触发器任务不能为空\"\n\nmsgid \"%s id does not exist\"\nmsgstr \"%s id 不存在\"\n\nmsgid \"Trigger id does not exist\"\nmsgstr \"触发器 id 不存在\"\n\nmsgid \"Trigger not found\"\nmsgstr \"未找到触发器\"\n\nmsgid \"Trigger must have at least one task\"\nmsgstr \"触发器必须至少有一个任务\"\n\nmsgid \"Trigger task number must be one\"\nmsgstr \"触发器任务数量必须为一个\"\n\nmsgid \"Incorrect trigger task\"\nmsgstr \"触发器任务不正确\"\n\nmsgid \"Trigger task ID\"\nmsgstr \"触发器任务ID\"\n\nmsgid \"Trigger task record ID\"\nmsgstr \"触发器任务记录ID\"\n\nmsgid \"Trigger task record id does not exist\"\nmsgstr \"触发器任务记录 id 不存在\"\n\nmsgid \"Order field\"\nmsgstr \"排序字段\"\n\nmsgid \"System Trigger\"\nmsgstr \"系统触发器\"\n\nmsgid \"Get the System trigger list of source\"\nmsgstr \"获取来源的系统触发器列表\"\n\nmsgid \"Get System Task source trigger details\"\nmsgstr \"获取系统任务来源触发器详情\"\n\nmsgid \"Modify the System task source trigger\"\nmsgstr \"修改系统任务来源触发器\"\n\nmsgid \"Modify the task source trigger\"\nmsgstr \"修改任务来源触发器\"\n\nmsgid \"Delete the System task source trigger\"\nmsgstr \"删除系统任务来源触发器\"\n\nmsgid \"Invalid source type\"\nmsgstr \"无效的来源类型\"\n\nmsgid \"Shared tool is not supported\"\nmsgstr \"不支持共享工具\"\n\nmsgid \"Read Trigger\"\nmsgstr \"查看触发器\"\n\nmsgid \"Create Trigger\"\nmsgstr \"创建触发器\"\n\nmsgid \"Edit Trigger\"\nmsgstr \"编辑触发器\"\n\nmsgid \"Delete Trigger\"\nmsgstr \"删除触发器\"\n\nmsgid \"Read execute record\"\nmsgstr \"查看执行记录\"\n\nmsgid \"ADMIN\"\nmsgstr \"系统管理员\"\n\nmsgid \"WORKSPACE_MANAGE\"\nmsgstr \"空间管理员\"\n\nmsgid \"USER\"\nmsgstr \"普通用户\"\n\nmsgid \"Generate share link\"\nmsgstr \"生成分享链接\"\n\nmsgid \"Chat record link\"\nmsgstr \"聊天记录链接\"\n\nmsgid \"Get chat record by share link\"\nmsgstr \"通过分享链接获取聊天记录\"\n\nmsgid \"Invalid chat record ids\"\nmsgstr \"无效的聊天记录ID\"\n\nmsgid \"Share link does not exist\"\nmsgstr \"分享链接不存在\"\n\nmsgid \"Chat has been deleted\"\nmsgstr \"聊天记录已被删除\"\n\nmsgid \"cron type requires cron_expression field\"\nmsgstr \"cron 类型需要 cron_expression 字段\"\n\nmsgid \"Invalid cron expression: %s\"\nmsgstr \"Cron 表达式不合法：%s\"\n\nmsgid \"Batch Remove Documents from Tag\"\nmsgstr \"批量删除标签下的文档\"\n\nmsgid \"Document does not belong to current knowledge\"\nmsgstr \"文档不属于当前知识库\"\n\nmsgid \"Move an application\"\nmsgstr \"移动应用程序\""
  },
  {
    "path": "apps/locales/zh_Hant/LC_MESSAGES/django.po",
    "content": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same license as the PACKAGE package.\n# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: PACKAGE VERSION\\n\"\n\"Report-Msgid-Bugs-To: \\n\"\n\"POT-Creation-Date: 2025-06-18 17:33+0800\\n\"\n\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n\"Language: \\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: apps/application/api/application_api.py:21\n#: apps/application/serializers/application.py:153\nmsgid \"Workflow Objects\"\nmsgstr \"工作流對象\"\n\n#: apps/application/api/application_api.py:52\n#: apps/application/api/application_chat.py:104\n#: apps/application/api/application_chat_record.py:74\n#: apps/role_setting/api/role_setting.py:171 apps/shared/api/shared_tool.py:79\n#: apps/shared/api/shared_tool.py:119 apps/workspace/api/workspace.py:110\n#: apps/xpack/api/user_group.py:61\nmsgid \"Current page\"\nmsgstr \"當前頁\"\n\n#: apps/application/api/application_api.py:59\n#: apps/application/api/application_chat.py:111\n#: apps/application/api/application_chat_record.py:81\n#: apps/role_setting/api/role_setting.py:178 apps/shared/api/shared_tool.py:86\n#: apps/shared/api/shared_tool.py:126 apps/workspace/api/workspace.py:117\n#: apps/xpack/api/user_group.py:68\nmsgid \"Page size\"\nmsgstr \"每頁大小\"\n\n#: apps/application/api/application_api.py:66\n#: apps/application/serializers/application.py:156\n#: apps/application/serializers/application.py:195\n#: apps/application/serializers/application.py:274\n#: apps/folders/serializers/folder.py:97 apps/folders/serializers/folder.py:139\n#: apps/knowledge/serializers/knowledge.py:52\n#: apps/knowledge/serializers/knowledge.py:59\n#: apps/tools/serializers/tool.py:453\n#: apps/xpack/serializers/dataset_lark_serializer.py:55\nmsgid \"folder id\"\nmsgstr \"文件夾 ID\"\n\n#: apps/application/api/application_api.py:73\n#: apps/application/serializers/application.py:149\n#: apps/application/serializers/application.py:275\n#: apps/application/serializers/application.py:282\n#: apps/application/serializers/application.py:368\n#| msgid \"Application\"\nmsgid \"Application Name\"\nmsgstr \"智能體名稱\"\n\n#: apps/application/api/application_api.py:80\n#: apps/application/serializers/application.py:152\n#: apps/application/serializers/application.py:276\n#: apps/application/serializers/application.py:283\n#: apps/application/serializers/application.py:284\n#: apps/application/serializers/application.py:370\n#| msgid \"Application/Version\"\nmsgid \"Application Description\"\nmsgstr \"智能體描述\"\n\n#: apps/application/api/application_api.py:87\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:78\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:30\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:47\n#: apps/application/serializers/application.py:99\n#: apps/application/serializers/application.py:277\n#: apps/application/serializers/application.py:295\n#: apps/application/serializers/application.py:413\n#: apps/application/serializers/application.py:540\n#: apps/role_setting/api/role_setting.py:144 apps/tools/serializers/tool.py:357\n#: apps/tools/serializers/tool.py:390 apps/users/api/user.py:52\n#: apps/users/api/user.py:110 apps/users/api/user.py:126\n#: apps/users/serializers/user.py:325 apps/workspace/api/workspace.py:82\n#: apps/workspace/serializers/workspace_serializers.py:270\n#: apps/workspace/serializers/workspace_serializers.py:306\n#: apps/xpack/api/chat_user.py:49 apps/xpack/api/chat_user.py:92\n#: apps/xpack/serializers/chat_user.py:301\nmsgid \"User ID\"\nmsgstr \"用戶 ID\"\n\n#: apps/application/api/application_chat.py:70\n#: apps/application/serializers/application_chat.py:56\nmsgid \"Minimum number of likes\"\nmsgstr \"最小點讚數\"\n\n#: apps/application/api/application_chat.py:76\n#: apps/application/serializers/application_chat.py:58\nmsgid \"Minimum number of clicks\"\nmsgstr \"最小點擊數\"\n\n#: apps/application/api/application_chat.py:82\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:18\n#: apps/application/serializers/application_chat.py:59\nmsgid \"Comparator\"\nmsgstr \"比較器\"\n\n#: apps/application/api/application_chat_record.py:46\n#: apps/application/api/application_chat_record.py:115\n#: apps/application/serializers/application_chat.py:47\n#: apps/application/serializers/application_chat_record.py:76\n#| msgid \"Chat\"\nmsgid \"Chat ID\"\nmsgstr \"對話 ID\"\n\n#: apps/application/api/application_chat_record.py:53\n#: apps/application/serializers/application_chat_record.py:77\nmsgid \"Is it in order\"\nmsgstr \"是否有序\"\n\n#: apps/application/api/application_chat_record.py:122\nmsgid \"Chat Record ID\"\nmsgstr \"對話記錄 ID\"\n\n#: apps/application/api/application_chat_record.py:129\n#: apps/shared/api/shared_knowledge.py:235\n#: apps/shared/api/shared_knowledge.py:256 apps/xpack/api/knowledge_lark.py:47\n#: apps/xpack/api/knowledge_lark.py:79 apps/xpack/api/knowledge_lark.py:111\n#| msgid \"Knowledge\"\nmsgid \"Knowledge ID\"\nmsgstr \"知識庫 ID\"\n\n#: apps/application/api/application_chat_record.py:136\n#: apps/shared/api/shared_knowledge.py:263 apps/xpack/api/knowledge_lark.py:86\n#| msgid \"Document\"\nmsgid \"Document ID\"\nmsgstr \"文檔 ID\"\n\n#: apps/application/api/application_chat_record.py:148\n#| msgid \"Paragraph list\"\nmsgid \"Paragraph ID\"\nmsgstr \"段落 ID\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:26\n#| msgid \"type error\"\nmsgid \"Model type error\"\nmsgstr \"模型類型錯誤\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:36\n#: apps/common/field/common.py:24 apps/common/field/common.py:37\n#| msgid \"type error\"\nmsgid \"Message type error\"\nmsgstr \"消息類型錯誤\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:55\n#| msgid \"Question list\"\nmsgid \"Conversation list\"\nmsgstr \"對話列表\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:56\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:29\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:18\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:12\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:13\n#: apps/application/flow/step_node/question_node/i_question_node.py:18\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:12\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:13\n#: apps/application/serializers/application.py:101\n#: apps/application/serializers/application.py:285\n#: apps/knowledge/serializers/common.py:71 apps/shared/api/shared_model.py:76\n#: apps/shared/api/shared_model.py:98\nmsgid \"Model id\"\nmsgstr \"模型ID\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:58\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:29\n#| msgid \"Paragraph list\"\nmsgid \"Paragraph List\"\nmsgstr \"段落列表\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:60\n#: apps/application/serializers/application_chat.py:182\n#: apps/application/serializers/application_chat_record.py:41\n#: apps/application/serializers/application_chat_record.py:140\n#: apps/application/serializers/application_chat_record.py:179\n#: apps/application/serializers/application_chat_record.py:247\n#: apps/application/serializers/application_chat_record.py:312\n#: apps/chat/serializers/chat.py:98 apps/chat/serializers/chat.py:114\n#| msgid \"User relation ID\"\nmsgid \"Conversation ID\"\nmsgstr \"對話 ID\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:62\n#: apps/application/flow/step_node/application_node/i_application_node.py:14\n#: apps/application/serializers/application_chat.py:182\n#: apps/chat/serializers/chat.py:40\n#| msgid \"Question list\"\nmsgid \"User Questions\"\nmsgstr \"用戶問題\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:65\nmsgid \"Post-processor\"\nmsgstr \"後置處理器\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:68\n#| msgid \"Create question\"\nmsgid \"Completion Question\"\nmsgstr \"完成問題\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:70\nmsgid \"Streaming Output\"\nmsgstr \"流式輸出\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:71\n#: apps/xpack/serializers/resource_chat_user.py:93\n#| msgid \"user id\"\nmsgid \"Chat user id\"\nmsgstr \"對話用戶 ID\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:73\n#| msgid \"Create user\"\nmsgid \"Chat user Type\"\nmsgstr \"對話用戶類型\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:76\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:47\nmsgid \"No reference segment settings\"\nmsgstr \"無參考段設置\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:81\nmsgid \"Model settings\"\nmsgstr \"模型設置\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:84\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:29\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:29\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:28\n#: apps/application/flow/step_node/question_node/i_question_node.py:29\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:20\nmsgid \"Model parameter settings\"\nmsgstr \"模型參數設置\"\n\n#: apps/application/chat_pipeline/step/chat_step/i_chat_step.py:91\nmsgid \"message type error\"\nmsgstr \"消息類型錯誤\"\n\n#: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:224\n#: apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py:270\nmsgid \"\"\n\"Sorry, the AI model is not configured. Please go to the application to set \"\n\"up the AI model first.\"\nmsgstr \"抱歉，AI 模型未配置，請先前往智能體設置 AI 模型。\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:26\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:24\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:24\n#: apps/application/serializers/application_chat_record.py:172\nmsgid \"question\"\nmsgstr \"問題\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:32\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:27\nmsgid \"History Questions\"\nmsgstr \"歷史問題\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:34\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:23\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:20\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:18\n#: apps/application/flow/step_node/question_node/i_question_node.py:24\nmsgid \"Number of multi-round conversations\"\nmsgstr \"多輪對話的數量\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:37\nmsgid \"Maximum length of the knowledge base paragraph\"\nmsgstr \"知識庫段落的最大長度\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:39\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:21\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:16\n#: apps/application/flow/step_node/question_node/i_question_node.py:21\n#: apps/application/serializers/application.py:79\n#: apps/application/serializers/application.py:124\n#: apps/knowledge/serializers/common.py:72\nmsgid \"Prompt word\"\nmsgstr \"提示詞\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:41\nmsgid \"System prompt words (role)\"\nmsgstr \"系統提示詞（角色）\"\n\n#: apps/application/chat_pipeline/step/generate_human_message_step/i_generate_human_message_step.py:44\nmsgid \"Completion problem\"\nmsgstr \"完成問題\"\n\n#: apps/application/chat_pipeline/step/reset_problem_step/i_reset_problem_step.py:32\n#: apps/application/serializers/application.py:215\nmsgid \"Question completion prompt\"\nmsgstr \"問題完成提示\"\n\n#: apps/application/chat_pipeline/step/reset_problem_step/impl/base_reset_problem_step.py:20\n#: apps/application/serializers/common.py:87\n#, python-brace-format\nmsgid \"\"\n\"() contains the user's question. Answer the guessed user's question based on \"\n\"the context ({question}) Requirement: Output a complete question and put it \"\n\"in the <data></data> tag\"\nmsgstr \" () 包含用戶的問題。根據上下文（{question}）要求：輸出一個完整的問題並提出在<data></data>標籤中\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:27\nmsgid \"System completes question text\"\nmsgstr \"系統完成問題文本\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:30\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:39\nmsgid \"Dataset id list\"\nmsgstr \"知識庫 ID 列表\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:33\nmsgid \"List of document ids to exclude\"\nmsgstr \"排除的文檔 ID 列表\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:36\nmsgid \"List of exclusion vector ids\"\nmsgstr \"排除的向量 ID 列表\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:39\n#: apps/application/flow/step_node/reranker_node/i_reranker_node.py:21\n#: apps/application/flow/step_node/reranker_node/i_reranker_node.py:24\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:24\n#: apps/application/serializers/application.py:84\n#: apps/application/serializers/application_chat.py:185\nmsgid \"Reference segment number\"\nmsgstr \"引用分段數\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:42\nmsgid \"Similarity\"\nmsgstr \"相似度\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:45\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:30\n#: apps/application/serializers/application.py:91\n#: apps/knowledge/serializers/knowledge.py:100\n#: apps/knowledge/serializers/knowledge.py:643\nmsgid \"The type only supports embedding|keywords|blend\"\nmsgstr \"類型僅支持嵌入|關鍵字|混合\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/i_search_dataset_step.py:46\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:31\n#: apps/application/serializers/application.py:92\nmsgid \"Retrieval Mode\"\nmsgstr \"檢索模式\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:31\n#: apps/application/serializers/application.py:113\n#: apps/application/serializers/application.py:657\n#: apps/application/serializers/application.py:664\n#: apps/application/serializers/application.py:671\n#: apps/knowledge/serializers/document.py:643\n#: apps/knowledge/serializers/knowledge.py:220\n#: apps/models_provider/serializers/model_serializer.py:116\n#: apps/models_provider/serializers/model_serializer.py:134\n#: apps/models_provider/serializers/model_serializer.py:370\n#: apps/models_provider/tools.py:111 apps/shared/serializers/shared_model.py:32\n#: apps/shared/serializers/shared_model.py:65\n#: apps/shared/serializers/shared_model.py:82\nmsgid \"Model does not exist\"\nmsgstr \"模型不存在\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:33\nmsgid \"No permission to use this model {model_name}\"\nmsgstr \"無權限使用此模型{model_name}\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:42\nmsgid \"\"\n\"The vector model of the associated knowledge base is inconsistent and the \"\n\"segmentation cannot be recalled.\"\nmsgstr \"關聯的知識庫的向量模型不一致，無法回調分段。\"\n\n#: apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py:44\nmsgid \"The knowledge base setting is wrong, please reset the knowledge base\"\nmsgstr \"知識庫設置錯誤，請重置知識庫\"\n\n#: apps/application/flow/common.py:206\n#, python-brace-format\nmsgid \"The branch {branch} of the {node} node needs to be connected\"\nmsgstr \"需要連接 {node} 節點的 {branch} 分支\"\n\n#: apps/application/flow/common.py:212\n#, python-brace-format\nmsgid \"{node} Nodes cannot be considered as end nodes\"\nmsgstr \"{node} 節點不能被視為結束節點\"\n\n#: apps/application/flow/common.py:226\nmsgid \"The starting node is required\"\nmsgstr \"開始節點是必需的\"\n\n#: apps/application/flow/common.py:228\nmsgid \"There can only be one starting node\"\nmsgstr \"只能有一個開始節點\"\n\n#: apps/application/flow/common.py:236\nmsgid \"The node {node} model does not exist\"\nmsgstr \"節點 {node} 模型不存在\"\n\n#: apps/application/flow/common.py:246\n#, python-brace-format\nmsgid \"Node {node} is unavailable\"\nmsgstr \"節點 {node} 不可用\"\n\n#: apps/application/flow/common.py:252\n#, python-brace-format\nmsgid \"The library ID of node {node} cannot be empty\"\nmsgstr \"工具庫 ID {node} 不能為空\"\n\n#: apps/application/flow/common.py:255\n#, python-brace-format\nmsgid \"The function library for node {node} is not available\"\nmsgstr \"工具庫 {node} 不可用\"\n\n#: apps/application/flow/common.py:261\nmsgid \"Basic information node is required\"\nmsgstr \"基本信息節點是必需的\"\n\n#: apps/application/flow/common.py:263\nmsgid \"There can only be one basic information node\"\nmsgstr \"只能有一個基本信息節點\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:20\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:15\n#: apps/application/flow/step_node/question_node/i_question_node.py:20\n#: apps/users/api/user.py:35 apps/users/api/user.py:102\nmsgid \"Role Setting\"\nmsgstr \"角色設置\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:26\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:25\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:30\n#: apps/application/flow/step_node/function_node/i_function_node.py:48\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:26\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:23\n#: apps/application/flow/step_node/question_node/i_question_node.py:27\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:15\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:16\nmsgid \"Whether to return content\"\nmsgstr \"是否返回內容\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:33\nmsgid \"Context Type\"\nmsgstr \"上下文類型\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:35\nmsgid \"Whether to enable MCP\"\nmsgstr \"是否啟用 MCP\"\n\n#: apps/application/flow/step_node/ai_chat_step_node/i_chat_node.py:36\nmsgid \"MCP Server\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:12\n#: apps/application/serializers/application.py:539\n#: apps/application/serializers/application_access_token.py:44\n#: apps/application/serializers/application_chat.py:38\n#: apps/application/serializers/application_chat.py:54\n#: apps/application/serializers/application_chat_record.py:43\n#: apps/application/serializers/application_chat_record.py:75\n#: apps/application/serializers/application_stats.py:35\n#: apps/application/serializers/application_version.py:21\n#: apps/application/serializers/application_version.py:67\n#: apps/chat/serializers/chat.py:118\n#: apps/chat/serializers/chat_authentication.py:80\n#: apps/xpack/api/application_setting.py:31 apps/xpack/api/platform.py:29\n#: apps/xpack/api/platform.py:70\nmsgid \"Application ID\"\nmsgstr \"智能體 ID\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:15\nmsgid \"API Input Fields\"\nmsgstr \"API 輸入欄位\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:17\nmsgid \"User Input Fields\"\nmsgstr \"用戶輸入欄位\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:18\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:25\n#: apps/chat/serializers/chat.py:57 apps/tools/serializers/tool.py:391\nmsgid \"picture\"\nmsgstr \"圖片\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:19\n#: apps/application/flow/step_node/document_extract_node/i_document_extract_node.py:12\n#: apps/chat/serializers/chat.py:58\nmsgid \"document\"\nmsgstr \"文檔\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:20\n#: apps/chat/serializers/chat.py:59\nmsgid \"Audio\"\nmsgstr \"音頻\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:22\n#: apps/chat/serializers/chat.py:62\nmsgid \"Child Nodes\"\nmsgstr \"子節點\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:23\n#: apps/application/flow/step_node/form_node/i_form_node.py:21\nmsgid \"Form Data\"\nmsgstr \"表單數據\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:57\nmsgid \"\"\n\"Parameter value error: The uploaded document lacks file_id, and the document \"\n\"upload fails\"\nmsgstr \"參數值錯誤：上傳的文檔缺少 file_id，文檔上傳失敗\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:66\nmsgid \"\"\n\"Parameter value error: The uploaded image lacks file_id, and the image \"\n\"upload fails\"\nmsgstr \"參數值錯誤：上傳的圖片缺少 file_id，圖片上傳失敗\"\n\n#: apps/application/flow/step_node/application_node/i_application_node.py:76\nmsgid \"\"\n\"Parameter value error: The uploaded audio lacks file_id, and the audio \"\n\"upload fails.\"\nmsgstr \"參數值錯誤：上傳的音頻缺少 file_id，音頻上傳失敗\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:19\n#: apps/models_provider/api/provide.py:24\nmsgid \"value\"\nmsgstr \"值\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:20\nmsgid \"Fields\"\nmsgstr \"欄位\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:24\nmsgid \"Branch id\"\nmsgstr \"分支 ID\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:25\nmsgid \"Branch Type\"\nmsgstr \"分支類型\"\n\n#: apps/application/flow/step_node/condition_node/i_condition_node.py:26\nmsgid \"Condition or|and\"\nmsgstr \"條件 或|與\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:20\nmsgid \"Response Type\"\nmsgstr \"響應類型\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:21\n#: apps/application/flow/step_node/variable_assign_node/i_variable_assign_node.py:13\nmsgid \"Reference Field\"\nmsgstr \"引用欄位\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:23\nmsgid \"Direct answer content\"\nmsgstr \"直接回答內容\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:31\nmsgid \"Reference field cannot be empty\"\nmsgstr \"引用欄位不能為空\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:33\nmsgid \"Reference field error\"\nmsgstr \"引用欄位錯誤\"\n\n#: apps/application/flow/step_node/direct_reply_node/i_reply_node.py:36\nmsgid \"Content cannot be empty\"\nmsgstr \"內容不能為空\"\n\n#: apps/application/flow/step_node/form_node/i_form_node.py:19\nmsgid \"Form Configuration\"\nmsgstr \"表單配置\"\n\n#: apps/application/flow/step_node/form_node/i_form_node.py:20\nmsgid \"Form output content\"\nmsgstr \"表單輸出內容\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:22\n#: apps/application/flow/step_node/function_node/i_function_node.py:24\nmsgid \"Variable Name\"\nmsgstr \"變量名稱\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:23\n#: apps/application/flow/step_node/function_node/i_function_node.py:34\nmsgid \"Variable Value\"\nmsgstr \"變量名稱\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:27\nmsgid \"Library ID\"\nmsgstr \"工具 ID\"\n\n#: apps/application/flow/step_node/function_lib_node/i_function_lib_node.py:36\nmsgid \"The function has been deleted\"\nmsgstr \"工具已被刪除\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:43\nmsgid \"Field: {name} No value set\"\nmsgstr \"欄位：{name} 未設置值\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:59\nmsgid \"Field: {name} Type: {_type} Value: {value} Unsupported types\"\nmsgstr \"欄位：{name} 類型：{_type} 值：{value} 類型轉換錯誤\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:63\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:100\nmsgid \"Field: {name} Type: {_type} Value: {value} Type error\"\nmsgstr \"欄位：{name} 類型：{_type} 值：{value} 類型轉換錯誤\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:91\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:96\n#: apps/tools/serializers/tool.py:254 apps/tools/serializers/tool.py:259\nmsgid \"type error\"\nmsgstr \"類型錯誤\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:106\n#: apps/tools/serializers/tool.py:398\nmsgid \"Function does not exist\"\nmsgstr \"工具不存在\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:108\nmsgid \"No permission to use this function {name}\"\nmsgstr \"無權限使用此工具 {name}\"\n\n#: apps/application/flow/step_node/function_lib_node/impl/base_function_lib_node.py:110\n#, python-brace-format\nmsgid \"Function {name} is unavailable\"\nmsgstr \"工具 {name} 不可用\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:25\nmsgid \"Is this field required\"\nmsgstr \"{keys} 是必填項\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:26\n#: apps/knowledge/serializers/document.py:203\n#: apps/tools/serializers/tool.py:120\nmsgid \"type\"\nmsgstr \"類型\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:28\nmsgid \"The field only supports string|int|dict|array|float\"\nmsgstr \"欄位僅支持字符串|整數|字典|數組|浮點數\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:30\n#: apps/folders/serializers/folder.py:106\n#: apps/folders/serializers/folder.py:141\n#: apps/folders/serializers/folder.py:196 apps/tools/serializers/tool.py:124\nmsgid \"source\"\nmsgstr \"來源\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:32\n#: apps/tools/serializers/tool.py:126\nmsgid \"The field only supports custom|reference\"\nmsgstr \"欄位僅支持自定義|引用\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:40\nmsgid \"{field}, this field is required.\"\nmsgstr \"{field_label} 欄位是必填項\"\n\n#: apps/application/flow/step_node/function_node/i_function_node.py:46\nmsgid \"function\"\nmsgstr \"工具內容\"\n\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:14\nmsgid \"Prompt word (positive)\"\nmsgstr \"提示詞 (正向)\"\n\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:16\nmsgid \"Prompt word (negative)\"\nmsgstr \"提示詞 (負向)\"\n\n#: apps/application/flow/step_node/image_generate_step_node/i_image_generate_node.py:23\n#: apps/application/flow/step_node/image_understand_step_node/i_image_understand_node.py:20\nmsgid \"Conversation storage type\"\nmsgstr \"對話存儲類型\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:13\nmsgid \"Mcp servers\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:16\nmsgid \"Mcp server\"\nmsgstr \"\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:18\nmsgid \"Mcp tool\"\nmsgstr \"Mcp 工具\"\n\n#: apps/application/flow/step_node/mcp_node/i_mcp_node.py:21\nmsgid \"Tool parameters\"\nmsgstr \"工具參數\"\n\n#: apps/application/flow/step_node/reranker_node/i_reranker_node.py:26\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:33\nmsgid \"Maximum number of words in a quoted segment\"\nmsgstr \"引用段落的最大字數\"\n\n#: apps/application/flow/step_node/search_dataset_node/i_search_dataset_node.py:27\n#: apps/knowledge/serializers/knowledge.py:97\n#: apps/knowledge/serializers/knowledge.py:640\nmsgid \"similarity\"\nmsgstr \"相似度\"\n\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:18\nmsgid \"The audio file cannot be empty\"\nmsgstr \"音頻文件不能為空\"\n\n#: apps/application/flow/step_node/speech_to_text_step_node/i_speech_to_text_node.py:33\nmsgid \"\"\n\"Parameter value error: The uploaded audio lacks file_id, and the audio \"\n\"upload fails\"\nmsgstr \"參數錯誤：上傳的音頻缺少 file_id，音頻上傳失敗\"\n\n#: apps/application/flow/step_node/text_to_speech_step_node/i_text_to_speech_node.py:18\nmsgid \"Text content\"\nmsgstr \"文本內容\"\n\n#: apps/application/models/application_chat.py:79\n#: apps/xpack/serializers/channel/chat_manage.py:94\n#: apps/xpack/serializers/channel/chat_manage.py:152\nmsgid \"\"\n\"Sorry, no relevant content was found. Please re-describe your problem or \"\n\"provide more information. \"\nmsgstr \"不好意思，沒有找到相關內容。請重新描述您的問題或提供更多信息。\"\n\n#: apps/application/serializers/application.py:78\nmsgid \"No reference status\"\nmsgstr \"無參考狀態\"\n\n#: apps/application/serializers/application.py:86\nmsgid \"Acquaintance\"\nmsgstr \"相似度\"\n\n#: apps/application/serializers/application.py:88\nmsgid \"Maximum number of quoted characters\"\nmsgstr \"引用字符的最大數量\"\n\n#: apps/application/serializers/application.py:95\nmsgid \"Segment settings not referenced\"\nmsgstr \"引用段設置未引用\"\n\n#: apps/application/serializers/application.py:104\n#: apps/application/serializers/application_chat_record.py:176\n#: apps/application/serializers/application_chat_record.py:252\n#: apps/application/serializers/application_chat_record.py:317\nmsgid \"Knowledge base id\"\nmsgstr \"知識庫\"\n\n#: apps/application/serializers/application.py:105\nmsgid \"Knowledge Base List\"\nmsgstr \"知識庫列表\"\n\n#: apps/application/serializers/application.py:119\nmsgid \"The knowledge base id does not exist\"\nmsgstr \"知識庫 ID 不存在\"\n\n#: apps/application/serializers/application.py:126\nmsgid \"Role prompts\"\nmsgstr \"角色提示\"\n\n#: apps/application/serializers/application.py:128\nmsgid \"No citation segmentation prompt\"\nmsgstr \"無引用段落提示\"\n\n#: apps/application/serializers/application.py:130\nmsgid \"Thinking process switch\"\nmsgstr \"思考過程開關\"\n\n#: apps/application/serializers/application.py:134\nmsgid \"The thinking process begins to mark\"\nmsgstr \"思考過程開始標記\"\n\n#: apps/application/serializers/application.py:138\nmsgid \"End of thinking process marker\"\nmsgstr \"思考過程結束標記\"\n\n#: apps/application/serializers/application.py:155\n#: apps/application/serializers/application.py:203\n#: apps/application/serializers/application.py:378\nmsgid \"Opening remarks\"\nmsgstr \"開始提示\"\n\n#: apps/application/serializers/application.py:191\nmsgid \"application name\"\nmsgstr \"智能體名稱\"\n\n#: apps/application/serializers/application.py:194\nmsgid \"application describe\"\nmsgstr \"智能體描述\"\n\n#: apps/application/serializers/application.py:197\n#: apps/application/serializers/application.py:372\n#: apps/common/constants/permission_constants.py:226\n#: apps/common/constants/permission_constants.py:259\n#: apps/common/constants/permission_constants.py:264\n#: apps/models_provider/views/model.py:63\n#: apps/models_provider/views/model.py:95\n#: apps/models_provider/views/model.py:113\n#: apps/models_provider/views/model.py:130\n#: apps/models_provider/views/model.py:145\n#: apps/models_provider/views/model.py:160\n#: apps/models_provider/views/model.py:173\n#: apps/models_provider/views/model.py:194\n#: apps/models_provider/views/model.py:210\n#: apps/models_provider/views/model_apply.py:29\n#: apps/models_provider/views/model_apply.py:41\n#: apps/models_provider/views/model_apply.py:53\n#: apps/models_provider/views/provide.py:25\n#: apps/models_provider/views/provide.py:48\n#: apps/models_provider/views/provide.py:62\n#: apps/models_provider/views/provide.py:80\n#: apps/models_provider/views/provide.py:97\nmsgid \"Model\"\nmsgstr \"模型\"\n\n#: apps/application/serializers/application.py:201\n#: apps/application/serializers/application.py:376\nmsgid \"Historical chat records\"\nmsgstr \"歷史聊天記錄\"\n\n#: apps/application/serializers/application.py:206\n#: apps/application/serializers/application.py:380\nmsgid \"Related Knowledge Base\"\nmsgstr \"相關知識庫\"\n\n#: apps/application/serializers/application.py:213\n#: apps/application/serializers/application.py:390\nmsgid \"Question completion\"\nmsgstr \"問題完成\"\n\n#: apps/application/serializers/application.py:217\nmsgid \"Application Type\"\nmsgstr \"智能體類型\"\n\n#: apps/application/serializers/application.py:221\nmsgid \"Application type only supports SIMPLE|WORK_FLOW\"\nmsgstr \"智能體類型僅支持 SIMPLE|WORK_FLOW\"\n\n#: apps/application/serializers/application.py:226\n#: apps/application/serializers/application.py:394\nmsgid \"Model parameters\"\nmsgstr \"模型參數\"\n\n#: apps/application/serializers/application.py:228\n#: apps/application/serializers/application.py:396\nmsgid \"Voice playback enabled\"\nmsgstr \"開啟語音播放\"\n\n#: apps/application/serializers/application.py:230\n#: apps/application/serializers/application.py:398\nmsgid \"Voice playback model ID\"\nmsgstr \"語音播放模型 ID\"\n\n#: apps/application/serializers/application.py:232\n#: apps/application/serializers/application.py:400\nmsgid \"Voice playback type\"\nmsgstr \"語音播放類型\"\n\n#: apps/application/serializers/application.py:234\n#: apps/application/serializers/application.py:402\nmsgid \"Voice playback autoplay\"\nmsgstr \"自動播放語音\"\n\n#: apps/application/serializers/application.py:236\n#: apps/application/serializers/application.py:404\nmsgid \"Voice recognition enabled\"\nmsgstr \"開啟語音識別\"\n\n#: apps/application/serializers/application.py:238\n#: apps/application/serializers/application.py:406\nmsgid \"Speech recognition model ID\"\nmsgstr \"語音識別模型 ID\"\n\n#: apps/application/serializers/application.py:240\n#: apps/application/serializers/application.py:408\nmsgid \"Voice recognition automatic transmission\"\nmsgstr \"語音識別自動播放\"\n\n#: apps/application/serializers/application.py:281\nmsgid \"Primary key id\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:286\nmsgid \"Application type\"\nmsgstr \"智能體類型\"\n\n#: apps/application/serializers/application.py:287\n#: apps/xpack/serializers/resource_chat_user.py:34\n#: apps/xpack/serializers/resource_chat_user.py:110\n#: apps/xpack/serializers/resource_chat_user_group.py:17\n#: apps/xpack/serializers/resource_chat_user_group.py:85\nmsgid \"Resource type\"\nmsgstr \"資源類型\"\n\n#: apps/application/serializers/application.py:288\nmsgid \"Affiliation user\"\nmsgstr \"關聯用戶\"\n\n#: apps/application/serializers/application.py:289\nmsgid \"Creation time\"\nmsgstr \"創建時間\"\n\n#: apps/application/serializers/application.py:290\nmsgid \"Modification time\"\nmsgstr \"修改時間\"\n\n#: apps/application/serializers/application.py:294\n#: apps/application/serializers/application_chat_record.py:42\n#: apps/application/serializers/application_chat_record.py:323\n#: apps/application/serializers/application_version.py:40\n#: apps/chat/serializers/chat.py:318 apps/role_setting/api/role_setting.py:147\n#: apps/users/api/user.py:64 apps/users/api/user.py:170\n#: apps/workspace/api/workspace.py:46 apps/workspace/api/workspace.py:66\n#: apps/workspace/api/workspace.py:85 apps/workspace/api/workspace.py:103\n#: apps/workspace/serializers/workspace_serializers.py:239\n#: apps/xpack/api/application_setting.py:24 apps/xpack/api/knowledge_lark.py:40\n#: apps/xpack/api/knowledge_lark.py:72 apps/xpack/api/knowledge_lark.py:104\n#: apps/xpack/api/platform.py:22 apps/xpack/api/platform.py:63\nmsgid \"Workspace ID\"\nmsgstr \"工作空間 ID\"\n\n#: apps/application/serializers/application.py:363\n#: apps/knowledge/serializers/document.py:157\n#: apps/knowledge/serializers/document.py:162 apps/oss/serializers/file.py:57\n#: apps/tools/serializers/tool.py:356\nmsgid \"file\"\nmsgstr \"文件\"\n\n#: apps/application/serializers/application.py:384\nmsgid \"Dataset settings\"\nmsgstr \"知識庫設置\"\n\n#: apps/application/serializers/application.py:387\nmsgid \"Model setup\"\nmsgstr \"模型設置\"\n\n#: apps/application/serializers/application.py:391\nmsgid \"Icon\"\nmsgstr \"\"\n\n#: apps/application/serializers/application.py:412\n#: apps/application/serializers/application_api_key.py:33\n#: apps/application/serializers/application_api_key.py:64\n#: apps/folders/serializers/folder.py:101\n#: apps/folders/serializers/folder.py:140\n#: apps/folders/serializers/folder.py:195\n#: apps/knowledge/serializers/document.py:253\n#: apps/knowledge/serializers/document.py:347\n#: apps/knowledge/serializers/document.py:408\n#: apps/knowledge/serializers/document.py:502\n#: apps/knowledge/serializers/document.py:736\n#: apps/knowledge/serializers/document.py:888\n#: apps/knowledge/serializers/document.py:963\n#: apps/knowledge/serializers/document.py:983\n#: apps/knowledge/serializers/document.py:1166\n#: apps/knowledge/serializers/knowledge.py:208\n#: apps/knowledge/serializers/knowledge.py:448\n#: apps/knowledge/serializers/knowledge.py:557\n#: apps/knowledge/serializers/knowledge.py:635\n#: apps/knowledge/serializers/paragraph.py:134\n#: apps/knowledge/serializers/paragraph.py:346\n#: apps/knowledge/serializers/paragraph.py:438\n#: apps/knowledge/serializers/paragraph.py:558\n#: apps/knowledge/serializers/problem.py:176\n#: apps/knowledge/serializers/problem.py:204\n#: apps/models_provider/api/model.py:30 apps/models_provider/api/model.py:86\n#: apps/models_provider/api/model.py:99\n#: apps/models_provider/serializers/model_serializer.py:259\n#: apps/models_provider/serializers/model_serializer.py:323\n#: apps/models_provider/serializers/model_serializer.py:392\n#: apps/shared/api/shared_knowledge.py:131\n#: apps/shared/api/shared_knowledge.py:164 apps/shared/api/shared_tool.py:60\n#: apps/shared/api/shared_tool.py:147\n#: apps/shared/serializers/shared_knowledge.py:61\n#: apps/shared/serializers/shared_knowledge.py:109\n#: apps/shared/serializers/shared_knowledge.py:157\n#: apps/shared/serializers/shared_model.py:110\n#: apps/shared/serializers/shared_tool.py:45\n#: apps/shared/serializers/shared_tool.py:86\n#: apps/system_manage/serializers/user_resource_permission.py:74\n#: apps/tools/serializers/tool.py:188 apps/tools/serializers/tool.py:210\n#: apps/tools/serializers/tool.py:268 apps/tools/serializers/tool.py:358\n#: apps/tools/serializers/tool.py:389 apps/tools/serializers/tool.py:425\n#: apps/tools/serializers/tool.py:452\n#: apps/xpack/serializers/dataset_lark_serializer.py:45\n#: apps/xpack/serializers/resource_chat_user.py:33\n#: apps/xpack/serializers/resource_chat_user.py:109\n#: apps/xpack/serializers/resource_chat_user_group.py:16\n#: apps/xpack/serializers/resource_chat_user_group.py:84\nmsgid \"workspace id\"\nmsgstr \"工作空間ID\"\n\n#: apps/application/serializers/application.py:459\nmsgid \"\"\n\"The community version supports up to 5 applications. If you need more \"\n\"applications, please contact us (https://fit2cloud.com/).\"\nmsgstr \"\"\n\"社區版支持最多5個智能體，如需更多智能體，請聯繫我們（https://fit2cloud.com/）。\"\n\n#: apps/application/serializers/application.py:471\n#: apps/common/handle/impl/qa/zip_parse_qa_handle.py:56\n#: apps/common/handle/impl/text/zip_split_handle.py:69\n#: apps/knowledge/serializers/document.py:864\n#: apps/knowledge/serializers/document.py:871\n#: apps/tools/serializers/tool.py:370\nmsgid \"Unsupported file format\"\nmsgstr \"不支持的文件格式\"\n\n#: apps/application/serializers/application.py:545\nmsgid \"Application id does not exist\"\nmsgstr \"智能體 ID 不存在\"\n\n#: apps/application/serializers/application.py:591\nmsgid \"work_flow is a required field\"\nmsgstr \"工作流是必填欄位\"\n\n#: apps/application/serializers/application.py:695\nmsgid \"Unknown knowledge base id {dataset_id}, unable to associate\"\nmsgstr \"未知知識庫 ID {dataset_id}，無法關聯\"\n\n#: apps/application/serializers/application_access_token.py:24\nmsgid \"Reset Token\"\nmsgstr \"重置令牌\"\n\n#: apps/application/serializers/application_access_token.py:25\nmsgid \"Is it enabled\"\nmsgstr \"是否開啟\"\n\n#: apps/application/serializers/application_access_token.py:28\nmsgid \"Number of visits\"\nmsgstr \"訪問次數\"\n\n#: apps/application/serializers/application_access_token.py:30\nmsgid \"Whether to enable whitelist\"\nmsgstr \"是否啟用白名單\"\n\n#: apps/application/serializers/application_access_token.py:32\n#: apps/application/serializers/application_access_token.py:33\nmsgid \"Whitelist\"\nmsgstr \"白名單\"\n\n#: apps/application/serializers/application_access_token.py:35\nmsgid \"Whether to display knowledge sources\"\nmsgstr \"是否展示知識來源\"\n\n#: apps/application/serializers/application_access_token.py:37\n#: apps/users/serializers/user.py:665\n#: apps/xpack/serializers/application_setting_serializer.py:37\nmsgid \"language\"\nmsgstr \"語言\"\n\n#: apps/application/serializers/application_api_key.py:21\nmsgid \"Availability\"\nmsgstr \"可用\"\n\n#: apps/application/serializers/application_api_key.py:24\nmsgid \"Is cross-domain allowed\"\nmsgstr \"是否允許跨域\"\n\n#: apps/application/serializers/application_api_key.py:28\nmsgid \"Cross-domain address\"\nmsgstr \"跨域地址\"\n\n#: apps/application/serializers/application_api_key.py:29\nmsgid \"Cross-domain list\"\nmsgstr \"跨域列表\"\n\n#: apps/application/serializers/application_api_key.py:34\n#: apps/application/serializers/application_api_key.py:65\n#: apps/knowledge/serializers/knowledge.py:72\n#: apps/xpack/serializers/application_setting_serializer.py:77\n#: apps/xpack/serializers/dataset_lark_serializer.py:295\nmsgid \"application id\"\nmsgstr \"智能體 ID\"\n\n#: apps/application/serializers/application_api_key.py:41\n#: apps/chat/serializers/chat.py:277 apps/chat/serializers/chat.py:332\n#: apps/xpack/serializers/application_setting_serializer.py:103\n#: apps/xpack/serializers/platform_serializer.py:81\n#: apps/xpack/serializers/platform_serializer.py:103\n#: apps/xpack/serializers/platform_serializer.py:138\n#: apps/xpack/serializers/platform_serializer.py:149\nmsgid \"Application does not exist\"\nmsgstr \"智能體不存在\"\n\n#: apps/application/serializers/application_api_key.py:66\nmsgid \"ApiKeyId\"\nmsgstr \"ApiKey ID\"\n\n#: apps/application/serializers/application_api_key.py:87\nmsgid \"APIKey does not exist\"\nmsgstr \"APIKey 不存在\"\n\n#: apps/application/serializers/application_chat.py:33\nmsgid \"chat id\"\nmsgstr \"對話 ID\"\n\n#: apps/application/serializers/application_chat.py:34\n#: apps/application/serializers/application_chat.py:51\n#: apps/application/serializers/application_chat.py:182\n#: apps/application/serializers/application_version.py:23\nmsgid \"summary\"\nmsgstr \"摘要\"\n\n#: apps/application/serializers/application_chat.py:35\nmsgid \"Chat User ID\"\nmsgstr \"對話用戶 ID\"\n\n#: apps/application/serializers/application_chat.py:36\nmsgid \"Chat User Type\"\nmsgstr \"對話用戶類型\"\n\n#: apps/application/serializers/application_chat.py:37\nmsgid \"Is delete\"\nmsgstr \"刪除\"\n\n#: apps/application/serializers/application_chat.py:39\n#: apps/application/serializers/application_stats.py:25\nmsgid \"Number of conversations\"\nmsgstr \"對話數量\"\n\n#: apps/application/serializers/application_chat.py:40\n#: apps/application/serializers/application_stats.py:29\nmsgid \"Number of Likes\"\nmsgstr \"點讚數量\"\n\n#: apps/application/serializers/application_chat.py:41\n#: apps/application/serializers/application_stats.py:31\nmsgid \"Number of thumbs-downs\"\nmsgstr \"點踩數量\"\n\n#: apps/application/serializers/application_chat.py:42\nmsgid \"Number of tags\"\nmsgstr \"標籤數量\"\n\n#: apps/application/serializers/application_chat.py:46\nmsgid \"Chat ID List\"\nmsgstr \"對話 ID 列表\"\n\n#: apps/application/serializers/application_chat.py:52\n#: apps/application/serializers/application_stats.py:36\n#: apps/xpack/serializers/operate_log_serializer.py:55\nmsgid \"Start time\"\nmsgstr \"開始時間\"\n\n#: apps/application/serializers/application_chat.py:53\n#: apps/application/serializers/application_stats.py:37\n#: apps/xpack/serializers/operate_log_serializer.py:56\nmsgid \"End time\"\nmsgstr \"結束時間\"\n\n#: apps/application/serializers/application_chat.py:61\nmsgid \"Only supports and|or\"\nmsgstr \"只支持 與|或\"\n\n#: apps/application/serializers/application_chat.py:183\nmsgid \"Problem after optimization\"\nmsgstr \"優化後的問題\"\n\n#: apps/application/serializers/application_chat.py:184\nmsgid \"answer\"\nmsgstr \"回答\"\n\n#: apps/application/serializers/application_chat.py:184\nmsgid \"User feedback\"\nmsgstr \"用戶反饋\"\n\n#: apps/application/serializers/application_chat.py:186\nmsgid \"Section title + content\"\nmsgstr \"分段標題 + 內容\"\n\n#: apps/application/serializers/application_chat.py:187\n#: apps/application/views/application_chat_record.py:139\n#: apps/application/views/application_chat_record.py:140\n#: apps/application/views/application_chat_record.py:141\n#: apps/common/constants/permission_constants.py:248\nmsgid \"Annotation\"\nmsgstr \"标注\"\n\n#: apps/application/serializers/application_chat.py:187\nmsgid \"Consuming tokens\"\nmsgstr \"消耗的令牌\"\n\n#: apps/application/serializers/application_chat.py:188\nmsgid \"Time consumed (s)\"\nmsgstr \"耗时 (s)\"\n\n#: apps/application/serializers/application_chat.py:189\nmsgid \"Question Time\"\nmsgstr \"提問時間\"\n\n#: apps/application/serializers/application_chat_record.py:44\n#: apps/application/serializers/application_chat_record.py:143\n#: apps/application/serializers/application_chat_record.py:250\n#: apps/application/serializers/application_chat_record.py:315\n#: apps/chat/serializers/chat.py:45\nmsgid \"Conversation record id\"\nmsgstr \"對話記錄 ID\"\n\n#: apps/application/serializers/application_chat_record.py:51\nmsgid \"Application authentication information does not exist\"\nmsgstr \"智能體認證信息不存在\"\n\n#: apps/application/serializers/application_chat_record.py:53\nmsgid \"Displaying knowledge sources is not enabled\"\nmsgstr \"知識庫來源展示未開啟\"\n\n#: apps/application/serializers/application_chat_record.py:70\n#: apps/chat/serializers/chat.py:127 apps/chat/serializers/chat.py:274\nmsgid \"Conversation does not exist\"\nmsgstr \"對話不存在\"\n\n#: apps/application/serializers/application_chat_record.py:152\n#: apps/application/serializers/application_chat_record.py:279\n#: apps/application/serializers/application_chat_record.py:336\n#: apps/chat/serializers/chat.py:205\nmsgid \"Conversation record does not exist\"\nmsgstr \"對話記錄不存在\"\n\n#: apps/application/serializers/application_chat_record.py:168\nmsgid \"Section title\"\nmsgstr \"章節標題\"\n\n#: apps/application/serializers/application_chat_record.py:169\nmsgid \"Paragraph content\"\nmsgstr \"段落內容\"\n\n#: apps/application/serializers/application_chat_record.py:177\n#: apps/application/serializers/application_chat_record.py:254\n#: apps/application/serializers/application_chat_record.py:319\nmsgid \"Document id\"\nmsgstr \"文檔 ID\"\n\n#: apps/application/serializers/application_chat_record.py:184\n#: apps/application/serializers/application_chat_record.py:260\n#: apps/knowledge/serializers/paragraph.py:246\nmsgid \"The document id is incorrect\"\nmsgstr \"文檔 ID 不正確\"\n\n#: apps/application/serializers/application_chat_record.py:203\nmsgid \"Conversation records that do not exist\"\nmsgstr \"對話記錄不存在\"\n\n#: apps/application/serializers/application_chat_record.py:321\nmsgid \"Paragraph id\"\nmsgstr \"段落 ID\"\n\n#: apps/application/serializers/application_chat_record.py:340\n#, python-brace-format\nmsgid \"\"\n\"The paragraph id is wrong. The current conversation record does not exist. \"\n\"[{paragraph_id}] paragraph id\"\nmsgstr \"段落 ID 錯誤。當前對話記錄不存在。[{paragraph_id}] 段落 ID\"\n\n#: apps/application/serializers/application_stats.py:26\nmsgid \"Number of new users\"\nmsgstr \"新用戶數量\"\n\n#: apps/application/serializers/application_stats.py:27\nmsgid \"Total number of users\"\nmsgstr \"總用戶數\"\n\n#: apps/application/serializers/application_stats.py:28\nmsgid \"date\"\nmsgstr \"日期\"\n\n#: apps/application/serializers/application_stats.py:30\nmsgid \"Tokens consumption\"\nmsgstr \"消耗的令牌\"\n\n#: apps/application/serializers/application_version.py:36\nmsgid \"Version Name\"\nmsgstr \"版本名稱\"\n\n#: apps/application/serializers/application_version.py:69\nmsgid \"Workflow version id\"\nmsgstr \"工作流版本 ID\"\n\n#: apps/application/serializers/application_version.py:79\n#: apps/application/serializers/application_version.py:94\nmsgid \"Workflow version does not exist\"\nmsgstr \"工作流版本不存在\"\n\n#: apps/application/views/application.py:41\n#: apps/application/views/application.py:42\n#: apps/application/views/application.py:43\nmsgid \"Create an application\"\nmsgstr \"創建一個智能體程式\"\n\n#: apps/application/views/application.py:47\n#: apps/application/views/application.py:65\n#: apps/application/views/application.py:82\n#: apps/application/views/application.py:103\n#: apps/application/views/application.py:123\n#: apps/application/views/application.py:145\n#: apps/application/views/application.py:166\n#: apps/application/views/application.py:187\n#: apps/application/views/application.py:206\n#: apps/application/views/application_access_token.py:32\n#: apps/application/views/application_access_token.py:47\n#: apps/application/views/application_chat.py:102\n#: apps/application/views/application_chat.py:122\n#: apps/application/views/application_stats.py:33\n#: apps/common/constants/permission_constants.py:224\n#: apps/common/constants/permission_constants.py:234\n#: apps/xpack/views/application_setting.py:29\n#: apps/xpack/views/application_setting.py:47\nmsgid \"Application\"\nmsgstr \"智能體\"\n\n#: apps/application/views/application.py:60\n#: apps/application/views/application.py:61\n#: apps/application/views/application.py:62\nmsgid \"Get the application list\"\nmsgstr \"獲取智能體列表\"\n\n#: apps/application/views/application.py:77\n#: apps/application/views/application.py:78\n#: apps/application/views/application.py:79\nmsgid \"Get the application list by page\"\nmsgstr \"分頁獲取智能體列表\"\n\n#: apps/application/views/application.py:97\n#: apps/application/views/application.py:98\n#: apps/application/views/application.py:99\nmsgid \"Import Application\"\nmsgstr \"導入智能體\"\n\n#: apps/application/views/application.py:117\n#: apps/application/views/application.py:118\n#: apps/application/views/application.py:119\nmsgid \"Export application\"\nmsgstr \"導出智能體\"\n\n#: apps/application/views/application.py:140\n#: apps/application/views/application.py:141\n#: apps/application/views/application.py:142\nmsgid \"Deleting application\"\nmsgstr \"刪除智能體\"\n\n#: apps/application/views/application.py:160\n#: apps/application/views/application.py:161\n#: apps/application/views/application.py:162\nmsgid \"Modify the application\"\nmsgstr \"修改智能體\"\n\n#: apps/application/views/application.py:181\n#: apps/application/views/application.py:182\n#: apps/application/views/application.py:183\nmsgid \"Get application details\"\nmsgstr \"獲取智能體詳情\"\n\n#: apps/application/views/application.py:200\n#: apps/application/views/application.py:201\n#: apps/application/views/application.py:202\nmsgid \"Publishing an application\"\nmsgstr \"發布智能體\"\n\n#: apps/application/views/application_access_token.py:27\n#: apps/application/views/application_access_token.py:28\n#: apps/application/views/application_access_token.py:29\nmsgid \"Modify application access restriction information\"\nmsgstr \"修改智能體訪問限制信息\"\n\n#: apps/application/views/application_access_token.py:43\n#: apps/application/views/application_access_token.py:44\n#: apps/application/views/application_access_token.py:45\nmsgid \"Get application access restriction information\"\nmsgstr \"獲取智能體訪問限制信息\"\n\n#: apps/application/views/application_api_key.py:31\n#: apps/application/views/application_api_key.py:32\n#: apps/application/views/application_api_key.py:33\nmsgid \"Create application ApiKey\"\nmsgstr \"創建智能體 API 密鑰\"\n\n#: apps/application/views/application_api_key.py:37\n#: apps/application/views/application_api_key.py:57\n#: apps/application/views/application_api_key.py:77\n#: apps/application/views/application_api_key.py:99\nmsgid \"Application Api Key\"\nmsgstr \"智能體 API 密鑰\"\n\n#: apps/application/views/application_api_key.py:52\nmsgid \"GET application ApiKey List\"\nmsgstr \"獲取智能體的 API 密鑰列表\"\n\n#: apps/application/views/application_api_key.py:53\n#: apps/application/views/application_api_key.py:54\nmsgid \"Create application ApiKey List\"\nmsgstr \"創建智能體 API 密鑰列表\"\n\n#: apps/application/views/application_api_key.py:71\n#: apps/application/views/application_api_key.py:72\n#: apps/application/views/application_api_key.py:73\nmsgid \"Modify application API_KEY\"\nmsgstr \"修改智能體 API 密鑰\"\n\n#: apps/application/views/application_api_key.py:93\n#: apps/application/views/application_api_key.py:94\n#: apps/application/views/application_api_key.py:95\nmsgid \"Delete Application API_KEY\"\nmsgstr \"刪除智能體 API 密鑰\"\n\n#: apps/application/views/application_chat.py:35\n#: apps/application/views/application_chat.py:36\n#: apps/application/views/application_chat.py:37\nmsgid \"Get the conversation list\"\nmsgstr \"獲取對話列表\"\n\n#: apps/application/views/application_chat.py:41\n#: apps/application/views/application_chat.py:61\n#: apps/application/views/application_chat.py:82\n#: apps/application/views/application_chat_record.py:37\n#: apps/application/views/application_chat_record.py:58\n#: apps/application/views/application_chat_record.py:82\n#: apps/application/views/application_chat_record.py:106\n#: apps/application/views/application_chat_record.py:125\n#: apps/application/views/application_chat_record.py:145\n#: apps/application/views/application_chat_record.py:171\nmsgid \"Application/Conversation Log\"\nmsgstr \"智能體/對話日誌\"\n\n#: apps/application/views/application_chat.py:55\n#: apps/application/views/application_chat.py:56\n#: apps/application/views/application_chat.py:57\nmsgid \"Get the conversation list by page\"\nmsgstr \"分頁獲取對話列表\"\n\n#: apps/application/views/application_chat.py:76\n#: apps/application/views/application_chat.py:77\n#: apps/application/views/application_chat.py:78\nmsgid \"Export conversation\"\nmsgstr \"導出對話\"\n\n#: apps/application/views/application_chat.py:97\n#: apps/application/views/application_chat.py:98\n#: apps/application/views/application_chat.py:99\nmsgid \"Get a temporary session id based on the application id\"\nmsgstr \"獲取智能體的臨時會話 ID\"\n\n#: apps/application/views/application_chat.py:116\n#: apps/application/views/application_chat.py:117\n#: apps/application/views/application_chat.py:118 apps/chat/views/chat.py:93\n#: apps/chat/views/chat.py:94 apps/chat/views/chat.py:95\nmsgid \"dialogue\"\nmsgstr \"對話\"\n\n#: apps/application/views/application_chat_record.py:31\n#: apps/application/views/application_chat_record.py:32\n#: apps/application/views/application_chat_record.py:33\nmsgid \"Get the conversation record list\"\nmsgstr \"獲取對話記錄列表\"\n\n#: apps/application/views/application_chat_record.py:52\n#: apps/application/views/application_chat_record.py:53\n#: apps/application/views/application_chat_record.py:54\nmsgid \"Get the conversation record list by page\"\nmsgstr \"分頁獲取對話記錄列表\"\n\n#: apps/application/views/application_chat_record.py:76\n#: apps/application/views/application_chat_record.py:77\n#: apps/application/views/application_chat_record.py:78\nmsgid \"Get conversation record details\"\nmsgstr \"獲取對話記錄詳情\"\n\n#: apps/application/views/application_chat_record.py:100\n#: apps/application/views/application_chat_record.py:101\n#: apps/application/views/application_chat_record.py:102\nmsgid \"Add to Knowledge Base\"\nmsgstr \"添加到知識庫\"\n\n#: apps/application/views/application_chat_record.py:119\n#: apps/application/views/application_chat_record.py:120\n#: apps/application/views/application_chat_record.py:121\nmsgid \"Get the list of marked paragraphs\"\nmsgstr \"獲取標記段落列表\"\n\n#: apps/application/views/application_chat_record.py:165\n#: apps/application/views/application_chat_record.py:166\n#: apps/application/views/application_chat_record.py:167\nmsgid \"Delete a Annotation\"\nmsgstr \"刪除注釋\"\n\n#: apps/application/views/application_stats.py:28\n#: apps/application/views/application_stats.py:29\n#: apps/application/views/application_stats.py:30\nmsgid \"Dialogue-related statistical trends\"\nmsgstr \"與對話有關的統計趨勢\"\n\n#: apps/application/views/application_version.py:30\n#: apps/application/views/application_version.py:31\n#: apps/application/views/application_version.py:32\nmsgid \"Get the application version list\"\nmsgstr \"獲取智能體版本列表\"\n\n#: apps/application/views/application_version.py:35\n#: apps/application/views/application_version.py:55\n#: apps/application/views/application_version.py:76\n#: apps/application/views/application_version.py:94\nmsgid \"Application/Version\"\nmsgstr \"智能體/ 版本\"\n\n#: apps/application/views/application_version.py:50\n#: apps/application/views/application_version.py:51\n#: apps/application/views/application_version.py:52\nmsgid \"Get the list of application versions by page\"\nmsgstr \"分頁獲取智能體版本列表\"\n\n#: apps/application/views/application_version.py:71\n#: apps/application/views/application_version.py:72\n#: apps/application/views/application_version.py:73\nmsgid \"Get application version details\"\nmsgstr \"獲取智能體版本詳情\"\n\n#: apps/application/views/application_version.py:88\n#: apps/application/views/application_version.py:89\n#: apps/application/views/application_version.py:90\nmsgid \"Modify application version information\"\nmsgstr \"修改智能體版本信息\"\n\n#: apps/chat/api/chat_authentication_api.py:38\n#: apps/chat/serializers/chat_authentication.py:28\n#: apps/chat/serializers/chat_authentication.py:54\n#: apps/xpack/serializers/chat_auth.py:25\nmsgid \"access_token\"\nmsgstr \"\"\n\n#: apps/chat/api/chat_embed_api.py:24\nmsgid \"host\"\nmsgstr \"\"\n\n#: apps/chat/api/chat_embed_api.py:31\n#: apps/chat/serializers/chat_embed_serializers.py:25\nmsgid \"protocol\"\nmsgstr \"協議\"\n\n#: apps/chat/api/chat_embed_api.py:38\n#: apps/chat/serializers/chat_embed_serializers.py:26\n#: apps/users/serializers/login.py:36\nmsgid \"token\"\nmsgstr \"令牌\"\n\n#: apps/chat/serializers/chat.py:42\nmsgid \"Is the answer in streaming mode\"\nmsgstr \"是否流式回答\"\n\n#: apps/chat/serializers/chat.py:43\nmsgid \"Do you want to reply again\"\nmsgstr \"是否重新回復\"\n\n#: apps/chat/serializers/chat.py:48\nmsgid \"Node id\"\nmsgstr \"節點 ID\"\n\n#: apps/chat/serializers/chat.py:51\nmsgid \"Runtime node id\"\nmsgstr \"運行時節點 ID\"\n\n#: apps/chat/serializers/chat.py:54\nmsgid \"Node parameters\"\nmsgstr \"節點參數\"\n\n#: apps/chat/serializers/chat.py:56\nmsgid \"Global variables\"\nmsgstr \"全局變量\"\n\n#: apps/chat/serializers/chat.py:60\n#: apps/common/constants/permission_constants.py:222\n#: apps/common/constants/permission_constants.py:228\nmsgid \"Other\"\nmsgstr \"其他\"\n\n#: apps/chat/serializers/chat.py:115 apps/chat/serializers/chat.py:320\nmsgid \"Client id\"\nmsgstr \"客戶端 ID\"\n\n#: apps/chat/serializers/chat.py:116 apps/chat/serializers/chat.py:321\nmsgid \"Client Type\"\nmsgstr \"客戶端類型\"\n\n#: apps/chat/serializers/chat.py:119 apps/chat/serializers/chat.py:322\n#: apps/common/constants/permission_constants.py:240\nmsgid \"Debug\"\nmsgstr \"調試\"\n\n#: apps/chat/serializers/chat.py:146\nmsgid \"The number of visits exceeds today's visits\"\nmsgstr \"今天的訪問次數超過限制\"\n\n#: apps/chat/serializers/chat.py:157\nmsgid \"The current model is not available\"\nmsgstr \"當前模型不可用\"\n\n#: apps/chat/serializers/chat.py:159\nmsgid \"The model is downloading, please try again later\"\nmsgstr \"下載過程被中斷，請重試\"\n\n#: apps/chat/serializers/chat.py:306 apps/chat/serializers/chat.py:357\nmsgid \"The application has not been published. Please use it after publishing.\"\nmsgstr \"智能體未發布，請發布後使用。\"\n\n#: apps/chat/serializers/chat_authentication.py:50\n#: apps/xpack/serializers/chat_auth.py:53\nmsgid \"Invalid access_token\"\nmsgstr \"access_token 無效\"\n\n#: apps/chat/serializers/chat_authentication.py:89\nmsgid \"Illegal User\"\nmsgstr \"非法用戶\"\n\n#: apps/chat/serializers/chat_embed_serializers.py:24\nmsgid \"Host\"\nmsgstr \"\"\n\n#: apps/chat/views/chat.py:37 apps/chat/views/chat.py:38\n#: apps/chat/views/chat.py:39\nmsgid \"Application Anonymous Certification\"\nmsgstr \"智能體匿名認證\"\n\n#: apps/chat/views/chat.py:42 apps/chat/views/chat.py:64\n#: apps/chat/views/chat.py:81 apps/chat/views/chat.py:99\n#: apps/chat/views/chat.py:120 apps/chat/views/chat_embed.py:27\n#: apps/xpack/views/chat_user_auth.py:419\nmsgid \"Chat\"\nmsgstr \"聊天\"\n\n#: apps/chat/views/chat.py:59 apps/chat/views/chat.py:60\n#: apps/chat/views/chat.py:61\nmsgid \"Get application related information\"\nmsgstr \"獲取智能體相關信息\"\n\n#: apps/chat/views/chat.py:76 apps/chat/views/chat.py:77\n#: apps/chat/views/chat.py:78\nmsgid \"Get application authentication information\"\nmsgstr \"獲取智能體認證信息\"\n\n#: apps/chat/views/chat.py:115 apps/chat/views/chat.py:116\n#: apps/chat/views/chat.py:117\nmsgid \"Get the session id according to the application id\"\nmsgstr \"根據智能體 ID 獲取會話 ID\"\n\n#: apps/chat/views/chat.py:131 apps/chat/views/chat.py:132\n#: apps/chat/views/chat.py:133 apps/users/views/login.py:70\n#: apps/users/views/login.py:71 apps/users/views/login.py:72\nmsgid \"Get captcha\"\nmsgstr \"獲取驗證碼\"\n\n#: apps/chat/views/chat.py:134\n#: apps/common/constants/permission_constants.py:210\n#: apps/users/views/login.py:41 apps/users/views/login.py:58\n#: apps/users/views/login.py:73 apps/users/views/user.py:63\n#: apps/users/views/user.py:77 apps/users/views/user.py:91\n#: apps/users/views/user.py:108 apps/users/views/user.py:123\n#: apps/users/views/user.py:136 apps/users/views/user.py:150\n#: apps/users/views/user.py:164 apps/users/views/user.py:180\n#: apps/users/views/user.py:193 apps/users/views/user.py:206\n#: apps/users/views/user.py:217 apps/users/views/user.py:235\n#: apps/users/views/user.py:251 apps/users/views/user.py:269\n#: apps/users/views/user.py:286 apps/users/views/user.py:303\n#: apps/users/views/user.py:321 apps/users/views/user.py:338\n#: apps/users/views/user.py:356 apps/xpack/views/chat_user_auth.py:206\nmsgid \"User Management\"\nmsgstr \"用戶管理\"\n\n#: apps/chat/views/chat_embed.py:22 apps/chat/views/chat_embed.py:23\n#: apps/chat/views/chat_embed.py:24\nmsgid \"Get embedded js\"\nmsgstr \"獲取嵌入式 JavaScript\"\n\n#: apps/common/auth/authenticate.py:80\nmsgid \"Not logged in, please log in first\"\nmsgstr \"未登錄，請先登錄\"\n\n#: apps/common/auth/authenticate.py:82 apps/common/auth/authenticate.py:89\n#: apps/common/auth/authenticate.py:95\nmsgid \"Authentication information is incorrect! illegal user\"\nmsgstr \"身份驗證信息不正確！非法用戶\"\n\n#: apps/common/auth/authentication.py:98\nmsgid \"No permission to access\"\nmsgstr \"無權限訪問\"\n\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:39\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:41\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:43\n#: apps/common/auth/handle/impl/chat_anonymous_user_token.py:49\n#: apps/xpack/auth/chat_user_token.py:41 apps/xpack/auth/chat_user_token.py:43\n#: apps/xpack/auth/chat_user_token.py:45 apps/xpack/auth/chat_user_token.py:49\nmsgid \"Authentication information is incorrect\"\nmsgstr \"身份驗證信息不正確\"\n\n#: apps/common/auth/handle/impl/user_token.py:265\nmsgid \"Login expired\"\nmsgstr \"登錄已過期\"\n\n#: apps/common/constants/exception_code_constants.py:31\n#: apps/users/serializers/login.py:53\n#: apps/xpack/serializers/chat_user_serializer.py:123\nmsgid \"The username or password is incorrect\"\nmsgstr \"用戶名或密碼不正確\"\n\n#: apps/common/constants/exception_code_constants.py:32\nmsgid \"Please log in first and bring the user Token\"\nmsgstr \"請先登錄並攜帶用戶 Token\"\n\n#: apps/common/constants/exception_code_constants.py:33\n#: apps/users/serializers/user.py:630\nmsgid \"Email sending failed\"\nmsgstr \"郵件發送失敗\"\n\n#: apps/common/constants/exception_code_constants.py:34\nmsgid \"Email format error\"\nmsgstr \"郵箱格式錯誤\"\n\n#: apps/common/constants/exception_code_constants.py:35\nmsgid \"The email has been registered, please log in directly\"\nmsgstr \"該郵箱已註冊，請直接登錄\"\n\n#: apps/common/constants/exception_code_constants.py:36\nmsgid \"The email is not registered, please register first\"\nmsgstr \"該郵箱未註冊，請先註冊\"\n\n#: apps/common/constants/exception_code_constants.py:38\nmsgid \"The verification code is incorrect or the verification code has expired\"\nmsgstr \"驗證碼不正確或已過期\"\n\n#: apps/common/constants/exception_code_constants.py:39\nmsgid \"The username has been registered, please log in directly\"\nmsgstr \"用戶名已註冊，請直接登錄\"\n\n#: apps/common/constants/exception_code_constants.py:41\nmsgid \"\"\n\"The username cannot be empty and must be between 6 and 20 characters long.\"\nmsgstr \"用戶名不能為空，且長度在6到20個字符之間。\"\n\n#: apps/common/constants/exception_code_constants.py:43\nmsgid \"Password and confirmation password are inconsistent\"\nmsgstr \"密碼和確認密碼不一致\"\n\n#: apps/common/constants/exception_code_constants.py:44\nmsgid \"The nickname is already registered\"\nmsgstr \"暱稱已註冊\"\n\n#: apps/common/constants/permission_constants.py:209\nmsgid \"System Setting\"\nmsgstr \"系統設置\"\n\n#: apps/common/constants/permission_constants.py:211\n#: apps/common/constants/permission_constants.py:272\n#: apps/role_setting/views/role_setting.py:44\n#: apps/role_setting/views/role_setting.py:67\n#: apps/role_setting/views/role_setting.py:84\n#: apps/role_setting/views/role_setting.py:103\n#: apps/role_setting/views/role_setting.py:125\n#: apps/role_setting/views/role_setting.py:145\n#: apps/role_setting/views/role_setting.py:167\n#: apps/role_setting/views/role_setting.py:191\n#: apps/role_setting/views/role_setting.py:210\nmsgid \"Role\"\nmsgstr \"角色\"\n\n#: apps/common/constants/permission_constants.py:212\n#: apps/common/constants/permission_constants.py:270\n#: apps/workspace/views/workspace.py:37 apps/workspace/views/workspace.py:49\n#: apps/workspace/views/workspace.py:63 apps/workspace/views/workspace.py:80\n#: apps/workspace/views/workspace.py:101 apps/workspace/views/workspace.py:119\n#: apps/workspace/views/workspace.py:138 apps/workspace/views/workspace.py:155\n#: apps/workspace/views/workspace.py:170 apps/workspace/views/workspace.py:188\n#: apps/workspace/views/workspace.py:207 apps/workspace/views/workspace.py:223\n#: apps/workspace/views/workspace.py:236 apps/workspace/views/workspace.py:250\nmsgid \"Workspace\"\nmsgstr \"工作空間\"\n\n#: apps/common/constants/permission_constants.py:213\nmsgid \"Resource Application\"\nmsgstr \"資源管理-智能體\"\n\n#: apps/common/constants/permission_constants.py:214\nmsgid \"Resource Knowledge\"\nmsgstr \"資源管理-知識庫\"\n\n#: apps/common/constants/permission_constants.py:215\nmsgid \"Resource Tool\"\nmsgstr \"資源管理-工具\"\n\n#: apps/common/constants/permission_constants.py:216\nmsgid \"Resource Model\"\nmsgstr \"資源管理-模型\"\n\n#: apps/common/constants/permission_constants.py:217\nmsgid \"Resource Permission\"\nmsgstr \"資源授權\"\n\n#: apps/common/constants/permission_constants.py:218\n#: apps/shared/views/shared_dataset_lark_views.py:30\n#: apps/shared/views/shared_dataset_lark_views.py:50\n#: apps/shared/views/shared_knowledge.py:33\n#: apps/shared/views/shared_knowledge.py:53\n#: apps/shared/views/shared_knowledge.py:76\n#: apps/shared/views/shared_knowledge.py:91\n#: apps/shared/views/shared_knowledge.py:106\n#: apps/shared/views/shared_knowledge.py:125\n#: apps/shared/views/shared_knowledge.py:151\n#: apps/shared/views/shared_knowledge.py:178\n#: apps/shared/views/shared_knowledge.py:196\n#: apps/shared/views/shared_knowledge.py:214\n#: apps/shared/views/shared_knowledge.py:235\n#: apps/shared/views/shared_knowledge.py:256\n#: apps/shared/views/shared_knowledge.py:276\n#: apps/shared/views/shared_knowledge.py:297\n#: apps/shared/views/shared_knowledge.py:312\n#: apps/shared/views/shared_knowledge.py:331\n#: apps/shared/views/shared_knowledge.py:354\n#: apps/shared/views/shared_knowledge.py:386\n#: apps/shared/views/shared_knowledge.py:407\nmsgid \"Shared Knowledge\"\nmsgstr \"共享資源-知識庫\"\n\n#: apps/common/constants/permission_constants.py:219\n#: apps/models_provider/views/model.py:227 apps/shared/views/shared_model.py:58\n#: apps/shared/views/shared_model.py:89 apps/shared/views/shared_model.py:107\n#: apps/shared/views/shared_model.py:124 apps/shared/views/shared_model.py:138\n#: apps/shared/views/shared_model.py:153 apps/shared/views/shared_model.py:166\n#: apps/shared/views/shared_model.py:186 apps/shared/views/shared_model.py:202\n#: apps/shared/views/shared_model.py:219 apps/shared/views/shared_model.py:234\n#: apps/shared/views/shared_model.py:253 apps/shared/views/shared_model.py:270\nmsgid \"Shared Model\"\nmsgstr \"共享資源-模型\"\n\n#: apps/common/constants/permission_constants.py:220\n#: apps/shared/views/shared_tool.py:30 apps/shared/views/shared_tool.py:49\n#: apps/shared/views/shared_tool.py:68 apps/shared/views/shared_tool.py:83\n#: apps/shared/views/shared_tool.py:98 apps/shared/views/shared_tool.py:116\n#: apps/shared/views/shared_tool.py:139 apps/shared/views/shared_tool.py:157\n#: apps/shared/views/shared_tool.py:175 apps/shared/views/shared_tool.py:194\n#: apps/shared/views/shared_tool.py:218 apps/shared/views/shared_tool.py:239\n#: apps/shared/views/shared_tool.py:254 apps/shared/views/shared_tool.py:273\n#: apps/shared/views/shared_tool.py:294\nmsgid \"Shared Tool\"\nmsgstr \"共享資源-工具\"\n\n#: apps/common/constants/permission_constants.py:221\nmsgid \"Operation Log\"\nmsgstr \"操作日誌\"\n\n#: apps/common/constants/permission_constants.py:223\nmsgid \"System Management\"\nmsgstr \"系統管理\"\n\n#: apps/common/constants/permission_constants.py:225\n#: apps/common/constants/permission_constants.py:235\n#: apps/common/constants/permission_constants.py:260\n#: apps/common/constants/permission_constants.py:265\nmsgid \"Knowledge\"\nmsgstr \"知識庫\"\n\n#: apps/common/constants/permission_constants.py:227\n#: apps/common/constants/permission_constants.py:258\n#: apps/common/constants/permission_constants.py:263\n#: apps/tools/views/tool.py:39 apps/tools/views/tool.py:61\n#: apps/tools/views/tool.py:82 apps/tools/views/tool.py:104\n#: apps/tools/views/tool.py:127 apps/tools/views/tool.py:146\n#: apps/tools/views/tool.py:172 apps/tools/views/tool.py:201\n#: apps/tools/views/tool.py:223 apps/tools/views/tool.py:250\n#: apps/tools/views/tool.py:274\nmsgid \"Tool\"\nmsgstr \"工具\"\n\n#: apps/common/constants/permission_constants.py:229\nmsgid \"Read\"\nmsgstr \"查看\"\n\n#: apps/common/constants/permission_constants.py:230\nmsgid \"Edit\"\nmsgstr \"編輯\"\n\n#: apps/common/constants/permission_constants.py:231\nmsgid \"Create\"\nmsgstr \"創建\"\n\n#: apps/common/constants/permission_constants.py:232\nmsgid \"Delete\"\nmsgstr \"刪除\"\n\n#: apps/common/constants/permission_constants.py:233\nmsgid \"Email Setting\"\nmsgstr \"郵箱設置\"\n\n#: apps/common/constants/permission_constants.py:236\n#: apps/common/constants/permission_constants.py:261\n#: apps/common/constants/permission_constants.py:266\nmsgid \"Document\"\nmsgstr \"文檔\"\n\n#: apps/common/constants/permission_constants.py:237\n#: apps/common/constants/permission_constants.py:262\n#: apps/common/constants/permission_constants.py:267\nmsgid \"Problem\"\nmsgstr \"問題\"\n\n#: apps/common/constants/permission_constants.py:238\nmsgid \"Import\"\nmsgstr \"導入\"\n\n#: apps/common/constants/permission_constants.py:239\nmsgid \"Export\"\nmsgstr \"導出\"\n\n#: apps/common/constants/permission_constants.py:241\nmsgid \"Sync\"\nmsgstr \"同步\"\n\n#: apps/common/constants/permission_constants.py:242\nmsgid \"Generate\"\nmsgstr \"生成問題\"\n\n#: apps/common/constants/permission_constants.py:243\nmsgid \"Add Member\"\nmsgstr \"添加成員\"\n\n#: apps/common/constants/permission_constants.py:244\nmsgid \"Remove Member\"\nmsgstr \"移除成員\"\n\n#: apps/common/constants/permission_constants.py:245\nmsgid \"Vector\"\nmsgstr \"向量化\"\n\n#: apps/common/constants/permission_constants.py:246\nmsgid \"Migrate\"\nmsgstr \"遷移\"\n\n#: apps/common/constants/permission_constants.py:247\nmsgid \"Relate\"\nmsgstr \"關聯分段\"\n\n#: apps/common/constants/permission_constants.py:249\nmsgid \"Clear Policy\"\nmsgstr \"清除策略\"\n\n#: apps/common/constants/permission_constants.py:250\nmsgid \"Login Auth\"\nmsgstr \"登錄認證\"\n\n#: apps/common/constants/permission_constants.py:251\nmsgid \"Display Settings\"\nmsgstr \"顯示設置\"\n\n#: apps/common/constants/permission_constants.py:252\n#: apps/common/constants/permission_constants.py:720\n#: apps/xpack/views/system_api_key.py:23 apps/xpack/views/system_api_key.py:38\n#: apps/xpack/views/system_api_key.py:56 apps/xpack/views/system_api_key.py:71\nmsgid \"System API Key\"\nmsgstr \"系統 API Key\"\n\n#: apps/common/constants/permission_constants.py:253\n#: apps/xpack/views/system_params.py:26 apps/xpack/views/system_params.py:42\nmsgid \"Appearance Settings\"\nmsgstr \"外觀設置\"\n\n#: apps/common/constants/permission_constants.py:254\n#: apps/common/constants/permission_constants.py:269\n#: apps/xpack/views/system_chat_user.py:339\n#: apps/xpack/views/system_chat_user.py:362\nmsgid \"Chat User\"\nmsgstr \"對話用戶\"\n\n#: apps/common/constants/permission_constants.py:255\n#: apps/common/constants/permission_constants.py:268\nmsgid \"User Group\"\nmsgstr \"用戶組\"\n\n#: apps/common/constants/permission_constants.py:256\nmsgid \"Chat User Auth\"\nmsgstr \"對話用戶認證\"\n\n#: apps/common/constants/permission_constants.py:257\nmsgid \"Overview\"\nmsgstr \"概覽\"\n\n#: apps/common/constants/permission_constants.py:271\n#: apps/common/constants/permission_constants.py:671\n#: apps/common/constants/permission_constants.py:677\n#: apps/common/constants/permission_constants.py:683\n#: apps/common/constants/permission_constants.py:689\nmsgid \"Dialogue log\"\nmsgstr \"對話日誌\"\n\n#: apps/common/constants/permission_constants.py:641\nmsgid \"Embed third party\"\nmsgstr \"嵌入第三方\"\n\n#: apps/common/constants/permission_constants.py:647\nmsgid \"Access restrictions\"\nmsgstr \"訪問限制\"\n\n#: apps/common/constants/permission_constants.py:653\nmsgid \"Display settings\"\nmsgstr \"顯示設置\"\n\n#: apps/common/constants/permission_constants.py:659\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:44\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:16\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:75\nmsgid \"API Key\"\nmsgstr \"\"\n\n#: apps/common/constants/permission_constants.py:665\nmsgid \"Public settings\"\nmsgstr \"公共訪問連接\"\n\n#: apps/common/constants/permission_constants.py:704\nmsgid \"About\"\nmsgstr \"關於\"\n\n#: apps/common/constants/permission_constants.py:709\n#: apps/users/views/user.py:88 apps/users/views/user.py:89\n#: apps/users/views/user.py:90\nmsgid \"Switch Language\"\nmsgstr \"切換語言\"\n\n#: apps/common/constants/permission_constants.py:714\nmsgid \"Change Password\"\nmsgstr \"修改密碼\"\n\n#: apps/common/constants/permission_constants.py:734\nmsgid \"Sync users\"\nmsgstr \"同步用戶\"\n\n#: apps/common/constants/permission_constants.py:755\n#: apps/common/constants/permission_constants.py:808\nmsgid \"Set up user groups\"\nmsgstr \"設置用戶組\"\n\n#: apps/common/event/__init__.py:27\nmsgid \"The download process was interrupted, please try again\"\nmsgstr \"下載過程被中斷，請重試\"\n\n#: apps/common/event/listener_manage.py:90\n#, python-brace-format\nmsgid \"Query vector data: {paragraph_id_list} error {error} {traceback}\"\nmsgstr \"查詢向量數據：{paragraph_id_list} 錯誤：{error} {traceback}\"\n\n#: apps/common/event/listener_manage.py:95\n#, python-brace-format\nmsgid \"Start--->Embedding paragraph: {paragraph_id_list}\"\nmsgstr \"開始--->向量段落: {paragraph_id_list}\"\n\n#: apps/common/event/listener_manage.py:107\n#, python-brace-format\nmsgid \"Vectorized paragraph: {paragraph_id_list} error {error} {traceback}\"\nmsgstr \"向量段落: {paragraph_id_list} 錯誤：{error} {traceback}\"\n\n#: apps/common/event/listener_manage.py:113\n#, python-brace-format\nmsgid \"End--->Embedding paragraph: {paragraph_id_list}\"\nmsgstr \"結束--->向量段落: {paragraph_id_list}\"\n\n#: apps/common/event/listener_manage.py:122\n#, python-brace-format\nmsgid \"Start--->Embedding paragraph: {paragraph_id}\"\nmsgstr \"開始--->向量段落: {paragraph_id}\"\n\n#: apps/common/event/listener_manage.py:147\n#, python-brace-format\nmsgid \"Vectorized paragraph: {paragraph_id} error {error} {traceback}\"\nmsgstr \"向量段落: {paragraph_id} 錯誤：{error} {traceback}\"\n\n#: apps/common/event/listener_manage.py:152\n#, python-brace-format\nmsgid \"End--->Embedding paragraph: {paragraph_id}\"\nmsgstr \"結束--->向量段落: {paragraph_id}\"\n\n#: apps/common/event/listener_manage.py:268\n#, python-brace-format\nmsgid \"Start--->Embedding document: {document_id}\"\nmsgstr \"開始--->向量文檔: {document_id}\"\n\n#: apps/common/event/listener_manage.py:288\n#, python-brace-format\nmsgid \"Vectorized document: {document_id} error {error} {traceback}\"\nmsgstr \"向量文檔: {document_id} 錯誤：{error} {traceback}\"\n\n#: apps/common/event/listener_manage.py:293\n#, python-brace-format\nmsgid \"End--->Embedding document: {document_id}\"\nmsgstr \"結束--->向量文檔: {document_id}\"\n\n#: apps/common/event/listener_manage.py:304\n#, python-brace-format\nmsgid \"Start--->Embedding knowledge: {knowledge_id}\"\nmsgstr \"開始--->向量知識庫: {knowledge_id}\"\n\n#: apps/common/event/listener_manage.py:308\n#, python-brace-format\nmsgid \"Start--->Embedding document: {document_list}\"\nmsgstr \"開始--->向量文檔: {document_list}\"\n\n#: apps/common/event/listener_manage.py:312\n#: apps/knowledge/task/embedding.py:116\n#, python-brace-format\nmsgid \"Vectorized knowledge: {knowledge_id} error {error} {traceback}\"\nmsgstr \"向量知識庫: {knowledge_id} 錯誤：{error} {traceback}\"\n\n#: apps/common/event/listener_manage.py:315\n#, python-brace-format\nmsgid \"End--->Embedding knowledge: {knowledge_id}\"\nmsgstr \"結束--->向量知識庫: {knowledge_id}\"\n\n#: apps/common/exception/handle_exception.py:32\n#: apps/common/handle/handle_exception.py:33\nmsgid \"Unknown exception\"\nmsgstr \"未知錯誤\"\n\n#: apps/common/field/common.py:48\nmsgid \"not a function\"\nmsgstr \"不是函數\"\n\n#: apps/common/forms/base_field.py:64\n#, python-brace-format\nmsgid \"The field {field_label} is required\"\nmsgstr \"{field_label} 欄位是必填項\"\n\n#: apps/common/forms/slider_field.py:56\n#, python-brace-format\nmsgid \"The {field_label} cannot be less than {min}\"\nmsgstr \"{field_label} 不能小於{min}\"\n\n#: apps/common/forms/slider_field.py:62\n#, python-brace-format\nmsgid \"The {field_label} cannot be greater than {max}\"\nmsgstr \"{field_label} 不能大於{max}\"\n\n#: apps/common/handle/impl/text/pdf_split_handle.py:281\n#, python-brace-format\nmsgid \"This document has no preface and is treated as ordinary text: {e}\"\nmsgstr \"該文檔沒有前言，視為普通文本: {e}\"\n\n#: apps/common/job/clean_chat_job.py:23\nmsgid \"start clean chat log\"\nmsgstr \"開始清理聊天日誌\"\n\n#: apps/common/job/clean_chat_job.py:69\nmsgid \"end clean chat log\"\nmsgstr \"結束清理聊天日誌\"\n\n#: apps/common/job/clean_debug_file_job.py:21\nmsgid \"start clean debug file\"\nmsgstr \"開始清理調試文件\"\n\n#: apps/common/job/clean_debug_file_job.py:25\nmsgid \"end clean debug file\"\nmsgstr \"結束清理調試文件\"\n\n#: apps/common/result/api.py:17 apps/common/result/api.py:27\nmsgid \"response code\"\nmsgstr \"響應碼\"\n\n#: apps/common/result/api.py:18 apps/common/result/api.py:19\n#: apps/common/result/api.py:28 apps/common/result/api.py:29\nmsgid \"error prompt\"\nmsgstr \"錯誤提示\"\n\n#: apps/common/result/api.py:43\nmsgid \"total number of data\"\nmsgstr \"總數據\"\n\n#: apps/common/result/api.py:44\nmsgid \"current page\"\nmsgstr \"當前頁\"\n\n#: apps/common/result/api.py:45\nmsgid \"page size\"\nmsgstr \"每頁大小\"\n\n#: apps/common/result/result.py:31\n#: apps/xpack/serializers/operate_log_serializer.py:134\nmsgid \"Success\"\nmsgstr \"成功\"\n\n#: apps/common/utils/common.py:91\nmsgid \"Text-to-speech node, the text content must be of string type\"\nmsgstr \"文本轉語音節點，文本內容必須是字符串類型\"\n\n#: apps/common/utils/common.py:93\nmsgid \"Text-to-speech node, the text content cannot be empty\"\nmsgstr \"文本轉語音節點，文本內容不能為空\"\n\n#: apps/common/utils/common.py:246\n#, python-brace-format\nmsgid \"Limit {count} exceeded, please contact us (https://fit2cloud.com/).\"\nmsgstr \"超過限制 {count}，請聯繫我們 (https://fit2cloud.com/).\"\n\n#: apps/folders/models/folder.py:6 apps/folders/models/folder.py:17\n#: apps/folders/serializers/folder.py:98\nmsgid \"folder name\"\nmsgstr \"文件夾名稱\"\n\n#: apps/folders/models/folder.py:8 apps/folders/models/folder.py:19\n#: apps/folders/serializers/folder.py:99\nmsgid \"folder description\"\nmsgstr \"文件夾描述\"\n\n#: apps/folders/models/folder.py:12 apps/folders/models/folder.py:23\n#: apps/folders/serializers/folder.py:102\nmsgid \"parent id\"\nmsgstr \"父級 ID\"\n\n#: apps/folders/serializers/folder.py:75\nmsgid \"Folder depth cannot exceed 5 levels\"\nmsgstr \"文件夾深度不能超過5級\"\n\n#: apps/folders/serializers/folder.py:100\nmsgid \"folder user id\"\nmsgstr \"文件夾用戶 ID\"\n\n#: apps/folders/serializers/folder.py:105\n#: apps/knowledge/serializers/knowledge.py:112\n#: apps/knowledge/serializers/knowledge.py:207\n#: apps/knowledge/serializers/knowledge.py:447\n#: apps/knowledge/serializers/knowledge.py:559\n#: apps/knowledge/serializers/knowledge.py:637\n#: apps/models_provider/serializers/model_serializer.py:108\n#: apps/models_provider/serializers/model_serializer.py:212\n#: apps/models_provider/serializers/model_serializer.py:252\n#: apps/shared/serializers/shared_knowledge.py:107\n#: apps/shared/serializers/shared_knowledge.py:156\n#: apps/shared/serializers/shared_tool.py:84\n#: apps/system_manage/serializers/user_resource_permission.py:75\n#: apps/tools/serializers/tool.py:187 apps/tools/serializers/tool.py:209\n#: apps/tools/serializers/tool.py:455 apps/users/serializers/user.py:664\n#: apps/xpack/serializers/dataset_lark_serializer.py:46\n#: apps/xpack/serializers/dataset_lark_serializer.py:285\n#: apps/xpack/serializers/system_api_key.py:23\nmsgid \"user id\"\nmsgstr \"用戶ID\"\n\n#: apps/folders/serializers/folder.py:123\nmsgid \"Folder name already exists\"\nmsgstr \"文件夾名稱已存在\"\n\n#: apps/folders/serializers/folder.py:150\n#: apps/folders/serializers/folder.py:182\nmsgid \"Folder does not exist\"\nmsgstr \"文件夾不存在\"\n\n#: apps/folders/serializers/folder.py:184\nmsgid \"Cannot delete root folder\"\nmsgstr \"無法刪除根文件夾\"\n\n#: apps/folders/views/folder.py:31 apps/folders/views/folder.py:32\n#: apps/folders/views/folder.py:33\nmsgid \"Create folder\"\nmsgstr \"創建文件夾\"\n\n#: apps/folders/views/folder.py:37 apps/folders/views/folder.py:63\n#: apps/folders/views/folder.py:86 apps/folders/views/folder.py:110\n#: apps/folders/views/folder.py:129\nmsgid \"Folder\"\nmsgstr \"文件夾\"\n\n#: apps/folders/views/folder.py:58 apps/folders/views/folder.py:59\n#: apps/folders/views/folder.py:60\nmsgid \"Get folder tree\"\nmsgstr \"獲取文件夾樹\"\n\n#: apps/folders/views/folder.py:80 apps/folders/views/folder.py:81\n#: apps/folders/views/folder.py:82\nmsgid \"Update folder\"\nmsgstr \"更新文件夾\"\n\n#: apps/folders/views/folder.py:105 apps/folders/views/folder.py:106\n#: apps/folders/views/folder.py:107\nmsgid \"Get folder\"\nmsgstr \"獲取文件夾\"\n\n#: apps/folders/views/folder.py:124 apps/folders/views/folder.py:125\n#: apps/folders/views/folder.py:126\nmsgid \"Delete folder\"\nmsgstr \"刪除文件夾\"\n\n#: apps/knowledge/api/problem.py:39 apps/knowledge/api/problem.py:52\n#: apps/knowledge/serializers/problem.py:40\nmsgid \"problem list\"\nmsgstr \"問題列表\"\n\n#: apps/knowledge/api/problem.py:40 apps/knowledge/api/problem.py:53\n#: apps/knowledge/serializers/problem.py:41\nmsgid \"problem\"\nmsgstr \"問題 ID\"\n\n#: apps/knowledge/serializers/common.py:32\n#: apps/knowledge/serializers/knowledge.py:62\n#: apps/shared/serializers/shared_knowledge.py:29\nmsgid \"source url\"\nmsgstr \"來源\"\n\n#: apps/knowledge/serializers/common.py:33\n#: apps/knowledge/serializers/document.py:152\nmsgid \"selector\"\nmsgstr \"選擇器\"\n\n#: apps/knowledge/serializers/common.py:40\n#, python-brace-format\nmsgid \"URL error, cannot parse [{source_url}]\"\nmsgstr \"URL 錯誤，無法解析 [{source_url}]\"\n\n#: apps/knowledge/serializers/common.py:48\n#: apps/knowledge/serializers/document.py:78\n#: apps/knowledge/serializers/document.py:170\n#: apps/knowledge/serializers/document.py:186\nmsgid \"id list\"\nmsgstr \"ID 列表\"\n\n#: apps/knowledge/serializers/common.py:58\n#, python-brace-format\nmsgid \"The following id does not exist: {error_id_list}\"\nmsgstr \"以下ID不存在: {error_id_list}\"\n\n#: apps/knowledge/serializers/common.py:74\n#: apps/knowledge/serializers/document.py:166\n#: apps/knowledge/serializers/document.py:171\n#: apps/knowledge/serializers/document.py:178\nmsgid \"state list\"\nmsgstr \"狀態列表\"\n\n#: apps/knowledge/serializers/common.py:117\n#: apps/knowledge/serializers/common.py:141\nmsgid \"The knowledge base is inconsistent with the vector model\"\nmsgstr \"知識庫與向量模型不一致\"\n\n#: apps/knowledge/serializers/common.py:119\n#: apps/knowledge/serializers/common.py:143\nmsgid \"Knowledge base setting error, please reset the knowledge base\"\nmsgstr \"知識庫設置錯誤，請重置知識庫\"\n\n#: apps/knowledge/serializers/document.py:79\n#: apps/knowledge/serializers/document.py:97\n#: apps/knowledge/serializers/document.py:353\nmsgid \"task type\"\nmsgstr \"任務類型\"\n\n#: apps/knowledge/serializers/document.py:87\n#: apps/knowledge/serializers/document.py:105\nmsgid \"task type not support\"\nmsgstr \"任務類型不支持\"\n\n#: apps/knowledge/serializers/document.py:91\n#: apps/knowledge/serializers/document.py:110\n#: apps/knowledge/serializers/document.py:350\nmsgid \"document name\"\nmsgstr \"文檔名稱\"\n\n#: apps/knowledge/serializers/document.py:93\nmsgid \"source file id\"\nmsgstr \"源文件 ID\"\n\n#: apps/knowledge/serializers/document.py:113\n#: apps/knowledge/serializers/document.py:194\nmsgid \"The type only supports optimization|directly_return\"\nmsgstr \"該類型僅支持優化|直接返回\"\n\n#: apps/knowledge/serializers/document.py:115\n#: apps/knowledge/serializers/document.py:187\n#: apps/knowledge/serializers/document.py:351\nmsgid \"hit handling method\"\nmsgstr \"命中處理方法\"\n\n#: apps/knowledge/serializers/document.py:118\n#: apps/knowledge/serializers/document.py:189\nmsgid \"directly return similarity\"\nmsgstr \"直接返回相似度\"\n\n#: apps/knowledge/serializers/document.py:120\n#: apps/knowledge/serializers/document.py:352\nmsgid \"document is active\"\nmsgstr \"文檔已激活\"\n\n#: apps/knowledge/serializers/document.py:139\n#: apps/knowledge/serializers/document.py:156\n#: apps/knowledge/serializers/document.py:161\nmsgid \"file list\"\nmsgstr \"文件 列表\"\n\n#: apps/knowledge/serializers/document.py:140\nmsgid \"limit\"\nmsgstr \"限制\"\n\n#: apps/knowledge/serializers/document.py:143\n#: apps/knowledge/serializers/document.py:144\nmsgid \"patterns\"\nmsgstr \"分割符\"\n\n#: apps/knowledge/serializers/document.py:146\nmsgid \"Auto Clean\"\nmsgstr \"自動清理\"\n\n#: apps/knowledge/serializers/document.py:150\n#: apps/knowledge/serializers/document.py:151\nmsgid \"document url list\"\nmsgstr \"文檔 URL 列表\"\n\n#: apps/knowledge/serializers/document.py:175\n#: apps/knowledge/serializers/document.py:182\nmsgid \"document id list\"\nmsgstr \"文檔 ID 列表\"\n\n#: apps/knowledge/serializers/document.py:176\n#: apps/knowledge/serializers/paragraph.py:58\n#: apps/models_provider/api/model.py:105\n#: apps/models_provider/serializers/model_apply_serializers.py:51\n#: apps/models_provider/serializers/model_serializer.py:107\n#: apps/models_provider/serializers/model_serializer.py:364\n#: apps/shared/api/shared_model.py:61\n#: apps/shared/serializers/shared_model.py:54\nmsgid \"model id\"\nmsgstr \"模型ID\"\n\n#: apps/knowledge/serializers/document.py:177\n#: apps/knowledge/serializers/paragraph.py:59\nmsgid \"prompt\"\nmsgstr \"提示詞\"\n\n#: apps/knowledge/serializers/document.py:201\nmsgid \"The template type only supports excel|csv\"\nmsgstr \"模板類型僅支持 excel|csv\"\n\n#: apps/knowledge/serializers/document.py:254\n#: apps/knowledge/serializers/document.py:348\n#: apps/knowledge/serializers/document.py:409\n#: apps/knowledge/serializers/document.py:504\n#: apps/knowledge/serializers/document.py:889\n#: apps/knowledge/serializers/document.py:964\n#: apps/knowledge/serializers/document.py:984\n#: apps/knowledge/serializers/document.py:1167\n#: apps/knowledge/serializers/knowledge.py:209\n#: apps/knowledge/serializers/knowledge.py:558\n#: apps/knowledge/serializers/paragraph.py:70\n#: apps/knowledge/serializers/paragraph.py:138\n#: apps/knowledge/serializers/paragraph.py:239\n#: apps/knowledge/serializers/paragraph.py:321\n#: apps/knowledge/serializers/paragraph.py:347\n#: apps/knowledge/serializers/paragraph.py:398\n#: apps/knowledge/serializers/paragraph.py:439\n#: apps/knowledge/serializers/paragraph.py:559\n#: apps/knowledge/serializers/problem.py:62\n#: apps/knowledge/serializers/problem.py:126\n#: apps/knowledge/serializers/problem.py:177\n#: apps/knowledge/serializers/problem.py:205\n#: apps/shared/api/shared_knowledge.py:196\n#: apps/shared/api/shared_knowledge.py:218\n#: apps/shared/serializers/shared_knowledge.py:158\n#: apps/shared/serializers/shared_knowledge.py:205\n#: apps/xpack/serializers/dataset_lark_serializer.py:104\n#: apps/xpack/serializers/dataset_lark_serializer.py:263\n#: apps/xpack/serializers/dataset_lark_serializer.py:284\nmsgid \"knowledge id\"\nmsgstr \"知識庫 ID\"\n\n#: apps/knowledge/serializers/document.py:255\n#: apps/knowledge/serializers/paragraph.py:441\nmsgid \"target knowledge id\"\nmsgstr \"當前知識庫 ID\"\n\n#: apps/knowledge/serializers/document.py:256\nmsgid \"document list\"\nmsgstr \"文檔列表\"\n\n#: apps/knowledge/serializers/document.py:257\n#: apps/knowledge/serializers/document.py:410\n#: apps/knowledge/serializers/document.py:503\n#: apps/knowledge/serializers/document.py:737\n#: apps/knowledge/serializers/paragraph.py:61\n#: apps/knowledge/serializers/paragraph.py:71\n#: apps/knowledge/serializers/paragraph.py:140\n#: apps/knowledge/serializers/paragraph.py:240\n#: apps/knowledge/serializers/paragraph.py:322\n#: apps/knowledge/serializers/paragraph.py:349\n#: apps/knowledge/serializers/paragraph.py:399\n#: apps/knowledge/serializers/paragraph.py:440\n#: apps/knowledge/serializers/paragraph.py:560\n#: apps/knowledge/serializers/problem.py:36\n#: apps/knowledge/serializers/problem.py:51\n#: apps/xpack/serializers/dataset_lark_serializer.py:160\nmsgid \"document id\"\nmsgstr \"文檔 ID\"\n\n#: apps/knowledge/serializers/document.py:354 apps/xpack/api/license.py:25\n#: apps/xpack/serializers/operate_log_serializer.py:60\n#: apps/xpack/serializers/operate_log_serializer.py:174\nmsgid \"status\"\nmsgstr \"狀態\"\n\n#: apps/knowledge/serializers/document.py:355\nmsgid \"order by\"\nmsgstr \"排序\"\n\n#: apps/knowledge/serializers/document.py:417\n#: apps/knowledge/serializers/document.py:510\n#: apps/xpack/serializers/dataset_lark_serializer.py:167\n#: apps/xpack/serializers/dataset_lark_serializer.py:189\nmsgid \"document id not exist\"\nmsgstr \"文檔 ID 不存在\"\n\n#: apps/knowledge/serializers/document.py:419\n#: apps/knowledge/serializers/knowledge.py:570\nmsgid \"Synchronization is only supported for web site types\"\nmsgstr \"僅支持網站類型的同步\"\n\n#: apps/knowledge/serializers/document.py:661\nmsgid \"The task is being executed, please do not send it repeatedly.\"\nmsgstr \"任務正在執行，請勿重複發送。\"\n\n#: apps/knowledge/serializers/document.py:674\nmsgid \"Section title (optional)\"\nmsgstr \"章節標題\"\n\n#: apps/knowledge/serializers/document.py:675\nmsgid \"\"\n\"Section content (required, question answer, no more than 4096 characters)\"\nmsgstr \"章節內容（必填，問答，不超過4096個字符）\"\n\n#: apps/knowledge/serializers/document.py:676\nmsgid \"Question (optional, one per line in the cell)\"\nmsgstr \"問題（可選，每個單元格一行）\"\n\n#: apps/knowledge/serializers/document.py:742\nmsgid \"knowledge id not exist\"\nmsgstr \"知識庫 ID 不存在\"\n\n#: apps/knowledge/serializers/document.py:898\nmsgid \"The maximum size of the uploaded file cannot exceed {}MB\"\nmsgstr \"上傳文件的最大大小不能超過 {}MB\"\n\n#: apps/knowledge/serializers/document.py:976\nmsgid \"space\"\nmsgstr \"空格\"\n\n#: apps/knowledge/serializers/document.py:977\nmsgid \"semicolon\"\nmsgstr \"分號\"\n\n#: apps/knowledge/serializers/document.py:977\nmsgid \"comma\"\nmsgstr \"逗號\"\n\n#: apps/knowledge/serializers/document.py:978\nmsgid \"period\"\nmsgstr \"句號\"\n\n#: apps/knowledge/serializers/document.py:978\nmsgid \"enter\"\nmsgstr \"回車\"\n\n#: apps/knowledge/serializers/document.py:979\nmsgid \"blank line\"\nmsgstr \"空行\"\n\n#: apps/knowledge/serializers/document.py:1140\nmsgid \"Hit handling method is required\"\nmsgstr \"命中處理方法是必需的\"\n\n#: apps/knowledge/serializers/document.py:1142\nmsgid \"The hit processing method must be directly_return|optimization\"\nmsgstr \"命中處理方法必須是直接返回|優化\"\n\n#: apps/knowledge/serializers/knowledge.py:51\n#: apps/knowledge/serializers/knowledge.py:58\n#: apps/knowledge/serializers/knowledge.py:67\n#: apps/knowledge/serializers/knowledge.py:108\n#: apps/shared/api/shared_knowledge.py:117\n#: apps/shared/api/shared_knowledge.py:150\n#: apps/shared/serializers/shared_knowledge.py:20\n#: apps/shared/serializers/shared_knowledge.py:26\n#: apps/shared/serializers/shared_knowledge.py:59\n#: apps/shared/serializers/shared_knowledge.py:106\n#: apps/xpack/serializers/dataset_lark_serializer.py:51\n#: apps/xpack/serializers/dataset_lark_serializer.py:289\nmsgid \"knowledge name\"\nmsgstr \"知識庫名稱\"\n\n#: apps/knowledge/serializers/knowledge.py:53\n#: apps/knowledge/serializers/knowledge.py:60\n#: apps/knowledge/serializers/knowledge.py:68\n#: apps/knowledge/serializers/knowledge.py:110\n#: apps/shared/api/shared_knowledge.py:124\n#: apps/shared/api/shared_knowledge.py:157\n#: apps/shared/serializers/shared_knowledge.py:21\n#: apps/shared/serializers/shared_knowledge.py:27\n#: apps/shared/serializers/shared_knowledge.py:60\n#: apps/shared/serializers/shared_knowledge.py:108\n#: apps/xpack/serializers/dataset_lark_serializer.py:53\n#: apps/xpack/serializers/dataset_lark_serializer.py:291\nmsgid \"knowledge description\"\nmsgstr \"知識庫描述\"\n\n#: apps/knowledge/serializers/knowledge.py:54\n#: apps/knowledge/serializers/knowledge.py:61\n#: apps/shared/serializers/shared_knowledge.py:22\n#: apps/shared/serializers/shared_knowledge.py:28\nmsgid \"knowledge embedding\"\nmsgstr \"知識庫向量\"\n\n#: apps/knowledge/serializers/knowledge.py:63\n#: apps/shared/serializers/shared_knowledge.py:30\nmsgid \"knowledge selector\"\nmsgstr \"知識庫選擇器\"\n\n#: apps/knowledge/serializers/knowledge.py:73\n#: apps/xpack/serializers/dataset_lark_serializer.py:296\nmsgid \"application id list\"\nmsgstr \"智能體 ID 列表\"\n\n#: apps/knowledge/serializers/knowledge.py:75\nmsgid \"file size limit\"\nmsgstr \"文件大小限制\"\n\n#: apps/knowledge/serializers/knowledge.py:76\nmsgid \"file count limit\"\nmsgstr \"文件數量限制\"\n\n#: apps/knowledge/serializers/knowledge.py:95\n#: apps/knowledge/serializers/knowledge.py:638\nmsgid \"query text\"\nmsgstr \"查詢文本\"\n\n#: apps/knowledge/serializers/knowledge.py:96\n#: apps/knowledge/serializers/knowledge.py:639\nmsgid \"top number\"\nmsgstr \"Top 數量\"\n\n#: apps/knowledge/serializers/knowledge.py:98\n#: apps/knowledge/serializers/knowledge.py:641\nmsgid \"search mode\"\nmsgstr \"搜索模式\"\n\n#: apps/knowledge/serializers/knowledge.py:113\nmsgid \"knowledge scope\"\nmsgstr \"知識庫範圍\"\n\n#: apps/knowledge/serializers/knowledge.py:169\n#: apps/tools/serializers/tool.py:434 apps/tools/serializers/tool.py:464\nmsgid \"Folder not found\"\nmsgstr \"文件夾不存在\"\n\n#: apps/knowledge/serializers/knowledge.py:236\n#: apps/knowledge/serializers/knowledge.py:265\nmsgid \"Failed to send the vectorization task, please try again later!\"\nmsgstr \"發送向量化任務失敗，請稍後再試！\"\n\n#: apps/knowledge/serializers/knowledge.py:315\n#: apps/knowledge/serializers/knowledge.py:471\n#: apps/knowledge/serializers/knowledge.py:533\n#: apps/xpack/serializers/dataset_lark_serializer.py:82\n#: apps/xpack/serializers/dataset_lark_serializer.py:340\nmsgid \"Knowledge base name duplicate!\"\nmsgstr \"知識庫名稱重複！\"\n\n#: apps/knowledge/serializers/knowledge.py:341\n#: apps/xpack/serializers/dataset_lark_serializer.py:359\n#, python-brace-format\nmsgid \"Unknown application id {knowledge_id}, cannot be associated\"\nmsgstr \"未知智能體 ID {knowledge_id}，無法關聯\"\n\n#: apps/knowledge/serializers/knowledge.py:449\n#: apps/shared/serializers/shared_knowledge.py:62\n#: apps/shared/serializers/shared_knowledge.py:110\n#: apps/shared/serializers/shared_tool.py:46\n#: apps/shared/serializers/shared_tool.py:87 apps/tools/serializers/tool.py:456\n#: apps/xpack/serializers/dataset_lark_serializer.py:47\nmsgid \"scope\"\nmsgstr \"範圍\"\n\n#: apps/knowledge/serializers/knowledge.py:460\nmsgid \"\"\n\"The community version supports up to 50 knowledge bases. If you need more \"\n\"knowledge bases, please contact us (https://fit2cloud.com/).\"\nmsgstr \"\"\n\"社區版支持最多50個知識庫，如需更多知識庫，請聯繫我們 (https://\"\n\"fit2cloud.com/).\"\n\n#: apps/knowledge/serializers/knowledge.py:560\nmsgid \"sync type\"\nmsgstr \"同步類型\"\n\n#: apps/knowledge/serializers/knowledge.py:562\nmsgid \"The synchronization type only supports:replace|complete\"\nmsgstr \"同步類型僅支持:replace|complete\"\n\n#: apps/knowledge/serializers/knowledge.py:568\n#: apps/knowledge/serializers/knowledge.py:649\nmsgid \"id does not exist\"\nmsgstr \"知識庫 ID 不存在\"\n\n#: apps/knowledge/serializers/knowledge.py:636 apps/users/api/user.py:76\nmsgid \"id\"\nmsgstr \"ID\"\n\n#: apps/knowledge/serializers/paragraph.py:39\n#: apps/knowledge/serializers/problem.py:27\n#: apps/knowledge/serializers/problem.py:31\n#: apps/knowledge/serializers/problem.py:206\nmsgid \"content\"\nmsgstr \"內容\"\n\n#: apps/knowledge/serializers/paragraph.py:41\n#: apps/knowledge/serializers/paragraph.py:48\n#: apps/knowledge/serializers/paragraph.py:51\n#: apps/knowledge/serializers/paragraph.py:65\n#: apps/knowledge/serializers/paragraph.py:67\n#: apps/knowledge/serializers/paragraph.py:323\nmsgid \"section title\"\nmsgstr \"章節標題\"\n\n#: apps/knowledge/serializers/paragraph.py:44\n#: apps/tools/serializers/tool.py:152 apps/tools/serializers/tool.py:164\n#: apps/xpack/serializers/system_api_key.py:11\nmsgid \"Is active\"\nmsgstr \"是否啟用\"\n\n#: apps/knowledge/serializers/paragraph.py:56\n#: apps/knowledge/serializers/paragraph.py:443\nmsgid \"paragraph id list\"\nmsgstr \"段落 ID 列表\"\n\n#: apps/knowledge/serializers/paragraph.py:57\n#: apps/knowledge/serializers/paragraph.py:72\n#: apps/knowledge/serializers/paragraph.py:136\n#: apps/knowledge/serializers/paragraph.py:350\n#: apps/knowledge/serializers/paragraph.py:444\n#: apps/knowledge/serializers/paragraph.py:561\n#: apps/knowledge/serializers/problem.py:35\n#: apps/knowledge/serializers/problem.py:50\nmsgid \"paragraph id\"\nmsgstr \"段落 ID\"\n\n#: apps/knowledge/serializers/paragraph.py:77\n#: apps/knowledge/serializers/paragraph.py:145\nmsgid \"Paragraph id does not exist\"\nmsgstr \"段落 ID 不存在\"\n\n#: apps/knowledge/serializers/paragraph.py:108\nmsgid \"Already associated, please do not associate again\"\nmsgstr \"已關聯，請勿再次關聯\"\n\n#: apps/knowledge/serializers/paragraph.py:181\nmsgid \"Problem id does not exist\"\nmsgstr \"問題 ID 不存在\"\n\n#: apps/knowledge/serializers/paragraph.py:348\n#: apps/knowledge/serializers/problem.py:26\n#: apps/knowledge/serializers/problem.py:46\n#: apps/knowledge/serializers/problem.py:56\n#: apps/knowledge/serializers/problem.py:127\nmsgid \"problem id\"\nmsgstr \"問題 ID\"\n\n#: apps/knowledge/serializers/paragraph.py:358\nmsgid \"Paragraph does not exist\"\nmsgstr \"段落不存在\"\n\n#: apps/knowledge/serializers/paragraph.py:360\nmsgid \"Problem does not exist\"\nmsgstr \"問題不存在\"\n\n#: apps/knowledge/serializers/paragraph.py:435\nmsgid \"The task is being executed, please do not send it again.\"\nmsgstr \"任務正在執行，請勿重複發送。\"\n\n#: apps/knowledge/serializers/paragraph.py:442\nmsgid \"target document id\"\nmsgstr \"目標文檔 ID\"\n\n#: apps/knowledge/serializers/paragraph.py:453\nmsgid \"The document to be migrated is consistent with the target document\"\nmsgstr \"遷移的文檔與目標文檔一致\"\n\n#: apps/knowledge/serializers/paragraph.py:455\nmsgid \"The document id does not exist [{document_id}]\"\nmsgstr \"以下文檔ID不存在: {error_id_list}\"\n\n#: apps/knowledge/serializers/paragraph.py:459\nmsgid \"The target document id does not exist [{document_id}]\"\nmsgstr \"以下目標文檔ID不存在: {error_id_list}\"\n\n#: apps/knowledge/serializers/paragraph.py:573\nmsgid \"new_position must be an integer\"\nmsgstr \"new_position 必須是整數\"\n\n#: apps/knowledge/serializers/problem.py:45\n#: apps/knowledge/serializers/problem.py:55\nmsgid \"problem id list\"\nmsgstr \"問題 ID 列表\"\n\n#: apps/knowledge/task/embedding.py:24 apps/knowledge/task/embedding.py:74\n#, python-brace-format\nmsgid \"Failed to obtain vector model: {error} {traceback}\"\nmsgstr \"向量模型獲取失敗: {error} {traceback}\"\n\n#: apps/knowledge/task/embedding.py:103\n#, python-brace-format\nmsgid \"Start--->Vectorized knowledge: {knowledge_id}\"\nmsgstr \"開始--->向量知識庫: {knowledge_id}\"\n\n#: apps/knowledge/task/embedding.py:107\n#, python-brace-format\nmsgid \"Knowledge documentation: {document_names}\"\nmsgstr \"知識庫文檔: {document_names}\"\n\n#: apps/knowledge/task/embedding.py:120\n#, python-brace-format\nmsgid \"End--->Vectorized knowledge: {knowledge_id}\"\nmsgstr \"結束--->向量知識庫: {knowledge_id}\"\n\n#: apps/knowledge/task/generate.py:106\n#, python-brace-format\nmsgid \"\"\n\"Generate issue based on document: {document_id} error {error}{traceback}\"\nmsgstr \"生成問題基於文檔: {document_id} 錯誤 {error}{traceback}\"\n\n#: apps/knowledge/task/generate.py:110\n#, python-brace-format\nmsgid \"End--->Generate problem: {document_id}\"\nmsgstr \"結束--->生成問題: {document_id}\"\n\n#: apps/knowledge/task/handler.py:121\n#, python-brace-format\nmsgid \"Association problem failed {error}\"\nmsgstr \"關聯問題失敗 {error}\"\n\n#: apps/knowledge/task/sync.py:30 apps/knowledge/task/sync.py:47\n#, python-brace-format\nmsgid \"Start--->Start synchronization web knowledge base:{knowledge_id}\"\nmsgstr \"開始--->開始同步 web 知識庫:{knowledge_id}\"\n\n#: apps/knowledge/task/sync.py:35 apps/knowledge/task/sync.py:51\n#, python-brace-format\nmsgid \"End--->End synchronization web knowledge base:{knowledge_id}\"\nmsgstr \"結束--->結束同步 web 知識庫:{knowledge_id}\"\n\n#: apps/knowledge/task/sync.py:37 apps/knowledge/task/sync.py:53\n#, python-brace-format\nmsgid \"Synchronize web knowledge base:{knowledge_id} error{error}{traceback}\"\nmsgstr \"同步 web 知識庫:{knowledge_id} 錯誤{error}{traceback}\"\n\n#: apps/knowledge/views/document.py:28 apps/knowledge/views/document.py:29\n#: apps/knowledge/views/document.py:30\nmsgid \"Create document\"\nmsgstr \"創建文檔\"\n\n#: apps/knowledge/views/document.py:34 apps/knowledge/views/document.py:57\n#: apps/knowledge/views/document.py:84 apps/knowledge/views/document.py:104\n#: apps/knowledge/views/document.py:128 apps/knowledge/views/document.py:160\n#: apps/knowledge/views/document.py:191 apps/knowledge/views/document.py:209\n#: apps/knowledge/views/document.py:238 apps/knowledge/views/document.py:267\n#: apps/knowledge/views/document.py:295 apps/knowledge/views/document.py:323\n#: apps/knowledge/views/document.py:352 apps/knowledge/views/document.py:382\n#: apps/knowledge/views/document.py:412 apps/knowledge/views/document.py:441\n#: apps/knowledge/views/document.py:472 apps/knowledge/views/document.py:501\n#: apps/knowledge/views/document.py:527 apps/knowledge/views/document.py:553\n#: apps/knowledge/views/document.py:579 apps/knowledge/views/document.py:599\n#: apps/knowledge/views/document.py:633 apps/knowledge/views/document.py:664\n#: apps/knowledge/views/document.py:695 apps/knowledge/views/document.py:723\n#: apps/knowledge/views/document.py:737\n#: apps/xpack/views/dataset_lark_views.py:72\n#: apps/xpack/views/dataset_lark_views.py:91\n#: apps/xpack/views/dataset_lark_views.py:111\n#: apps/xpack/views/dataset_lark_views.py:132\nmsgid \"Knowledge Base/Documentation\"\nmsgstr \"知識庫/文檔\"\n\n#: apps/knowledge/views/document.py:52 apps/knowledge/views/document.py:53\n#: apps/knowledge/views/document.py:54\nmsgid \"Get document\"\nmsgstr \"獲取文檔\"\n\n#: apps/knowledge/views/document.py:79 apps/knowledge/views/document.py:80\n#: apps/knowledge/views/document.py:81\nmsgid \"Get document details\"\nmsgstr \"文檔文檔詳情\"\n\n#: apps/knowledge/views/document.py:98 apps/knowledge/views/document.py:99\n#: apps/knowledge/views/document.py:100\nmsgid \"Modify document\"\nmsgstr \"修改文檔\"\n\n#: apps/knowledge/views/document.py:123 apps/knowledge/views/document.py:124\n#: apps/knowledge/views/document.py:125\nmsgid \"Delete document\"\nmsgstr \"刪除文檔\"\n\n#: apps/knowledge/views/document.py:154 apps/knowledge/views/document.py:155\n#: apps/knowledge/views/document.py:156\nmsgid \"Segmented document\"\nmsgstr \"分段文檔\"\n\n#: apps/knowledge/views/document.py:186 apps/knowledge/views/document.py:187\n#: apps/knowledge/views/document.py:188\nmsgid \"Get a list of segment IDs\"\nmsgstr \"獲取分段列表\"\n\n#: apps/knowledge/views/document.py:203 apps/knowledge/views/document.py:204\n#: apps/knowledge/views/document.py:205\nmsgid \"Modify document hit processing methods in batches\"\nmsgstr \"批量修改文檔命中處理方法\"\n\n#: apps/knowledge/views/document.py:232 apps/knowledge/views/document.py:233\n#: apps/knowledge/views/document.py:234\nmsgid \"Synchronize web site types\"\nmsgstr \"同步網站類型\"\n\n#: apps/knowledge/views/document.py:261 apps/knowledge/views/document.py:262\n#: apps/knowledge/views/document.py:263\nmsgid \"Refresh document vector library\"\nmsgstr \"刷新文檔向量庫\"\n\n#: apps/knowledge/views/document.py:289 apps/knowledge/views/document.py:290\n#: apps/knowledge/views/document.py:291\nmsgid \"Cancel task\"\nmsgstr \"取消任務\"\n\n#: apps/knowledge/views/document.py:317 apps/knowledge/views/document.py:318\n#: apps/knowledge/views/document.py:319\nmsgid \"Cancel tasks in batches\"\nmsgstr \"批量取消任務\"\n\n#: apps/knowledge/views/document.py:346 apps/knowledge/views/document.py:347\n#: apps/knowledge/views/document.py:348\nmsgid \"Create documents in batches\"\nmsgstr \"批量創建文檔\"\n\n#: apps/knowledge/views/document.py:376 apps/knowledge/views/document.py:377\n#: apps/knowledge/views/document.py:378\nmsgid \"Batch sync documents\"\nmsgstr \"批量同步文檔\"\n\n#: apps/knowledge/views/document.py:406 apps/knowledge/views/document.py:407\n#: apps/knowledge/views/document.py:408\nmsgid \"Delete documents in batches\"\nmsgstr \"批量刪除文檔\"\n\n#: apps/knowledge/views/document.py:436 apps/knowledge/views/document.py:437\nmsgid \"Batch refresh document vector library\"\nmsgstr \"批量刷新文檔向量庫\"\n\n#: apps/knowledge/views/document.py:466 apps/knowledge/views/document.py:467\n#: apps/knowledge/views/document.py:468\nmsgid \"Batch generate related problems\"\nmsgstr \"批量生成相關問題\"\n\n#: apps/knowledge/views/document.py:496 apps/knowledge/views/document.py:497\n#: apps/knowledge/views/document.py:498\nmsgid \"Get document by pagination\"\nmsgstr \"分頁獲取文檔\"\n\n#: apps/knowledge/views/document.py:523 apps/knowledge/views/document.py:524\nmsgid \"Export document\"\nmsgstr \"導出文檔\"\n\n#: apps/knowledge/views/document.py:549 apps/knowledge/views/document.py:550\nmsgid \"Export Zip document\"\nmsgstr \"導出 Zip 文檔\"\n\n#: apps/knowledge/views/document.py:575 apps/knowledge/views/document.py:576\nmsgid \"Download source file\"\nmsgstr \"下載源文件\"\n\n#: apps/knowledge/views/document.py:594 apps/knowledge/views/document.py:595\nmsgid \"Migrate documents in batches\"\nmsgstr \"批量遷移文檔\"\n\n#: apps/knowledge/views/document.py:627 apps/knowledge/views/document.py:628\n#: apps/knowledge/views/document.py:629\n#: apps/shared/views/shared_document.py:570\nmsgid \"Create Web site documents\"\nmsgstr \"創建網站文檔\"\n\n#: apps/knowledge/views/document.py:658 apps/knowledge/views/document.py:659\n#: apps/knowledge/views/document.py:660\nmsgid \"Import QA and create documentation\"\nmsgstr \"導入問答並創建文檔\"\n\n#: apps/knowledge/views/document.py:689 apps/knowledge/views/document.py:690\n#: apps/knowledge/views/document.py:691\nmsgid \"Import tables and create documents\"\nmsgstr \"導入表格並創建文檔\"\n\n#: apps/knowledge/views/document.py:719 apps/knowledge/views/document.py:720\nmsgid \"Get QA template\"\nmsgstr \"獲取問答模板\"\n\n#: apps/knowledge/views/document.py:733 apps/knowledge/views/document.py:734\nmsgid \"Get form template\"\nmsgstr \"獲取表格模板\"\n\n#: apps/knowledge/views/knowledge.py:25 apps/knowledge/views/knowledge.py:26\n#: apps/knowledge/views/knowledge.py:27\nmsgid \"Get knowledge by folder\"\nmsgstr \"根據文件夾獲取知識庫\"\n\n#: apps/knowledge/views/knowledge.py:30 apps/knowledge/views/knowledge.py:59\n#: apps/knowledge/views/knowledge.py:83 apps/knowledge/views/knowledge.py:106\n#: apps/knowledge/views/knowledge.py:127 apps/knowledge/views/knowledge.py:156\n#: apps/knowledge/views/knowledge.py:188 apps/knowledge/views/knowledge.py:218\n#: apps/knowledge/views/knowledge.py:242 apps/knowledge/views/knowledge.py:266\n#: apps/knowledge/views/knowledge.py:293 apps/knowledge/views/knowledge.py:319\n#: apps/knowledge/views/knowledge.py:343 apps/knowledge/views/knowledge.py:369\n#: apps/knowledge/views/knowledge.py:397\n#: apps/xpack/views/dataset_lark_views.py:29\n#: apps/xpack/views/dataset_lark_views.py:50\nmsgid \"Knowledge Base\"\nmsgstr \"知識庫\"\n\n#: apps/knowledge/views/knowledge.py:53 apps/knowledge/views/knowledge.py:54\n#: apps/knowledge/views/knowledge.py:55\nmsgid \"Edit knowledge\"\nmsgstr \"修改知識庫\"\n\n#: apps/knowledge/views/knowledge.py:77 apps/knowledge/views/knowledge.py:78\n#: apps/knowledge/views/knowledge.py:79\nmsgid \"Delete knowledge\"\nmsgstr \"刪除知識庫\"\n\n#: apps/knowledge/views/knowledge.py:101 apps/knowledge/views/knowledge.py:102\n#: apps/knowledge/views/knowledge.py:103\nmsgid \"Get knowledge\"\nmsgstr \"獲取知識庫\"\n\n#: apps/knowledge/views/knowledge.py:122 apps/knowledge/views/knowledge.py:123\n#: apps/knowledge/views/knowledge.py:124\nmsgid \"Get the knowledge base paginated list\"\nmsgstr \"獲取知識庫分頁列表\"\n\n#: apps/knowledge/views/knowledge.py:150 apps/knowledge/views/knowledge.py:151\n#: apps/knowledge/views/knowledge.py:152\nmsgid \"Synchronize the knowledge base of the website\"\nmsgstr \"同步網站知識庫\"\n\n#: apps/knowledge/views/knowledge.py:182 apps/knowledge/views/knowledge.py:183\n#: apps/knowledge/views/knowledge.py:184\nmsgid \"Hit test list\"\nmsgstr \"命中測試列表\"\n\n#: apps/knowledge/views/knowledge.py:212 apps/knowledge/views/knowledge.py:213\n#: apps/knowledge/views/knowledge.py:214\nmsgid \"Re-vectorize\"\nmsgstr \"重新向量化\"\n\n#: apps/knowledge/views/knowledge.py:238 apps/knowledge/views/knowledge.py:239\nmsgid \"Export knowledge base\"\nmsgstr \"導出知識庫\"\n\n#: apps/knowledge/views/knowledge.py:262 apps/knowledge/views/knowledge.py:263\nmsgid \"Export knowledge base containing images\"\nmsgstr \"導出包含圖片的知識庫\"\n\n#: apps/knowledge/views/knowledge.py:287 apps/knowledge/views/knowledge.py:288\n#: apps/knowledge/views/knowledge.py:289\nmsgid \"Generate related\"\nmsgstr \"生成相關\"\n\n#: apps/knowledge/views/knowledge.py:314 apps/knowledge/views/knowledge.py:315\n#: apps/knowledge/views/knowledge.py:316\nmsgid \"Get model for knowledge base\"\nmsgstr \"獲取知識庫模型\"\n\n#: apps/knowledge/views/knowledge.py:338 apps/knowledge/views/knowledge.py:339\n#: apps/knowledge/views/knowledge.py:340\nmsgid \"Get embedding model for knowledge base\"\nmsgstr \"獲取知識庫向量模型\"\n\n#: apps/knowledge/views/knowledge.py:363 apps/knowledge/views/knowledge.py:364\n#: apps/knowledge/views/knowledge.py:365\nmsgid \"Create base knowledge\"\nmsgstr \"創建知識庫\"\n\n#: apps/knowledge/views/knowledge.py:391 apps/knowledge/views/knowledge.py:392\n#: apps/knowledge/views/knowledge.py:393\nmsgid \"Create web knowledge\"\nmsgstr \"創建 web 知識庫\"\n\n#: apps/knowledge/views/paragraph.py:24 apps/knowledge/views/paragraph.py:25\n#: apps/knowledge/views/paragraph.py:26\nmsgid \"Paragraph list\"\nmsgstr \"段落列表\"\n\n#: apps/knowledge/views/paragraph.py:29 apps/knowledge/views/paragraph.py:53\n#: apps/knowledge/views/paragraph.py:82 apps/knowledge/views/paragraph.py:102\n#: apps/knowledge/views/paragraph.py:138 apps/knowledge/views/paragraph.py:167\n#: apps/knowledge/views/paragraph.py:199 apps/knowledge/views/paragraph.py:224\n#: apps/knowledge/views/paragraph.py:259 apps/knowledge/views/paragraph.py:289\n#: apps/knowledge/views/paragraph.py:316 apps/knowledge/views/paragraph.py:351\n#: apps/knowledge/views/paragraph.py:385 apps/knowledge/views/paragraph.py:415\nmsgid \"Knowledge Base/Documentation/Paragraph\"\nmsgstr \"知識庫/文檔/段落\"\n\n#: apps/knowledge/views/paragraph.py:48 apps/knowledge/views/paragraph.py:49\nmsgid \"Create Paragraph\"\nmsgstr \"創建段落\"\n\n#: apps/knowledge/views/paragraph.py:76 apps/knowledge/views/paragraph.py:77\n#: apps/knowledge/views/paragraph.py:78\nmsgid \"Batch Paragraph\"\nmsgstr \"批量段落\"\n\n#: apps/knowledge/views/paragraph.py:97 apps/knowledge/views/paragraph.py:98\nmsgid \"Migrate paragraphs in batches\"\nmsgstr \"批量遷移段落\"\n\n#: apps/knowledge/views/paragraph.py:132 apps/knowledge/views/paragraph.py:133\n#: apps/knowledge/views/paragraph.py:134\nmsgid \"Batch Generate Related\"\nmsgstr \"批量生成相關\"\n\n#: apps/knowledge/views/paragraph.py:161 apps/knowledge/views/paragraph.py:162\n#: apps/knowledge/views/paragraph.py:163\nmsgid \"Modify paragraph data\"\nmsgstr \"修改段落數據\"\n\n#: apps/knowledge/views/paragraph.py:194 apps/knowledge/views/paragraph.py:195\n#: apps/knowledge/views/paragraph.py:196\nmsgid \"Get paragraph details\"\nmsgstr \"獲取段落詳情\"\n\n#: apps/knowledge/views/paragraph.py:219 apps/knowledge/views/paragraph.py:220\n#: apps/knowledge/views/paragraph.py:221\nmsgid \"Delete paragraph\"\nmsgstr \"刪除段落\"\n\n#: apps/knowledge/views/paragraph.py:253 apps/knowledge/views/paragraph.py:254\n#: apps/knowledge/views/paragraph.py:255\nmsgid \"Add associated questions\"\nmsgstr \"添加關聯問題\"\n\n#: apps/knowledge/views/paragraph.py:284 apps/knowledge/views/paragraph.py:285\n#: apps/knowledge/views/paragraph.py:286\nmsgid \"Get a list of paragraph questions\"\nmsgstr \"獲取段落問題列表\"\n\n#: apps/knowledge/views/paragraph.py:310 apps/knowledge/views/paragraph.py:311\n#: apps/knowledge/views/paragraph.py:312\nmsgid \"Disassociation issue\"\nmsgstr \"取消關聯問題\"\n\n#: apps/knowledge/views/paragraph.py:345 apps/knowledge/views/paragraph.py:346\n#: apps/knowledge/views/paragraph.py:347\nmsgid \"Related questions\"\nmsgstr \"關聯問題\"\n\n#: apps/knowledge/views/paragraph.py:380 apps/knowledge/views/paragraph.py:381\n#: apps/knowledge/views/paragraph.py:382\nmsgid \"Get paragraph list by pagination\"\nmsgstr \"獲取段落列表\"\n\n#: apps/knowledge/views/paragraph.py:409 apps/knowledge/views/paragraph.py:410\n#: apps/knowledge/views/paragraph.py:411\n#: apps/resource_manage/views/paragraph.py:364\n#: apps/resource_manage/views/paragraph.py:365\n#: apps/resource_manage/views/paragraph.py:366\n#: apps/shared/views/shared_paragraph.py:365\n#: apps/shared/views/shared_paragraph.py:366\n#: apps/shared/views/shared_paragraph.py:367\nmsgid \"Adjust paragraph position\"\nmsgstr \"調整段落位置\"\n\n#: apps/knowledge/views/problem.py:23 apps/knowledge/views/problem.py:24\n#: apps/knowledge/views/problem.py:25\nmsgid \"Question list\"\nmsgstr \"問題列表\"\n\n#: apps/knowledge/views/problem.py:28 apps/knowledge/views/problem.py:53\n#: apps/knowledge/views/problem.py:78 apps/knowledge/views/problem.py:104\n#: apps/knowledge/views/problem.py:131 apps/knowledge/views/problem.py:157\n#: apps/knowledge/views/problem.py:186 apps/knowledge/views/problem.py:216\nmsgid \"Knowledge Base/Documentation/Paragraph/Question\"\nmsgstr \"知識庫/文檔/段落/問題\"\n\n#: apps/knowledge/views/problem.py:47 apps/knowledge/views/problem.py:48\n#: apps/knowledge/views/problem.py:49\nmsgid \"Create question\"\nmsgstr \"創建問題\"\n\n#: apps/knowledge/views/problem.py:73 apps/knowledge/views/problem.py:74\n#: apps/knowledge/views/problem.py:75\nmsgid \"Get a list of associated paragraphs\"\nmsgstr \"獲取關聯段落列表\"\n\n#: apps/knowledge/views/problem.py:98 apps/knowledge/views/problem.py:99\n#: apps/knowledge/views/problem.py:100\nmsgid \"Batch associated paragraphs\"\nmsgstr \"批量關聯段落\"\n\n#: apps/knowledge/views/problem.py:125 apps/knowledge/views/problem.py:126\n#: apps/knowledge/views/problem.py:127\nmsgid \"Batch deletion issues\"\nmsgstr \"批量刪除問題\"\n\n#: apps/knowledge/views/problem.py:152 apps/knowledge/views/problem.py:153\n#: apps/knowledge/views/problem.py:154\nmsgid \"Delete question\"\nmsgstr \"刪除問題\"\n\n#: apps/knowledge/views/problem.py:180 apps/knowledge/views/problem.py:181\n#: apps/knowledge/views/problem.py:182\nmsgid \"Modify question\"\nmsgstr \"修改問題\"\n\n#: apps/knowledge/views/problem.py:211 apps/knowledge/views/problem.py:212\n#: apps/knowledge/views/problem.py:213\nmsgid \"Get the list of questions by page\"\nmsgstr \"分頁獲取問題列表\"\n\n#: apps/maxkb/settings/base.py:101\nmsgid \"Intelligent customer service platform\"\nmsgstr \"强大易用的企業級智能體平臺\"\n\n#: apps/models_provider/api/model.py:37 apps/models_provider/api/provide.py:17\n#: apps/models_provider/api/provide.py:23\n#: apps/models_provider/api/provide.py:28\n#: apps/models_provider/api/provide.py:30\n#: apps/models_provider/api/provide.py:82\n#: apps/models_provider/serializers/model_serializer.py:40\n#: apps/models_provider/serializers/model_serializer.py:215\n#: apps/models_provider/serializers/model_serializer.py:253\n#: apps/models_provider/serializers/model_serializer.py:318\n#: apps/models_provider/serializers/model_serializer.py:393\n#: apps/shared/api/shared_model.py:18\n#: apps/shared/serializers/shared_model.py:111\nmsgid \"model name\"\nmsgstr \"模型名稱\"\n\n#: apps/models_provider/api/model.py:44 apps/models_provider/api/provide.py:29\n#: apps/models_provider/api/provide.py:70\n#: apps/models_provider/api/provide.py:98\n#: apps/models_provider/serializers/model_serializer.py:42\n#: apps/models_provider/serializers/model_serializer.py:217\n#: apps/models_provider/serializers/model_serializer.py:255\n#: apps/models_provider/serializers/model_serializer.py:319\n#: apps/models_provider/serializers/model_serializer.py:394\n#: apps/shared/api/shared_model.py:25\n#: apps/shared/serializers/shared_model.py:112\nmsgid \"model type\"\nmsgstr \"模型類型\"\n\n#: apps/models_provider/api/model.py:51\n#: apps/models_provider/serializers/model_serializer.py:43\n#: apps/models_provider/serializers/model_serializer.py:219\n#: apps/models_provider/serializers/model_serializer.py:256\n#: apps/models_provider/serializers/model_serializer.py:320\n#: apps/models_provider/serializers/model_serializer.py:395\n#: apps/shared/api/shared_model.py:32\n#: apps/shared/serializers/shared_model.py:113\nmsgid \"base model\"\nmsgstr \"基礎模型\"\n\n#: apps/models_provider/api/model.py:58 apps/models_provider/api/provide.py:18\n#: apps/models_provider/api/provide.py:38\n#: apps/models_provider/api/provide.py:76\n#: apps/models_provider/api/provide.py:104\n#: apps/models_provider/api/provide.py:126\n#: apps/models_provider/serializers/model_serializer.py:41\n#: apps/models_provider/serializers/model_serializer.py:254\n#: apps/models_provider/serializers/model_serializer.py:321\n#: apps/models_provider/serializers/model_serializer.py:396\n#: apps/shared/api/shared_model.py:39\n#: apps/shared/serializers/shared_model.py:114\nmsgid \"provider\"\nmsgstr \"供應商\"\n\n#: apps/models_provider/api/model.py:65\n#: apps/models_provider/serializers/model_serializer.py:322\n#: apps/models_provider/serializers/model_serializer.py:397\n#: apps/shared/api/shared_model.py:46\n#: apps/shared/serializers/shared_model.py:115\nmsgid \"create user\"\nmsgstr \"創建用戶\"\n\n#: apps/models_provider/api/provide.py:19\n#: apps/xpack/serializers/application_setting_serializer.py:41\n#: apps/xpack/serializers/system_params.py:21\nmsgid \"icon\"\nmsgstr \"圖標\"\n\n#: apps/models_provider/api/provide.py:34 apps/tools/serializers/tool.py:134\nmsgid \"input type\"\nmsgstr \"輸入類型\"\n\n#: apps/models_provider/api/provide.py:35\nmsgid \"label\"\nmsgstr \"標籤\"\n\n#: apps/models_provider/api/provide.py:36\nmsgid \"text field\"\nmsgstr \"文本欄位\"\n\n#: apps/models_provider/api/provide.py:37\nmsgid \"value field\"\nmsgstr \"值\"\n\n#: apps/models_provider/api/provide.py:39\nmsgid \"method\"\nmsgstr \"方法\"\n\n#: apps/models_provider/api/provide.py:40 apps/tools/serializers/tool.py:119\n#: apps/tools/serializers/tool.py:133\nmsgid \"required\"\nmsgstr \"必填\"\n\n#: apps/models_provider/api/provide.py:41\nmsgid \"default value\"\nmsgstr \"默認值\"\n\n#: apps/models_provider/api/provide.py:42\nmsgid \"relation show field dict\"\nmsgstr \"關係顯示欄位\"\n\n#: apps/models_provider/api/provide.py:43\nmsgid \"relation trigger field dict\"\nmsgstr \"關係觸發欄位\"\n\n#: apps/models_provider/api/provide.py:44\nmsgid \"trigger type\"\nmsgstr \"觸發類型\"\n\n#: apps/models_provider/api/provide.py:45\nmsgid \"attrs\"\nmsgstr \"屬性\"\n\n#: apps/models_provider/api/provide.py:46\nmsgid \"props info\"\nmsgstr \"props 信息\"\n\n#: apps/models_provider/base_model_provider.py:60\nmsgid \"Model type cannot be empty\"\nmsgstr \"模型類型不能為空\"\n\n#: apps/models_provider/base_model_provider.py:85\nmsgid \"The current platform does not support downloading models\"\nmsgstr \"當前平臺不支持下載模型\"\n\n#: apps/models_provider/base_model_provider.py:143\nmsgid \"LLM\"\nmsgstr \"大語言模型\"\n\n#: apps/models_provider/base_model_provider.py:144\nmsgid \"Embedding Model\"\nmsgstr \"向量模型\"\n\n#: apps/models_provider/base_model_provider.py:145\nmsgid \"Speech2Text\"\nmsgstr \"語音識別\"\n\n#: apps/models_provider/base_model_provider.py:146\nmsgid \"TTS\"\nmsgstr \"語音合成\"\n\n#: apps/models_provider/base_model_provider.py:147\nmsgid \"Vision Model\"\nmsgstr \"視覺模型\"\n\n#: apps/models_provider/base_model_provider.py:148\nmsgid \"Image Generation\"\nmsgstr \"圖片生成\"\n\n#: apps/models_provider/base_model_provider.py:149\nmsgid \"Rerank\"\nmsgstr \"重排模型\"\n\n#: apps/models_provider/base_model_provider.py:223\nmsgid \"The model does not support\"\nmsgstr \"模型不支持\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:42\nmsgid \"\"\n\"With the GTE-Rerank text sorting series model developed by Alibaba Tongyi \"\n\"Lab, developers can integrate high-quality text retrieval and sorting \"\n\"through the LlamaIndex framework.\"\nmsgstr \"\"\n\"阿里巴巴通義實驗室開發的GTE-Rerank文本排序系列模型，開發者可以通過LlamaIndex\"\n\"框架進行集成高質量文本檢索、排序。\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:45\nmsgid \"\"\n\"Chinese (including various dialects such as Cantonese), English, Japanese, \"\n\"and Korean support free switching between multiple languages.\"\nmsgstr \"中文（含粵語等各種方言）、英文、日語、韓語支持多個語種自由切換\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:48\nmsgid \"\"\n\"CosyVoice is based on a new generation of large generative speech models, \"\n\"which can predict emotions, intonation, rhythm, etc. based on context, and \"\n\"has better anthropomorphic effects.\"\nmsgstr \"\"\n\"CosyVoice基於新一代生成式語音大模型，能根據上下文預測情緒、語調、韻律等，具有\"\n\"更好的擬人效果\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:51\nmsgid \"\"\n\"Universal text vector is Tongyi Lab's multi-language text unified vector \"\n\"model based on the LLM base. It provides high-level vector services for \"\n\"multiple mainstream languages around the world and helps developers quickly \"\n\"convert text data into high-quality vector data.\"\nmsgstr \"\"\n\"通用文本向量，是通義實驗室基於LLM底座的多語言文本統一向量模型，面向全球多個主\"\n\"流語種，提供高水準的向量服務，幫助開發者將文本數據快速轉換為高質量的向量數\"\n\"據。\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:69\nmsgid \"\"\n\"Tongyi Wanxiang - a large image model for text generation, supports \"\n\"bilingual input in Chinese and English, and supports the input of reference \"\n\"pictures for reference content or reference style migration. Key styles \"\n\"include but are not limited to watercolor, oil painting, Chinese painting, \"\n\"sketch, flat illustration, two-dimensional, and 3D. Cartoon.\"\nmsgstr \"\"\n\"通義萬相-文本生成圖像大模型，支持中英文雙語輸入，支持輸入參考圖片進行參考內容\"\n\"或者參考風格遷移，重點風格包括但不限於水彩、油畫、中國畫、素描、扁平插畫、二\"\n\"次元、3D卡通。\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py:95\nmsgid \"Alibaba Cloud Bailian\"\nmsgstr \"阿里雲百鍊\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py:53\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:50\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:74\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:77\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:61\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py:43\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py:37\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:33\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:34\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:53\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:37\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:40\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:69\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:32\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/gemini_model_provider/model/stt.py:43\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:37\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:37\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:44\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:35\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:35\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:58\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:37\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:58\n#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:23\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:37\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:51\n#: apps/models_provider/impl/tencent_model_provider/model/tti.py:54\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:50\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:36\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:32\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/volcanic_engine_model_provider/model/tts.py:77\n#: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:60\n#: apps/models_provider/impl/xf_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:76\n#: apps/models_provider/impl/xf_model_provider/model/tts.py:101\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:32\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:50\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:34\n#: apps/models_provider/impl/xinference_model_provider/model/tts.py:44\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:31\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:56\n#: apps/models_provider/impl/zhipu_model_provider/model/tti.py:49\nmsgid \"Hello\"\nmsgstr \"你好\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:36\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:46\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:44\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:96\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:89\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:23\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:21\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:40\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:30\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/azure_model_provider/credential/stt.py:23\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:58\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:41\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:22\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/gemini_model_provider/credential/stt.py:21\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:28\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/ollama_model_provider/credential/image.py:19\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:44\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:27\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:31\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:25\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:48\n#: apps/models_provider/impl/openai_model_provider/credential/stt.py:22\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:61\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:40\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:25\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:28\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:22\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:61\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:22\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:19\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:28\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:78\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/vllm_model_provider/credential/image.py:22\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:39\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:26\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:22\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:25\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:41\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:51\n#: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:46\n#: apps/models_provider/impl/xf_model_provider/credential/embedding.py:27\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:29\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/xf_model_provider/credential/stt.py:24\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:47\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:19\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:22\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:39\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:25\n#: apps/models_provider/impl/xinference_model_provider/credential/stt.py:21\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:59\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:39\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:21\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:40\n#, python-brace-format\nmsgid \"{model_type} Model type is not supported\"\nmsgstr \"{model_type} 模型類型不支持\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:44\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:67\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:55\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:53\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:105\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:98\n#, python-brace-format\nmsgid \"{key} is required\"\nmsgstr \"{key} 是必填項\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py:60\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:85\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py:69\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt.py:67\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:121\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:113\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:43\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:65\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:42\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:61\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:50\n#: apps/models_provider/impl/azure_model_provider/credential/stt.py:40\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:77\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:58\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:65\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:42\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/gemini_model_provider/credential/stt.py:38\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:64\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:44\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:45\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:51\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:45\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:67\n#: apps/models_provider/impl/openai_model_provider/credential/stt.py:39\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:80\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:58\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:45\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:44\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:39\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:80\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:40\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/tencent_model_provider/credential/embedding.py:30\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:47\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:57\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:104\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/vllm_model_provider/credential/image.py:42\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:55\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:43\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:42\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:66\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:42\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:58\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:68\n#: apps/models_provider/impl/wenxin_model_provider/credential/embedding.py:38\n#: apps/models_provider/impl/xf_model_provider/credential/embedding.py:38\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:50\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:84\n#: apps/models_provider/impl/xf_model_provider/credential/stt.py:41\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:65\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:41\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:40\n#: apps/models_provider/impl/xinference_model_provider/credential/stt.py:37\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:77\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:56\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:41\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:64\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:59\n#, python-brace-format\nmsgid \"\"\n\"Verification failed, please check whether the parameters are correct: {error}\"\nmsgstr \"認證失敗，請檢查參數是否正確：{error}\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:17\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:14\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:20\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:14\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:22\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:41\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:22\nmsgid \"Temperature\"\nmsgstr \"溫度\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:18\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:21\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:15\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:16\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:42\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:16\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:23\nmsgid \"\"\n\"Higher values make the output more random, while lower values make it more \"\n\"focused and deterministic\"\nmsgstr \"較高的數值會使輸出更加隨機，而較低的數值會使其更加集中和確定\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:30\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:23\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:43\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:29\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:50\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:31\nmsgid \"Output the maximum Tokens\"\nmsgstr \"輸出最大Token數\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:31\nmsgid \"Specify the maximum number of tokens that the model can generate.\"\nmsgstr \"指定模型可以生成的最大 tokens 數\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py:43\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:15\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:74\nmsgid \"API URL\"\nmsgstr \"\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:15\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:14\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:15\nmsgid \"Image size\"\nmsgstr \"图片尺寸\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:20\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:15\nmsgid \"Specify the size of the generated image, such as: 1024x1024\"\nmsgstr \"指定生成圖片的尺寸, 如: 1024x1024\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:40\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:43\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:43\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:41\nmsgid \"Number of pictures\"\nmsgstr \"圖片數量\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:34\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:40\nmsgid \"Specify the number of generated images\"\nmsgstr \"指定生成圖片的數量\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44\nmsgid \"Style\"\nmsgstr \"風格\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:44\nmsgid \"Specify the style of generated images\"\nmsgstr \"指定生成圖片的風格\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:48\nmsgid \"Default value, the image style is randomly output by the model\"\nmsgstr \"默認值，圖片風格由模型隨機輸出\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:49\nmsgid \"photography\"\nmsgstr \"攝影\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:50\nmsgid \"Portraits\"\nmsgstr \"人像寫真\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:51\nmsgid \"3D cartoon\"\nmsgstr \"3D卡通\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:52\nmsgid \"animation\"\nmsgstr \"動畫\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:53\nmsgid \"painting\"\nmsgstr \"油畫\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:54\nmsgid \"watercolor\"\nmsgstr \"水彩\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:55\nmsgid \"sketch\"\nmsgstr \"素描\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:56\nmsgid \"Chinese painting\"\nmsgstr \"中國畫\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py:57\nmsgid \"flat illustration\"\nmsgstr \"扁平插畫\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20\nmsgid \"Timbre\"\nmsgstr \"音色\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:20\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15\nmsgid \"Chinese sounds can support mixed scenes of Chinese and English\"\nmsgstr \"中文音色支持中英文混合場景\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:26\nmsgid \"Long Xiaochun\"\nmsgstr \"龍小淳\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:27\nmsgid \"Long Xiaoxia\"\nmsgstr \"龍小夏\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:28\nmsgid \"Long Xiaochen\"\nmsgstr \"龍小誠\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:29\nmsgid \"Long Xiaobai\"\nmsgstr \"龍小白\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:30\nmsgid \"Long Laotie\"\nmsgstr \"龍老鐵\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:31\nmsgid \"Long Shu\"\nmsgstr \"龍書\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:32\nmsgid \"Long Shuo\"\nmsgstr \"龍碩\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:33\nmsgid \"Long Jing\"\nmsgstr \"龍婧\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:34\nmsgid \"Long Miao\"\nmsgstr \"龍妙\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:35\nmsgid \"Long Yue\"\nmsgstr \"龍悅\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:36\nmsgid \"Long Yuan\"\nmsgstr \"龍媛\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:37\nmsgid \"Long Fei\"\nmsgstr \"龍飛\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:38\nmsgid \"Long Jielidou\"\nmsgstr \"龍傑力豆\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:39\nmsgid \"Long Tong\"\nmsgstr \"龍彤\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:40\nmsgid \"Long Xiang\"\nmsgstr \"龍祥\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47\nmsgid \"Speaking speed\"\nmsgstr \"語速\"\n\n#: apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py:47\nmsgid \"[0.5, 2], the default is 1, usually one decimal place is enough\"\nmsgstr \"[0.5,2]，默認為1，通常一位小數就足夠了\"\n\n#: apps/models_provider/impl/anthropic_model_provider/credential/image.py:28\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:32\n#: apps/models_provider/impl/azure_model_provider/credential/image.py:35\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:64\n#: apps/models_provider/impl/azure_model_provider/credential/stt.py:28\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:63\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:46\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/gemini_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/gemini_model_provider/credential/image.py:27\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/gemini_model_provider/credential/stt.py:26\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:32\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:46\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:62\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:63\n#: apps/models_provider/impl/openai_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/openai_model_provider/credential/image.py:30\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:53\n#: apps/models_provider/impl/openai_model_provider/credential/stt.py:27\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:66\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:45\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/image.py:30\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py:32\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py:27\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:66\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py:27\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/tencent_model_provider/credential/image.py:32\n#: apps/models_provider/impl/vllm_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/vllm_model_provider/credential/image.py:27\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:65\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py:31\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py:27\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:52\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py:30\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:46\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:56\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:55\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:72\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:34\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:71\n#: apps/models_provider/impl/xf_model_provider/credential/stt.py:29\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:52\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:40\n#: apps/models_provider/impl/xinference_model_provider/credential/image.py:27\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:59\n#: apps/models_provider/impl/xinference_model_provider/credential/reranker.py:29\n#: apps/models_provider/impl/xinference_model_provider/credential/stt.py:26\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:64\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:44\n#: apps/models_provider/impl/zhipu_model_provider/credential/image.py:26\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:51\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:45\n#, python-brace-format\nmsgid \"{key}  is required\"\nmsgstr \"{key} 是必填項\"\n\n#: apps/models_provider/impl/anthropic_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:24\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:33\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:44\n#: apps/models_provider/impl/deepseek_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/gemini_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/kimi_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:30\n#: apps/models_provider/impl/openai_model_provider/credential/llm.py:33\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:25\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:32\n#: apps/models_provider/impl/xf_model_provider/credential/llm.py:51\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:25\n#: apps/models_provider/impl/zhipu_model_provider/credential/llm.py:32\nmsgid \"Specify the maximum number of tokens that the model can generate\"\nmsgstr \"指定模型可以生成的最大 tokens 數\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:36\nmsgid \"\"\n\"An update to Claude 2 that doubles the context window and improves \"\n\"reliability, hallucination rates, and evidence-based accuracy in long \"\n\"documents and RAG contexts.\"\nmsgstr \"\"\n\"Claude 2 的更新，採用雙倍的上下文窗口，並在長文檔和 RAG 上下文中提高可靠性、\"\n\"幻覺率和循證準確性。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:43\nmsgid \"\"\n\"Anthropic is a powerful model that can handle a variety of tasks, from \"\n\"complex dialogue and creative content generation to detailed command \"\n\"obedience.\"\nmsgstr \"\"\n\"Anthropic 功能強大的模型，可處理各種任務，從複雜的對話和創意內容生成到詳細的\"\n\"指令服從。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:50\nmsgid \"\"\n\"The Claude 3 Haiku is Anthropic's fastest and most compact model, with near-\"\n\"instant responsiveness. The model can answer simple queries and requests \"\n\"quickly. Customers will be able to build seamless AI experiences that mimic \"\n\"human interactions. Claude 3 Haiku can process images and return text \"\n\"output, and provides 200K context windows.\"\nmsgstr \"\"\n\"Claude 3 Haiku 是 Anthropic 最快速、最緊湊的模型，具有近乎即時的響應能力。該\"\n\"模型可以快速回答簡單的查詢和請求。客戶將能夠構建模仿人類交互的無縫人工智能體\"\n\"驗。 Claude 3 Haiku 可以處理圖像和返回文本輸出，並且提供 200K 上下文窗口。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:57\nmsgid \"\"\n\"The Claude 3 Sonnet model from Anthropic strikes the ideal balance between \"\n\"intelligence and speed, especially when it comes to handling enterprise \"\n\"workloads. This model offers maximum utility while being priced lower than \"\n\"competing products, and it's been engineered to be a solid choice for \"\n\"deploying AI at scale.\"\nmsgstr \"\"\n\"Anthropic 推出的 Claude 3 Sonnet 模型在智能和速度之間取得理想的平衡，尤其是在\"\n\"處理企業工作負載方面。該模型提供最大的效用，同時價格低於競爭產品，並且其經過\"\n\"精心設計，是大規模部署人工智慧的可靠選擇。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:64\nmsgid \"\"\n\"The Claude 3.5 Sonnet raises the industry standard for intelligence, \"\n\"outperforming competing models and the Claude 3 Opus in extensive \"\n\"evaluations, with the speed and cost-effectiveness of our mid-range models.\"\nmsgstr \"\"\n\"Claude 3.5 Sonnet提高了智能的行業標準，在廣泛的評估中超越了競爭對手的型號和\"\n\"Claude 3 Opus，具有我們中端型號的速度和成本效益。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:71\nmsgid \"\"\n\"A faster, more affordable but still very powerful model that can handle a \"\n\"range of tasks including casual conversation, text analysis, summarization \"\n\"and document question answering.\"\nmsgstr \"\"\n\"一種更快速、更實惠但仍然非常強大的模型，它可以處理一系列任務，包括隨意對話、\"\n\"文本分析、摘要和文檔問題回答。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:78\nmsgid \"\"\n\"Titan Text Premier is the most powerful and advanced model in the Titan Text \"\n\"series, designed to deliver exceptional performance for a variety of \"\n\"enterprise applications. With its cutting-edge features, it delivers greater \"\n\"accuracy and outstanding results, making it an excellent choice for \"\n\"organizations looking for a top-notch text processing solution.\"\nmsgstr \"\"\n\"Titan Text Premier 是 Titan Text 系列中功能強大且先進的型號，旨在為各種企業應\"\n\"用程序提供卓越的性能。憑藉其尖端功能，它提供了更高的準確性和出色的結果，使其\"\n\"成為尋求一流文本處理解決方案的組織的絕佳選擇。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:85\nmsgid \"\"\n\"Amazon Titan Text Lite is a lightweight, efficient model ideal for fine-\"\n\"tuning English-language tasks, including summarization and copywriting, \"\n\"where customers require smaller, more cost-effective, and highly \"\n\"customizable models.\"\nmsgstr \"\"\n\"Amazon Titan Text Lite 是一種輕量級的高效模型，非常適合英語任務的微調，包括摘\"\n\"要和文案寫作等，在這種場景下，客戶需要更小、更經濟高效且高度可定製的模型\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:91\nmsgid \"\"\n\"Amazon Titan Text Express has context lengths of up to 8,000 tokens, making \"\n\"it ideal for a variety of high-level general language tasks, such as open-\"\n\"ended text generation and conversational chat, as well as support in \"\n\"retrieval-augmented generation (RAG). At launch, the model is optimized for \"\n\"English, but other languages are supported.\"\nmsgstr \"\"\n\"Amazon Titan Text Express 的上下文長度長達 8000 個 tokens，因而非常適合各種高\"\n\"級常規語言任務，例如開放式文本生成和對話式聊天，以及檢索增強生成（RAG）中的支\"\n\"持。在發布時，該模型針對英語進行了優化，但也支持其他語言。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:97\nmsgid \"\"\n\"7B dense converter for rapid deployment and easy customization. Small in \"\n\"size yet powerful in a variety of use cases. Supports English and code, as \"\n\"well as 32k context windows.\"\nmsgstr \"\"\n\"7B 密集型轉換器，可快速部署，易於定製。體積雖小，但功能強大，適用於各種用例。\"\n\"支持英語和代碼，以及 32k 的上下文窗口。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:103\nmsgid \"\"\n\"Advanced Mistral AI large-scale language model capable of handling any \"\n\"language task, including complex multilingual reasoning, text understanding, \"\n\"transformation, and code generation.\"\nmsgstr \"\"\n\"先進的 Mistral AI 大型語言模型，能夠處理任何語言任務，包括複雜的多語言推理、\"\n\"文本理解、轉換和代碼生成。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:109\nmsgid \"\"\n\"Ideal for content creation, conversational AI, language understanding, R&D, \"\n\"and enterprise applications\"\nmsgstr \"非常適合內容創作、對話式人工智慧、語言理解、研發和企業智能體\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:115\nmsgid \"\"\n\"Ideal for limited computing power and resources, edge devices, and faster \"\n\"training times.\"\nmsgstr \"非常適合有限的計算能力和資源、邊緣設備和更快的訓練時間。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py:123\nmsgid \"\"\n\"Titan Embed Text is the largest embedding model in the Amazon Titan Embed \"\n\"series and can handle various text embedding tasks, such as text \"\n\"classification, text similarity calculation, etc.\"\nmsgstr \"\"\n\"Titan Embed Text 是 Amazon Titan Embed 系列中最大的嵌入模型，可以處理各種文本\"\n\"嵌入任務，如文本分類、文本相似度計算等。\"\n\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py:28\n#: apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py:47\n#, python-brace-format\nmsgid \"The following fields are required: {keys}\"\nmsgstr \"以下欄位是必填項: {keys}\"\n\n#: apps/models_provider/impl/azure_model_provider/credential/embedding.py:44\n#: apps/models_provider/impl/azure_model_provider/credential/llm.py:76\nmsgid \"Verification failed, please check whether the parameters are correct\"\nmsgstr \"認證失敗，請檢查參數是否正確\"\n\n#: apps/models_provider/impl/azure_model_provider/credential/tti.py:28\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:29\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:28\nmsgid \"Picture quality\"\nmsgstr \"圖片質量\"\n\n#: apps/models_provider/impl/azure_model_provider/credential/tts.py:17\n#: apps/models_provider/impl/openai_model_provider/credential/tts.py:17\nmsgid \"\"\n\"Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) \"\n\"to find one that suits your desired tone and audience. The current voiceover \"\n\"is optimized for English.\"\nmsgstr \"\"\n\"嘗試不同的聲音（合金、回聲、寓言、縞瑪瑙、新星和閃光），找到一種適合您所需的\"\n\"音調和聽眾的聲音。當前的語音針對英語進行了優化。\"\n\n#: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:24\nmsgid \"Good at common conversational tasks, supports 32K contexts\"\nmsgstr \"擅長通用對話任務，支持 32K 上下文\"\n\n#: apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py:29\nmsgid \"Good at handling programming tasks, supports 16K contexts\"\nmsgstr \"擅長處理編程任務，支持 16K 上下文\"\n\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:32\nmsgid \"Latest Gemini 1.0 Pro model, updated with Google update\"\nmsgstr \"最新的 Gemini 1.0 Pro 模型，更新了 Google 更新\"\n\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:36\nmsgid \"Latest Gemini 1.0 Pro Vision model, updated with Google update\"\nmsgstr \"最新的Gemini 1.0 Pro Vision模型，隨Google更新而更新\"\n\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:43\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:47\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:54\n#: apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py:58\nmsgid \"Latest Gemini 1.5 Flash model, updated with Google updates\"\nmsgstr \"最新的Gemini 1.5 Flash模型，隨Google更新而更新\"\n\n#: apps/models_provider/impl/gemini_model_provider/model/stt.py:53\nmsgid \"convert audio to text\"\nmsgstr \"將音頻轉換為文本\"\n\n#: apps/models_provider/impl/local_model_provider/credential/embedding.py:53\n#: apps/models_provider/impl/local_model_provider/credential/reranker.py:54\nmsgid \"Model catalog\"\nmsgstr \"模型目錄\"\n\n#: apps/models_provider/impl/local_model_provider/local_model_provider.py:39\nmsgid \"local model\"\nmsgstr \"本地模型\"\n\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:30\n#: apps/models_provider/impl/ollama_model_provider/credential/image.py:23\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:48\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:35\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:43\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:24\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:44\nmsgid \"API domain name is invalid\"\nmsgstr \"API 域名無效\"\n\n#: apps/models_provider/impl/ollama_model_provider/credential/embedding.py:35\n#: apps/models_provider/impl/ollama_model_provider/credential/image.py:28\n#: apps/models_provider/impl/ollama_model_provider/credential/llm.py:53\n#: apps/models_provider/impl/ollama_model_provider/credential/reranker.py:40\n#: apps/models_provider/impl/vllm_model_provider/credential/llm.py:47\n#: apps/models_provider/impl/xinference_model_provider/credential/embedding.py:30\n#: apps/models_provider/impl/xinference_model_provider/credential/llm.py:48\nmsgid \"The model does not exist, please download the model first\"\nmsgstr \"模型不存在，請先下載模型\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:56\nmsgid \"\"\n\"Llama 2 is a set of pretrained and fine-tuned generative text models ranging \"\n\"in size from 7 billion to 70 billion. This is a repository of 7B pretrained \"\n\"models. Links to other models can be found in the index at the bottom.\"\nmsgstr \"\"\n\"Llama 2 是一組經過預訓練和微調的生成文本模型，其規模從 70 億到 700 億個不等。\"\n\"這是 7B 預訓練模型的存儲庫。其他模型的連結可以在底部的索引中找到。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:60\nmsgid \"\"\n\"Llama 2 is a set of pretrained and fine-tuned generative text models ranging \"\n\"in size from 7 billion to 70 billion. This is a repository of 13B pretrained \"\n\"models. Links to other models can be found in the index at the bottom.\"\nmsgstr \"\"\n\"Llama 2 是一組經過預訓練和微調的生成文本模型，其規模從 70 億到 700 億個不等。\"\n\"這是 13B 預訓練模型的存儲庫。其他模型的連結可以在底部的索引中找到。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:64\nmsgid \"\"\n\"Llama 2 is a set of pretrained and fine-tuned generative text models ranging \"\n\"in size from 7 billion to 70 billion. This is a repository of 70B pretrained \"\n\"models. Links to other models can be found in the index at the bottom.\"\nmsgstr \"\"\n\"Llama 2 是一組經過預訓練和微調的生成文本模型，其規模從 70 億到 700 億個不等。\"\n\"這是 70B 預訓練模型的存儲庫。其他模型的連結可以在底部的索引中找到。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:68\nmsgid \"\"\n\"Since the Chinese alignment of Llama2 itself is weak, we use the Chinese \"\n\"instruction set to fine-tune meta-llama/Llama-2-13b-chat-hf with LoRA so \"\n\"that it has strong Chinese conversation capabilities.\"\nmsgstr \"\"\n\"由於Llama2本身的中文對齊較弱，我們採用中文指令集，對meta-llama/Llama-2-13b-\"\n\"chat-hf進行LoRA微調，使其具備較強的中文對話能力。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:72\nmsgid \"\"\n\"Meta Llama 3: The most capable public product LLM to date. 8 billion \"\n\"parameters.\"\nmsgstr \"Meta Llama 3：迄今為止最有能力的公開產品LLM。80億參數。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:76\nmsgid \"\"\n\"Meta Llama 3: The most capable public product LLM to date. 70 billion \"\n\"parameters.\"\nmsgstr \"Meta Llama 3：迄今為止最有能力的公開產品LLM。700億參數。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:80\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 0.5b has significantly enhanced \"\n\"the model's alignment with human preferences and its multi-language \"\n\"processing capabilities. Models of all sizes support a context length of \"\n\"32768 tokens. 500 million parameters.\"\nmsgstr \"\"\n\"qwen 1.5 0.5b 相較於以往版本，模型與人類偏好的對齊程度以及多語言處理能力上有\"\n\"顯著增強。所有規模的模型都支持32768個tokens的上下文長度。5億參數。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:84\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 1.8b has significantly enhanced \"\n\"the model's alignment with human preferences and its multi-language \"\n\"processing capabilities. Models of all sizes support a context length of \"\n\"32768 tokens. 1.8 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 1.8b 相較於以往版本，模型與人類偏好的對齊程度以及多語言處理能力上有\"\n\"顯著增強。所有規模的模型都支持32768個tokens的上下文長度。18億參數。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:88\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 4b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"4 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 4b 相較於以往版本，模型與人類偏好的對齊程度以及多語言處理能力上有顯\"\n\"著增強。所有規模的模型都支持32768個tokens的上下文長度。40億參數。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:93\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 7b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"7 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 7b 相較於以往版本，模型與人類偏好的對齊程度以及多語言處理能力上有顯\"\n\"著增強。所有規模的模型都支持32768個tokens的上下文長度。70億參數。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:97\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 14b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"14 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 14b 相較於以往版本，模型與人類偏好的對齊程度以及多語言處理能力上有顯\"\n\"著增強。所有規模的模型都支持32768個tokens的上下文長度。140億參數。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:101\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 32b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"32 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 32b 相較於以往版本，模型與人類偏好的對齊程度以及多語言處理能力上有顯\"\n\"著增強。所有規模的模型都支持32768個tokens的上下文長度。320億參數。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:105\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 72b has significantly enhanced the \"\n\"model's alignment with human preferences and its multi-language processing \"\n\"capabilities. Models of all sizes support a context length of 32768 tokens. \"\n\"72 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 72b 相較於以往版本，模型與人類偏好的對齊程度以及多語言處理能力上有顯\"\n\"著增強。所有規模的模型都支持32768個tokens的上下文長度。720億參數。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:109\nmsgid \"\"\n\"Compared with previous versions, qwen 1.5 110b has significantly enhanced \"\n\"the model's alignment with human preferences and its multi-language \"\n\"processing capabilities. Models of all sizes support a context length of \"\n\"32768 tokens. 110 billion parameters.\"\nmsgstr \"\"\n\"qwen 1.5 110b 相較於以往版本，模型與人類偏好的對齊程度以及多語言處理能力上有\"\n\"顯著增強。所有規模的模型都支持32768個tokens的上下文長度。1100億參數。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:153\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:193\nmsgid \"\"\n\"Phi-3 Mini is Microsoft's 3.8B parameter, lightweight, state-of-the-art open \"\n\"model.\"\nmsgstr \"Phi-3 Mini是Microsoft的3.8B參數，輕量級，最先進的開放模型。\"\n\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:162\n#: apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py:197\nmsgid \"\"\n\"A high-performance open embedding model with a large token context window.\"\nmsgstr \"一個具有大 tokens上下文窗口的高性能開放嵌入模型。\"\n\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:16\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:16\nmsgid \"\"\n\"The image generation endpoint allows you to create raw images based on text \"\n\"prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 \"\n\"or 1792x1024 pixels.\"\nmsgstr \"\"\n\"圖像生成端點允許您根據文本提示創建原始圖像。使用 DALL·E 3 時，圖像的尺寸可以\"\n\"為 1024x1024、1024x1792 或 1792x1024 像素。\"\n\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:29\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:29\nmsgid \"\"\n\"       \\n\"\n\"By default, images are produced in standard quality, but with DALL·E 3 you \"\n\"can set quality: \\\"hd\\\" to enhance detail. Square, standard quality images \"\n\"are generated fastest.\\n\"\n\"        \"\nmsgstr \"\"\n\"默認情況下，圖像以標準質量生成，但使用 DALL·E 3 時，您可以設置質量：「hd」以增\"\n\"強細節。方形、標準質量的圖像生成速度最快。\"\n\n#: apps/models_provider/impl/openai_model_provider/credential/tti.py:44\n#: apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py:44\nmsgid \"\"\n\"You can use DALL·E 3 to request 1 image at a time (requesting more images by \"\n\"issuing parallel requests), or use DALL·E 2 with the n parameter to request \"\n\"up to 10 images at a time.\"\nmsgstr \"\"\n\"您可以使用 DALL·E 3 一次請求 1 個圖像（通過發出並行請求來請求更多圖像），或者\"\n\"使用帶有 n 參數的 DALL·E 2 一次最多請求 10 個圖像。\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:35\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:119\n#: apps/models_provider/impl/siliconCloud_model_provider/siliconCloud_model_provider.py:118\nmsgid \"The latest gpt-3.5-turbo, updated with OpenAI adjustments\"\nmsgstr \"最新的gpt-3.5-turbo，隨OpenAI調整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:38\nmsgid \"Latest gpt-4, updated with OpenAI adjustments\"\nmsgstr \"最新的gpt-4，隨OpenAI調整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:40\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:99\nmsgid \"\"\n\"The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with OpenAI \"\n\"adjustments\"\nmsgstr \"最新的GPT-4o，比gpt-4-turbo更便宜、更快，隨OpenAI調整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:43\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:102\nmsgid \"\"\n\"The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with OpenAI \"\n\"adjustments\"\nmsgstr \"最新的gpt-4o-mini，比gpt-4o更便宜、更快，隨OpenAI調整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:46\nmsgid \"The latest gpt-4-turbo, updated with OpenAI adjustments\"\nmsgstr \"最新的gpt-4-turbo，隨OpenAI調整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:49\nmsgid \"The latest gpt-4-turbo-preview, updated with OpenAI adjustments\"\nmsgstr \"最新的gpt-4-turbo-preview，隨OpenAI調整而更新\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:53\nmsgid \"\"\n\"gpt-3.5-turbo snapshot on January 25, 2024, supporting context length 16,385 \"\n\"tokens\"\nmsgstr \"2024年1月25日的gpt-3.5-turbo快照，支持上下文長度16,385 tokens\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:57\nmsgid \"\"\n\"gpt-3.5-turbo snapshot on November 6, 2023, supporting context length 16,385 \"\n\"tokens\"\nmsgstr \"2023年11月6日的gpt-3.5-turbo快照，支持上下文長度16,385 tokens\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:61\nmsgid \"\"\n\"[Legacy] gpt-3.5-turbo snapshot on June 13, 2023, will be deprecated on June \"\n\"13, 2024\"\nmsgstr \"[Legacy] 2023年6月13日的gpt-3.5-turbo快照，將於2024年6月13日棄用\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:65\nmsgid \"\"\n\"gpt-4o snapshot on May 13, 2024, supporting context length 128,000 tokens\"\nmsgstr \"2024年5月13日的gpt-4o快照，支持上下文長度128,000 tokens\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:69\nmsgid \"\"\n\"gpt-4-turbo snapshot on April 9, 2024, supporting context length 128,000 \"\n\"tokens\"\nmsgstr \"2024年4月9日的gpt-4-turbo快照，支持上下文長度128,000 tokens\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:72\nmsgid \"\"\n\"gpt-4-turbo snapshot on January 25, 2024, supporting context length 128,000 \"\n\"tokens\"\nmsgstr \"2024年1月25日的gpt-4-turbo快照，支持上下文長度128,000 tokens\"\n\n#: apps/models_provider/impl/openai_model_provider/openai_model_provider.py:75\nmsgid \"\"\n\"gpt-4-turbo snapshot on November 6, 2023, supporting context length 128,000 \"\n\"tokens\"\nmsgstr \"2023年11月6日的gpt-4-turbo快照，支持上下文長度128,000 tokens\"\n\n#: apps/models_provider/impl/tencent_cloud_model_provider/tencent_cloud_model_provider.py:58\nmsgid \"Tencent Cloud\"\nmsgstr \"騰訊雲\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/llm.py:41\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:88\n#, python-brace-format\nmsgid \"{keys} is required\"\nmsgstr \"{keys} 是必填項\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14\nmsgid \"painting style\"\nmsgstr \"繪畫風格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:14\nmsgid \"If not passed, the default value is 201 (Japanese anime style)\"\nmsgstr \"如果未傳遞，則默認值為201（日本動漫風格）\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:18\nmsgid \"Not limited to style\"\nmsgstr \"不限於風格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:19\nmsgid \"ink painting\"\nmsgstr \"水墨畫\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:20\nmsgid \"concept art\"\nmsgstr \"概念藝術\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:21\nmsgid \"Oil painting 1\"\nmsgstr \"油畫1\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:22\nmsgid \"Oil Painting 2 (Van Gogh)\"\nmsgstr \"油畫2（梵谷）\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:23\nmsgid \"watercolor painting\"\nmsgstr \"水彩畫\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:24\nmsgid \"pixel art\"\nmsgstr \"像素畫\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:25\nmsgid \"impasto style\"\nmsgstr \"厚塗風格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:26\nmsgid \"illustration\"\nmsgstr \"插圖\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:27\nmsgid \"paper cut style\"\nmsgstr \"剪紙風格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:28\nmsgid \"Impressionism 1 (Monet)\"\nmsgstr \"印象派1（莫奈）\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:29\nmsgid \"Impressionism 2\"\nmsgstr \"印象派2\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:31\nmsgid \"classical portraiture\"\nmsgstr \"古典肖像畫\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:32\nmsgid \"black and white sketch\"\nmsgstr \"黑白素描畫\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:33\nmsgid \"cyberpunk\"\nmsgstr \"賽博朋克\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:34\nmsgid \"science fiction style\"\nmsgstr \"科幻風格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:35\nmsgid \"dark style\"\nmsgstr \"暗黑風格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:37\nmsgid \"vaporwave\"\nmsgstr \"蒸汽波\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:38\nmsgid \"Japanese animation\"\nmsgstr \"日系動漫\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:39\nmsgid \"monster style\"\nmsgstr \"怪獸風格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:40\nmsgid \"Beautiful ancient style\"\nmsgstr \"唯美古風\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:41\nmsgid \"retro anime\"\nmsgstr \"復古動漫\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:42\nmsgid \"Game cartoon hand drawing\"\nmsgstr \"遊戲卡通手繪\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:43\nmsgid \"Universal realistic style\"\nmsgstr \"通用寫實風格\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50\nmsgid \"Generate image resolution\"\nmsgstr \"生成圖像解析度\"\n\n#: apps/models_provider/impl/tencent_model_provider/credential/tti.py:50\nmsgid \"If not transmitted, the default value is 768:768.\"\nmsgstr \"不傳默認使用768:768。\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:38\nmsgid \"\"\n\"The most effective version of the current hybrid model, the trillion-level \"\n\"parameter scale MOE-32K long article model. Reaching the absolute leading \"\n\"level on various benchmarks, with complex instructions and reasoning, \"\n\"complex mathematical capabilities, support for function call, and \"\n\"application focus optimization in fields such as multi-language translation, \"\n\"finance, law, and medical care\"\nmsgstr \"\"\n\"當前混元模型中效果最優版本，萬億級參數規模 MOE-32K 長文模型。在各種 \"\n\"benchmark 上達到絕對領先的水平，複雜指令和推理，具備複雜數學能力，支持 \"\n\"functioncall，在多語言翻譯、金融法律醫療等領域智能體重點優化\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:45\nmsgid \"\"\n\"A better routing strategy is adopted to simultaneously alleviate the \"\n\"problems of load balancing and expert convergence. For long articles, the \"\n\"needle-in-a-haystack index reaches 99.9%\"\nmsgstr \"\"\n\"採用更優的路由策略，同時緩解了負載均衡和專家趨同的問題。長文方面，大海撈針指\"\n\"標達到99.9%\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:51\nmsgid \"\"\n\"Upgraded to MOE structure, the context window is 256k, leading many open \"\n\"source models in multiple evaluation sets such as NLP, code, mathematics, \"\n\"industry, etc.\"\nmsgstr \"\"\n\"升級為 MOE 結構，上下文窗口為 256k ，在 NLP，代碼，數學，行業等多項評測集上領\"\n\"先眾多開源模型\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:57\nmsgid \"\"\n\"Hunyuan's latest version of the role-playing model, a role-playing model \"\n\"launched by Hunyuan's official fine-tuning training, is based on the Hunyuan \"\n\"model combined with the role-playing scene data set for additional training, \"\n\"and has better basic effects in role-playing scenes.\"\nmsgstr \"\"\n\"混元最新版角色扮演模型，混元官方精調訓練推出的角色扮演模型，基於混元模型結合\"\n\"角色扮演場景數據集進行增訓，在角色扮演場景具有更好的基礎效果\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:63\nmsgid \"\"\n\"Hunyuan's latest MOE architecture FunctionCall model has been trained with \"\n\"high-quality FunctionCall data and has a context window of 32K, leading in \"\n\"multiple dimensions of evaluation indicators.\"\nmsgstr \"\"\n\"混元最新 MOE 架構 FunctionCall 模型，經過高質量的 FunctionCall 數據訓練，上下\"\n\"文窗口達 32K，在多個維度的評測指標上處於領先。\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:69\nmsgid \"\"\n\"Hunyuan's latest code generation model, after training the base model with \"\n\"200B high-quality code data, and iterating on high-quality SFT data for half \"\n\"a year, the context long window length has been increased to 8K, and it \"\n\"ranks among the top in the automatic evaluation indicators of code \"\n\"generation in the five major languages; the five major languages In the \"\n\"manual high-quality evaluation of 10 comprehensive code tasks that consider \"\n\"all aspects, the performance is in the first echelon.\"\nmsgstr \"\"\n\"混元最新代碼生成模型，經過 200B 高質量代碼數據增訓基座模型，迭代半年高質量 \"\n\"SFT 數據訓練，上下文長窗口長度增大到 8K，五大語言代碼生成自動評測指標上位居前\"\n\"列；五大語言10項考量各方面綜合代碼任務人工高質量評測上，性能處於第一梯隊\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:77\nmsgid \"\"\n\"Tencent's Hunyuan Embedding interface can convert text into high-quality \"\n\"vector data. The vector dimension is 1024 dimensions.\"\nmsgstr \"\"\n\"騰訊混元 Embedding 接口，可以將文本轉化為高質量的向量數據。向量維度為1024維。\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:87\nmsgid \"Mixed element visual model\"\nmsgstr \"混元視覺模型\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:94\nmsgid \"Hunyuan graph model\"\nmsgstr \"混元生圖模型\"\n\n#: apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py:125\nmsgid \"Tencent Hunyuan\"\nmsgstr \"騰訊混元\"\n\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:24\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:42\nmsgid \"Facebook’s 125M parameter model\"\nmsgstr \"Facebook的125M參數模型\"\n\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:25\nmsgid \"BAAI’s 7B parameter model\"\nmsgstr \"BAAI的7B參數模型\"\n\n#: apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py:26\nmsgid \"BAAI’s 13B parameter mode\"\nmsgstr \"BAAI的13B參數模型\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py:16\nmsgid \"\"\n\"If the gap between width, height and 512 is too large, the picture rendering \"\n\"effect will be poor and the probability of excessive delay will increase \"\n\"significantly. Recommended ratio and corresponding width and height before \"\n\"super score: width*height\"\nmsgstr \"\"\n\"寬、高與512差距過大，則出圖效果不佳、延遲過長概率顯著增加。超分前建議比例及對\"\n\"應寬高：width*height\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:15\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:15\nmsgid \"timbre\"\nmsgstr \"音色\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:28\nmsgid \"speaking speed\"\nmsgstr \"語速\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py:31\nmsgid \"[0.2,3], the default is 1, usually one decimal place is enough\"\nmsgstr \"[0.2,3]，默認為1，通常保留一位小數即可\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:39\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:44\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:88\nmsgid \"\"\n\"The user goes to the model inference page of Volcano Ark to create an \"\n\"inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call \"\n\"it.\"\nmsgstr \"\"\n\"用戶前往火山方舟的模型推理頁面創建推理接入點，這裡需要輸入ep-xxxxxxxxxx-yyyy\"\n\"進行調用\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:59\nmsgid \"Universal 2.0-Vincent Diagram\"\nmsgstr \"通用2.0-文生圖\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:64\nmsgid \"Universal 2.0Pro-Vincent Chart\"\nmsgstr \"通用2.0Pro-文生圖\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:69\nmsgid \"Universal 1.4-Vincent Chart\"\nmsgstr \"通用1.4-文生圖\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:74\nmsgid \"Animation 1.3.0-Vincent Picture\"\nmsgstr \"動漫1.3.0-文生圖\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:79\nmsgid \"Animation 1.3.1-Vincent Picture\"\nmsgstr \"動漫1.3.1-文生圖\"\n\n#: apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py:113\nmsgid \"volcano engine\"\nmsgstr \"火山引擎\"\n\n#: apps/models_provider/impl/wenxin_model_provider/credential/llm.py:51\n#, python-brace-format\nmsgid \"{model_name} The model does not support\"\nmsgstr \"{model_name} 模型不支持\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:24\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:53\nmsgid \"\"\n\"ERNIE-Bot-4 is a large language model independently developed by Baidu. It \"\n\"covers massive Chinese data and has stronger capabilities in dialogue Q&A, \"\n\"content creation and generation.\"\nmsgstr \"\"\n\"ERNIE-Bot-4是百度自行研發的大語言模型，覆蓋海量中文數據，具有更強的對話問答、\"\n\"內容創作生成等能力。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:27\nmsgid \"\"\n\"ERNIE-Bot is a large language model independently developed by Baidu. It \"\n\"covers massive Chinese data and has stronger capabilities in dialogue Q&A, \"\n\"content creation and generation.\"\nmsgstr \"\"\n\"ERNIE-Bot是百度自行研發的大語言模型，覆蓋海量中文數據，具有更強的對話問答、內\"\n\"容創作生成等能力。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:30\nmsgid \"\"\n\"ERNIE-Bot-turbo is a large language model independently developed by Baidu. \"\n\"It covers massive Chinese data, has stronger capabilities in dialogue Q&A, \"\n\"content creation and generation, and has a faster response speed.\"\nmsgstr \"\"\n\"ERNIE-Bot-turbo是百度自行研發的大語言模型，覆蓋海量中文數據，具有更強的對話問\"\n\"答、內容創作生成等能力，響應速度更快。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:33\nmsgid \"\"\n\"BLOOMZ-7B is a well-known large language model in the industry. It was \"\n\"developed and open sourced by BigScience and can output text in 46 languages \"\n\"and 13 programming languages.\"\nmsgstr \"\"\n\"BLOOMZ-7B是業內知名的大語言模型，由BigScience研發並開源，能夠以46種語言和13種\"\n\"程式語言輸出文本。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:39\nmsgid \"\"\n\"Llama-2-13b-chat was developed by Meta AI and is open source. It performs \"\n\"well in scenarios such as coding, reasoning and knowledge application. \"\n\"Llama-2-13b-chat is a native open source version with balanced performance \"\n\"and effect, suitable for conversation scenarios.\"\nmsgstr \"\"\n\"Llama-2-13b-chat由Meta AI研發並開源，在編碼、推理及知識智能體等場景表現優秀，\"\n\"Llama-2-13b-chat是性能與效果均衡的原生開源版本，適用於對話場景。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:42\nmsgid \"\"\n\"Llama-2-70b-chat was developed by Meta AI and is open source. It performs \"\n\"well in scenarios such as coding, reasoning, and knowledge application. \"\n\"Llama-2-70b-chat is a native open source version with high-precision effects.\"\nmsgstr \"\"\n\"Llama-2-70b-chat由Meta AI研發並開源，在編碼、推理及知識智能體等場景表現優秀，\"\n\"Llama-2-70b-chat是高精度效果的原生開源版本。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:45\nmsgid \"\"\n\"The Chinese enhanced version developed by the Qianfan team based on \"\n\"Llama-2-7b has performed well on Chinese knowledge bases such as CMMLU and C-\"\n\"EVAL.\"\nmsgstr \"\"\n\"千帆團隊在Llama-2-7b基礎上的中文增強版本，在CMMLU、C-EVAL等中文知識庫上表現優\"\n\"異。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:49\nmsgid \"\"\n\"Embedding-V1 is a text representation model based on Baidu Wenxin large \"\n\"model technology. It can convert text into a vector form represented by \"\n\"numerical values and can be used in text retrieval, information \"\n\"recommendation, knowledge mining and other scenarios. Embedding-V1 provides \"\n\"the Embeddings interface, which can generate corresponding vector \"\n\"representations based on input content. You can call this interface to input \"\n\"text into the model and obtain the corresponding vector representation for \"\n\"subsequent text processing and analysis.\"\nmsgstr \"\"\n\"Embedding-V1是一個基於百度文心大模型技術的文本表示模型，可以將文本轉化為用數\"\n\"值表示的向量形式，用於文本檢索、信息推薦、知識挖掘等場景。 Embedding-V1提供了\"\n\"Embeddings接口，可以根據輸入內容生成對應的向量表示。您可以通過調用該接口，將\"\n\"文本輸入到模型中，獲取到對應的向量表示，從而進行後續的文本處理和分析。\"\n\n#: apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py:66\nmsgid \"Thousand sails large model\"\nmsgstr \"千帆大模型\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/image.py:42\nmsgid \"Please outline this picture\"\nmsgstr \"請描述這張圖片\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:15\nmsgid \"Speaker\"\nmsgstr \"發音人\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:16\nmsgid \"\"\n\"Speaker, optional value: Please go to the console to add a trial or purchase \"\n\"speaker. After adding, the speaker parameter value will be displayed.\"\nmsgstr \"\"\n\"發音人，可選值：請到控制臺添加試用或購買發音人，添加後即顯示發音人參數值\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:21\nmsgid \"iFlytek Xiaoyan\"\nmsgstr \"訊飛小燕\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:22\nmsgid \"iFlytek Xujiu\"\nmsgstr \"訊飛許久\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:23\nmsgid \"iFlytek Xiaoping\"\nmsgstr \"訊飛小萍\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:24\nmsgid \"iFlytek Xiaojing\"\nmsgstr \"訊飛小婧\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:25\nmsgid \"iFlytek Xuxiaobao\"\nmsgstr \"訊飛許小寶\"\n\n#: apps/models_provider/impl/xf_model_provider/credential/tts.py:28\nmsgid \"Speech speed, optional value: [0-100], default is 50\"\nmsgstr \"語速，可選值：[0-100]，默認為50\"\n\n#: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:39\n#: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:50\nmsgid \"Chinese and English recognition\"\nmsgstr \"中英文識別\"\n\n#: apps/models_provider/impl/xf_model_provider/xf_model_provider.py:66\nmsgid \"iFlytek Spark\"\nmsgstr \"訊飛星火\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:15\nmsgid \"\"\n\"The image generation endpoint allows you to create raw images based on text \"\n\"prompts. The dimensions of the image can be 1024x1024, 1024x1792, or \"\n\"1792x1024 pixels.\"\nmsgstr \"\"\n\"圖像生成端點允許您根據文本提示創建原始圖像。圖像的尺寸可以為 1024x1024、\"\n\"1024x1792 或 1792x1024 像素。\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:29\nmsgid \"\"\n\"By default, images are generated in standard quality, you can set quality: \"\n\"\\\"hd\\\" to enhance detail. Square, standard quality images are generated \"\n\"fastest.\"\nmsgstr \"\"\n\"默認情況下，圖像以標準質量生成，您可以設置質量：「hd」以增強細節。方形、標準質\"\n\"量的圖像生成速度最快。\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tti.py:42\nmsgid \"\"\n\"You can request 1 image at a time (requesting more images by making parallel \"\n\"requests), or up to 10 images at a time using the n parameter.\"\nmsgstr \"\"\n\"您可以一次請求 1 個圖像（通過發出並行請求來請求更多圖像），或者使用 n 參數一\"\n\"次最多請求 10 個圖像。\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:20\nmsgid \"Chinese female\"\nmsgstr \"中文女\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:21\nmsgid \"Chinese male\"\nmsgstr \"中文男\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:22\nmsgid \"Japanese male\"\nmsgstr \"日語男\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:23\nmsgid \"Cantonese female\"\nmsgstr \"粵語女\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:24\nmsgid \"English female\"\nmsgstr \"英文女\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:25\nmsgid \"English male\"\nmsgstr \"英文男\"\n\n#: apps/models_provider/impl/xinference_model_provider/credential/tts.py:26\nmsgid \"Korean female\"\nmsgstr \"韓語女\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:37\nmsgid \"\"\n\"Code Llama is a language model specifically designed for code generation.\"\nmsgstr \"Code Llama 是一個專門用於代碼生成的語言模型。\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:44\nmsgid \"\"\n\"       \\n\"\n\"Code Llama Instruct is a fine-tuned version of Code Llama's instructions, \"\n\"designed to perform specific tasks.\\n\"\n\"        \"\nmsgstr \"\"\n\"Code Llama Instruct 是 Code Llama 的指令微調版本，專為執行特定任務而設計。\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:53\nmsgid \"\"\n\"Code Llama Python is a language model specifically designed for Python code \"\n\"generation.\"\nmsgstr \"Code Llama Python 是一個專門用於 Python 代碼生成的語言模型。\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:60\nmsgid \"\"\n\"CodeQwen 1.5 is a language model for code generation with high performance.\"\nmsgstr \"CodeQwen 1.5 是一個用於代碼生成的語言模型，具有較高的性能。\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:67\nmsgid \"CodeQwen 1.5 Chat is a chat model version of CodeQwen 1.5.\"\nmsgstr \"CodeQwen 1.5 Chat 是一個聊天模型版本的 CodeQwen 1.5。\"\n\n#: apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py:74\nmsgid \"Deepseek is a large-scale language model with 13 billion parameters.\"\nmsgstr \"Deepseek Chat 是一個聊天模型版本的 Deepseek。\"\n\n#: apps/models_provider/impl/zhipu_model_provider/credential/tti.py:16\nmsgid \"\"\n\"Image size, only cogview-3-plus supports this parameter. Optional range: \"\n\"[1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440], the \"\n\"default is 1024x1024.\"\nmsgstr \"\"\n\"圖片尺寸，僅 cogview-3-plus 支持該參數。可選範圍：\"\n\"[1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440]，默認是\"\n\"1024x1024。\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:34\nmsgid \"\"\n\"Have strong multi-modal understanding capabilities. Able to understand up to \"\n\"five images simultaneously and supports video content understanding\"\nmsgstr \"具有強大的多模態理解能力。能夠同時理解多達五張圖像，並支持視頻內容理解\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:37\nmsgid \"\"\n\"Focus on single picture understanding. Suitable for scenarios requiring \"\n\"efficient image analysis\"\nmsgstr \"專注於單圖理解。適用於需要高效圖像解析的場景\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:40\nmsgid \"\"\n\"Focus on single picture understanding. Suitable for scenarios requiring \"\n\"efficient image analysis (free)\"\nmsgstr \"專注於單圖理解。適用於需要高效圖像解析的場景(免費)\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:46\nmsgid \"\"\n\"Quickly and accurately generate images based on user text descriptions. \"\n\"Resolution supports 1024x1024\"\nmsgstr \"根據用戶文字描述快速、精準生成圖像。解析度支持1024x1024\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:49\nmsgid \"\"\n\"Generate high-quality images based on user text descriptions, supporting \"\n\"multiple image sizes\"\nmsgstr \"根據用戶文字描述生成高質量圖像，支持多圖片尺寸\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:52\nmsgid \"\"\n\"Generate high-quality images based on user text descriptions, supporting \"\n\"multiple image sizes (free)\"\nmsgstr \"根據用戶文字描述生成高質量圖像，支持多圖片尺寸(免費)\"\n\n#: apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py:75\nmsgid \"zhipu AI\"\nmsgstr \"智譜 AI\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:32\n#: apps/models_provider/serializers/model_apply_serializers.py:37\nmsgid \"vector text\"\nmsgstr \"向量文本\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:33\nmsgid \"vector text list\"\nmsgstr \"向量文本列表\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:41\nmsgid \"text\"\nmsgstr \"文本\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:42\nmsgid \"metadata\"\nmsgstr \"元數據\"\n\n#: apps/models_provider/serializers/model_apply_serializers.py:47\nmsgid \"query\"\nmsgstr \"查詢\"\n\n#: apps/models_provider/serializers/model_serializer.py:44\n#: apps/models_provider/serializers/model_serializer.py:257\nmsgid \"parameter configuration\"\nmsgstr \"參數配置\"\n\n#: apps/models_provider/serializers/model_serializer.py:45\n#: apps/models_provider/serializers/model_serializer.py:222\n#: apps/models_provider/serializers/model_serializer.py:258\nmsgid \"certification information\"\nmsgstr \"認證信息\"\n\n#: apps/models_provider/serializers/model_serializer.py:118\nmsgid \"Shared models cannot be deleted or modified\"\nmsgstr \"共享模型不能被刪除或修改\"\n\n#: apps/models_provider/serializers/model_serializer.py:230\n#: apps/models_provider/serializers/model_serializer.py:269\n#, python-brace-format\nmsgid \"base model【{model_name}】already exists\"\nmsgstr \"模型【{model_name}】已存在\"\n\n#: apps/models_provider/serializers/model_serializer.py:309\nmsgid \"Model saving failed\"\nmsgstr \"模型保存失敗\"\n\n#: apps/models_provider/views/model.py:60\n#: apps/models_provider/views/model.py:61\n#: apps/models_provider/views/model.py:62 apps/shared/views/shared_model.py:55\n#: apps/shared/views/shared_model.py:56 apps/shared/views/shared_model.py:57\nmsgid \"Create model\"\nmsgstr \"創建模型\"\n\n#: apps/models_provider/views/model.py:90\n#: apps/models_provider/views/model.py:91\n#: apps/models_provider/views/model.py:92 apps/shared/views/shared_model.py:84\n#: apps/shared/views/shared_model.py:85 apps/shared/views/shared_model.py:86\nmsgid \"Query model list\"\nmsgstr \"查詢模型列表\"\n\n#: apps/models_provider/views/model.py:107\n#: apps/models_provider/views/model.py:108\n#: apps/models_provider/views/model.py:109\n#: apps/shared/views/shared_model.py:101 apps/shared/views/shared_model.py:102\n#: apps/shared/views/shared_model.py:103\nmsgid \"Update model\"\nmsgstr \"更新模型\"\n\n#: apps/models_provider/views/model.py:125\n#: apps/models_provider/views/model.py:126\n#: apps/models_provider/views/model.py:127\n#: apps/shared/views/shared_model.py:119 apps/shared/views/shared_model.py:120\n#: apps/shared/views/shared_model.py:121\nmsgid \"Delete model\"\nmsgstr \"刪除模型\"\n\n#: apps/models_provider/views/model.py:140\n#: apps/models_provider/views/model.py:141\n#: apps/models_provider/views/model.py:142\n#: apps/shared/views/shared_model.py:133 apps/shared/views/shared_model.py:134\n#: apps/shared/views/shared_model.py:135\nmsgid \"Query model details\"\nmsgstr \"查詢模型詳情\"\n\n#: apps/models_provider/views/model.py:155\n#: apps/models_provider/views/model.py:156\n#: apps/models_provider/views/model.py:157\n#: apps/shared/views/shared_model.py:148 apps/shared/views/shared_model.py:149\n#: apps/shared/views/shared_model.py:150\nmsgid \"Get model parameter form\"\nmsgstr \"獲取模型參數表單\"\n\n#: apps/models_provider/views/model.py:167\n#: apps/models_provider/views/model.py:168\n#: apps/models_provider/views/model.py:169\n#: apps/shared/views/shared_model.py:160 apps/shared/views/shared_model.py:161\n#: apps/shared/views/shared_model.py:162\nmsgid \"Save model parameter form\"\nmsgstr \"保存模型參數表單\"\n\n#: apps/models_provider/views/model.py:187\n#: apps/models_provider/views/model.py:189\n#: apps/models_provider/views/model.py:191\n#: apps/shared/views/shared_model.py:179 apps/shared/views/shared_model.py:181\n#: apps/shared/views/shared_model.py:183\nmsgid \"\"\n\"Query model meta information, this interface does not carry authentication \"\n\"information\"\nmsgstr \"查詢模型元信息，該接口不攜帶認證信息\"\n\n#: apps/models_provider/views/model.py:204\n#: apps/models_provider/views/model.py:205\n#: apps/models_provider/views/model.py:206\n#: apps/shared/views/shared_model.py:196 apps/shared/views/shared_model.py:197\n#: apps/shared/views/shared_model.py:198\nmsgid \"Pause model download\"\nmsgstr \"下載模型暫停\"\n\n#: apps/models_provider/views/model.py:222\n#: apps/models_provider/views/model.py:223\n#: apps/models_provider/views/model.py:224\nmsgid \"Get Share model\"\nmsgstr \"獲取共享模型\"\n\n#: apps/models_provider/views/model_apply.py:25\n#: apps/models_provider/views/model_apply.py:26\n#: apps/models_provider/views/model_apply.py:27\n#: apps/models_provider/views/model_apply.py:37\n#: apps/models_provider/views/model_apply.py:38\n#: apps/models_provider/views/model_apply.py:39\nmsgid \"Vectorization documentation\"\nmsgstr \"向量化文檔\"\n\n#: apps/models_provider/views/model_apply.py:49\n#: apps/models_provider/views/model_apply.py:50\n#: apps/models_provider/views/model_apply.py:51\nmsgid \"Reorder documents\"\nmsgstr \"重新排序文檔\"\n\n#: apps/models_provider/views/provide.py:21\n#: apps/models_provider/views/provide.py:22\n#: apps/models_provider/views/provide.py:23\nmsgid \"Get a list of model suppliers\"\nmsgstr \"獲取模型供應商列表\"\n\n#: apps/models_provider/views/provide.py:43\n#: apps/models_provider/views/provide.py:44\n#: apps/models_provider/views/provide.py:45\nmsgid \"Get a list of model types\"\nmsgstr \"獲取模型類型列表\"\n\n#: apps/models_provider/views/provide.py:57\n#: apps/models_provider/views/provide.py:58\n#: apps/models_provider/views/provide.py:59\nmsgid \"Example of obtaining model list\"\nmsgstr \"獲取模型列表示例\"\n\n#: apps/models_provider/views/provide.py:75\n#: apps/models_provider/views/provide.py:76\n#: apps/models_provider/views/provide.py:77\nmsgid \"Get model default parameters\"\nmsgstr \"獲取模型默認參數\"\n\n#: apps/models_provider/views/provide.py:92\n#: apps/models_provider/views/provide.py:93\n#: apps/models_provider/views/provide.py:94\nmsgid \"Get the model creation form\"\nmsgstr \"獲取模型創建表單\"\n\n#: apps/oss/serializers/file.py:80\nmsgid \"File not found\"\nmsgstr \"文件未找到\"\n\n#: apps/oss/views/file.py:21 apps/oss/views/file.py:22\n#: apps/oss/views/file.py:23\nmsgid \"Upload file\"\nmsgstr \"上傳文件\"\n\n#: apps/oss/views/file.py:27 apps/oss/views/file.py:41\n#: apps/oss/views/file.py:53\nmsgid \"File\"\nmsgstr \"文件\"\n\n#: apps/oss/views/file.py:36 apps/oss/views/file.py:37\n#: apps/oss/views/file.py:38\nmsgid \"Get file\"\nmsgstr \"獲取文件\"\n\n#: apps/oss/views/file.py:48 apps/oss/views/file.py:49\n#: apps/oss/views/file.py:50\nmsgid \"Delete file\"\nmsgstr \"刪除文件\"\n\n#: apps/resource_manage/views/document.py:30\n#: apps/resource_manage/views/document.py:31\n#: apps/resource_manage/views/document.py:32\nmsgid \"Create system knowledge\"\nmsgstr \"創建系統知識庫\"\n\n#: apps/resource_manage/views/document.py:36\n#: apps/resource_manage/views/document.py:56\n#: apps/resource_manage/views/document.py:83\n#: apps/resource_manage/views/document.py:113\n#: apps/resource_manage/views/document.py:130\n#: apps/resource_manage/views/document.py:155\n#: apps/resource_manage/views/document.py:183\n#: apps/resource_manage/views/document.py:210\n#: apps/resource_manage/views/document.py:237\n#: apps/resource_manage/views/document.py:267\n#: apps/resource_manage/views/document.py:296\n#: apps/resource_manage/views/document.py:317\n#: apps/resource_manage/views/document.py:345\n#: apps/resource_manage/views/document.py:362\n#: apps/resource_manage/views/document.py:383\n#: apps/resource_manage/views/document.py:411\n#: apps/resource_manage/views/document.py:435\n#: apps/resource_manage/views/document.py:460\n#: apps/resource_manage/views/document.py:485\n#: apps/resource_manage/views/document.py:507\n#: apps/resource_manage/views/document.py:530\n#: apps/resource_manage/views/document.py:553\n#: apps/resource_manage/views/document.py:571\n#: apps/resource_manage/views/document.py:585\nmsgid \"System Knowledge/Documentation\"\nmsgstr \"系統知識庫/文檔\"\n\n#: apps/resource_manage/views/document.py:51\n#: apps/resource_manage/views/document.py:52\n#: apps/resource_manage/views/document.py:53\nmsgid \"Get system document\"\nmsgstr \"獲取文檔\"\n\n#: apps/resource_manage/views/document.py:77\n#: apps/resource_manage/views/document.py:78\n#: apps/resource_manage/views/document.py:79\nmsgid \"Segmented system document\"\nmsgstr \"分段文檔\"\n\n#: apps/resource_manage/views/document.py:108\n#: apps/resource_manage/views/document.py:109\n#: apps/resource_manage/views/document.py:110\nmsgid \"Get a list of system segment IDs\"\nmsgstr \"獲取分段ID列表\"\n\n#: apps/resource_manage/views/document.py:124\n#: apps/resource_manage/views/document.py:125\n#: apps/resource_manage/views/document.py:126\nmsgid \"Cancel system tasks in batches\"\nmsgstr \"批量取消任務\"\n\n#: apps/resource_manage/views/document.py:149\n#: apps/resource_manage/views/document.py:150\n#: apps/resource_manage/views/document.py:151\nmsgid \"Create system knowledges in batches\"\nmsgstr \"批量創建知識庫\"\n\n#: apps/resource_manage/views/document.py:177\n#: apps/resource_manage/views/document.py:178\n#: apps/resource_manage/views/document.py:179\nmsgid \"Batch sync system knowledges\"\nmsgstr \"批量同步知識庫\"\n\n#: apps/resource_manage/views/document.py:204\n#: apps/resource_manage/views/document.py:206\nmsgid \"Delete system document in batches\"\nmsgstr \"批量刪除文檔\"\n\n#: apps/resource_manage/views/document.py:205\nmsgid \"Delete system knowledge in batches\"\nmsgstr \"批量刪除知識庫\"\n\n#: apps/resource_manage/views/document.py:232\n#: apps/resource_manage/views/document.py:233\nmsgid \"Batch refresh system document vector library\"\nmsgstr \"批量刷新文檔向量庫\"\n\n#: apps/resource_manage/views/document.py:261\n#: apps/resource_manage/views/document.py:262\n#: apps/resource_manage/views/document.py:263\nmsgid \"Batch generate related system problems\"\nmsgstr \"批量生成相關問題\"\n\n#: apps/resource_manage/views/document.py:290\n#: apps/resource_manage/views/document.py:291\n#: apps/resource_manage/views/document.py:292\nmsgid \"Modify system document hit processing methods in batches\"\nmsgstr \"批量修改文檔命中處理方式\"\n\n#: apps/resource_manage/views/document.py:312\n#: apps/resource_manage/views/document.py:313\nmsgid \"Migrate system knowledges in batches\"\nmsgstr \"批量遷移知識庫\"\n\n#: apps/resource_manage/views/document.py:340\n#: apps/resource_manage/views/document.py:341\n#: apps/resource_manage/views/document.py:342\nmsgid \"Get system document details\"\nmsgstr \"獲取文檔詳情\"\n\n#: apps/resource_manage/views/document.py:356\n#: apps/resource_manage/views/document.py:357\n#: apps/resource_manage/views/document.py:358\nmsgid \"Modify system document\"\nmsgstr \"修改文檔\"\n\n#: apps/resource_manage/views/document.py:378\n#: apps/resource_manage/views/document.py:379\n#: apps/resource_manage/views/document.py:380\nmsgid \"Delete system document\"\nmsgstr \"刪除文檔\"\n\n#: apps/resource_manage/views/document.py:405\n#: apps/resource_manage/views/document.py:406\n#: apps/resource_manage/views/document.py:407\nmsgid \"Synchronize system web site types\"\nmsgstr \"同步網站類型\"\n\n#: apps/resource_manage/views/document.py:429\n#: apps/resource_manage/views/document.py:430\n#: apps/resource_manage/views/document.py:431\nmsgid \"Refresh system knowledge vector library\"\nmsgstr \"刷新文檔向量庫\"\n\n#: apps/resource_manage/views/document.py:454\n#: apps/resource_manage/views/document.py:455\n#: apps/resource_manage/views/document.py:456\nmsgid \"Cancel system task\"\nmsgstr \"取消任務\"\n\n#: apps/resource_manage/views/document.py:480\n#: apps/resource_manage/views/document.py:481\n#: apps/resource_manage/views/document.py:482\nmsgid \"Get system document by pagination\"\nmsgstr \"分頁獲取文檔\"\n\n#: apps/resource_manage/views/document.py:503\n#: apps/resource_manage/views/document.py:504\nmsgid \"Export system knowledge\"\nmsgstr \"導出知識庫\"\n\n#: apps/resource_manage/views/document.py:526\n#: apps/resource_manage/views/document.py:527\nmsgid \"Export Zip system knowledge\"\nmsgstr \"導出Zip知識庫\"\n\n#: apps/resource_manage/views/document.py:549\n#: apps/resource_manage/views/document.py:550\nmsgid \"Download system source file\"\nmsgstr \"下載系統源文件\"\n\n#: apps/resource_manage/views/document.py:567\n#: apps/resource_manage/views/document.py:568\nmsgid \"Get system QA template\"\nmsgstr \"獲取系統問答模板\"\n\n#: apps/resource_manage/views/document.py:581\n#: apps/resource_manage/views/document.py:582\nmsgid \"Get system form template\"\nmsgstr \"獲取系統表單模板\"\n\n#: apps/resource_manage/views/knowledge.py:26\n#: apps/resource_manage/views/knowledge.py:27\n#: apps/resource_manage/views/knowledge.py:28\nmsgid \"Get system knowledge list\"\nmsgstr \"獲取系統知識庫列表\"\n\n#: apps/resource_manage/views/knowledge.py:31\n#: apps/resource_manage/views/knowledge.py:50\n#: apps/resource_manage/views/knowledge.py:72\n#: apps/resource_manage/views/knowledge.py:87\n#: apps/resource_manage/views/knowledge.py:102\n#: apps/resource_manage/views/knowledge.py:121\n#: apps/resource_manage/views/knowledge.py:147\n#: apps/resource_manage/views/knowledge.py:174\n#: apps/resource_manage/views/knowledge.py:192\n#: apps/resource_manage/views/knowledge.py:210\n#: apps/resource_manage/views/knowledge.py:231\n#: apps/resource_manage/views/knowledge.py:252\n#: apps/resource_manage/views/knowledge.py:272\nmsgid \"System Knowledge\"\nmsgstr \"系統知識庫\"\n\n#: apps/resource_manage/views/knowledge.py:45\n#: apps/resource_manage/views/knowledge.py:46\n#: apps/resource_manage/views/knowledge.py:47\nmsgid \"Get system knowledge list by pagination\"\nmsgstr \"獲取系統知識庫分頁列表\"\n\n#: apps/resource_manage/views/knowledge.py:66\n#: apps/resource_manage/views/knowledge.py:67\n#: apps/resource_manage/views/knowledge.py:68\nmsgid \"Update system knowledge\"\nmsgstr \"更新系統知識庫\"\n\n#: apps/resource_manage/views/knowledge.py:82\n#: apps/resource_manage/views/knowledge.py:83\n#: apps/resource_manage/views/knowledge.py:84\nmsgid \"Get system knowledge\"\nmsgstr \"獲取知識庫\"\n\n#: apps/resource_manage/views/knowledge.py:97\n#: apps/resource_manage/views/knowledge.py:98\n#: apps/resource_manage/views/knowledge.py:99\nmsgid \"Delete system knowledge\"\nmsgstr \"刪除知識庫\"\n\n#: apps/resource_manage/views/knowledge.py:115\n#: apps/resource_manage/views/knowledge.py:116\n#: apps/resource_manage/views/knowledge.py:117\nmsgid \"Synchronize the system knowledge base of the website\"\nmsgstr \"同步網站知識庫\"\n\n#: apps/resource_manage/views/knowledge.py:141\n#: apps/resource_manage/views/knowledge.py:142\n#: apps/resource_manage/views/knowledge.py:143\nmsgid \"System Hit test list\"\nmsgstr \"命中測試列表\"\n\n#: apps/resource_manage/views/knowledge.py:168\n#: apps/resource_manage/views/knowledge.py:169\n#: apps/resource_manage/views/knowledge.py:170\nmsgid \"System Re-vectorize\"\nmsgstr \"重新向量化\"\n\n#: apps/resource_manage/views/knowledge.py:188\n#: apps/resource_manage/views/knowledge.py:189\nmsgid \"Export system knowledge base\"\nmsgstr \"導出系統知識庫\"\n\n#: apps/resource_manage/views/knowledge.py:206\n#: apps/resource_manage/views/knowledge.py:207\nmsgid \"Export system knowledge base containing images\"\nmsgstr \"導出包含圖片的系統知識庫\"\n\n#: apps/resource_manage/views/knowledge.py:225\n#: apps/resource_manage/views/knowledge.py:226\n#: apps/resource_manage/views/knowledge.py:227\nmsgid \"System generate related\"\nmsgstr \"生成相關\"\n\n#: apps/resource_manage/views/knowledge.py:247\n#: apps/resource_manage/views/knowledge.py:248\n#: apps/resource_manage/views/knowledge.py:249\nmsgid \"Get model for system knowledge base\"\nmsgstr \"獲取系統知識庫模型\"\n\n#: apps/resource_manage/views/knowledge.py:267\n#: apps/resource_manage/views/knowledge.py:268\n#: apps/resource_manage/views/knowledge.py:269\nmsgid \"Get embedding model for system knowledge base\"\nmsgstr \"獲取系統知識庫嵌入模型\"\n\n#: apps/resource_manage/views/paragraph.py:24\n#: apps/resource_manage/views/paragraph.py:25\n#: apps/resource_manage/views/paragraph.py:26\nmsgid \"System paragraph list\"\nmsgstr \"段落列表\"\n\n#: apps/resource_manage/views/paragraph.py:29\n#: apps/resource_manage/views/paragraph.py:50\n#: apps/resource_manage/views/paragraph.py:76\n#: apps/resource_manage/views/paragraph.py:93\n#: apps/resource_manage/views/paragraph.py:125\n#: apps/resource_manage/views/paragraph.py:151\n#: apps/resource_manage/views/paragraph.py:179\n#: apps/resource_manage/views/paragraph.py:201\n#: apps/resource_manage/views/paragraph.py:233\n#: apps/resource_manage/views/paragraph.py:259\n#: apps/resource_manage/views/paragraph.py:283\n#: apps/resource_manage/views/paragraph.py:314\n#: apps/resource_manage/views/paragraph.py:344\n#: apps/resource_manage/views/paragraph.py:370\nmsgid \"System Knowledge/Documentation/Paragraph\"\nmsgstr \"系統知識庫/文檔/段落\"\n\n#: apps/resource_manage/views/paragraph.py:45\n#: apps/resource_manage/views/paragraph.py:46\nmsgid \"Create system paragraph\"\nmsgstr \"創建段落\"\n\n#: apps/resource_manage/views/paragraph.py:70\n#: apps/resource_manage/views/paragraph.py:71\n#: apps/resource_manage/views/paragraph.py:72\nmsgid \"Batch system paragraph\"\nmsgstr \"批量關聯段落\"\n\n#: apps/resource_manage/views/paragraph.py:88\n#: apps/resource_manage/views/paragraph.py:89\nmsgid \"Migrate system paragraphs in batches\"\nmsgstr \"批量遷移段落\"\n\n#: apps/resource_manage/views/paragraph.py:119\n#: apps/resource_manage/views/paragraph.py:120\n#: apps/resource_manage/views/paragraph.py:121\nmsgid \"Batch generate system related\"\nmsgstr \"批量生成相關\"\n\n#: apps/resource_manage/views/paragraph.py:145\n#: apps/resource_manage/views/paragraph.py:146\n#: apps/resource_manage/views/paragraph.py:147\nmsgid \"Modify system paragraph data\"\nmsgstr \"修改段落數據\"\n\n#: apps/resource_manage/views/paragraph.py:174\n#: apps/resource_manage/views/paragraph.py:175\n#: apps/resource_manage/views/paragraph.py:176\nmsgid \"Get system paragraph details\"\nmsgstr \"獲取段落詳情\"\n\n#: apps/resource_manage/views/paragraph.py:196\n#: apps/resource_manage/views/paragraph.py:197\n#: apps/resource_manage/views/paragraph.py:198\nmsgid \"Delete system paragraph\"\nmsgstr \"刪除段落\"\n\n#: apps/resource_manage/views/paragraph.py:227\n#: apps/resource_manage/views/paragraph.py:228\n#: apps/resource_manage/views/paragraph.py:229\nmsgid \"Add system associated questions\"\nmsgstr \"添加關聯問題\"\n\n#: apps/resource_manage/views/paragraph.py:254\n#: apps/resource_manage/views/paragraph.py:255\n#: apps/resource_manage/views/paragraph.py:256\nmsgid \"Get a list of system paragraph questions\"\nmsgstr \"獲取段落問題列表\"\n\n#: apps/resource_manage/views/paragraph.py:277\n#: apps/resource_manage/views/paragraph.py:278\n#: apps/resource_manage/views/paragraph.py:279\nmsgid \"Disassociation system issue\"\nmsgstr \"取消關聯問題\"\n\n#: apps/resource_manage/views/paragraph.py:308\n#: apps/resource_manage/views/paragraph.py:309\n#: apps/resource_manage/views/paragraph.py:310\nmsgid \"Related system questions\"\nmsgstr \"關聯問題\"\n\n#: apps/resource_manage/views/paragraph.py:339\n#: apps/resource_manage/views/paragraph.py:340\n#: apps/resource_manage/views/paragraph.py:341\nmsgid \"Get system paragraph list by pagination\"\nmsgstr \"獲取段落列表\"\n\n#: apps/resource_manage/views/problem.py:23\n#: apps/resource_manage/views/problem.py:24\n#: apps/resource_manage/views/problem.py:25\nmsgid \"System question list\"\nmsgstr \"問題列表\"\n\n#: apps/resource_manage/views/problem.py:28\n#: apps/resource_manage/views/problem.py:50\n#: apps/resource_manage/views/problem.py:71\n#: apps/resource_manage/views/problem.py:94\n#: apps/resource_manage/views/problem.py:115\n#: apps/resource_manage/views/problem.py:135\n#: apps/resource_manage/views/problem.py:158\n#: apps/resource_manage/views/problem.py:182\nmsgid \"System Knowledge/Documentation/Paragraph/Question\"\nmsgstr \"系統知識庫/文檔/段落/問題\"\n\n#: apps/resource_manage/views/problem.py:44\n#: apps/resource_manage/views/problem.py:45\n#: apps/resource_manage/views/problem.py:46\nmsgid \"Create system question\"\nmsgstr \"創建問題\"\n\n#: apps/resource_manage/views/problem.py:66\n#: apps/resource_manage/views/problem.py:67\n#: apps/resource_manage/views/problem.py:68\nmsgid \"Get a list of associated system paragraphs\"\nmsgstr \"獲取關聯段落列表\"\n\n#: apps/resource_manage/views/problem.py:88\n#: apps/resource_manage/views/problem.py:89\n#: apps/resource_manage/views/problem.py:90\nmsgid \"Batch associated system paragraphs\"\nmsgstr \"批量關聯段落\"\n\n#: apps/resource_manage/views/problem.py:109\n#: apps/resource_manage/views/problem.py:110\n#: apps/resource_manage/views/problem.py:111\nmsgid \"Batch deletion system issues\"\nmsgstr \"批量刪除問題\"\n\n#: apps/resource_manage/views/problem.py:130\n#: apps/resource_manage/views/problem.py:131\n#: apps/resource_manage/views/problem.py:132\nmsgid \"Delete system question\"\nmsgstr \"刪除問題\"\n\n#: apps/resource_manage/views/problem.py:152\n#: apps/resource_manage/views/problem.py:153\n#: apps/resource_manage/views/problem.py:154\nmsgid \"Modify system question\"\nmsgstr \"修改問題\"\n\n#: apps/resource_manage/views/problem.py:177\n#: apps/resource_manage/views/problem.py:178\n#: apps/resource_manage/views/problem.py:179\nmsgid \"Get the list of system questions by page\"\nmsgstr \"分頁獲取問題列表\"\n\n#: apps/resource_manage/views/tool.py:24 apps/resource_manage/views/tool.py:25\n#: apps/resource_manage/views/tool.py:26\nmsgid \"Get system tool\"\nmsgstr \"獲取工具\"\n\n#: apps/resource_manage/views/tool.py:29 apps/resource_manage/views/tool.py:50\n#: apps/resource_manage/views/tool.py:65 apps/resource_manage/views/tool.py:80\n#: apps/resource_manage/views/tool.py:98 apps/resource_manage/views/tool.py:119\n#: apps/resource_manage/views/tool.py:137\n#: apps/resource_manage/views/tool.py:156\n#: apps/resource_manage/views/tool.py:179\nmsgid \"System Tool\"\nmsgstr \"工具\"\n\n#: apps/resource_manage/views/tool.py:44 apps/resource_manage/views/tool.py:45\n#: apps/resource_manage/views/tool.py:46\nmsgid \"Update system tool\"\nmsgstr \"更新工具\"\n\n#: apps/resource_manage/views/tool.py:60 apps/resource_manage/views/tool.py:61\n#: apps/resource_manage/views/tool.py:62\nmsgid \"Get system tool by id\"\nmsgstr \"獲取工具\"\n\n#: apps/resource_manage/views/tool.py:75 apps/resource_manage/views/tool.py:76\n#: apps/resource_manage/views/tool.py:77\nmsgid \"Delete system tool\"\nmsgstr \"刪除工具\"\n\n#: apps/resource_manage/views/tool.py:93 apps/resource_manage/views/tool.py:94\n#: apps/resource_manage/views/tool.py:95\nmsgid \"Get system tool list by pagination\"\nmsgstr \"獲取工具列表\"\n\n#: apps/resource_manage/views/tool.py:114\n#: apps/resource_manage/views/tool.py:115\n#: apps/resource_manage/views/tool.py:116\nmsgid \"Export system tool\"\nmsgstr \"導出工具\"\n\n#: apps/resource_manage/views/tool.py:132\n#: apps/resource_manage/views/tool.py:133\n#: apps/resource_manage/views/tool.py:134\nmsgid \"Debug system tool\"\nmsgstr \"調試工具\"\n\n#: apps/resource_manage/views/tool.py:150\n#: apps/resource_manage/views/tool.py:151\n#: apps/resource_manage/views/tool.py:152\nmsgid \"Check system code\"\nmsgstr \"檢查代碼\"\n\n#: apps/resource_manage/views/tool.py:173\n#: apps/resource_manage/views/tool.py:174\n#: apps/resource_manage/views/tool.py:175\nmsgid \"Edit system tool icon\"\nmsgstr \"修改工具圖標\"\n\n#: apps/role_setting/api/role_setting.py:16\n#: apps/role_setting/api/role_setting.py:22\n#: apps/role_setting/api/role_setting.py:33\n#: apps/role_setting/api/role_setting.py:143\n#: apps/role_setting/serializers/role_setting_serializers.py:193\n#: apps/workspace/api/workspace.py:81 apps/xpack/api/chat_user.py:104\nmsgid \"ID\"\nmsgstr \"\"\n\n#: apps/role_setting/api/role_setting.py:17\n#: apps/role_setting/api/role_setting.py:23\n#: apps/role_setting/api/role_setting.py:34 apps/users/serializers/user.py:258\n#: apps/xpack/api/knowledge_lark.py:24 apps/xpack/serializers/chat_user.py:235\nmsgid \"Name\"\nmsgstr \"用戶名\"\n\n#: apps/role_setting/api/role_setting.py:18\n#: apps/role_setting/api/role_setting.py:29\n#: apps/role_setting/serializers/role_setting_serializers.py:194\nmsgid \"Enable\"\nmsgstr \"啟用\"\n\n#: apps/role_setting/api/role_setting.py:26\nmsgid \"Permission\"\nmsgstr \"權限\"\n\n#: apps/role_setting/api/role_setting.py:37\nmsgid \"Children\"\nmsgstr \"子級\"\n\n#: apps/role_setting/api/role_setting.py:55\n#: apps/role_setting/serializers/role_setting_serializers.py:107\nmsgid \"Role type\"\nmsgstr \"角色類型\"\n\n#: apps/role_setting/api/role_setting.py:76\nmsgid \"Internal role\"\nmsgstr \"內置角色\"\n\n#: apps/role_setting/api/role_setting.py:80\nmsgid \"Custom role\"\nmsgstr \"自定義角色\"\n\n#: apps/role_setting/api/role_setting.py:108\n#: apps/role_setting/api/role_setting.py:128\n#: apps/role_setting/api/role_setting.py:164\n#: apps/role_setting/serializers/role_setting_serializers.py:110\n#: apps/role_setting/serializers/role_setting_serializers.py:329\n#: apps/users/api/user.py:26 apps/workspace/api/workspace.py:86\nmsgid \"Role ID\"\nmsgstr \"角色 ID\"\n\n#: apps/role_setting/api/role_setting.py:135\nmsgid \"User relation ID\"\nmsgstr \"用戶關係 ID\"\n\n#: apps/role_setting/api/role_setting.py:145\n#: apps/role_setting/api/role_setting.py:185\n#: apps/role_setting/serializers/role_setting_serializers.py:330\n#: apps/users/api/user.py:77 apps/users/serializers/login.py:27\n#: apps/users/serializers/user.py:56 apps/users/serializers/user.py:114\n#: apps/workspace/api/workspace.py:83 apps/workspace/api/workspace.py:124\n#: apps/workspace/serializers/workspace_serializers.py:240\n#: apps/xpack/api/auth_config.py:24 apps/xpack/api/chat_user.py:105\n#: apps/xpack/api/user_group.py:75 apps/xpack/serializers/chat_user.py:62\n#: apps/xpack/serializers/chat_user.py:564\nmsgid \"Username\"\nmsgstr \"用戶名\"\n\n#: apps/role_setting/api/role_setting.py:146 apps/workspace/api/workspace.py:84\n#: apps/xpack/api/chat_user.py:106\nmsgid \"Nickname\"\nmsgstr \"暱稱\"\n\n#: apps/role_setting/api/role_setting.py:148\nmsgid \"Workspace Name\"\nmsgstr \"工作空間\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:104\nmsgid \"Role name\"\nmsgstr \"角色名稱\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:122\n#: apps/role_setting/serializers/role_setting_serializers.py:200\n#: apps/role_setting/serializers/role_setting_serializers.py:325\n#: apps/role_setting/serializers/role_setting_serializers.py:337\nmsgid \"Role does not exist\"\nmsgstr \"角色不存在\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:124\n#: apps/role_setting/serializers/role_setting_serializers.py:202\nmsgid \"Cannot modify built-in role\"\nmsgstr \"不能修改內置角色\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:132\nmsgid \"Role name already exists\"\nmsgstr \"角色名稱已存在\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:152\nmsgid \"Invalid role type\"\nmsgstr \"無效的角色類型\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:204\nmsgid \"Cannot delete built-in role\"\nmsgstr \"無法刪除內置角色\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:262\n#: apps/users/api/user.py:135 apps/users/serializers/user.py:471\n#: apps/workspace/serializers/workspace_serializers.py:161\n#: apps/xpack/api/user_group.py:90 apps/xpack/serializers/chat_user.py:158\n#: apps/xpack/serializers/chat_user.py:172\n#: apps/xpack/serializers/chat_user.py:502\nmsgid \"User IDs\"\nmsgstr \"用戶 ID\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:267\n#: apps/users/api/user.py:30\nmsgid \"Workspace IDs\"\nmsgstr \"工作空間 ID\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:272\n#: apps/workspace/serializers/workspace_serializers.py:172\nmsgid \"Members\"\nmsgstr \"成員集合\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:312\n#: apps/workspace/serializers/workspace_serializers.py:223\nmsgid \"User relation does not exist\"\nmsgstr \"用戶關係不存在\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:316\n#: apps/workspace/serializers/workspace_serializers.py:226\nmsgid \"Cannot remove member from built-in role\"\nmsgstr \"不能從內置角色中移除成員\"\n\n#: apps/role_setting/serializers/role_setting_serializers.py:370\nmsgid \"Only update members to normal users\"\nmsgstr \"只能為普通用戶更新成員\"\n\n#: apps/role_setting/views/role_setting.py:39\n#: apps/role_setting/views/role_setting.py:40\n#: apps/role_setting/views/role_setting.py:41\nmsgid \"Get role permission template\"\nmsgstr \"獲取角色權限模板\"\n\n#: apps/role_setting/views/role_setting.py:62\n#: apps/role_setting/views/role_setting.py:63\n#: apps/role_setting/views/role_setting.py:64\nmsgid \"Create or update role\"\nmsgstr \"創建或更新角色\"\n\n#: apps/role_setting/views/role_setting.py:80\n#: apps/role_setting/views/role_setting.py:81\n#: apps/role_setting/views/role_setting.py:82\nmsgid \"Get role list\"\nmsgstr \"獲取角色列表\"\n\n#: apps/role_setting/views/role_setting.py:98\n#: apps/role_setting/views/role_setting.py:99\n#: apps/role_setting/views/role_setting.py:100\nmsgid \"Delete role\"\nmsgstr \"刪除角色\"\n\n#: apps/role_setting/views/role_setting.py:120\n#: apps/role_setting/views/role_setting.py:121\n#: apps/role_setting/views/role_setting.py:122\nmsgid \"Create or update role permission\"\nmsgstr \"創建或更新角色權限\"\n\n#: apps/role_setting/views/role_setting.py:140\n#: apps/role_setting/views/role_setting.py:141\n#: apps/role_setting/views/role_setting.py:142\nmsgid \"Get role permission\"\nmsgstr \"獲取角色權限\"\n\n#: apps/role_setting/views/role_setting.py:161\n#: apps/role_setting/views/role_setting.py:162\n#: apps/role_setting/views/role_setting.py:163\nmsgid \"Add member to system role\"\nmsgstr \"系統角色添加成員\"\n\n#: apps/role_setting/views/role_setting.py:186\n#: apps/role_setting/views/role_setting.py:187\n#: apps/role_setting/views/role_setting.py:188\nmsgid \"Remove member from system role\"\nmsgstr \"系統角色移除成員\"\n\n#: apps/role_setting/views/role_setting.py:205\n#: apps/role_setting/views/role_setting.py:206\n#: apps/role_setting/views/role_setting.py:207\nmsgid \"Get system role member list\"\nmsgstr \"獲取系統角色成員列表\"\n\n#: apps/role_setting/views/role_setting.py:223\n#: apps/role_setting/views/role_setting.py:224\n#: apps/role_setting/views/role_setting.py:225\nmsgid \"Get Workspace role list\"\nmsgstr \"獲取工作空間角色列表\"\n\n#: apps/role_setting/views/role_setting.py:227\n#: apps/role_setting/views/role_setting.py:248\n#: apps/role_setting/views/role_setting.py:273\n#: apps/role_setting/views/role_setting.py:292\nmsgid \"Workspace Role\"\nmsgstr \"工作空間角色\"\n\n#: apps/role_setting/views/role_setting.py:242\n#: apps/role_setting/views/role_setting.py:243\n#: apps/role_setting/views/role_setting.py:244\nmsgid \"Add member to workspace role\"\nmsgstr \"工作空間角色添加成員\"\n\n#: apps/role_setting/views/role_setting.py:268\n#: apps/role_setting/views/role_setting.py:269\n#: apps/role_setting/views/role_setting.py:270\nmsgid \"Remove member from workspace role\"\nmsgstr \"工作空間角色移除成員\"\n\n#: apps/role_setting/views/role_setting.py:287\n#: apps/role_setting/views/role_setting.py:288\n#: apps/role_setting/views/role_setting.py:289\nmsgid \"Get workspace role member list\"\nmsgstr \"獲取工作空間角色成員列表\"\n\n#: apps/shared/api/shared_knowledge.py:242 apps/xpack/api/knowledge_lark.py:54\nmsgid \"Folder token\"\nmsgstr \"文件夾 Token\"\n\n#: apps/shared/api/shared_tool.py:19 apps/shared/api/shared_tool.py:46\n#: apps/shared/api/shared_tool.py:93 apps/shared/api/shared_tool.py:133\n#: apps/shared/serializers/shared_tool.py:43\n#: apps/shared/serializers/shared_tool.py:83 apps/tools/serializers/tool.py:142\n#: apps/tools/serializers/tool.py:158 apps/tools/serializers/tool.py:454\nmsgid \"tool name\"\nmsgstr \"工具名稱\"\n\n#: apps/shared/api/shared_tool.py:26 apps/shared/api/shared_tool.py:53\n#: apps/shared/api/shared_tool.py:100 apps/shared/api/shared_tool.py:140\n#: apps/shared/serializers/shared_tool.py:44\n#: apps/shared/serializers/shared_tool.py:85 apps/tools/serializers/tool.py:144\n#: apps/tools/serializers/tool.py:159\nmsgid \"tool description\"\nmsgstr \"工具描述\"\n\n#: apps/shared/api/shared_tool.py:166 apps/shared/api/shared_tool.py:184\n#: apps/shared/api/shared_tool.py:256 apps/shared/api/shared_tool.py:288\n#: apps/shared/api/shared_tool.py:310 apps/shared/serializers/shared_tool.py:66\n#: apps/shared/serializers/shared_tool.py:107\n#: apps/tools/serializers/tool.py:267\nmsgid \"tool id\"\nmsgstr \"工具 ID\"\n\n#: apps/shared/serializers/shared_knowledge.py:35\n#: apps/shared/serializers/shared_model.py:20\n#: apps/shared/serializers/shared_tool.py:19\nmsgid \"workspace id list\"\nmsgstr \"工作空間ID\"\n\n#: apps/shared/serializers/shared_knowledge.py:36\n#: apps/shared/serializers/shared_model.py:21\n#: apps/shared/serializers/shared_tool.py:20\nmsgid \"authentication type\"\nmsgstr \"認證類型\"\n\n#: apps/shared/serializers/shared_knowledge.py:196\n#: apps/shared/serializers/shared_knowledge.py:216\n#: apps/shared/serializers/shared_knowledge.py:236\nmsgid \"Knowledge does not exist\"\nmsgstr \"知識庫不存在\"\n\n#: apps/shared/serializers/shared_knowledge.py:218\n#: apps/shared/serializers/shared_knowledge.py:238\nmsgid \"Only shared knowledge can be authorized\"\nmsgstr \"僅限共享知識庫可以被授權\"\n\n#: apps/shared/serializers/shared_tool.py:74\nmsgid \"Only shared tools can be deleted\"\nmsgstr \"僅限共享工具可以被刪除\"\n\n#: apps/shared/serializers/shared_tool.py:76\nmsgid \"System tools cannot be deleted\"\nmsgstr \"系統工具不能被刪除\"\n\n#: apps/shared/serializers/shared_tool.py:118\n#: apps/shared/serializers/shared_tool.py:138\nmsgid \"Tool does not exist\"\nmsgstr \"工具不存在\"\n\n#: apps/shared/serializers/shared_tool.py:120\n#: apps/shared/serializers/shared_tool.py:140\nmsgid \"Only shared tools can be authorized\"\nmsgstr \"僅限共享工具可以被授權\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:25\n#: apps/shared/views/shared_dataset_lark_views.py:26\n#: apps/shared/views/shared_dataset_lark_views.py:27\n#: apps/xpack/views/dataset_lark_views.py:23\n#: apps/xpack/views/dataset_lark_views.py:24\n#: apps/xpack/views/dataset_lark_views.py:25\nmsgid \"Create a lark knowledge base\"\nmsgstr \"創建知識庫\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:44\n#: apps/shared/views/shared_dataset_lark_views.py:45\n#: apps/shared/views/shared_dataset_lark_views.py:46\n#: apps/xpack/views/dataset_lark_views.py:44\n#: apps/xpack/views/dataset_lark_views.py:45\n#: apps/xpack/views/dataset_lark_views.py:46\nmsgid \"Update a lark knowledge base\"\nmsgstr \"更新飛書知識庫\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:67\n#: apps/shared/views/shared_dataset_lark_views.py:68\n#: apps/shared/views/shared_dataset_lark_views.py:69\n#: apps/xpack/views/dataset_lark_views.py:67\n#: apps/xpack/views/dataset_lark_views.py:68\n#: apps/xpack/views/dataset_lark_views.py:69\nmsgid \"Get document list from lark\"\nmsgstr \"獲取飛書文檔列表\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:72\n#: apps/shared/views/shared_dataset_lark_views.py:90\n#: apps/shared/views/shared_dataset_lark_views.py:109\n#: apps/shared/views/shared_dataset_lark_views.py:129\n#: apps/shared/views/shared_document.py:36\n#: apps/shared/views/shared_document.py:56\n#: apps/shared/views/shared_document.py:83\n#: apps/shared/views/shared_document.py:114\n#: apps/shared/views/shared_document.py:131\n#: apps/shared/views/shared_document.py:156\n#: apps/shared/views/shared_document.py:184\n#: apps/shared/views/shared_document.py:211\n#: apps/shared/views/shared_document.py:238\n#: apps/shared/views/shared_document.py:268\n#: apps/shared/views/shared_document.py:297\n#: apps/shared/views/shared_document.py:318\n#: apps/shared/views/shared_document.py:346\n#: apps/shared/views/shared_document.py:363\n#: apps/shared/views/shared_document.py:384\n#: apps/shared/views/shared_document.py:412\n#: apps/shared/views/shared_document.py:436\n#: apps/shared/views/shared_document.py:461\n#: apps/shared/views/shared_document.py:486\n#: apps/shared/views/shared_document.py:508\n#: apps/shared/views/shared_document.py:531\n#: apps/shared/views/shared_document.py:554\n#: apps/shared/views/shared_document.py:575\n#: apps/shared/views/shared_document.py:602\n#: apps/shared/views/shared_document.py:629\n#: apps/shared/views/shared_document.py:651\n#: apps/shared/views/shared_document.py:665\nmsgid \"Shared Knowledge/Documentation\"\nmsgstr \"共享知識庫/文檔\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:85\n#: apps/shared/views/shared_dataset_lark_views.py:86\n#: apps/shared/views/shared_dataset_lark_views.py:87\n#: apps/xpack/views/dataset_lark_views.py:86\n#: apps/xpack/views/dataset_lark_views.py:87\n#: apps/xpack/views/dataset_lark_views.py:88\nmsgid \"Import documents to the lark knowledge base\"\nmsgstr \"導入文檔到飛書知識庫\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:104\n#: apps/shared/views/shared_dataset_lark_views.py:105\n#: apps/shared/views/shared_dataset_lark_views.py:106\n#: apps/xpack/views/dataset_lark_views.py:106\n#: apps/xpack/views/dataset_lark_views.py:107\n#: apps/xpack/views/dataset_lark_views.py:108\nmsgid \"Synchronize lark document\"\nmsgstr \"同步飛書文檔\"\n\n#: apps/shared/views/shared_dataset_lark_views.py:123\n#: apps/shared/views/shared_dataset_lark_views.py:124\n#: apps/shared/views/shared_dataset_lark_views.py:125\n#: apps/xpack/views/dataset_lark_views.py:126\n#: apps/xpack/views/dataset_lark_views.py:127\n#: apps/xpack/views/dataset_lark_views.py:128\nmsgid \"Batch synchronize lark document\"\nmsgstr \"批量同步飛書文檔\"\n\n#: apps/shared/views/shared_document.py:30\n#: apps/shared/views/shared_document.py:31\n#: apps/shared/views/shared_document.py:32\nmsgid \"Create shared document\"\nmsgstr \"創建共享文檔\"\n\n#: apps/shared/views/shared_document.py:51\n#: apps/shared/views/shared_document.py:52\n#: apps/shared/views/shared_document.py:53\nmsgid \"Get shared document\"\nmsgstr \"獲取共享文檔\"\n\n#: apps/shared/views/shared_document.py:77\n#: apps/shared/views/shared_document.py:78\n#: apps/shared/views/shared_document.py:79\nmsgid \"Segmented shared document\"\nmsgstr \"分段共享文檔\"\n\n#: apps/shared/views/shared_document.py:109\n#: apps/shared/views/shared_document.py:110\n#: apps/shared/views/shared_document.py:111\nmsgid \"Get a list of shared segment IDs\"\nmsgstr \"獲取共享分段 ID 列表\"\n\n#: apps/shared/views/shared_document.py:125\n#: apps/shared/views/shared_document.py:126\n#: apps/shared/views/shared_document.py:127\nmsgid \"Cancel shared tasks in batches\"\nmsgstr \"批量取消任務\"\n\n#: apps/shared/views/shared_document.py:150\n#: apps/shared/views/shared_document.py:151\n#: apps/shared/views/shared_document.py:152\nmsgid \"Create shared documents in batches\"\nmsgstr \"批量創建共享文檔\"\n\n#: apps/shared/views/shared_document.py:178\n#: apps/shared/views/shared_document.py:179\n#: apps/shared/views/shared_document.py:180\nmsgid \"Batch sync shared documents\"\nmsgstr \"批量同步共享文檔\"\n\n#: apps/shared/views/shared_document.py:205\n#: apps/shared/views/shared_document.py:206\n#: apps/shared/views/shared_document.py:207\nmsgid \"Delete shared documents in batches\"\nmsgstr \"批量刪除共享文檔\"\n\n#: apps/shared/views/shared_document.py:233\n#: apps/shared/views/shared_document.py:234\nmsgid \"Batch refresh shared document vector library\"\nmsgstr \"批量刷新共享文檔向量庫\"\n\n#: apps/shared/views/shared_document.py:262\n#: apps/shared/views/shared_document.py:263\n#: apps/shared/views/shared_document.py:264\nmsgid \"Batch generate related shared problems\"\nmsgstr \"批量生成相關問題\"\n\n#: apps/shared/views/shared_document.py:291\n#: apps/shared/views/shared_document.py:292\n#: apps/shared/views/shared_document.py:293\nmsgid \"Modify shared document hit processing methods in batches\"\nmsgstr \"批量修改文檔命中處理方法\"\n\n#: apps/shared/views/shared_document.py:313\n#: apps/shared/views/shared_document.py:314\nmsgid \"Migrate shared documents in batches\"\nmsgstr \"批量遷移共享文檔\"\n\n#: apps/shared/views/shared_document.py:341\n#: apps/shared/views/shared_document.py:342\n#: apps/shared/views/shared_document.py:343\nmsgid \"Get shared document details\"\nmsgstr \"文檔文檔詳情\"\n\n#: apps/shared/views/shared_document.py:357\n#: apps/shared/views/shared_document.py:358\n#: apps/shared/views/shared_document.py:359\nmsgid \"Modify shared document\"\nmsgstr \"修改共享文檔\"\n\n#: apps/shared/views/shared_document.py:379\n#: apps/shared/views/shared_document.py:380\n#: apps/shared/views/shared_document.py:381\nmsgid \"Delete shared document\"\nmsgstr \"刪除共享文檔\"\n\n#: apps/shared/views/shared_document.py:406\n#: apps/shared/views/shared_document.py:407\n#: apps/shared/views/shared_document.py:408\nmsgid \"Synchronize shared web site types\"\nmsgstr \"同步共享網站類型\"\n\n#: apps/shared/views/shared_document.py:430\n#: apps/shared/views/shared_document.py:431\n#: apps/shared/views/shared_document.py:432\nmsgid \"Refresh shared document vector library\"\nmsgstr \"刷新共享文檔向量庫\"\n\n#: apps/shared/views/shared_document.py:455\n#: apps/shared/views/shared_document.py:456\n#: apps/shared/views/shared_document.py:457\nmsgid \"Cancel shared task\"\nmsgstr \"取消任務\"\n\n#: apps/shared/views/shared_document.py:481\n#: apps/shared/views/shared_document.py:482\n#: apps/shared/views/shared_document.py:483\nmsgid \"Get shared document by pagination\"\nmsgstr \"獲取共享文檔分頁列表\"\n\n#: apps/shared/views/shared_document.py:504\n#: apps/shared/views/shared_document.py:505\nmsgid \"Export shared document\"\nmsgstr \"導出共享文檔\"\n\n#: apps/shared/views/shared_document.py:527\n#: apps/shared/views/shared_document.py:528\nmsgid \"Export Zip shared document\"\nmsgstr \"導出共享文檔 Zip\"\n\n#: apps/shared/views/shared_document.py:550\n#: apps/shared/views/shared_document.py:551\nmsgid \"Download shared source file\"\nmsgstr \"下載共享源文件\"\n\n#: apps/shared/views/shared_document.py:569\n#: apps/shared/views/shared_document.py:571\nmsgid \"Create Web site shared documents\"\nmsgstr \"創建網站文檔\"\n\n#: apps/shared/views/shared_document.py:596\n#: apps/shared/views/shared_document.py:597\n#: apps/shared/views/shared_document.py:598\nmsgid \"Import QA and create shared documentation\"\nmsgstr \"導入問答並創建文檔\"\n\n#: apps/shared/views/shared_document.py:623\n#: apps/shared/views/shared_document.py:624\n#: apps/shared/views/shared_document.py:625\nmsgid \"Import tables and create shared documents\"\nmsgstr \"導入表格並創建文檔\"\n\n#: apps/shared/views/shared_document.py:647\n#: apps/shared/views/shared_document.py:648\nmsgid \"Get shared QA template\"\nmsgstr \"獲取共享問答模板\"\n\n#: apps/shared/views/shared_document.py:661\n#: apps/shared/views/shared_document.py:662\nmsgid \"Get shared form template\"\nmsgstr \"獲取共享表格模板\"\n\n#: apps/shared/views/shared_knowledge.py:28\n#: apps/shared/views/shared_knowledge.py:29\n#: apps/shared/views/shared_knowledge.py:30\nmsgid \"Get share resource knowledge\"\nmsgstr \"獲取共享資源知識庫\"\n\n#: apps/shared/views/shared_knowledge.py:48\n#: apps/shared/views/shared_knowledge.py:49\n#: apps/shared/views/shared_knowledge.py:50\nmsgid \"Get shared knowledge list by pagination\"\nmsgstr \"獲取共享知識庫分頁列表\"\n\n#: apps/shared/views/shared_knowledge.py:70\n#: apps/shared/views/shared_knowledge.py:71\n#: apps/shared/views/shared_knowledge.py:72\nmsgid \"Update shared knowledge\"\nmsgstr \"更新共享知識庫\"\n\n#: apps/shared/views/shared_knowledge.py:86\n#: apps/shared/views/shared_knowledge.py:87\n#: apps/shared/views/shared_knowledge.py:88\nmsgid \"Get shared knowledge\"\nmsgstr \"獲取共享知識庫\"\n\n#: apps/shared/views/shared_knowledge.py:101\n#: apps/shared/views/shared_knowledge.py:102\n#: apps/shared/views/shared_knowledge.py:103\nmsgid \"Delete shared knowledge\"\nmsgstr \"刪除共享知識庫\"\n\n#: apps/shared/views/shared_knowledge.py:119\n#: apps/shared/views/shared_knowledge.py:120\n#: apps/shared/views/shared_knowledge.py:121\nmsgid \"Synchronize the shared knowledge base of the website\"\nmsgstr \"同步共享知識庫網站\"\n\n#: apps/shared/views/shared_knowledge.py:145\n#: apps/shared/views/shared_knowledge.py:146\n#: apps/shared/views/shared_knowledge.py:147\nmsgid \"Shared Hit test list\"\nmsgstr \"命中測試列表\"\n\n#: apps/shared/views/shared_knowledge.py:172\n#: apps/shared/views/shared_knowledge.py:173\n#: apps/shared/views/shared_knowledge.py:174\nmsgid \"Shared Re-vectorize\"\nmsgstr \"重新向量化\"\n\n#: apps/shared/views/shared_knowledge.py:192\n#: apps/shared/views/shared_knowledge.py:193\nmsgid \"Export shared knowledge base\"\nmsgstr \"導出共享知識庫\"\n\n#: apps/shared/views/shared_knowledge.py:210\n#: apps/shared/views/shared_knowledge.py:211\nmsgid \"Export shared knowledge base containing images\"\nmsgstr \"導出包含圖片的共享知識庫\"\n\n#: apps/shared/views/shared_knowledge.py:229\n#: apps/shared/views/shared_knowledge.py:230\n#: apps/shared/views/shared_knowledge.py:231\nmsgid \"Shared generate related\"\nmsgstr \"生成相關\"\n\n#: apps/shared/views/shared_knowledge.py:251\n#: apps/shared/views/shared_knowledge.py:252\n#: apps/shared/views/shared_knowledge.py:253\nmsgid \"Get model for shared knowledge base\"\nmsgstr \"獲取共享知識庫模型\"\n\n#: apps/shared/views/shared_knowledge.py:271\n#: apps/shared/views/shared_knowledge.py:272\n#: apps/shared/views/shared_knowledge.py:273\nmsgid \"Get embedding model for shared knowledge base\"\nmsgstr \"獲取共享知識庫嵌入模型\"\n\n#: apps/shared/views/shared_knowledge.py:291\n#: apps/shared/views/shared_knowledge.py:293\nmsgid \"Authorization knowledge workspace\"\nmsgstr \"授權知識工作空間\"\n\n#: apps/shared/views/shared_knowledge.py:292\nmsgid \"Authorization knowledge workspace \"\nmsgstr \"授權知識工作空間\"\n\n#: apps/shared/views/shared_knowledge.py:307\n#: apps/shared/views/shared_knowledge.py:308\n#: apps/shared/views/shared_knowledge.py:309\nmsgid \"Get Authorization knowledge workspace\"\nmsgstr \"獲取授權知識工作空間\"\n\n#: apps/shared/views/shared_knowledge.py:326\n#: apps/shared/views/shared_knowledge.py:327\n#: apps/shared/views/shared_knowledge.py:328\nmsgid \"Create shared base knowledge\"\nmsgstr \"創建共享知識庫\"\n\n#: apps/shared/views/shared_knowledge.py:349\n#: apps/shared/views/shared_knowledge.py:350\n#: apps/shared/views/shared_knowledge.py:351\nmsgid \"Create shared web knowledge\"\nmsgstr \"創建 web 知識庫\"\n\n#: apps/shared/views/shared_knowledge.py:381\n#: apps/shared/views/shared_knowledge.py:382\n#: apps/shared/views/shared_knowledge.py:383\nmsgid \"Get shared workspace knowledge\"\nmsgstr \"獲取共享工作空間知識庫\"\n\n#: apps/shared/views/shared_knowledge.py:402\n#: apps/shared/views/shared_knowledge.py:403\n#: apps/shared/views/shared_knowledge.py:404\nmsgid \"Get shared workspace knowledge list by pagination\"\nmsgstr \"獲取共享工作空間知識庫分頁列表\"\n\n#: apps/shared/views/shared_model.py:213 apps/shared/views/shared_model.py:215\nmsgid \"Authorization model workspace\"\nmsgstr \"授權模型工作空間\"\n\n#: apps/shared/views/shared_model.py:214\nmsgid \"Authorization model workspace \"\nmsgstr \"授權模型工作空間\"\n\n#: apps/shared/views/shared_model.py:229 apps/shared/views/shared_model.py:230\n#: apps/shared/views/shared_model.py:231\nmsgid \"Get Authorization model workspace\"\nmsgstr \"獲取授權模型工作空間\"\n\n#: apps/shared/views/shared_model.py:248 apps/shared/views/shared_model.py:249\n#: apps/shared/views/shared_model.py:250\nmsgid \"Get Share model by workspace id\"\nmsgstr \"獲取共享模型工作空間 ID\"\n\n#: apps/shared/views/shared_model.py:265 apps/shared/views/shared_model.py:266\n#: apps/shared/views/shared_model.py:267\nmsgid \"Get Share model by workspace id with pagination\"\nmsgstr \"獲取共享模型工作空間 ID 分頁列表\"\n\n#: apps/shared/views/shared_paragraph.py:24\n#: apps/shared/views/shared_paragraph.py:25\n#: apps/shared/views/shared_paragraph.py:26\nmsgid \"Shared paragraph list\"\nmsgstr \"共享段落列表\"\n\n#: apps/shared/views/shared_paragraph.py:29\n#: apps/shared/views/shared_paragraph.py:50\n#: apps/shared/views/shared_paragraph.py:76\n#: apps/shared/views/shared_paragraph.py:93\n#: apps/shared/views/shared_paragraph.py:125\n#: apps/shared/views/shared_paragraph.py:151\n#: apps/shared/views/shared_paragraph.py:179\n#: apps/shared/views/shared_paragraph.py:201\n#: apps/shared/views/shared_paragraph.py:233\n#: apps/shared/views/shared_paragraph.py:259\n#: apps/shared/views/shared_paragraph.py:283\n#: apps/shared/views/shared_paragraph.py:314\n#: apps/shared/views/shared_paragraph.py:345\n#: apps/shared/views/shared_paragraph.py:371\nmsgid \"Shared Knowledge/Documentation/Paragraph\"\nmsgstr \"共享知識庫/文檔/段落\"\n\n#: apps/shared/views/shared_paragraph.py:45\n#: apps/shared/views/shared_paragraph.py:46\nmsgid \"Create shared paragraph\"\nmsgstr \"創建段落\"\n\n#: apps/shared/views/shared_paragraph.py:70\n#: apps/shared/views/shared_paragraph.py:71\n#: apps/shared/views/shared_paragraph.py:72\nmsgid \"Batch shared paragraph\"\nmsgstr \"批量關聯段落\"\n\n#: apps/shared/views/shared_paragraph.py:88\n#: apps/shared/views/shared_paragraph.py:89\nmsgid \"Migrate shared paragraphs in batches\"\nmsgstr \"批量遷移共享段落\"\n\n#: apps/shared/views/shared_paragraph.py:119\n#: apps/shared/views/shared_paragraph.py:120\n#: apps/shared/views/shared_paragraph.py:121\nmsgid \"Batch generate shared related\"\nmsgstr \"批量生成相關\"\n\n#: apps/shared/views/shared_paragraph.py:145\n#: apps/shared/views/shared_paragraph.py:146\n#: apps/shared/views/shared_paragraph.py:147\nmsgid \"Modify shared paragraph data\"\nmsgstr \"修改段落數據\"\n\n#: apps/shared/views/shared_paragraph.py:174\n#: apps/shared/views/shared_paragraph.py:175\n#: apps/shared/views/shared_paragraph.py:176\nmsgid \"Get shared paragraph details\"\nmsgstr \"獲取段落詳情\"\n\n#: apps/shared/views/shared_paragraph.py:196\n#: apps/shared/views/shared_paragraph.py:197\n#: apps/shared/views/shared_paragraph.py:198\nmsgid \"Delete shared paragraph\"\nmsgstr \"刪除段落\"\n\n#: apps/shared/views/shared_paragraph.py:227\n#: apps/shared/views/shared_paragraph.py:228\n#: apps/shared/views/shared_paragraph.py:229\nmsgid \"Add shared associated questions\"\nmsgstr \"添加關聯問題\"\n\n#: apps/shared/views/shared_paragraph.py:254\n#: apps/shared/views/shared_paragraph.py:255\n#: apps/shared/views/shared_paragraph.py:256\nmsgid \"Get a list of shared paragraph questions\"\nmsgstr \"獲取共享段落問題列表\"\n\n#: apps/shared/views/shared_paragraph.py:277\n#: apps/shared/views/shared_paragraph.py:278\n#: apps/shared/views/shared_paragraph.py:279\nmsgid \"Disassociation shared issue\"\nmsgstr \"取消關聯問題\"\n\n#: apps/shared/views/shared_paragraph.py:308\n#: apps/shared/views/shared_paragraph.py:309\n#: apps/shared/views/shared_paragraph.py:310\nmsgid \"Related shared questions\"\nmsgstr \"關聯問題\"\n\n#: apps/shared/views/shared_paragraph.py:340\n#: apps/shared/views/shared_paragraph.py:341\n#: apps/shared/views/shared_paragraph.py:342\nmsgid \"Get shared paragraph list by pagination\"\nmsgstr \"獲取段落列表\"\n\n#: apps/shared/views/shared_problem.py:23\n#: apps/shared/views/shared_problem.py:24\n#: apps/shared/views/shared_problem.py:25\nmsgid \"Shared question list\"\nmsgstr \"問題列表\"\n\n#: apps/shared/views/shared_problem.py:28\n#: apps/shared/views/shared_problem.py:50\n#: apps/shared/views/shared_problem.py:71\n#: apps/shared/views/shared_problem.py:94\n#: apps/shared/views/shared_problem.py:115\n#: apps/shared/views/shared_problem.py:135\n#: apps/shared/views/shared_problem.py:158\n#: apps/shared/views/shared_problem.py:182\nmsgid \"Shared Knowledge/Documentation/Paragraph/Question\"\nmsgstr \"知識庫/文檔/段落/問題\"\n\n#: apps/shared/views/shared_problem.py:44\n#: apps/shared/views/shared_problem.py:45\n#: apps/shared/views/shared_problem.py:46\nmsgid \"Create shared question\"\nmsgstr \"創建問題\"\n\n#: apps/shared/views/shared_problem.py:66\n#: apps/shared/views/shared_problem.py:67\n#: apps/shared/views/shared_problem.py:68\nmsgid \"Get a list of associated shared paragraphs\"\nmsgstr \"獲取關聯段落列表\"\n\n#: apps/shared/views/shared_problem.py:88\n#: apps/shared/views/shared_problem.py:89\n#: apps/shared/views/shared_problem.py:90\nmsgid \"Batch associated shared paragraphs\"\nmsgstr \"批量關聯段落\"\n\n#: apps/shared/views/shared_problem.py:109\n#: apps/shared/views/shared_problem.py:110\n#: apps/shared/views/shared_problem.py:111\nmsgid \"Batch deletion shared issues\"\nmsgstr \"批量刪除問題\"\n\n#: apps/shared/views/shared_problem.py:130\n#: apps/shared/views/shared_problem.py:131\n#: apps/shared/views/shared_problem.py:132\nmsgid \"Delete shared question\"\nmsgstr \"刪除問題\"\n\n#: apps/shared/views/shared_problem.py:152\n#: apps/shared/views/shared_problem.py:153\n#: apps/shared/views/shared_problem.py:154\nmsgid \"Modify shared question\"\nmsgstr \"修改問題\"\n\n#: apps/shared/views/shared_problem.py:177\n#: apps/shared/views/shared_problem.py:178\n#: apps/shared/views/shared_problem.py:179\nmsgid \"Get the list of shared questions by page\"\nmsgstr \"分頁獲取問題列表\"\n\n#: apps/shared/views/shared_tool.py:25 apps/shared/views/shared_tool.py:26\n#: apps/shared/views/shared_tool.py:27\nmsgid \"Get share resource tool\"\nmsgstr \"獲取共享資源工具\"\n\n#: apps/shared/views/shared_tool.py:43 apps/shared/views/shared_tool.py:44\n#: apps/shared/views/shared_tool.py:45\nmsgid \"Create shared tool\"\nmsgstr \"創建共享工具\"\n\n#: apps/shared/views/shared_tool.py:62 apps/shared/views/shared_tool.py:63\n#: apps/shared/views/shared_tool.py:64\nmsgid \"Update shared tool\"\nmsgstr \"更新共享工具\"\n\n#: apps/shared/views/shared_tool.py:78 apps/shared/views/shared_tool.py:79\n#: apps/shared/views/shared_tool.py:80\nmsgid \"Get shared tool\"\nmsgstr \"獲取共享工具\"\n\n#: apps/shared/views/shared_tool.py:93 apps/shared/views/shared_tool.py:94\n#: apps/shared/views/shared_tool.py:95\nmsgid \"Delete shared tool\"\nmsgstr \"刪除共享工具\"\n\n#: apps/shared/views/shared_tool.py:111 apps/shared/views/shared_tool.py:112\n#: apps/shared/views/shared_tool.py:113\nmsgid \"Get shared tool list by pagination\"\nmsgstr \"獲取共享工具列表\"\n\n#: apps/shared/views/shared_tool.py:134 apps/shared/views/shared_tool.py:135\n#: apps/shared/views/shared_tool.py:136\nmsgid \"Import shared tool\"\nmsgstr \"導入共享工具\"\n\n#: apps/shared/views/shared_tool.py:152 apps/shared/views/shared_tool.py:153\n#: apps/shared/views/shared_tool.py:154\nmsgid \"Export shared tool\"\nmsgstr \"導出共享工具\"\n\n#: apps/shared/views/shared_tool.py:170 apps/shared/views/shared_tool.py:171\n#: apps/shared/views/shared_tool.py:172\nmsgid \"Debug shared Tool\"\nmsgstr \"調試共享工具\"\n\n#: apps/shared/views/shared_tool.py:188 apps/shared/views/shared_tool.py:189\n#: apps/shared/views/shared_tool.py:190\nmsgid \"Check shared code\"\nmsgstr \"檢查代碼\"\n\n#: apps/shared/views/shared_tool.py:212 apps/shared/views/shared_tool.py:213\n#: apps/shared/views/shared_tool.py:214\nmsgid \"Edit shared tool icon\"\nmsgstr \"修改共享工具圖標\"\n\n#: apps/shared/views/shared_tool.py:233 apps/shared/views/shared_tool.py:235\nmsgid \"Authorization tool workspace\"\nmsgstr \"授權工作空間工具\"\n\n#: apps/shared/views/shared_tool.py:234\nmsgid \"Authorization tool workspace \"\nmsgstr \"授權工作空間工具\"\n\n#: apps/shared/views/shared_tool.py:249 apps/shared/views/shared_tool.py:250\n#: apps/shared/views/shared_tool.py:251\nmsgid \"Get Authorization tool workspace\"\nmsgstr \"獲取授權工作空間工具\"\n\n#: apps/shared/views/shared_tool.py:268 apps/shared/views/shared_tool.py:269\n#: apps/shared/views/shared_tool.py:270\nmsgid \"Get shared workspace tool\"\nmsgstr \"獲取共享工作空間工具\"\n\n#: apps/shared/views/shared_tool.py:289 apps/shared/views/shared_tool.py:290\n#: apps/shared/views/shared_tool.py:291\nmsgid \"Get shared workspace tool list by pagination\"\nmsgstr \"獲取共享工作空間工具分頁列表\"\n\n#: apps/system_manage/serializers/email_setting.py:28\nmsgid \"SMTP host\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:29\nmsgid \"SMTP port\"\nmsgstr \"\"\n\n#: apps/system_manage/serializers/email_setting.py:30\n#: apps/system_manage/serializers/email_setting.py:34\nmsgid \"Sender's email\"\nmsgstr \"發送者郵箱\"\n\n#: apps/system_manage/serializers/email_setting.py:31 apps/users/api/user.py:93\n#: apps/users/serializers/login.py:28 apps/users/serializers/user.py:57\n#: apps/users/serializers/user.py:126 apps/users/serializers/user.py:291\n#: apps/users/serializers/user.py:517 apps/xpack/api/auth_config.py:25\n#: apps/xpack/api/chat_user.py:71 apps/xpack/serializers/chat_auth.py:24\n#: apps/xpack/serializers/chat_user.py:74\n#: apps/xpack/serializers/chat_user.py:267\n#: apps/xpack/serializers/chat_user.py:601\nmsgid \"Password\"\nmsgstr \"密碼\"\n\n#: apps/system_manage/serializers/email_setting.py:32\nmsgid \"Whether to enable TLS\"\nmsgstr \"是否啟用 TLS\"\n\n#: apps/system_manage/serializers/email_setting.py:33\nmsgid \"Whether to enable SSL\"\nmsgstr \"是否啟用 SSL\"\n\n#: apps/system_manage/serializers/email_setting.py:49\nmsgid \"Email verification failed\"\nmsgstr \"郵件認證失敗\"\n\n#: apps/system_manage/serializers/user_resource_permission.py:53\nmsgid \"target id\"\nmsgstr \"當前 ID\"\n\n#: apps/system_manage/serializers/user_resource_permission.py:70\nmsgid \"Non-existent application|knowledge base id[\"\nmsgstr \"不存在的智能體|知識庫 ID[\"\n\n#: apps/system_manage/views/email_setting.py:50\n#: apps/system_manage/views/email_setting.py:51\n#: apps/system_manage/views/email_setting.py:52\nmsgid \"Create or update email settings\"\nmsgstr \"創建或更新郵件設置\"\n\n#: apps/system_manage/views/email_setting.py:55\n#: apps/system_manage/views/email_setting.py:70\n#: apps/system_manage/views/email_setting.py:86\nmsgid \"Email Settings\"\nmsgstr \"郵箱設置\"\n\n#: apps/system_manage/views/email_setting.py:66\n#: apps/system_manage/views/email_setting.py:67\nmsgid \"Test email settings\"\nmsgstr \"測試郵箱設置\"\n\n#: apps/system_manage/views/email_setting.py:82\n#: apps/system_manage/views/email_setting.py:83\n#: apps/system_manage/views/email_setting.py:84\nmsgid \"Get email settings\"\nmsgstr \"獲取郵箱設置\"\n\n#: apps/system_manage/views/system_profile.py:22\n#: apps/system_manage/views/system_profile.py:23\nmsgid \"Get MaxKB related information\"\nmsgstr \"獲取 MaxKB 相關信息\"\n\n#: apps/system_manage/views/system_profile.py:25\nmsgid \"System parameters\"\nmsgstr \"系統參數\"\n\n#: apps/system_manage/views/user_resource_permission.py:40\n#: apps/system_manage/views/user_resource_permission.py:41\nmsgid \"Obtain resource authorization list\"\nmsgstr \"獲取資源授權列表\"\n\n#: apps/system_manage/views/user_resource_permission.py:44\n#: apps/system_manage/views/user_resource_permission.py:60\nmsgid \"Resources authorization\"\nmsgstr \"資源授權\"\n\n#: apps/system_manage/views/user_resource_permission.py:55\n#: apps/system_manage/views/user_resource_permission.py:56\nmsgid \"Modify the resource authorization list\"\nmsgstr \"修改資源授權列表\"\n\n#: apps/tools/serializers/tool.py:118 apps/tools/serializers/tool.py:169\nmsgid \"variable name\"\nmsgstr \"變量名稱\"\n\n#: apps/tools/serializers/tool.py:122\nmsgid \"fields only support string|int|dict|array|float\"\nmsgstr \"欄位僅支持字符串|整數|字典|數組|浮點數\"\n\n#: apps/tools/serializers/tool.py:131\nmsgid \"field name\"\nmsgstr \"欄位名稱\"\n\n#: apps/tools/serializers/tool.py:132\nmsgid \"field label\"\nmsgstr \"標籤\"\n\n#: apps/tools/serializers/tool.py:146 apps/tools/serializers/tool.py:160\n#: apps/tools/serializers/tool.py:174\nmsgid \"tool content\"\nmsgstr \"工具內容\"\n\n#: apps/tools/serializers/tool.py:148 apps/tools/serializers/tool.py:161\n#: apps/tools/serializers/tool.py:175\nmsgid \"input field list\"\nmsgstr \"輸入欄位列表\"\n\n#: apps/tools/serializers/tool.py:150 apps/tools/serializers/tool.py:162\n#: apps/tools/serializers/tool.py:176\nmsgid \"init field list\"\nmsgstr \"內置欄位列表\"\n\n#: apps/tools/serializers/tool.py:163 apps/tools/serializers/tool.py:177\nmsgid \"init params\"\nmsgstr \"內置參數\"\n\n#: apps/tools/serializers/tool.py:170\nmsgid \"variable value\"\nmsgstr \"變量名稱\"\n\n#: apps/tools/serializers/tool.py:182\nmsgid \"function content\"\nmsgstr \"工具內容\"\n\n#: apps/tools/serializers/tool.py:238\nmsgid \"field has no value set\"\nmsgstr \"欄位未設置值\"\n\n#: apps/tools/serializers/tool.py:262\n#, python-brace-format\nmsgid \"Field: {name} Type: {_type} Value: {value} Type conversion error\"\nmsgstr \"欄位：{name} 類型：{_type} 值：{value} 類型轉換錯誤\"\n\n#: apps/tools/serializers/tool.py:275\nmsgid \"Tool not found\"\nmsgstr \"工具不存在\"\n\n#: apps/tools/serializers/tool.py:388\nmsgid \"function ID\"\nmsgstr \"工具 ID\"\n\n#: apps/tools/views/tool.py:33 apps/tools/views/tool.py:34\n#: apps/tools/views/tool.py:35\nmsgid \"Create tool\"\nmsgstr \"創建工具\"\n\n#: apps/tools/views/tool.py:56 apps/tools/views/tool.py:57\n#: apps/tools/views/tool.py:58\nmsgid \"Get tool by folder\"\nmsgstr \"通過文件夾獲取工具\"\n\n#: apps/tools/views/tool.py:77 apps/tools/views/tool.py:78\n#: apps/tools/views/tool.py:79\nmsgid \"Debug Tool\"\nmsgstr \"調試工具\"\n\n#: apps/tools/views/tool.py:98 apps/tools/views/tool.py:99\n#: apps/tools/views/tool.py:100\nmsgid \"Update tool\"\nmsgstr \"更新工具\"\n\n#: apps/tools/views/tool.py:122 apps/tools/views/tool.py:123\n#: apps/tools/views/tool.py:124\nmsgid \"Get tool\"\nmsgstr \"獲取工具\"\n\n#: apps/tools/views/tool.py:141 apps/tools/views/tool.py:142\n#: apps/tools/views/tool.py:143\nmsgid \"Delete tool\"\nmsgstr \"刪除工具\"\n\n#: apps/tools/views/tool.py:167 apps/tools/views/tool.py:168\n#: apps/tools/views/tool.py:169\nmsgid \"Get tool list by pagination\"\nmsgstr \"獲取工具列表\"\n\n#: apps/tools/views/tool.py:195 apps/tools/views/tool.py:196\n#: apps/tools/views/tool.py:197\nmsgid \"Import tool\"\nmsgstr \"導入工具\"\n\n#: apps/tools/views/tool.py:218 apps/tools/views/tool.py:219\n#: apps/tools/views/tool.py:220\nmsgid \"Export tool\"\nmsgstr \"導出工具\"\n\n#: apps/tools/views/tool.py:244 apps/tools/views/tool.py:245\n#: apps/tools/views/tool.py:246\nmsgid \"Check code\"\nmsgstr \"檢查代碼\"\n\n#: apps/tools/views/tool.py:268 apps/tools/views/tool.py:269\n#: apps/tools/views/tool.py:270\nmsgid \"Edit tool icon\"\nmsgstr \"修改工具圖標\"\n\n#: apps/users/api/user.py:154\nmsgid \"Email or Username\"\nmsgstr \"郵箱或用戶名\"\n\n#: apps/users/api/user.py:224\nmsgid \"Language\"\nmsgstr \"語言\"\n\n#: apps/users/serializers/login.py:29 apps/users/serializers/login.py:69\nmsgid \"captcha\"\nmsgstr \"驗證碼\"\n\n#: apps/users/serializers/login.py:50\n#: apps/xpack/serializers/chat_user_serializer.py:120\nmsgid \"Captcha code error or expiration\"\nmsgstr \"驗證碼錯誤或過期\"\n\n#: apps/users/serializers/login.py:55\n#: apps/xpack/serializers/auth_config_serializer.py:192\n#: apps/xpack/serializers/chat_user_serializer.py:125\n#: apps/xpack/serializers/qr_login/qr_login.py:37\nmsgid \"The user has been disabled, please contact the administrator!\"\nmsgstr \"用戶已被禁用，請聯繫管理員！\"\n\n#: apps/users/serializers/user.py:47\nmsgid \"Is Edit Password\"\nmsgstr \"是否編輯密碼\"\n\n#: apps/users/serializers/user.py:48\nmsgid \"permissions\"\nmsgstr \"無權限訪問\"\n\n#: apps/users/serializers/user.py:58 apps/users/serializers/user.py:106\n#: apps/users/serializers/user.py:250 apps/users/serializers/user.py:557\n#: apps/users/serializers/user.py:641 apps/xpack/api/chat_user.py:107\n#: apps/xpack/serializers/chat_user.py:54\n#: apps/xpack/serializers/chat_user.py:227\nmsgid \"Email\"\nmsgstr \"郵箱\"\n\n#: apps/users/serializers/user.py:59 apps/users/serializers/user.py:140\n#: apps/xpack/serializers/chat_user.py:88\nmsgid \"Nick name\"\nmsgstr \"暱稱\"\n\n#: apps/users/serializers/user.py:60 apps/users/serializers/user.py:145\n#: apps/users/serializers/user.py:263 apps/xpack/api/chat_user.py:108\n#: apps/xpack/serializers/chat_user.py:93\n#: apps/xpack/serializers/chat_user.py:240\nmsgid \"Phone\"\nmsgstr \"手機\"\n\n#: apps/users/serializers/user.py:120 apps/xpack/serializers/chat_user.py:68\nmsgid \"Username must be 4-64 characters long\"\nmsgstr \"用戶名必須為4-64個字符\"\n\n#: apps/users/serializers/user.py:133 apps/users/serializers/user.py:298\n#: apps/xpack/serializers/chat_user.py:81\n#: apps/xpack/serializers/chat_user.py:274\nmsgid \"\"\n\"The password must be 6-20 characters long and must be a combination of \"\n\"letters, numbers, and special characters.\"\nmsgstr \"密碼必須為6-20個字符，且必須包含大小写字母、數字和特殊字符。\"\n\n#: apps/users/serializers/user.py:170\nmsgid \"Email or username\"\nmsgstr \"郵箱或用戶名\"\n\n#: apps/users/serializers/user.py:226\nmsgid \"\"\n\"The community version supports up to 2 users. If you need more users, please \"\n\"contact us (https://fit2cloud.com/).\"\nmsgstr \"\"\n\"社區版支持最多2個用戶，如需更多用戶，請聯繫我們（https://fit2cloud.com/）。\"\n\n#: apps/users/serializers/user.py:270 apps/xpack/api/auth_config.py:31\n#: apps/xpack/api/chat_user.py:109 apps/xpack/serializers/chat_user.py:247\nmsgid \"Is Active\"\nmsgstr \"是否啟用\"\n\n#: apps/users/serializers/user.py:281 apps/xpack/serializers/chat_user.py:262\nmsgid \"Nickname is already in use\"\nmsgstr \"Nickname已被使用\"\n\n#: apps/users/serializers/user.py:286\nmsgid \"Email is already in use\"\nmsgstr \"郵箱已被使用\"\n\n#: apps/users/serializers/user.py:305 apps/xpack/serializers/chat_user.py:281\nmsgid \"Re Password\"\nmsgstr \"確認密碼\"\n\n#: apps/users/serializers/user.py:310 apps/users/serializers/user.py:522\n#: apps/users/serializers/user.py:529 apps/xpack/serializers/chat_user.py:286\n#: apps/xpack/serializers/chat_user.py:606\n#: apps/xpack/serializers/chat_user.py:613\nmsgid \"\"\n\"The confirmation password must be 6-20 characters long and must be a \"\n\"combination of letters, numbers, and special characters.\"\nmsgstr \"確認密碼必須為6-20個字符，且必須包含字母、數字和特殊字符。\"\n\n#: apps/users/serializers/user.py:333 apps/xpack/serializers/chat_user.py:309\nmsgid \"User does not exist\"\nmsgstr \"用戶不存在\"\n\n#: apps/users/serializers/user.py:348\nmsgid \"Unable to delete administrator\"\nmsgstr \"無法刪除管理員\"\n\n#: apps/users/serializers/user.py:366\nmsgid \"Cannot modify administrator status\"\nmsgstr \"不能修改管理員狀態\"\n\n#: apps/users/serializers/user.py:478 apps/xpack/serializers/chat_user.py:164\n#: apps/xpack/serializers/chat_user.py:192\n#: apps/xpack/serializers/chat_user.py:512\nmsgid \"User IDs cannot be empty\"\nmsgstr \"用戶 ID 不能為空\"\n\n#: apps/users/serializers/user.py:524 apps/xpack/serializers/chat_user.py:608\nmsgid \"Confirm Password\"\nmsgstr \"確認密碼\"\n\n#: apps/users/serializers/user.py:561 apps/users/serializers/user.py:647\n#: apps/xpack/api/knowledge_lark.py:26\nmsgid \"Type\"\nmsgstr \"類型\"\n\n#: apps/users/serializers/user.py:563 apps/users/serializers/user.py:651\nmsgid \"The type only supports register|reset_password\"\nmsgstr \"類型僅支持 register|reset_password\"\n\n#: apps/users/serializers/user.py:581\n#, python-brace-format\nmsgid \"Do not send emails again within {seconds} seconds\"\nmsgstr \"不要在 {seconds} 秒內再次發送郵件\"\n\n#: apps/users/serializers/user.py:611\nmsgid \"\"\n\"The email service has not been set up. Please contact the administrator to \"\n\"set up the email service in [Email Settings].\"\nmsgstr \"郵箱服務尚未設置，請聯繫管理員在 [郵箱設置] 中設置郵箱服務。\"\n\n#: apps/users/serializers/user.py:622\n#, python-brace-format\nmsgid \"【Intelligent knowledge base question and answer system-{action}】\"\nmsgstr \"【智能知識庫問答系統-{action}】\"\n\n#: apps/users/serializers/user.py:623\nmsgid \"User registration\"\nmsgstr \"用戶註冊\"\n\n#: apps/users/serializers/user.py:623 apps/users/views/user.py:248\n#: apps/users/views/user.py:249 apps/users/views/user.py:250\n#: apps/users/views/user.py:283 apps/users/views/user.py:284\n#: apps/users/views/user.py:285\nmsgid \"Change password\"\nmsgstr \"修改密碼\"\n\n#: apps/users/serializers/user.py:644\nmsgid \"Verification code\"\nmsgstr \"驗證碼\"\n\n#: apps/users/serializers/user.py:672\nmsgid \"language only support:\"\nmsgstr \"語言僅支持：\"\n\n#: apps/users/views/login.py:38 apps/users/views/login.py:39\n#: apps/users/views/login.py:40 apps/xpack/views/chat_user_auth.py:184\n#: apps/xpack/views/chat_user_auth.py:185\n#: apps/xpack/views/chat_user_auth.py:186\nmsgid \"Log in\"\nmsgstr \"登錄\"\n\n#: apps/users/views/login.py:55 apps/users/views/login.py:56\n#: apps/users/views/login.py:57 apps/xpack/views/chat_user_auth.py:203\n#: apps/xpack/views/chat_user_auth.py:204\n#: apps/xpack/views/chat_user_auth.py:205\nmsgid \"Sign out\"\nmsgstr \"登出\"\n\n#: apps/users/views/user.py:60 apps/users/views/user.py:61\n#: apps/users/views/user.py:62 apps/users/views/user.py:74\n#: apps/users/views/user.py:75 apps/xpack/views/system_chat_user.py:359\n#: apps/xpack/views/system_chat_user.py:360\n#: apps/xpack/views/system_chat_user.py:361\nmsgid \"Get current user information\"\nmsgstr \"獲取當前用戶信息\"\n\n#: apps/users/views/user.py:120 apps/users/views/user.py:121\n#: apps/users/views/user.py:122\nmsgid \"Get all user\"\nmsgstr \"獲取所有用戶\"\n\n#: apps/users/views/user.py:133 apps/users/views/user.py:134\n#: apps/users/views/user.py:135\nmsgid \"Get user list under workspace\"\nmsgstr \"獲取工作空間下用戶列表\"\n\n#: apps/users/views/user.py:147 apps/users/views/user.py:148\n#: apps/users/views/user.py:149\nmsgid \"Get user member under workspace\"\nmsgstr \"獲取工作空間下用戶成員\"\n\n#: apps/users/views/user.py:161 apps/users/views/user.py:162\n#: apps/users/views/user.py:163\nmsgid \"Create user\"\nmsgstr \"創建用戶\"\n\n#: apps/users/views/user.py:177 apps/users/views/user.py:178\n#: apps/users/views/user.py:179\nmsgid \"Get default password\"\nmsgstr \"獲取默認密碼\"\n\n#: apps/users/views/user.py:190 apps/users/views/user.py:191\n#: apps/users/views/user.py:192\nmsgid \"Delete user\"\nmsgstr \"刪除用戶\"\n\n#: apps/users/views/user.py:203 apps/users/views/user.py:204\n#: apps/users/views/user.py:205\nmsgid \"Get user information\"\nmsgstr \"獲取用戶信息\"\n\n#: apps/users/views/user.py:214 apps/users/views/user.py:215\n#: apps/users/views/user.py:216\nmsgid \"Update user information\"\nmsgstr \"更新當前用戶信息\"\n\n#: apps/users/views/user.py:232 apps/users/views/user.py:233\n#: apps/users/views/user.py:234\nmsgid \"Batch delete user\"\nmsgstr \"批量刪除用戶\"\n\n#: apps/users/views/user.py:266 apps/users/views/user.py:267\n#: apps/users/views/user.py:268 apps/workspace/views/workspace_chat_user.py:168\n#: apps/workspace/views/workspace_chat_user.py:169\n#: apps/workspace/views/workspace_chat_user.py:170\n#: apps/xpack/views/system_chat_user.py:197\n#: apps/xpack/views/system_chat_user.py:198\n#: apps/xpack/views/system_chat_user.py:199\nmsgid \"Get user paginated list\"\nmsgstr \"獲取用戶分頁列表\"\n\n#: apps/users/views/user.py:300 apps/users/views/user.py:301\n#: apps/users/views/user.py:302\nmsgid \"Send email\"\nmsgstr \"發送郵件\"\n\n#: apps/users/views/user.py:318 apps/users/views/user.py:319\n#: apps/users/views/user.py:320\nmsgid \"Check whether the verification code is correct\"\nmsgstr \"檢查驗證碼是否正確\"\n\n#: apps/users/views/user.py:335 apps/users/views/user.py:336\n#: apps/users/views/user.py:337\nmsgid \"Send email to current user\"\nmsgstr \"發送郵件給當前用戶\"\n\n#: apps/users/views/user.py:353 apps/users/views/user.py:354\n#: apps/users/views/user.py:355 apps/xpack/views/system_chat_user.py:336\n#: apps/xpack/views/system_chat_user.py:337\n#: apps/xpack/views/system_chat_user.py:338\nmsgid \"Modify current user password\"\nmsgstr \"修改當前用戶密碼\"\n\n#: apps/users/views/user.py:368 apps/xpack/views/system_chat_user.py:352\nmsgid \"Failed to change password\"\nmsgstr \"修改密碼失敗\"\n\n#: apps/workspace/api/workspace.py:73\n#: apps/workspace/serializers/workspace_serializers.py:213\nmsgid \"User Relation ID\"\nmsgstr \"用戶關係 ID\"\n\n#: apps/workspace/api/workspace.py:87\nmsgid \"Role Name\"\nmsgstr \"角色名稱\"\n\n#: apps/workspace/serializers/workspace_serializers.py:42\n#: apps/workspace/serializers/workspace_serializers.py:95\n#: apps/workspace/serializers/workspace_serializers.py:110\n#: apps/workspace/serializers/workspace_serializers.py:177\n#: apps/workspace/serializers/workspace_serializers.py:219\n#: apps/workspace/serializers/workspace_serializers.py:246\nmsgid \"Workspace does not exist\"\nmsgstr \"工作空間不存在\"\n\n#: apps/workspace/serializers/workspace_serializers.py:49\nmsgid \"Workspace name already exists\"\nmsgstr \"工作空間名稱已存在\"\n\n#: apps/workspace/serializers/workspace_serializers.py:97\n#: apps/workspace/serializers/workspace_serializers.py:112\nmsgid \"Default workspace cannot be deleted\"\nmsgstr \"默認工作空間不能被刪除\"\n\n#: apps/workspace/serializers/workspace_serializers.py:122\nmsgid \"Applications Resource\"\nmsgstr \"智能體資源\"\n\n#: apps/workspace/serializers/workspace_serializers.py:124\nmsgid \"Knowledge Resource\"\nmsgstr \"知識庫資源\"\n\n#: apps/workspace/serializers/workspace_serializers.py:130\nmsgid \"This workspace contains %s, cannot be deleted.\"\nmsgstr \"此工作空間包含 %s，不能被刪除。\"\n\n#: apps/workspace/serializers/workspace_serializers.py:166\nmsgid \"Role IDs\"\nmsgstr \"角色 IDs\"\n\n#: apps/workspace/views/workspace.py:32 apps/workspace/views/workspace.py:33\n#: apps/workspace/views/workspace.py:34\nmsgid \"Create or update workspace\"\nmsgstr \"創建或更新工作空間\"\n\n#: apps/workspace/views/workspace.py:45 apps/workspace/views/workspace.py:46\n#: apps/workspace/views/workspace.py:47\nmsgid \"Get system workspace list\"\nmsgstr \"獲取系統工作空間列表\"\n\n#: apps/workspace/views/workspace.py:58 apps/workspace/views/workspace.py:59\n#: apps/workspace/views/workspace.py:60\nmsgid \"Delete workspace\"\nmsgstr \"刪除工作空間\"\n\n#: apps/workspace/views/workspace.py:75 apps/workspace/views/workspace.py:76\n#: apps/workspace/views/workspace.py:77\nmsgid \"Check workspace can it be deleted\"\nmsgstr \"檢查工作空間是否可以被刪除\"\n\n#: apps/workspace/views/workspace.py:95 apps/workspace/views/workspace.py:96\n#: apps/workspace/views/workspace.py:97\nmsgid \"Add member to system workspace\"\nmsgstr \"系統工作空間添加成員\"\n\n#: apps/workspace/views/workspace.py:114 apps/workspace/views/workspace.py:115\n#: apps/workspace/views/workspace.py:116\nmsgid \"Remove member from system workspace\"\nmsgstr \"系統工作空間移除成員\"\n\n#: apps/workspace/views/workspace.py:133 apps/workspace/views/workspace.py:134\n#: apps/workspace/views/workspace.py:135\nmsgid \"Get system workspace member list\"\nmsgstr \"獲取系統工作空間成員列表\"\n\n#: apps/workspace/views/workspace.py:151 apps/workspace/views/workspace.py:152\n#: apps/workspace/views/workspace.py:153\nmsgid \"Get workspace list\"\nmsgstr \"獲取工作空間列表\"\n\n#: apps/workspace/views/workspace.py:164 apps/workspace/views/workspace.py:165\n#: apps/workspace/views/workspace.py:166\nmsgid \"Add member to workspace\"\nmsgstr \"工作空間添加成員\"\n\n#: apps/workspace/views/workspace.py:183 apps/workspace/views/workspace.py:184\n#: apps/workspace/views/workspace.py:185\nmsgid \"Remove member from workspace\"\nmsgstr \"工作空間移除成員\"\n\n#: apps/workspace/views/workspace.py:202 apps/workspace/views/workspace.py:203\n#: apps/workspace/views/workspace.py:204\nmsgid \"Get workspace member list\"\nmsgstr \"獲取工作空間成員列表\"\n\n#: apps/workspace/views/workspace.py:219 apps/workspace/views/workspace.py:220\n#: apps/workspace/views/workspace.py:221\nmsgid \"Get workspace list by current user\"\nmsgstr \"獲取當前用戶工作空間列表\"\n\n#: apps/workspace/views/workspace.py:232 apps/workspace/views/workspace.py:233\n#: apps/workspace/views/workspace.py:234\nmsgid \"Get workspace list by user\"\nmsgstr \"根據用戶獲取工作空間列表\"\n\n#: apps/workspace/views/workspace.py:246 apps/workspace/views/workspace.py:247\n#: apps/workspace/views/workspace.py:248\nmsgid \"Get current user role list\"\nmsgstr \"獲取當前用戶角色列表\"\n\n#: apps/workspace/views/workspace_chat_user.py:44\n#: apps/workspace/views/workspace_chat_user.py:45\n#: apps/workspace/views/workspace_chat_user.py:46\n#: apps/workspace/views/workspace_chat_user.py:51\n#: apps/xpack/views/system_chat_user.py:57\n#: apps/xpack/views/system_chat_user.py:58\n#: apps/xpack/views/system_chat_user.py:59\nmsgid \"Create chat user\"\nmsgstr \"創建對話用戶\"\n\n#: apps/workspace/views/workspace_chat_user.py:47\n#: apps/workspace/views/workspace_chat_user.py:51\n#: apps/workspace/views/workspace_chat_user.py:63\n#: apps/workspace/views/workspace_chat_user.py:67\n#: apps/workspace/views/workspace_chat_user.py:76\n#: apps/workspace/views/workspace_chat_user.py:87\n#: apps/workspace/views/workspace_chat_user.py:92\n#: apps/workspace/views/workspace_chat_user.py:105\n#: apps/workspace/views/workspace_chat_user.py:120\n#: apps/workspace/views/workspace_chat_user.py:124\n#: apps/workspace/views/workspace_chat_user.py:136\n#: apps/workspace/views/workspace_chat_user.py:140\n#: apps/workspace/views/workspace_chat_user.py:152\n#: apps/workspace/views/workspace_chat_user.py:171\nmsgid \"Workspace/Chat user\"\nmsgstr \"工作空間/對話用戶\"\n\n#: apps/workspace/views/workspace_chat_user.py:60\n#: apps/workspace/views/workspace_chat_user.py:61\n#: apps/workspace/views/workspace_chat_user.py:62\n#: apps/workspace/views/workspace_chat_user.py:67\n#: apps/xpack/views/system_chat_user.py:86\n#: apps/xpack/views/system_chat_user.py:87\n#: apps/xpack/views/system_chat_user.py:88\nmsgid \"Delete chat user\"\nmsgstr \"刪除對話用戶\"\n\n#: apps/workspace/views/workspace_chat_user.py:73\n#: apps/workspace/views/workspace_chat_user.py:74\n#: apps/workspace/views/workspace_chat_user.py:75\n#: apps/xpack/views/system_chat_user.py:99\n#: apps/xpack/views/system_chat_user.py:100\n#: apps/xpack/views/system_chat_user.py:101\nmsgid \"Get chat user information\"\nmsgstr \"獲取對話用戶信息\"\n\n#: apps/workspace/views/workspace_chat_user.py:84\n#: apps/workspace/views/workspace_chat_user.py:85\n#: apps/workspace/views/workspace_chat_user.py:86\n#: apps/workspace/views/workspace_chat_user.py:92\n#: apps/xpack/views/system_chat_user.py:110\n#: apps/xpack/views/system_chat_user.py:111\n#: apps/xpack/views/system_chat_user.py:112\nmsgid \"Update chat user information\"\nmsgstr \"更新對話用戶信息\"\n\n#: apps/workspace/views/workspace_chat_user.py:102\n#: apps/workspace/views/workspace_chat_user.py:103\n#: apps/workspace/views/workspace_chat_user.py:104\n#: apps/workspace/views/workspace_chat_user.py:261\n#: apps/workspace/views/workspace_chat_user.py:262\n#: apps/workspace/views/workspace_chat_user.py:263\n#: apps/xpack/views/system_chat_user.py:128\n#: apps/xpack/views/system_chat_user.py:129\n#: apps/xpack/views/system_chat_user.py:130\n#: apps/xpack/views/system_chat_user.py:318\n#: apps/xpack/views/system_chat_user.py:319\n#: apps/xpack/views/system_chat_user.py:320\nmsgid \"Get user list by group\"\nmsgstr \"獲取用戶組列表\"\n\n#: apps/workspace/views/workspace_chat_user.py:117\n#: apps/workspace/views/workspace_chat_user.py:118\n#: apps/workspace/views/workspace_chat_user.py:119\n#: apps/workspace/views/workspace_chat_user.py:124\n#: apps/xpack/views/system_chat_user.py:143\n#: apps/xpack/views/system_chat_user.py:144\n#: apps/xpack/views/system_chat_user.py:145\nmsgid \"Batch delete chat user\"\nmsgstr \"批量刪除對話用戶\"\n\n#: apps/workspace/views/workspace_chat_user.py:133\n#: apps/workspace/views/workspace_chat_user.py:134\n#: apps/workspace/views/workspace_chat_user.py:135\n#: apps/workspace/views/workspace_chat_user.py:140\n#: apps/xpack/views/system_chat_user.py:160\n#: apps/xpack/views/system_chat_user.py:161\n#: apps/xpack/views/system_chat_user.py:162\nmsgid \"Batch add chat user to group\"\nmsgstr \"批量添加對話用戶到用戶組\"\n\n#: apps/workspace/views/workspace_chat_user.py:149\n#: apps/workspace/views/workspace_chat_user.py:150\n#: apps/workspace/views/workspace_chat_user.py:151\n#: apps/xpack/views/system_chat_user.py:177\n#: apps/xpack/views/system_chat_user.py:178\n#: apps/xpack/views/system_chat_user.py:179\nmsgid \"Change chat user password\"\nmsgstr \"修改對話用戶密碼\"\n\n#: apps/workspace/views/workspace_chat_user.py:186\n#: apps/workspace/views/workspace_chat_user.py:187\n#: apps/workspace/views/workspace_chat_user.py:188\n#: apps/xpack/views/system_chat_user.py:230\n#: apps/xpack/views/system_chat_user.py:231\n#: apps/xpack/views/system_chat_user.py:232\nmsgid \"Create or update Chat User Group\"\nmsgstr \"創建或更新對話用戶組\"\n\n#: apps/workspace/views/workspace_chat_user.py:191\n#: apps/workspace/views/workspace_chat_user.py:202\n#: apps/workspace/views/workspace_chat_user.py:216\n#: apps/workspace/views/workspace_chat_user.py:232\n#: apps/workspace/views/workspace_chat_user.py:249\n#: apps/workspace/views/workspace_chat_user.py:264\nmsgid \"Workspace/User Group\"\nmsgstr \"工作空間/用戶組\"\n\n#: apps/workspace/views/workspace_chat_user.py:198\n#: apps/workspace/views/workspace_chat_user.py:199\n#: apps/workspace/views/workspace_chat_user.py:200\n#: apps/xpack/views/system_chat_user.py:243\n#: apps/xpack/views/system_chat_user.py:244\n#: apps/xpack/views/system_chat_user.py:245\nmsgid \"Get user group list\"\nmsgstr \"獲取用戶組列表\"\n\n#: apps/workspace/views/workspace_chat_user.py:211\n#: apps/workspace/views/workspace_chat_user.py:212\n#: apps/workspace/views/workspace_chat_user.py:213\n#: apps/xpack/views/system_chat_user.py:256\n#: apps/xpack/views/system_chat_user.py:257\n#: apps/xpack/views/system_chat_user.py:258\nmsgid \"Delete chat user group\"\nmsgstr \"刪除對話用戶組\"\n\n#: apps/workspace/views/workspace_chat_user.py:226\n#: apps/workspace/views/workspace_chat_user.py:227\n#: apps/workspace/views/workspace_chat_user.py:228\n#: apps/xpack/views/system_chat_user.py:273\n#: apps/xpack/views/system_chat_user.py:274\n#: apps/xpack/views/system_chat_user.py:275\nmsgid \"Add member to chat user group\"\nmsgstr \"添加成員到對話用戶組\"\n\n#: apps/workspace/views/workspace_chat_user.py:243\n#: apps/workspace/views/workspace_chat_user.py:244\n#: apps/workspace/views/workspace_chat_user.py:245\n#: apps/xpack/views/system_chat_user.py:295\n#: apps/xpack/views/system_chat_user.py:296\n#: apps/xpack/views/system_chat_user.py:297\nmsgid \"Remove member from chat user group\"\nmsgstr \"從對話用戶組移除成員\"\n\n#: apps/xpack/api/auth_config.py:29\nmsgid \"Auth Type\"\nmsgstr \"認證類型\"\n\n#: apps/xpack/api/auth_config.py:30\nmsgid \"Config\"\nmsgstr \"配置\"\n\n#: apps/xpack/api/auth_config.py:77\nmsgid \"Corp ID\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:78\nmsgid \"Agent ID\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:79\nmsgid \"App Secret\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:80\nmsgid \"Callback URL\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:84\nmsgid \"Key\"\nmsgstr \"\"\n\n#: apps/xpack/api/auth_config.py:106\nmsgid \"Access Token\"\nmsgstr \"\"\n\n#: apps/xpack/api/chat_user.py:31 apps/xpack/api/chat_user.py:83\n#: apps/xpack/api/chat_user.py:113 apps/xpack/serializers/chat_user.py:101\n#: apps/xpack/serializers/chat_user.py:177\n#: apps/xpack/serializers/chat_user.py:252\nmsgid \"User Group IDs\"\nmsgstr \"用戶組 IDs\"\n\n#: apps/xpack/api/chat_user.py:118\nmsgid \"User Group Names\"\nmsgstr \"用戶組名稱\"\n\n#: apps/xpack/api/chat_user.py:132 apps/xpack/serializers/chat_user.py:120\n#: apps/xpack/serializers/resource_chat_user.py:37\nmsgid \"Username or Nickname\"\nmsgstr \"用戶名或暱稱\"\n\n#: apps/xpack/api/chat_user.py:148 apps/xpack/serializers/chat_user.py:360\nmsgid \"Sync Type\"\nmsgstr \"同步類型\"\n\n#: apps/xpack/api/knowledge_lark.py:25\nmsgid \"Token\"\nmsgstr \"令牌\"\n\n#: apps/xpack/api/knowledge_lark.py:27\nmsgid \"Is Exist\"\nmsgstr \"是否存在\"\n\n#: apps/xpack/api/license.py:13\nmsgid \"corporation\"\nmsgstr \"公司\"\n\n#: apps/xpack/api/license.py:14\nmsgid \"isv\"\nmsgstr \"\"\n\n#: apps/xpack/api/license.py:15\nmsgid \"expired\"\nmsgstr \"過期時間\"\n\n#: apps/xpack/api/license.py:16\nmsgid \"product\"\nmsgstr \"產品\"\n\n#: apps/xpack/api/license.py:17\nmsgid \"edition\"\nmsgstr \"版本\"\n\n#: apps/xpack/api/license.py:18\nmsgid \"license version\"\nmsgstr \"license 版本\"\n\n#: apps/xpack/api/license.py:19\nmsgid \"count\"\nmsgstr \"數量\"\n\n#: apps/xpack/api/license.py:20\nmsgid \"serial number\"\nmsgstr \"序列號\"\n\n#: apps/xpack/api/license.py:21\nmsgid \"remark\"\nmsgstr \"備註\"\n\n#: apps/xpack/api/license.py:26\nmsgid \"message\"\nmsgstr \"消息\"\n\n#: apps/xpack/api/license.py:27\nmsgid \"license details\"\nmsgstr \"license 詳情\"\n\n#: apps/xpack/api/license.py:36\n#: apps/xpack/serializers/license/license_serializers.py:56\nmsgid \"license file\"\nmsgstr \"license 文件\"\n\n#: apps/xpack/api/license.py:37\nmsgid \"License file is required\"\nmsgstr \"license 文件是必需的\"\n\n#: apps/xpack/api/license.py:38\nmsgid \"Invalid license file format\"\nmsgstr \"無效的 license 文件格式\"\n\n#: apps/xpack/api/operate_log.py:12\n#: apps/xpack/serializers/operate_log_serializer.py:57\nmsgid \"menu\"\nmsgstr \"菜單\"\n\n#: apps/xpack/api/operate_log.py:13\n#: apps/xpack/serializers/operate_log_serializer.py:58\nmsgid \"operate\"\nmsgstr \"操作\"\n\n#: apps/xpack/api/operate_log.py:14\nmsgid \"menu_label\"\nmsgstr \"菜單標籤\"\n\n#: apps/xpack/api/operate_log.py:15\nmsgid \"operate_label\"\nmsgstr \"操作標籤\"\n\n#: apps/xpack/api/platform.py:35\nmsgid \"Platform type\"\nmsgstr \"平臺類型\"\n\n#: apps/xpack/api/platform.py:50\nmsgid \"Platform configuration\"\nmsgstr \"平臺配置\"\n\n#: apps/xpack/api/resource_chat_user_group.py:40\n#: apps/xpack/serializers/resource_chat_user.py:25\n#: apps/xpack/serializers/resource_chat_user_group.py:69\nmsgid \"is auth\"\nmsgstr \"是否認證\"\n\n#: apps/xpack/api/user_group.py:31 apps/xpack/api/user_group.py:99\nmsgid \"User Group ID\"\nmsgstr \"用戶組 ID\"\n\n#: apps/xpack/api/user_group.py:54 apps/xpack/serializers/chat_user.py:406\n#: apps/xpack/serializers/chat_user.py:563\nmsgid \"Group ID\"\nmsgstr \"用戶組 ID\"\n\n#: apps/xpack/api/user_group.py:114 apps/xpack/serializers/chat_user.py:541\nmsgid \"User group relation IDs\"\nmsgstr \"用戶組關係 ID\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:19\nmsgid \"theme color\"\nmsgstr \"主題顏色\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:21\nmsgid \"header font color\"\nmsgstr \"標題字體顏色\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:25\nmsgid \"float location type\"\nmsgstr \"浮動位置類型\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:26\nmsgid \"float location value\"\nmsgstr \"浮動位置值\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:30\nmsgid \"float location x\"\nmsgstr \"浮動位置 X\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:31\nmsgid \"float location y\"\nmsgstr \"浮動位置 Y\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:35\nmsgid \"show source\"\nmsgstr \"顯示來源\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:36\nmsgid \"show exec\"\nmsgstr \"顯示執行\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:38\nmsgid \"show history\"\nmsgstr \"顯示歷史\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:39\nmsgid \"draggable\"\nmsgstr \"是否可拖動\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:40\nmsgid \"show guide\"\nmsgstr \"顯示論壇\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:42\nmsgid \"icon url\"\nmsgstr \"圖標\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:43\nmsgid \"chat background\"\nmsgstr \"聊天背景\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:44\nmsgid \"chat background url\"\nmsgstr \"聊天背景地址\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:45\nmsgid \"avatar\"\nmsgstr \"頭像\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:46\nmsgid \"avatar url\"\nmsgstr \"頭像地址\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:47\nmsgid \"user avatar\"\nmsgstr \"用戶頭像\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:48\nmsgid \"user avatar url\"\nmsgstr \"用戶頭像地址\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:49\nmsgid \"float icon\"\nmsgstr \"浮動圖標\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:50\nmsgid \"float icon url\"\nmsgstr \"浮動圖標地址\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:51\nmsgid \"disclaimer\"\nmsgstr \"免責申明\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:52\nmsgid \"disclaimer value\"\nmsgstr \"免責申明內容\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:55\nmsgid \"show avatar\"\nmsgstr \"顯示頭像\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:56\nmsgid \"show user avatar\"\nmsgstr \"顯示用戶頭像\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:124\nmsgid \"Float location field type error\"\nmsgstr \"浮動位置欄位類型錯誤\"\n\n#: apps/xpack/serializers/application_setting_serializer.py:130\nmsgid \"Custom theme field type error\"\nmsgstr \"自定義主題欄位類型錯誤\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:27\n#: apps/xpack/serializers/platform_serializer.py:31\nmsgid \"App Secret is required\"\nmsgstr \"App Secret 是必填項\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:28\n#: apps/xpack/serializers/platform_serializer.py:26\n#: apps/xpack/serializers/platform_serializer.py:34\n#: apps/xpack/serializers/platform_serializer.py:40\n#: apps/xpack/serializers/platform_serializer.py:46\nmsgid \"Callback URL is required\"\nmsgstr \"Callback URL 是必填項\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:32\n#: apps/xpack/serializers/auth_config_serializer.py:41\nmsgid \"Corp ID is required\"\nmsgstr \"Corp ID 是必填項\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:33\n#: apps/xpack/serializers/platform_serializer.py:22\nmsgid \"Agent ID is required\"\nmsgstr \"Agent ID 是必填項\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:37\n#: apps/xpack/serializers/auth_config_serializer.py:42\nmsgid \"App Key is required\"\nmsgstr \"App Key 是必填項\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:53\nmsgid \"LDAP server cannot be empty\"\nmsgstr \"LDAP server不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:54\nmsgid \"Base DN cannot be empty\"\nmsgstr \"Base DN不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:55\nmsgid \"Password cannot be empty\"\nmsgstr \"密碼不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:56\nmsgid \"OU cannot be empty\"\nmsgstr \"OU不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:57\nmsgid \"LDAP filter cannot be empty\"\nmsgstr \"LDAP過濾器不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:58\nmsgid \"LDAP mapping cannot be empty\"\nmsgstr \"LDAP映射不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:62\nmsgid \"Authorization address cannot be empty\"\nmsgstr \"認證地址不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:63\nmsgid \"Token address cannot be empty\"\nmsgstr \"令牌地址不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:64\nmsgid \"User information address cannot be empty\"\nmsgstr \"用戶信息地址不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:65\nmsgid \"Scope cannot be empty\"\nmsgstr \"範圍不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:66\nmsgid \"Client ID cannot be empty\"\nmsgstr \"客戶端 ID 不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:67\nmsgid \"Client secret cannot be empty\"\nmsgstr \"客戶端密鑰不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:68\nmsgid \"Redirect address cannot be empty\"\nmsgstr \"重定向地址不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:69\nmsgid \"Field mapping cannot be empty\"\nmsgstr \"欄位映射不能為空\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:262\nmsgid \"Configuration information is wrong and failed to save\"\nmsgstr \"配置信息錯誤，保存失敗\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:288\nmsgid \"Connection failed\"\nmsgstr \"連接失敗\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:306\nmsgid \"Platform does not exist\"\nmsgstr \"平臺不存在\"\n\n#: apps/xpack/serializers/auth_config_serializer.py:316\nmsgid \"Unsupported platform type\"\nmsgstr \"不支持的平臺類型\"\n\n#: apps/xpack/serializers/channel/chat_manage.py:100\nmsgid \"Think: \"\nmsgstr \"思考内容: \"\n\n#: apps/xpack/serializers/channel/chat_manage.py:103\n#: apps/xpack/serializers/channel/chat_manage.py:105\nmsgid \"AI reply: \"\nmsgstr \"AI 回復: \"\n\n#: apps/xpack/serializers/channel/chat_manage.py:318\nmsgid \"Thinking, please wait a moment!\"\nmsgstr \"思考中，請稍等！\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:19\n#: apps/xpack/serializers/channel/wechat.py:91\n#: apps/xpack/serializers/channel/wechat.py:132\n#: apps/xpack/serializers/channel/wecom.py:78\n#: apps/xpack/serializers/channel/wecom.py:259\nmsgid \"The corresponding platform configuration was not found\"\nmsgstr \"未找到對應的平臺配置\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:27\n#: apps/xpack/serializers/channel/lark.py:117\nmsgid \"Currently only text messages are supported\"\nmsgstr \"目前僅支持文本消息\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:91\n#: apps/xpack/serializers/channel/wechat.py:163\n#: apps/xpack/serializers/channel/wecom.py:189\nmsgid \"Image download failed, check network\"\nmsgstr \"圖片下載失敗，請檢查網絡\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:92\n#: apps/xpack/serializers/channel/wechat.py:161\n#: apps/xpack/serializers/channel/wecom.py:185\nmsgid \"Please analyze the content of the image.\"\nmsgstr \"請分析圖片內容。\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:95\nmsgid \"DingTalk application: {user}\"\nmsgstr \"釘釘智能體: {user}\"\n\n#: apps/xpack/serializers/channel/ding_talk.py:106\n#: apps/xpack/serializers/channel/ding_talk.py:151\nmsgid \"Content generated by AI\"\nmsgstr \"AI 生成的內容\"\n\n#: apps/xpack/serializers/channel/lark.py:92\nmsgid \"Lark application: \"\nmsgstr \"飛書智能體: \"\n\n#: apps/xpack/serializers/channel/slack.py:116\nmsgid \"The corresponding platform configuration for Slack was not found\"\nmsgstr \"Slack 的對應平臺配置未找到\"\n\n#: apps/xpack/serializers/channel/slack.py:206\nmsgid \"Thinking...\"\nmsgstr \"思考中...\"\n\n#: apps/xpack/serializers/channel/slack.py:333\nmsgid \"Invalid json format.\"\nmsgstr \"json 格式無效。\"\n\n#: apps/xpack/serializers/channel/slack.py:339\nmsgid \"Invalid Slack request\"\nmsgstr \"Slack 請求無效\"\n\n#: apps/xpack/serializers/channel/slack.py:347\nmsgid \"Slack application: {user}\"\nmsgstr \"Slack 智能體: {user}\"\n\n#: apps/xpack/serializers/channel/slack.py:480\nmsgid \"Stop\"\nmsgstr \"停止\"\n\n#: apps/xpack/serializers/channel/tools.py:58\n#, python-brace-format\nmsgid \"\"\n\"Thinking about 【{question}】...If you want me to continue answering, please \"\n\"reply {trigger_message}\"\nmsgstr \"思考【{question}】...如果你想讓我繼續回答，請回復 {trigger_message}\"\n\n#: apps/xpack/serializers/channel/tools.py:158\nmsgid \"\"\n\"\\n\"\n\" ------------\\n\"\n\"[To be continued, reply \\\"Continue to answer the question]\"\nmsgstr \"\"\n\"\\n\"\n\"------------\\n\"\n\"[待續，回復 \\\"繼續回答問題]\"\n\n#: apps/xpack/serializers/channel/tools.py:238\n#, python-brace-format\nmsgid \"\"\n\"To be continued, reply \\\"{trigger_message}\\\" to continue answering the \"\n\"question\"\nmsgstr \"待續，回復 \\\"{trigger_message}\\\" 繼續回答問題\"\n\n#: apps/xpack/serializers/channel/wechat.py:143\n#, python-brace-format\nmsgid \"WeChat Official Account: {account}\"\nmsgstr \"微信公眾帳號: {account}\"\n\n#: apps/xpack/serializers/channel/wechat.py:150\n#: apps/xpack/serializers/channel/wecom.py:171\n#: apps/xpack/serializers/channel/wecom.py:175\nmsgid \"\"\n\"The app does not enable the speech-to-text function or the speech-to-text \"\n\"function fails.\"\nmsgstr \"智能體未開啟語音轉文字功能或語音轉文字功能失敗。\"\n\n#: apps/xpack/serializers/channel/wechat.py:189\nmsgid \"Message types not supported yet\"\nmsgstr \"消息類型暫不支持\"\n\n#: apps/xpack/serializers/channel/wechat.py:196\nmsgid \"Welcome to subscribe\"\nmsgstr \"歡迎訂閱\"\n\n#: apps/xpack/serializers/channel/wecom.py:86\nmsgid \"Enterprise WeChat user: \"\nmsgstr \"企業微信用戶: \"\n\n#: apps/xpack/serializers/channel/wecom.py:97\nmsgid \"Enterprise WeChat customer service: \"\nmsgstr \"企業微信客服: \"\n\n#: apps/xpack/serializers/channel/wecom.py:134\n#: apps/xpack/serializers/channel/wecom.py:150\nmsgid \"This type of message is not supported yet\"\nmsgstr \"此類型消息暫不支持\"\n\n#: apps/xpack/serializers/channel/wecom.py:254\nmsgid \"Signature missing\"\nmsgstr \"籤名缺失\"\n\n#: apps/xpack/serializers/channel/wecom.py:266\n#: apps/xpack/serializers/channel/wecom.py:273\n#, python-brace-format\nmsgid \"An error occurred while processing the GET request {e}\"\nmsgstr \"get 請求處理時發生錯誤 {e}\"\n\n#: apps/xpack/serializers/chat_auth.py:51\nmsgid \"The password is incorrect\"\nmsgstr \"密碼不正確\"\n\n#: apps/xpack/serializers/chat_user.py:42\nmsgid \"Some user groups do not exist\"\nmsgstr \"某些用戶組不存在\"\n\n#: apps/xpack/serializers/chat_user.py:181\nmsgid \"Is Append\"\nmsgstr \"是否追加\"\n\n#: apps/xpack/serializers/chat_user.py:194\nmsgid \"User Group IDs cannot be empty\"\nmsgstr \"用戶組 IDs 不能為空\"\n\n#: apps/xpack/serializers/chat_user.py:198\nmsgid \"Some users do not exist\"\nmsgstr \"某些用戶不存在\"\n\n#: apps/xpack/serializers/chat_user.py:361\nmsgid \"Sync Type: LOCAL or LDAP\"\nmsgstr \"同步類型: LOCAL 或 LDAP\"\n\n#: apps/xpack/serializers/chat_user.py:403\nmsgid \"Unsupported sync type\"\nmsgstr \"不支持的同步類型\"\n\n#: apps/xpack/serializers/chat_user.py:412\n#: apps/xpack/serializers/chat_user.py:444\n#: apps/xpack/serializers/chat_user.py:483\n#: apps/xpack/serializers/chat_user.py:510\n#: apps/xpack/serializers/chat_user.py:548\n#: apps/xpack/serializers/chat_user.py:570\nmsgid \"User group does not exist\"\nmsgstr \"用戶組不存在\"\n\n#: apps/xpack/serializers/chat_user.py:451\nmsgid \"User group name already exists\"\nmsgstr \"用戶組名稱已存在\"\n\n#: apps/xpack/serializers/chat_user.py:485\nmsgid \"Default user group cannot be deleted\"\nmsgstr \"默認用戶組不能被刪除\"\n\n#: apps/xpack/serializers/chat_user.py:550\nmsgid \"User group relation IDs cannot be empty\"\nmsgstr \"用戶組關係 IDs 不能為空\"\n\n#: apps/xpack/serializers/chat_user_serializer.py:75\nmsgid \"Invalid access token\"\nmsgstr \"無效的訪問令牌\"\n\n#: apps/xpack/serializers/chat_user_serializer.py:102\nmsgid \"The user does not have permission to access the application\"\nmsgstr \"用戶沒有訪問智能體的權限\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:56\n#: apps/xpack/serializers/dataset_lark_serializer.py:299\nmsgid \"app id\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:57\n#: apps/xpack/serializers/dataset_lark_serializer.py:300\nmsgid \"app secret\"\nmsgstr \"\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:58\n#: apps/xpack/serializers/dataset_lark_serializer.py:105\n#: apps/xpack/serializers/dataset_lark_serializer.py:301\nmsgid \"folder token\"\nmsgstr \"文件夾令牌\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:60\nmsgid \"embedding model\"\nmsgstr \"向量模型\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:71\n#: apps/xpack/serializers/dataset_lark_serializer.py:311\nmsgid \"Network error or folder token error!\"\nmsgstr \"網絡錯誤或文件夾令牌錯誤！\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:113\n#: apps/xpack/serializers/dataset_lark_serializer.py:155\n#: apps/xpack/task/sync.py:308\nmsgid \"Knowledge base not found!\"\nmsgstr \"知識庫未找到！\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:125\n#: apps/xpack/task/sync.py:240\nmsgid \"Failed to get lark document list!\"\nmsgstr \"獲取飛書文檔列表失敗！\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:147\nmsgid \"Knowledge id\"\nmsgstr \"知識庫 ID\"\n\n#: apps/xpack/serializers/dataset_lark_serializer.py:169\nmsgid \"Synchronization is only supported for lark documents\"\nmsgstr \"僅支持飛書文檔的同步\"\n\n#: apps/xpack/serializers/license/license_serializers.py:102\n#: apps/xpack/serializers/license/license_serializers.py:123\n#: apps/xpack/serializers/license/license_tools.py:111\nmsgid \"The license is invalid\"\nmsgstr \"許可證無效\"\n\n#: apps/xpack/serializers/license/license_tools.py:136\nmsgid \"License usage limit exceeded.\"\nmsgstr \"License 使用限制已超出。\"\n\n#: apps/xpack/serializers/license/license_tools.py:160\nmsgid \"The network is busy, try again later.\"\nmsgstr \"網絡繁忙，請稍後再試。\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:59\nmsgid \"user\"\nmsgstr \"用戶\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:61\nmsgid \"ip_address\"\nmsgstr \"IP 地址\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:62\nmsgid \"workspace_id\"\nmsgstr \"工作空間ID\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:134\nmsgid \"Fail\"\nmsgstr \"失敗\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:171\nmsgid \"Menu\"\nmsgstr \"菜單\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:172\nmsgid \"Operate\"\nmsgstr \"操作\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:173\nmsgid \"Operate user\"\nmsgstr \"操作用戶\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:175\nmsgid \"Ip Address\"\nmsgstr \"IP位址\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:176\nmsgid \"API Details\"\nmsgstr \"API詳情\"\n\n#: apps/xpack/serializers/operate_log_serializer.py:177\nmsgid \"Operate Time\"\nmsgstr \"操作時間\"\n\n#: apps/xpack/serializers/platform_serializer.py:12\nmsgid \"app_id is required\"\nmsgstr \"app_id 是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:13\nmsgid \"app_secret is required\"\nmsgstr \"app_secret 是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:14\nmsgid \"token is required\"\nmsgstr \"token 是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:15\nmsgid \"callback_url is required\"\nmsgstr \"callback_url 是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:21\n#: apps/xpack/serializers/platform_serializer.py:30\nmsgid \"App ID is required\"\nmsgstr \"App ID 是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:23\nmsgid \"Secret is required\"\nmsgstr \"Secret 是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:24\nmsgid \"Token is required\"\nmsgstr \"Token 是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:33\nmsgid \"Verification Token is required\"\nmsgstr \"驗證令牌是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:38\nmsgid \"Client ID is required\"\nmsgstr \"Client ID 是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:39\nmsgid \"Client Secret is required\"\nmsgstr \"客戶端密鑰是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:44\nmsgid \"Signing Secret is required\"\nmsgstr \"籤名密鑰是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:45\nmsgid \"Bot User Token is required\"\nmsgstr \"機器人用戶令牌是必填項\"\n\n#: apps/xpack/serializers/platform_serializer.py:66\nmsgid \"Check if the fields are correct\"\nmsgstr \"檢查欄位是否正確\"\n\n#: apps/xpack/serializers/platform_serializer.py:155\n#, python-brace-format\nmsgid \"The platform configuration corresponding to {type} was not found\"\nmsgstr \"未找到對應 {type} 的平臺配置\"\n\n#: apps/xpack/serializers/resource_chat_user.py:35\n#: apps/xpack/serializers/resource_chat_user.py:111\n#: apps/xpack/serializers/resource_chat_user_group.py:18\n#: apps/xpack/serializers/resource_chat_user_group.py:86\nmsgid \"Resource id\"\nmsgstr \"資源ID\"\n\n#: apps/xpack/serializers/resource_chat_user.py:38\n#: apps/xpack/serializers/resource_chat_user.py:112\nmsgid \"User group id\"\nmsgstr \"用戶組ID\"\n\n#: apps/xpack/serializers/resource_chat_user.py:94\nmsgid \"Is auth\"\nmsgstr \"是否授權\"\n\n#: apps/xpack/serializers/resource_chat_user_group.py:20\nmsgid \"User group name\"\nmsgstr \"用戶名\"\n\n#: apps/xpack/serializers/resource_chat_user_group.py:68\nmsgid \"user_group_id\"\nmsgstr \"用戶組ID\"\n\n#: apps/xpack/serializers/sso_auth/cas.py:32\nmsgid \"HttpClient query failed: \"\nmsgstr \"HttpClient 查詢失敗: \"\n\n#: apps/xpack/serializers/sso_auth/cas.py:58\nmsgid \"CAS authentication failed\"\nmsgstr \"CAS 認證失敗\"\n\n#: apps/xpack/serializers/sso_auth/oauth2.py:165\n#: apps/xpack/serializers/sso_auth/oauth2.py:184\n#: apps/xpack/serializers/sso_auth/oauth2.py:187\nmsgid \"Failed to obtain user information\"\nmsgstr \"獲取用戶信息失敗\"\n\n#: apps/xpack/serializers/system_api_key.py:12\nmsgid \"Allow cross domain\"\nmsgstr \"允許跨域\"\n\n#: apps/xpack/serializers/system_api_key.py:13\nmsgid \"Cross domain list\"\nmsgstr \"跨域列表\"\n\n#: apps/xpack/serializers/system_api_key.py:44\nmsgid \"system API key id\"\nmsgstr \"系統 API 密鑰 ID\"\n\n#: apps/xpack/serializers/system_params.py:20\nmsgid \"theme\"\nmsgstr \"主題\"\n\n#: apps/xpack/serializers/system_params.py:22\nmsgid \"login logo\"\nmsgstr \"登錄 logo\"\n\n#: apps/xpack/serializers/system_params.py:23\nmsgid \"login image\"\nmsgstr \"登陸圖片\"\n\n#: apps/xpack/serializers/system_params.py:24\nmsgid \"title\"\nmsgstr \"標題\"\n\n#: apps/xpack/serializers/system_params.py:25\nmsgid \"slogan\"\nmsgstr \"標語\"\n\n#: apps/xpack/serializers/system_params.py:26\n#: apps/xpack/serializers/system_params.py:27\nmsgid \"show user manual\"\nmsgstr \"顯示用戶手冊\"\n\n#: apps/xpack/serializers/system_params.py:28\nmsgid \"user manual url\"\nmsgstr \"用戶手冊網址\"\n\n#: apps/xpack/serializers/system_params.py:29\nmsgid \"show forum\"\nmsgstr \"顯示論壇\"\n\n#: apps/xpack/serializers/system_params.py:30\nmsgid \"forum url\"\nmsgstr \"論壇網址\"\n\n#: apps/xpack/serializers/system_params.py:31\nmsgid \"show project\"\nmsgstr \"顯示項目\"\n\n#: apps/xpack/serializers/system_params.py:32\nmsgid \"project url\"\nmsgstr \"項目網址\"\n\n#: apps/xpack/views/application_setting.py:24\n#: apps/xpack/views/application_setting.py:25\n#: apps/xpack/views/application_setting.py:26\nmsgid \"Modify Application Settings\"\nmsgstr \"修改智能體設置\"\n\n#: apps/xpack/views/application_setting.py:42\n#: apps/xpack/views/application_setting.py:43\n#: apps/xpack/views/application_setting.py:44\nmsgid \"Get Application Settings\"\nmsgstr \"獲取智能體設置\"\n\n#: apps/xpack/views/auth.py:52 apps/xpack/views/auth.py:53\n#: apps/xpack/views/auth.py:54 apps/xpack/views/chat_user_auth.py:46\n#: apps/xpack/views/chat_user_auth.py:47 apps/xpack/views/chat_user_auth.py:48\nmsgid \"Get authentication types\"\nmsgstr \"獲取認證類型\"\n\n#: apps/xpack/views/auth.py:55 apps/xpack/views/auth.py:70\n#: apps/xpack/views/auth.py:90 apps/xpack/views/auth.py:108\n#: apps/xpack/views/auth.py:223 apps/xpack/views/auth.py:235\n#: apps/xpack/views/auth.py:249\nmsgid \"Authentication Configuration\"\nmsgstr \"認證配置\"\n\n#: apps/xpack/views/auth.py:67 apps/xpack/views/auth.py:68\n#: apps/xpack/views/auth.py:69 apps/xpack/views/chat_user_auth.py:62\n#: apps/xpack/views/chat_user_auth.py:63 apps/xpack/views/chat_user_auth.py:64\nmsgid \"Test LDAP connection\"\nmsgstr \"測試 LDAP 連接\"\n\n#: apps/xpack/views/auth.py:87 apps/xpack/views/auth.py:88\n#: apps/xpack/views/auth.py:89\nmsgid \"Add or modify authentication configuration\"\nmsgstr \"添加或修改認證配置\"\n\n#: apps/xpack/views/auth.py:105 apps/xpack/views/auth.py:106\n#: apps/xpack/views/auth.py:107\nmsgid \"Get authentication configuration\"\nmsgstr \"獲取認證配置\"\n\n#: apps/xpack/views/auth.py:118 apps/xpack/views/auth.py:119\n#: apps/xpack/views/auth.py:120 apps/xpack/views/chat_user_auth.py:112\n#: apps/xpack/views/chat_user_auth.py:113\n#: apps/xpack/views/chat_user_auth.py:114\nmsgid \"Ldap Log in\"\nmsgstr \"LDAP 登錄\"\n\n#: apps/xpack/views/auth.py:121 apps/xpack/views/auth.py:138\n#: apps/xpack/views/auth.py:157 apps/xpack/views/auth.py:176\n#: apps/xpack/views/auth.py:194 apps/xpack/views/auth.py:208\n#: apps/xpack/views/auth.py:266 apps/xpack/views/auth.py:286\n#: apps/xpack/views/auth.py:307 apps/xpack/views/auth.py:327\n#: apps/xpack/views/auth.py:348 apps/xpack/views/auth.py:368\nmsgid \"Three-party login\"\nmsgstr \"三方登錄\"\n\n#: apps/xpack/views/auth.py:135 apps/xpack/views/auth.py:136\n#: apps/xpack/views/auth.py:137 apps/xpack/views/chat_user_auth.py:129\n#: apps/xpack/views/chat_user_auth.py:130\n#: apps/xpack/views/chat_user_auth.py:131\nmsgid \"CAS Log in\"\nmsgstr \"CAS 登錄\"\n\n#: apps/xpack/views/auth.py:154 apps/xpack/views/auth.py:155\n#: apps/xpack/views/auth.py:156 apps/xpack/views/chat_user_auth.py:148\n#: apps/xpack/views/chat_user_auth.py:149\n#: apps/xpack/views/chat_user_auth.py:150\nmsgid \"OIDC Log in\"\nmsgstr \"OIDC 登錄\"\n\n#: apps/xpack/views/auth.py:173 apps/xpack/views/auth.py:174\n#: apps/xpack/views/auth.py:175 apps/xpack/views/chat_user_auth.py:167\n#: apps/xpack/views/chat_user_auth.py:168\n#: apps/xpack/views/chat_user_auth.py:169\nmsgid \"OAuth2 Log in\"\nmsgstr \"OAuth2 登錄\"\n\n#: apps/xpack/views/auth.py:191 apps/xpack/views/auth.py:192\n#: apps/xpack/views/auth.py:193 apps/xpack/views/chat_user_auth.py:220\n#: apps/xpack/views/chat_user_auth.py:221\n#: apps/xpack/views/chat_user_auth.py:222\nmsgid \"Scan code login type\"\nmsgstr \"掃碼登錄類型\"\n\n#: apps/xpack/views/auth.py:205 apps/xpack/views/auth.py:206\n#: apps/xpack/views/auth.py:207 apps/xpack/views/auth.py:220\n#: apps/xpack/views/auth.py:221 apps/xpack/views/auth.py:222\n#: apps/xpack/views/chat_user_auth.py:234\n#: apps/xpack/views/chat_user_auth.py:235\n#: apps/xpack/views/chat_user_auth.py:236\n#: apps/xpack/views/chat_user_auth.py:249\n#: apps/xpack/views/chat_user_auth.py:250\n#: apps/xpack/views/chat_user_auth.py:251\nmsgid \"Get platform information\"\nmsgstr \"獲取平臺信息\"\n\n#: apps/xpack/views/auth.py:232 apps/xpack/views/auth.py:233\n#: apps/xpack/views/auth.py:234 apps/xpack/views/chat_user_auth.py:261\n#: apps/xpack/views/chat_user_auth.py:262\n#: apps/xpack/views/chat_user_auth.py:263\nmsgid \"Modify platform information\"\nmsgstr \"修改平臺信息\"\n\n#: apps/xpack/views/auth.py:246 apps/xpack/views/auth.py:247\n#: apps/xpack/views/auth.py:248 apps/xpack/views/chat_user_auth.py:275\n#: apps/xpack/views/chat_user_auth.py:276\n#: apps/xpack/views/chat_user_auth.py:277\nmsgid \"Test platform connection\"\nmsgstr \"測試平臺連接\"\n\n#: apps/xpack/views/auth.py:263 apps/xpack/views/auth.py:264\n#: apps/xpack/views/auth.py:265 apps/xpack/views/chat_user_auth.py:292\n#: apps/xpack/views/chat_user_auth.py:293\n#: apps/xpack/views/chat_user_auth.py:294\nmsgid \"DingTalk callback\"\nmsgstr \"釘釘回調\"\n\n#: apps/xpack/views/auth.py:283 apps/xpack/views/auth.py:284\n#: apps/xpack/views/auth.py:285 apps/xpack/views/chat_user_auth.py:312\n#: apps/xpack/views/chat_user_auth.py:313\n#: apps/xpack/views/chat_user_auth.py:314\nmsgid \"DingTalk OAuth2 callback\"\nmsgstr \"釘釘 OAuth2 回調\"\n\n#: apps/xpack/views/auth.py:304 apps/xpack/views/auth.py:305\n#: apps/xpack/views/auth.py:306 apps/xpack/views/chat_user_auth.py:333\n#: apps/xpack/views/chat_user_auth.py:334\n#: apps/xpack/views/chat_user_auth.py:335\nmsgid \"WeCom callback\"\nmsgstr \"企業微信回調\"\n\n#: apps/xpack/views/auth.py:324 apps/xpack/views/auth.py:325\n#: apps/xpack/views/auth.py:326 apps/xpack/views/chat_user_auth.py:353\n#: apps/xpack/views/chat_user_auth.py:354\n#: apps/xpack/views/chat_user_auth.py:355\nmsgid \"WeCom OAuth2 callback\"\nmsgstr \"企業微信 OAuth2 回調\"\n\n#: apps/xpack/views/auth.py:345 apps/xpack/views/auth.py:346\n#: apps/xpack/views/auth.py:347 apps/xpack/views/chat_user_auth.py:374\n#: apps/xpack/views/chat_user_auth.py:375\n#: apps/xpack/views/chat_user_auth.py:376\nmsgid \"Lark callback\"\nmsgstr \"飛書回調\"\n\n#: apps/xpack/views/auth.py:365 apps/xpack/views/auth.py:366\n#: apps/xpack/views/auth.py:367 apps/xpack/views/chat_user_auth.py:394\n#: apps/xpack/views/chat_user_auth.py:395\n#: apps/xpack/views/chat_user_auth.py:396\nmsgid \"Lark OAuth2 callback\"\nmsgstr \"飛書 OAuth2 回調\"\n\n#: apps/xpack/views/chat_user_auth.py:49 apps/xpack/views/chat_user_auth.py:65\n#: apps/xpack/views/chat_user_auth.py:85 apps/xpack/views/chat_user_auth.py:252\n#: apps/xpack/views/chat_user_auth.py:264\n#: apps/xpack/views/chat_user_auth.py:278\nmsgid \"Chat User/Authentication Configuration\"\nmsgstr \"對話用戶/認證配置\"\n\n#: apps/xpack/views/chat_user_auth.py:82 apps/xpack/views/chat_user_auth.py:83\n#: apps/xpack/views/chat_user_auth.py:84\nmsgid \"Add or modify Chat/Authentication Configuration\"\nmsgstr \"添加或修改對話/認證配置\"\n\n#: apps/xpack/views/chat_user_auth.py:99 apps/xpack/views/chat_user_auth.py:100\n#: apps/xpack/views/chat_user_auth.py:101\nmsgid \"Get Authentication Configuration\"\nmsgstr \"獲取認證配置\"\n\n#: apps/xpack/views/chat_user_auth.py:102\nmsgid \"Chat User/login authentication\"\nmsgstr \"對話用戶/登錄認證\"\n\n#: apps/xpack/views/chat_user_auth.py:115\n#: apps/xpack/views/chat_user_auth.py:132\n#: apps/xpack/views/chat_user_auth.py:151\n#: apps/xpack/views/chat_user_auth.py:170\n#: apps/xpack/views/chat_user_auth.py:223\n#: apps/xpack/views/chat_user_auth.py:237\n#: apps/xpack/views/chat_user_auth.py:295\n#: apps/xpack/views/chat_user_auth.py:315\n#: apps/xpack/views/chat_user_auth.py:336\n#: apps/xpack/views/chat_user_auth.py:356\n#: apps/xpack/views/chat_user_auth.py:377\n#: apps/xpack/views/chat_user_auth.py:397\nmsgid \"Chat User/Three-party login\"\nmsgstr \"對話用戶/三方登錄\"\n\n#: apps/xpack/views/chat_user_auth.py:187\nmsgid \"Chat User/login\"\nmsgstr \"對話用戶/登錄\"\n\n#: apps/xpack/views/chat_user_auth.py:414\n#: apps/xpack/views/chat_user_auth.py:415\n#: apps/xpack/views/chat_user_auth.py:416\nmsgid \"Application Password Certification\"\nmsgstr \"智能體密碼認證\"\n\n#: apps/xpack/views/license.py:31 apps/xpack/views/license.py:32\n#: apps/xpack/views/license.py:33\nmsgid \"Get license information\"\nmsgstr \"獲取許可證信息\"\n\n#: apps/xpack/views/license.py:42 apps/xpack/views/license.py:44\nmsgid \"Update license information\"\nmsgstr \"更新許可證信息\"\n\n#: apps/xpack/views/license.py:43\nmsgid \"Update license information by uploading a new license file\"\nmsgstr \"通過上傳新許可證文件更新許可證信息\"\n\n#: apps/xpack/views/operate_log.py:21 apps/xpack/views/operate_log.py:22\n#: apps/xpack/views/operate_log.py:23\nmsgid \"Get menu operate log\"\nmsgstr \"獲取菜單操作日誌\"\n\n#: apps/xpack/views/operate_log.py:25 apps/xpack/views/operate_log.py:41\n#: apps/xpack/views/operate_log.py:57\nmsgid \"System operate log\"\nmsgstr \"系統操作日誌\"\n\n#: apps/xpack/views/operate_log.py:36 apps/xpack/views/operate_log.py:37\n#: apps/xpack/views/operate_log.py:38\nmsgid \"Get paginated operate log\"\nmsgstr \"獲取分頁操作日誌\"\n\n#: apps/xpack/views/operate_log.py:54 apps/xpack/views/operate_log.py:55\n#: apps/xpack/views/operate_log.py:56\nmsgid \"Export operate log\"\nmsgstr \"導出操作日誌\"\n\n#: apps/xpack/views/platform.py:60 apps/xpack/views/platform.py:61\n#: apps/xpack/views/platform.py:62\nmsgid \"Get platform configuration\"\nmsgstr \"獲取平臺配置\"\n\n#: apps/xpack/views/platform.py:65 apps/xpack/views/platform.py:79\nmsgid \"Application/application access\"\nmsgstr \"智能體/智能體訪問\"\n\n#: apps/xpack/views/platform.py:73 apps/xpack/views/platform.py:74\n#: apps/xpack/views/platform.py:75\nmsgid \"Update platform configuration\"\nmsgstr \"更新平臺配置\"\n\n#: apps/xpack/views/platform.py:94 apps/xpack/views/platform.py:95\n#: apps/xpack/views/platform.py:96\nmsgid \"Get platform status\"\nmsgstr \"獲取平臺狀態\"\n\n#: apps/xpack/views/platform.py:98 apps/xpack/views/platform.py:118\nmsgid \"Application/Get platform status\"\nmsgstr \"智能體/獲取平臺狀態\"\n\n#: apps/xpack/views/platform.py:113 apps/xpack/views/platform.py:114\n#: apps/xpack/views/platform.py:115\nmsgid \"Update platform status\"\nmsgstr \"更新平臺狀態\"\n\n#: apps/xpack/views/resource_chat_user.py:27\n#: apps/xpack/views/resource_chat_user.py:28\n#: apps/xpack/views/resource_chat_user.py:29\nmsgid \"Get Resource chat user List\"\nmsgstr \"獲取資源聊天用戶列表\"\n\n#: apps/xpack/views/resource_chat_user.py:32\n#: apps/xpack/views/resource_chat_user.py:54\n#: apps/xpack/views/resource_chat_user.py:77\n#: apps/xpack/views/system_chat_user_group.py:24\n#: apps/xpack/views/system_chat_user_group.py:45\n#: apps/xpack/views/system_chat_user_group.py:67\nmsgid \"Chat user\"\nmsgstr \"聊天用戶\"\n\n#: apps/xpack/views/resource_chat_user.py:48\n#: apps/xpack/views/resource_chat_user.py:49\n#: apps/xpack/views/resource_chat_user.py:50\nmsgid \"Edit Resource chat user List\"\nmsgstr \"編輯資源聊天用戶列表\"\n\n#: apps/xpack/views/resource_chat_user.py:72\n#: apps/xpack/views/resource_chat_user.py:73\n#: apps/xpack/views/resource_chat_user.py:74\nmsgid \"Get Resource chat user page List\"\nmsgstr \"獲取資源聊天用戶分頁列表\"\n\n#: apps/xpack/views/system_api_key.py:19 apps/xpack/views/system_api_key.py:20\n#: apps/xpack/views/system_api_key.py:21\nmsgid \"Create SystemAPIKey\"\nmsgstr \"創建系統 API 密鑰\"\n\n#: apps/xpack/views/system_api_key.py:34 apps/xpack/views/system_api_key.py:35\n#: apps/xpack/views/system_api_key.py:36\nmsgid \"Get SystemAPIKey List\"\nmsgstr \"獲取系統 API 密鑰列表\"\n\n#: apps/xpack/views/system_api_key.py:50 apps/xpack/views/system_api_key.py:51\n#: apps/xpack/views/system_api_key.py:52\nmsgid \"Update SystemAPIKey\"\nmsgstr \"更新系統 API 密鑰\"\n\n#: apps/xpack/views/system_api_key.py:66 apps/xpack/views/system_api_key.py:67\n#: apps/xpack/views/system_api_key.py:68\nmsgid \"Delete SystemAPIKey\"\nmsgstr \"刪除系統 API 密鑰\"\n\n#: apps/xpack/views/system_chat_user.py:60\n#: apps/xpack/views/system_chat_user.py:76\n#: apps/xpack/views/system_chat_user.py:89\n#: apps/xpack/views/system_chat_user.py:102\n#: apps/xpack/views/system_chat_user.py:113\n#: apps/xpack/views/system_chat_user.py:131\n#: apps/xpack/views/system_chat_user.py:146\n#: apps/xpack/views/system_chat_user.py:163\n#: apps/xpack/views/system_chat_user.py:180\n#: apps/xpack/views/system_chat_user.py:200\n#: apps/xpack/views/system_chat_user.py:217\nmsgid \"System/Chat user\"\nmsgstr \"系統/對話用戶\"\n\n#: apps/xpack/views/system_chat_user.py:73\n#: apps/xpack/views/system_chat_user.py:74\n#: apps/xpack/views/system_chat_user.py:75\nmsgid \"Get chat user list\"\nmsgstr \"獲取對話用戶列表\"\n\n#: apps/xpack/views/system_chat_user.py:214\n#: apps/xpack/views/system_chat_user.py:216\nmsgid \"Sync chat users\"\nmsgstr \"同步對話用戶\"\n\n#: apps/xpack/views/system_chat_user.py:215\nmsgid \"Sync chat users from external source\"\nmsgstr \"從外部源同步對話用戶\"\n\n#: apps/xpack/views/system_chat_user.py:235\n#: apps/xpack/views/system_chat_user.py:247\n#: apps/xpack/views/system_chat_user.py:261\n#: apps/xpack/views/system_chat_user.py:279\n#: apps/xpack/views/system_chat_user.py:301\n#: apps/xpack/views/system_chat_user.py:321\nmsgid \"System/User Group\"\nmsgstr \"系統/用戶組\"\n\n#: apps/xpack/views/system_chat_user_group.py:19\n#: apps/xpack/views/system_chat_user_group.py:20\n#: apps/xpack/views/system_chat_user_group.py:21\nmsgid \"Get Resource chat user group List\"\nmsgstr \"獲取資源聊天用戶組列表\"\n\n#: apps/xpack/views/system_chat_user_group.py:39\n#: apps/xpack/views/system_chat_user_group.py:40\n#: apps/xpack/views/system_chat_user_group.py:41\nmsgid \"Edit Resource chat user group List\"\nmsgstr \"編輯資源聊天用戶組列表\"\n\n#: apps/xpack/views/system_chat_user_group.py:62\n#: apps/xpack/views/system_chat_user_group.py:64\nmsgid \"Get Resource chat user group page List\"\nmsgstr \"獲取資源聊天用戶組分頁列表\"\n\n#: apps/xpack/views/system_chat_user_group.py:63\nmsgid \"Get Resource chat user page group List\"\nmsgstr \"獲取資源聊天用戶分頁組列表\"\n\n#: apps/xpack/views/system_params.py:22 apps/xpack/views/system_params.py:23\n#: apps/xpack/views/system_params.py:24\nmsgid \"View appearance settings\"\nmsgstr \"查看外觀設置\"\n\n#: apps/xpack/views/system_params.py:39 apps/xpack/views/system_params.py:40\n#: apps/xpack/views/system_params.py:41\nmsgid \"Update appearance settings\"\nmsgstr \"更新外觀設置\"\n\nmsgid \"Application Access\"\nmsgstr \"智能體介入\"\n\nmsgid \"Display execution details\"\nmsgstr \"是否顯示執行詳情\"\n\nmsgid \"LOCAL\"\nmsgstr \"帳號登入\"\n\nmsgid \"CAS\"\nmsgstr \"CAS\"\n\nmsgid \"LDAP\"\nmsgstr \"LDAP\"\n\nmsgid \"OIDC\"\nmsgstr \"OIDC\"\n\nmsgid \"OAuth2\"\nmsgstr \"OAuth2\"\n\nmsgid \"dingtalk\"\nmsgstr \"钉钉\"\n\nmsgid \"wecom\"\nmsgstr \"企业微信\"\n\nmsgid \"lark\"\nmsgstr \"飞书\"\n\nmsgid \"Get tool list\"\nmsgstr \"獲取工具列表\"\n\nmsgid \"Setting\"\nmsgstr \"設置\"\n\nmsgid \"Get verification results\"\nmsgstr \"獲取驗證結果\"\n\nmsgid \"Validation\"\nmsgstr \"驗證\"\n\nmsgid \"Models Resource\"\nmsgstr \"模型資源\"\n\nmsgid \"Tools Resource\"\nmsgstr \"工具資源\"\n\nmsgid \"Get resource model list\"\nmsgstr \"獲取資源模型列表\"\n\nmsgid \"System Model\"\nmsgstr \"系統模型\"\n\nmsgid \"Dialogue users\"\nmsgstr \"對話用戶\"\n\nmsgid \"Conversation log\"\nmsgstr \"對話日誌\"\n\nmsgid \"Public access link\"\nmsgstr \"公共訪問鏈接\"\n\nmsgid \"User management\"\nmsgstr \"用戶管理\"\n\nmsgid \"Chat User/logout\"\nmsgstr \"對話用戶/登出\"\n\nmsgid \"Paragraph\"\nmsgstr \"段落\"\n\nmsgid \"User group\"\nmsgstr \"用戶組\"\n\nmsgid \"Remove member from user group\"\nmsgstr \"從用戶組中移除成員\"\n\nmsgid \"Create a web site knowledge base\"\nmsgstr \"創建web知識庫\"\n\nmsgid \"Modify knowledge base information\"\nmsgstr \"修改知識庫信息\"\n\nmsgid \"Delete knowledge base\"\nmsgstr \"刪除知識庫\"\n\nmsgid \"model\"\nmsgstr \"模型\"\n\nmsgid \"Batch add user to group\"\nmsgstr \"批量添加用戶到組\"\n\nmsgid \"Add internal tool\"\nmsgstr \"添加内置工具\"\n\nmsgid \"Batch generate related\"\nmsgstr \"批量生成相關\"\n\nmsgid \"Update personal system API_KEY\"\nmsgstr \"更新個人系統 API KEY\"\n\nmsgid \"Delete user group\"\nmsgstr \"刪除用戶組\"\n\nmsgid \"Add user\"\nmsgstr \"添加用戶\"\n\nmsgid \"folder\"\nmsgstr \"文件夾\"\n\nmsgid \"Create or update user group\"\nmsgstr \"創建或更新用戶組\"\n\nmsgid \"Edit folder\"\nmsgstr \"編輯文件夾\"\n\nmsgid \"Email settings\"\nmsgstr \"郵件設置\"\n\nmsgid \"trial listening\"\nmsgstr \"試聽\"\n\nmsgid \"Add member to user group\"\nmsgstr \"添加成員到用戶組\"\n\nmsgid \"System\"\nmsgstr \"系統\"\n\nmsgid \"Shared Knowledge/Document\"\nmsgstr \"共享知識/文件\"\n\nmsgid \"System Application\"\nmsgstr \"系統智能體\"\n\nmsgid \"Hit-Test\"\nmsgstr \"命中測試\"\n\nmsgid \"Export Application\"\nmsgstr \"導出智能體\"\n\nmsgid \"Add ApiKey\"\nmsgstr \"添加 API KEY\"\n\nmsgid \"Delete application API_KEY\"\nmsgstr \"删除智能體 API KEY\"\n\nmsgid \"knowledge Base\"\nmsgstr \"知識庫\"\n\nmsgid \"API KEY\"\nmsgstr \"API KEY\"\n\nmsgid \"Download\"\nmsgstr \"下載\"\n\nmsgid \"Delete personal system API_KEY\"\nmsgstr \"删除個人系統API KEY\"\n\nmsgid \"Add personal system API_KEY\"\nmsgstr \"添加個人系統API KEY\"\n\nmsgid \"Generate related documents\"\nmsgstr \"生成相關文檔\"\n\nmsgid \"Modify application access token\"\nmsgstr \"修改智能體程序訪問權杖\"\n\nmsgid \"File not exist. Only manually uploaded documents are supported\"\nmsgstr \"文件不存在, 僅支持手動上傳的文檔\"\n\nmsgid \"Resource\"\nmsgstr \"資源管理\"\n\nmsgid \"LDAP configuration not found or not active\"\nmsgstr \"LDAP 配置未找到或未激活\"\n\nmsgid \"Lark configuration not found or not active\"\nmsgstr \"Lark 配置未找到或未激活\"\n\nmsgid \"Failed to get Lark collaborators\"\nmsgstr \"獲取 Lark 協作者失敗\"\n\nmsgid \"Failed to get Lark user details\"\nmsgstr \"獲取 Lark 用戶詳情失敗\"\n\nmsgid \"WeCom configuration not found or not active\"\nmsgstr \"WeCom 配置未找到或未激活\"\n\nmsgid \"Failed to get WeCom access token\"\nmsgstr \"獲取 WeCom 訪問權杖失敗\"\n\nmsgid \"Failed to get WeCom agent info\"\nmsgstr \"獲取 WeCom 智能助手信息失敗\"\n\nmsgid \"Failed to get WeCom department users\"\nmsgstr \"獲取 WeCom 部門用戶失敗\"\n\nmsgid \"Failed to get WeCom user info\"\nmsgstr \"獲取 WeCom 用戶詳情失敗\"\n\nmsgid \"Publish status\"\nmsgstr \"發佈狀態\"\n\nmsgid \"Unpublished\"\nmsgstr \"未發佈\"\n\nmsgid \"Published\"\nmsgstr \"已發佈\"\n\nmsgid \"users_permission\"\nmsgstr \"用戶許可權\"\n\nmsgid \"Get user authorization status of resource\"\nmsgstr \"獲取資源對用戶的授權狀態\"\n\nmsgid \"Edit user authorization status of resource\"\nmsgstr \"修改資源對用戶的授權狀態\"\n\nmsgid \"Get user authorization status of resource by page\"\nmsgstr \"分頁獲取資源對用戶的授權狀態\"\n\nmsgid \"Obtain resource authorization list by page\"\nmsgstr \"分頁獲取資源授權清單\"\n\nmsgid \"Engine model type\"\nmsgstr \"引擎模型類型\"\n\nmsgid \"If not passed, the default value is 16k_zh (Chinese universal)\"\nmsgstr \"如果未傳遞，默認值為 16k_zh（中文通用）\"\n\nmsgid \"Chinese telephone universal\"\nmsgstr \"中文電話通用\"\n\nmsgid \"English telephone universal\"\nmsgstr \"英文電話通用\"\n\nmsgid \"Commonly used in Chinese\"\nmsgstr \"中文常用\"\n\nmsgid \"Chinese, English, and Guangdong\"\nmsgstr \"中文、英文和廣東話\"\n\nmsgid \"Chinese medical\"\nmsgstr \"中文醫療\"\n\nmsgid \"English\"\nmsgstr \"英文\"\n\nmsgid \"Cantonese\"\nmsgstr \"粵語\"\n\nmsgid \"Japanese\"\nmsgstr \"日語\"\n\nmsgid \"Korean\"\nmsgstr \"韓語\"\n\nmsgid \"Vietnamese\"\nmsgstr \"越南語\"\n\nmsgid \"Malay language\"\nmsgstr \"馬來語\"\n\nmsgid \"Indonesian language\"\nmsgstr \"印尼語\"\n\nmsgid \"Filipino language\"\nmsgstr \"菲律賓語\"\n\nmsgid \"Thai\"\nmsgstr \"泰語\"\n\nmsgid \"Portuguese\"\nmsgstr \"葡萄牙語\"\n\nmsgid \"Turkish\"\nmsgstr \"土耳其語\"\n\nmsgid \"Arabic\"\nmsgstr \"阿拉伯語\"\n\nmsgid \"Spanish\"\nmsgstr \"西班牙語\"\n\nmsgid \"Hindi\"\nmsgstr \"印地語\"\n\nmsgid \"French\"\nmsgstr \"法語\"\n\nmsgid \"German\"\nmsgstr \"德語\"\n\nmsgid \"Multiple dialects, supporting 23 dialects\"\nmsgstr \"多種方言，支持 23 種方言\"\n\nmsgid \"This interface is used to recognize short audio files within 60 seconds. Supports Mandarin Chinese, English, Cantonese, Japanese, Vietnamese, Malay, Indonesian, Filipino, Thai, Portuguese, Turkish, Arabic, Hindi, French, German, and 23 Chinese dialects.\"\nmsgstr \"本介面用於識別 60 秒之內的短音頻文件。支援中文普通話、英語、粵語、日語、越南語、馬來語、印度尼西亞語、菲律賓語、泰語、葡萄牙語、土耳其語、阿拉伯語、印地語、法語、德語及 23 種漢語方言。\"\n\nmsgid \"CueWord\"\nmsgstr \"提示詞\"\n\nmsgid \"If not passed, the default value is What is this audio saying? Only answer the audio content\"\nmsgstr \"如果未傳遞，預設值為這段音訊在說什麼，只回答音訊的內容\"\n\nmsgid \"The Qwen Omni series model supports inputting multiple modalities of data, including video, audio, images, and text, and outputting audio and text.\"\nmsgstr \"Qwen-Omni系列模型支持輸入多種模態的數據，包括視頻、音訊、圖片、文字，並輸出音訊與文字\"\n\nmsgid \"resource authorization\"\nmsgstr \"資源授權\"\n\nmsgid \"The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition.\"\nmsgstr \"基於Qwen-Audio的端到端語音辨識大模型，支持3分鐘以內的音訊識別，現時主要支持中英文識別。\"\n\nmsgid \"If not passed, the default value is 'zh'\"\nmsgstr \"如果未傳遞，則預設值為'zh'\"\n\nmsgid \"System resources authorization\"\nmsgstr \"系統資源授權\"\n\nmsgid \"This folder contains resources that you dont have permission\"\nmsgstr \"此資料夾包含您沒有許可權的資源\"\n\n\nmsgid \"Text to Video\"\nmsgstr \"文生視頻\"\n\nmsgid \"Image to Video\"\nmsgstr \"圖生視頻\"\n\nmsgid \"Authentication failed. Please verify that the parameters are correct\"\nmsgstr \"認證失敗，請檢查參數是否正確\"\n\nmsgid \"Chat context\"\nmsgstr \"聊天上下文\"\n\nmsgid \"Prompt template\"\nmsgstr \"提示詞範本\"\n\nmsgid \"generate prompt\"\nmsgstr \"生成提示詞\"\n\nmsgid \"Watermark\"\nmsgstr \"水印\"\n\nmsgid \"Whether to add watermark\"\nmsgstr \"是否添加水印\"\n\nmsgid \"Resolution\"\nmsgstr \"分辨率\"\n\nmsgid \"Ratio\"\nmsgstr \"比例\"\n\nmsgid \"Duration\"\nmsgstr \"時長\"\n\nmsgid \"Failed to generate video\"\nmsgstr \"生成視頻失敗\"\n\nmsgid \"password\"\nmsgstr \"密码登录\"\n\nmsgid \"Failed to obtain the image\"\nmsgstr \"獲取圖片失敗\"\n\nmsgid \"Update auth setting\"\nmsgstr \"更新認證設置\"\n\nmsgid \"If not passed, the default value is streaming_asr_demo\"\nmsgstr \"如果未傳入，則預設值為 streaming_asr_demo\"\n\nmsgid \"If not passed, the default value is 16000\"\nmsgstr \"如果未傳入，則預設值為 16000\"\n\nmsgid \"Sample Rate\"\nmsgstr \"採樣率\"\n\nmsgid \"Captcha is required\"\nmsgstr \"驗證碼是必填項\"\n\nmsgid \"Tag\"\nmsgstr \"標籤管理\"\n\nmsgid \"Tag Setting\"\nmsgstr \"標籤設定\"\n\nmsgid \"Download Original Document\"\nmsgstr \"下載原文件\"\n\nmsgid \"Replace Original Document\"\nmsgstr \"替換原文件\"\n\nmsgid \"Update License\"\nmsgstr \"更新許可證\"\n\nmsgid \"Tag Key\"\nmsgstr \"標籤\"\n\nmsgid \"Tag Value\"\nmsgstr \"標籤值\"\n\nmsgid \"Tag id does not exist\"\nmsgstr \"標籤ID不存在\"\n\nmsgid \"Tag key already exists\"\nmsgstr \"標籤已存在\"\n\nmsgid \"Tag value already exists\"\nmsgstr \"標籤值已存在\"\n\nmsgid \"Non-existent id\"\nmsgstr \"不存在的ID\"\n\nmsgid \"No permission for the target folder\"\nmsgstr \"沒有目標資料夾的權限\"\n\nmsgid \"Application token usage statistics\"\nmsgstr \"智能體令牌使用統計\"\n\nmsgid \"Application top question statistics\"\nmsgstr \"智能體提問次數統計\"\n\nmsgid \"SAML2 Metadata\"\nmsgstr \"SAML2 元數據\"\n\nmsgid \"SAML2 Log in\"\nmsgstr \"SAML2 登入\"\n\nmsgid \"SAML2 SSO\"\nmsgstr \"SAML2 單點登入\"\n\nmsgid \"Workflow\"\nmsgstr \"工作流\"\n\nmsgid \"Web source url\"\nmsgstr \"Web 根地址\"\n\nmsgid \"Web knowledge selector\"\nmsgstr \"選擇器\"\n\nmsgid \"The default is body, you can enter .classname/#idname/tagname\"\nmsgstr \"默認為 body，可輸入 .classname/#idname/tagname\"\n\nmsgid \"Please enter the Web root address\"\nmsgstr \"請輸入 Web 根地址\"\n\nmsgid \"File size exceeds limit\"\nmsgstr \"文件大小超出限制\"\n\nmsgid \"File upload is not enabled\"\nmsgstr \"文件上傳未啟用\"\n\nmsgid \"Blocked unsafe redirect to internal host\"\nmsgstr \"阻止不安全的重定向到內部主機\"\n\nmsgid \"Audio file recognition - Tongyi Qwen\"\nmsgstr \"錄音文件識別-通義千問\"\n\nmsgid \"Real-time speech recognition - Fun-ASR/Paraformer\"\nmsgstr \"實時語音識別-Fun-ASR/Paraformer\"\n\nmsgid \"Qwen-Omni\"\nmsgstr \"多模態\"\n\nmsgid \"Super-humanoid: Lingxiaoxuan Flow\"\nmsgstr \"聆小璇\"\n\nmsgid \"Super-humanoid: Lingyuyan Flow\"\nmsgstr \"聆玉言\"\n\nmsgid \"Super-humanoid: Lingfeiyi Flow\"\nmsgstr \"聆飛逸\"\n\nmsgid \"Super-humanoid: Lingxiaoyue Flow\"\nmsgstr \"聆小玥\"\n\nmsgid \"Super-humanoid: Sun Dasheng Flow\"\nmsgstr \"孫大聖\"\n\nmsgid \"Super-humanoid: Lingyuzhao Flow\"\nmsgstr \"聆玉昭\"\n\nmsgid \"Super-humanoid: Lingxiaotang Flow\"\nmsgstr \"聆小糖\"\n\nmsgid \"Super-humanoid: Lingxiaorong Flow\"\nmsgstr \"聆小蓉\"\n\nmsgid \"Super-humanoid: Xinyun Flow\"\nmsgstr \"心雲\"\n\nmsgid \"Super-humanoid: Grant (EN)\"\nmsgstr \"Grant\"\n\nmsgid \"Super-humanoid: Lila (EN)\"\nmsgstr \"Lila\"\n\nmsgid \"Super-humanoid: Lingwanwan Pro\"\nmsgstr \"聆萬萬\"\n\nmsgid \"Super-humanoid: Yiyi Pro\"\nmsgstr \"依依\"\n\nmsgid \"Super-humanoid: Huifangnv Pro\"\nmsgstr \"惠芳女\"\n\nmsgid \"Super-humanoid: Lingxiaoying Pro\"\nmsgstr \"聆小穎\"\n\nmsgid \"Super-humanoid: Lingfeibo Pro\"\nmsgstr \"聆飛博\"\n\nmsgid \"Super-humanoid: Lingyuyan Pro\"\nmsgstr \"聆玉言\"\n\nmsgid \"Login failed %s times, account will be locked, you have %s more chances !\"\nmsgstr \"登录失败 %s 次，账号将被锁定，您还有 %s 次机会！\"\n\nmsgid \"This account has been locked for %s minutes, please try again later\"\nmsgstr \"該帳號已被鎖定 %s 分鐘，請稍後再試\"\n\nmsgid \"User does not have permission to use API Key\"\nmsgstr \"使用者沒有使用 API Key 的權限\"\n\nmsgid \"Import knowledge workflow\"\nmsgstr \"匯入知識工作流\"\n\nmsgid \"Export knowledge workflow\"\nmsgstr \"匯出知識工作流\"\n\nmsgid \"Role IDs cannot be empty\"\nmsgstr \"角色 ID 不能为空\"\n\nmsgid \"Some roles do not exist\"\nmsgstr \"部分角色不存在\"\n\nmsgid \"Authorized pagination list for obtaining resources\"\nmsgstr \"獲取資源的關係分頁清單\"\n\nmsgid \"Resources mapping\"\nmsgstr \"資源映射\"\n\nmsgid \"Batch set user roles\"\nmsgstr \"批量設置用戶角色\"\n\nmsgid \"Role Setting cannot be empty\"\nmsgstr \"角色設置不能為空\"\n\nmsgid \"View related resources\"\nmsgstr \"查看關聯資源\"\n\nmsgid \"Feedback reason\"\nmsgstr \"反饋理由\"\n\nmsgid \"Other reason content\"\nmsgstr \"其他反饋理由內容\"\n\nmsgid \"accurate\"\nmsgstr \"內容準確\"\n\nmsgid \"complete\"\nmsgstr \"內容完善\"\n\nmsgid \"inaccurate\"\nmsgstr \"內容不準確\"\n\nmsgid \"incomplete\"\nmsgstr \"內容不完善\"\n\nmsgid \"Secret key is invalid\"\nmsgstr \"密鑰無效\"\n\nmsgid \"Secret key is expired\"\nmsgstr \"密鑰已過期\"\n\nmsgid \"Online Usage\"\nmsgstr \"線上使用\"\n\nmsgid \"API Call\"\nmsgstr \"API 調用\"\n\nmsgid \"Enterprise WeChat\"\nmsgstr \"企業微信應用\"\n\nmsgid \"WeChat Public Account\"\nmsgstr \"微信公眾號\"\n\nmsgid \"Lark\"\nmsgstr \"飛書應用\"\n\nmsgid \"DingTalk\"\nmsgstr \"釘釘應用\"\n\nmsgid \"Enterprise WeChat Robot\"\nmsgstr \"企業微信機器人\"\n\nmsgid \"Trigger\"\nmsgstr \"觸發器\"\n\nmsgid \"Slack\"\nmsgstr \"Slack 應用\"\n\nmsgid \"Root Directory\"\nmsgstr \"根目錄\"\n\nmsgid \"Create trigger\"\nmsgstr \"建立觸發器\"\n\nmsgid \"Get the trigger list\"\nmsgstr \"取得觸發器清單\"\n\nmsgid \"Get trigger details\"\nmsgstr \"取得觸發器詳情\"\n\nmsgid \"Modify the trigger\"\nmsgstr \"修改觸發器\"\n\nmsgid \"Delete the trigger\"\nmsgstr \"刪除觸發器\"\n\nmsgid \"Delete trigger in batches\"\nmsgstr \"批次刪除觸發器\"\n\nmsgid \"Activate trigger in batches\"\nmsgstr \"批次啟用/停用觸發器\"\n\nmsgid \"Get the trigger list by page\"\nmsgstr \"分頁取得觸發器清單\"\n\nmsgid \"Create trigger in source\"\nmsgstr \"資源端建立觸發器\"\n\nmsgid \"Get the trigger list of source\"\nmsgstr \"取得資源端觸發器清單\"\n\nmsgid \"Get Task source trigger details\"\nmsgstr \"取得資源端觸發器詳情\"\n\nmsgid \"Delete the task source trigger\"\nmsgstr \"刪除資源端觸發器\"\n\nmsgid \"Get the task list of triggers\"\nmsgstr \"取得觸發器任務清單\"\n\nmsgid \"Retrieve detailed records of tasks executed by the trigger.\"\nmsgstr \"取得由該觸發器執行的任務詳細記錄。\"\n\nmsgid \"Get a paginated list of execution records for trigger tasks.\"\nmsgstr \"取得觸發器任務執行記錄的分頁清單。\"\n\nmsgid \"%s must be an array\"\nmsgstr \"%s 必須是陣列類型\"\n\nmsgid \"%s must not be empty\"\nmsgstr \"%s 不能為空\"\n\nmsgid \"%s values must be between %s and %s\"\nmsgstr \"%s 的值必須在 %s 到 %s 之間\"\n\nmsgid \"Invalid time format: %s, must be HH:MM (e.g., 09:00)\"\nmsgstr \"時間格式無效: %s，必須是 HH:MM 格式 (例如: 09:00)\"\n\nmsgid \"schedule_type must be one of %s\"\nmsgstr \"schedule_type 必須是以下值之一: %s\"\n\nmsgid \"interval_value must be an integer greater than or equal to 1\"\nmsgstr \"interval_value 必須是大於或等於 1 的整數\"\n\nmsgid \"interval_unit must be one of %s\"\nmsgstr \"interval_unit 必須是以下值之一: %s\"\n\nmsgid \"body must be an array\"\nmsgstr \"body 必須是陣列類型\"\n\nmsgid \"Error trigger type\"\nmsgstr \"觸發器類型錯誤\"\n\nmsgid \"The following id does not exist: %s\"\nmsgstr \"以下 id 不存在: %s\"\n\nmsgid \"%s must be a dict\"\nmsgstr \"%s 必須是字典類型\"\n\nmsgid \"input_field_list must be a dict\"\nmsgstr \"input_field_list 必須是字典類型\"\n\nmsgid \"%s type requires %s field\"\nmsgstr \"%s 類型需要 %s 欄位\"\n\nmsgid \"trigger name\"\nmsgstr \"觸發器名稱\"\n\nmsgid \"trigger description\"\nmsgstr \"觸發器描述\"\n\nmsgid \"trigger setting\"\nmsgstr \"觸發器設定\"\n\nmsgid \"Trigger ID\"\nmsgstr \"觸發器ID\"\n\nmsgid \"Trigger task can not be empty\"\nmsgstr \"觸發器任務不能為空\"\n\nmsgid \"%s id does not exist\"\nmsgstr \"%s id 不存在\"\n\nmsgid \"Trigger id does not exist\"\nmsgstr \"觸發器 id 不存在\"\n\nmsgid \"Trigger not found\"\nmsgstr \"未找到觸發器\"\n\nmsgid \"Trigger must have at least one task\"\nmsgstr \"觸發器必須至少有一個任務\"\n\nmsgid \"Trigger task number must be one\"\nmsgstr \"觸發器任務數量必須為一個\"\n\nmsgid \"Incorrect trigger task\"\nmsgstr \"觸發器任務不正確\"\n\nmsgid \"Trigger task ID\"\nmsgstr \"觸發器任務ID\"\n\nmsgid \"Trigger task record ID\"\nmsgstr \"觸發器任務記錄ID\"\n\nmsgid \"Trigger task record id does not exist\"\nmsgstr \"觸發器任務記錄 id 不存在\"\n\nmsgid \"Order field\"\nmsgstr \"排序欄位\"\n\nmsgid \"System Trigger\"\nmsgstr \"系統觸發器\"\n\nmsgid \"Get the System trigger list of source\"\nmsgstr \"取得來源的系統觸發器清單\"\n\nmsgid \"Get System Task source trigger details\"\nmsgstr \"取得系統任務來源觸發器詳情\"\n\nmsgid \"Modify the System task source trigger\"\nmsgstr \"修改系統任務來源觸發器\"\n\nmsgid \"Modify the task source trigger\"\nmsgstr \"修改任務來源觸發器\"\n\nmsgid \"Delete the System task source trigger\"\nmsgstr \"刪除系統任務來源觸發器\"\n\nmsgid \"Invalid source type\"\nmsgstr \"無效的來源類型\"\n\nmsgid \"Shared tool is not supported\"\nmsgstr \"不支援共享工具\"\n\nmsgid \"Read Trigger\"\nmsgstr \"檢視觸發器\"\n\nmsgid \"Create Trigger\"\nmsgstr \"建立觸發器\"\n\nmsgid \"Edit Trigger\"\nmsgstr \"編輯觸發器\"\n\nmsgid \"Delete Trigger\"\nmsgstr \"刪除觸發器\"\n\nmsgid \"Read execute record\"\nmsgstr \"檢視執行記錄\"\n\nmsgid \"ADMIN\"\nmsgstr \"系統管理員\"\n\nmsgid \"WORKSPACE_MANAGE\"\nmsgstr \"空間管理員\"\n\nmsgid \"USER\"\nmsgstr \"普通用戶\"\n\nmsgid \"Generate share link\"\nmsgstr \"產生分享連結\"\n\nmsgid \"Chat record link\"\nmsgstr \"聊天記錄連結\"\n\nmsgid \"Get chat record by share link\"\nmsgstr \"透過分享連結取得聊天記錄\"\n\nmsgid \"Invalid chat record ids\"\nmsgstr \"無效的聊天記錄ID\"\n\nmsgid \"Share link does not exist\"\nmsgstr \"分享連結不存在\"\n\nmsgid \"Chat has been deleted\"\nmsgstr \"聊天記錄已被刪除\"\n\nmsgid \"cron type requires cron_expression field\"\nmsgstr \"cron 類型需要 cron_expression 欄位\"\n\nmsgid \"Invalid cron expression: %s\"\nmsgstr \"Cron 表達式不合法：%s\"\n\nmsgid \"Batch Remove Documents from Tag\"\nmsgstr \"批量刪除標籤下的文件\"\n\nmsgid \"Document does not belong to current knowledge\"\nmsgstr \"文件不屬於當前知識庫\"\n\nmsgid \"Move an application\"\nmsgstr \"移動應用程序\""
  },
  {
    "path": "apps/manage.py",
    "content": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\nimport os\nimport sys\n\n\ndef main():\n    \"\"\"Run administrative tasks.\"\"\"\n    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maxkb.settings')\n    try:\n        from django.core.management import execute_from_command_line\n    except ImportError as exc:\n        raise ImportError(\n            \"Couldn't import Django. Are you sure it's installed and \"\n            \"available on your PYTHONPATH environment variable? Did you \"\n            \"forget to activate a virtual environment?\"\n        ) from exc\n    execute_from_command_line(sys.argv)\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "apps/maxkb/__init__.py",
    "content": ""
  },
  {
    "path": "apps/maxkb/asgi.py",
    "content": "\"\"\"\nASGI config for maxkb project.\n\nIt exposes the ASGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/4.2/howto/deployment/asgi/\n\"\"\"\n\nimport os\n\nfrom django.core.asgi import get_asgi_application\n\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maxkb.settings')\n\napplication = get_asgi_application()\n"
  },
  {
    "path": "apps/maxkb/conf.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： conf.py\n    @date：2025/4/11 16:58\n    @desc:\n\"\"\"\nimport errno\nimport logging\nimport os\n\nimport yaml\n\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\nPROJECT_DIR = os.path.dirname(BASE_DIR)\nlogger = logging.getLogger('maxkb.conf')\n\n\nclass Config(dict):\n    defaults = {\n        # 数据库相关配置\n        \"DB_HOST\": \"127.0.0.1\",\n        \"DB_PORT\": 5432,\n        \"DB_USER\": \"root\",\n        \"DB_PASSWORD\": \"Password123@postgres\",\n        \"DB_ENGINE\": \"dj_db_conn_pool.backends.postgresql\",\n        \"DB_MAX_OVERFLOW\": 80,\n        'LOCAL_MODEL_HOST': '127.0.0.1',\n        'LOCAL_MODEL_PORT': '11636',\n        'LOCAL_MODEL_PROTOCOL': \"http\",\n        'LOCAL_MODEL_HOST_WORKER': 1,\n        # 语言\n        'LANGUAGE_CODE': 'zh-CN',\n        \"DEBUG\": False,\n        # redis host\n        'REDIS_HOST': '127.0.0.1',\n        # 端口\n        'REDIS_PORT': 6379,\n        # 密码\n        'REDIS_PASSWORD': 'Password123@redis',\n        # 库\n        'REDIS_DB': 0,\n        # 最大连接数\n        'REDIS_MAX_CONNECTIONS': 100\n    }\n\n    def get_debug(self) -> bool:\n        return self.get('DEBUG') if 'DEBUG' in self else True\n\n    def get_time_zone(self) -> str:\n        return self.get('TIME_ZONE') if 'TIME_ZONE' in self else 'Asia/Shanghai'\n\n    def get_db_setting(self) -> dict:\n        return {\n            \"NAME\": self.get('DB_NAME'),\n            \"HOST\": self.get('DB_HOST'),\n            \"PORT\": self.get('DB_PORT'),\n            \"USER\": self.get('DB_USER'),\n            \"PASSWORD\": self.get('DB_PASSWORD'),\n            \"ENGINE\": self.get('DB_ENGINE'),\n            \"CONN_MAX_AGE\": 0,\n            \"POOL_OPTIONS\": {\n                \"POOL_SIZE\": 20,\n                \"MAX_OVERFLOW\": int(self.get('DB_MAX_OVERFLOW')),\n                \"RECYCLE\": 1800,\n                \"PRE_PING\": True,\n                \"TIMEOUT\": 30\n            }\n        }\n\n    def get_cache_setting(self):\n        redis_config = {\n            'default': {\n                'BACKEND': 'django_redis.cache.RedisCache',\n                'LOCATION': f'redis://{self.get(\"REDIS_HOST\")}:{self.get(\"REDIS_PORT\")}/{self.get(\"REDIS_DB\")}',\n                'OPTIONS': {\n                    'CLIENT_CLASS': 'django_redis.client.DefaultClient',\n                    \"PASSWORD\": self.get(\"REDIS_PASSWORD\"),\n                    \"CONNECTION_POOL_KWARGS\": {\"max_connections\": int(self.get(\"REDIS_MAX_CONNECTIONS\"))}\n                },\n            },\n        }\n        if self.get('REDIS_SENTINEL_SENTINELS') is not None:\n            sentinels_str = self.get('REDIS_SENTINEL_SENTINELS')\n            sentinels = [\n                (host.strip(), int(port))\n                for hostport in sentinels_str.split(',')\n                for host, port in [hostport.strip().split(':')]\n            ]\n\n            redis_config['default']['LOCATION'] = f'redis://{self.get(\"REDIS_SENTINEL_MASTER\")}/{self.get(\"REDIS_DB\")}'\n            redis_config['default']['OPTIONS'].update({\n                'CLIENT_CLASS': 'django_redis.client.SentinelClient',\n                'SENTINELS': sentinels,\n                'SENTINEL_MASTER': self.get('REDIS_SENTINEL_MASTER'),\n                'PASSWORD': self.get(\"REDIS_PASSWORD\"),\n            })\n\n        return redis_config\n\n    def get_language_code(self):\n        return self.get('LANGUAGE_CODE', 'zh-CN')\n\n    def get_log_level(self):\n        return self.get('LOG_LEVEL', 'DEBUG')\n\n    def get_sandbox_python_package_paths(self):\n        return self.get('SANDBOX_PYTHON_PACKAGE_PATHS',\n                        '/opt/py3/lib/python3.11/site-packages,/opt/maxkb-app/sandbox/python-packages,/opt/maxkb/python-packages')\n\n    def get_admin_path(self):\n        return self.get('ADMIN_PATH', '/admin')\n\n    def get_chat_path(self):\n        return self.get('CHAT_PATH', '/chat')\n\n    def get_session_timeout(self):\n        return int(self.get('SESSION_TIMEOUT', 28800))\n\n    def __init__(self, *args):\n        super().__init__(*args)\n\n    def __repr__(self):\n        return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))\n\n    def __getitem__(self, item):\n        return self.get(item)\n\n    def __getattr__(self, item):\n        return self.get(item)\n\n\nclass ConfigManager:\n    config_class = Config\n\n    def __init__(self, root_path=None):\n        self.root_path = root_path\n        self.config = self.config_class()\n        for key in self.config_class.defaults:\n            self.config[key] = self.config_class.defaults[key]\n\n    def from_mapping(self, *mapping, **kwargs):\n        \"\"\"Updates the config like :meth:`update` ignoring items with non-upper\n        keys.\n\n        .. versionadded:: 0.11\n        \"\"\"\n        mappings = []\n        if len(mapping) == 1:\n            if hasattr(mapping[0], 'items'):\n                mappings.append(mapping[0].items())\n            else:\n                mappings.append(mapping[0])\n        elif len(mapping) > 1:\n            raise TypeError(\n                'expected at most 1 positional argument, got %d' % len(mapping)\n            )\n        mappings.append(kwargs.items())\n        for mapping in mappings:\n            for (key, value) in mapping:\n                if key.isupper():\n                    self.config[key] = value\n        return True\n\n    def from_yaml(self, filename, silent=False):\n        if self.root_path:\n            filename = os.path.join(self.root_path, filename)\n        try:\n            with open(filename, 'rt', encoding='utf8') as f:\n                obj = yaml.safe_load(f)\n        except IOError as e:\n            if silent and e.errno in (errno.ENOENT, errno.EISDIR):\n                return False\n            e.strerror = 'Unable to load configuration file (%s)' % e.strerror\n            raise\n        if obj:\n            return self.from_mapping(obj)\n        return True\n\n    def load_from_yml(self):\n        for i in ['config_example.yml', 'config.yaml', 'config.yml']:\n            if not os.path.isfile(os.path.join(self.root_path, i)):\n                continue\n            loaded = self.from_yaml(i)\n            if loaded:\n                return True\n        msg = f\"\"\"\n\n                   Error: No config file found.\n\n                   You can run `cp config_example.yml {self.root_path}/config.yml`, and edit it.\n\n                   \"\"\"\n        raise ImportError(msg)\n\n    def load_from_env(self):\n        keys = os.environ.keys()\n        config = {key.replace('MAXKB_', ''): os.environ.get(key) for key in keys if key.startswith('MAXKB_')}\n        if len(config.keys()) <= 0:\n            msg = f\"\"\"\n\n                             Error: No config env found.\n\n                             Please set environment variables\n                                MAXKB_CONFIG_TYPE: 配置文件读取方式 FILE: 使用配置文件配置  ENV: 使用ENV配置\n                                MAXKB_DB_NAME: 数据库名称\n                                MAXKB_DB_HOST: 数据库主机\n                                MAXKB_DB_PORT: 数据库端口\n                                MAXKB_DB_USER: 数据库用户名\n                                MAXKB_DB_PASSWORD: 数据库密码\n                                \n                                MAXKB_REDIS_HOST:缓存数据库主机\n                                MAXKB_REDIS_PORT:缓存数据库端口\n                                MAXKB_REDIS_PASSWORD:缓存数据库密码\n                                MAXKB_REDIS_DB:缓存数据库\n                                MAXKB_REDIS_MAX_CONNECTIONS:缓存数据库最大连接数\n                             \"\"\"\n            raise ImportError(msg)\n        self.from_mapping(config)\n        return True\n\n    @classmethod\n    def load_user_config(cls, root_path=None, config_class=None):\n        config_class = config_class or Config\n        cls.config_class = config_class\n        if not root_path:\n            root_path = PROJECT_DIR\n        manager = cls(root_path=root_path)\n        config_type = os.environ.get('MAXKB_CONFIG_TYPE')\n        if config_type is None or config_type != 'ENV':\n            manager.load_from_yml()\n        else:\n            manager.load_from_env()\n        config = manager.config\n        return config\n"
  },
  {
    "path": "apps/maxkb/const.py",
    "content": "# -*- coding: utf-8 -*-\n#\nimport os\n\nfrom dotenv import load_dotenv\n\nfrom .conf import ConfigManager\n\n__all__ = ['BASE_DIR', 'PROJECT_DIR', 'VERSION', 'CONFIG']\n\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\nLOG_DIR = os.path.join('/', 'opt', 'maxkb', 'logs')\nPROJECT_DIR = os.path.dirname(BASE_DIR)\nVERSION = '2.0.0'\n\n# load environment variables from .env file\nload_dotenv()\n# print(os.getenv('MAXKB_CONFIG'))\nif os.getenv('MAXKB_CONFIG') is not None:\n    CONFIG = ConfigManager.load_user_config(root_path=PROJECT_DIR)\nelse:\n    CONFIG = ConfigManager.load_user_config(root_path=os.path.abspath('/opt/maxkb/conf'))\n\n"
  },
  {
    "path": "apps/maxkb/settings/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/4/11 16:39\n    @desc:\n\"\"\"\nfrom .base import *\nfrom .logging import *\nfrom .auth import *\nfrom .lib import *\nfrom .mem import *"
  },
  {
    "path": "apps/maxkb/settings/auth/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py\n    @date：2025/11/5 14:50\n    @desc:\n\"\"\"\nimport os\n\nif os.environ.get('SERVER_NAME', 'web') == 'local_model':\n    from .model import *\nelse:\n    from .web import *\n"
  },
  {
    "path": "apps/maxkb/settings/auth/model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： auth.py\n    @date：2024/7/9 18:47\n    @desc:\n\"\"\"\n\nAUTH_HANDLES = [\n]\nCHAT_AUTH_HANDLES = [\n]\n"
  },
  {
    "path": "apps/maxkb/settings/auth/web.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： auth.py\n    @date：2024/7/9 18:47\n    @desc:\n\"\"\"\nUSER_TOKEN_AUTH = 'common.auth.handle.impl.user_token.UserToken'\nCHAT_ANONYMOUS_USER_AURH = 'common.auth.handle.impl.chat_anonymous_user_token.ChatAnonymousUserToken'\nAPPLICATION_KEY_AUTH = 'common.auth.handle.impl.application_key.ApplicationKey'\nAUTH_HANDLES = [\n    USER_TOKEN_AUTH\n]\n\nCHAT_AUTH_HANDLES = [\n    CHAT_ANONYMOUS_USER_AURH,\n    APPLICATION_KEY_AUTH\n]\n"
  },
  {
    "path": "apps/maxkb/settings/base/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/11/5 14:53\n    @desc:\n\"\"\"\nimport os\n\nif os.environ.get('SERVER_NAME', 'web') == 'local_model':\n    from .model import *\nelse:\n    from .web import *\n"
  },
  {
    "path": "apps/maxkb/settings/base/model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： model.py\n    @date：2025/11/5 14:53\n    @desc:\n\"\"\"\n\nfrom pathlib import Path\nfrom ...const import CONFIG, PROJECT_DIR\nimport os\nfrom django.utils.translation import gettext_lazy as _\n\n# Build paths inside the project like this: BASE_DIR / 'subdir'.\nBASE_DIR = Path(__file__).resolve().parent.parent.parent\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = CONFIG.get(\"SECRET_KEY\") or 'django-insecure-zm^1_^i5)3gp^&0io6zg72&z!a*d=9kf9o2%uft+27l)+t(#3e'\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = CONFIG.get_debug()\n\nALLOWED_HOSTS = ['*']\n\n# Application definition\n\nINSTALLED_APPS = [\n    'django.contrib.contenttypes',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'rest_framework',\n    'local_model',\n]\n\nMIDDLEWARE = [\n    'django.middleware.locale.LocaleMiddleware',\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n\n]\n\nREST_FRAMEWORK = {\n    'EXCEPTION_HANDLER': 'common.exception.handle_exception.handle_exception',\n    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',\n    'DEFAULT_AUTHENTICATION_CLASSES': ['common.auth.authenticate.AnonymousAuthentication']\n}\nSTATICFILES_DIRS = [(os.path.join(PROJECT_DIR, 'ui', 'dist'))]\nSTATIC_ROOT = os.path.join(BASE_DIR.parent, 'static')\nROOT_URLCONF = 'maxkb.urls'\nAPPS_DIR = os.path.join(PROJECT_DIR, 'apps')\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [\"apps/static/admin\"],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n    },\n    {\"NAME\": \"CHAT\",\n     'BACKEND': 'django.template.backends.django.DjangoTemplates',\n     'DIRS': [\"apps/static/chat\"],\n     'APP_DIRS': True,\n     'OPTIONS': {\n         'context_processors': [\n             'django.template.context_processors.debug',\n             'django.template.context_processors.request',\n             'django.contrib.auth.context_processors.auth',\n             'django.contrib.messages.context_processors.messages',\n         ],\n     },\n     },\n    {\"NAME\": \"DOC\",\n     'BACKEND': 'django.template.backends.django.DjangoTemplates',\n     'DIRS': [\"apps/static/drf_spectacular_sidecar\"],\n     'APP_DIRS': True,\n     'OPTIONS': {\n         'context_processors': [\n             'django.template.context_processors.debug',\n             'django.template.context_processors.request',\n             'django.contrib.auth.context_processors.auth',\n             'django.contrib.messages.context_processors.messages',\n         ],\n     },\n     },\n]\nSPECTACULAR_SETTINGS = {\n    'TITLE': 'MaxKB API',\n    'DESCRIPTION': _('Intelligent customer service platform'),\n    'VERSION': 'v2',\n    'SERVE_INCLUDE_SCHEMA': False,\n    # OTHER SETTINGS\n    'SWAGGER_UI_DIST': f'{CONFIG.get_admin_path()}/api-doc/swagger-ui-dist',  # shorthand to use the sidecar instead\n    'SWAGGER_UI_FAVICON_HREF': f'{CONFIG.get_admin_path()}/api-doc/swagger-ui-dist/favicon-32x32.png',\n    'REDOC_DIST': f'{CONFIG.get_admin_path()}/api-doc/redoc',\n    'SECURITY_DEFINITIONS': {\n        'Bearer': {\n            'type': 'apiKey',\n            'name': 'AUTHORIZATION',\n            'in': 'header',\n        }\n    }\n}\nWSGI_APPLICATION = 'maxkb.wsgi.application'\n\n# Database\n# https://docs.djangoproject.com/en/4.2/ref/settings/#databases\n\nDATABASES = {'default': CONFIG.get_db_setting()}\n\nCACHES = CONFIG.get_cache_setting()\n\n# Password validation\n# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',\n    },\n]\n\n# Internationalization\n# https://docs.djangoproject.com/en/4.2/topics/i18n/\n\nLANGUAGE_CODE = CONFIG.get(\"LANGUAGE_CODE\")\n\nTIME_ZONE = CONFIG.get_time_zone()\n\nUSE_I18N = True\n\nUSE_TZ = True\n\n# 文件上传配置\nDATA_UPLOAD_MAX_NUMBER_FILES = 1000\n\n# 支持的语言\nLANGUAGES = [\n    ('en', 'English'),\n    ('zh', '中文简体'),\n    ('zh-hant', '中文繁体')\n]\n# 翻译文件路径\nLOCALE_PATHS = [\n    os.path.join(BASE_DIR.parent, 'locales')\n]\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/4.2/howto/static-files/\n\nSTATIC_URL = 'static/'\n\n# Default primary key field type\n# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field\n\nDEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'\n\nedition = 'CE'\n\nif os.environ.get('MAXKB_REDIS_SENTINEL_SENTINELS') is not None:\n    DJANGO_REDIS_CONNECTION_FACTORY = \"django_redis.pool.SentinelConnectionFactory\"\n"
  },
  {
    "path": "apps/maxkb/settings/base/web.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： web.py\n    @date：2025/11/5 14:53\n    @desc:\n\"\"\"\nfrom pathlib import Path\nfrom ...const import CONFIG, PROJECT_DIR\nimport os\nfrom django.utils.translation import gettext_lazy as _\n\n# Build paths inside the project like this: BASE_DIR / 'subdir'.\nBASE_DIR = Path(__file__).resolve().parent.parent.parent\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = CONFIG.get(\"SECRET_KEY\") or 'django-insecure-zm^1_^i5)3gp^&0io6zg72&z!a*d=9kf9o2%uft+27l)+t(#3e'\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = CONFIG.get_debug()\n\nALLOWED_HOSTS = ['*']\n\n# Application definition\n\nINSTALLED_APPS = [\n    'django.contrib.contenttypes',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'rest_framework',\n    'drf_spectacular',\n    'drf_spectacular_sidecar',\n    'users.apps.UsersConfig',\n    'tools.apps.ToolConfig',\n    'knowledge',\n    'common',\n    'system_manage',\n    'models_provider',\n    'django_celery_beat',\n    'application',\n    'chat',\n    'oss',\n    'trigger',\n    'django_apscheduler',\n]\n\nMIDDLEWARE = [\n    'django.middleware.locale.LocaleMiddleware',\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'common.middleware.gzip.GZipMiddleware',\n    'common.middleware.chat_headers_middleware.ChatHeadersMiddleware',\n    'common.middleware.cross_domain_middleware.CrossDomainMiddleware',\n    'common.middleware.doc_headers_middleware.DocHeadersMiddleware',\n\n]\n\nREST_FRAMEWORK = {\n    'EXCEPTION_HANDLER': 'common.exception.handle_exception.handle_exception',\n    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',\n    'DEFAULT_AUTHENTICATION_CLASSES': ['common.auth.authenticate.AnonymousAuthentication']\n}\nSTATICFILES_DIRS = [(os.path.join(PROJECT_DIR, 'ui', 'dist'))]\nSTATIC_ROOT = os.path.join(BASE_DIR.parent, 'static')\nROOT_URLCONF = 'maxkb.urls'\nAPPS_DIR = os.path.join(PROJECT_DIR, 'apps')\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [\"apps/static/admin\"],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n    },\n    {\"NAME\": \"CHAT\",\n     'BACKEND': 'django.template.backends.django.DjangoTemplates',\n     'DIRS': [\"apps/static/chat\"],\n     'APP_DIRS': True,\n     'OPTIONS': {\n         'context_processors': [\n             'django.template.context_processors.debug',\n             'django.template.context_processors.request',\n             'django.contrib.auth.context_processors.auth',\n             'django.contrib.messages.context_processors.messages',\n         ],\n     },\n     },\n    {\"NAME\": \"DOC\",\n     'BACKEND': 'django.template.backends.django.DjangoTemplates',\n     'DIRS': [\"apps/static/drf_spectacular_sidecar\"],\n     'APP_DIRS': True,\n     'OPTIONS': {\n         'context_processors': [\n             'django.template.context_processors.debug',\n             'django.template.context_processors.request',\n             'django.contrib.auth.context_processors.auth',\n             'django.contrib.messages.context_processors.messages',\n         ],\n     },\n     },\n]\nSPECTACULAR_SETTINGS = {\n    'TITLE': 'MaxKB API',\n    'DESCRIPTION': _('Intelligent customer service platform'),\n    'VERSION': 'v2',\n    'SERVE_INCLUDE_SCHEMA': False,\n    # OTHER SETTINGS\n    'SWAGGER_UI_DIST': f'{CONFIG.get_admin_path()}/api-doc/swagger-ui-dist',  # shorthand to use the sidecar instead\n    'SWAGGER_UI_FAVICON_HREF': f'{CONFIG.get_admin_path()}/api-doc/swagger-ui-dist/favicon-32x32.png',\n    'REDOC_DIST': f'{CONFIG.get_admin_path()}/api-doc/redoc',\n    'SECURITY_DEFINITIONS': {\n        'Bearer': {\n            'type': 'apiKey',\n            'name': 'AUTHORIZATION',\n            'in': 'header',\n        }\n    }\n}\nWSGI_APPLICATION = 'maxkb.wsgi.application'\n\n# Database\n# https://docs.djangoproject.com/en/4.2/ref/settings/#databases\n\nDATABASES = {'default': CONFIG.get_db_setting()}\n\nCACHES = CONFIG.get_cache_setting()\n\n# Password validation\n# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',\n    },\n]\n\n# Internationalization\n# https://docs.djangoproject.com/en/4.2/topics/i18n/\n\nLANGUAGE_CODE = CONFIG.get(\"LANGUAGE_CODE\")\n\nTIME_ZONE = CONFIG.get_time_zone()\n\nUSE_I18N = True\n\nUSE_TZ = True\n\n# 文件上传配置\nDATA_UPLOAD_MAX_NUMBER_FILES = 1000\n\n# 支持的语言\nLANGUAGES = [\n    ('en', 'English'),\n    ('zh', '中文简体'),\n    ('zh-hant', '中文繁体')\n]\n# 翻译文件路径\nLOCALE_PATHS = [\n    os.path.join(BASE_DIR.parent, 'locales')\n]\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/4.2/howto/static-files/\n\nSTATIC_URL = 'static/'\n\n# Default primary key field type\n# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field\n\nDEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'\n\nedition = 'CE'\n\nif os.environ.get('MAXKB_REDIS_SENTINEL_SENTINELS') is not None:\n    DJANGO_REDIS_CONNECTION_FACTORY = \"django_redis.pool.SentinelConnectionFactory\"\n"
  },
  {
    "path": "apps/maxkb/settings/lib.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： lib.py\n    @date：2024/8/16 17:12\n    @desc:\n\"\"\"\nimport os\n\nfrom redis.sentinel import Sentinel\n\nfrom maxkb.const import CONFIG, PROJECT_DIR, LOG_DIR\n\n# celery相关配置\ncelery_data_dir = os.path.join(PROJECT_DIR, 'data', 'celery_task')\nif not os.path.exists(celery_data_dir) or not os.path.isdir(celery_data_dir):\n    os.makedirs(celery_data_dir, 0o700, exist_ok=True)\n    os.chmod(os.path.dirname(celery_data_dir), 0o700)\n# Celery using redis as broker\nredis_celery_once_db = CONFIG.get(\"REDIS_DB\")\nredis_celery_db = CONFIG.get('REDIS_DB')\nCELERY_BROKER_URL_FORMAT = '%(protocol)s://:%(password)s@%(host)s:%(port)s/%(db)s'\nif CONFIG.get('REDIS_SENTINEL_MASTER') and CONFIG.get('REDIS_SENTINEL_SENTINELS'):\n    sentinels_str = CONFIG.get('REDIS_SENTINEL_SENTINELS')\n    sentinels = [\n        (host.strip(), int(port))\n        for hostport in sentinels_str.split(',')\n        for host, port in [hostport.strip().split(':')]\n    ]\n    CELERY_BROKER_URL = ';'.join([CELERY_BROKER_URL_FORMAT % {\n        'protocol': 'sentinel', 'password': CONFIG.get('REDIS_PASSWORD'),\n        'host': item[0], 'port': item[1], 'db': redis_celery_db\n    } for item in sentinels])\n    SENTINEL_OPTIONS = {\n        'master_name': CONFIG.get('REDIS_SENTINEL_MASTER'),\n    }\n    CELERY_BROKER_TRANSPORT_OPTIONS = CELERY_RESULT_BACKEND_TRANSPORT_OPTIONS = SENTINEL_OPTIONS\n\n    # celery-once 哨兵模式配置\n    sentinel = Sentinel(\n        sentinels,\n        socket_timeout=5,\n        password=CONFIG.get('REDIS_SENTINEL_PASSWORD', CONFIG.get('REDIS_PASSWORD'))\n    )\n    master_host, master_port = sentinel.discover_master(CONFIG.get('REDIS_SENTINEL_MASTER'))\n    celery_once_settings = {\n        'url': f\"redis://:{CONFIG.get('REDIS_PASSWORD')}@{master_host}:{master_port}/{redis_celery_once_db}\",\n        'master_name': CONFIG.get('REDIS_SENTINEL_MASTER'),\n        'password': CONFIG.get('REDIS_PASSWORD'),\n        'db': redis_celery_once_db,\n    }\nelse:\n    CELERY_BROKER_URL = CELERY_BROKER_URL_FORMAT % {\n        'protocol': 'redis',\n        'password': CONFIG.get('REDIS_PASSWORD'),\n        'host': CONFIG.get('REDIS_HOST'),\n        'port': CONFIG.get('REDIS_PORT'),\n        'db': redis_celery_db\n    }\n    # celery-once 常规模式配置\n    celery_once_settings = {\n        'url': CELERY_BROKER_URL_FORMAT % {\n            'protocol': 'redis',\n            'password': CONFIG.get('REDIS_PASSWORD'),\n            'host': CONFIG.get('REDIS_HOST'),\n            'port': CONFIG.get('REDIS_PORT'),\n            'db': redis_celery_once_db,\n        }\n    }\nCELERY_result_backend = CELERY_BROKER_URL\nCELERY_timezone = CONFIG.get_time_zone()\nCELERY_ENABLE_UTC = False\nCELERY_task_serializer = 'hmac_signed_serializer'\nCELERY_result_serializer = 'hmac_signed_serializer'\nCELERY_accept_content = ['json', 'hmac_signed_serializer']\nCELERY_RESULT_EXPIRES = 600\nCELERY_WORKER_TASK_LOG_FORMAT = '%(asctime).19s %(message)s'\nCELERY_WORKER_LOG_FORMAT = '%(asctime).19s %(message)s'\nCELERY_TASK_EAGER_PROPAGATES = True\nCELERY_WORKER_REDIRECT_STDOUTS = True\nCELERY_WORKER_REDIRECT_STDOUTS_LEVEL = \"INFO\"\nCELERY_TASK_SOFT_TIME_LIMIT = 3600\nCELERY_WORKER_CANCEL_LONG_RUNNING_TASKS_ON_CONNECTION_LOSS = True\n# celery-once 配置\ncelery_once_settings['default_timeout'] = 3600  # 锁的默认超时时间（秒）\nCELERY_ONCE = {\n    'backend': 'celery_once.backends.Redis',\n    'settings': celery_once_settings\n}\nCELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True\nCELERY_LOG_DIR = os.path.join(LOG_DIR, 'celery')\n"
  },
  {
    "path": "apps/maxkb/settings/logging.py",
    "content": "# -*- coding: utf-8 -*-\n#\nimport os\n\nfrom ..const import PROJECT_DIR, CONFIG, LOG_DIR\n\nMAX_KB_LOG_FILE = os.path.join(LOG_DIR, 'maxkb.log')\nDRF_EXCEPTION_LOG_FILE = os.path.join(LOG_DIR, 'drf_exception.log')\nUNEXPECTED_EXCEPTION_LOG_FILE = os.path.join(LOG_DIR, 'unexpected_exception.log')\nLOG_LEVEL = CONFIG.get_log_level()\n\nLOGGING = {\n    'version': 1,\n    'disable_existing_loggers': False,\n    'formatters': {\n        'verbose': {\n            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'\n        },\n        'main': {\n            'datefmt': '%Y-%m-%d %H:%M:%S',\n            'format': '%(asctime)s [%(module)s %(levelname)s] %(message)s',\n        },\n        'exception': {\n            'datefmt': '%Y-%m-%d %H:%M:%S',\n            'format': '\\n%(asctime)s [%(levelname)s] %(message)s',\n        },\n        'simple': {\n            'format': '%(levelname)s %(message)s'\n        },\n        'syslog': {\n            'format': 'maxkb: %(message)s'\n        },\n        'msg': {\n            'format': '%(message)s'\n        }\n    },\n    'handlers': {\n        'null': {\n            'level': 'DEBUG',\n            'class': 'logging.NullHandler',\n        },\n        'console': {\n            'level': 'DEBUG',\n            'class': 'logging.StreamHandler',\n            'formatter': 'main'\n        },\n        'file': {\n            'encoding': 'utf8',\n            'level': 'DEBUG',\n            'class': 'common.utils.logger.DailyTimedRotatingFileHandler',\n            'when': 'midnight',\n            'interval': 1,\n            'backupCount': 7,\n            'formatter': 'main',\n            'filename': MAX_KB_LOG_FILE,\n        },\n        'drf_exception': {\n            'encoding': 'utf8',\n            'level': 'DEBUG',\n            'class': 'common.utils.logger.DailyTimedRotatingFileHandler',\n            'formatter': 'exception',\n            'when': 'midnight',\n            'interval': 1,\n            'backupCount': 7,\n            'filename': DRF_EXCEPTION_LOG_FILE,\n        },\n        'unexpected_exception': {\n            'encoding': 'utf8',\n            'level': 'DEBUG',\n            'class': 'common.utils.logger.DailyTimedRotatingFileHandler',\n            'when': 'midnight',\n            'interval': 1,\n            'backupCount': 7,\n            'formatter': 'exception',\n            'filename': UNEXPECTED_EXCEPTION_LOG_FILE,\n        },\n        'syslog': {\n            'level': 'INFO',\n            'class': 'logging.NullHandler',\n            'formatter': 'syslog'\n        },\n    },\n    'loggers': {\n        'django': {\n            'handlers': ['null'],\n            'propagate': False,\n            'level': LOG_LEVEL,\n        },\n        'django.request': {\n            'handlers': ['console', 'file', 'syslog'],\n            'level': LOG_LEVEL,\n            'propagate': False,\n        },\n        'sqlalchemy': {\n            'handlers': ['console', 'file', 'syslog'],\n            'level': \"ERROR\",\n            'propagate': False,\n        },\n        'django.db.backends': {\n            'handlers': ['console', 'file', 'syslog'],\n            'propagate': False,\n            'level': LOG_LEVEL,\n        },\n        'django.server': {\n            'handlers': ['console', 'file', 'syslog'],\n            'level': 'ERROR',\n            'propagate': False,\n        },\n        'max_kb': {\n            'handlers': ['console', 'file'],\n            'level': LOG_LEVEL,\n            'propagate': False,\n        },\n        'common.event': {\n            'handlers': ['console', 'file'],\n            'level': \"DEBUG\",\n            'propagate': False,\n        },\n    }\n}\n\nSYSLOG_ENABLE = CONFIG.SYSLOG_ENABLE\n\nif not os.path.isdir(LOG_DIR):\n    os.makedirs(LOG_DIR, mode=0o700, exist_ok=True)\n"
  },
  {
    "path": "apps/maxkb/settings/mem.py",
    "content": "# coding=utf-8\nimport os\nimport gc\nimport threading\nfrom maxkb.const import CONFIG\nfrom common.utils.logger import maxkb_logger\nimport random\nimport psutil\n\nCURRENT_PID=os.getpid()\n# 1 hour\nGC_INTERVAL = 3600\n\ndef enable_force_gc():\n    before = int(psutil.Process(CURRENT_PID).memory_info().rss / 1024 / 1024)\n    collected = gc.collect()\n    try:\n        import ctypes\n        ctypes.CDLL(\"libc.so.6\").malloc_trim(0)\n    except Exception:\n        pass\n    after = int(psutil.Process(CURRENT_PID).memory_info().rss / 1024 / 1024)\n    maxkb_logger.debug(f\"(PID: {CURRENT_PID}) Forced GC ({collected} objects and {before - after} MB recycled)\")\n    t = threading.Timer(GC_INTERVAL - random.randint(0, 900), enable_force_gc)\n    t.daemon = True\n    t.start()\n\nif CONFIG.get(\"ENABLE_FORCE_GC\", '1') == \"1\":\n    maxkb_logger.info(f\"(PID: {CURRENT_PID}) Forced GC enabled\")\n    enable_force_gc()\n"
  },
  {
    "path": "apps/maxkb/urls/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB-xpack\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/11/5 14:45\n    @desc:\n\"\"\"\nimport os\n\nif os.environ.get('SERVER_NAME', 'web') == 'local_model':\n    from .model import *\nelse:\n    from .web import *\n"
  },
  {
    "path": "apps/maxkb/urls/model.py",
    "content": "\"\"\"\nURL configuration for maxkb project.\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https://docs.djangoproject.com/en/4.2/topics/http/urls/\nExamples:\nFunction views\n    1. Add an import:  from my_app import views\n    2. Add a URL to urlpatterns:  path('', views.home, name='home')\nClass-based views\n    1. Add an import:  from other_app.views import Home\n    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')\nIncluding another URLconf\n    1. Import the include() function: from django.urls import include, path\n    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))\n\"\"\"\n\nfrom django.urls import path, include\n\nfrom maxkb.const import CONFIG\n\nadmin_api_prefix = CONFIG.get_admin_path()[1:] + '/api/'\nadmin_ui_prefix = CONFIG.get_admin_path()\nchat_api_prefix = CONFIG.get_chat_path()[1:] + '/api/'\nchat_ui_prefix = CONFIG.get_chat_path()\nurlpatterns = [\n    path(admin_api_prefix, include(\"local_model.urls\")),\n]\n"
  },
  {
    "path": "apps/maxkb/urls/web.py",
    "content": "\"\"\"\nURL configuration for maxkb project.\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https://docs.djangoproject.com/en/4.2/topics/http/urls/\nExamples:\nFunction views\n    1. Add an import:  from my_app import views\n    2. Add a URL to urlpatterns:  path('', views.home, name='home')\nClass-based views\n    1. Add an import:  from other_app.views import Home\n    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')\nIncluding another URLconf\n    1. Import the include() function: from django.urls import include, path\n    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))\n\"\"\"\nimport os\nfrom pathlib import Path\n\nfrom django.http import HttpResponse, HttpResponseRedirect\nfrom django.urls import path, re_path, include\nfrom django.views import static\nfrom rest_framework import status\n\nfrom chat.urls import urlpatterns as chat_urlpatterns\nfrom common.init.init_doc import init_doc\nfrom common.result import Result\nfrom maxkb import settings\nfrom maxkb.conf import PROJECT_DIR\nfrom maxkb.const import CONFIG\n\nadmin_api_prefix = CONFIG.get_admin_path()[1:] + '/api/'\nadmin_ui_prefix = CONFIG.get_admin_path()\nchat_api_prefix = CONFIG.get_chat_path()[1:] + '/api/'\nchat_ui_prefix = CONFIG.get_chat_path()\nurlpatterns = [\n    path(admin_api_prefix, include(\"users.urls\")),\n    path(admin_api_prefix, include(\"tools.urls\")),\n    path(admin_api_prefix, include(\"models_provider.urls\")),\n    path(admin_api_prefix, include(\"folders.urls\")),\n    path(admin_api_prefix, include(\"knowledge.urls\")),\n    path(admin_api_prefix, include(\"system_manage.urls\")),\n    path(admin_api_prefix, include(\"application.urls\")),\n    path(admin_api_prefix, include(\"trigger.urls\")),\n    path(admin_api_prefix, include(\"oss.urls\")),\n    path(chat_api_prefix, include(\"oss.urls\")),\n    path(chat_api_prefix, include(\"chat.urls\")),\n    path(f'{admin_ui_prefix[1:]}/', include('oss.retrieval_urls')),\n    path(f'{chat_ui_prefix[1:]}/', include('oss.retrieval_urls')),\n]\ninit_doc(urlpatterns, chat_urlpatterns)\n\n\ndef pro():\n    urlpatterns.append(\n        re_path(rf'^{CONFIG.get_admin_path()[1:]}/api-doc/(?P<path>.*)$', static.serve,\n                {'document_root': os.path.join(settings.STATIC_ROOT, \"drf_spectacular_sidecar\")}, name='doc'),\n    )\n\n    urlpatterns.append(\n        re_path(rf'^{CONFIG.get_chat_path()[1:]}/api-doc/(?P<path>.*)$', static.serve,\n                {'document_root': os.path.join(settings.STATIC_ROOT, \"drf_spectacular_sidecar\")}, name='doc_chat'),\n    )\n    # 暴露ui静态资源\n    urlpatterns.append(\n        re_path(rf\"^{CONFIG.get_admin_path()[1:]}/(?P<path>.*)$\", static.serve,\n                {'document_root': os.path.join(settings.STATIC_ROOT, \"admin\")},\n                name='admin'),\n    )\n    # 暴露ui静态资源\n    urlpatterns.append(\n        re_path(rf'^{CONFIG.get_chat_path()[1:]}/(?P<path>.*)$', static.serve,\n                {'document_root': os.path.join(settings.STATIC_ROOT, \"chat\")},\n                name='chat'),\n    )\n\n\nif not settings.DEBUG:\n    pro()\n\n\ndef get_index_html(index_path):\n    file = open(index_path, \"r\", encoding='utf-8')\n    content = file.read()\n    file.close()\n    return content\n\n\ndef get_all_files(directory):\n    base_path = Path(directory)\n    file_paths = [\n        '/' + str(file.relative_to(base_path)).replace('\\\\', '/')\n        for file in base_path.rglob('*')\n        if file.is_file()\n    ]\n    return sorted(file_paths, key=len, reverse=True)\n\n\nstatic_dict = {\n    chat_ui_prefix: get_all_files(os.path.join(PROJECT_DIR, 'apps', \"static\", 'chat')),\n    admin_ui_prefix: get_all_files(os.path.join(PROJECT_DIR, 'apps', \"static\", 'admin'))\n}\n\n\ndef page_not_found(request, exception):\n    \"\"\"\n    页面不存在处理\n    \"\"\"\n    if request.path.startswith(admin_ui_prefix + '/api/'):\n        return Result(response_status=status.HTTP_404_NOT_FOUND, code=404, message=\"HTTP_404_NOT_FOUND\")\n    if request.path.startswith(chat_ui_prefix + '/api/'):\n        return Result(response_status=status.HTTP_404_NOT_FOUND, code=404, message=\"HTTP_404_NOT_FOUND\")\n    if request.path.startswith(chat_ui_prefix):\n        in_ = [url for url in static_dict.get(chat_ui_prefix) if request.path.endswith(url)]\n        if len(in_) > 0:\n            a = chat_ui_prefix + in_[0]\n            return HttpResponseRedirect(a)\n        index_path = os.path.join(PROJECT_DIR, 'apps', \"static\", 'chat', 'index.html')\n        content = get_index_html(index_path)\n        content = content.replace(\"prefix: '/chat'\", f\"prefix: '{CONFIG.get_chat_path()}'\")\n        if not os.path.exists(index_path):\n            return HttpResponse(\"页面不存在\", status=404)\n        return HttpResponse(content, status=200)\n    elif request.path.startswith(admin_ui_prefix):\n        in_ = [url for url in static_dict.get(admin_ui_prefix) if request.path.endswith(url)]\n        if len(in_) > 0:\n            a = admin_ui_prefix + in_[0]\n            return HttpResponseRedirect(a)\n        index_path = os.path.join(PROJECT_DIR, 'apps', \"static\", 'admin', 'index.html')\n        if not os.path.exists(index_path):\n            return HttpResponse(\"页面不存在\", status=404)\n        content = get_index_html(index_path)\n        content = content.replace(\"prefix: '/admin'\", f\"prefix: '{CONFIG.get_admin_path()}'\").replace(\n            \"chatPrefix: '/chat'\", f\"chatPrefix: '{CONFIG.get_chat_path()}'\")\n        return HttpResponse(content, status=200)\n    else:\n        return HttpResponseRedirect(admin_ui_prefix + '/')\n\n\nhandler404 = page_not_found\n"
  },
  {
    "path": "apps/maxkb/wsgi/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/11/5 15:14\n    @desc:\n\"\"\"\nimport os\n\nif os.environ.get('SERVER_NAME', 'web') == 'local_model':\n    from .model import *\nelse:\n    from .web import *\n"
  },
  {
    "path": "apps/maxkb/wsgi/model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： model.py\n    @date：2025/11/5 15:14\n    @desc:\n\"\"\"\nimport os\n\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maxkb.settings')\n\napplication = get_wsgi_application()"
  },
  {
    "path": "apps/maxkb/wsgi/web.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： web.py\n    @date：2025/11/5 15:14\n    @desc:\n\"\"\"\nimport builtins\nimport os\nimport sys\n\nfrom django.core.wsgi import get_wsgi_application\n\n\nclass TorchBlocker:\n    def __init__(self):\n        self.original_import = builtins.__import__\n\n    def __call__(self, name, *args, **kwargs):\n        if len([True for i in\n                ['torch']\n                if\n                i in name.lower()]) > 0:\n            import types\n            return types.ModuleType(name)\n        else:\n            return self.original_import(name, *args, **kwargs)\n\n\n# 安装导入拦截器\nbuiltins.__import__ = TorchBlocker()\n\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maxkb.settings')\nos.environ['TIKTOKEN_CACHE_DIR'] = '/opt/maxkb-app/model/tokenizer/openai-tiktoken-cl100k-base'\napplication = get_wsgi_application()\n\n\ndef post_handler():\n    from common.database_model_manage.database_model_manage import DatabaseModelManage\n    from common import event\n    from common.init import init_template\n    event.run()\n    DatabaseModelManage.init()\n    init_template.run()\n\n\n# 启动后处理函数\npost_handler()\n"
  },
  {
    "path": "apps/models_provider/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "apps/models_provider/api/__init__.py",
    "content": "# coding=utf-8\n"
  },
  {
    "path": "apps/models_provider/api/model.py",
    "content": "# coding=utf-8\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\nfrom rest_framework import serializers\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, DefaultResultSerializer\nfrom models_provider.serializers.model_serializer import ModelModelSerializer, ModelCreateRequest\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass ModelCreateResponse(ResultSerializer):\n    def get_data(self):\n        return ModelModelSerializer()\n\n\nclass ModelListResponse(APIMixin):\n    @staticmethod\n    def get_response():\n        class ModelListResult(ResultSerializer):\n            def get_data(self):\n                return ModelModelSerializer(many=True)\n\n        return ModelListResult\n\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"workspace_id\",\n            description=_(\"workspace id\"),\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            required=True,\n        ),\n            OpenApiParameter(\n                name=\"name\",\n                description=_(\"model name\"),\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"model_type\",\n                description=_(\"model type\"),\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"model_name\",\n                description=_(\"base model\"),\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"provider\",\n                description=_(\"provider\"),\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"create_user\",\n                description=_(\"create user\"),\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                required=False,\n            )\n        ]\n\n\nclass ModelCreateAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return ModelCreateRequest\n\n    @staticmethod\n    def get_response():\n        return ModelCreateResponse\n\n    @classmethod\n    def get_parameters(cls):\n        return [OpenApiParameter(\n            name=\"workspace_id\",\n            description=_(\"workspace id\"),\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            required=True,\n        )]\n\n\nclass GetModelApi(APIMixin):\n\n    @staticmethod\n    def get_query_params_api():\n        return [OpenApiParameter(\n            name=\"workspace_id\",\n            description=_(\"workspace id\"),\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            required=True,\n        ), OpenApiParameter(\n            name=\"model_id\",\n            description=_(\"model id\"),\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            required=True,\n        )\n        ]\n    @staticmethod\n    def get_request():\n        return []\n\n    @staticmethod\n    def get_response():\n        return ModelCreateResponse\n\n\nclass ModelEditApi(APIMixin):\n    @staticmethod\n    def get_request():\n        return ModelCreateRequest\n\n    @staticmethod\n    def get_response():\n        return ModelCreateResponse\n\n\nclass DefaultModelResponse(APIMixin):\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer()\n"
  },
  {
    "path": "apps/models_provider/api/provide.py",
    "content": "# coding=utf-8\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer\nfrom rest_framework import serializers\nfrom django.utils.translation import gettext_lazy as _\n\n\nclass ProvideResponse(ResultSerializer):\n    def get_data(self):\n        return ProvideSerializer()\n\n\nclass ProvideSerializer(serializers.Serializer):\n    name = serializers.CharField(required=True, max_length=64, label=_(\"model name\"))\n    provider = serializers.CharField(required=True, label=_(\"provider\"))\n    icon = serializers.CharField(required=True, label=_(\"icon\"))\n\n\nclass ProvideListSerializer(serializers.Serializer):\n    key = serializers.CharField(required=True, max_length=64, label=_(\"model name\"))\n    value = serializers.CharField(required=True, label=_(\"value\"))\n\n\nclass ModelListSerializer(serializers.Serializer):\n    name = serializers.CharField(required=True, label=_(\"model name\"))\n    model_type = serializers.CharField(required=True, label=_(\"model type\"))\n    desc = serializers.CharField(required=True, label=_(\"model name\"))\n\n\nclass ModelParamsFormSerializer(serializers.Serializer):\n    input_type = serializers.CharField(required=False, label=_(\"input type\"))\n    label = serializers.CharField(required=False, label=_(\"label\"))\n    text_field = serializers.CharField(required=False, label=_(\"text field\"))\n    value_field = serializers.CharField(required=False, label=_(\"value field\"))\n    provider = serializers.CharField(required=False, label=_(\"provider\"))\n    method = serializers.CharField(required=False, label=_(\"method\"))\n    required = serializers.BooleanField(required=False, label=_(\"required\"))\n    default_value = serializers.CharField(required=False, label=_(\"default value\"))\n    relation_show_field_dict = serializers.DictField(required=False, label=_(\"relation show field dict\"))\n    relation_trigger_field_dict = serializers.DictField(required=False, label=_(\"relation trigger field dict\"))\n    trigger_type = serializers.CharField(required=False, label=_(\"trigger type\"))\n    attrs = serializers.DictField(required=False, label=_(\"attrs\"))\n    props_info = serializers.DictField(required=False, label=_(\"props info\"))\n\n\nclass ModelParamsFormResponse(ResultSerializer):\n    def get_data(self):\n        return ModelParamsFormSerializer(many=True)\n\n\nclass ModelListResponse(ResultSerializer):\n    def get_data(self):\n        return ModelListSerializer(many=True)\n\n\nclass ProvideListResponse(ResultSerializer):\n    def get_data(self):\n        return ProvideListSerializer(many=True)\n\n\nclass ProvideApi(APIMixin):\n    class ModelParamsForm(APIMixin):\n        @staticmethod\n        def get_query_params_api():\n            return [OpenApiParameter(\n                name=\"model_type\",\n                description=_(\"model type\"),\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                required=True,\n            ), OpenApiParameter(\n                name=\"provider\",\n                description=_(\"provider\"),\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                required=True,\n            ), OpenApiParameter(\n                name=\"model_name\",\n                description=_(\"model name\"),\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                required=True,\n            )\n            ]\n\n        @staticmethod\n        def get_response():\n            return ModelParamsFormResponse\n\n    class ModelList(APIMixin):\n        @staticmethod\n        def get_query_params_api():\n            return [OpenApiParameter(\n                name=\"model_type\",\n                description=_(\"model type\"),\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                required=True,\n            ), OpenApiParameter(\n                name=\"provider\",\n                description=_(\"provider\"),\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                required=True,\n            )\n            ]\n\n        @staticmethod\n        def get_response():\n            return ModelListResponse\n\n    @staticmethod\n    def get_response():\n        return ProvideResponse\n\n    class ModelTypeList(APIMixin):\n        @staticmethod\n        def get_query_params_api():\n            return [OpenApiParameter(\n                # 参数的名称是done\n                name=\"provider\",\n                # 对参数的备注\n                description=_(\"provider\"),\n                # 指定参数的类型\n                type=OpenApiTypes.STR,\n                location=OpenApiParameter.QUERY,\n                # 指定必须给\n                required=True,\n            )]\n\n        @staticmethod\n        def get_response():\n            return ProvideListResponse\n"
  },
  {
    "path": "apps/models_provider/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass ModelsProviderConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'models_provider'\n"
  },
  {
    "path": "apps/models_provider/base_model_provider.py",
    "content": "# coding=utf-8\n\nfrom abc import ABC, abstractmethod\nfrom enum import Enum\nfrom functools import reduce\nfrom typing import Dict, Iterator, Type, List\n\nfrom pydantic import BaseModel\n\nfrom common.exception.app_exception import AppApiException\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.utils.common import encryption\n\n\nclass DownModelChunkStatus(Enum):\n    success = \"success\"\n    error = \"error\"\n    pulling = \"pulling\"\n    unknown = 'unknown'\n\n\nclass ValidCode(Enum):\n    valid_error = 500\n    model_not_fount = 404\n\n\nclass DownModelChunk:\n    def __init__(self, status: DownModelChunkStatus, digest: str, progress: int, details: str, index: int):\n        self.details = details\n        self.status = status\n        self.digest = digest\n        self.progress = progress\n        self.index = index\n\n    def to_dict(self):\n        return {\n            \"details\": self.details,\n            \"status\": self.status.value,\n            \"digest\": self.digest,\n            \"progress\": self.progress,\n            \"index\": self.index\n        }\n\n\nclass IModelProvider(ABC):\n    @abstractmethod\n    def get_model_info_manage(self):\n        pass\n\n    @abstractmethod\n    def get_model_provide_info(self):\n        pass\n\n    def get_model_type_list(self):\n        return self.get_model_info_manage().get_model_type_list()\n\n    def get_model_list(self, model_type):\n        if model_type is None:\n            raise AppApiException(500, _('Model type cannot be empty'))\n        return self.get_model_info_manage().get_model_list_by_model_type(model_type)\n\n    def get_model_credential(self, model_type, model_name):\n        model_info = self.get_model_info_manage().get_model_info(model_type, model_name)\n        model_credential = model_info.model_credential\n\n        if model_type == 'TTI' and model_name.startswith(('qwen', 'wan2.6', 'wan')):\n            if hasattr(model_credential, 'api_base'):\n                api_base = model_credential.api_base\n                if hasattr(api_base, 'default_value') and not api_base.default_value:\n                    api_base.default_value = 'https://dashscope.aliyuncs.com/api/v1'\n        return model_credential\n\n    def get_model_params(self, model_type, model_name):\n        model_info = self.get_model_info_manage().get_model_info(model_type, model_name)\n        return model_info.model_credential\n\n    def is_valid_credential(self, model_type, model_name, model_credential: Dict[str, object],\n                            model_params: Dict[str, object], raise_exception=False):\n        model_info = self.get_model_info_manage().get_model_info(model_type, model_name)\n        return model_info.model_credential.is_valid(model_type, model_name, model_credential, model_params, self,\n                                                    raise_exception=raise_exception)\n\n    def get_model(self, model_type, model_name, model_credential: Dict[str, object], **model_kwargs) -> BaseModel:\n        model_info = self.get_model_info_manage().get_model_info(model_type, model_name)\n        return model_info.model_class.new_instance(model_type, model_name, model_credential, **model_kwargs)\n\n    def get_dialogue_number(self):\n        return 3\n\n    def down_model(self, model_type: str, model_name, model_credential: Dict[str, object]) -> Iterator[DownModelChunk]:\n        raise AppApiException(500, _('The current platform does not support downloading models'))\n\n\nclass MaxKBBaseModel(ABC):\n    @staticmethod\n    @abstractmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        pass\n\n    @staticmethod\n    def is_cache_model():\n        return True\n\n    @staticmethod\n    def filter_optional_params(model_kwargs):\n        optional_params = {}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming', 'show_ref_label', 'stream']:\n                if key == 'extra_body' and isinstance(value, dict):\n                    optional_params = {**optional_params, **value}\n                else:\n                    optional_params[key] = value\n        return optional_params\n\n\nclass BaseModelCredential(ABC):\n\n    @abstractmethod\n    def is_valid(self, model_type: str, model_name, model: Dict[str, object], model_params, provider,\n                 raise_exception=True):\n        pass\n\n    @abstractmethod\n    def encryption_dict(self, model_info: Dict[str, object]):\n        \"\"\"\n        :param model_info: 模型数据\n        :return: 加密后数据\n        \"\"\"\n        pass\n\n    def get_model_params_setting_form(self, model_name):\n        \"\"\"\n               模型参数设置表单\n               :return:\n        \"\"\"\n        pass\n\n    @staticmethod\n    def encryption(message: str):\n        \"\"\"\n            加密敏感字段数据  加密方式是 如果密码是 1234567890  那么给前端则是 123******890\n        :param message:\n        :return:\n        \"\"\"\n        return encryption(message)\n\n\nclass ModelTypeConst(Enum):\n    LLM = {'code': 'LLM', 'message': _('LLM')}\n    EMBEDDING = {'code': 'EMBEDDING', 'message': _('Embedding Model')}\n    STT = {'code': 'STT', 'message': _('Speech2Text')}\n    TTS = {'code': 'TTS', 'message': _('TTS')}\n    IMAGE = {'code': 'IMAGE', 'message': _('Vision Model')}\n    TTI = {'code': 'TTI', 'message': _('Image Generation')}\n    RERANKER = {'code': 'RERANKER', 'message': _('Rerank')}\n    # 文生视频 图生视频\n    TTV = {'code': 'TTV', 'message': _('Text to Video')}\n    ITV = {'code': 'ITV', 'message': _('Image to Video')}\n\n\nclass ModelInfo:\n    def __init__(self, name: str, desc: str, model_type: ModelTypeConst, model_credential: BaseModelCredential,\n                 model_class: Type[MaxKBBaseModel],\n                 **keywords):\n        self.name = name\n        self.desc = desc\n        self.model_type = model_type.name\n        self.model_credential = model_credential\n        self.model_class = model_class\n        if keywords is not None:\n            for key in keywords.keys():\n                self.__setattr__(key, keywords.get(key))\n\n    def get_name(self):\n        \"\"\"\n        获取模型名称\n        :return: 模型名称\n        \"\"\"\n        return self.name\n\n    def get_desc(self):\n        \"\"\"\n        获取模型描述\n        :return: 模型描述\n        \"\"\"\n        return self.desc\n\n    def get_model_type(self):\n        return self.model_type\n\n    def get_model_class(self):\n        return self.model_class\n\n    def to_dict(self):\n        return reduce(lambda x, y: {**x, **y},\n                      [{attr: self.__getattribute__(attr)} for attr in vars(self) if\n                       not attr.startswith(\"__\") and not attr == 'model_credential' and not attr == 'model_class'], {})\n\n\nclass ModelInfoManage:\n    def __init__(self):\n        self.model_dict = {}\n        self.model_list = []\n        self.default_model_list = []\n        self.default_model_dict = {}\n\n    def append_model_info(self, model_info: ModelInfo):\n        self.model_list.append(model_info)\n        model_type_dict = self.model_dict.get(model_info.model_type)\n        if model_type_dict is None:\n            self.model_dict[model_info.model_type] = {model_info.name: model_info}\n        else:\n            model_type_dict[model_info.name] = model_info\n\n    def append_default_model_info(self, model_info: ModelInfo):\n        self.default_model_list.append(model_info)\n        self.default_model_dict[model_info.model_type] = model_info\n\n    def get_model_list(self):\n        return [model.to_dict() for model in self.model_list]\n\n    def get_model_list_by_model_type(self, model_type):\n        return [model.to_dict() for model in self.model_list if model.model_type == model_type]\n\n    def get_model_type_list(self):\n        return [{'key': _type.value.get('message'), 'value': _type.value.get('code')} for _type in ModelTypeConst if\n                len([model for model in self.model_list if model.model_type == _type.name]) > 0]\n\n    def get_model_info(self, model_type, model_name) -> ModelInfo:\n        model_info = self.model_dict.get(model_type, {}).get(model_name, self.default_model_dict.get(model_type))\n        if model_info is None:\n            raise AppApiException(500, _('The model does not support'))\n        return model_info\n\n    class builder:\n        def __init__(self):\n            self.modelInfoManage = ModelInfoManage()\n\n        def append_model_info(self, model_info: ModelInfo):\n            self.modelInfoManage.append_model_info(model_info)\n            return self\n\n        def append_model_info_list(self, model_info_list: List[ModelInfo]):\n            for model_info in model_info_list:\n                self.modelInfoManage.append_model_info(model_info)\n            return self\n\n        def append_default_model_info(self, model_info: ModelInfo):\n            self.modelInfoManage.append_default_model_info(model_info)\n            return self\n\n        def build(self):\n            return self.modelInfoManage\n\n\nclass ModelProvideInfo:\n    def __init__(self, provider: str, name: str, icon: str):\n        self.provider = provider\n\n        self.name = name\n\n        self.icon = icon\n\n    def to_dict(self):\n        return reduce(lambda x, y: {**x, **y},\n                      [{attr: self.__getattribute__(attr)} for attr in vars(self) if\n                       not attr.startswith(\"__\")], {})\n"
  },
  {
    "path": "apps/models_provider/base_ttv.py",
    "content": "# coding=utf-8\nfrom abc import abstractmethod\n\nfrom pydantic import BaseModel\n\n\nclass BaseGenerationVideo(BaseModel):\n    @abstractmethod\n    def check_auth(self):\n        pass\n\n    @abstractmethod\n    def generate_video(self, prompt: str, negative_prompt: str = None, first_frame_url=None, last_frame_url=None):\n        pass\n"
  },
  {
    "path": "apps/models_provider/constants/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/constants/model_provider_constants.py",
    "content": "# coding=utf-8\nfrom enum import Enum\n\nfrom models_provider.impl.aliyun_bai_lian_model_provider.aliyun_bai_lian_model_provider import \\\n    AliyunBaiLianModelProvider\nfrom models_provider.impl.anthropic_model_provider.anthropic_model_provider import AnthropicModelProvider\nfrom models_provider.impl.aws_bedrock_model_provider.aws_bedrock_model_provider import BedrockModelProvider\nfrom models_provider.impl.azure_model_provider.azure_model_provider import AzureModelProvider\nfrom models_provider.impl.deepseek_model_provider.deepseek_model_provider import DeepSeekModelProvider\nfrom models_provider.impl.docker_ai_model_provider.docker_ai_model_provider import DockerModelProvider\nfrom models_provider.impl.gemini_model_provider.gemini_model_provider import GeminiModelProvider\nfrom models_provider.impl.kimi_model_provider.kimi_model_provider import KimiModelProvider\nfrom models_provider.impl.local_model_provider.local_model_provider import LocalModelProvider\nfrom models_provider.impl.ollama_model_provider.ollama_model_provider import OllamaModelProvider\nfrom models_provider.impl.openai_model_provider.openai_model_provider import OpenAIModelProvider\nfrom models_provider.impl.regolo_model_provider.regolo_model_provider import RegoloModelProvider\nfrom models_provider.impl.siliconCloud_model_provider.siliconCloud_model_provider import SiliconCloudModelProvider\nfrom models_provider.impl.tencent_cloud_model_provider.tencent_cloud_model_provider import TencentCloudModelProvider\nfrom models_provider.impl.tencent_model_provider.tencent_model_provider import TencentModelProvider\nfrom models_provider.impl.vllm_model_provider.vllm_model_provider import VllmModelProvider\nfrom models_provider.impl.volcanic_engine_model_provider.volcanic_engine_model_provider import \\\n    VolcanicEngineModelProvider\nfrom models_provider.impl.wenxin_model_provider.wenxin_model_provider import WenxinModelProvider\nfrom models_provider.impl.xf_model_provider.xf_model_provider import XunFeiModelProvider\nfrom models_provider.impl.xinference_model_provider.xinference_model_provider import XinferenceModelProvider\nfrom models_provider.impl.zhipu_model_provider.zhipu_model_provider import ZhiPuModelProvider\n\n\nclass ModelProvideConstants(Enum):\n    model_azure_provider = AzureModelProvider()\n    model_wenxin_provider = WenxinModelProvider()\n    model_ollama_provider = OllamaModelProvider()\n    model_openai_provider = OpenAIModelProvider()\n    model_docker_ai_provider = DockerModelProvider()\n    model_kimi_provider = KimiModelProvider()\n    model_zhipu_provider = ZhiPuModelProvider()\n    model_xf_provider = XunFeiModelProvider()\n    model_deepseek_provider = DeepSeekModelProvider()\n    model_gemini_provider = GeminiModelProvider()\n    model_volcanic_engine_provider = VolcanicEngineModelProvider()\n    model_tencent_provider = TencentModelProvider()\n    model_tencent_cloud_provider = TencentCloudModelProvider()\n    model_aws_bedrock_provider = BedrockModelProvider()\n    model_local_provider = LocalModelProvider()\n    model_xinference_provider = XinferenceModelProvider()\n    model_vllm_provider = VllmModelProvider()\n    aliyun_bai_lian_model_provider = AliyunBaiLianModelProvider()\n    model_anthropic_provider = AnthropicModelProvider()\n    model_siliconCloud_provider = SiliconCloudModelProvider()\n    model_regolo_provider = RegoloModelProvider()\n"
  },
  {
    "path": "apps/models_provider/impl/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py\n    @date：2024/9/9 17:42\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/aliyun_bai_lian_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： aliyun_bai_lian_model_provider.py\n    @date：2024/9/9 17:43\n    @desc:\n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import ModelProvideInfo, ModelTypeConst, ModelInfo, IModelProvider, \\\n    ModelInfoManage\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.stt.asr_stt import AliyunBaiLianAsrSTTModelCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.embedding import \\\n    AliyunBaiLianEmbeddingCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.image import QwenVLModelCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.itv import ImageToVideoModelCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.llm import BaiLianLLMModelCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.stt.omni_stt import AliyunBaiLianOmiSTTModelCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.reranker import \\\n    AliyunBaiLianRerankerCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.stt import AliyunBaiLianSTTModelCredential, \\\n    AliyunBaiLianDefaultSTTModelCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.tti import QwenTextToImageModelCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.tts import AliyunBaiLianTTSModelCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.credential.ttv import TextToVideoModelCredential\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.stt.asr_stt import AliyunBaiLianAsrSpeechToText\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.embedding import AliyunBaiLianEmbedding\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.image import QwenVLChatModel\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.llm import BaiLianChatModel\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.stt.omni_stt import AliyunBaiLianOmiSpeechToText\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.reranker import AliyunBaiLianReranker\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.stt import AliyunBaiLianSpeechToText, \\\n    AliyunBaiLianDefaultSpeechToText\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.tti import QwenTextToImageModel\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.tts import AliyunBaiLianTextToSpeech\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _, gettext\n\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.ttv import GenerationVideoModel\n\naliyun_bai_lian_model_credential = AliyunBaiLianRerankerCredential()\naliyun_bai_lian_tts_model_credential = AliyunBaiLianTTSModelCredential()\naliyun_bai_lian_stt_model_credential = AliyunBaiLianSTTModelCredential()\naliyun_bai_lian_omi_stt_model_credential = AliyunBaiLianOmiSTTModelCredential()\naliyun_bai_lian_asr_stt_model_credential = AliyunBaiLianAsrSTTModelCredential()\naliyun_bai_lian_default_stt_model_credential = AliyunBaiLianDefaultSTTModelCredential()\naliyun_bai_lian_embedding_model_credential = AliyunBaiLianEmbeddingCredential()\naliyun_bai_lian_llm_model_credential = BaiLianLLMModelCredential()\nqwenvl_model_credential = QwenVLModelCredential()\nqwentti_model_credential = QwenTextToImageModelCredential()\naliyun_bai_lian_ttv_model_credential = TextToVideoModelCredential()\naliyun_bai_lian_itv_model_credential = ImageToVideoModelCredential()\n\nmodel_info_list = [ModelInfo('gte-rerank-v2',\n                             _('With the GTE-Rerank text sorting series model developed by Alibaba Tongyi Lab, developers can integrate high-quality text retrieval and sorting through the LlamaIndex framework.'),\n                             ModelTypeConst.RERANKER, aliyun_bai_lian_model_credential, AliyunBaiLianReranker),\n                   ModelInfo('paraformer-realtime-v2',\n                             _('Chinese (including various dialects such as Cantonese), English, Japanese, and Korean support free switching between multiple languages.'),\n                             ModelTypeConst.STT, aliyun_bai_lian_stt_model_credential, AliyunBaiLianSpeechToText),\n                   ModelInfo('cosyvoice-v1',\n                             _('CosyVoice is based on a new generation of large generative speech models, which can predict emotions, intonation, rhythm, etc. based on context, and has better anthropomorphic effects.'),\n                             ModelTypeConst.TTS, aliyun_bai_lian_tts_model_credential, AliyunBaiLianTextToSpeech),\n                   ModelInfo('text-embedding-v1',\n                             _(\"Universal text vector is Tongyi Lab's multi-language text unified vector model based on the LLM base. It provides high-level vector services for multiple mainstream languages around the world and helps developers quickly convert text data into high-quality vector data.\"),\n                             ModelTypeConst.EMBEDDING, aliyun_bai_lian_embedding_model_credential,\n                             AliyunBaiLianEmbedding),\n                   ModelInfo('qwen3-0.6b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n                   ModelInfo('qwen3-1.7b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n                   ModelInfo('qwen3-4b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n                   ModelInfo('qwen3-8b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n                   ModelInfo('qwen3-14b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n                   ModelInfo('qwen3-32b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n                   ModelInfo('qwen3-30b-a3b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n                   ModelInfo('qwen3-235b-a22b', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n\n                   ModelInfo('qwen-turbo', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n                   ModelInfo('qwen-plus', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n                   ModelInfo('qwen-max', '', ModelTypeConst.LLM, aliyun_bai_lian_llm_model_credential,\n                             BaiLianChatModel),\n                   ModelInfo('qwen-omni-turbo',\n                             _('The Qwen Omni series model supports inputting multiple modalities of data, including video, audio, images, and text, and outputting audio and text.'),\n                             ModelTypeConst.STT, aliyun_bai_lian_omi_stt_model_credential,\n                             AliyunBaiLianOmiSpeechToText),\n                   ModelInfo('qwen2.5-omni-7b',\n                             _('The Qwen Omni series model supports inputting multiple modalities of data, including video, audio, images, and text, and outputting audio and text.'),\n                             ModelTypeConst.STT, aliyun_bai_lian_omi_stt_model_credential,\n                             AliyunBaiLianOmiSpeechToText),\n                   ModelInfo('qwen-audio-asr',\n                             _('The Qwen Audio based end-to-end speech recognition model supports audio recognition within 3 minutes. At present, it mainly supports Chinese and English recognition.'),\n                             ModelTypeConst.STT, aliyun_bai_lian_asr_stt_model_credential,\n                             AliyunBaiLianAsrSpeechToText),\n                   ]\n\nmodule_info_vl_list = [\n    ModelInfo('qwen-vl-max', '', ModelTypeConst.IMAGE, qwenvl_model_credential, QwenVLChatModel),\n    ModelInfo('qwen-vl-max-0809', '', ModelTypeConst.IMAGE, qwenvl_model_credential, QwenVLChatModel),\n    ModelInfo('qwen-vl-plus-0809', '', ModelTypeConst.IMAGE, qwenvl_model_credential, QwenVLChatModel),\n]\nmodule_info_tti_list = [\n    ModelInfo('wanx-v1',\n              _('Tongyi Wanxiang - a large image model for text generation, supports bilingual input in Chinese and English, and supports the input of reference pictures for reference content or reference style migration. Key styles include but are not limited to watercolor, oil painting, Chinese painting, sketch, flat illustration, two-dimensional, and 3D. Cartoon.'),\n              ModelTypeConst.TTI, qwentti_model_credential, QwenTextToImageModel),\n]\nmodel_info_ttv_list = [\n    ModelInfo('wan2.2-t2v-plus', '', ModelTypeConst.TTV, aliyun_bai_lian_ttv_model_credential,\n              GenerationVideoModel),\n    ModelInfo('wanx2.1-t2v-turbo', '', ModelTypeConst.TTV, aliyun_bai_lian_ttv_model_credential,\n              GenerationVideoModel),\n    ModelInfo('wanx2.1-t2v-plus', '', ModelTypeConst.TTV, aliyun_bai_lian_ttv_model_credential,\n              GenerationVideoModel),\n]\nmodule_info_itv_list = [\n    ModelInfo('wan2.2-i2v-flash', '', ModelTypeConst.ITV, aliyun_bai_lian_itv_model_credential,\n              GenerationVideoModel),\n    ModelInfo('wan2.2-i2v-plus', '', ModelTypeConst.ITV, aliyun_bai_lian_itv_model_credential,\n              GenerationVideoModel),\n    ModelInfo('wanx2.1-i2v-plus', '', ModelTypeConst.ITV, aliyun_bai_lian_itv_model_credential,\n              GenerationVideoModel),\n    ModelInfo('wanx2.1-i2v-turbo', '', ModelTypeConst.ITV, aliyun_bai_lian_itv_model_credential,\n              GenerationVideoModel),\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_model_info_list(module_info_vl_list)\n    .append_default_model_info(module_info_vl_list[0])\n    .append_model_info_list(module_info_tti_list)\n    .append_default_model_info(module_info_tti_list[0])\n    .append_default_model_info(model_info_list[1])\n    .append_default_model_info(model_info_list[2])\n    .append_default_model_info(ModelInfo('default',\n                             _('default'),\n                             ModelTypeConst.STT, aliyun_bai_lian_default_stt_model_credential,\n                             AliyunBaiLianDefaultSpeechToText))\n    .append_default_model_info(model_info_list[3])\n    .append_default_model_info(model_info_list[4])\n    .append_default_model_info(model_info_list[0])\n    .append_model_info_list(model_info_ttv_list)\n    .append_default_model_info(model_info_ttv_list[0])\n    .append_model_info_list(module_info_itv_list)\n    .append_default_model_info(module_info_itv_list[0])\n    .build()\n)\n\n\nclass AliyunBaiLianModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='aliyun_bai_lian_model_provider', name=gettext('Alibaba Cloud Bailian'),\n                                icon=get_file_content(\n                                    os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl',\n                                                 'aliyun_bai_lian_model_provider',\n                                                 'icon',\n                                                 'aliyun_bai_lian_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/10/16 17:01\n    @desc:\n\"\"\"\nfrom typing import Dict, Any\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.embedding import AliyunBaiLianEmbedding\nfrom common.utils.logger import maxkb_logger\n\n\nclass BaiLianEmbeddingModelParams(BaseForm):\n    dimensions = forms.SingleSelect(\n        TooltipLabel(\n            _('Dimensions'),\n            _('')\n        ),\n        required=True,\n        default_value=1024,\n        value_field='value',\n        text_field='label',\n        option_list=[\n            {'label': '1024', 'value': '1024'},\n            {'label': '768', 'value': '768'},\n            {'label': '512', 'value': '512'},\n        ]\n    )\n\n\nclass AliyunBaiLianEmbeddingCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(\n            self,\n            model_type: str,\n            model_name: str,\n            model_credential: Dict[str, Any],\n            model_params: Dict[str, Any],\n            provider: Any,\n            raise_exception: bool = False\n    ) -> bool:\n        \"\"\"\n        验证模型凭据是否有效\n        \"\"\"\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                f\"{model_type} Model type is not supported\"\n            )\n        required_keys = ['dashscope_api_key', 'api_base']\n        missing_keys = [key for key in required_keys if key not in model_credential]\n        if missing_keys:\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    f\"{', '.join(missing_keys)} is required\"\n                )\n            return False\n\n        try:\n            model: AliyunBaiLianEmbedding = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_(\"Hello\"))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    f\"Verification failed, please check whether the parameters are correct: {e}\"\n                )\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"\n        加密敏感信息\n        \"\"\"\n        api_key = model.get('dashscope_api_key', '')\n        return {**model, 'dashscope_api_key': super().encryption(api_key)}\n\n    def get_model_params_setting_form(self, model_name):\n        return BaiLianEmbeddingModelParams()\n\n    api_base = forms.TextInputField(_('API URL'), required=True,\n                                    default_value='https://dashscope.aliyuncs.com/compatible-mode/v1')\n    dashscope_api_key = forms.PasswordInputField('API Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/image.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 18:41\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass QwenModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=1.0,\n                                    _min=0.1,\n                                    _max=1.9,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass QwenVLModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(\n            self,\n            model_type: str,\n            model_name: str,\n            model_credential: Dict[str, object],\n            model_params: dict,\n            provider,\n            raise_exception: bool = False\n    ) -> bool:\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                gettext('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n        required_keys = ['api_key', 'api_base']\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        gettext('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth(model_credential.get('api_key'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    gettext('Verification failed, please check whether the parameters are correct: {error}').format(\n                        error=str(e)\n                    )\n                )\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField(_('API URL'), required=True, default_value='https://dashscope.aliyuncs.com/compatible-mode/v1')\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return QwenModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/itv.py",
    "content": "# coding=utf-8\n\nfrom typing import Dict, Any\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel\nfrom common.forms.switch_field import SwitchField\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass QwenModelParams(BaseForm):\n    \"\"\"\n    Parameters class for the Qwen Image-to-Video model.\n    Defines fields such as Video size, number of Videos, and style.\n    \"\"\"\n    resolution = SingleSelect(\n        TooltipLabel(_('Resolution'), ''),\n        required=True,\n        default_value='480P',\n        option_list=[\n            {'value': '480P', 'label': '480P'},\n            {'value': '720P', 'label': '720P'},\n            {'value': '1080P', 'label': '1080P'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    watermark = SwitchField(\n        TooltipLabel(_('Watermark'), _('Whether to add watermark')),\n        attrs={\"active-value\": \"true\", \"inactive-value\": \"false\"},\n        default_value=False,\n    )\n\n\nclass ImageToVideoModelCredential(BaseForm, BaseModelCredential):\n    \"\"\"\n    Credential class for the Qwen Image-to-Video model.\n    Provides validation and encryption for the model credentials.\n    \"\"\"\n\n    api_key = PasswordInputField('API Key', required=True)\n\n    def is_valid(\n            self,\n            model_type: str,\n            model_name: str,\n            model_credential: Dict[str, Any],\n            model_params: Dict[str, Any],\n            provider,\n            raise_exception: bool = False\n    ) -> bool:\n        \"\"\"\n        Validate the model credentials.\n\n        :param model_type: Type of the model (e.g., 'TEXT_TO_Video').\n        :param model_name: Name of the model.\n        :param model_credential: Dictionary containing the model credentials.\n        :param model_params: Parameters for the model.\n        :param provider: Model provider instance.\n        :param raise_exception: Whether to raise an exception on validation failure.\n        :return: Boolean indicating whether the credentials are valid.\n        \"\"\"\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                gettext('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n\n        required_keys = ['api_key']\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        gettext('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    gettext(\n                        'Verification failed, please check whether the parameters are correct: {error}'\n                    ).format(error=str(e))\n                )\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n        \"\"\"\n        Encrypt sensitive fields in the model dictionary.\n\n        :param model: Dictionary containing model details.\n        :return: Dictionary with encrypted sensitive fields.\n        \"\"\"\n        return {\n            **model,\n            'api_key': super().encryption(model.get('api_key', ''))\n        }\n\n    def get_model_params_setting_form(self, model_name: str):\n        \"\"\"\n        Get the parameter setting form for the specified model.\n\n        :param model_name: Name of the model.\n        :return: Parameter setting form.\n        \"\"\"\n        return QwenModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/llm.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom langchain_core.messages import HumanMessage\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass BaiLianLLMModelParams(BaseForm):\n    temperature = forms.SliderField(\n        TooltipLabel(\n            _('Temperature'),\n            _('Higher values make the output more random, while lower values make it more focused and deterministic')\n        ),\n        required=True,\n        default_value=0.7,\n        _min=0.1,\n        _max=1.0,\n        _step=0.01,\n        precision=2\n    )\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(\n            _('Output the maximum Tokens'),\n            _('Specify the maximum number of tokens that the model can generate.')\n        ),\n        required=True,\n        default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0\n    )\n\n\nclass BaiLianLLMModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField(_('API URL'), required=True)\n    api_key = forms.PasswordInputField(_('API Key'), required=True)\n\n    def is_valid(\n            self,\n            model_type: str,\n            model_name: str,\n            model_credential: Dict[str, object],\n            model_params: dict,\n            provider,\n            raise_exception: bool = False\n    ) -> bool:\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                gettext('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        gettext('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            if model_params.get('stream'):\n                for res in model.stream([HumanMessage(content=gettext('Hello'))]):\n                    pass\n            else:\n                model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    gettext('Verification failed, please check whether the parameters are correct: {error}').format(\n                        error=str(e)\n                    )\n                )\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name: str) -> BaiLianLLMModelParams:\n        return BaiLianLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/reranker.py",
    "content": "# coding=utf-8\n\nfrom typing import Dict, Any\n\nfrom django.utils.translation import gettext as _\nfrom langchain_core.documents import Document\n\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, PasswordInputField\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.aliyun_bai_lian_model_provider.model.reranker import AliyunBaiLianReranker\nfrom common.utils.logger import maxkb_logger\n\nclass AliyunBaiLianRerankerCredential(BaseForm, BaseModelCredential):\n    \"\"\"\n    Credential class for the Aliyun BaiLian Reranker model.\n    Provides validation and encryption for the model credentials.\n    \"\"\"\n\n    dashscope_api_key = PasswordInputField('API Key', required=True)\n\n    def is_valid(\n        self,\n        model_type: str,\n        model_name: str,\n        model_credential: Dict[str, Any],\n        model_params: Dict[str, Any],\n        provider,\n        raise_exception: bool = False\n    ) -> bool:\n        \"\"\"\n        Validate the model credentials.\n\n        :param model_type: Type of the model (e.g., 'RERANKER').\n        :param model_name: Name of the model.\n        :param model_credential: Dictionary containing the model credentials.\n        :param model_params: Parameters for the model.\n        :param provider: Model provider instance.\n        :param raise_exception: Whether to raise an exception on validation failure.\n        :return: Boolean indicating whether the credentials are valid.\n        \"\"\"\n        if model_type != 'RERANKER':\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                _('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n\n        required_keys = ['dashscope_api_key']\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        _('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model: AliyunBaiLianReranker = provider.get_model(model_type, model_name, model_credential)\n            model.compress_documents([Document(page_content=_('Hello'))], _('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    _('Verification failed, please check whether the parameters are correct: {error}').format(error=str(e))\n                )\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n        \"\"\"\n        Encrypt sensitive fields in the model dictionary.\n\n        :param model: Dictionary containing model details.\n        :return: Dictionary with encrypted sensitive fields.\n        \"\"\"\n        return {\n            **model,\n            'dashscope_api_key': super().encryption(model.get('dashscope_api_key', ''))\n        }\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2025/12/5 15:11\n    @desc:\n\"\"\"\nfrom .stt import AliyunBaiLianSTTModelCredential\nfrom .omni_stt import AliyunBaiLianOmiSTTModelCredential\nfrom .default_stt import AliyunBaiLianDefaultSTTModelCredential\nfrom .asr_stt import AliyunBaiLianAsrSTTModelCredential"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt/asr_stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict, Any\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext as _\nfrom common.utils.logger import maxkb_logger\n\n\nclass AliyunBaiLianAsrSTTModelCredential(BaseForm, BaseModelCredential):\n    api_url = forms.TextInputField(_('API URL'), required=True)\n    api_key = forms.PasswordInputField(_('API Key'), required=True)\n\n    def is_valid(self,\n                 model_type: str,\n                 model_name: str,\n                 model_credential: Dict[str, Any],\n                 model_params: Dict[str, Any],\n                 provider,\n                 raise_exception: bool = False\n                 ) -> bool:\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                _('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n\n        required_keys = ['api_key']\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        _('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    _('Verification failed, please check whether the parameters are correct: {error}').format(\n                        error=str(e))\n                )\n            return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n\n        return {\n            **model,\n            'api_key': super().encryption(model.get('api_key', ''))\n        }\n\n    def get_model_params_setting_form(self, model_name):\n\n        pass\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt/default_stt.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： default_stt.py\n    @date：2025/12/5 15:12\n    @desc:\n\"\"\"\nfrom typing import Dict, Any\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom maxkb.settings import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext as _\n\n\n\nclass AliyunBaiLianDefaultSTTModelCredential(BaseForm, BaseModelCredential):\n    type = forms.SingleSelect(_(\"API\"), required=True, text_field='label', default_value='qwen', provider='', method='',\n                       value_field='value', option_list=[\n            {'label': _('Audio file recognition - Tongyi Qwen'),\n             'value': 'qwen'},\n            {'label': _('Qwen-Omni'),\n             'value': 'omni'},\n            {'label': _('Real-time speech recognition - Fun-ASR/Paraformer'),\n             'value': 'other'}\n        ])\n    api_url = forms.TextInputField(_('API URL'), required=True, relation_show_field_dict={'type': ['qwen', 'omni']})\n    api_key = forms.PasswordInputField(_('API Key'), required=True)\n\n    def is_valid(self,\n                 model_type: str,\n                 model_name: str,\n                 model_credential: Dict[str, Any],\n                 model_params: Dict[str, Any],\n                 provider,\n                 raise_exception: bool = False\n                 ) -> bool:\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                _('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n\n        required_keys = ['api_key']\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        _('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    _('Verification failed, please check whether the parameters are correct: {error}').format(\n                        error=str(e))\n                )\n            return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n\n        return {\n            **model,\n            'api_key': super().encryption(model.get('api_key', ''))\n        }\n\n    def get_model_params_setting_form(self, model_name):\n\n        pass"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt/omni_stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict, Any\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, PasswordInputField, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext as _\nfrom common.utils.logger import maxkb_logger\n\nclass AliyunBaiLianOmiSTTModelParams(BaseForm):\n    CueWord = forms.TextInputField(\n        TooltipLabel(_('CueWord'), _('If not passed, the default value is What is this audio saying? Only answer the audio content')),\n        required=True,\n        default_value='这段音频在说什么，只回答音频的内容',\n    )\n\n\nclass AliyunBaiLianOmiSTTModelCredential(BaseForm, BaseModelCredential):\n    api_url = forms.TextInputField(_('API URL'), required=True)\n    api_key = forms.PasswordInputField(_('API Key'), required=True)\n\n    def is_valid(self,\n                 model_type: str,\n                 model_name: str,\n                 model_credential: Dict[str, Any],\n                 model_params: Dict[str, Any],\n                 provider,\n                 raise_exception: bool = False\n                 ) -> bool:\n\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                _('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n\n        required_keys = ['api_key']\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        _('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                ValidCode.valid_error.value,\n                _('Verification failed, please check whether the parameters are correct: {error}').format(error=str(e))\n                )\n            return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n\n        return {\n            **model,\n            'api_key': super().encryption(model.get('api_key', ''))\n        }\n\n\n    def get_model_params_setting_form(self, model_name):\n\n        return AliyunBaiLianOmiSTTModelParams()"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/stt/stt.py",
    "content": "# coding=utf-8\n\nfrom typing import Dict, Any\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, PasswordInputField, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass AliyunBaiLianSTTModelParams(BaseForm):\n    sample_rate = forms.SliderField(\n        TooltipLabel(_('Sample Rate'), _('If not passed, the default value is 16000')),\n        required=True,\n        default_value=16000,\n        _step=4000, _min=0, _max=20000,precision=0\n    )\n\nclass AliyunBaiLianSTTModelCredential(BaseForm, BaseModelCredential):\n    \"\"\"\n    Credential class for the Aliyun BaiLian STT (Speech-to-Text) model.\n    Provides validation and encryption for the model credentials.\n    \"\"\"\n\n    api_key = PasswordInputField(\"API Key\", required=True)\n\n    def is_valid(\n        self,\n        model_type: str,\n        model_name: str,\n        model_credential: Dict[str, Any],\n        model_params: Dict[str, Any],\n        provider,\n        raise_exception: bool = False\n    ) -> bool:\n        \"\"\"\n        Validate the model credentials.\n\n        :param model_type: Type of the model (e.g., 'STT').\n        :param model_name: Name of the model.\n        :param model_credential: Dictionary containing the model credentials.\n        :param model_params: Parameters for the model.\n        :param provider: Model provider instance.\n        :param raise_exception: Whether to raise an exception on validation failure.\n        :return: Boolean indicating whether the credentials are valid.\n        \"\"\"\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                _('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n\n        required_keys = ['api_key']\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        _('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential,**model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    _('Verification failed, please check whether the parameters are correct: {error}').format(error=str(e))\n                )\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n        \"\"\"\n        Encrypt sensitive fields in the model dictionary.\n\n        :param model: Dictionary containing model details.\n        :return: Dictionary with encrypted sensitive fields.\n        \"\"\"\n        return {\n            **model,\n            'api_key': super().encryption(model.get('api_key', ''))\n        }\n\n    def get_model_params_setting_form(self, model_name: str):\n        \"\"\"\n        Get the parameter setting form for the specified model.\n\n        :param model_name: Name of the model.\n        :return: Parameter setting form (not implemented).\n        \"\"\"\n        return AliyunBaiLianSTTModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tti.py",
    "content": "# coding=utf-8\n\nfrom typing import Dict, Any\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\n\nclass QwenModelParams(BaseForm):\n    \"\"\"\n    Parameters class for the Qwen Text-to-Image model.\n    Defines fields such as image size, number of images, and style.\n    \"\"\"\n\n    size = SingleSelect(\n        TooltipLabel(_('Image size'), _('Specify the size of the generated image, such as: 1024x1024')),\n        required=True,\n        default_value='1024*1024',\n        option_list=[\n            {'value': '1024*1024', 'label': '1024*1024'},\n            {'value': '720*1280', 'label': '720*1280'},\n            {'value': '768*1152', 'label': '768*1152'},\n            {'value': '1280*720', 'label': '1280*720'},\n        ],\n        text_field='label',\n        value_field='value',\n        attrs={'allow-create': True, 'filterable': True}\n    )\n\n    n = SliderField(\n        TooltipLabel(_('Number of pictures'), _('Specify the number of generated images')),\n        required=True,\n        default_value=1,\n        _min=1,\n        _max=4,\n        _step=1,\n        precision=0\n    )\n\n    style = SingleSelect(\n        TooltipLabel(_('Style'), _('Specify the style of generated images')),\n        required=True,\n        default_value='<auto>',\n        option_list=[\n            {'value': '<auto>', 'label': _('Default value, the image style is randomly output by the model')},\n            {'value': '<photography>', 'label': _('photography')},\n            {'value': '<portrait>', 'label': _('Portraits')},\n            {'value': '<3d cartoon>', 'label': _('3D cartoon')},\n            {'value': '<anime>', 'label': _('animation')},\n            {'value': '<oil painting>', 'label': _('painting')},\n            {'value': '<watercolor>', 'label': _('watercolor')},\n            {'value': '<sketch>', 'label': _('sketch')},\n            {'value': '<chinese painting>', 'label': _('Chinese painting')},\n            {'value': '<flat illustration>', 'label': _('flat illustration')},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n\nclass QwenTextToImageModelCredential(BaseForm, BaseModelCredential):\n    \"\"\"\n    Credential class for the Qwen Text-to-Image model.\n    Provides validation and encryption for the model credentials.\n    \"\"\"\n    api_base = forms.TextInputField(_('API URL'), required=True,\n                                    default_value='https://dashscope.aliyuncs.com/api/v1')\n    api_key = PasswordInputField('API Key', required=True)\n\n    def is_valid(\n            self,\n            model_type: str,\n            model_name: str,\n            model_credential: Dict[str, Any],\n            model_params: Dict[str, Any],\n            provider,\n            raise_exception: bool = False\n    ) -> bool:\n        \"\"\"\n        Validate the model credentials.\n\n        :param model_type: Type of the model (e.g., 'TEXT_TO_IMAGE').\n        :param model_name: Name of the model.\n        :param model_credential: Dictionary containing the model credentials.\n        :param model_params: Parameters for the model.\n        :param provider: Model provider instance.\n        :param raise_exception: Whether to raise an exception on validation failure.\n        :return: Boolean indicating whether the credentials are valid.\n        \"\"\"\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                gettext('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n\n        required_keys = ['api_key', 'api_base']\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        gettext('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    gettext(\n                        'Verification failed, please check whether the parameters are correct: {error}'\n                    ).format(error=str(e))\n                )\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n        \"\"\"\n        Encrypt sensitive fields in the model dictionary.\n\n        :param model: Dictionary containing model details.\n        :return: Dictionary with encrypted sensitive fields.\n        \"\"\"\n        return {\n            **model,\n            'api_key': super().encryption(model.get('api_key', ''))\n        }\n\n    def get_model_params_setting_form(self, model_name: str):\n        \"\"\"\n        Get the parameter setting form for the specified model.\n\n        :param model_name: Name of the model.\n        :return: Parameter setting form.\n        \"\"\"\n        return QwenModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/tts.py",
    "content": "# coding=utf-8\n\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass AliyunBaiLianTTSModelGeneralParams(BaseForm):\n    \"\"\"\n    Parameters class for the Aliyun BaiLian TTS (Text-to-Speech) model.\n    Defines fields such as voice and speech rate.\n    \"\"\"\n\n    voice = SingleSelect(\n        TooltipLabel(_('Timbre'), _('Chinese sounds can support mixed scenes of Chinese and English')),\n        required=True,\n        default_value='longxiaochun',\n        text_field='value',\n        value_field='value',\n        option_list=[\n            {'label': _('Long Xiaochun'), 'value': 'longxiaochun'},\n            {'label': _('Long Xiaoxia'), 'value': 'longxiaoxia'},\n            {'label': _('Long Xiaochen'), 'value': 'longxiaocheng'},\n            {'label': _('Long Xiaobai'), 'value': 'longxiaobai'},\n            {'label': _('Long Laotie'), 'value': 'longlaotie'},\n            {'label': _('Long Shu'), 'value': 'longshu'},\n            {'label': _('Long Shuo'), 'value': 'longshuo'},\n            {'label': _('Long Jing'), 'value': 'longjing'},\n            {'label': _('Long Miao'), 'value': 'longmiao'},\n            {'label': _('Long Yue'), 'value': 'longyue'},\n            {'label': _('Long Yuan'), 'value': 'longyuan'},\n            {'label': _('Long Fei'), 'value': 'longfei'},\n            {'label': _('Long Jielidou'), 'value': 'longjielidou'},\n            {'label': _('Long Tong'), 'value': 'longtong'},\n            {'label': _('Long Xiang'), 'value': 'longxiang'},\n            {'label': 'Stella', 'value': 'loongstella'},\n            {'label': 'Bella', 'value': 'loongbella'},\n            {'label': 'longxiaochun_v2', 'value': 'longxiaochun_v2'},\n            {'label': 'longyingmu_v3', 'value': 'longyingmu_v3'},\n        ]\n    )\n\n    speech_rate = SliderField(\n        TooltipLabel(_('Speaking speed'), _('[0.5, 2], the default is 1, usually one decimal place is enough')),\n        required=True,\n        default_value=1,\n        _min=0.5,\n        _max=2,\n        _step=0.1,\n        precision=1\n    )\n\n\nclass AliyunBaiLianTTSModelCredential(BaseForm, BaseModelCredential):\n    \"\"\"\n    Credential class for the Aliyun BaiLian TTS (Text-to-Speech) model.\n    Provides validation and encryption for the model credentials.\n    \"\"\"\n\n    api_key = PasswordInputField(\"API Key\", required=True)\n\n    def is_valid(\n        self,\n        model_type: str,\n        model_name: str,\n        model_credential: Dict[str, object],\n        model_params,\n        provider,\n        raise_exception: bool = False\n    ) -> bool:\n        \"\"\"\n        Validate the model credentials.\n\n        :param model_type: Type of the model (e.g., 'TTS').\n        :param model_name: Name of the model.\n        :param model_credential: Dictionary containing the model credentials.\n        :param model_params: Parameters for the model.\n        :param provider: Model provider instance.\n        :param raise_exception: Whether to raise an exception on validation failure.\n        :return: Boolean indicating whether the credentials are valid.\n        \"\"\"\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                gettext('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n\n        required_keys = ['api_key']\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        gettext('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    gettext(\n                        'Verification failed, please check whether the parameters are correct: {error}'\n                    ).format(error=str(e))\n                )\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n        \"\"\"\n        Encrypt sensitive fields in the model dictionary.\n\n        :param model: Dictionary containing model details.\n        :return: Dictionary with encrypted sensitive fields.\n        \"\"\"\n        return {\n            **model,\n            'api_key': super().encryption(model.get('api_key', ''))\n        }\n\n    def get_model_params_setting_form(self, model_name: str):\n        \"\"\"\n        Get the parameter setting form for the specified model.\n\n        :param model_name: Name of the model.\n        :return: Parameter setting form.\n        \"\"\"\n        return AliyunBaiLianTTSModelGeneralParams()\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/credential/ttv.py",
    "content": "# coding=utf-8\n\nfrom typing import Dict, Any\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, PasswordInputField, SingleSelect, SliderField, TooltipLabel\nfrom common.forms.switch_field import SwitchField\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass QwenModelParams(BaseForm):\n    \"\"\"\n    Parameters class for the Qwen Text-to-Video model.\n    Defines fields such as Video size, number of Videos, and style.\n    \"\"\"\n\n    size = SingleSelect(\n        TooltipLabel(_('Video size'), _('Specify the size of the generated Video, such as: 1024x1024')),\n        required=True,\n        default_value='1280*720',\n        option_list=[\n            {'value': '832*480', 'label': '832*480'},\n            {'value': '480*832', 'label': '480*832'},\n            {'value': '1280*720', 'label': '1280*720'},\n            {'value': '720*1280', 'label': '720*1280'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    watermark = SwitchField(\n        TooltipLabel(_('Watermark'), _('Whether to add watermark')),\n        attrs={\"active-value\": \"true\", \"inactive-value\": \"false\"},\n        default_value=False,\n    )\n\n\nclass TextToVideoModelCredential(BaseForm, BaseModelCredential):\n    \"\"\"\n    Credential class for the Qwen Text-to-Video model.\n    Provides validation and encryption for the model credentials.\n    \"\"\"\n\n    api_key = PasswordInputField('API Key', required=True)\n\n    def is_valid(\n            self,\n            model_type: str,\n            model_name: str,\n            model_credential: Dict[str, Any],\n            model_params: Dict[str, Any],\n            provider,\n            raise_exception: bool = False\n    ) -> bool:\n        \"\"\"\n        Validate the model credentials.\n\n        :param model_type: Type of the model (e.g., 'TEXT_TO_Video').\n        :param model_name: Name of the model.\n        :param model_credential: Dictionary containing the model credentials.\n        :param model_params: Parameters for the model.\n        :param provider: Model provider instance.\n        :param raise_exception: Whether to raise an exception on validation failure.\n        :return: Boolean indicating whether the credentials are valid.\n        \"\"\"\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            raise AppApiException(\n                ValidCode.valid_error.value,\n                gettext('{model_type} Model type is not supported').format(model_type=model_type)\n            )\n\n        required_keys = ['api_key']\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(\n                        ValidCode.valid_error.value,\n                        gettext('{key} is required').format(key=key)\n                    )\n                return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    gettext(\n                        'Verification failed, please check whether the parameters are correct: {error}'\n                    ).format(error=str(e))\n                )\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n        \"\"\"\n        Encrypt sensitive fields in the model dictionary.\n\n        :param model: Dictionary containing model details.\n        :return: Dictionary with encrypted sensitive fields.\n        \"\"\"\n        return {\n            **model,\n            'api_key': super().encryption(model.get('api_key', ''))\n        }\n\n    def get_model_params_setting_form(self, model_name: str):\n        \"\"\"\n        Get the parameter setting form for the specified model.\n\n        :param model_name: Name of the model.\n        :return: Parameter setting form.\n        \"\"\"\n        return QwenModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/10/16 16:34\n    @desc:\n\"\"\"\nfrom typing import Dict, List\n\nfrom openai import OpenAI\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass AliyunBaiLianEmbedding(MaxKBBaseModel):\n    model_name: str\n    optional_params: dict\n\n    def __init__(self, api_key, model_name: str, api_base: str, optional_params: dict):\n        self.client = OpenAI(api_key=api_key, base_url=api_base).embeddings\n        self.model_name = model_name\n        self.optional_params = optional_params\n\n    def is_cache_model(self):\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return AliyunBaiLianEmbedding(\n            api_key=model_credential.get('dashscope_api_key'),\n            model_name=model_name,\n            api_base=model_credential.get('api_base') or 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n            optional_params=optional_params\n        )\n\n    def embed_query(self, text: str):\n        res = self.embed_documents([text])\n        return res[0]\n\n    def embed_documents(\n            self, texts: List[str], chunk_size: int | None = None\n    ) -> List[List[float]]:\n        if len(self.optional_params) > 0:\n            res = self.client.create(\n                input=texts, model=self.model_name, encoding_format=\"float\",\n                **self.optional_params\n            )\n        else:\n            res = self.client.create(input=texts, model=self.model_name, encoding_format=\"float\")\n        return [e.embedding for e in res.data]\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/image.py",
    "content": "# coding=utf-8\nimport json\nimport time\nfrom typing import Dict, Optional, Any, Iterator\n\nimport requests\nfrom langchain_core.language_models import LanguageModelInput\nfrom langchain_core.messages import BaseMessageChunk, AIMessage\nfrom langchain_core.runnables import RunnableConfig\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass QwenVLChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        chat_tong_yi = QwenVLChatModel(\n            model_name=model_name,\n            openai_api_key=model_credential.get('api_key'),\n            openai_api_base=model_credential.get('api_base') or 'https://dashscope.aliyuncs.com/compatible-mode/v1',\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n        return chat_tong_yi\n\n    def check_auth(self, api_key):\n        return True\n\n    def get_upload_policy(self, api_key, model_name):\n        \"\"\"获取文件上传凭证\"\"\"\n        url = \"https://dashscope.aliyuncs.com/api/v1/uploads\"\n        if 'dashscope-us' in self.openai_api_base:\n            url = \"https://dashscope-us.aliyuncs.com/api/v1/uploads\"\n        elif 'dashscope-intl' in self.openai_api_base:\n            url = \"https://dashscope-intl.aliyuncs.com/api/v1/uploads\"\n        headers = {\n            \"Authorization\": f\"Bearer {api_key}\",\n            \"Content-Type\": \"application/json\"\n        }\n        params = {\n            \"action\": \"getPolicy\",\n            \"model\": model_name\n        }\n\n        response = requests.get(url, headers=headers, params=params)\n        if response.status_code != 200:\n            raise Exception(f\"Failed to get upload policy: {response.text}\")\n\n        return response.json()['data']\n\n    def upload_file_to_oss(self, policy_data, file_stream, file_name):\n        \"\"\"将文件流上传到临时存储OSS\"\"\"\n        # 构建OSS上传的目标路径\n        key = f\"{policy_data['upload_dir']}/{file_name}\"\n\n        # 构建上传数据\n        files = {\n            'OSSAccessKeyId': (None, policy_data['oss_access_key_id']),\n            'Signature': (None, policy_data['signature']),\n            'policy': (None, policy_data['policy']),\n            'x-oss-object-acl': (None, policy_data['x_oss_object_acl']),\n            'x-oss-forbid-overwrite': (None, policy_data['x_oss_forbid_overwrite']),\n            'key': (None, key),\n            'success_action_status': (None, '200'),\n            'file': (file_name, file_stream)\n        }\n\n        # 执行上传请求\n        response = requests.post(policy_data['upload_host'], files=files)\n        if response.status_code != 200:\n            raise Exception(f\"Failed to upload file: {response.text}\")\n\n        return f\"oss://{key}\"\n\n    def upload_file_and_get_url(self, file_stream, file_name):\n        max_retries = 3\n\n        retry_delay = 1  # 初始重试延迟（秒）\n\n        for attempt in range(max_retries):\n            try:\n                # 1. 获取上传凭证，上传凭证接口有限流，超出限流将导致请求失败\n                policy_data = self.get_upload_policy(self.openai_api_key.get_secret_value(), self.model_name)\n                # 2. 上传文件到OSS\n                oss_url = self.upload_file_to_oss(policy_data, file_stream, file_name)\n                return oss_url\n            except Exception as e:\n                if attempt < max_retries - 1:\n                    # 指数退避策略\n                    time.sleep(retry_delay * (2 ** attempt))\n                    continue\n                else:\n                    raise Exception(f\"文件上传失败，已重试{max_retries}次: {str(e)}\")\n\n    def stream(\n            self,\n            input: LanguageModelInput,\n            config: Optional[RunnableConfig] = None,\n            *,\n            stop: Optional[list[str]] = None,\n            **kwargs: Any,\n    ) -> Iterator[BaseMessageChunk]:\n        url = f\"{self.openai_api_base}/chat/completions\"\n\n        headers = {\n            \"Authorization\": f\"Bearer {self.openai_api_key.get_secret_value()}\",\n            \"Content-Type\": \"application/json\",\n            \"X-DashScope-OssResourceResolve\": \"enable\"\n        }\n        # 遍历input 获取所有的content 构造新的消息体\n        messages = []\n        for message in input:\n            if message.type == \"human\":\n                messages.append({\n                    \"role\": \"user\",\n                    \"content\": message.content\n                })\n            elif message.type == \"ai\":\n                messages.append({\n                    \"role\": \"assistant\",\n                    \"content\": message.content\n                })\n            elif message.type == \"system\":\n                messages.append({\n                    \"role\": \"system\",\n                    \"content\": message.content\n                })\n\n        data = {\n            \"model\": self.model_name,\n            \"messages\": messages,\n            **self.extra_body,\n            \"stream\": True,\n        }\n\n        # 增加重试机制\n        max_retries = 3\n        retry_delay = 1\n\n        for attempt in range(max_retries):\n            try:\n                response = requests.post(url, headers=headers, json=data, stream=True, timeout=30)\n                if response.status_code != 200:\n                    raise Exception(f\"Failed to get response: {response.text}\")\n\n                for line in response.iter_lines():\n                    if line:\n                        try:\n                            decoded_line = line.decode('utf-8')\n                            # 检查是否是有效的SSE数据行\n                            if decoded_line.startswith('data: '):\n                                # 提取JSON部分\n                                json_str = decoded_line[6:]  # 移除 'data: ' 前缀\n                                # 检查是否是结束标记\n                                if json_str.strip() == '[DONE]':\n                                    continue\n\n                                # 尝试解析JSON\n                                chunk_data = json.loads(json_str)\n\n                                if 'choices' in chunk_data and chunk_data['choices']:\n                                    delta = chunk_data['choices'][0].get('delta', {})\n                                    content = delta.get('content', '')\n                                    if content:\n                                        yield AIMessage(content=content)\n                        except json.JSONDecodeError:\n                            # 忽略无法解析的行\n                            continue\n                        except Exception as e:\n                            # 处理其他可能的异常\n                            continue\n                break  # 成功执行则退出重试循环\n\n            except (requests.exceptions.ProxyError, requests.exceptions.ConnectionError) as e:\n                if attempt < max_retries - 1:\n                    time.sleep(retry_delay * (2 ** attempt))  # 指数退避\n                    continue\n                else:\n                    raise Exception(f\"网络连接失败，已重试{max_retries}次: {str(e)}\")\n            except Exception as e:\n                if attempt < max_retries - 1:\n                    time.sleep(retry_delay * (2 ** attempt))\n                    continue\n                else:\n                    raise Exception(f\"请求失败，已重试{max_retries}次: {str(e)}\")\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/llm.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\nfrom typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass BaiLianChatModel(MaxKBBaseModel, BaseChatOpenAI):\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        # if 'qwen-omni-turbo' in model_name or 'qwq' in model_name:\n        #     optional_params['streaming'] = True\n        return BaiLianChatModel(\n            model=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            streaming=True,\n            extra_body=optional_params\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/reranker.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： reranker.py.py\n    @date：2024/9/2 16:42\n    @desc:\n\"\"\"\nfrom http import HTTPStatus\nfrom typing import Sequence, Optional, Any, Dict\n\nimport dashscope\nfrom langchain_core.callbacks import Callbacks\nfrom langchain_core.documents import BaseDocumentCompressor, Document\nfrom langchain_core.documents import BaseDocumentCompressor\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass AliyunBaiLianReranker(MaxKBBaseModel, BaseDocumentCompressor):\n    model: Optional[str]\n    api_key: Optional[str]\n\n    top_n: Optional[int] = 3  # 取前 N 个最相关的结果\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return AliyunBaiLianReranker(model=model_name,\n                                     api_key=model_credential.get('dashscope_api_key'),\n                                     top_n=model_kwargs.get('top_n', 3))\n\n    def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \\\n            Sequence[Document]:\n        if not documents:\n            return []\n\n        texts = [doc.page_content for doc in documents]\n        resp = dashscope.TextReRank.call(\n            model=self.model,\n            query=query,\n            documents=texts,\n            top_n=self.top_n,\n            api_key=self.api_key,\n            return_documents=True\n        )\n        if resp.status_code == HTTPStatus.OK:\n            return [\n                Document(\n                    page_content=item.get('document', {}).get('text', ''),\n                    metadata={'relevance_score': item.get('relevance_score')}\n                )\n                for item in resp.output.get('results', [])\n            ]\n        else:\n            return []\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/stt/__init__.py",
    "content": "#coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2025/12/5 15:39\n    @desc:\n\"\"\"\n\nfrom .asr_stt import AliyunBaiLianAsrSpeechToText\nfrom .default_stt import AliyunBaiLianDefaultSpeechToText\nfrom .stt import AliyunBaiLianSpeechToText\nfrom .omni_stt import AliyunBaiLianOmiSpeechToText"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/stt/asr_stt.py",
    "content": "import base64\nimport os.path\nimport traceback\nfrom typing import Dict\n\nimport dashscope\n\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\nclass AliyunBaiLianAsrSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    api_key: str\n    api_url: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n        self.api_url = kwargs.get('api_url')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return AliyunBaiLianAsrSpeechToText(\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            api_url=model_credential.get('api_url'),\n            params=model_kwargs,\n            **model_kwargs\n        )\n\n    def check_auth(self):\n        cwd = os.path.dirname(os.path.abspath(__file__))\n        with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as audio_file:\n            self.speech_to_text(audio_file)\n\n    def speech_to_text(self, audio_file):\n\n        try:\n\n            base64_audio = base64.b64encode(audio_file.read()).decode(\"utf-8\")\n\n            messages = [\n                {\n                    \"role\": \"user\",\n                    \"content\": [\n                        {\"audio\": f\"data:audio/mp3;base64,{base64_audio}\"},\n                    ]\n                }\n            ]\n            response = dashscope.MultiModalConversation.call(\n                api_key=self.api_key,\n                model=self.model,\n                messages=messages,\n                result_format=\"message\",\n                **self.params\n            )\n            if response.status_code == 200:\n                text = response[\"output\"][\"choices\"][0][\"message\"].content[0][\"text\"]\n                return text\n            else:\n                raise Exception('Error: ', response.message)\n\n        except Exception as err:\n            maxkb_logger.error(f\":Error: {str(err)}: {traceback.format_exc()}\")\n            raise err\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/stt/default_stt.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： default_stt.py\n    @date：2025/12/5 15:40\n    @desc:\n\"\"\"\nimport os\nfrom typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\nclass AliyunBaiLianDefaultSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    def check_auth(self):\n        pass\n\n    def speech_to_text(self, audio_file):\n        pass\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        from models_provider.impl.aliyun_bai_lian_model_provider.model.stt import AliyunBaiLianOmiSpeechToText, \\\n            AliyunBaiLianSpeechToText, AliyunBaiLianAsrSpeechToText\n        stt_type=model_credential.get('type')\n        if stt_type == 'qwen':\n            return AliyunBaiLianAsrSpeechToText(\n                model=model_name,\n                api_key=model_credential.get('api_key'),\n                api_url=model_credential.get('api_url'),\n                params=model_kwargs,\n                **model_kwargs\n            )\n        elif stt_type == 'omni':\n            return AliyunBaiLianOmiSpeechToText(\n                model=model_name,\n                api_key=model_credential.get('api_key'),\n                api_url=model_credential.get('api_url'),\n                params=model_kwargs,\n                **model_kwargs\n            )\n        else:\n            return AliyunBaiLianSpeechToText(\n                model=model_name,\n                api_key=model_credential.get('api_key'),\n                params=model_kwargs,\n                **model_kwargs,\n            )\n\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/stt/omni_stt.py",
    "content": "import base64\nimport os\nimport traceback\nfrom typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\nclass AliyunBaiLianOmiSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    api_key: str\n    api_url: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n        self.api_url = kwargs.get('api_url')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return AliyunBaiLianOmiSpeechToText(\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            api_url=model_credential.get('api_url') ,\n            params= model_kwargs,\n            **model_kwargs\n        )\n\n\n    def check_auth(self):\n        cwd = os.path.dirname(os.path.abspath(__file__))\n        with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as audio_file:\n            self.speech_to_text(audio_file)\n\n\n\n    def speech_to_text(self, audio_file):\n        try:\n            client = OpenAI(\n                # 若没有配置环境变量，请用阿里云百炼API Key将下行替换为：api_key=\"sk-xxx\",\n                api_key=self.api_key,\n                base_url=self.api_url,\n            )\n\n            base64_audio = base64.b64encode(audio_file.read()).decode(\"utf-8\")\n\n            completion = client.chat.completions.create(\n                model=self.model,\n                messages=[\n                    {\n                        \"role\": \"user\",\n                        \"content\": [\n                            {\n                                \"type\": \"input_audio\",\n                                \"input_audio\": {\n                                    \"data\": f\"data:;base64,{base64_audio}\",\n                                    \"format\": \"mp3\",\n                                },\n                            },\n                            {\"type\": \"text\", \"text\": self.params.get('CueWord') or '这段音频在说什么'},\n                        ],\n                    },\n                ],\n                # 设置输出数据的模态，当前支持两种：[\"text\",\"audio\"]、[\"text\"]\n                modalities=[\"text\"],\n                # stream 必须设置为 True，否则会报错\n                stream=True,\n                stream_options={\"include_usage\": True},\n                extra_body = {'enable_thinking': False, **self.params},\n            )\n            result = []\n            for chunk in completion:\n                if chunk.choices and hasattr(chunk.choices[0].delta, 'content'):\n                    content = chunk.choices[0].delta.content\n                    result.append(content)\n            return \"\".join(result)\n\n        except Exception as err:\n            maxkb_logger.error(f\":Error: {str(err)}: {traceback.format_exc()}\")\n            raise err\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/stt/stt.py",
    "content": "import os\nimport tempfile\nfrom typing import Dict\n\nimport dashscope\nfrom dashscope.audio.asr import (Recognition)\nfrom pydub import AudioSegment\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\nclass AliyunBaiLianSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {}\n        if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None:\n            optional_params['max_tokens'] = model_kwargs['max_tokens']\n        if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None:\n            optional_params['temperature'] = model_kwargs['temperature']\n        return AliyunBaiLianSpeechToText(\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            params=model_kwargs,\n            **optional_params,\n        )\n\n    def check_auth(self):\n        cwd = os.path.dirname(os.path.abspath(__file__))\n        with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as f:\n            self.speech_to_text(f)\n\n    def speech_to_text(self, audio_file):\n        dashscope.api_key = self.api_key\n        recognition_params = {\n            'model': self.model,\n            'format': 'mp3',\n            'sample_rate': 16000,\n            'callback': None,\n            **self.params\n        }\n        recognition = Recognition(**recognition_params)\n\n\n        with tempfile.NamedTemporaryFile(delete=False) as temp_file:\n            # 将上传的文件保存到临时文件中\n            temp_file.write(audio_file.read())\n            # 获取临时文件的路径\n            temp_file_path = temp_file.name\n\n        try:\n            audio = AudioSegment.from_file(temp_file_path)\n            if audio.channels != 1:\n                audio = audio.set_channels(1)\n            audio = audio.set_frame_rate(16000)\n\n            # 将转换后的音频文件保存到临时文件中\n            audio.export(temp_file_path, format='mp3')\n            # 识别临时文件\n            result = recognition.call(temp_file_path)\n            text = ''\n            if result.status_code == 200:\n                result_sentence = result.get_sentence()\n                if result_sentence is not None:\n                    for sentence in result_sentence:\n                        text += sentence['text']\n                    return text\n            else:\n                raise Exception('Error: ', result.message)\n        finally:\n            # 删除临时文件\n            os.remove(temp_file_path)\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tti.py",
    "content": "# coding=utf-8\nfrom http import HTTPStatus\nfrom typing import Dict\n\nfrom dashscope import ImageSynthesis, MultiModalConversation\nfrom dashscope.aigc.image_generation import ImageGeneration\n\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\n\n\nclass QwenTextToImageModel(MaxKBBaseModel, BaseTextToImage):\n    api_key: str\n    model_name: str\n    params: dict\n    api_base: str\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model_name = kwargs.get('model_name')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'size': '1024*1024', 'n': 1}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        api_base = model_credential.get('api_base')\n        if api_base is None:\n            api_base = 'https://dashscope.aliyuncs.com/api/v1'\n\n        chat_tong_yi = QwenTextToImageModel(\n            model_name=model_name,\n            api_key=model_credential.get('api_key'),\n            api_base=api_base,\n            **optional_params,\n        )\n        return chat_tong_yi\n\n    def check_auth(self):\n        # from openai import OpenAI\n        #\n        # client = OpenAI(\n        #     # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\"\n        #     api_key=self.api_key,\n        #     base_url=self.api_base,\n        # )\n        # client.chat.completions.create(\n        #     # 模型列表：https://help.aliyun.com/zh/model-studio/getting-started/models\n        #     model=\"qwen-max\",\n        #     messages=[\n        #         {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n        #         {\"role\": \"user\", \"content\": gettext('Hello')},\n        #     ]\n        #\n        # )\n        return True\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        import dashscope\n        dashscope.base_http_api_url = self.api_base\n        if self.model_name.startswith(\"wan2.6\") or self.model_name.startswith(\"z\"):\n            from dashscope.api_entities.dashscope_response import Message\n            # 以下为北京地域url，各地域的base_url不同\n            message = Message(\n                role=\"user\",\n                content=[\n                    {\n                        'text': prompt\n                    }\n                ]\n            )\n            rsp = ImageGeneration.call(\n                model=self.model_name,\n                api_key=self.api_key,\n                messages=[message],\n                negative_prompt=negative_prompt,\n                **self.params\n            )\n            file_urls = []\n            if rsp.status_code == HTTPStatus.OK:\n                for result in rsp.output.choices:\n                    file_urls.append(result.message.content[0].get('image'))\n            else:\n                maxkb_logger.error('sync_call Failed, status_code: %s, code: %s, message: %s' %\n                                   (rsp.status_code, rsp.code, rsp.message))\n                raise Exception('sync_call Failed, status_code: %s, code: %s, message: %s' %\n                                (rsp.status_code, rsp.code, rsp.message))\n            return file_urls\n        elif self.model_name.startswith(\"wan\") or self.model_name.startswith(\"qwen-image-plus\"):\n            rsp = ImageSynthesis.call(api_key=self.api_key,\n                                      model=self.model_name,\n                                      prompt=prompt,\n                                      negative_prompt=negative_prompt,\n                                      **self.params)\n            file_urls = []\n            if rsp.status_code == HTTPStatus.OK:\n                for result in rsp.output.results:\n                    file_urls.append(result.url)\n            else:\n                maxkb_logger.error('sync_call Failed, status_code: %s, code: %s, message: %s' %\n                                   (rsp.status_code, rsp.code, rsp.message))\n                raise Exception('sync_call Failed, status_code: %s, code: %s, message: %s' %\n                                (rsp.status_code, rsp.code, rsp.message))\n            return file_urls\n        elif self.model_name.startswith(\"qwen\"):\n            messages = [\n                {\n                    \"role\": \"user\",\n                    \"content\": [\n                        {\n                            \"type\": \"text\",\n                            \"text\": prompt\n                        }\n                    ]\n                }\n            ]\n            rsp = MultiModalConversation.call(\n                api_key=self.api_key,\n                model=self.model_name,\n                messages=messages,\n                result_format='message',\n                stream=False,\n                negative_prompt=negative_prompt,\n                **self.params\n            )\n            file_urls = []\n            if rsp.status_code == HTTPStatus.OK:\n                for result in rsp.output.choices:\n                    file_urls.append(result.message.content[0].get('image'))\n            else:\n                maxkb_logger.error('sync_call Failed, status_code: %s, code: %s, message: %s' %\n                                   (rsp.status_code, rsp.code, rsp.message))\n                raise Exception('sync_call Failed, status_code: %s, code: %s, message: %s' %\n                                (rsp.status_code, rsp.code, rsp.message))\n            return file_urls\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/tts.py",
    "content": "from typing import Dict\n\nimport dashscope\n\nfrom django.utils.translation import gettext as _\n\nfrom common.utils.common import _remove_empty_lines\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tts import BaseTextToSpeech\n\n\nclass AliyunBaiLianTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'voice': 'longxiaochun', 'speech_rate': 1.0}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n\n        return AliyunBaiLianTextToSpeech(\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        self.text_to_speech(_('Hello'))\n\n    def text_to_speech(self, text):\n        dashscope.api_key = self.api_key\n        text = _remove_empty_lines(text)\n        if 'sambert' in self.model:\n            from dashscope.audio.tts import SpeechSynthesizer\n            audio = SpeechSynthesizer.call(model=self.model, text=text, **self.params).get_audio_data()\n        else:\n            from dashscope.audio.tts_v2 import SpeechSynthesizer\n            synthesizer = SpeechSynthesizer(model=self.model, **self.params)\n            audio = synthesizer.call(text)\n        if audio is None:\n            raise Exception('Failed to generate audio')\n        if type(audio) == str:\n            raise Exception(audio)\n        return audio\n\n"
  },
  {
    "path": "apps/models_provider/impl/aliyun_bai_lian_model_provider/model/ttv.py",
    "content": "import time\nfrom http import HTTPStatus\nfrom typing import Dict\n\nimport requests\nfrom dashscope import VideoSynthesis\n\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.base_ttv import BaseGenerationVideo\n\n\nclass GenerationVideoModel(MaxKBBaseModel, BaseGenerationVideo):\n    api_key: str\n    model_name: str\n    params: dict\n    max_retries: int = 3\n    retry_delay: int = 5  # seconds\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.model_name = kwargs.get('model_name')\n        self.params = kwargs.get('params', {})\n        self.max_retries = kwargs.get('max_retries', 3)\n        self.retry_delay = 5\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return GenerationVideoModel(\n            model_name=model_name,\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        return True\n\n    def _safe_call(self, func, **kwargs):\n        \"\"\"带重试的请求封装\"\"\"\n        for attempt in range(self.max_retries):\n            try:\n                rsp = func(**kwargs)\n                return rsp\n            except (requests.exceptions.ProxyError,\n                    requests.exceptions.ConnectionError,\n                    requests.exceptions.Timeout) as e:\n                maxkb_logger.error(f\"⚠️ 网络错误: {e}，正在重试 {attempt + 1}/{self.max_retries}...\")\n                time.sleep(self.retry_delay)\n        raise RuntimeError(\"多次重试后仍无法连接到 DashScope API，请检查代理或网络配置\")\n\n    # --- 通用异步生成函数 ---\n    def generate_video(self, prompt, negative_prompt=None, first_frame_url=None, last_frame_url=None, **kwargs):\n        \"\"\"\n            prompt: 文本描述\n            negative_prompt: 反向文本描述\n            first_frame_url: 起始关键帧图片 URL (KF2V 必填)\n            last_frame_url: 结束关键帧图片 URL (KF2V 必填)\n            如果没有提供last_frame_url，则表示只提供了first_frame_url，生成的是单关键帧视频（KFV） 参数是img_url\n            \"\"\"\n\n        # 构建基础参数\n        params = {\"api_key\": self.api_key, \"prompt\": prompt, \"model\": self.model_name,\n                  \"negative_prompt\": negative_prompt}\n        if first_frame_url and last_frame_url:\n            params['first_frame_url'] = first_frame_url\n            params[\"last_frame_url\"] = last_frame_url\n        elif first_frame_url:\n            params['img_url'] = first_frame_url\n\n        # 合并所有额外参数\n        params.update(self.params)\n\n        # --- 异步提交任务 ---\n        rsp = self._safe_call(VideoSynthesis.async_call, **params)\n        if rsp.status_code != HTTPStatus.OK:\n            maxkb_logger.info(f'提交任务失败，status_code: {rsp.status_code}, code: {rsp.code}, message: {rsp.message}')\n            raise RuntimeError(f'提交任务失败，status_code: {rsp.status_code}, code: {rsp.code}, message: {rsp.message}')\n\n        maxkb_logger.info(\"task_id:\", rsp.output.task_id)\n\n        # --- 查询任务状态 ---\n        status = self._safe_call(VideoSynthesis.fetch, task=rsp, api_key=self.api_key)\n        if status.status_code == HTTPStatus.OK:\n            maxkb_logger.info(\"当前任务状态:\", status.output.task_status)\n        else:\n            maxkb_logger.error(\n                f'获取任务状态失败，status_code: {status.status_code}, code: {status.code}, message: {status.message}')\n            raise RuntimeError(\n                f'获取任务状态失败，status_code: {status.status_code}, code: {status.code}, message: {status.message}')\n\n        # --- 等待任务完成 ---\n        rsp = self._safe_call(VideoSynthesis.wait, task=rsp, api_key=self.api_key)\n        if rsp.status_code == HTTPStatus.OK:\n            maxkb_logger.info(\"视频生成完成！视频 URL:\", rsp.output.video_url)\n            if rsp.output.task_status == \"SUCCEEDED\":\n                maxkb_logger.info(\"视频生成完成！视频 URL:\", rsp.output.video_url)\n                return rsp.output.video_url\n            else:\n                maxkb_logger.error(\"视频生成失败！\")\n                raise RuntimeError(f'生成失败, message: {rsp.output.message}')\n        else:\n            maxkb_logger.error(f'生成失败，status_code: {rsp.status_code}, code: {rsp.code}, message: {rsp.message}')\n            raise RuntimeError(f'生成失败，status_code: {rsp.status_code}, code: {rsp.code}, message: {rsp.message}')\n"
  },
  {
    "path": "apps/models_provider/impl/anthropic_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/3/28 16:25\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/anthropic_model_provider/anthropic_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： openai_model_provider.py\n    @date：2024/3/28 16:26\n    @desc:\n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \\\n    ModelTypeConst, ModelInfoManage\nfrom models_provider.impl.anthropic_model_provider.credential.image import AnthropicImageModelCredential\nfrom models_provider.impl.anthropic_model_provider.credential.llm import AnthropicLLMModelCredential\nfrom models_provider.impl.anthropic_model_provider.model.image import AnthropicImage\nfrom models_provider.impl.anthropic_model_provider.model.llm import AnthropicChatModel\nfrom maxkb.conf import PROJECT_DIR\n\nopenai_llm_model_credential = AnthropicLLMModelCredential()\nopenai_image_model_credential = AnthropicImageModelCredential()\n\nmodel_info_list = [\n    ModelInfo('claude-3-opus-20240229', '', ModelTypeConst.LLM,\n              openai_llm_model_credential, AnthropicChatModel\n              ),\n    ModelInfo('claude-3-sonnet-20240229', '', ModelTypeConst.LLM, openai_llm_model_credential,\n              AnthropicChatModel),\n    ModelInfo('claude-3-haiku-20240307', '', ModelTypeConst.LLM, openai_llm_model_credential,\n              AnthropicChatModel),\n    ModelInfo('claude-3-5-sonnet-20240620', '', ModelTypeConst.LLM, openai_llm_model_credential,\n              AnthropicChatModel),\n    ModelInfo('claude-3-5-haiku-20241022', '', ModelTypeConst.LLM, openai_llm_model_credential,\n              AnthropicChatModel),\n    ModelInfo('claude-3-5-sonnet-20241022', '', ModelTypeConst.LLM, openai_llm_model_credential,\n              AnthropicChatModel),\n]\n\nimage_model_info = [\n    ModelInfo('claude-3-5-sonnet-20241022', '', ModelTypeConst.IMAGE, openai_image_model_credential,\n              AnthropicImage),\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_default_model_info(model_info_list[0])\n    .append_model_info_list(image_model_info)\n    .append_default_model_info(image_model_info[0])\n    .build()\n)\n\n\nclass AnthropicModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_anthropic_provider', name='Anthropic', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'anthropic_model_provider', 'icon',\n                         'anthropic_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/anthropic_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/anthropic_model_provider/credential/image.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass AnthropicImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass AnthropicImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField(_('API URL'), required=True)\n    api_key = forms.PasswordInputField(_('API Key'), required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext(\"Hello\")}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return AnthropicImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/anthropic_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 18:32\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom common.utils.logger import maxkb_logger\n\nclass AnthropicLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass AnthropicLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField(_('API URL'), required=True)\n    api_key = forms.PasswordInputField(_('API Key'), required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return AnthropicLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/anthropic_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/anthropic_model_provider/model/image.py",
    "content": "from typing import Dict\n\nfrom langchain_anthropic import ChatAnthropic\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass AnthropicImage(MaxKBBaseModel, ChatAnthropic):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return AnthropicImage(\n            model=model_name,\n            anthropic_api_url=model_credential.get('api_base'),\n            anthropic_api_key=model_credential.get('api_key'),\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            **optional_params,\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/anthropic_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： llm.py\n    @date：2024/4/18 15:28\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom langchain_anthropic import ChatAnthropic\nfrom langchain_core.messages import BaseMessage, get_buffer_string\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass AnthropicChatModel(MaxKBBaseModel, ChatAnthropic):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        azure_chat_open_ai = AnthropicChatModel(\n            model=model_name,\n            anthropic_api_url=model_credential.get('api_base'),\n            anthropic_api_key=model_credential.get('api_key'),\n            **optional_params,\n            custom_get_token_ids=custom_get_token_ids\n        )\n        return azure_chat_open_ai\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        try:\n            return super().get_num_tokens_from_messages(messages)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n    def get_num_tokens(self, text: str) -> int:\n        try:\n            return super().get_num_tokens(text)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n"
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n"
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/aws_bedrock_model_provider.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\nimport os\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import (\n    IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, ModelInfoManage\n)\nfrom models_provider.impl.aws_bedrock_model_provider.credential.embedding import BedrockEmbeddingCredential\nfrom models_provider.impl.aws_bedrock_model_provider.credential.image import BedrockVLModelCredential\nfrom models_provider.impl.aws_bedrock_model_provider.credential.llm import BedrockLLMModelCredential\nfrom models_provider.impl.aws_bedrock_model_provider.credential.reranker import BedrockRerankerCredential\nfrom models_provider.impl.aws_bedrock_model_provider.model.embedding import BedrockEmbeddingModel\nfrom models_provider.impl.aws_bedrock_model_provider.model.image import BedrockVLModel\nfrom models_provider.impl.aws_bedrock_model_provider.model.llm import BedrockModel\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\nfrom models_provider.impl.aws_bedrock_model_provider.model.reranker import BedrockRerankerModel\n\n\ndef _create_model_info(model_name, description, model_type, credential_class, model_class):\n    return ModelInfo(\n        name=model_name,\n        desc=description,\n        model_type=model_type,\n        model_credential=credential_class(),\n        model_class=model_class\n    )\n\n\ndef _get_aws_bedrock_icon_path():\n    return os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'aws_bedrock_model_provider',\n                        'icon', 'bedrock_icon_svg')\n\n\ndef _initialize_model_info():\n    model_info_list = [\n        _create_model_info(\n            'anthropic.claude-v2:1',\n            _('An update to Claude 2 that doubles the context window and improves reliability, hallucination rates, and evidence-based accuracy in long documents and RAG contexts.'),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel\n        ),\n        _create_model_info(\n            'anthropic.claude-v2',\n            _('Anthropic is a powerful model that can handle a variety of tasks, from complex dialogue and creative content generation to detailed command obedience.'),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel\n        ),\n        _create_model_info(\n            'anthropic.claude-3-haiku-20240307-v1:0',\n            _(\"The Claude 3 Haiku is Anthropic's fastest and most compact model, with near-instant responsiveness. The model can answer simple queries and requests quickly. Customers will be able to build seamless AI experiences that mimic human interactions. Claude 3 Haiku can process images and return text output, and provides 200K context windows.\"),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel\n        ),\n        _create_model_info(\n            'anthropic.claude-3-sonnet-20240229-v1:0',\n            _(\"The Claude 3 Sonnet model from Anthropic strikes the ideal balance between intelligence and speed, especially when it comes to handling enterprise workloads. This model offers maximum utility while being priced lower than competing products, and it's been engineered to be a solid choice for deploying AI at scale.\"),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel\n        ),\n        _create_model_info(\n            'anthropic.claude-3-5-sonnet-20240620-v1:0',\n            _('The Claude 3.5 Sonnet raises the industry standard for intelligence, outperforming competing models and the Claude 3 Opus in extensive evaluations, with the speed and cost-effectiveness of our mid-range models.'),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel\n        ),\n        _create_model_info(\n            'anthropic.claude-instant-v1',\n            _('A faster, more affordable but still very powerful model that can handle a range of tasks including casual conversation, text analysis, summarization and document question answering.'),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel\n        ),\n        _create_model_info(\n            'amazon.titan-text-premier-v1:0',\n            _(\"Titan Text Premier is the most powerful and advanced model in the Titan Text series, designed to deliver exceptional performance for a variety of enterprise applications. With its cutting-edge features, it delivers greater accuracy and outstanding results, making it an excellent choice for organizations looking for a top-notch text processing solution.\"),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel\n        ),\n        _create_model_info(\n            'amazon.titan-text-lite-v1',\n            _('Amazon Titan Text Lite is a lightweight, efficient model ideal for fine-tuning English-language tasks, including summarization and copywriting, where customers require smaller, more cost-effective, and highly customizable models.'),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel),\n        _create_model_info(\n            'amazon.titan-text-express-v1',\n            _('Amazon Titan Text Express has context lengths of up to 8,000 tokens, making it ideal for a variety of high-level general language tasks, such as open-ended text generation and conversational chat, as well as support in retrieval-augmented generation (RAG). At launch, the model is optimized for English, but other languages are supported.'),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel),\n        _create_model_info(\n            'mistral.mistral-7b-instruct-v0:2',\n            _('7B dense converter for rapid deployment and easy customization. Small in size yet powerful in a variety of use cases. Supports English and code, as well as 32k context windows.'),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel),\n        _create_model_info(\n            'mistral.mistral-large-2402-v1:0',\n            _('Advanced Mistral AI large-scale language model capable of handling any language task, including complex multilingual reasoning, text understanding, transformation, and code generation.'),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel),\n        _create_model_info(\n            'meta.llama3-70b-instruct-v1:0',\n            _('Ideal for content creation, conversational AI, language understanding, R&D, and enterprise applications'),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel),\n        _create_model_info(\n            'meta.llama3-8b-instruct-v1:0',\n            _('Ideal for limited computing power and resources, edge devices, and faster training times.'),\n            ModelTypeConst.LLM,\n            BedrockLLMModelCredential,\n            BedrockModel),\n    ]\n    embedded_model_info_list = [\n        _create_model_info(\n            'amazon.titan-embed-text-v1',\n            _('Titan Embed Text is the largest embedding model in the Amazon Titan Embed series and can handle various text embedding tasks, such as text classification, text similarity calculation, etc.'),\n            ModelTypeConst.EMBEDDING,\n            BedrockEmbeddingCredential,\n            BedrockEmbeddingModel\n        ),\n    ]\n\n    reranker_model_info_list = [\n        _create_model_info(\n            'amazon.rerank-v1:0',\n            '',\n            ModelTypeConst.RERANKER,\n            BedrockRerankerCredential,\n            BedrockRerankerModel\n        ),\n        _create_model_info(\n            'cohere.rerank-v3-5:0',\n            '',\n            ModelTypeConst.RERANKER,\n            BedrockRerankerCredential,\n            BedrockRerankerModel\n        )\n    ]\n    vl_model_info_list = [\n\n        _create_model_info(\n            'global.anthropic.claude-sonnet-4-5-20250929-v1:0',\n            '',\n            ModelTypeConst.IMAGE,\n            BedrockVLModelCredential,\n            BedrockVLModel\n        ),\n        _create_model_info(\n            'us.anthropic.claude-sonnet-4-5-20250929-v1:0',\n            '',\n            ModelTypeConst.IMAGE,\n            BedrockVLModelCredential,\n            BedrockVLModel\n        ),\n        _create_model_info(\n            'global.anthropic.claude-haiku-4-5-20251001-v1:0',\n            '',\n            ModelTypeConst.IMAGE,\n            BedrockVLModelCredential,\n            BedrockVLModel\n        ),\n    ]\n\n    model_info_manage = ModelInfoManage.builder() \\\n        .append_model_info_list(model_info_list) \\\n        .append_default_model_info(model_info_list[0]) \\\n        .append_model_info_list(embedded_model_info_list) \\\n        .append_default_model_info(embedded_model_info_list[0]) \\\n        .append_model_info_list(vl_model_info_list) \\\n        .append_default_model_info(vl_model_info_list[0]) \\\n        .append_model_info_list(reranker_model_info_list) \\\n        .append_default_model_info(reranker_model_info_list[0]) \\\n        .build()\n\n    return model_info_manage\n\n\nclass BedrockModelProvider(IModelProvider):\n    def __init__(self):\n        self._model_info_manage = _initialize_model_info()\n\n    def get_model_info_manage(self):\n        return self._model_info_manage\n\n    def get_model_provide_info(self):\n        icon_path = _get_aws_bedrock_icon_path()\n        icon_data = get_file_content(icon_path)\n        return ModelProvideInfo(\n            provider='model_aws_bedrock_provider',\n            name='Amazon Bedrock',\n            icon=icon_data\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/credential/embedding.py",
    "content": "from typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.aws_bedrock_model_provider.model.embedding import BedrockEmbeddingModel\nfrom common.utils.logger import maxkb_logger\n\nclass BedrockEmbeddingCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('{model_type} Model type is not supported').format(model_type=model_type))\n            return False\n\n        required_keys = ['region_name', 'access_key_id', 'secret_access_key']\n        if not all(key in model_credential for key in required_keys):\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('The following fields are required: {keys}').format(\n                                          keys=\", \".join(required_keys)))\n            return False\n\n        try:\n            model: BedrockEmbeddingModel = provider.get_model(model_type, model_name, model_credential)\n            aa = model.embed_query(_('Hello'))\n        except AppApiException:\n            raise\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'secret_access_key': super().encryption(model.get('secret_access_key', ''))}\n\n    region_name = forms.TextInputField('Region Name', required=True)\n    access_key_id = forms.TextInputField('Access Key ID', required=True)\n    secret_access_key = forms.PasswordInputField('Secret Access Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/credential/image.py",
    "content": "from typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import ValidCode, BaseModelCredential\nfrom common.utils.logger import maxkb_logger\n\n\nclass BedrockImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=1024,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass BedrockVLModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('{model_type} Model type is not supported').format(model_type=model_type))\n            return False\n\n        required_keys = ['region_name', 'access_key_id', 'secret_access_key']\n        if not all(key in model_credential for key in required_keys):\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('The following fields are required: {keys}').format(\n                                          keys=\", \".join(required_keys)))\n            return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except AppApiException:\n            raise\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'secret_access_key': super().encryption(model.get('secret_access_key', ''))}\n\n    region_name = forms.TextInputField('Region Name', required=True)\n    access_key_id = forms.TextInputField('Access Key ID', required=True)\n    secret_access_key = forms.PasswordInputField('Secret Access Key', required=True)\n    base_url = forms.TextInputField('Proxy URL', required=False)\n\n    def get_model_params_setting_form(self, model_name):\n        return BedrockImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/credential/llm.py",
    "content": "from typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import ValidCode, BaseModelCredential\nfrom common.utils.logger import maxkb_logger\n\nclass BedrockLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=1024,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass BedrockLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(mt.get('value') == model_type for mt in model_type_list):\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('{model_type} Model type is not supported').format(model_type=model_type))\n            return False\n\n        required_keys = ['region_name', 'access_key_id', 'secret_access_key']\n        if not all(key in model_credential for key in required_keys):\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('The following fields are required: {keys}').format(\n                                          keys=\", \".join(required_keys)))\n            return False\n\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except AppApiException:\n            raise\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'secret_access_key': super().encryption(model.get('secret_access_key', ''))}\n\n    region_name = forms.TextInputField('Region Name', required=True)\n    access_key_id = forms.TextInputField('Access Key ID', required=True)\n    secret_access_key = forms.PasswordInputField('Secret Access Key', required=True)\n    base_url = forms.TextInputField('Proxy URL', required=False)\n\n    def get_model_params_setting_form(self, model_name):\n        return BedrockLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/credential/reranker.py",
    "content": "import traceback\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.documents import Document\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import ValidCode, BaseModelCredential\n\n\nclass BedrockRerankerModelParams(BaseForm):\n    top_n = forms.SliderField(TooltipLabel(_('Top N'),\n                                          _('Number of top documents to return after reranking')),\n                             required=True, default_value=3,\n                             _min=1,\n                             _max=20,\n                             _step=1,\n                             precision=0)\n\n\nclass BedrockRerankerCredential(BaseForm, BaseModelCredential):\n    access_key_id = forms.PasswordInputField(_('Access Key ID'), required=True)\n    secret_access_key = forms.PasswordInputField(_('Secret Access Key'), required=True)\n    region_name = forms.TextInputField(_('Region Name'), required=True, default_value='us-east-1')\n    base_url = forms.TextInputField(_('Base URL'), required=False)\n\n    def is_valid(self, model_type: str, model_name: str, model_credential: Dict[str, object], model_params,\n                 provider,\n                 raise_exception: bool = False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value, _('Model type is not supported'))\n\n        for key in ['access_key_id', 'secret_access_key', 'region_name']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('%(key)s is required') % {'key': key})\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            # Use top_n=1 for validation since we only have 1 test document\n            test_docs = [\n                Document(page_content=str(_('Hello'))),\n                Document(page_content=str(_('World'))),\n                Document(page_content=str(_('Test')))\n            ]\n            model.compress_documents(test_docs, str(_('Hello')))\n        except Exception as e:\n            traceback.print_exc()\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: %(error)s') % {'error': str(e)})\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'access_key_id': super().encryption(model.get('access_key_id', '')),\n                'secret_access_key': super().encryption(model.get('secret_access_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return BedrockRerankerModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/model/embedding.py",
    "content": "from langchain_aws import BedrockEmbeddings\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom typing import Dict, List\n\nfrom models_provider.impl.aws_bedrock_model_provider.model.llm import _update_aws_credentials\n\n\nclass BedrockEmbeddingModel(MaxKBBaseModel, BedrockEmbeddings):\n    def __init__(self, model_id: str, region_name: str, credentials_profile_name: str,\n                 **kwargs):\n        super().__init__(model_id=model_id, region_name=region_name,\n                         credentials_profile_name=credentials_profile_name, **kwargs)\n\n    @classmethod\n    def new_instance(cls, model_type: str, model_name: str, model_credential: Dict[str, str],\n                     **model_kwargs) -> 'BedrockModel':\n        _update_aws_credentials(model_credential['access_key_id'], model_credential['access_key_id'],\n                                model_credential['secret_access_key'])\n        return cls(\n            model_id=model_name,\n            region_name=model_credential['region_name'],\n            credentials_profile_name=model_credential['access_key_id'],\n        )\n\n    def embed_documents(self, texts: List[str]) -> List[List[float]]:\n        \"\"\"Compute doc embeddings using a Bedrock model.\n\n        Args:\n            texts: The list of texts to embed\n\n        Returns:\n            List of embeddings, one for each text.\n        \"\"\"\n        results = []\n        for text in texts:\n            response = self._embedding_func(text)\n\n            if self.normalize:\n                response = self._normalize_vector(response)\n\n            results.append(response)\n\n        return results\n\n    def embed_query(self, text: str) -> List[float]:\n        \"\"\"Compute query embeddings using a Bedrock model.\n\n        Args:\n            text: The text to embed.\n\n        Returns:\n            Embeddings for the text.\n        \"\"\"\n        embedding = self._embedding_func(text)\n\n        if self.normalize:\n            return self._normalize_vector(embedding)\n\n        return embedding\n"
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/model/image.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @file: image.py\n    @desc: AWS Bedrock Vision-Language Model Implementation\n\"\"\"\nfrom typing import Dict, List\n\nfrom botocore.config import Config\nfrom langchain_aws import ChatBedrock\nfrom langchain_core.messages import BaseMessage, get_buffer_string\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.aws_bedrock_model_provider.model.llm import _update_aws_credentials\n\n\nclass BedrockVLModel(MaxKBBaseModel, ChatBedrock):\n    \"\"\"\n    AWS Bedrock Vision-Language Model\n    Supports Claude 3 models with vision capabilities (Haiku, Sonnet, Opus)\n    \"\"\"\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    def __init__(self, model_id: str, region_name: str, credentials_profile_name: str,\n                 streaming: bool = False, config: Config = None, **kwargs):\n        super().__init__(\n            model_id=model_id,\n            region_name=region_name,\n            credentials_profile_name=credentials_profile_name,\n            streaming=streaming,\n            config=config,\n            **kwargs\n        )\n\n    @classmethod\n    def new_instance(cls, model_type: str, model_name: str, model_credential: Dict[str, str],\n                     **model_kwargs) -> 'BedrockVLModel':\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n\n        config = {}\n        # Check if proxy URL is provided\n        if 'base_url' in model_credential and model_credential['base_url']:\n            proxy_url = model_credential['base_url']\n            config = Config(\n                proxies={\n                    'http': proxy_url,\n                    'https': proxy_url\n                },\n                connect_timeout=60,\n                read_timeout=60\n            )\n        _update_aws_credentials(\n            model_credential['access_key_id'],\n            model_credential['access_key_id'],\n            model_credential['secret_access_key']\n        )\n\n        return cls(\n            model_id=model_name,\n            region_name=model_credential['region_name'],\n            credentials_profile_name=model_credential['access_key_id'],\n            streaming=model_kwargs.pop('streaming', True),\n            model_kwargs=optional_params,\n            config=config\n        )\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        \"\"\"\n        Get the number of tokens from messages\n        Falls back to local tokenizer if the model's tokenizer fails\n        \"\"\"\n        try:\n            return super().get_num_tokens_from_messages(messages)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n    def get_num_tokens(self, text: str) -> int:\n        \"\"\"\n        Get the number of tokens from text\n        Falls back to local tokenizer if the model's tokenizer fails\n        \"\"\"\n        try:\n            return super().get_num_tokens(text)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n"
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/model/llm.py",
    "content": "import os\nimport re\nfrom typing import Dict, List\n\nfrom botocore.config import Config\nfrom langchain_aws import ChatBedrock\nfrom langchain_core.messages import BaseMessage, get_buffer_string\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\ndef get_max_tokens_keyword(model_name):\n    \"\"\"\n    根据模型名称返回正确的 max_tokens 关键字。\n\n    :param model_name: 模型名称字符串\n    :return: 对应的 max_tokens 关键字字符串\n    \"\"\"\n    maxTokens = [\"ai21.j2-ultra-v1\", \"ai21.j2-mid-v1\"]\n    # max_tokens_to_sample = [\"anthropic.claude-v2:1\", \"anthropic.claude-v2\", \"anthropic.claude-instant-v1\"]\n    maxTokenCount = [\"amazon.titan-text-lite-v1\", \"amazon.titan-text-express-v1\"]\n    max_new_tokens = [\n        \"us.meta.llama3-2-1b-instruct-v1:0\", \"us.meta.llama3-2-3b-instruct-v1:0\", \"us.meta.llama3-2-11b-instruct-v1:0\",\n        \"us.meta.llama3-2-90b-instruct-v1:0\"]\n    if model_name in maxTokens:\n        return 'maxTokens'\n    elif model_name in maxTokenCount:\n        return 'maxTokenCount'\n    elif model_name in max_new_tokens:\n        return 'max_new_tokens'\n    else:\n        return 'max_tokens'\n\n\nclass BedrockModel(MaxKBBaseModel, ChatBedrock):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    def __init__(self, model_id: str, region_name: str, credentials_profile_name: str,\n                 streaming: bool = False, config: Config = None, **kwargs):\n        super().__init__(model_id=model_id, region_name=region_name,\n                         credentials_profile_name=credentials_profile_name, streaming=streaming, config=config,\n                         **kwargs)\n\n    @classmethod\n    def new_instance(cls, model_type: str, model_name: str, model_credential: Dict[str, str],\n                     **model_kwargs) -> 'BedrockModel':\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n\n        config = {}\n        # 判断model_kwargs是否包含 base_url 且不为空\n        if 'base_url' in model_credential and model_credential['base_url']:\n            proxy_url = model_credential['base_url']\n            config = Config(\n                proxies={\n                    'http': proxy_url,\n                    'https': proxy_url\n                },\n                connect_timeout=60,\n                read_timeout=60\n            )\n        _update_aws_credentials(model_credential['access_key_id'], model_credential['access_key_id'],\n                                model_credential['secret_access_key'])\n\n        return cls(\n            model_id=model_name,\n            region_name=model_credential['region_name'],\n            credentials_profile_name=model_credential['access_key_id'],\n            streaming=model_kwargs.pop('streaming', True),\n            model_kwargs=optional_params,\n            config=config\n        )\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        try:\n            return super().get_num_tokens_from_messages(messages)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n    def get_num_tokens(self, text: str) -> int:\n        try:\n            return super().get_num_tokens(text)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n\ndef _update_aws_credentials(profile_name, access_key_id, secret_access_key):\n    credentials_path = os.path.join(os.path.expanduser(\"~\"), \".aws\", \"credentials\")\n    os.makedirs(os.path.dirname(credentials_path), exist_ok=True)\n\n    content = open(credentials_path, 'r').read() if os.path.exists(credentials_path) else ''\n    pattern = rf'\\n*\\[{profile_name}\\]\\n*(aws_access_key_id = .*)\\n*(aws_secret_access_key = .*)\\n*'\n    content = re.sub(pattern, '', content, flags=re.DOTALL)\n\n    if not re.search(rf'\\[{profile_name}\\]', content):\n        content += f\"\\n[{profile_name}]\\naws_access_key_id = {access_key_id}\\naws_secret_access_key = {secret_access_key}\\n\"\n\n    with open(credentials_path, 'w') as file:\n        file.write(content)\n"
  },
  {
    "path": "apps/models_provider/impl/aws_bedrock_model_provider/model/reranker.py",
    "content": "import os\nimport re\nfrom typing import Dict, List, Sequence, Optional, Any\n\nfrom botocore.config import Config\nfrom langchain_aws import BedrockRerank\nfrom langchain_core.callbacks import Callbacks\nfrom langchain_core.documents import BaseDocumentCompressor, Document\nfrom pydantic import ConfigDict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.aws_bedrock_model_provider.model.llm import _update_aws_credentials\n\n\nclass BedrockRerankerModel(MaxKBBaseModel, BaseDocumentCompressor):\n    model_config = ConfigDict(arbitrary_types_allowed=True)\n\n    model_id: Optional[str] = None\n    model_arn: Optional[str] = None\n    region_name: Optional[str] = None\n    credentials_profile_name: Optional[str] = None\n    aws_access_key_id: Optional[str] = None\n    aws_secret_access_key: Optional[str] = None\n    config: Optional[Any] = None\n    top_n: Optional[int] = 3\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type: str, model_name: str, model_credential: Dict[str, str],\n                     **model_kwargs) -> 'BedrockRerankerModel':\n        top_n = model_kwargs.get('top_n', 3)\n        region_name = model_credential['region_name']\n        model_arn = f\"arn:aws:bedrock:{region_name}::foundation-model/{model_name}\"\n\n        config = None\n        if 'base_url' in model_credential and model_credential['base_url']:\n            proxy_url = model_credential['base_url']\n            config = Config(\n                proxies={\n                    'http': proxy_url,\n                    'https': proxy_url\n                },\n                connect_timeout=60,\n                read_timeout=60\n            )\n\n        _update_aws_credentials(model_credential['access_key_id'], model_credential['access_key_id'],\n                                model_credential['secret_access_key'])\n\n        return BedrockRerankerModel(\n            model_id=model_name,\n            model_arn=model_arn,\n            region_name=region_name,\n            credentials_profile_name=model_credential['access_key_id'],\n            aws_access_key_id=model_credential['access_key_id'],\n            aws_secret_access_key=model_credential['secret_access_key'],\n            config=config,\n            top_n=top_n\n        )\n\n    def compress_documents(self, documents: Sequence[Document], query: str,\n                          callbacks: Optional[Callbacks] = None) -> Sequence[Document]:\n        \"\"\"Compress documents using Bedrock reranking.\"\"\"\n        if not documents:\n            return []\n\n        reranker = BedrockRerank(\n            model_arn=self.model_arn,\n            region_name=self.region_name,\n            credentials_profile_name=self.credentials_profile_name,\n            aws_access_key_id=self.aws_access_key_id,\n            aws_secret_access_key=self.aws_secret_access_key,\n            config=self.config,\n            top_n=self.top_n\n        )\n        return reranker.compress_documents(documents, query, callbacks)\n\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2023/10/31 17:16\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/azure_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： azure_model_provider.py\n    @date：2023/10/31 16:19\n    @desc:\n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \\\n    ModelTypeConst, ModelInfoManage\nfrom models_provider.impl.azure_model_provider.credential.embedding import AzureOpenAIEmbeddingCredential\nfrom models_provider.impl.azure_model_provider.credential.image import AzureOpenAIImageModelCredential\nfrom models_provider.impl.azure_model_provider.credential.llm import AzureLLMModelCredential\nfrom models_provider.impl.azure_model_provider.credential.stt import AzureOpenAISTTModelCredential\nfrom models_provider.impl.azure_model_provider.credential.tti import AzureOpenAITextToImageModelCredential\nfrom models_provider.impl.azure_model_provider.credential.tts import AzureOpenAITTSModelCredential\nfrom models_provider.impl.azure_model_provider.model.azure_chat_model import AzureChatModel\nfrom models_provider.impl.azure_model_provider.model.embedding import AzureOpenAIEmbeddingModel\nfrom models_provider.impl.azure_model_provider.model.image import AzureOpenAIImage\nfrom models_provider.impl.azure_model_provider.model.stt import AzureOpenAISpeechToText\nfrom models_provider.impl.azure_model_provider.model.tti import AzureOpenAITextToImage\nfrom models_provider.impl.azure_model_provider.model.tts import AzureOpenAITextToSpeech\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext_lazy as _\n\nbase_azure_llm_model_credential = AzureLLMModelCredential()\nbase_azure_embedding_model_credential = AzureOpenAIEmbeddingCredential()\nbase_azure_image_model_credential = AzureOpenAIImageModelCredential()\nbase_azure_tti_model_credential = AzureOpenAITextToImageModelCredential()\nbase_azure_tts_model_credential = AzureOpenAITTSModelCredential()\nbase_azure_stt_model_credential = AzureOpenAISTTModelCredential()\n\ndefault_model_info = [\n    ModelInfo('Azure OpenAI', '', ModelTypeConst.LLM,\n              base_azure_llm_model_credential, AzureChatModel, api_version='2024-02-15-preview'\n              ),\n    ModelInfo('gpt-4', '', ModelTypeConst.LLM,\n              base_azure_llm_model_credential, AzureChatModel, api_version='2024-02-15-preview'\n              ),\n    ModelInfo('gpt-4o', '', ModelTypeConst.LLM,\n              base_azure_llm_model_credential, AzureChatModel, api_version='2024-02-15-preview'\n              ),\n    ModelInfo('gpt-4o-mini', '', ModelTypeConst.LLM,\n              base_azure_llm_model_credential, AzureChatModel, api_version='2024-02-15-preview'\n              ),\n]\n\nembedding_model_info = [\n    ModelInfo('text-embedding-3-large', '', ModelTypeConst.EMBEDDING,\n              base_azure_embedding_model_credential, AzureOpenAIEmbeddingModel, api_version='2023-05-15'\n              ),\n    ModelInfo('text-embedding-3-small', '', ModelTypeConst.EMBEDDING,\n              base_azure_embedding_model_credential, AzureOpenAIEmbeddingModel, api_version='2023-05-15'\n              ),\n    ModelInfo('text-embedding-ada-002', '', ModelTypeConst.EMBEDDING,\n              base_azure_embedding_model_credential, AzureOpenAIEmbeddingModel, api_version='2023-05-15'\n              ),\n]\n\nimage_model_info = [\n    ModelInfo('gpt-4o', '', ModelTypeConst.IMAGE,\n              base_azure_image_model_credential, AzureOpenAIImage, api_version='2023-05-15'\n              ),\n    ModelInfo('gpt-4o-mini', '', ModelTypeConst.IMAGE,\n              base_azure_image_model_credential, AzureOpenAIImage, api_version='2023-05-15'\n              ),\n]\n\ntti_model_info = [\n    ModelInfo('dall-e-3', '', ModelTypeConst.TTI,\n              base_azure_tti_model_credential, AzureOpenAITextToImage, api_version='2023-05-15'\n              ),\n]\n\ntts_model_info = [\n    ModelInfo('tts', '', ModelTypeConst.TTS,\n              base_azure_tts_model_credential, AzureOpenAITextToSpeech, api_version='2023-05-15'\n              ),\n]\n\nstt_model_info = [\n    ModelInfo('whisper', '', ModelTypeConst.STT,\n              base_azure_stt_model_credential, AzureOpenAISpeechToText, api_version='2023-05-15'\n              ),\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_default_model_info(default_model_info[0])\n    .append_model_info_list(default_model_info)\n    .append_model_info_list(embedding_model_info)\n    .append_default_model_info(embedding_model_info[0])\n    .append_model_info_list(image_model_info)\n    .append_default_model_info(image_model_info[0])\n    .append_model_info_list(stt_model_info)\n    .append_default_model_info(stt_model_info[0])\n    .append_model_info_list(tts_model_info)\n    .append_default_model_info(tts_model_info[0])\n    .append_model_info_list(tti_model_info)\n    .append_default_model_info(tti_model_info[0])\n    .build()\n)\n\n\nclass AzureModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_azure_provider', name='Azure OpenAI', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'azure_model_provider', 'icon',\n                         'azure_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 17:08\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass AzureOpenAIEmbeddingCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key', 'api_version']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct'))\n            else:\n                return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_version = forms.TextInputField(\"Api Version\", required=True)\n\n    api_base = forms.TextInputField('Azure Endpoint', required=True)\n\n    api_key = forms.PasswordInputField(\"API Key\", required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/credential/image.py",
    "content": "# coding=utf-8\nimport base64\nimport os\nfrom typing import Dict\n\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext_lazy as _, gettext\n\n\nclass AzureOpenAIImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass AzureOpenAIImageModelCredential(BaseForm, BaseModelCredential):\n    api_version = forms.TextInputField(\"API Version\", required=True)\n    api_base = forms.TextInputField('Azure Endpoint', required=True)\n    api_key = forms.PasswordInputField(\"API Key\", required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key', 'api_version']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext('Hello')}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return AzureOpenAIImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 17:08\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom langchain_core.messages import HumanMessage\nfrom openai import BadRequestError\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom common.utils.logger import maxkb_logger\n\nclass AzureLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass o3MiniLLMModelParams(BaseForm):\n    max_completion_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=5000,\n        _step=1,\n        precision=0)\n\n\nclass AzureLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key', 'deployment_name', 'api_version']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException) or isinstance(e, BadRequestError):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('Verification failed, please check whether the parameters are correct'))\n            else:\n                return False\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_version = forms.TextInputField(\"API Version\", required=True)\n\n    api_base = forms.TextInputField('Azure Endpoint', required=True)\n\n    api_key = forms.PasswordInputField(\"API Key\", required=True)\n\n    deployment_name = forms.TextInputField(\"Deployment name\", required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        if 'o3' in model_name or 'o1' in model_name:\n            return o3MiniLLMModelParams()\n        return AzureLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/credential/stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass AzureOpenAISTTModelCredential(BaseForm, BaseModelCredential):\n    api_version = forms.TextInputField(\"API Version\", required=True)\n    api_base = forms.TextInputField('Azure Endpoint', required=True)\n    api_key = forms.PasswordInputField(\"API Key\", required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key', 'api_version']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        pass\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/credential/tti.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass AzureOpenAITTIModelParams(BaseForm):\n    size = forms.SingleSelect(\n        TooltipLabel(_('Image size'), _('Specify the size of the generated image, such as: 1024x1024')),\n        required=True,\n        default_value='1024x1024',\n        option_list=[\n            {'value': '1024x1024', 'label': '1024x1024'},\n            {'value': '1024x1792', 'label': '1024x1792'},\n            {'value': '1792x1024', 'label': '1792x1024'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    quality = forms.SingleSelect(\n        TooltipLabel(_('Picture quality'), ''),\n        required=True,\n        default_value='standard',\n        option_list=[\n            {'value': 'standard', 'label': 'standard'},\n            {'value': 'hd', 'label': 'hd'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    n = forms.SliderField(\n        TooltipLabel(_('Number of pictures'), _('Specify the number of generated images')),\n        required=True, default_value=1,\n        _min=1,\n        _max=10,\n        _step=1,\n        precision=0)\n\n\nclass AzureOpenAITextToImageModelCredential(BaseForm, BaseModelCredential):\n    api_version = forms.TextInputField(\"API Version\", required=True)\n    api_base = forms.TextInputField('Azure Endpoint', required=True)\n    api_key = forms.PasswordInputField(\"API Key\", required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key', 'api_version']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return AzureOpenAITTIModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/credential/tts.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass AzureOpenAITTSModelGeneralParams(BaseForm):\n    # alloy, echo, fable, onyx, nova, shimmer\n    voice = forms.SingleSelect(\n        TooltipLabel('Voice',\n                     _('Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) to find one that suits your desired tone and audience. The current voiceover is optimized for English.')),\n        required=True, default_value='alloy',\n        text_field='value',\n        value_field='value',\n        option_list=[\n            {'text': 'alloy', 'value': 'alloy'},\n            {'text': 'echo', 'value': 'echo'},\n            {'text': 'fable', 'value': 'fable'},\n            {'text': 'onyx', 'value': 'onyx'},\n            {'text': 'nova', 'value': 'nova'},\n            {'text': 'shimmer', 'value': 'shimmer'},\n        ])\n\n\nclass AzureOpenAITTSModelCredential(BaseForm, BaseModelCredential):\n    api_version = forms.TextInputField(\"API Version\", required=True)\n    api_base = forms.TextInputField('Azure Endpoint', required=True)\n    api_key = forms.PasswordInputField(\"API Key\", required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key', 'api_version']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value, gettext(\n                    'Verification failed, please check whether the parameters are correct: {error}').format(\n                    error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return AzureOpenAITTSModelGeneralParams()\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/model/azure_chat_model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： azure_chat_model.py\n    @date：2024/4/28 11:45\n    @desc:\n\"\"\"\n\nfrom typing import List, Dict, Optional, Any\n\nfrom langchain_core.language_models import LanguageModelInput\nfrom langchain_core.messages import BaseMessage, get_buffer_string\nfrom langchain_core.runnables import RunnableConfig\nfrom langchain_openai import AzureChatOpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass AzureChatModel(MaxKBBaseModel, AzureChatOpenAI):\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n\n        return AzureChatModel(\n            azure_endpoint=model_credential.get('api_base'),\n            model_name=model_name,\n            openai_api_version=model_credential.get('api_version', '2024-02-15-preview'),\n            deployment_name=model_credential.get('deployment_name'),\n            openai_api_key=model_credential.get('api_key'),\n            openai_api_type=\"azure\",\n            **optional_params,\n            streaming=True,\n        )\n\n    def get_last_generation_info(self) -> Optional[Dict[str, Any]]:\n        return self.__dict__.get('_last_generation_info')\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        try:\n            return self.get_last_generation_info().get('input_tokens', 0)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n    def get_num_tokens(self, text: str) -> int:\n        try:\n            return self.get_last_generation_info().get('output_tokens', 0)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n\n    def invoke(\n            self,\n            input: LanguageModelInput,\n            config: Optional[RunnableConfig] = None,\n            *,\n            stop: Optional[list[str]] = None,\n            **kwargs: Any,\n    ) -> BaseMessage:\n        message = super().invoke(input, config, stop=stop, **kwargs)\n        if isinstance(message.content, str):\n            return message\n        elif isinstance(message.content, list):\n            # 构造新的响应消息返回\n            content = message.content\n            normalized_parts = []\n            for item in content:\n                if isinstance(item, dict):\n                    if item.get('type') == 'text':\n                        normalized_parts.append(item.get('text', ''))\n            message.content = ''.join(normalized_parts)\n            self.__dict__.setdefault('_last_generation_info', {}).update(message.usage_metadata)\n            return message\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 17:44\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom langchain_openai import AzureOpenAIEmbeddings\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass AzureOpenAIEmbeddingModel(MaxKBBaseModel, AzureOpenAIEmbeddings):\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return AzureOpenAIEmbeddingModel(\n            model=model_name,\n            openai_api_key=model_credential.get('api_key'),\n            azure_endpoint=model_credential.get('api_base'),\n            openai_api_version=model_credential.get('api_version'),\n            openai_api_type=\"azure\",\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/model/image.py",
    "content": "from typing import Dict, List\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\nfrom langchain_openai import AzureChatOpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass AzureOpenAIImage(MaxKBBaseModel, AzureChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return AzureOpenAIImage(\n            model_name=model_name,\n            openai_api_key=model_credential.get('api_key'),\n            azure_endpoint=model_credential.get('api_base'),\n            openai_api_version=model_credential.get('api_version'),\n            openai_api_type=\"azure\",\n            streaming=True,\n            **optional_params,\n        )\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        try:\n            return super().get_num_tokens_from_messages(messages)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n    def get_num_tokens(self, text: str) -> int:\n        try:\n            return super().get_num_tokens(text)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/model/stt.py",
    "content": "import io\nfrom typing import Dict\n\nfrom openai import AzureOpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass AzureOpenAISpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    api_base: str\n    api_key: str\n    api_version: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.api_version = kwargs.get('api_version')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {}\n        if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None:\n            optional_params['max_tokens'] = model_kwargs['max_tokens']\n        if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None:\n            optional_params['temperature'] = model_kwargs['temperature']\n        return AzureOpenAISpeechToText(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            api_version=model_credential.get('api_version'),\n            params=model_kwargs,\n            **optional_params,\n        )\n\n    def check_auth(self):\n        client = AzureOpenAI(\n            azure_endpoint=self.api_base,\n            api_key=self.api_key,\n            api_version=self.api_version\n        )\n        response_list = client.models.with_raw_response.list()\n        # print(response_list)\n\n    def speech_to_text(self, audio_file):\n        client = AzureOpenAI(\n            azure_endpoint=self.api_base,\n            api_key=self.api_key,\n            api_version=self.api_version\n        )\n        audio_data = audio_file.read()\n        buffer = io.BytesIO(audio_data)\n        buffer.name = \"file.mp3\"  # this is the important line\n\n        filter_params = {k: v for k, v in self.params.items() if k not in {'model_id', 'use_local', 'streaming'}}\n        transcription_params = {\n            'model': self.model,\n            'file': buffer,\n            'language': 'zh'\n        }\n\n        res = client.audio.transcriptions.create(**transcription_params, extra_body=filter_params)\n        return res.text\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/model/tti.py",
    "content": "from typing import Dict\n\nfrom openai import AzureOpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass AzureOpenAITextToImage(MaxKBBaseModel, BaseTextToImage):\n    api_base: str\n    api_key: str\n    api_version: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.api_version = kwargs.get('api_version')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return AzureOpenAITextToImage(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            api_version=model_credential.get('api_version'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        chat = AzureOpenAI(api_key=self.api_key, azure_endpoint=self.api_base, api_version=self.api_version)\n        response_list = chat.models.with_raw_response.list()\n\n        # self.generate_image('生成一个小猫图片')\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        chat = AzureOpenAI(api_key=self.api_key, azure_endpoint=self.api_base, api_version=self.api_version)\n        res = chat.images.generate(model=self.model, prompt=prompt, **self.params)\n        file_urls = []\n        try:\n            for content in res.data:\n                url = content.url\n                file_urls.append(url)\n            return file_urls\n        except Exception as e:\n            raise e\n\n\n"
  },
  {
    "path": "apps/models_provider/impl/azure_model_provider/model/tts.py",
    "content": "from typing import Dict\n\nfrom openai import AzureOpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom common.utils.common import _remove_empty_lines\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tts import BaseTextToSpeech\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass AzureOpenAITextToSpeech(MaxKBBaseModel, BaseTextToSpeech):\n    api_base: str\n    api_key: str\n    api_version: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.api_version = kwargs.get('api_version')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'voice': 'alloy'}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return AzureOpenAITextToSpeech(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            api_version=model_credential.get('api_version'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        client = AzureOpenAI(\n            azure_endpoint=self.api_base,\n            api_key=self.api_key,\n            api_version=self.api_version\n        )\n        response_list = client.models.with_raw_response.list()\n        # print(response_list)\n\n    def text_to_speech(self, text):\n        client = AzureOpenAI(\n            azure_endpoint=self.api_base,\n            api_key=self.api_key,\n            api_version=self.api_version\n        )\n        text = _remove_empty_lines(text)\n        with client.audio.speech.with_streaming_response.create(\n                model=self.model,\n                input=text,\n                **self.params\n        ) as response:\n            return response.read()\n"
  },
  {
    "path": "apps/models_provider/impl/base_chat_open_ai.py",
    "content": "# coding=utf-8\nimport base64\nfrom concurrent.futures import ThreadPoolExecutor\nfrom typing import Dict, Optional, Any, Iterator, cast, Union, Sequence, Callable, Mapping\n\nfrom langchain_core.language_models import LanguageModelInput\nfrom langchain_core.messages import BaseMessage, get_buffer_string, BaseMessageChunk, HumanMessageChunk, AIMessageChunk, \\\n    SystemMessageChunk, FunctionMessageChunk, ChatMessageChunk\nfrom langchain_core.messages.ai import UsageMetadata\nfrom langchain_core.messages.tool import tool_call_chunk, ToolMessageChunk\nfrom langchain_core.outputs import ChatGenerationChunk\nfrom langchain_core.runnables import RunnableConfig, ensure_config\nfrom langchain_core.tools import BaseTool\nfrom langchain_openai import ChatOpenAI\nfrom langchain_openai.chat_models.base import _create_usage_metadata\nfrom requests.exceptions import ReadTimeout\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom common.utils.logger import maxkb_logger\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\ndef _convert_delta_to_message_chunk(\n        _dict: Mapping[str, Any], default_class: type[BaseMessageChunk]\n) -> BaseMessageChunk:\n    \"\"\"Convert to a LangChain message chunk.\"\"\"\n    id_ = _dict.get(\"id\")\n    role = cast(str, _dict.get(\"role\"))\n    content = cast(str, _dict.get(\"content\") or \"\")\n    additional_kwargs: dict = {}\n    if 'reasoning_content' in _dict:\n        additional_kwargs['reasoning_content'] = _dict.get('reasoning_content')\n    if _dict.get(\"function_call\"):\n        function_call = dict(_dict[\"function_call\"])\n        if \"name\" in function_call and function_call[\"name\"] is None:\n            function_call[\"name\"] = \"\"\n        additional_kwargs[\"function_call\"] = function_call\n    tool_call_chunks = []\n    if raw_tool_calls := _dict.get(\"tool_calls\"):\n        try:\n            tool_call_chunks = [\n                tool_call_chunk(\n                    name=rtc[\"function\"].get(\"name\"),\n                    args=rtc[\"function\"].get(\"arguments\"),\n                    id=rtc.get(\"id\"),\n                    index=rtc[\"index\"],\n                )\n                for rtc in raw_tool_calls\n            ]\n        except KeyError:\n            pass\n\n    if role == \"user\" or default_class == HumanMessageChunk:\n        return HumanMessageChunk(content=content, id=id_)\n    if role == \"assistant\" or default_class == AIMessageChunk:\n        return AIMessageChunk(\n            content=content,\n            additional_kwargs=additional_kwargs,\n            id=id_,\n            tool_call_chunks=tool_call_chunks,  # type: ignore[arg-type]\n        )\n    if role in (\"system\", \"developer\") or default_class == SystemMessageChunk:\n        if role == \"developer\":\n            additional_kwargs = {\"__openai_role__\": \"developer\"}\n        else:\n            additional_kwargs = {}\n        return SystemMessageChunk(\n            content=content, id=id_, additional_kwargs=additional_kwargs\n        )\n    if role == \"function\" or default_class == FunctionMessageChunk:\n        return FunctionMessageChunk(content=content, name=_dict[\"name\"], id=id_)\n    if role == \"tool\" or default_class == ToolMessageChunk:\n        return ToolMessageChunk(\n            content=content, tool_call_id=_dict[\"tool_call_id\"], id=id_\n        )\n    if role or default_class == ChatMessageChunk:\n        return ChatMessageChunk(content=content, role=role, id=id_)\n    return default_class(content=content, id=id_)  # type: ignore[call-arg]#\n\n\nclass BaseChatOpenAI(ChatOpenAI):\n    usage_metadata: dict = {}\n    custom_get_token_ids = custom_get_token_ids\n\n    def get_last_generation_info(self) -> Optional[Dict[str, Any]]:\n        return self.usage_metadata\n\n    def get_num_tokens_from_messages(\n            self,\n            messages: list[BaseMessage],\n            tools: Optional[\n                Sequence[Union[dict[str, Any], type, Callable, BaseTool]]\n            ] = None,\n            timeout: Optional[float] = 0.5,\n    ) -> int:\n        if self.usage_metadata is None or self.usage_metadata == {}:\n\n            with ThreadPoolExecutor(max_workers=1) as executor:\n                future = executor.submit(super().get_num_tokens_from_messages, messages, tools)\n                try:\n                    response = future.result()\n                    maxkb_logger.info(\"请求成功（未超时）\")\n                    return response\n                except Exception as e:\n                    if isinstance(e, ReadTimeout):\n                        raise  # 继续抛出\n                    else:\n                        tokenizer = TokenizerManage.get_tokenizer()\n                        return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n        return self.usage_metadata.get('input_tokens', self.usage_metadata.get('prompt_tokens', 0))\n\n    def get_num_tokens(self, text: str) -> int:\n        if self.usage_metadata is None or self.usage_metadata == {}:\n            try:\n                return super().get_num_tokens(text)\n            except Exception as e:\n                tokenizer = TokenizerManage.get_tokenizer()\n                return len(tokenizer.encode(text))\n        return self.get_last_generation_info().get('output_tokens',\n                                                   self.get_last_generation_info().get('completion_tokens', 0))\n\n    def _stream(self, *args: Any, **kwargs: Any) -> Iterator[ChatGenerationChunk]:\n        kwargs['stream_usage'] = True\n        for chunk in super()._stream(*args, **kwargs):\n            if chunk.message.usage_metadata is not None:\n                self.usage_metadata = chunk.message.usage_metadata\n            yield chunk\n\n    def _convert_chunk_to_generation_chunk(\n            self,\n            chunk: dict,\n            default_chunk_class: type,\n            base_generation_info: dict | None,\n    ) -> ChatGenerationChunk | None:\n        if chunk.get(\"type\") == \"content.delta\":  # From beta.chat.completions.stream\n            return None\n        token_usage = chunk.get(\"usage\")\n        choices = (\n                chunk.get(\"choices\", [])\n                # From beta.chat.completions.stream\n                or chunk.get(\"chunk\", {}).get(\"choices\", [])\n        )\n\n        usage_metadata: UsageMetadata | None = (\n            _create_usage_metadata(token_usage, chunk.get(\"service_tier\"))\n            if token_usage\n            else None\n        )\n        if len(choices) == 0:\n            # logprobs is implicitly None\n            generation_chunk = ChatGenerationChunk(\n                message=default_chunk_class(content=\"\", usage_metadata=usage_metadata),\n                generation_info=base_generation_info,\n            )\n            if self.output_version == \"v1\":\n                generation_chunk.message.content = []\n                generation_chunk.message.response_metadata[\"output_version\"] = \"v1\"\n\n            return generation_chunk\n\n        choice = choices[0]\n        if choice[\"delta\"] is None:\n            return None\n\n        message_chunk = _convert_delta_to_message_chunk(\n            choice[\"delta\"], default_chunk_class\n        )\n        generation_info = {**base_generation_info} if base_generation_info else {}\n\n        if finish_reason := choice.get(\"finish_reason\"):\n            generation_info[\"finish_reason\"] = finish_reason\n            if model_name := chunk.get(\"model\"):\n                generation_info[\"model_name\"] = model_name\n            if system_fingerprint := chunk.get(\"system_fingerprint\"):\n                generation_info[\"system_fingerprint\"] = system_fingerprint\n            if service_tier := chunk.get(\"service_tier\"):\n                generation_info[\"service_tier\"] = service_tier\n\n        logprobs = choice.get(\"logprobs\")\n        if logprobs:\n            generation_info[\"logprobs\"] = logprobs\n\n        if usage_metadata and isinstance(message_chunk, AIMessageChunk):\n            message_chunk.usage_metadata = usage_metadata\n\n        message_chunk.response_metadata[\"model_provider\"] = \"openai\"\n        return ChatGenerationChunk(\n            message=message_chunk, generation_info=generation_info or None\n        )\n\n    def invoke(\n            self,\n            input: LanguageModelInput,\n            config: Optional[RunnableConfig] = None,\n            *,\n            stop: Optional[list[str]] = None,\n            **kwargs: Any,\n    ) -> BaseMessage:\n        config = ensure_config(config)\n        chat_result = cast(\n            \"ChatGeneration\",\n            self.generate_prompt(\n                [self._convert_input(input)],\n                stop=stop,\n                callbacks=config.get(\"callbacks\"),\n                tags=config.get(\"tags\"),\n                metadata=config.get(\"metadata\"),\n                run_name=config.get(\"run_name\"),\n                run_id=config.pop(\"run_id\", None),\n                **kwargs,\n            ).generations[0][0],\n\n        ).message\n\n        self.usage_metadata = chat_result.response_metadata[\n            'token_usage'] if 'token_usage' in chat_result.response_metadata else chat_result.usage_metadata\n        return chat_result\n\n    def upload_file_and_get_url(self, file_stream, file_name):\n        \"\"\"上传文件并获取文件URL\"\"\"\n        base64_video = base64.b64encode(file_stream).decode(\"utf-8\")\n        video_format = get_video_format(file_name)\n        return f'data:{video_format};base64,{base64_video}'\n\n\ndef get_video_format(file_name):\n    extension = file_name.split('.')[-1].lower()\n    format_map = {\n        'mp4': 'video/mp4',\n        'avi': 'video/avi',\n        'mov': 'video/mov',\n        'wmv': 'video/x-ms-wmv'\n    }\n    return format_map.get(extension, 'video/mp4')\n"
  },
  {
    "path": "apps/models_provider/impl/base_stt.py",
    "content": "# coding=utf-8\nfrom abc import abstractmethod\n\nfrom pydantic import BaseModel\n\n\nclass BaseSpeechToText(BaseModel):\n    @abstractmethod\n    def check_auth(self):\n        pass\n\n    @abstractmethod\n    def speech_to_text(self, audio_file):\n        pass\n"
  },
  {
    "path": "apps/models_provider/impl/base_tti.py",
    "content": "# coding=utf-8\nfrom abc import abstractmethod\n\nfrom pydantic import BaseModel\n\n\nclass BaseTextToImage(BaseModel):\n    @abstractmethod\n    def check_auth(self):\n        pass\n\n    @abstractmethod\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        pass\n"
  },
  {
    "path": "apps/models_provider/impl/base_tts.py",
    "content": "# coding=utf-8\nfrom abc import abstractmethod\n\nfrom pydantic import BaseModel\n\n\nclass BaseTextToSpeech(BaseModel):\n    @abstractmethod\n    def check_auth(self):\n        pass\n\n    @abstractmethod\n    def text_to_speech(self, text):\n        pass\n"
  },
  {
    "path": "apps/models_provider/impl/deepseek_model_provider/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\"\"\"\n@Project ：MaxKB\n@File    ：__init__.py.py\n@Author  ：Brian Yang\n@Date    ：5/12/24 7:38 AM\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/deepseek_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/deepseek_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 17:51\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\n\nclass DeepSeekLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass DeepSeekLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key', 'api_base']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField(_('API URL'), required=True,\n                                    default_value='https://api.deepseek.com')\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return DeepSeekLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/deepseek_model_provider/deepseek_model_provider.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\"\"\"\n@Project ：MaxKB \n@File    ：deepseek_model_provider.py\n@Author  ：Brian Yang\n@Date    ：5/12/24 7:40 AM \n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \\\n    ModelInfoManage\nfrom models_provider.impl.deepseek_model_provider.credential.llm import DeepSeekLLMModelCredential\nfrom models_provider.impl.deepseek_model_provider.model.llm import DeepSeekChatModel\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\ndeepseek_llm_model_credential = DeepSeekLLMModelCredential()\ndeepseek_reasoner = ModelInfo('deepseek-reasoner', '', ModelTypeConst.LLM,\n                              deepseek_llm_model_credential, DeepSeekChatModel\n                              )\n\ndeepseek_chat = ModelInfo('deepseek-chat', _('Good at common conversational tasks, supports 32K contexts'),\n                          ModelTypeConst.LLM,\n                          deepseek_llm_model_credential, DeepSeekChatModel\n                          )\n\ndeepseek_coder = ModelInfo('deepseek-coder', _('Good at handling programming tasks, supports 16K contexts'),\n                           ModelTypeConst.LLM,\n                           deepseek_llm_model_credential,\n                           DeepSeekChatModel)\n\nmodel_info_manage = ModelInfoManage.builder().append_model_info(deepseek_reasoner).append_model_info(deepseek_chat).append_model_info(\n    deepseek_coder).append_default_model_info(\n    deepseek_coder).build()\n\n\nclass DeepSeekModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_deepseek_provider', name='DeepSeek', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'deepseek_model_provider', 'icon',\n                         'deepseek_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/deepseek_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/deepseek_model_provider/model/llm.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\"\"\"\n@Project ：MaxKB \n@File    ：llm.py\n@Author  ：Brian Yang\n@Date    ：5/12/24 7:44 AM \n\"\"\"\nimport json\nfrom typing import Dict, Any\n\nfrom langchain_core.language_models import LanguageModelInput\nfrom langchain_core.messages import AIMessage\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass DeepSeekChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n\n        deepseek_chat_open_ai = DeepSeekChatModel(\n            model=model_name,\n            openai_api_base=model_credential.get('api_base') or 'https://api.deepseek.com',\n            openai_api_key=model_credential.get('api_key'),\n            extra_body=optional_params\n        )\n        return deepseek_chat_open_ai\n\n    def _get_request_payload(\n            self,\n            input_: LanguageModelInput,\n            *,\n            stop: list[str] | None = None,\n            **kwargs: Any,\n    ) -> dict:\n        # Get original messages to preserve reasoning_content before base conversion\n        messages = self._convert_input(input_).to_messages()\n        # Store reasoning_content for AIMessages with tool_calls\n        # According to DeepSeek API docs, reasoning_content is REQUIRED when tool_calls\n        # are present during the tool invocation process (within same question/turn).\n        # See: https://api-docs.deepseek.com/guides/thinking_mode#tool-calls\n        reasoning_content_map = {}\n        for i, msg in enumerate(messages):\n            if (\n                    isinstance(msg, AIMessage)\n                    and (msg.tool_calls or msg.invalid_tool_calls)\n                    and (reasoning := msg.additional_kwargs.get(\"reasoning_content\"))\n            ):\n                reasoning_content_map[i] = reasoning\n\n        payload = super()._get_request_payload(input_, stop=stop, **kwargs)\n\n        # Restore reasoning_content for assistant messages with tool_calls\n        # This is required by DeepSeek API - missing it causes 400 error\n        if \"messages\" in payload and reasoning_content_map:\n            for i, message in enumerate(payload[\"messages\"]):\n                if (\n                        i in reasoning_content_map\n                        and message.get(\"role\") == \"assistant\"\n                        and message.get(\"tool_calls\")\n                ):\n                    message[\"reasoning_content\"] = reasoning_content_map[i]\n\n        # Apply DeepSeek-specific message formatting\n        for message in payload[\"messages\"]:\n            if message[\"role\"] == \"tool\" and isinstance(message[\"content\"], list):\n                message[\"content\"] = json.dumps(message[\"content\"])\n            elif message[\"role\"] == \"assistant\" and isinstance(\n                    message[\"content\"], list\n            ):\n                # DeepSeek API expects assistant content to be a string, not a list.\n                # Extract text blocks and join them, or use empty string if none exist.\n                text_parts = [\n                    block.get(\"text\", \"\")\n                    for block in message[\"content\"]\n                    if isinstance(block, dict) and block.get(\"type\") == \"text\"\n                ]\n                message[\"content\"] = \"\".join(text_parts) if text_parts else \"\"\n        return payload\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/3/28 16:25\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 16:45\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass DockerAIEmbeddingModelParams(BaseForm):\n    dimensions = forms.SingleSelect(\n        TooltipLabel(\n            _('Dimensions'),\n            _('')\n        ),\n        required=True,\n        default_value=1024,\n        value_field='value',\n        text_field='label',\n        option_list=[\n            {'label': '1536', 'value': '1536'},\n            {'label': '1024', 'value': '1024'},\n            {'label': '768', 'value': '768'},\n            {'label': '512', 'value': '512'},\n        ]\n    )\n\n\nclass DockerAIEmbeddingCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=True):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return DockerAIEmbeddingModelParams()\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/credential/image.py",
    "content": "# coding=utf-8\nimport base64\nimport os\nfrom typing import Dict\n\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext_lazy as _, gettext\n\n\nclass DockerAIImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass DockerAIImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext('Hello')}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return DockerAIImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 18:32\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\nfrom openai import BadRequestError\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass DockerAILLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass DockerAILLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException) or isinstance(e, BadRequestError):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return DockerAILLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/credential/reranker.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： reranker.py\n    @date：2024/9/9 17:51\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\nfrom langchain_core.documents import Document\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.docker_ai_model_provider.model.reranker import DockerAIReranker\nfrom common.utils.logger import maxkb_logger\n\nclass DockerAIRerankerCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        if not model_type == 'RERANKER':\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n        for key in ['api_base']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model: DockerAIReranker = provider.get_model(model_type, model_name, model_credential)\n            model.compress_documents([Document(page_content=_('Hello'))], _('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model}\n    api_base = forms.TextInputField('API URL', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/credential/stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass DockerAISTTModelParams(BaseForm):\n    language = forms.TextInputField(\n        TooltipLabel(_('language'), _('If not passed, the default value is zh')),\n        required=True,\n        default_value='zh',\n    )\n\nclass DockerAISTTModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n\n        return DockerAISTTModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/credential/tti.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass DockerAITTIModelParams(BaseForm):\n    size = forms.SingleSelect(\n        TooltipLabel(_('Image size'),\n                     _('The image generation endpoint allows you to create raw images based on text prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 or 1792x1024 pixels.')),\n        required=True,\n        default_value='1024x1024',\n        option_list=[\n            {'value': '1024x1024', 'label': '1024x1024'},\n            {'value': '1024x1792', 'label': '1024x1792'},\n            {'value': '1792x1024', 'label': '1792x1024'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    quality = forms.SingleSelect(\n        TooltipLabel(_('Picture quality'), _('''       \nBy default, images are produced in standard quality, but with DALL·E 3 you can set quality: \"hd\" to enhance detail. Square, standard quality images are generated fastest.\n        ''')),\n        required=True,\n        default_value='standard',\n        option_list=[\n            {'value': 'standard', 'label': 'standard'},\n            {'value': 'hd', 'label': 'hd'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    n = forms.SliderField(\n        TooltipLabel(_('Number of pictures'),\n                     _('You can use DALL·E 3 to request 1 image at a time (requesting more images by issuing parallel requests), or use DALL·E 2 with the n parameter to request up to 10 images at a time.')),\n        required=True, default_value=1,\n        _min=1,\n        _max=10,\n        _step=1,\n        precision=0)\n\n\nclass DockerAITextToImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return DockerAITTIModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/credential/tts.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass DockerAITTSModelGeneralParams(BaseForm):\n    # alloy, echo, fable, onyx, nova, shimmer\n    voice = forms.SingleSelect(\n        TooltipLabel('Voice',\n                     _('Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) to find one that suits your desired tone and audience. The current voiceover is optimized for English.')),\n        required=True, default_value='alloy',\n        text_field='value',\n        value_field='value',\n        option_list=[\n            {'text': 'alloy', 'value': 'alloy'},\n            {'text': 'echo', 'value': 'echo'},\n            {'text': 'fable', 'value': 'fable'},\n            {'text': 'onyx', 'value': 'onyx'},\n            {'text': 'nova', 'value': 'nova'},\n            {'text': 'shimmer', 'value': 'shimmer'},\n        ])\n\n\nclass DockerAITTSModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return DockerAITTSModelGeneralParams()\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/docker_ai_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： docker_ai_model_provider.py\n    @date：2024/3/28 16:26\n    @desc:\n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \\\n    ModelTypeConst, ModelInfoManage\nfrom models_provider.impl.docker_ai_model_provider.credential.embedding import DockerAIEmbeddingCredential\nfrom models_provider.impl.docker_ai_model_provider.credential.image import DockerAIImageModelCredential\nfrom models_provider.impl.docker_ai_model_provider.credential.llm import DockerAILLMModelCredential\nfrom models_provider.impl.docker_ai_model_provider.credential.reranker import DockerAIRerankerCredential\nfrom models_provider.impl.docker_ai_model_provider.credential.stt import DockerAISTTModelCredential\nfrom models_provider.impl.docker_ai_model_provider.credential.tti import DockerAITextToImageModelCredential\nfrom models_provider.impl.docker_ai_model_provider.credential.tts import DockerAITTSModelCredential\nfrom models_provider.impl.docker_ai_model_provider.model.embedding import DockerAIEmbeddingModel\nfrom models_provider.impl.docker_ai_model_provider.model.image import DockerAIImage\nfrom models_provider.impl.docker_ai_model_provider.model.llm import DockerAIChatModel\nfrom models_provider.impl.docker_ai_model_provider.model.reranker import DockerAIReranker\nfrom models_provider.impl.docker_ai_model_provider.model.stt import DockerAISpeechToText\nfrom models_provider.impl.docker_ai_model_provider.model.tti import DockerAITextToImage\nfrom models_provider.impl.docker_ai_model_provider.model.tts import DockerAITextToSpeech\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext_lazy as _\n\ndocker_ai_llm_model_credential = DockerAILLMModelCredential()\ndocker_ai_stt_model_credential = DockerAISTTModelCredential()\ndocker_ai_tts_model_credential = DockerAITTSModelCredential()\ndocker_ai_image_model_credential = DockerAIImageModelCredential()\ndocker_ai_tti_model_credential = DockerAITextToImageModelCredential()\nmodel_info_list = [\n    ModelInfo('ai/qwen3-vl:8B', '', ModelTypeConst.LLM,\n              docker_ai_llm_model_credential, DockerAIChatModel\n              ),\n]\nopen_ai_embedding_credential = DockerAIEmbeddingCredential()\nmodel_info_embedding_list = [\n    ModelInfo('ai/qwen3-embedding-vllm', '',\n              ModelTypeConst.EMBEDDING, open_ai_embedding_credential,\n              DockerAIEmbeddingModel),\n]\n\n# model_info_image_list = [\n#     ModelInfo('gpt-4o', _('The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with DockerAI adjustments'),\n#               ModelTypeConst.IMAGE, docker_ai_image_model_credential,\n#               DockerAIImage),\n#     ModelInfo('gpt-4o-mini',\n#               _('The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with DockerAI adjustments'),\n#               ModelTypeConst.IMAGE, docker_ai_image_model_credential,\n#               DockerAIImage),\n# ]\n\n# model_info_tti_list = [\n#     ModelInfo('dall-e-3', '',\n#               ModelTypeConst.TTI, docker_ai_tti_model_credential,\n#               DockerAITextToImage),\n# ]\ndocker_ai_reranker_model_credential = DockerAIRerankerCredential()\nmodel_info_rerank_list = [\n    ModelInfo('ai/qwen3-reranker:0.6B', '',\n              ModelTypeConst.RERANKER, docker_ai_reranker_model_credential,\n              DockerAIReranker),\n]\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_default_model_info(\n        ModelInfo('gpt-3.5-turbo', _('The latest gpt-3.5-turbo, updated with DockerAI adjustments'), ModelTypeConst.LLM,\n                  docker_ai_llm_model_credential, DockerAIChatModel\n                  ))\n    .append_model_info_list(model_info_embedding_list)\n    .append_default_model_info(model_info_embedding_list[0])\n    # .append_model_info_list(model_info_image_list)\n    # .append_default_model_info(model_info_image_list[0])\n    # .append_model_info_list(model_info_tti_list)\n    # .append_default_model_info(model_info_tti_list[0])\n    # .append_default_model_info(ModelInfo('whisper-1', '',\n    #                                      ModelTypeConst.STT, docker_ai_stt_model_credential,\n    #                                      DockerAISpeechToText)\n    #                            )\n    # .append_default_model_info(ModelInfo('tts-1', '',\n    #                                      ModelTypeConst.TTS, docker_ai_tts_model_credential,\n    #                                      DockerAITextToSpeech))\n    .append_model_info_list(model_info_rerank_list)\n    .append_default_model_info(model_info_rerank_list[0])\n    .build()\n)\n\n\nclass DockerModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_docker_ai_provider', name='Docker AI', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'docker_ai_model_provider', 'icon',\n                         'docker_ai_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 17:44\n    @desc:\n\"\"\"\nfrom typing import Dict, List\n\nimport openai\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass DockerAIEmbeddingModel(MaxKBBaseModel):\n    model_name: str\n    optional_params: dict\n\n    def __init__(self, api_key, base_url, model_name: str, optional_params: dict):\n        self.client = openai.OpenAI(api_key=api_key, base_url=base_url).embeddings\n        self.model_name = model_name\n        self.optional_params = optional_params\n\n    def is_cache_model(self):\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return DockerAIEmbeddingModel(\n            api_key=model_credential.get('api_key'),\n            model_name=model_name,\n            base_url=model_credential.get('api_base'),\n            optional_params=optional_params\n        )\n\n    def embed_query(self, text: str):\n        res = self.embed_documents([text])\n        return res[0]\n\n    def embed_documents(\n            self, texts: List[str], chunk_size: int | None = None\n    ) -> List[List[float]]:\n        if len(self.optional_params) > 0:\n            res = self.client.create(\n                input=texts, model=self.model_name, encoding_format=\"float\",\n                **self.optional_params\n            )\n        else:\n            res = self.client.create(input=texts, model=self.model_name, encoding_format=\"float\")\n        return [e.embedding for e in res.data]\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/model/image.py",
    "content": "from typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass DockerAIImage(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return DockerAIImage(\n            model_name=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： llm.py\n    @date：2024/4/18 15:28\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass DockerAIChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        streaming = model_kwargs.get('streaming', True)\n        if 'o1' in model_name:\n            streaming = False\n        chat_open_ai = DockerAIChatModel(\n            model=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            extra_body=optional_params,\n            streaming=streaming,\n            custom_get_token_ids=custom_get_token_ids\n        )\n        return chat_open_ai\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        try:\n            return super().get_num_tokens_from_messages(messages)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n    def get_num_tokens(self, text: str) -> int:\n        try:\n            return super().get_num_tokens(text)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/model/reranker.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： siliconcloud_reranker.py\n    @date：2024/9/10 9:45\n    @desc: SiliconCloud 文档重排封装\n\"\"\"\nimport json\nfrom typing import Sequence, Optional, Any, Dict\nimport requests\n\nfrom langchain_core.callbacks import Callbacks\nfrom langchain_core.documents import BaseDocumentCompressor, Document\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass DockerAIReranker(MaxKBBaseModel, BaseDocumentCompressor):\n    api_base: Optional[str]\n    model: Optional[str]\n\n    top_n: Optional[int] = 3  # 取前 N 个最相关的结果\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return DockerAIReranker(\n            api_base=model_credential.get('api_base'),\n            model=model_name,\n            top_n=model_kwargs.get('top_n', 3)\n        )\n\n    def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \\\n            Sequence[Document]:\n        if not documents:\n            return []\n\n        # 预处理文本\n        texts = [doc.page_content for doc in documents]\n\n        headers = {\n            \"Content-Type\": \"application/json\"\n        }\n        payload = {\n            \"model\": self.model,\n            \"query\": query,\n            \"documents\": texts,\n            \"top_n\": self.top_n,\n        }\n\n        response = requests.post(f\"{self.api_base}/rerank\", data=json.dumps(payload), headers=headers)\n\n        if response.status_code != 200:\n            raise RuntimeError(f\"Docker AI API 请求失败: {response.text}\")\n\n        res = response.json()\n\n        # 解析返回结果\n        return [\n            Document(\n                page_content=payload['documents'][item.get('index')],\n                metadata={'relevance_score': item.get('relevance_score')}\n            )\n            for item in res.get('results', [])\n        ]\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/model/stt.py",
    "content": "import asyncio\nimport io\nfrom typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass DockerAISpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {}\n        if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None:\n            optional_params['max_tokens'] = model_kwargs['max_tokens']\n        if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None:\n            optional_params['temperature'] = model_kwargs['temperature']\n        return DockerAISpeechToText(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            params = model_kwargs,\n            **optional_params,\n        )\n\n    def check_auth(self):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        response_list = client.models.with_raw_response.list()\n        # print(response_list)\n\n    def speech_to_text(self, audio_file):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        audio_data = audio_file.read()\n        buffer = io.BytesIO(audio_data)\n        buffer.name = \"file.mp3\"  # this is the important line\n\n        filter_params = {k: v for k,v in self.params.items() if k not in {'model_id','use_local','streaming'}}\n        transcription_params = {\n            'model': self.model,\n            'file': buffer,\n            'language': 'zh'\n        }\n\n        res = client.audio.transcriptions.create(**transcription_params,extra_body=filter_params)\n        return res.text\n\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/model/tti.py",
    "content": "from typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass DockerAITextToImage(MaxKBBaseModel, BaseTextToImage):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return DockerAITextToImage(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        chat = OpenAI(api_key=self.api_key, base_url=self.api_base)\n        response_list = chat.models.with_raw_response.list()\n\n        # self.generate_image('生成一个小猫图片')\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        chat = OpenAI(api_key=self.api_key, base_url=self.api_base)\n        res = chat.images.generate(model=self.model, prompt=prompt, **self.params)\n        file_urls = []\n        try:\n            for content in res.data:\n                if content.url:\n                    file_urls.append(content.url)\n                elif content.b64_json:\n                    file_urls.append(content.b64_json)\n            return file_urls\n        except Exception as e:\n            raise e\n"
  },
  {
    "path": "apps/models_provider/impl/docker_ai_model_provider/model/tts.py",
    "content": "from typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom common.utils.common import _remove_empty_lines\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tts import BaseTextToSpeech\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass DockerAITextToSpeech(MaxKBBaseModel, BaseTextToSpeech):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'voice': 'alloy'}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return DockerAITextToSpeech(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        response_list = client.models.with_raw_response.list()\n        # print(response_list)\n\n    def text_to_speech(self, text):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        text = _remove_empty_lines(text)\n        with client.audio.speech.with_streaming_response.create(\n                model=self.model,\n                input=text,\n                **self.params\n        ) as response:\n            return response.read()\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\"\"\"\n@Project ：MaxKB \n@File    ：__init__.py.py\n@Author  ：Brian Yang\n@Date    ：5/13/24 7:40 AM \n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 16:45\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass GeminiEmbeddingCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=True):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_key = forms.PasswordInputField('API Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/credential/image.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass GeminiImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass GeminiImageModelCredential(BaseForm, BaseModelCredential):\n    api_key = forms.PasswordInputField('API Key', required=True)\n    base_url = forms.TextInputField('Base URL', required=True, default_value='https://generativelanguage.googleapis.com')\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key', 'base_url']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext('Hello')}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return GeminiImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 17:57\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass GeminiLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass GeminiLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key', 'base_url']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_key = forms.PasswordInputField('API Key', required=True)\n    base_url = forms.TextInputField('Base URL', required=True, default_value='https://generativelanguage.googleapis.com')\n\n    def get_model_params_setting_form(self, model_name):\n        return GeminiLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/credential/stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass GeminiSTTModelCredential(BaseForm, BaseModelCredential):\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        pass\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/credential/tti.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\n\nclass GeminiTTIModelParams(BaseForm):\n    pass\n\n\nclass GeminiTextToImageModelCredential(BaseForm, BaseModelCredential):\n    base_url = forms.TextInputField('Base Url', required=True,\n                                    default_value='https://generativelanguage.googleapis.com')\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['base_url', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return GeminiTTIModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/gemini_model_provider.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\"\"\"\n@Project ：MaxKB \n@File    ：gemini_model_provider.py\n@Author  ：Brian Yang\n@Date    ：5/13/24 7:47 AM \n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \\\n    ModelInfoManage\nfrom models_provider.impl.gemini_model_provider.credential.embedding import GeminiEmbeddingCredential\nfrom models_provider.impl.gemini_model_provider.credential.image import GeminiImageModelCredential\nfrom models_provider.impl.gemini_model_provider.credential.llm import GeminiLLMModelCredential\nfrom models_provider.impl.gemini_model_provider.credential.stt import GeminiSTTModelCredential\nfrom models_provider.impl.gemini_model_provider.credential.tti import GeminiTextToImageModelCredential\nfrom models_provider.impl.gemini_model_provider.model.embedding import GeminiEmbeddingModel\nfrom models_provider.impl.gemini_model_provider.model.image import GeminiImage\nfrom models_provider.impl.gemini_model_provider.model.llm import GeminiChatModel\nfrom models_provider.impl.gemini_model_provider.model.stt import GeminiSpeechToText\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\nfrom models_provider.impl.gemini_model_provider.model.tti import GeminiTextToImage\n\ngemini_llm_model_credential = GeminiLLMModelCredential()\ngemini_image_model_credential = GeminiImageModelCredential()\ngemini_stt_model_credential = GeminiSTTModelCredential()\ngemini_embedding_model_credential = GeminiEmbeddingCredential()\ngemini_tti_model_credential = GeminiTextToImageModelCredential()\n\nmodel_info_list = [\n    ModelInfo('gemini-1.0-pro', _('Latest Gemini 1.0 Pro model, updated with Google update'),\n              ModelTypeConst.LLM,\n              gemini_llm_model_credential,\n              GeminiChatModel),\n    ModelInfo('gemini-1.0-pro-vision', _('Latest Gemini 1.0 Pro Vision model, updated with Google update'),\n              ModelTypeConst.LLM,\n              gemini_llm_model_credential,\n              GeminiChatModel),\n]\n\nmodel_image_info_list = [\n    ModelInfo('gemini-1.5-flash', _('Latest Gemini 1.5 Flash model, updated with Google updates'),\n              ModelTypeConst.IMAGE,\n              gemini_image_model_credential,\n              GeminiImage),\n    ModelInfo('gemini-1.5-pro', _('Latest Gemini 1.5 Flash model, updated with Google updates'),\n              ModelTypeConst.IMAGE,\n              gemini_image_model_credential,\n              GeminiImage),\n]\n\nmodel_stt_info_list = [\n    ModelInfo('gemini-1.5-flash', _('Latest Gemini 1.5 Flash model, updated with Google updates'),\n              ModelTypeConst.STT,\n              gemini_stt_model_credential,\n              GeminiSpeechToText),\n    ModelInfo('gemini-1.5-pro', _('Latest Gemini 1.5 Flash model, updated with Google updates'),\n              ModelTypeConst.STT,\n              gemini_stt_model_credential,\n              GeminiSpeechToText),\n]\n\nmodel_embedding_info_list = [\n    ModelInfo('models/embedding-001', '',\n              ModelTypeConst.EMBEDDING,\n              gemini_embedding_model_credential,\n              GeminiEmbeddingModel),\n    ModelInfo('models/text-embedding-004', '',\n              ModelTypeConst.EMBEDDING,\n              gemini_embedding_model_credential,\n              GeminiEmbeddingModel),\n]\n\nmodel_tti_info_list = [\n    ModelInfo('gemini-3.1-flash-image-preview', \"\",\n              ModelTypeConst.TTI,\n              gemini_tti_model_credential,\n              GeminiTextToImage)\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_model_info_list(model_image_info_list)\n    .append_model_info_list(model_stt_info_list)\n    .append_model_info_list(model_embedding_info_list)\n    .append_model_info_list(model_tti_info_list)\n    .append_default_model_info(model_info_list[0])\n    .append_default_model_info(model_image_info_list[0])\n    .append_default_model_info(model_stt_info_list[0])\n    .append_default_model_info(model_embedding_info_list[0])\n    .append_default_model_info(model_tti_info_list[0])\n    .build()\n)\n\n\nclass GeminiModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_gemini_provider', name='Gemini', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'gemini_model_provider', 'icon',\n                         'gemini_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 17:44\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom langchain_google_genai import GoogleGenerativeAIEmbeddings\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass GeminiEmbeddingModel(MaxKBBaseModel, GoogleGenerativeAIEmbeddings):\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return GeminiEmbeddingModel(\n            google_api_key=model_credential.get('api_key'),\n            model=model_name,\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/model/image.py",
    "content": "from typing import Dict\n\nfrom langchain_google_genai import ChatGoogleGenerativeAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass GeminiImage(MaxKBBaseModel, ChatGoogleGenerativeAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        base_url = model_credential.get('base_url', \"https://generativelanguage.googleapis.com\")\n        if base_url:\n            optional_params.setdefault(\"model_kwargs\", {})\n            optional_params[\"model_kwargs\"][\"http_options\"] = {\"base_url\": base_url}\n        return GeminiImage(\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/model/llm.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\"\"\"\n@Project ：MaxKB \n@File    ：llm.py\n@Author  ：Brian Yang\n@Date    ：5/13/24 7:40 AM \n\"\"\"\nfrom typing import List, Dict, Optional, Any\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\nfrom langchain_google_genai import ChatGoogleGenerativeAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass GeminiChatModel(MaxKBBaseModel, ChatGoogleGenerativeAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        base_url = model_credential.get('base_url', \"https://generativelanguage.googleapis.com\")\n        if base_url:\n            optional_params.setdefault(\"model_kwargs\", {})\n            optional_params[\"model_kwargs\"][\"http_options\"] = {\"base_url\": base_url}\n        gemini_chat = GeminiChatModel(\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            **optional_params\n        )\n        return gemini_chat\n\n    def get_last_generation_info(self) -> Optional[Dict[str, Any]]:\n        return self.__dict__.get('_last_generation_info')\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        try:\n            return self.get_last_generation_info().get('input_tokens', 0)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n    def get_num_tokens(self, text: str) -> int:\n        try:\n            return self.get_last_generation_info().get('output_tokens', 0)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/model/stt.py",
    "content": "from typing import Dict\n\nfrom django.utils.translation import gettext as _\nfrom langchain_core.messages import HumanMessage\nfrom langchain_google_genai import ChatGoogleGenerativeAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass GeminiSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    api_key: str\n    model: str\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {}\n        if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None:\n            optional_params['max_tokens'] = model_kwargs['max_tokens']\n        if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None:\n            optional_params['temperature'] = model_kwargs['temperature']\n        return GeminiSpeechToText(\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        client = ChatGoogleGenerativeAI(\n            model=self.model,\n            google_api_key=self.api_key\n        )\n        response_list = client.invoke(_('Hello'))\n        # print(response_list)\n\n    def speech_to_text(self, audio_file):\n        client = ChatGoogleGenerativeAI(\n            model=self.model,\n            google_api_key=self.api_key\n        )\n        audio_data = audio_file.read()\n        system_instruction = \"\"\"You are a professional speech-to-text assistant. Your task is to:\n1. Transcribe the audio content accurately into text\n2. Output ONLY the transcribed text without any additional comments...\"\"\"\n\n        msg = HumanMessage(content=[\n            {'type': 'text', 'text': system_instruction},\n            {\"type\": \"media\", 'mime_type': 'audio/mp3', \"data\": audio_data}\n        ])\n        res = client.invoke([msg])\n        if isinstance(res.content, list):\n            for item in res.content:\n                if isinstance(item, dict) and 'text' in item:\n                    return item['text'].strip()\n                elif hasattr(item, 'text'):\n                    return item.text.strip()\n            return ''\n        elif isinstance(res.content, dict):\n            return res.content.get('text', '').strip()\n        else:\n            return str(res.content).strip() if res.content else ''\n\n"
  },
  {
    "path": "apps/models_provider/impl/gemini_model_provider/model/tti.py",
    "content": "import base64\nfrom typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass GeminiTextToImage(MaxKBBaseModel, BaseTextToImage):\n    base_url: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.base_url = kwargs.get('base_url')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return GeminiTextToImage(\n            model=model_name,\n            base_url=model_credential.get('base_url', \"https://generativelanguage.googleapis.com\"),\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        return True\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        from google import genai\n        from google.genai import types\n        from PIL import Image\n        file_urls = []\n        client = genai.Client(api_key=self.api_key, http_options={\"base_url\": self.base_url}, **self.params)\n        if self.model.startswith('imagen'):\n            config = types.GenerateImagesConfig(**self.params)\n\n            # 如果有 negative_prompt 就加入\n            if negative_prompt:\n                config.negative_prompt = negative_prompt\n            response = client.models.generate_images(\n                model=self.model,\n                prompt=prompt,\n                config=config\n            )\n            for generated_image in response.generated_images:\n                img_base64 = base64.b64encode(generated_image.image.image_bytes).decode(\"utf-8\")\n                file_urls.append(f'data:{generated_image.image.mime_type};base64,{img_base64}')\n        else:\n            config = types.GenerateContentConfig(**self.params)\n            if negative_prompt:\n                config.negative_prompt = negative_prompt\n            response = client.models.generate_content(\n                model=self.model,\n                contents=[prompt],\n                config=config\n            )\n\n            for part in response.parts:\n                if part.text is not None:\n                    print(part.text)\n                elif part.inline_data is not None:\n                    image_bytes = part.inline_data.data\n                    img_base64 = base64.b64encode(image_bytes).decode(\"utf-8\")\n                    file_urls.append(f'data:{part.inline_data.mime_type};base64,{img_base64}')\n\n        return file_urls\n"
  },
  {
    "path": "apps/models_provider/impl/kimi_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2023/10/31 17:16\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/kimi_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/kimi_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 18:06\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass KimiLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.3,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=1024,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass KimiLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value, gettext(\n                    'Verification failed, please check whether the parameters are correct: {error}').format(\n                    error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return KimiLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/kimi_model_provider/kimi_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： kimi_model_provider.py\n    @date：2024/3/28 16:26\n    @desc:\n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \\\n    ModelTypeConst, ModelInfoManage\nfrom models_provider.impl.kimi_model_provider.credential.llm import KimiLLMModelCredential\nfrom models_provider.impl.kimi_model_provider.model.llm import KimiChatModel\nfrom maxkb.conf import PROJECT_DIR\n\nkimi_llm_model_credential = KimiLLMModelCredential()\n\nmoonshot_v1_8k = ModelInfo('moonshot-v1-8k', '', ModelTypeConst.LLM, kimi_llm_model_credential,\n                           KimiChatModel)\nmoonshot_v1_32k = ModelInfo('moonshot-v1-32k', '', ModelTypeConst.LLM, kimi_llm_model_credential,\n                            KimiChatModel)\nmoonshot_v1_128k = ModelInfo('moonshot-v1-128k', '', ModelTypeConst.LLM, kimi_llm_model_credential,\n                             KimiChatModel)\n\nmodel_info_manage = ModelInfoManage.builder().append_model_info(moonshot_v1_8k).append_model_info(\n    moonshot_v1_32k).append_default_model_info(moonshot_v1_128k).append_default_model_info(moonshot_v1_8k).build()\n\n\nclass KimiModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_dialogue_number(self):\n        return 3\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_kimi_provider', name='Kimi', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'kimi_model_provider', 'icon',\n                         'kimi_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/kimi_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/kimi_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： llm.py\n    @date：2023/11/10 17:45\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass KimiChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n\n        kimi_chat_open_ai = KimiChatModel(\n            openai_api_base=model_credential['api_base'],\n            openai_api_key=model_credential['api_key'],\n            model=model_name,\n            extra_body=optional_params,\n        )\n        return kimi_chat_open_ai\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py\n    @date：2024/7/10 17:48\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/credential/embedding/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/11/7 14:02\n    @desc:\n\"\"\"\nimport os\n\nif os.environ.get('SERVER_NAME', 'web') == 'local_model':\n    from .model import *\nelse:\n    from .web import *\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/credential/embedding/model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： model.py.py\n    @date：2025/11/7 14:02\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.local_model_provider.model.embedding import LocalEmbedding\nfrom common.utils.logger import maxkb_logger\n\nclass LocalEmbeddingCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        if not model_type == 'EMBEDDING':\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n        for key in ['cache_folder']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model: LocalEmbedding = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(gettext('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return model\n\n    cache_folder = forms.TextInputField(_('Model catalog'), required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/credential/embedding/web.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： web.py\n    @date：2025/11/7 14:03\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nimport requests\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common import forms\nfrom common.forms import BaseForm\nfrom maxkb.const import CONFIG\nfrom models_provider.base_model_provider import BaseModelCredential\n\n\nclass LocalEmbeddingCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        bind = f'{CONFIG.get(\"LOCAL_MODEL_HOST\")}:{CONFIG.get(\"LOCAL_MODEL_PORT\")}'\n        prefix = CONFIG.get_admin_path()\n        res = requests.post(\n            f'{CONFIG.get(\"LOCAL_MODEL_PROTOCOL\")}://{bind}{prefix}/api/model/validate',\n            json={'model_name': model_name, 'model_type': model_type, 'model_credential': model_credential})\n        result = res.json()\n        if result.get('code', 500) == 200:\n            return result.get('data')\n        raise Exception(result.get('message'))\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return model\n\n    cache_folder = forms.TextInputField(_('Model catalog'), required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/credential/reranker/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/11/7 14:22\n    @desc:\n\"\"\"\nimport os\n\nif os.environ.get('SERVER_NAME', 'web') == 'local_model':\n    from .model import *\nelse:\n    from .web import *\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/credential/reranker/model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： model.py\n    @date：2025/11/7 14:23\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom langchain_core.documents import Document\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.local_model_provider.model.reranker import LocalReranker\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom common.utils.logger import maxkb_logger\n\nclass LocalRerankerCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        if not model_type == 'RERANKER':\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n        for key in ['cache_dir']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model: LocalReranker = provider.get_model(model_type, model_name, model_credential)\n            model.compress_documents([Document(page_content=gettext('Hello'))], gettext('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return model\n\n    cache_dir = forms.TextInputField(_('Model catalog'), required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/credential/reranker/web.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： web.py\n    @date：2025/11/7 14:23\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nimport requests\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common import forms\nfrom common.forms import BaseForm\nfrom maxkb.const import CONFIG\nfrom models_provider.base_model_provider import BaseModelCredential\n\n\nclass LocalRerankerCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        bind = f'{CONFIG.get(\"LOCAL_MODEL_HOST\")}:{CONFIG.get(\"LOCAL_MODEL_PORT\")}'\n        prefix = CONFIG.get_admin_path()\n        res = requests.post(\n            f'{CONFIG.get(\"LOCAL_MODEL_PROTOCOL\")}://{bind}{prefix}/api/model/validate',\n            json={'model_name': model_name, 'model_type': model_type, 'model_credential': model_credential})\n        result = res.json()\n        if result.get('code', 500) == 200:\n            return result.get('data')\n        raise Exception(result.get('message'))\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return model\n\n    cache_dir = forms.TextInputField(_('Model catalog'), required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/local_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： zhipu_model_provider.py\n    @date：2024/04/19 13:5\n    @desc:\n\"\"\"\nimport os\n\nfrom django.utils.translation import gettext as _\n\nfrom common.utils.common import get_file_content\nfrom maxkb.conf import PROJECT_DIR\nfrom models_provider.base_model_provider import ModelProvideInfo, ModelTypeConst, ModelInfo, IModelProvider, \\\n    ModelInfoManage\nfrom models_provider.impl.local_model_provider.credential.embedding import LocalEmbeddingCredential\nfrom models_provider.impl.local_model_provider.credential.reranker import LocalRerankerCredential\nfrom models_provider.impl.local_model_provider.model.embedding import LocalEmbedding\nfrom models_provider.impl.local_model_provider.model.reranker import LocalReranker\n\nembedding_text2vec_base_chinese = ModelInfo('shibing624/text2vec-base-chinese', '', ModelTypeConst.EMBEDDING,\n                                            LocalEmbeddingCredential(), LocalEmbedding)\nbge_reranker_v2_m3 = ModelInfo('BAAI/bge-reranker-v2-m3', '', ModelTypeConst.RERANKER,\n                               LocalRerankerCredential(), LocalReranker)\n\nmodel_info_manage = (ModelInfoManage.builder().append_model_info(embedding_text2vec_base_chinese)\n                     .append_default_model_info(embedding_text2vec_base_chinese)\n                     .append_model_info(bge_reranker_v2_m3)\n                     .append_default_model_info(bge_reranker_v2_m3)\n                     .build())\n\n\nclass LocalModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_local_provider', name=_('local model'), icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'local_model_provider', 'icon',\n                         'local_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/model/embedding/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py\n    @date：2025/11/5 15:24\n    @desc:\n\"\"\"\nimport os\n\nif os.environ.get('SERVER_NAME', 'web') == 'local_model':\n    from .model import *\nelse:\n    from .web import *\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/model/embedding/model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： model.py\n    @date：2025/11/5 15:26\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom langchain_huggingface import HuggingFaceEmbeddings\n\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\nmax_retries = 3\n\n\nclass LocalEmbedding(MaxKBBaseModel, HuggingFaceEmbeddings):\n    @staticmethod\n    def is_cache_model():\n        return True\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        for attempt in range(max_retries):\n            try:\n                embedding = LocalEmbedding(model_name=model_name, cache_folder=model_credential.get('cache_folder'),\n                                           model_kwargs={'device': model_credential.get('device')},\n                                           encode_kwargs={'normalize_embeddings': True}\n                                           )\n                # 测试一下是否真的能用\n                embedding.embed_query(\"test\")\n                return embedding\n            except Exception as e:\n                if 'meta tensor' in str(e).lower() and attempt < max_retries - 1:\n                    maxkb_logger.warning(\n                        f\"Test failed with meta tensor error, retrying... (attempt {attempt + 1}/{max_retries})\")\n                    import time\n                    time.sleep(1)\n                    continue\n                raise e\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/model/embedding/web.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： web.py\n    @date：2025/11/5 15:24\n    @desc:\n\"\"\"\n\nfrom typing import Dict, List\n\nimport requests\nfrom anthropic import BaseModel\nfrom langchain_core.embeddings import Embeddings\n\nfrom maxkb.const import CONFIG\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass LocalEmbedding(MaxKBBaseModel, BaseModel, Embeddings):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.model_id = kwargs.get('model_id', None)\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return LocalEmbedding(model_name=model_name, cache_folder=model_credential.get('cache_folder'),\n                              model_kwargs={'device': model_credential.get('device')},\n                              encode_kwargs={'normalize_embeddings': True},\n                              **model_kwargs)\n\n    model_id: str = None\n\n    def embed_query(self, text: str) -> List[float]:\n        bind = f'{CONFIG.get(\"LOCAL_MODEL_HOST\")}:{CONFIG.get(\"LOCAL_MODEL_PORT\")}'\n        prefix = CONFIG.get_admin_path()\n        res = requests.post(\n            f'{CONFIG.get(\"LOCAL_MODEL_PROTOCOL\")}://{bind}{prefix}/api/model/{self.model_id}/embed_query',\n            {'text': text})\n        result = res.json()\n        if result.get('code', 500) == 200:\n            return result.get('data')\n        raise Exception(result.get('message'))\n\n    def embed_documents(self, texts: List[str]) -> List[List[float]]:\n        bind = f'{CONFIG.get(\"LOCAL_MODEL_HOST\")}:{CONFIG.get(\"LOCAL_MODEL_PORT\")}'\n        prefix = CONFIG.get_admin_path()\n        res = requests.post(\n            f'{CONFIG.get(\"LOCAL_MODEL_PROTOCOL\")}://{bind}/{prefix}/api/model/{self.model_id}/embed_documents',\n            {'texts': texts})\n        result = res.json()\n        if result.get('code', 500) == 200:\n            return result.get('data')\n        raise Exception(result.get('message'))\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/model/reranker/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/11/5 15:30\n    @desc:\n\"\"\"\nimport os\n\nif os.environ.get('SERVER_NAME', 'web') == 'local_model':\n    from .model import *\nelse:\n    from .web import *\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/model/reranker/model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： model.py\n    @date：2025/11/5 15:30\n    @desc:\n\"\"\"\n\nfrom typing import Sequence, Optional, Dict, Any\n\nfrom langchain_core.callbacks import Callbacks\nfrom langchain_core.documents import Document, BaseDocumentCompressor\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass LocalReranker(MaxKBBaseModel, BaseDocumentCompressor):\n    client: Any = None\n    tokenizer: Any = None\n    model: Optional[str] = None\n    cache_dir: Optional[str] = None\n    model_kwargs: Any = {}\n\n    def __init__(self, model_name, cache_dir=None, **model_kwargs):\n        super().__init__()\n        from transformers import AutoModelForSequenceClassification, AutoTokenizer\n        self.model = model_name\n        self.cache_dir = cache_dir\n        self.model_kwargs = model_kwargs\n        self.client = AutoModelForSequenceClassification.from_pretrained(self.model, cache_dir=self.cache_dir)\n        self.tokenizer = AutoTokenizer.from_pretrained(self.model, cache_dir=self.cache_dir)\n        self.client = self.client.to(self.model_kwargs.get('device', 'cpu'))\n        self.client.eval()\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return LocalReranker(model_name, cache_dir=model_credential.get('cache_dir'))\n\n    def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \\\n            Sequence[Document]:\n        if documents is None or len(documents) == 0:\n            return []\n        import torch\n        with torch.no_grad():\n            inputs = self.tokenizer([[query, document.page_content] for document in documents], padding=True,\n                                    truncation=True, return_tensors='pt', max_length=512)\n            scores = [torch.sigmoid(s).float().item() for s in\n                      self.client(**inputs, return_dict=True).logits.view(-1, ).float()]\n            result = [Document(page_content=documents[index].page_content, metadata={'relevance_score': scores[index]})\n                      for index\n                      in range(len(documents))]\n            result.sort(key=lambda row: row.metadata.get('relevance_score'), reverse=True)\n            return result\n"
  },
  {
    "path": "apps/models_provider/impl/local_model_provider/model/reranker/web.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： web.py\n    @date：2025/11/5 15:30\n    @desc:\n\"\"\"\nfrom typing import Sequence, Optional, Dict\n\nimport requests\nfrom anthropic import BaseModel\nfrom langchain_core.callbacks import Callbacks\nfrom langchain_core.documents import Document, BaseDocumentCompressor\n\nfrom maxkb.const import CONFIG\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass LocalReranker(MaxKBBaseModel, BaseModel, BaseDocumentCompressor):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return LocalReranker(model_type=model_type, model_name=model_name, model_credential=model_credential,\n                             **model_kwargs)\n\n    model_id: str = None\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.model_id = kwargs.get('model_id', None)\n\n    def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \\\n            Sequence[Document]:\n        if documents is None or len(documents) == 0:\n            return []\n        prefix = CONFIG.get_admin_path()\n        bind = f'{CONFIG.get(\"LOCAL_MODEL_HOST\")}:{CONFIG.get(\"LOCAL_MODEL_PORT\")}'\n        res = requests.post(\n            f'{CONFIG.get(\"LOCAL_MODEL_PROTOCOL\")}://{bind}{prefix}/api/model/{self.model_id}/compress_documents',\n            json={'documents': [{'page_content': document.page_content, 'metadata': document.metadata} for document in\n                                documents], 'query': query}, headers={'Content-Type': 'application/json'})\n        result = res.json()\n        if result.get('code', 500) == 200:\n            return [Document(page_content=document.get('page_content'), metadata=document.get('metadata')) for document\n                    in result.get('data')]\n        raise Exception(result.get('message'))\n"
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/3/5 17:20\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 15:10\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.local_model_provider.model.embedding import LocalEmbedding\n\n\nclass OllamaEmbeddingModelCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n        try:\n            model_list = provider.get_base_model_list(model_credential.get('api_base'))\n        except Exception as e:\n            raise AppApiException(ValidCode.valid_error.value, _('API domain name is invalid'))\n        exist = [model for model in (model_list.get('models') if model_list.get('models') is not None else []) if\n                 model.get('model') == model_name or model.get('model').replace(\":latest\", \"\") == model_name]\n        if len(exist) == 0:\n            raise AppApiException(ValidCode.model_not_fount,\n                                  _('The model does not exist, please download the model first'))\n        model: LocalEmbedding = provider.get_model(model_type, model_name, model_credential)\n        model.embed_query(_('Hello'))\n        return True\n\n    def encryption_dict(self, model_info: Dict[str, object]):\n        return model_info\n\n    def build_model(self, model_info: Dict[str, object]):\n        for key in ['model']:\n            if key not in model_info:\n                raise AppApiException(500, _('{key}  is required').format(key=key))\n        return self\n\n    api_base = forms.TextInputField('API URL', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/credential/image.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext_lazy as _, gettext\n\n\nclass OllamaImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass OllamaImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n        try:\n            model_list = provider.get_base_model_list(model_credential.get('api_base'))\n        except Exception as e:\n            raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid'))\n        exist = [model for model in (model_list.get('models') if model_list.get('models') is not None else []) if\n                 model.get('model') == model_name or model.get('model').replace(\":latest\", \"\") == model_name]\n        if len(exist) == 0:\n            raise AppApiException(ValidCode.model_not_fount,\n                                  gettext('The model does not exist, please download the model first'))\n\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return OllamaImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 18:19\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass OllamaLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.3,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    num_predict = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=1024,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass OllamaLLMModelCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n        try:\n            model_list = provider.get_base_model_list(model_credential.get('api_base'))\n        except Exception as e:\n            raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid'))\n        exist = [model for model in (model_list.get('models') if model_list.get('models') is not None else []) if\n                 model.get('model') == model_name or model.get('model').replace(\":latest\", \"\") == model_name]\n        if len(exist) == 0:\n            raise AppApiException(ValidCode.model_not_fount,\n                                  gettext('The model does not exist, please download the model first'))\n        return True\n\n    def encryption_dict(self, model_info: Dict[str, object]):\n        return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))}\n\n    def build_model(self, model_info: Dict[str, object]):\n        for key in ['api_key', 'model']:\n            if key not in model_info:\n                raise AppApiException(500, gettext('{key}  is required').format(key=key))\n        self.api_key = model_info.get('api_key')\n        return self\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return OllamaLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/credential/reranker.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 15:10\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.ollama_model_provider.model.reranker import OllamaReranker\nfrom langchain_core.documents import BaseDocumentCompressor, Document\nfrom django.utils.translation import gettext_lazy as _, gettext\n\n\nclass OllamaReRankModelCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        if not model_type == 'RERANKER':\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n        try:\n            model_list = provider.get_base_model_list(model_credential.get('api_base'))\n        except Exception as e:\n            raise AppApiException(ValidCode.valid_error.value, _('API domain name is invalid'))\n        exist = [model for model in (model_list.get('models') if model_list.get('models') is not None else []) if\n                 model.get('model') == model_name or model.get('model').replace(\":latest\", \"\") == model_name]\n        if len(exist) == 0:\n            raise AppApiException(ValidCode.model_not_fount,\n                                  _('The model does not exist, please download the model first'))\n\n        try:\n            model: OllamaReranker = provider.get_model(model_type, model_name, model_credential)\n            model.compress_documents([Document(page_content=gettext('Hello'))], gettext('Hello'))\n        except Exception as e:\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model_info: Dict[str, object]):\n        return model_info\n\n    def build_model(self, model_info: Dict[str, object]):\n        for key in ['model']:\n            if key not in model_info:\n                raise AppApiException(500, _('{key}  is required').format(key=key))\n        return self\n\n    api_base = forms.TextInputField('API URL', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 15:02\n    @desc:\n\"\"\"\nfrom typing import Dict, List\n\nfrom langchain_ollama import OllamaEmbeddings\n\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass OllamaEmbedding(MaxKBBaseModel, OllamaEmbeddings):\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return OllamaEmbedding(\n            model=model_name,\n            base_url=model_credential.get('api_base'),\n        )\n\n    def embed_documents(self, texts: List[str]) -> List[List[float]]:\n        \"\"\"Embed documents using an Ollama deployed embedding model.\n\n        Args:\n            texts: The list of texts to embed.\n\n        Returns:\n            List of embeddings, one for each text.\n        \"\"\"\n        return self._client.embed(\n            self.model, texts, options=self._default_params, keep_alive=self.keep_alive\n        )[\"embeddings\"]\n\n    def embed_query(self, text: str) -> List[float]:\n        \"\"\"Embed a query using a Ollama deployed embedding model.\n\n        Args:\n            text: The text to embed.\n\n        Returns:\n            Embeddings for the text.\n        \"\"\"\n        return self.embed_documents([text])[0]\n"
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/model/image.py",
    "content": "from typing import Dict\nfrom urllib.parse import urlparse, ParseResult\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\ndef get_base_url(url: str):\n    parse = urlparse(url)\n    result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='',\n                             query='',\n                             fragment='').geturl()\n    return result_url[:-1] if result_url.endswith(\"/\") else result_url\n\n\nclass OllamaImage(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        api_base = model_credential.get('api_base', '')\n        base_url = get_base_url(api_base)\n        base_url = base_url if base_url.endswith('/v1') else (base_url + '/v1')\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return OllamaImage(\n            model_name=model_name,\n            openai_api_base=base_url,\n            openai_api_key=model_credential.get('api_key'),\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： llm.py\n    @date：2024/3/6 11:48\n    @desc:\n\"\"\"\nfrom typing import List, Dict\nfrom urllib.parse import urlparse, ParseResult\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\nfrom langchain_ollama.chat_models import ChatOllama\n\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\ndef get_base_url(url: str):\n    parse = urlparse(url)\n    result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='',\n                             query='',\n                             fragment='').geturl()\n    return result_url[:-1] if result_url.endswith(\"/\") else result_url\n\n\nclass OllamaChatModel(MaxKBBaseModel, ChatOllama):\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        api_base = model_credential.get('api_base', '')\n        base_url = get_base_url(api_base)\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n\n        return OllamaChatModel(model=model_name, base_url=base_url,\n                               stream=True, **optional_params)\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        tokenizer = TokenizerManage.get_tokenizer()\n        return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n    def get_num_tokens(self, text: str) -> int:\n        tokenizer = TokenizerManage.get_tokenizer()\n        return len(tokenizer.encode(text))\n"
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/model/reranker.py",
    "content": "from typing import Sequence, Optional, Dict\n\nfrom langchain_ollama import OllamaEmbeddings\nfrom langchain_core.callbacks import Callbacks\nfrom langchain_core.documents import Document\nfrom pydantic import BaseModel, Field\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass OllamaReranker(MaxKBBaseModel, OllamaEmbeddings, BaseModel):\n    top_n: Optional[int] = Field(3, description=\"Number of top documents to return\")\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return OllamaReranker(\n            model=model_name,\n            base_url=model_credential.get('api_base'),\n            **optional_params\n        )\n\n    def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \\\n            Sequence[Document]:\n        from sklearn.metrics.pairwise import cosine_similarity\n        \"\"\"Rank documents based on their similarity to the query.\n\n              Args:\n                  query: The query text.\n                  documents: The list of document texts to rank.\n\n              Returns:\n                  List of documents sorted by relevance to the query.\n              \"\"\"\n        # 获取查询和文档的嵌入\n        query_embedding = self.embed_query(query)\n        documents = [doc.page_content for doc in documents]\n        document_embeddings = self.embed_documents(documents)\n        # 计算相似度\n        similarities = cosine_similarity([query_embedding], document_embeddings)[0]\n        ranked_docs = [(doc, _) for _, doc in sorted(zip(similarities, documents), reverse=True)][:self.top_n]\n        return [\n            Document(\n                page_content=doc,  # 第一个值是文档内容\n                metadata={'relevance_score': score}  # 第二个值是相似度分数\n            )\n            for doc, score in ranked_docs\n        ]\n"
  },
  {
    "path": "apps/models_provider/impl/ollama_model_provider/ollama_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： ollama_model_provider.py\n    @date：2024/3/5 17:23\n    @desc:\n\"\"\"\nimport json\nimport os\nfrom typing import Dict, Iterator\nfrom urllib.parse import urlparse, ParseResult\n\nimport requests\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \\\n    BaseModelCredential, DownModelChunk, DownModelChunkStatus, ValidCode, ModelInfoManage\nfrom models_provider.impl.ollama_model_provider.credential.embedding import OllamaEmbeddingModelCredential\nfrom models_provider.impl.ollama_model_provider.credential.image import OllamaImageModelCredential\nfrom models_provider.impl.ollama_model_provider.credential.llm import OllamaLLMModelCredential\nfrom models_provider.impl.ollama_model_provider.credential.reranker import OllamaReRankModelCredential\nfrom models_provider.impl.ollama_model_provider.model.embedding import OllamaEmbedding\nfrom models_provider.impl.ollama_model_provider.model.image import OllamaImage\nfrom models_provider.impl.ollama_model_provider.model.llm import OllamaChatModel\nfrom models_provider.impl.ollama_model_provider.model.reranker import OllamaReranker\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\n\"\"\n\nollama_llm_model_credential = OllamaLLMModelCredential()\nmodel_info_list = [\n    ModelInfo(\n        'deepseek-r1:1.5b',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'deepseek-r1:7b',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'deepseek-r1:8b',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'deepseek-r1:14b',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'deepseek-r1:32b',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n\n    ModelInfo(\n        'llama2',\n        _('Llama 2 is a set of pretrained and fine-tuned generative text models ranging in size from 7 billion to 70 billion. This is a repository of 7B pretrained models. Links to other models can be found in the index at the bottom.'),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'llama2:13b',\n        _('Llama 2 is a set of pretrained and fine-tuned generative text models ranging in size from 7 billion to 70 billion. This is a repository of 13B pretrained models. Links to other models can be found in the index at the bottom.'),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'llama2:70b',\n        _('Llama 2 is a set of pretrained and fine-tuned generative text models ranging in size from 7 billion to 70 billion. This is a repository of 70B pretrained models. Links to other models can be found in the index at the bottom.'),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'llama2-chinese:13b',\n        _('Since the Chinese alignment of Llama2 itself is weak, we use the Chinese instruction set to fine-tune meta-llama/Llama-2-13b-chat-hf with LoRA so that it has strong Chinese conversation capabilities.'),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'llama3:8b',\n        _('Meta Llama 3: The most capable public product LLM to date. 8 billion parameters.'),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'llama3:70b',\n        _('Meta Llama 3: The most capable public product LLM to date. 70 billion parameters.'),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen:0.5b',\n        _(\"Compared with previous versions, qwen 1.5 0.5b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 500 million parameters.\"),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen:1.8b',\n        _(\"Compared with previous versions, qwen 1.5 1.8b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 1.8 billion parameters.\"),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen:4b',\n        _(\"Compared with previous versions, qwen 1.5 4b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 4 billion parameters.\"),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n\n    ModelInfo(\n        'qwen:7b',\n        _(\"Compared with previous versions, qwen 1.5 7b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 7 billion parameters.\"),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen:14b',\n        _(\"Compared with previous versions, qwen 1.5 14b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 14 billion parameters.\"),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen:32b',\n        _(\"Compared with previous versions, qwen 1.5 32b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 32 billion parameters.\"),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen:72b',\n        _(\"Compared with previous versions, qwen 1.5 72b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 72 billion parameters.\"),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen:110b',\n        _(\"Compared with previous versions, qwen 1.5 110b has significantly enhanced the model's alignment with human preferences and its multi-language processing capabilities. Models of all sizes support a context length of 32768 tokens. 110 billion parameters.\"),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen2:72b-instruct',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen2:57b-a14b-instruct',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen2:7b-instruct',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen2.5:72b-instruct',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen2.5:32b-instruct',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen2.5:14b-instruct',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen2.5:7b-instruct',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen2.5:1.5b-instruct',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen2.5:0.5b-instruct',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'qwen2.5:3b-instruct',\n        '',\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n    ModelInfo(\n        'phi3',\n        _(\"Phi-3 Mini is Microsoft's 3.8B parameter, lightweight, state-of-the-art open model.\"),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel),\n]\nollama_embedding_model_credential = OllamaEmbeddingModelCredential()\nollama_image_model_credential = OllamaImageModelCredential()\nollama_reranker_model_credential = OllamaReRankModelCredential()\nembedding_model_info = [\n    ModelInfo(\n        'nomic-embed-text',\n        _('A high-performance open embedding model with a large token context window.'),\n        ModelTypeConst.EMBEDDING, ollama_embedding_model_credential, OllamaEmbedding),\n]\nreranker_model_info = [\n    ModelInfo(\n        'linux6200/bge-reranker-v2-m3',\n        '',\n        ModelTypeConst.RERANKER, ollama_reranker_model_credential, OllamaReranker),\n]\n\nimage_model_info = [\n    ModelInfo(\n        'llava:7b',\n        '',\n        ModelTypeConst.IMAGE, ollama_image_model_credential, OllamaImage),\n    ModelInfo(\n        'llava:13b',\n        '',\n        ModelTypeConst.IMAGE, ollama_image_model_credential, OllamaImage),\n    ModelInfo(\n        'llava:34b',\n        '',\n        ModelTypeConst.IMAGE, ollama_image_model_credential, OllamaImage),\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_model_info_list(embedding_model_info)\n    .append_default_model_info(ModelInfo(\n        'phi3',\n        _('Phi-3 Mini is Microsoft\\'s 3.8B parameter, lightweight, state-of-the-art open model.'),\n        ModelTypeConst.LLM, ollama_llm_model_credential, OllamaChatModel))\n    .append_default_model_info(ModelInfo(\n        'nomic-embed-text',\n        _('A high-performance open embedding model with a large token context window.'),\n        ModelTypeConst.EMBEDDING, ollama_embedding_model_credential, OllamaEmbedding), )\n    .append_model_info_list(image_model_info)\n    .append_default_model_info(image_model_info[0])\n    .append_model_info_list(reranker_model_info)\n    .append_default_model_info(reranker_model_info[0])\n    .build()\n)\n\n\ndef get_base_url(url: str):\n    parse = urlparse(url)\n    result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='',\n                             query='',\n                             fragment='').geturl()\n    return result_url[:-1] if result_url.endswith(\"/\") else result_url\n\n\ndef convert_to_down_model_chunk(row_str: str, chunk_index: int):\n    row = json.loads(row_str)\n    status = DownModelChunkStatus.unknown\n    digest = \"\"\n    progress = 100\n    if 'status' in row:\n        digest = row.get('status')\n        if row.get('status') == 'success':\n            status = DownModelChunkStatus.success\n        if row.get('status').__contains__(\"pulling\"):\n            progress = 0\n            status = DownModelChunkStatus.pulling\n            if 'total' in row and 'completed' in row:\n                progress = (row.get('completed') / row.get('total') * 100)\n    elif 'error' in row:\n        status = DownModelChunkStatus.error\n        digest = row.get('error')\n    return DownModelChunk(status=status, digest=digest, progress=progress, details=row_str, index=chunk_index)\n\n\ndef convert(response_stream) -> Iterator[DownModelChunk]:\n    temp = \"\"\n    index = 0\n    for c in response_stream:\n        index += 1\n        row_content = c.decode()\n        temp += row_content\n        if row_content.endswith('}') or row_content.endswith('\\n'):\n            rows = [t for t in temp.split(\"\\n\") if len(t) > 0]\n            for row in rows:\n                yield convert_to_down_model_chunk(row, index)\n            temp = \"\"\n\n    if len(temp) > 0:\n        rows = [t for t in temp.split(\"\\n\") if len(t) > 0]\n        for row in rows:\n            yield convert_to_down_model_chunk(row, index)\n\n\nclass OllamaModelProvider(IModelProvider):\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_ollama_provider', name='Ollama', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'ollama_model_provider', 'icon',\n                         'ollama_icon_svg')))\n\n    @staticmethod\n    def get_base_model_list(api_base):\n        base_url = get_base_url(api_base)\n        r = requests.request(method=\"GET\", url=f\"{base_url}/api/tags\", timeout=5)\n        r.raise_for_status()\n        return r.json()\n\n    def down_model(self, model_type: str, model_name, model_credential: Dict[str, object]) -> Iterator[DownModelChunk]:\n        api_base = model_credential.get('api_base', '')\n        base_url = get_base_url(api_base)\n        r = requests.request(\n            method=\"POST\",\n            url=f\"{base_url}/api/pull\",\n            data=json.dumps({\"name\": model_name}).encode(),\n            stream=True,\n        )\n        return convert(r)\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/3/28 16:25\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 16:45\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass OpenAIEmbeddingModelParams(BaseForm):\n    dimensions = forms.SingleSelect(\n        TooltipLabel(\n            _('Dimensions'),\n            _('')\n        ),\n        required=True,\n        default_value=1024,\n        value_field='value',\n        text_field='label',\n        option_list=[\n            {'label': '1536', 'value': '1536'},\n            {'label': '1024', 'value': '1024'},\n            {'label': '768', 'value': '768'},\n            {'label': '512', 'value': '512'},\n        ]\n    )\n\n\nclass OpenAIEmbeddingCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=True):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return OpenAIEmbeddingModelParams()\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/credential/image.py",
    "content": "# coding=utf-8\nimport base64\nimport os\nfrom typing import Dict\n\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext_lazy as _, gettext\n\n\nclass OpenAIImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass OpenAIImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext('Hello')}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return OpenAIImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 18:32\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\nfrom openai import BadRequestError\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass OpenAILLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass OpenAILLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException) or isinstance(e, BadRequestError):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return OpenAILLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/credential/stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass OpenAISTTModelParams(BaseForm):\n    language = forms.TextInputField(\n        TooltipLabel(_('language'), _('If not passed, the default value is zh')),\n        required=True,\n        default_value='zh',\n    )\n\nclass OpenAISTTModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n\n        return OpenAISTTModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/credential/tti.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass OpenAITTIModelParams(BaseForm):\n    size = forms.SingleSelect(\n        TooltipLabel(_('Image size'),\n                     _('The image generation endpoint allows you to create raw images based on text prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 or 1792x1024 pixels.')),\n        required=True,\n        default_value='1024x1024',\n        option_list=[\n            {'value': '1024x1024', 'label': '1024x1024'},\n            {'value': '1024x1792', 'label': '1024x1792'},\n            {'value': '1792x1024', 'label': '1792x1024'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    quality = forms.SingleSelect(\n        TooltipLabel(_('Picture quality'), _('''       \nBy default, images are produced in standard quality, but with DALL·E 3 you can set quality: \"hd\" to enhance detail. Square, standard quality images are generated fastest.\n        ''')),\n        required=True,\n        default_value='standard',\n        option_list=[\n            {'value': 'standard', 'label': 'standard'},\n            {'value': 'hd', 'label': 'hd'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    n = forms.SliderField(\n        TooltipLabel(_('Number of pictures'),\n                     _('You can use DALL·E 3 to request 1 image at a time (requesting more images by issuing parallel requests), or use DALL·E 2 with the n parameter to request up to 10 images at a time.')),\n        required=True, default_value=1,\n        _min=1,\n        _max=10,\n        _step=1,\n        precision=0)\n\n\nclass OpenAITextToImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return OpenAITTIModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/credential/tts.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass OpenAITTSModelGeneralParams(BaseForm):\n    # alloy, echo, fable, onyx, nova, shimmer\n    voice = forms.SingleSelect(\n        TooltipLabel('Voice',\n                     _('Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) to find one that suits your desired tone and audience. The current voiceover is optimized for English.')),\n        required=True, default_value='alloy',\n        text_field='value',\n        value_field='value',\n        option_list=[\n            {'text': 'alloy', 'value': 'alloy'},\n            {'text': 'echo', 'value': 'echo'},\n            {'text': 'fable', 'value': 'fable'},\n            {'text': 'onyx', 'value': 'onyx'},\n            {'text': 'nova', 'value': 'nova'},\n            {'text': 'shimmer', 'value': 'shimmer'},\n        ])\n\n\nclass OpenAITTSModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return OpenAITTSModelGeneralParams()\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 17:44\n    @desc:\n\"\"\"\nfrom typing import Dict, List\n\nimport openai\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass OpenAIEmbeddingModel(MaxKBBaseModel):\n    model_name: str\n    optional_params: dict\n\n    def __init__(self, api_key, base_url, model_name: str, optional_params: dict):\n        self.client = openai.OpenAI(api_key=api_key, base_url=base_url).embeddings\n        self.model_name = model_name\n        self.optional_params = optional_params\n\n    def is_cache_model(self):\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return OpenAIEmbeddingModel(\n            api_key=model_credential.get('api_key'),\n            model_name=model_name,\n            base_url=model_credential.get('api_base'),\n            optional_params=optional_params\n        )\n\n    def embed_query(self, text: str):\n        res = self.embed_documents([text])\n        return res[0]\n\n    def embed_documents(\n            self, texts: List[str], chunk_size: int | None = None\n    ) -> List[List[float]]:\n        if len(self.optional_params) > 0:\n            res = self.client.create(\n                input=texts, model=self.model_name, encoding_format=\"float\",\n                **self.optional_params\n            )\n        else:\n            res = self.client.create(input=texts, model=self.model_name, encoding_format=\"float\")\n        return [e.embedding for e in res.data]\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/model/image.py",
    "content": "from typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass OpenAIImage(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return OpenAIImage(\n            model_name=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： llm.py\n    @date：2024/4/18 15:28\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass OpenAIChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        streaming = model_kwargs.get('streaming', True)\n        if 'o1' in model_name:\n            streaming = False\n        chat_open_ai = OpenAIChatModel(\n            model=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            extra_body=optional_params,\n            streaming=streaming,\n            custom_get_token_ids=custom_get_token_ids\n        )\n        return chat_open_ai\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        try:\n            return super().get_num_tokens_from_messages(messages)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n    def get_num_tokens(self, text: str) -> int:\n        try:\n            return super().get_num_tokens(text)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/model/stt.py",
    "content": "import asyncio\nimport io\nfrom typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass OpenAISpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {}\n        if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None:\n            optional_params['max_tokens'] = model_kwargs['max_tokens']\n        if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None:\n            optional_params['temperature'] = model_kwargs['temperature']\n        return OpenAISpeechToText(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            params = model_kwargs,\n            **optional_params,\n        )\n\n    def check_auth(self):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        response_list = client.models.with_raw_response.list()\n        # print(response_list)\n\n    def speech_to_text(self, audio_file):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        audio_data = audio_file.read()\n        buffer = io.BytesIO(audio_data)\n        buffer.name = \"file.mp3\"  # this is the important line\n\n        filter_params = {k: v for k,v in self.params.items() if k not in {'model_id','use_local','streaming'}}\n        transcription_params = {\n            'model': self.model,\n            'file': buffer,\n            'language': 'zh'\n        }\n\n        res = client.audio.transcriptions.create(**transcription_params,extra_body=filter_params)\n        return res.text\n\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/model/tti.py",
    "content": "from typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass OpenAITextToImage(MaxKBBaseModel, BaseTextToImage):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return OpenAITextToImage(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        chat = OpenAI(api_key=self.api_key, base_url=self.api_base)\n        response_list = chat.models.with_raw_response.list()\n\n        # self.generate_image('生成一个小猫图片')\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        chat = OpenAI(api_key=self.api_key, base_url=self.api_base)\n        res = chat.images.generate(model=self.model, prompt=prompt, **self.params)\n        file_urls = []\n        try:\n            for content in res.data:\n                if content.url:\n                    file_urls.append(content.url)\n                elif content.b64_json:\n                    file_urls.append(content.b64_json)\n\n            return file_urls\n        except Exception as e:\n            raise f\"OpenAI generate image error: {e}\"\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/model/tts.py",
    "content": "from typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom common.utils.common import _remove_empty_lines\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tts import BaseTextToSpeech\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass OpenAITextToSpeech(MaxKBBaseModel, BaseTextToSpeech):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'voice': 'alloy'}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return OpenAITextToSpeech(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        response_list = client.models.with_raw_response.list()\n        # print(response_list)\n\n    def text_to_speech(self, text):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        text = _remove_empty_lines(text)\n        with client.audio.speech.with_streaming_response.create(\n                model=self.model,\n                input=text,\n                **self.params\n        ) as response:\n            return response.read()\n"
  },
  {
    "path": "apps/models_provider/impl/openai_model_provider/openai_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： openai_model_provider.py\n    @date：2024/3/28 16:26\n    @desc:\n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \\\n    ModelTypeConst, ModelInfoManage\nfrom models_provider.impl.openai_model_provider.credential.embedding import OpenAIEmbeddingCredential\nfrom models_provider.impl.openai_model_provider.credential.image import OpenAIImageModelCredential\nfrom models_provider.impl.openai_model_provider.credential.llm import OpenAILLMModelCredential\nfrom models_provider.impl.openai_model_provider.credential.stt import OpenAISTTModelCredential\nfrom models_provider.impl.openai_model_provider.credential.tti import OpenAITextToImageModelCredential\nfrom models_provider.impl.openai_model_provider.credential.tts import OpenAITTSModelCredential\nfrom models_provider.impl.openai_model_provider.model.embedding import OpenAIEmbeddingModel\nfrom models_provider.impl.openai_model_provider.model.image import OpenAIImage\nfrom models_provider.impl.openai_model_provider.model.llm import OpenAIChatModel\nfrom models_provider.impl.openai_model_provider.model.stt import OpenAISpeechToText\nfrom models_provider.impl.openai_model_provider.model.tti import OpenAITextToImage\nfrom models_provider.impl.openai_model_provider.model.tts import OpenAITextToSpeech\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext_lazy as _\n\nopenai_llm_model_credential = OpenAILLMModelCredential()\nopenai_stt_model_credential = OpenAISTTModelCredential()\nopenai_tts_model_credential = OpenAITTSModelCredential()\nopenai_image_model_credential = OpenAIImageModelCredential()\nopenai_tti_model_credential = OpenAITextToImageModelCredential()\nmodel_info_list = [\n    ModelInfo('gpt-3.5-turbo', _('The latest gpt-3.5-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM,\n              openai_llm_model_credential, OpenAIChatModel\n              ),\n    ModelInfo('gpt-4', _('Latest gpt-4, updated with OpenAI adjustments'), ModelTypeConst.LLM, openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-4o', _('The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with OpenAI adjustments'),\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-4o-mini', _('The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with OpenAI adjustments'),\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-4-turbo', _('The latest gpt-4-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM,\n              openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-4-turbo-preview', _('The latest gpt-4-turbo-preview, updated with OpenAI adjustments'),\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-3.5-turbo-0125',\n              _('gpt-3.5-turbo snapshot on January 25, 2024, supporting context length 16,385 tokens'), ModelTypeConst.LLM,\n              openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-3.5-turbo-1106',\n              _('gpt-3.5-turbo snapshot on November 6, 2023, supporting context length 16,385 tokens'), ModelTypeConst.LLM,\n              openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-3.5-turbo-0613',\n              _('[Legacy] gpt-3.5-turbo snapshot on June 13, 2023, will be deprecated on June 13, 2024'),\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-4o-2024-05-13',\n              _('gpt-4o snapshot on May 13, 2024, supporting context length 128,000 tokens'),\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-4-turbo-2024-04-09',\n              _('gpt-4-turbo snapshot on April 9, 2024, supporting context length 128,000 tokens'),\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-4-0125-preview', _('gpt-4-turbo snapshot on January 25, 2024, supporting context length 128,000 tokens'),\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('gpt-4-1106-preview', _('gpt-4-turbo snapshot on November 6, 2023, supporting context length 128,000 tokens'),\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              OpenAIChatModel),\n    ModelInfo('whisper-1', '',\n              ModelTypeConst.STT, openai_stt_model_credential,\n              OpenAISpeechToText),\n    ModelInfo('tts-1', '',\n              ModelTypeConst.TTS, openai_tts_model_credential,\n              OpenAITextToSpeech)\n]\nopen_ai_embedding_credential = OpenAIEmbeddingCredential()\nmodel_info_embedding_list = [\n    ModelInfo('text-embedding-ada-002', '',\n              ModelTypeConst.EMBEDDING, open_ai_embedding_credential,\n              OpenAIEmbeddingModel),\n    ModelInfo('text-embedding-3-small', '',\n              ModelTypeConst.EMBEDDING, open_ai_embedding_credential,\n              OpenAIEmbeddingModel),\n    ModelInfo('text-embedding-3-large', '',\n              ModelTypeConst.EMBEDDING, open_ai_embedding_credential,\n              OpenAIEmbeddingModel)\n]\n\nmodel_info_image_list = [\n    ModelInfo('gpt-4o', _('The latest GPT-4o, cheaper and faster than gpt-4-turbo, updated with OpenAI adjustments'),\n              ModelTypeConst.IMAGE, openai_image_model_credential,\n              OpenAIImage),\n    ModelInfo('gpt-4o-mini', _('The latest gpt-4o-mini, cheaper and faster than gpt-4o, updated with OpenAI adjustments'),\n              ModelTypeConst.IMAGE, openai_image_model_credential,\n              OpenAIImage),\n]\n\nmodel_info_tti_list = [\n    ModelInfo('dall-e-3', '',\n              ModelTypeConst.TTI, openai_tti_model_credential,\n              OpenAITextToImage),\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_default_model_info(ModelInfo('gpt-3.5-turbo', _('The latest gpt-3.5-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM,\n                                         openai_llm_model_credential, OpenAIChatModel\n                                         ))\n    .append_model_info_list(model_info_embedding_list)\n    .append_default_model_info(model_info_embedding_list[0])\n    .append_model_info_list(model_info_image_list)\n    .append_default_model_info(model_info_image_list[0])\n    .append_model_info_list(model_info_tti_list)\n    .append_default_model_info(model_info_tti_list[0])\n    .append_default_model_info(ModelInfo('whisper-1', '',\n              ModelTypeConst.STT, openai_stt_model_credential,\n              OpenAISpeechToText)\n    )\n    .append_default_model_info(ModelInfo('tts-1', '',\n              ModelTypeConst.TTS, openai_tts_model_credential,\n              OpenAITextToSpeech))\n    .build()\n)\n\n\nclass OpenAIModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_openai_provider', name='OpenAI', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'openai_model_provider', 'icon',\n                         'openai_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/qwen_model_provider/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/qwen_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/qwen_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/regolo_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/3/28 16:25\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/regolo_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 16:45\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\n\nclass RegoloEmbeddingCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=True):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key', 'api_base']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField(_('API URL'), required=True,\n                                    default_value='https://api.regolo.ai/v1')\n    api_key = forms.PasswordInputField('API Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/regolo_model_provider/credential/image.py",
    "content": "# coding=utf-8\nimport base64\nimport os\nfrom typing import Dict\n\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\n\nclass RegoloImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass RegoloImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True, default_value='https://api.regolo.ai/v1')\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key', 'api_base']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext('Hello')}])])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return RegoloImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/regolo_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 18:32\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\n\nclass RegoloLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass RegoloLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key', 'api_base']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True, default_value='https://api.regolo.ai/v1')\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return RegoloLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/regolo_model_provider/credential/tti.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass RegoloTTIModelParams(BaseForm):\n    size = forms.SingleSelect(\n        TooltipLabel(_('Image size'),\n                     _('The image generation endpoint allows you to create raw images based on text prompts. ')),\n        required=True,\n        default_value='1024x1024',\n        option_list=[\n            {'value': '1024x1024', 'label': '1024x1024'},\n            {'value': '1024x1792', 'label': '1024x1792'},\n            {'value': '1792x1024', 'label': '1792x1024'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    quality = forms.SingleSelect(\n        TooltipLabel(_('Picture quality'), _('''       \nBy default, images are produced in standard quality.\n        ''')),\n        required=True,\n        default_value='standard',\n        option_list=[\n            {'value': 'standard', 'label': 'standard'},\n            {'value': 'hd', 'label': 'hd'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    n = forms.SliderField(\n        TooltipLabel(_('Number of pictures'),\n                     _('1 as default')),\n        required=True, default_value=1,\n        _min=1,\n        _max=10,\n        _step=1,\n        precision=0)\n\n\nclass RegoloTextToImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True, default_value='https://api.regolo.ai/v1')\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key', 'api_base']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return RegoloTTIModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/regolo_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 17:44\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom langchain_openai import OpenAIEmbeddings\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass RegoloEmbeddingModel(MaxKBBaseModel, OpenAIEmbeddings):\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return RegoloEmbeddingModel(\n            api_key=model_credential.get('api_key'),\n            model=model_name,\n            openai_api_base=model_credential.get('api_base') or \"https://api.regolo.ai/v1\",\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/regolo_model_provider/model/image.py",
    "content": "from typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass RegoloImage(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return RegoloImage(\n            model_name=model_name,\n            openai_api_base=model_credential.get('api_base') or \"https://api.regolo.ai/v1\",\n            openai_api_key=model_credential.get('api_key'),\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/regolo_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： llm.py\n    @date：2024/4/18 15:28\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\nfrom langchain_openai.chat_models import ChatOpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass RegoloChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return RegoloChatModel(\n            model=model_name,\n            openai_api_base=model_credential.get('api_base') or \"https://api.regolo.ai/v1\",\n            openai_api_key=model_credential.get('api_key'),\n            extra_body=optional_params\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/regolo_model_provider/model/tti.py",
    "content": "from typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass RegoloTextToImage(MaxKBBaseModel, BaseTextToImage):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return RegoloTextToImage(\n            model=model_name,\n            api_base=model_credential.get('api_base') or \"https://api.regolo.ai/v1\",\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        chat = OpenAI(api_key=self.api_key, base_url=self.api_base)\n        response_list = chat.models.with_raw_response.list()\n\n        # self.generate_image('生成一个小猫图片')\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        chat = OpenAI(api_key=self.api_key, base_url=self.api_base)\n        res = chat.images.generate(model=self.model, prompt=prompt, **self.params)\n        file_urls = []\n        try:\n            for content in res.data:\n                url = content.url\n                file_urls.append(url)\n            return file_urls\n        except Exception as e:\n            raise f\"RegoloTextToImage generate_image error: {e}\"\n"
  },
  {
    "path": "apps/models_provider/impl/regolo_model_provider/regolo_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： openai_model_provider.py\n    @date：2024/3/28 16:26\n    @desc:\n\"\"\"\nimport os\n\nfrom django.utils.translation import gettext as _\n\nfrom common.utils.common import get_file_content\nfrom maxkb.conf import PROJECT_DIR\nfrom models_provider.base_model_provider import ModelInfo, ModelTypeConst, ModelInfoManage, IModelProvider, \\\n    ModelProvideInfo\nfrom models_provider.impl.regolo_model_provider.credential.embedding import RegoloEmbeddingCredential\nfrom models_provider.impl.regolo_model_provider.credential.llm import RegoloLLMModelCredential\nfrom models_provider.impl.regolo_model_provider.credential.tti import RegoloTextToImageModelCredential\nfrom models_provider.impl.regolo_model_provider.model.embedding import RegoloEmbeddingModel\nfrom models_provider.impl.regolo_model_provider.model.llm import RegoloChatModel\nfrom models_provider.impl.regolo_model_provider.model.tti import RegoloTextToImage\n\nopenai_llm_model_credential = RegoloLLMModelCredential()\nopenai_tti_model_credential = RegoloTextToImageModelCredential()\nmodel_info_list = [\n    ModelInfo('Phi-4', '', ModelTypeConst.LLM,\n              openai_llm_model_credential, RegoloChatModel\n              ),\n    ModelInfo('DeepSeek-R1-Distill-Qwen-32B', '', ModelTypeConst.LLM,\n              openai_llm_model_credential,\n              RegoloChatModel),\n    ModelInfo('maestrale-chat-v0.4-beta', '',\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              RegoloChatModel),\n    ModelInfo('Llama-3.3-70B-Instruct',\n              '',\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              RegoloChatModel),\n    ModelInfo('Llama-3.1-8B-Instruct',\n              '',\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              RegoloChatModel),\n    ModelInfo('DeepSeek-Coder-6.7B-Instruct', '',\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              RegoloChatModel)\n]\nopen_ai_embedding_credential = RegoloEmbeddingCredential()\nmodel_info_embedding_list = [\n    ModelInfo('gte-Qwen2', '',\n              ModelTypeConst.EMBEDDING, open_ai_embedding_credential,\n              RegoloEmbeddingModel),\n]\n\nmodel_info_tti_list = [\n    ModelInfo('FLUX.1-dev', '',\n              ModelTypeConst.TTI, openai_tti_model_credential,\n              RegoloTextToImage),\n    ModelInfo('sdxl-turbo', '',\n              ModelTypeConst.TTI, openai_tti_model_credential,\n              RegoloTextToImage),\n]\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_default_model_info(\n        ModelInfo('gpt-3.5-turbo', _('The latest gpt-3.5-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM,\n                  openai_llm_model_credential, RegoloChatModel\n                  ))\n    .append_model_info_list(model_info_embedding_list)\n    .append_default_model_info(model_info_embedding_list[0])\n    .append_model_info_list(model_info_tti_list)\n    .append_default_model_info(model_info_tti_list[0])\n\n    .build()\n)\n\n\nclass RegoloModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_regolo_provider', name='Regolo', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'regolo_model_provider',\n                         'icon',\n                         'regolo_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/3/28 16:25\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 16:45\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass SiliconCloudEmbeddingCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=True):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/credential/image.py",
    "content": "# coding=utf-8\nimport base64\nimport os\nfrom typing import Dict\n\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext_lazy as _, gettext\n\n\nclass SiliconCloudImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass SiliconCloudImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext('Hello')}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return SiliconCloudImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 18:32\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass SiliconCloudLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass SiliconCloudLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return SiliconCloudLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/credential/reranker.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： reranker.py\n    @date：2024/9/9 17:51\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\nfrom langchain_core.documents import Document\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.siliconCloud_model_provider.model.reranker import SiliconCloudReranker\nfrom common.utils.logger import maxkb_logger\n\nclass SiliconCloudRerankerCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        if not model_type == 'RERANKER':\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model: SiliconCloudReranker = provider.get_model(model_type, model_name, model_credential)\n            model.compress_documents([Document(page_content=_('Hello'))], _('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/credential/stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass SiliconCloudSTTModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential,**model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        pass\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/credential/tti.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass SiliconCloudTTIModelParams(BaseForm):\n    size = forms.SingleSelect(\n        TooltipLabel(_('Image size'),\n                     _('The image generation endpoint allows you to create raw images based on text prompts. When using the DALL·E 3, the image size can be 1024x1024, 1024x1792 or 1792x1024 pixels.')),\n        required=True,\n        default_value='1024x1024',\n        option_list=[\n            {'value': '1024x1024', 'label': '1024x1024'},\n            {'value': '1024x1792', 'label': '1024x1792'},\n            {'value': '1792x1024', 'label': '1792x1024'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    quality = forms.SingleSelect(\n        TooltipLabel(_('Picture quality'), _('''       \nBy default, images are produced in standard quality, but with DALL·E 3 you can set quality: \"hd\" to enhance detail. Square, standard quality images are generated fastest.\n        ''')),\n        required=True,\n        default_value='standard',\n        option_list=[\n            {'value': 'standard', 'label': 'standard'},\n            {'value': 'hd', 'label': 'hd'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    n = forms.SliderField(\n        TooltipLabel(_('Number of pictures'),\n                     _('You can use DALL·E 3 to request 1 image at a time (requesting more images by issuing parallel requests), or use DALL·E 2 with the n parameter to request up to 10 images at a time.')),\n        required=True, default_value=1,\n        _min=1,\n        _max=10,\n        _step=1,\n        precision=0)\n\n\nclass SiliconCloudTextToImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return SiliconCloudTTIModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/credential/tts.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass SiliconCloudTTSModelGeneralParams(BaseForm):\n    # alloy, echo, fable, onyx, nova, shimmer\n    voice = forms.SingleSelect(\n        TooltipLabel('Voice',\n                     _('Try out the different sounds (Alloy, Echo, Fable, Onyx, Nova, and Sparkle) to find one that suits your desired tone and audience. The current voiceover is optimized for English.')),\n        required=True, default_value='fnlp/MOSS-TTSD-v0.5:alex',\n        text_field='label',\n        value_field='value',\n        option_list=[\n            {'label': 'alex', 'value': 'fnlp/MOSS-TTSD-v0.5:alex'},\n            {'label': 'anna', 'value': 'fnlp/MOSS-TTSD-v0.5:anna'},\n            {'label': 'bella', 'value': 'fnlp/MOSS-TTSD-v0.5:bella'},\n            {'label': 'charles', 'value': 'fnlp/MOSS-TTSD-v0.5:charles'},\n            {'label': 'benjamin', 'value': 'fnlp/MOSS-TTSD-v0.5:benjamin'},\n            {'label': 'claire', 'value': 'fnlp/MOSS-TTSD-v0.5:claire'},\n            {'label': 'david', 'value': 'fnlp/MOSS-TTSD-v0.5:david'},\n            {'label': 'diana', 'value': 'fnlp/MOSS-TTSD-v0.5:diana'},\n        ])\n\n\nclass SiliconCloudTTSModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n\n    def get_model_params_setting_form(self, model_name):\n        return SiliconCloudTTSModelGeneralParams()"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/10/16 16:34\n    @desc:\n\"\"\"\nfrom typing import Dict, List\nfrom common.utils.logger import maxkb_logger\nimport requests\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass SiliconCloudEmbeddingModel(MaxKBBaseModel):\n    model_name: str\n    openai_api_key: str\n    base_url: str\n    optional_params: dict\n\n    def __init__(self, api_key, model_name: str, base_url, optional_params: dict):\n        self.openai_api_key = api_key\n        self.base_url = base_url\n        self.model_name = model_name\n        self.optional_params = optional_params\n\n    def is_cache_model(self):\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return SiliconCloudEmbeddingModel(\n            api_key=model_credential.get('api_key'),\n            model_name=model_name,\n            optional_params=optional_params,\n            base_url=model_credential.get('api_base'),\n        )\n\n    def embed_query(self, text: str) -> list:\n        payload = {\n            \"model\": self.model_name,\n            \"input\": text,\n            **self.optional_params\n        }\n        headers = {\n            \"Authorization\": f\"Bearer {self.openai_api_key}\",\n            \"Content-Type\": \"application/json\"\n        }\n\n        response = requests.post(self.base_url + '/embeddings', json=payload, headers=headers)\n        data = response.json()\n        if isinstance(data, dict):\n            if data['data'] is None or 'code' in data:\n                raise ValueError(f\"Embedding API returned no data: {data}\")\n            # 假设返回结构中有 'data[0].embedding'\n            return data[\"data\"][0][\"embedding\"]\n        else:\n            maxkb_logger.error(f\"Unexpected response from Embedding API: {data}\")\n\n    def embed_documents(self, texts: list) -> list:\n        return [self.embed_query(text) for text in texts]\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/model/image.py",
    "content": "from typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass SiliconCloudImage(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return SiliconCloudImage(\n            model_name=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： llm.py\n    @date：2024/4/18 15:28\n    @desc:\n\"\"\"\nfrom typing import List, Dict\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\nfrom langchain_openai.chat_models import ChatOpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass SiliconCloudChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return SiliconCloudChatModel(\n            model=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            extra_body=optional_params\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/model/reranker.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： siliconcloud_reranker.py\n    @date：2024/9/10 9:45\n    @desc: SiliconCloud 文档重排封装\n\"\"\"\n\nfrom typing import Sequence, Optional, Any, Dict\nimport requests\n\nfrom langchain_core.callbacks import Callbacks\nfrom langchain_core.documents import BaseDocumentCompressor, Document\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom django.utils.translation import gettext as _\n\n\nclass SiliconCloudReranker(MaxKBBaseModel, BaseDocumentCompressor):\n    api_base: Optional[str]\n    \"\"\"SiliconCloud API URL\"\"\"\n    model: Optional[str]\n    \"\"\"SiliconCloud 重排模型 ID\"\"\"\n    api_key: Optional[str]\n    \"\"\"API Key\"\"\"\n\n    top_n: Optional[int] = 3  # 取前 N 个最相关的结果\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return SiliconCloudReranker(\n            api_base=model_credential.get('api_base'),\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            top_n=model_kwargs.get('top_n', 3)\n        )\n\n    def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \\\n            Sequence[Document]:\n        if not documents:\n            return []\n\n        # 预处理文本\n        texts = [doc.page_content for doc in documents]\n\n        # 发送请求到 SiliconCloud API\n        headers = {\n            \"Authorization\": f\"Bearer {self.api_key}\",\n            \"Content-Type\": \"application/json\"\n        }\n        payload = {\n            \"model\": self.model,\n            \"query\": query,\n            \"documents\": texts,\n            \"top_n\": self.top_n,\n            \"return_documents\": True,\n        }\n\n        response = requests.post(f\"{self.api_base}/rerank\", json=payload, headers=headers)\n\n        if response.status_code != 200:\n            raise RuntimeError(f\"SiliconCloud API 请求失败: {response.text}\")\n\n        res = response.json()\n\n        # 解析返回结果\n        return [\n            Document(\n                page_content=item.get('document', {}).get('text', ''),\n                metadata={'relevance_score': item.get('relevance_score')}\n            )\n            for item in res.get('results', [])\n        ]\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/model/stt.py",
    "content": "import asyncio\nimport io\nfrom typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass SiliconCloudSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {}\n        if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None:\n            optional_params['max_tokens'] = model_kwargs['max_tokens']\n        if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None:\n            optional_params['temperature'] = model_kwargs['temperature']\n        return SiliconCloudSpeechToText(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            params=model_kwargs,\n            **optional_params,\n        )\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    def check_auth(self):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        response_list = client.models.with_raw_response.list()\n        # print(response_list)\n\n    def speech_to_text(self, audio_file):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        audio_data = audio_file.read()\n        buffer = io.BytesIO(audio_data)\n        buffer.name = \"file.mp3\"  # this is the important line\n\n        filter_params = {k: v for k, v in self.params.items() if k not in {'model_id', 'use_local', 'streaming'}}\n        transcription_params = {\n            'model': self.model,\n            'file': buffer,\n            'language': 'zh'\n        }\n\n        res = client.audio.transcriptions.create(**transcription_params,extra_body=filter_params)\n        return res.text\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/model/tti.py",
    "content": "from typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass SiliconCloudTextToImage(MaxKBBaseModel, BaseTextToImage):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return SiliconCloudTextToImage(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        chat = OpenAI(api_key=self.api_key, base_url=self.api_base)\n        response_list = chat.models.with_raw_response.list()\n\n        # self.generate_image('生成一个小猫图片')\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        chat = OpenAI(api_key=self.api_key, base_url=self.api_base)\n        res = chat.images.generate(model=self.model, prompt=prompt, **self.params)\n        file_urls = []\n        try:\n            for content in res.data:\n                url = content.url\n                file_urls.append(url)\n            return file_urls\n        except Exception as e:\n            raise e\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/model/tts.py",
    "content": "from typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom common.utils.common import _remove_empty_lines\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tts import BaseTextToSpeech\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass SiliconCloudTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'voice': 'alloy'}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return SiliconCloudTextToSpeech(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        response_list = client.models.with_raw_response.list()\n        # print(response_list)\n\n    def text_to_speech(self, text):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        text = _remove_empty_lines(text)\n        with client.audio.speech.with_streaming_response.create(\n                model=self.model,\n                input=text,\n                **self.params\n        ) as response:\n            return response.read()\n\n    def is_cache_model(self):\n        return False\n"
  },
  {
    "path": "apps/models_provider/impl/siliconCloud_model_provider/siliconCloud_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： openai_model_provider.py\n    @date：2024/3/28 16:26\n    @desc:\n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \\\n    ModelTypeConst, ModelInfoManage\nfrom models_provider.impl.siliconCloud_model_provider.credential.embedding import \\\n    SiliconCloudEmbeddingCredential\nfrom models_provider.impl.siliconCloud_model_provider.credential.image import SiliconCloudImageModelCredential\nfrom models_provider.impl.siliconCloud_model_provider.credential.llm import SiliconCloudLLMModelCredential\nfrom models_provider.impl.siliconCloud_model_provider.credential.reranker import SiliconCloudRerankerCredential\nfrom models_provider.impl.siliconCloud_model_provider.credential.stt import SiliconCloudSTTModelCredential\nfrom models_provider.impl.siliconCloud_model_provider.credential.tti import \\\n    SiliconCloudTextToImageModelCredential\nfrom models_provider.impl.siliconCloud_model_provider.credential.tts import SiliconCloudTTSModelCredential\nfrom models_provider.impl.siliconCloud_model_provider.model.embedding import SiliconCloudEmbeddingModel\nfrom models_provider.impl.siliconCloud_model_provider.model.image import SiliconCloudImage\nfrom models_provider.impl.siliconCloud_model_provider.model.llm import SiliconCloudChatModel\nfrom models_provider.impl.siliconCloud_model_provider.model.reranker import SiliconCloudReranker\nfrom models_provider.impl.siliconCloud_model_provider.model.stt import SiliconCloudSpeechToText\nfrom models_provider.impl.siliconCloud_model_provider.model.tti import SiliconCloudTextToImage\nfrom models_provider.impl.siliconCloud_model_provider.model.tts import SiliconCloudTextToSpeech\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\nopenai_llm_model_credential = SiliconCloudLLMModelCredential()\nopenai_stt_model_credential = SiliconCloudSTTModelCredential()\nopenai_reranker_model_credential = SiliconCloudRerankerCredential()\nopenai_tti_model_credential = SiliconCloudTextToImageModelCredential()\nopenai_image_model_credential = SiliconCloudImageModelCredential()\nopenai_tts_model_credential = SiliconCloudTTSModelCredential()\nmodel_info_list = [\n    ModelInfo('deepseek-ai/DeepSeek-R1-Distill-Llama-8B', '', ModelTypeConst.LLM,\n              openai_llm_model_credential, SiliconCloudChatModel\n              ),\n    ModelInfo('deepseek-ai/DeepSeek-R1-Distill-Qwen-7B', '', ModelTypeConst.LLM,\n              openai_llm_model_credential,\n              SiliconCloudChatModel),\n    ModelInfo('deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B', '',\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              SiliconCloudChatModel),\n    ModelInfo('Qwen/Qwen2.5-7B-Instruct',\n              '',\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              SiliconCloudChatModel),\n    ModelInfo('Qwen/Qwen2.5-Coder-7B-Instruct', '',\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              SiliconCloudChatModel),\n    ModelInfo('internlm/internlm2_5-7b-chat', '',\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              SiliconCloudChatModel),\n    ModelInfo('Qwen/Qwen2-1.5B-Instruct', '',\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              SiliconCloudChatModel),\n    ModelInfo('THUDM/glm-4-9b-chat', '',\n              ModelTypeConst.LLM, openai_llm_model_credential,\n              SiliconCloudChatModel),\n    ModelInfo('FunAudioLLM/SenseVoiceSmall', '',\n              ModelTypeConst.STT, openai_stt_model_credential,\n              SiliconCloudSpeechToText),\n]\nopen_ai_embedding_credential = SiliconCloudEmbeddingCredential()\nmodel_info_embedding_list = [\n    ModelInfo('netease-youdao/bce-embedding-base_v1', '',\n              ModelTypeConst.EMBEDDING, open_ai_embedding_credential,\n              SiliconCloudEmbeddingModel),\n    ModelInfo('BAAI/bge-m3', '',\n              ModelTypeConst.EMBEDDING, open_ai_embedding_credential,\n              SiliconCloudEmbeddingModel),\n    ModelInfo('BAAI/bge-large-en-v1.5', '',\n              ModelTypeConst.EMBEDDING, open_ai_embedding_credential,\n              SiliconCloudEmbeddingModel),\n    ModelInfo('BAAI/bge-large-zh-v1.5', '',\n              ModelTypeConst.EMBEDDING, open_ai_embedding_credential,\n              SiliconCloudEmbeddingModel),\n]\n\nmodel_info_tti_list = [\n    ModelInfo('deepseek-ai/Janus-Pro-7B', '',\n              ModelTypeConst.TTI, openai_tti_model_credential,\n              SiliconCloudTextToImage),\n    ModelInfo('stabilityai/stable-diffusion-3-5-large', '',\n              ModelTypeConst.TTI, openai_tti_model_credential,\n              SiliconCloudTextToImage),\n    ModelInfo('black-forest-labs/FLUX.1-schnell', '',\n              ModelTypeConst.TTI, openai_tti_model_credential,\n              SiliconCloudTextToImage),\n    ModelInfo('stabilityai/stable-diffusion-3-medium', '',\n              ModelTypeConst.TTI, openai_tti_model_credential,\n              SiliconCloudTextToImage),\n    ModelInfo('stabilityai/stable-diffusion-xl-base-1.0', '',\n              ModelTypeConst.TTI, openai_tti_model_credential,\n              SiliconCloudTextToImage),\n    ModelInfo('stabilityai/stable-diffusion-2-1', '',\n              ModelTypeConst.TTI, openai_tti_model_credential,\n              SiliconCloudTextToImage),\n]\nmodel_rerank_list = [\n    ModelInfo('netease-youdao/bce-reranker-base_v1', '', ModelTypeConst.RERANKER,\n              openai_reranker_model_credential, SiliconCloudReranker\n              ),\n    ModelInfo('BAAI/bge-reranker-v2-m3', '', ModelTypeConst.RERANKER,\n              openai_reranker_model_credential, SiliconCloudReranker\n              ),\n]\nmodel_tts_list = [\n    ModelInfo('FunAudioLLM/CosyVoice2-0.5B', '',\n              ModelTypeConst.TTS, openai_tts_model_credential,\n              SiliconCloudTextToSpeech),\n]\nmodel_image_info_list = [\n    ModelInfo('Qwen/Qwen3-VL-32B-Instruct', '',\n              ModelTypeConst.IMAGE, openai_image_model_credential,\n              SiliconCloudImage),\n]\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_default_model_info(\n        ModelInfo('gpt-3.5-turbo', _('The latest gpt-3.5-turbo, updated with OpenAI adjustments'), ModelTypeConst.LLM,\n                  openai_llm_model_credential, SiliconCloudChatModel\n                  ))\n    .append_model_info_list(model_info_embedding_list)\n    .append_default_model_info(model_info_embedding_list[0])\n    .append_model_info_list(model_info_tti_list)\n    .append_default_model_info(model_info_tti_list[0])\n    .append_default_model_info(ModelInfo('whisper-1', '',\n                                         ModelTypeConst.STT, openai_stt_model_credential,\n                                         SiliconCloudSpeechToText))\n    .append_model_info_list(model_rerank_list)\n    .append_default_model_info(model_rerank_list[0])\n    .append_model_info_list(model_tts_list)\n    .append_default_model_info(model_tts_list[0])\n    .append_model_info_list(model_image_info_list)\n    .append_default_model_info(model_image_info_list[0])\n\n    .build()\n)\n\n\nclass SiliconCloudModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_siliconCloud_provider', name='SILICONFLOW', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'siliconCloud_model_provider',\n                         'icon',\n                         'siliconCloud_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_cloud_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/3/28 16:25\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_cloud_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/tencent_cloud_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 18:32\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass TencentCloudLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass TencentCloudLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return TencentCloudLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_cloud_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/tencent_cloud_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： llm.py\n    @date：2024/4/18 15:28\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass TencentCloudChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        azure_chat_open_ai = TencentCloudChatModel(\n            model=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            extra_body=optional_params,\n            custom_get_token_ids=custom_get_token_ids\n        )\n        return azure_chat_open_ai\n\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_cloud_model_provider/tencent_cloud_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： openai_model_provider.py\n    @date：2024/3/28 16:26\n    @desc:\n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, \\\n    ModelTypeConst, ModelInfoManage\nfrom models_provider.impl.openai_model_provider.credential.embedding import OpenAIEmbeddingCredential\nfrom models_provider.impl.openai_model_provider.credential.image import OpenAIImageModelCredential\nfrom models_provider.impl.openai_model_provider.credential.llm import OpenAILLMModelCredential\nfrom models_provider.impl.openai_model_provider.credential.stt import OpenAISTTModelCredential\nfrom models_provider.impl.openai_model_provider.credential.tti import OpenAITextToImageModelCredential\nfrom models_provider.impl.openai_model_provider.credential.tts import OpenAITTSModelCredential\nfrom models_provider.impl.openai_model_provider.model.embedding import OpenAIEmbeddingModel\nfrom models_provider.impl.openai_model_provider.model.image import OpenAIImage\nfrom models_provider.impl.openai_model_provider.model.llm import OpenAIChatModel\nfrom models_provider.impl.openai_model_provider.model.stt import OpenAISpeechToText\nfrom models_provider.impl.openai_model_provider.model.tti import OpenAITextToImage\nfrom models_provider.impl.openai_model_provider.model.tts import OpenAITextToSpeech\nfrom models_provider.impl.tencent_cloud_model_provider.credential.llm import TencentCloudLLMModelCredential\nfrom models_provider.impl.tencent_cloud_model_provider.model.llm import TencentCloudChatModel\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext_lazy as _\n\nopenai_llm_model_credential = TencentCloudLLMModelCredential()\nmodel_info_list = [\n    ModelInfo('deepseek-v3', '', ModelTypeConst.LLM,\n              openai_llm_model_credential, TencentCloudChatModel\n              ),\n    ModelInfo('deepseek-r1', '', ModelTypeConst.LLM,\n              openai_llm_model_credential, TencentCloudChatModel\n              ),\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_default_model_info(\n        ModelInfo('deepseek-v3', '', ModelTypeConst.LLM,\n                  openai_llm_model_credential, TencentCloudChatModel\n                  ))\n    .build()\n)\n\n\nclass TencentCloudModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_tencent_cloud_provider', name=_('Tencent Cloud'), icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'tencent_cloud_model_provider',\n                         'icon',\n                         'tencent_cloud_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/credential/embedding.py",
    "content": "from typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass TencentEmbeddingCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=True) -> bool:\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n        self.valid_form(model_credential)\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]) -> Dict[str, object]:\n        encrypted_secret_key = super().encryption(model.get('SecretKey', ''))\n        return {**model, 'SecretKey': encrypted_secret_key}\n\n    SecretId = forms.PasswordInputField('SecretId', required=True)\n    SecretKey = forms.PasswordInputField('SecretKey', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/credential/image.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 18:41\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass TencentModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=1.0,\n                                    _min=0.1,\n                                    _max=1.9,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass TencentVisionModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n        for key in ['api_key', 'api_base']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext('Hello')}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True, default_value='https://api.hunyuan.cloud.tencent.com/v1')\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return TencentModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass TencentLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.5,\n                                    _min=0.1,\n                                    _max=2.0,\n                                    _step=0.01,\n                                    precision=2)\n\n\nclass TencentLLMModelCredential(BaseForm, BaseModelCredential):\n    REQUIRED_FIELDS = ['hunyuan_app_id', 'hunyuan_secret_id', 'hunyuan_secret_key']\n\n    @classmethod\n    def _validate_model_type(cls, model_type, provider, raise_exception=False):\n        if not any(mt['value'] == model_type for mt in provider.get_model_type_list()):\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('{model_type} Model type is not supported').format(model_type=model_type))\n            return False\n        return True\n\n    @classmethod\n    def _validate_credential_fields(cls, model_credential, raise_exception=False):\n        missing_keys = [key for key in cls.REQUIRED_FIELDS if key not in model_credential]\n        if missing_keys:\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('{keys} is required').format(keys=\", \".join(missing_keys)))\n            return False\n        return True\n\n    def is_valid(self, model_type, model_name, model_credential, model_params, provider, raise_exception=False):\n        if not (self._validate_model_type(model_type, provider, raise_exception) and\n                self._validate_credential_fields(model_credential, raise_exception)):\n            return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            return False\n        return True\n\n    def encryption_dict(self, model):\n        return {**model, 'hunyuan_secret_key': super().encryption(model.get('hunyuan_secret_key', ''))}\n\n    hunyuan_app_id = forms.TextInputField('APP ID', required=True)\n    hunyuan_secret_id = forms.PasswordInputField('SecretId', required=True)\n    hunyuan_secret_key = forms.PasswordInputField('SecretKey', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return TencentLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/credential/tti.py",
    "content": "# coding=utf-8\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass TencentTTIModelParams(BaseForm):\n    Style = forms.SingleSelect(\n        TooltipLabel(_('painting style'), _('If not passed, the default value is 201 (Japanese anime style)')),\n        required=True,\n        default_value='201',\n        option_list=[\n            {'value': '000', 'label': _('Not limited to style')},\n            {'value': '101', 'label': _('ink painting')},\n            {'value': '102', 'label': _('concept art')},\n            {'value': '103', 'label': _('Oil painting 1')},\n            {'value': '118', 'label': _('Oil Painting 2 (Van Gogh)')},\n            {'value': '104', 'label': _('watercolor painting')},\n            {'value': '105', 'label': _('pixel art')},\n            {'value': '106', 'label': _('impasto style')},\n            {'value': '107', 'label': _('illustration')},\n            {'value': '108', 'label': _('paper cut style')},\n            {'value': '109', 'label': _('Impressionism 1 (Monet)')},\n            {'value': '119', 'label': _('Impressionism 2')},\n            {'value': '110', 'label': '2.5D'},\n            {'value': '111', 'label': _('classical portraiture')},\n            {'value': '112', 'label': _('black and white sketch')},\n            {'value': '113', 'label': _('cyberpunk')},\n            {'value': '114', 'label': _('science fiction style')},\n            {'value': '115', 'label': _('dark style')},\n            {'value': '116', 'label': '3D'},\n            {'value': '117', 'label': _('vaporwave')},\n            {'value': '201', 'label': _('Japanese animation')},\n            {'value': '202', 'label': _('monster style')},\n            {'value': '203', 'label': _('Beautiful ancient style')},\n            {'value': '204', 'label': _('retro anime')},\n            {'value': '301', 'label': _('Game cartoon hand drawing')},\n            {'value': '401', 'label': _('Universal realistic style')},\n        ],\n        value_field='value',\n        text_field='label'\n    )\n\n    Resolution = forms.SingleSelect(\n        TooltipLabel(_('Generate image resolution'), _('If not transmitted, the default value is 768:768.')),\n        required=True,\n        default_value='768:768',\n        option_list=[\n            {'value': '768:768', 'label': '768:768（1:1）'},\n            {'value': '768:1024', 'label': '768:1024（3:4）'},\n            {'value': '1024:768', 'label': '1024:768（4:3）'},\n            {'value': '1024:1024', 'label': '1024:1024（1:1）'},\n            {'value': '720:1280', 'label': '720:1280（9:16）'},\n            {'value': '1280:720', 'label': '1280:720（16:9）'},\n            {'value': '768:1280', 'label': '768:1280（3:5）'},\n            {'value': '1280:768', 'label': '1280:768（5:3）'},\n            {'value': '1080:1920', 'label': '1080:1920（9:16）'},\n            {'value': '1920:1080', 'label': '1920:1080（16:9）'},\n        ],\n        value_field='value',\n        text_field='label'\n    )\n\n\nclass TencentTTIModelCredential(BaseForm, BaseModelCredential):\n    REQUIRED_FIELDS = ['hunyuan_secret_id', 'hunyuan_secret_key']\n\n    @classmethod\n    def _validate_model_type(cls, model_type, provider, raise_exception=False):\n        if not any(mt['value'] == model_type for mt in provider.get_model_type_list()):\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('{model_type} Model type is not supported').format(model_type=model_type))\n            return False\n        return True\n\n    @classmethod\n    def _validate_credential_fields(cls, model_credential, raise_exception=False):\n        missing_keys = [key for key in cls.REQUIRED_FIELDS if key not in model_credential]\n        if missing_keys:\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('{keys} is required').format(keys=\", \".join(missing_keys)))\n            return False\n        return True\n\n    def is_valid(self, model_type, model_name, model_credential, model_params, provider, raise_exception=False):\n        if not (self._validate_model_type(model_type, provider, raise_exception) and\n                self._validate_credential_fields(model_credential, raise_exception)):\n            return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            return False\n        return True\n\n    def encryption_dict(self, model):\n        return {**model, 'hunyuan_secret_key': super().encryption(model.get('hunyuan_secret_key', ''))}\n\n    hunyuan_secret_id = forms.PasswordInputField('SecretId', required=True)\n    hunyuan_secret_key = forms.PasswordInputField('SecretKey', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return TencentTTIModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/model/embedding.py",
    "content": " \nfrom typing import Dict, List\n\nfrom langchain_core.embeddings import Embeddings\nfrom tencentcloud.common import credential\nfrom tencentcloud.hunyuan.v20230901.hunyuan_client import HunyuanClient\nfrom tencentcloud.hunyuan.v20230901.models import GetEmbeddingRequest\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass TencentEmbeddingModel(MaxKBBaseModel, Embeddings):\n    def embed_documents(self, texts: List[str]) -> List[List[float]]:\n        return [self.embed_query(text) for text in texts]\n\n    def embed_query(self, text: str) -> List[float]:\n        request = GetEmbeddingRequest()\n        request.Input = text\n        res = self.client.GetEmbedding(request)\n        return res.Data[0].Embedding\n\n    def __init__(self, secret_id: str, secret_key: str, model_name: str):\n        self.secret_id = secret_id\n        self.secret_key = secret_key\n        self.model_name = model_name\n        cred = credential.Credential(\n            secret_id, secret_key\n        )\n        self.client = HunyuanClient(cred, \"\")\n\n    @staticmethod\n    def new_instance(model_type: str, model_name: str, model_credential: Dict[str, str], **model_kwargs):\n        return TencentEmbeddingModel(\n            secret_id=model_credential.get('SecretId'),\n            secret_key=model_credential.get('SecretKey'),\n            model_name=model_name,\n        )\n\n    def _generate_auth_token(self):\n        # Example method to generate an authentication token for the model API\n        return f\"{self.secret_id}:{self.secret_key}\"\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/model/hunyuan.py",
    "content": "import json\nimport logging\nfrom typing import Any, Dict, Iterator, List, Mapping, Optional, Type\n\nfrom langchain_core.callbacks import CallbackManagerForLLMRun\nfrom langchain_core.language_models.chat_models import (\n    BaseChatModel,\n    generate_from_stream,\n)\nfrom langchain_core.messages import (\n    AIMessage,\n    AIMessageChunk,\n    BaseMessage,\n    BaseMessageChunk,\n    ChatMessage,\n    ChatMessageChunk,\n    HumanMessage,\n    HumanMessageChunk, SystemMessage,\n)\nfrom langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult\nfrom pydantic import Field, SecretStr, root_validator\nfrom langchain_core.utils import (\n    convert_to_secret_str,\n    get_from_dict_or_env,\n    get_pydantic_field_names,\n    pre_init,\n)\n\nlogger = logging.getLogger(__name__)\n\n\ndef _convert_message_to_dict(message: BaseMessage) -> dict:\n    message_dict: Dict[str, Any]\n    if isinstance(message, ChatMessage):\n        message_dict = {\"Role\": message.role, \"Content\": message.content}\n    elif isinstance(message, HumanMessage):\n        message_dict = {\"Role\": \"user\", \"Content\": message.content}\n    elif isinstance(message, AIMessage):\n        message_dict = {\"Role\": \"assistant\", \"Content\": message.content}\n    elif isinstance(message, SystemMessage):\n        message_dict = {\"Role\": \"system\", \"Content\": message.content}\n    else:\n        raise TypeError(f\"Got unknown type {message}\")\n\n    return message_dict\n\n\ndef _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage:\n    role = _dict[\"Role\"]\n    if role == \"user\":\n        return HumanMessage(content=_dict[\"Content\"])\n    elif role == \"assistant\":\n        return AIMessage(content=_dict.get(\"Content\", \"\") or \"\")\n    else:\n        return ChatMessage(content=_dict[\"Content\"], role=role)\n\n\ndef _convert_delta_to_message_chunk(\n        _dict: Mapping[str, Any], default_class: Type[BaseMessageChunk]\n) -> BaseMessageChunk:\n    role = _dict.get(\"Role\")\n    content = _dict.get(\"Content\") or \"\"\n\n    if role == \"user\" or default_class == HumanMessageChunk:\n        return HumanMessageChunk(content=content)\n    elif role == \"assistant\" or default_class == AIMessageChunk:\n        return AIMessageChunk(content=content)\n    elif role or default_class == ChatMessageChunk:\n        return ChatMessageChunk(content=content, role=role)  # type: ignore[arg-type]\n    else:\n        return default_class(content=content)  # type: ignore[call-arg]\n\n\ndef _create_chat_result(response: Mapping[str, Any]) -> ChatResult:\n    generations = []\n    for choice in response[\"Choices\"]:\n        message = _convert_dict_to_message(choice[\"Message\"])\n        generations.append(ChatGeneration(message=message))\n\n    token_usage = response[\"Usage\"]\n    llm_output = {\"token_usage\": token_usage}\n    return ChatResult(generations=generations, llm_output=llm_output)\n\n\nclass ChatHunyuan(BaseChatModel):\n    \"\"\"Tencent Hunyuan chat models API by Tencent.\n\n    For more information, see https://cloud.tencent.com/document/product/1729\n    \"\"\"\n\n    @property\n    def lc_secrets(self) -> Dict[str, str]:\n        return {\n            \"hunyuan_app_id\": \"HUNYUAN_APP_ID\",\n            \"hunyuan_secret_id\": \"HUNYUAN_SECRET_ID\",\n            \"hunyuan_secret_key\": \"HUNYUAN_SECRET_KEY\",\n        }\n\n    @property\n    def lc_serializable(self) -> bool:\n        return True\n\n    hunyuan_app_id: Optional[int] = None\n    \"\"\"Hunyuan App ID\"\"\"\n    hunyuan_secret_id: Optional[str] = None\n    \"\"\"Hunyuan Secret ID\"\"\"\n    hunyuan_secret_key: Optional[SecretStr] = None\n    \"\"\"Hunyuan Secret Key\"\"\"\n    streaming: bool = False\n    \"\"\"Whether to stream the results or not.\"\"\"\n    request_timeout: int = 60\n    \"\"\"Timeout for requests to Hunyuan API. Default is 60 seconds.\"\"\"\n    temperature: float = 1.0\n    \"\"\"What sampling temperature to use.\"\"\"\n    top_p: float = 1.0\n    \"\"\"What probability mass to use.\"\"\"\n    model: str = \"hunyuan-lite\"\n    \"\"\"What Model to use.\n     Optional model:\n    - hunyuan-lite、\n    - hunyuan-standard\n    - hunyuan-standard-256K\n    - hunyuan-pro\n    - hunyuan-code\n    - hunyuan-role\n    - hunyuan-functioncall\n    - hunyuan-vision\n    \"\"\"\n    stream_moderation: bool = False\n    \"\"\"Whether to review the results or not when streaming is true.\"\"\"\n    enable_enhancement: bool = True\n    \"\"\"Whether to enhancement the results or not.\"\"\"\n\n    model_kwargs: Dict[str, Any] = Field(default_factory=dict)\n    \"\"\"Holds any model parameters valid for API call not explicitly specified.\"\"\"\n\n    class Config:\n        \"\"\"Configuration for this pydantic object.\"\"\"\n\n        validate_by_name = True\n\n    @root_validator(pre=True)\n    def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"Build extra kwargs from additional params that were passed in.\"\"\"\n        all_required_field_names = get_pydantic_field_names(cls)\n        extra = values.get(\"model_kwargs\", {})\n        for field_name in list(values):\n            if field_name in extra:\n                raise ValueError(f\"Found {field_name} supplied twice.\")\n            if field_name not in all_required_field_names:\n                logger.warning(\n                    f\"\"\"WARNING! {field_name} is not default parameter.\n                    {field_name} was transferred to model_kwargs.\n                    Please confirm that {field_name} is what you intended.\"\"\"\n                )\n                extra[field_name] = values.pop(field_name)\n\n        invalid_model_kwargs = all_required_field_names.intersection(extra.keys())\n        if invalid_model_kwargs:\n            raise ValueError(\n                f\"Parameters {invalid_model_kwargs} should be specified explicitly. \"\n                f\"Instead they were passed in as part of `model_kwargs` parameter.\"\n            )\n\n        values[\"model_kwargs\"] = extra\n        return values\n\n    @pre_init\n    def validate_environment(cls, values: Dict) -> Dict:\n        values[\"hunyuan_app_id\"] = get_from_dict_or_env(\n            values,\n            \"hunyuan_app_id\",\n            \"HUNYUAN_APP_ID\",\n        )\n        values[\"hunyuan_secret_id\"] = get_from_dict_or_env(\n            values,\n            \"hunyuan_secret_id\",\n            \"HUNYUAN_SECRET_ID\",\n        )\n        values[\"hunyuan_secret_key\"] = convert_to_secret_str(\n            get_from_dict_or_env(\n                values,\n                \"hunyuan_secret_key\",\n                \"HUNYUAN_SECRET_KEY\",\n            )\n        )\n        return values\n\n    @property\n    def _default_params(self) -> Dict[str, Any]:\n        \"\"\"Get the default parameters for calling Hunyuan API.\"\"\"\n        normal_params = {\n            \"Temperature\": self.temperature,\n            \"TopP\": self.top_p,\n            \"Model\": self.model,\n            \"Stream\": self.streaming,\n            \"StreamModeration\": self.stream_moderation,\n            \"EnableEnhancement\": self.enable_enhancement,\n        }\n        return {**normal_params, **self.model_kwargs}\n\n    def _generate(\n            self,\n            messages: List[BaseMessage],\n            stop: Optional[List[str]] = None,\n            run_manager: Optional[CallbackManagerForLLMRun] = None,\n            **kwargs: Any,\n    ) -> ChatResult:\n        if self.streaming:\n            stream_iter = self._stream(\n                messages=messages, stop=stop, run_manager=run_manager, **kwargs\n            )\n            return generate_from_stream(stream_iter)\n\n        res = self._chat(messages, **kwargs)\n        return _create_chat_result(json.loads(res.to_json_string()))\n\n    usage_metadata: dict = {}\n\n    def _stream(\n            self,\n            messages: List[BaseMessage],\n            stop: Optional[List[str]] = None,\n            run_manager: Optional[CallbackManagerForLLMRun] = None,\n            **kwargs: Any,\n    ) -> Iterator[ChatGenerationChunk]:\n        res = self._chat(messages, **kwargs)\n\n        default_chunk_class = AIMessageChunk\n        for chunk in res:\n            chunk = chunk.get(\"data\", \"\")\n            if len(chunk) == 0:\n                continue\n            response = json.loads(chunk)\n            if \"error\" in response:\n                raise ValueError(f\"Error from Hunyuan api response: {response}\")\n\n            for choice in response[\"Choices\"]:\n                chunk = _convert_delta_to_message_chunk(\n                    choice[\"Delta\"], default_chunk_class\n                )\n                default_chunk_class = chunk.__class__\n                # FinishReason === stop\n                if choice.get(\"FinishReason\") == \"stop\":\n                    self.usage_metadata = response.get(\"Usage\", {})\n                cg_chunk = ChatGenerationChunk(message=chunk)\n                if run_manager:\n                    run_manager.on_llm_new_token(chunk.content, chunk=cg_chunk)\n                yield cg_chunk\n\n    def _chat(self, messages: List[BaseMessage], **kwargs: Any) -> Any:\n        if self.hunyuan_secret_key is None:\n            raise ValueError(\"Hunyuan secret key is not set.\")\n\n        try:\n            from tencentcloud.common import credential\n            from tencentcloud.hunyuan.v20230901 import hunyuan_client, models\n        except ImportError:\n            raise ImportError(\n                \"Could not import tencentcloud python package. \"\n                \"Please install it with `pip install tencentcloud-sdk-python`.\"\n            )\n\n        parameters = {**self._default_params, **kwargs}\n        cred = credential.Credential(\n            self.hunyuan_secret_id, str(self.hunyuan_secret_key.get_secret_value())\n        )\n        client = hunyuan_client.HunyuanClient(cred, \"\")\n        req = models.ChatCompletionsRequest()\n        params = {\n            \"Messages\": [_convert_message_to_dict(m) for m in messages],\n            **parameters,\n        }\n        req.from_json_string(json.dumps(params))\n        resp = client.ChatCompletions(req)\n        return resp\n\n    @property\n    def _llm_type(self) -> str:\n        return \"hunyuan-chat\"\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/model/image.py",
    "content": "from typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass TencentVision(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return TencentVision(\n            model_name=model_name,\n            openai_api_base=model_credential.get('api_base') or 'https://api.hunyuan.cloud.tencent.com/v1',\n            openai_api_key=model_credential.get('api_key'),\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n\n    @staticmethod\n    def is_cache_model():\n        return False\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\nfrom typing import List, Dict, Optional, Any\n\nfrom langchain_core.messages import BaseMessage\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.tencent_model_provider.model.hunyuan import ChatHunyuan\n\n\nclass TencentModel(MaxKBBaseModel, ChatHunyuan):\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    def __init__(self, model_name: str, credentials: Dict[str, str], streaming: bool = False, **kwargs):\n        hunyuan_app_id = credentials.get('hunyuan_app_id')\n        hunyuan_secret_id = credentials.get('hunyuan_secret_id')\n        hunyuan_secret_key = credentials.get('hunyuan_secret_key')\n\n        optional_params = MaxKBBaseModel.filter_optional_params(kwargs)\n\n        if not all([hunyuan_app_id, hunyuan_secret_id, hunyuan_secret_key]):\n            raise ValueError(\n                \"All of 'hunyuan_app_id', 'hunyuan_secret_id', and 'hunyuan_secret_key' must be provided in credentials.\")\n\n        super().__init__(model=model_name, hunyuan_app_id=hunyuan_app_id, hunyuan_secret_id=hunyuan_secret_id,\n                         hunyuan_secret_key=hunyuan_secret_key, streaming=streaming,\n                         temperature=optional_params.get('temperature', 1.0)\n                         )\n\n    @staticmethod\n    def new_instance(model_type: str, model_name: str, model_credential: Dict[str, object],\n                     **model_kwargs) -> 'TencentModel':\n        streaming = model_kwargs.pop('streaming', False)\n        return TencentModel(model_name=model_name, credentials=model_credential, streaming=streaming, **model_kwargs)\n\n    def get_last_generation_info(self) -> Optional[Dict[str, Any]]:\n        return self.usage_metadata\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        return self.usage_metadata.get('PromptTokens', 0)\n\n    def get_num_tokens(self, text: str) -> int:\n        return self.usage_metadata.get('CompletionTokens', 0)\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/model/tti.py",
    "content": "# coding=utf-8\n\nimport json\nimport logging\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\nfrom tencentcloud.common import credential\nfrom tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException\nfrom tencentcloud.common.profile.client_profile import ClientProfile\nfrom tencentcloud.common.profile.http_profile import HttpProfile\nfrom tencentcloud.hunyuan.v20230901 import hunyuan_client, models\n\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\nfrom models_provider.impl.tencent_model_provider.model.hunyuan import ChatHunyuan\n\n\nclass TencentTextToImageModel(MaxKBBaseModel, BaseTextToImage):\n    hunyuan_secret_id: str\n    hunyuan_secret_key: str\n    model: str\n    params: dict\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.hunyuan_secret_id = kwargs.get('hunyuan_secret_id')\n        self.hunyuan_secret_key = kwargs.get('hunyuan_secret_key')\n        self.model = kwargs.get('model_name')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def new_instance(model_type: str, model_name: str, model_credential: Dict[str, object],\n                     **model_kwargs) -> 'TencentTextToImageModel':\n        optional_params = {'params': {'Style': '201', 'Resolution': '768:768'}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return TencentTextToImageModel(\n            model=model_name,\n            hunyuan_secret_id=model_credential.get('hunyuan_secret_id'),\n            hunyuan_secret_key=model_credential.get('hunyuan_secret_key'),\n            **optional_params\n        )\n\n    def check_auth(self):\n        chat = ChatHunyuan(hunyuan_app_id='111111',\n                           hunyuan_secret_id=self.hunyuan_secret_id,\n                           hunyuan_secret_key=self.hunyuan_secret_key,\n                           model=\"hunyuan-standard\")\n        res = chat.invoke(_('Hello'))\n        # print(res)\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        try:\n            # 实例化一个认证对象，入参需要传入腾讯云账户 SecretId 和 SecretKey，此处还需注意密钥对的保密\n            # 代码泄露可能会导致 SecretId 和 SecretKey 泄露，并威胁账号下所有资源的安全性。以下代码示例仅供参考，建议采用更安全的方式来使用密钥，请参见：https://cloud.tencent.com/document/product/1278/85305\n            # 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取\n            cred = credential.Credential(self.hunyuan_secret_id, self.hunyuan_secret_key)\n            # 实例化一个http选项，可选的，没有特殊需求可以跳过\n            httpProfile = HttpProfile()\n            httpProfile.endpoint = \"hunyuan.tencentcloudapi.com\"\n\n            # 实例化一个client选项，可选的，没有特殊需求可以跳过\n            clientProfile = ClientProfile()\n            clientProfile.httpProfile = httpProfile\n            # 实例化要请求产品的client对象,clientProfile是可选的\n            client = hunyuan_client.HunyuanClient(cred, \"ap-guangzhou\", clientProfile)\n\n            # 实例化一个请求对象,每个接口都会对应一个request对象\n            req = models.TextToImageLiteRequest()\n            params = {\n                \"Prompt\": prompt,\n                \"NegativePrompt\": negative_prompt,\n                \"RspImgType\": \"url\",\n                **self.params\n            }\n            req.from_json_string(json.dumps(params))\n\n            # 返回的resp是一个TextToImageLiteResponse的实例，与请求对象对应\n            resp = client.TextToImageLite(req)\n            file_urls = []\n\n            file_urls.append(resp.ResultImage)\n            return file_urls\n        except TencentCloudSDKException as err:\n            maxkb_logger.error(f\"Tencent Text to Image API call failed: {err}\")\n            raise f\"Tencent Text to Image API call failed: {err}\"\n"
  },
  {
    "path": "apps/models_provider/impl/tencent_model_provider/tencent_model_provider.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\nimport os\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import (\n    IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, ModelInfoManage\n)\nfrom models_provider.impl.tencent_model_provider.credential.embedding import TencentEmbeddingCredential\nfrom models_provider.impl.tencent_model_provider.credential.image import TencentVisionModelCredential\nfrom models_provider.impl.tencent_model_provider.credential.llm import TencentLLMModelCredential\nfrom models_provider.impl.tencent_model_provider.credential.stt import TencentSTTModelCredential\nfrom models_provider.impl.tencent_model_provider.credential.tti import TencentTTIModelCredential\nfrom models_provider.impl.tencent_model_provider.model.embedding import TencentEmbeddingModel\nfrom models_provider.impl.tencent_model_provider.model.image import TencentVision\nfrom models_provider.impl.tencent_model_provider.model.llm import TencentModel\nfrom models_provider.impl.tencent_model_provider.model.stt import TencentSpeechToText\nfrom models_provider.impl.tencent_model_provider.model.tti import TencentTextToImageModel\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\ndef _create_model_info(model_name, description, model_type, credential_class, model_class):\n    return ModelInfo(\n        name=model_name,\n        desc=description,\n        model_type=model_type,\n        model_credential=credential_class(),\n        model_class=model_class\n    )\n\n\ndef _get_tencent_icon_path():\n    return os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'tencent_model_provider',\n                        'icon', 'tencent_icon_svg')\n\n\ndef _initialize_model_info():\n    model_info_list = [_create_model_info(\n        'hunyuan-pro',\n        _('The most effective version of the current hybrid model, the trillion-level parameter scale MOE-32K long article model. Reaching the absolute leading level on various benchmarks, with complex instructions and reasoning, complex mathematical capabilities, support for function call, and application focus optimization in fields such as multi-language translation, finance, law, and medical care'),\n        ModelTypeConst.LLM,\n        TencentLLMModelCredential,\n        TencentModel\n    ),\n        _create_model_info(\n            'hunyuan-standard',\n            _('A better routing strategy is adopted to simultaneously alleviate the problems of load balancing and expert convergence. For long articles, the needle-in-a-haystack index reaches 99.9%'),\n            ModelTypeConst.LLM,\n            TencentLLMModelCredential,\n            TencentModel),\n        _create_model_info(\n            'hunyuan-lite',\n            _('Upgraded to MOE structure, the context window is 256k, leading many open source models in multiple evaluation sets such as NLP, code, mathematics, industry, etc.'),\n            ModelTypeConst.LLM,\n            TencentLLMModelCredential,\n            TencentModel),\n        _create_model_info(\n            'hunyuan-role',\n            _(\"Hunyuan's latest version of the role-playing model, a role-playing model launched by Hunyuan's official fine-tuning training, is based on the Hunyuan model combined with the role-playing scene data set for additional training, and has better basic effects in role-playing scenes.\"),\n            ModelTypeConst.LLM,\n            TencentLLMModelCredential,\n            TencentModel),\n        _create_model_info(\n            'hunyuan-functioncall',\n            _(\"Hunyuan's latest MOE architecture FunctionCall model has been trained with high-quality FunctionCall data and has a context window of 32K, leading in multiple dimensions of evaluation indicators.\"),\n            ModelTypeConst.LLM,\n            TencentLLMModelCredential,\n            TencentModel),\n        _create_model_info(\n            'hunyuan-code',\n            _(\"Hunyuan's latest code generation model, after training the base model with 200B high-quality code data, and iterating on high-quality SFT data for half a year, the context long window length has been increased to 8K, and it ranks among the top in the automatic evaluation indicators of code generation in the five major languages; the five major languages In the manual high-quality evaluation of 10 comprehensive code tasks that consider all aspects, the performance is in the first echelon.\"),\n            ModelTypeConst.LLM,\n            TencentLLMModelCredential,\n            TencentModel),\n        _create_model_info(\n            'asr-sentence',\n            _(\"This interface is used to recognize short audio files within 60 seconds. Supports Mandarin Chinese, English, Cantonese, Japanese, Vietnamese, Malay, Indonesian, Filipino, Thai, Portuguese, Turkish, Arabic, Hindi, French, German, and 23 Chinese dialects.\"),\n            ModelTypeConst.STT,\n            TencentSTTModelCredential,\n            TencentSpeechToText),\n    ]\n\n    tencent_embedding_model_info = _create_model_info(\n        'hunyuan-embedding',\n        _(\"Tencent's Hunyuan Embedding interface can convert text into high-quality vector data. The vector dimension is 1024 dimensions.\"),\n        ModelTypeConst.EMBEDDING,\n        TencentEmbeddingCredential,\n        TencentEmbeddingModel\n    )\n\n    model_info_embedding_list = [tencent_embedding_model_info]\n\n    model_info_vision_list = [_create_model_info(\n        'hunyuan-vision',\n        _('Mixed element visual model'),\n        ModelTypeConst.IMAGE,\n        TencentVisionModelCredential,\n        TencentVision)]\n\n    model_info_tti_list = [_create_model_info(\n        'hunyuan-dit',\n        _('Hunyuan graph model'),\n        ModelTypeConst.TTI,\n        TencentTTIModelCredential,\n        TencentTextToImageModel)]\n\n    model_info_manage = ModelInfoManage.builder() \\\n        .append_model_info_list(model_info_list) \\\n        .append_model_info_list(model_info_embedding_list) \\\n        .append_model_info_list(model_info_vision_list) \\\n        .append_default_model_info(model_info_vision_list[0]) \\\n        .append_model_info_list(model_info_tti_list) \\\n        .append_default_model_info(model_info_tti_list[0]) \\\n        .append_default_model_info(model_info_list[0]) \\\n        .append_default_model_info(tencent_embedding_model_info) \\\n        .build()\n\n    return model_info_manage\n\n\nclass TencentModelProvider(IModelProvider):\n    def __init__(self):\n        self._model_info_manage = _initialize_model_info()\n\n    def get_model_info_manage(self):\n        return self._model_info_manage\n\n    def get_model_provide_info(self):\n        icon_path = _get_tencent_icon_path()\n        icon_data = get_file_content(icon_path)\n        return ModelProvideInfo(\n            provider='model_tencent_provider',\n            name=_('Tencent Hunyuan'),\n            icon=icon_data\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/__init__.py",
    "content": "# coding=utf-8\n"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 16:45\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass VllmEmbeddingCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=True):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/credential/image.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass VllmImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass VllmImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": \"你好\"}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return VllmImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/credential/llm.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass VLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass VLLMModelCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n        try:\n            model_list = provider.get_base_model_list(model_credential.get('api_base'), model_credential.get('api_key'))\n        except Exception as e:\n            raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid'))\n        exist = provider.get_model_info_by_name(model_list, model_name)\n        if len(exist) == 0:\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('The model does not exist, please download the model first'))\n        model = provider.get_model(model_type, model_name, model_credential, **model_params)\n        try:\n            res = model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext(\n                                      'Verification failed, please check whether the parameters are correct: {error}').format(\n                                      error=str(e)))\n        return True\n\n    def encryption_dict(self, model_info: Dict[str, object]):\n        return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))}\n\n    def build_model(self, model_info: Dict[str, object]):\n        for key in ['api_key', 'model']:\n            if key not in model_info:\n                raise AppApiException(500, gettext('{key}  is required').format(key=key))\n        self.api_key = model_info.get('api_key')\n        return self\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return VLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/credential/reranker.py",
    "content": "from typing import Dict\n\nfrom langchain_core.documents import Document\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom django.utils.translation import gettext_lazy as _\n\nfrom models_provider.impl.vllm_model_provider.model.reranker import VllmBgeReranker\nfrom common.utils.logger import maxkb_logger\n\nclass VllmRerankerCredential(BaseForm, BaseModelCredential):\n    api_url = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=True):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_url', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model: VllmBgeReranker = provider.get_model(model_type, model_name, model_credential)\n            test_text = str(_('Hello'))\n            model.compress_documents([Document(page_content=test_text)], test_text)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(\n                    ValidCode.valid_error.value,\n                    _('Verification failed, please check whether the parameters are correct: {error}').format(\n                        error=str(e))\n                )\n            return False\n\n        return True\n\n    def encryption_dict(self, model_info: Dict[str, object]):\n        return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))}"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/credential/whisper_stt.py",
    "content": "# coding=utf-8\nimport traceback\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass VLLMWhisperModelParams(BaseForm):\n    Language = forms.TextInputField(\n        TooltipLabel(_('language'),\n                     _(\"If not passed, the default value is 'zh'\")),\n        required=True,\n        default_value='zh',\n    )\n\n\nclass VLLMWhisperModelCredential(BaseForm, BaseModelCredential):\n    api_url = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self,\n                 model_type: str,\n                 model_name,\n                 model_credential: Dict[str, object],\n                 model_params,\n                 provider,\n                 raise_exception=False):\n\n        model_type_list = provider.get_model_type_list()\n\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n        try:\n            model_list = provider.get_base_model_list(model_credential.get('api_url'), model_credential.get('api_key'))\n        except Exception as e:\n            raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid'))\n        exist = provider.get_model_info_by_name(model_list, model_name)\n        if len(exist) == 0:\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('The model does not exist, please download the model first'))\n        model = provider.get_model(model_type, model_name, model_credential, **model_params)\n        return True\n\n    def encryption_dict(self, model_info: Dict[str, object]):\n        return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))}\n\n    def build_model(self, model_info: Dict[str, object]):\n        for key in ['api_key', 'model']:\n            if key not in model_info:\n                raise AppApiException(500, gettext('{key}  is required').format(key=key))\n        self.api_key = model_info.get('api_key')\n        return self\n\n    def get_model_params_setting_form(self, model_name):\n        return VLLMWhisperModelParams()"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 17:44\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom langchain_openai import OpenAIEmbeddings\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass VllmEmbeddingModel(MaxKBBaseModel, OpenAIEmbeddings):\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return VllmEmbeddingModel(\n            model=model_name,\n            openai_api_key=model_credential.get('api_key'),\n            openai_api_base=model_credential.get('api_base'),\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/model/image.py",
    "content": "from typing import Dict, List\n\nfrom langchain_core.messages import get_buffer_string, BaseMessage\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass VllmImage(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return VllmImage(\n            model_name=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n\n    def is_cache_model(self):\n        return False\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        if self.usage_metadata is None or self.usage_metadata == {}:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n        return self.usage_metadata.get('input_tokens', 0)\n\n    def get_num_tokens(self, text: str) -> int:\n        if self.usage_metadata is None or self.usage_metadata == {}:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n        return self.get_last_generation_info().get('output_tokens', 0)\n"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\nfrom typing import Dict, List\nfrom urllib.parse import urlparse, ParseResult\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\ndef get_base_url(url: str):\n    parse = urlparse(url)\n    result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='',\n                             query='',\n                             fragment='').geturl()\n    return result_url[:-1] if result_url.endswith(\"/\") else result_url\n\n\nclass VllmChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        vllm_chat_open_ai = VllmChatModel(\n            model=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            extra_body=optional_params,\n            streaming=True,\n            stream_usage=True,\n        )\n        return vllm_chat_open_ai\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        if self.usage_metadata is None or self.usage_metadata == {}:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n        return self.usage_metadata.get('input_tokens', 0)\n\n    def get_num_tokens(self, text: str) -> int:\n        if self.usage_metadata is None or self.usage_metadata == {}:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n        return self.get_last_generation_info().get('output_tokens', 0)\n"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/model/reranker.py",
    "content": "from typing import Sequence, Optional, Dict, Any\n\nimport cohere\nfrom langchain_core.callbacks import Callbacks\nfrom langchain_core.documents import BaseDocumentCompressor, Document\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass VllmBgeReranker(MaxKBBaseModel, BaseDocumentCompressor):\n    api_key: str\n    api_url: str\n    model: str\n    params: dict\n    client: Any = None\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n        self.api_url = kwargs.get('api_url')\n        self.client = cohere.ClientV2(kwargs.get('api_key'), base_url=kwargs.get('api_url'))\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        r_url = model_credential.get('api_url')[:-3] if model_credential.get('api_url').endswith('/v1') else model_credential.get('api_url')\n        return VllmBgeReranker(\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            api_url=r_url,\n            params=model_kwargs,\n            **model_kwargs\n        )\n\n    def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \\\n            Sequence[Document]:\n        if documents is None or len(documents) == 0:\n            return []\n\n        ds = [d.page_content for d in documents]\n        result = self.client.rerank(model=self.model, query=query, documents=ds)\n        return [Document(page_content=d.document.get('text'), metadata={'relevance_score': d.relevance_score}) for d in\n                result.results]\n"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/model/whisper_sst.py",
    "content": "import base64\nimport os\nimport traceback\nfrom typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\n\nclass VllmWhisperSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    api_key: str\n    api_url: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n        self.api_url = kwargs.get('api_url')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return VllmWhisperSpeechToText(\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            api_url=model_credential.get('api_url'),\n            params=model_kwargs,\n            **model_kwargs\n        )\n\n    def check_auth(self):\n        cwd = os.path.dirname(os.path.abspath(__file__))\n        with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as audio_file:\n            self.speech_to_text(audio_file)\n\n    def speech_to_text(self, audio_file):\n\n        base_url = self.api_url if self.api_url.endswith('v1') else f\"{self.api_url}/v1\"\n\n        try:\n            client = OpenAI(\n                api_key=self.api_key,\n                base_url=base_url\n            )\n            buf = audio_file.read()\n            filter_params = {k: v for k, v in self.params.items() if k not in {'model_id', 'use_local', 'streaming'}}\n            transcription_params = {\n                'model': self.model,\n                'file': buf,\n                'language': 'zh',\n            }\n            result = client.audio.transcriptions.create(\n                **transcription_params, extra_body=filter_params\n            )\n\n            return result.text\n\n        except Exception as err:\n            maxkb_logger.error(f\":Error: {str(err)}: {traceback.format_exc()}\")\n            raise err"
  },
  {
    "path": "apps/models_provider/impl/vllm_model_provider/vllm_model_provider.py",
    "content": "# coding=utf-8\nimport os\nfrom urllib.parse import urlparse, ParseResult\n\nimport requests\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \\\n    ModelInfoManage\nfrom models_provider.impl.vllm_model_provider.credential.embedding import VllmEmbeddingCredential\nfrom models_provider.impl.vllm_model_provider.credential.image import VllmImageModelCredential\nfrom models_provider.impl.vllm_model_provider.credential.llm import VLLMModelCredential\nfrom models_provider.impl.vllm_model_provider.credential.reranker import VllmRerankerCredential\nfrom models_provider.impl.vllm_model_provider.credential.whisper_stt import VLLMWhisperModelCredential\nfrom models_provider.impl.vllm_model_provider.model.embedding import VllmEmbeddingModel\nfrom models_provider.impl.vllm_model_provider.model.image import VllmImage\nfrom models_provider.impl.vllm_model_provider.model.llm import VllmChatModel\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\nfrom models_provider.impl.vllm_model_provider.model.reranker import VllmBgeReranker\nfrom models_provider.impl.vllm_model_provider.model.whisper_sst import VllmWhisperSpeechToText\n\nv_llm_model_credential = VLLMModelCredential()\nimage_model_credential = VllmImageModelCredential()\nembedding_model_credential = VllmEmbeddingCredential()\nwhisper_model_credential = VLLMWhisperModelCredential()\nrerank_model_credential = VllmRerankerCredential()\n\nmodel_info_list = [\n    ModelInfo('facebook/opt-125m', _('Facebook’s 125M parameter model'), ModelTypeConst.LLM, v_llm_model_credential,\n              VllmChatModel),\n    ModelInfo('BAAI/Aquila-7B', _('BAAI’s 7B parameter model'), ModelTypeConst.LLM, v_llm_model_credential,\n              VllmChatModel),\n    ModelInfo('BAAI/AquilaChat-7B', _('BAAI’s 13B parameter mode'), ModelTypeConst.LLM, v_llm_model_credential,\n              VllmChatModel),\n\n]\n\nimage_model_info_list = [\n    ModelInfo('Qwen/Qwen2-VL-2B-Instruct', '', ModelTypeConst.IMAGE, image_model_credential, VllmImage),\n]\n\nembedding_model_info_list = [\n    ModelInfo('HIT-TMG/KaLM-embedding-multilingual-mini-instruct-v1.5', '', ModelTypeConst.EMBEDDING,\n              embedding_model_credential, VllmEmbeddingModel),\n]\n\nwhisper_model_info_list = [\n    ModelInfo('whisper-tiny', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText),\n    ModelInfo('whisper-large-v3-turbo', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText),\n    ModelInfo('whisper-small', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText),\n    ModelInfo('whisper-large-v3', '', ModelTypeConst.STT, whisper_model_credential, VllmWhisperSpeechToText),\n]\n\nreranker_model_info_list = [\n    ModelInfo('BAAI/bge-reranker-v2-m3', '', ModelTypeConst.RERANKER, rerank_model_credential, VllmBgeReranker),\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_default_model_info(ModelInfo('facebook/opt-125m',\n                                         _('Facebook’s 125M parameter model'),\n                                         ModelTypeConst.LLM, v_llm_model_credential, VllmChatModel))\n    .append_model_info_list(image_model_info_list)\n    .append_default_model_info(image_model_info_list[0])\n    .append_model_info_list(embedding_model_info_list)\n    .append_default_model_info(embedding_model_info_list[0])\n    .append_model_info_list(whisper_model_info_list)\n    .append_default_model_info(whisper_model_info_list[0])\n    .append_model_info_list(reranker_model_info_list)\n    .append_default_model_info(reranker_model_info_list[0])\n    .build()\n)\n\n\ndef get_base_url(url: str):\n    parse = urlparse(url)\n    result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='',\n                             query='',\n                             fragment='').geturl()\n    return result_url[:-1] if result_url.endswith(\"/\") else result_url\n\n\nclass VllmModelProvider(IModelProvider):\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_vllm_provider', name='vLLM', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'vllm_model_provider', 'icon',\n                         'vllm_icon_svg')))\n\n    @staticmethod\n    def get_base_model_list(api_base, api_key):\n        base_url = get_base_url(api_base)\n        base_url = base_url if base_url.endswith('/v1') else (base_url + '/v1')\n        headers = {}\n        if api_key:\n            headers['Authorization'] = f\"Bearer {api_key}\"\n        r = requests.request(method=\"GET\", url=f\"{base_url}/models\", headers=headers, timeout=5)\n        r.raise_for_status()\n        return r.json().get('data')\n\n    @staticmethod\n    def get_model_info_by_name(model_list, model_name):\n        if model_list is None:\n            return []\n        return [model for model in model_list if model.get('id') == model_name]\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/credential/bigModel_stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\n\nclass VolcanicEngineBigModelSTTModelParams(BaseForm):\n    uid = forms.TextInputField(\n        TooltipLabel(_('User ID'), _('If not passed, the default value is streaming_asr_demo')),\n        required=True,\n        default_value='streaming_asr_demo'\n    )\n\n\nclass VolcanicEngineBigModelSTTModelCredential(BaseForm, BaseModelCredential):\n    volcanic_app_id = forms.TextInputField('App ID', required=True)\n    volcanic_token = forms.PasswordInputField('Access Token', required=True)\n    volcanic_api_url = forms.TextInputField('API URL', required=True,\n                                            default_value='https://openspeech.bytedance.com/api/v3/auc/bigmodel/recognize/flash')\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['volcanic_api_url', 'volcanic_app_id', 'volcanic_token']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'volcanic_token': super().encryption(model.get('volcanic_token', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return VolcanicEngineBigModelSTTModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/7/12 16:45\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass VolcanicEmbeddingCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=True):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/credential/image.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass VolcanicEngineImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.95,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=1024,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass VolcanicEngineImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key', 'api_base']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext('Hello')}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return VolcanicEngineImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/11 17:57\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass VolcanicEngineLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.3,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=1024,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass VolcanicEngineLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['access_key_id', 'secret_access_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'access_key_id': super().encryption(model.get('access_key_id', ''))}\n\n    access_key_id = forms.PasswordInputField('Access Key ID', required=True)\n    secret_access_key = forms.PasswordInputField('Secret Access Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return VolcanicEngineLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/credential/stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass VolcanicEngineSTTModelParams(BaseForm):\n    uid = forms.TextInputField(\n        TooltipLabel(_('User ID'),_('If not passed, the default value is streaming_asr_demo')),\n        required=True,\n        default_value='streaming_asr_demo'\n    )\n\n\n\nclass VolcanicEngineSTTModelCredential(BaseForm, BaseModelCredential):\n    volcanic_api_url = forms.TextInputField('API URL', required=True,\n                                            default_value='wss://openspeech.bytedance.com/api/v2/asr')\n    volcanic_app_id = forms.TextInputField('App ID', required=True)\n    volcanic_token = forms.PasswordInputField('Access Token', required=True)\n    volcanic_cluster = forms.TextInputField('Cluster ID', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['volcanic_api_url', 'volcanic_app_id', 'volcanic_token', 'volcanic_cluster']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'volcanic_token': super().encryption(model.get('volcanic_token', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return VolcanicEngineSTTModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/credential/tti.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\n\nclass VolcanicEngineTTIModelGeneralParams(BaseForm):\n    size = forms.SingleSelect(\n        TooltipLabel(_('Image size'),\n                     _('If the gap between width, height and 512 is too large, the picture rendering effect will be poor and the probability of excessive delay will increase significantly. Recommended ratio and corresponding width and height before super score: width*height')),\n        required=True,\n        default_value='512x512',\n        option_list=[\n            {'label': '512x512', 'value': '512x512'},\n            {'label': '1024x1024', 'value': '1024x1024'},\n            {'label': '864x1152', 'value': '864x1152'},\n            {'label': '1152x864', 'value': '1152x864'},\n            {'label': '1280x720', 'value': '1280x720'},\n            {'label': '720x1280', 'value': '720x1280'},\n            {'label': '832x1248', 'value': '832x1248'},\n            {'label': '1248x832', 'value': '1248x832'},\n            {'label': '1512x648', 'value': '1512x648'},\n\n        ],\n        text_field='label',\n        value_field='value')\n\n\nclass VolcanicEngineTTIModelCredential(BaseForm, BaseModelCredential):\n    volcanic_api_url = forms.TextInputField('API URL', required=True,\n                                            default_value='https://ark.cn-beijing.volces.com/api/v3')\n    api_key = forms.PasswordInputField('Api key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value, gettext(\n                    'Verification failed, please check whether the parameters are correct: {error}').format(\n                    error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return VolcanicEngineTTIModelGeneralParams()\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/credential/tts.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass VolcanicEngineTTSModelGeneralParams(BaseForm):\n    voice_type = forms.SingleSelect(\n        TooltipLabel(_('timbre'), _('Chinese sounds can support mixed scenes of Chinese and English')),\n        required=True, default_value='zh_female_cancan_mars_bigtts',\n        text_field='value',\n        value_field='value',\n        option_list=[\n            {'text': '灿灿/Shiny', 'value': 'zh_female_cancan_mars_bigtts'},\n            {'text': '清新女声', 'value': 'zh_female_qingxinnvsheng_mars_bigtts'},\n            {'text': '爽快思思/Skye', 'value': 'zh_female_shuangkuaisisi_moon_bigtts'},\n            {'text': '湾区大叔', 'value': 'zh_female_wanqudashu_moon_bigtts' },\n            {'text': '呆萌川妹', 'value': 'zh_female_daimengchuanmei_moon_bigtts'},\n            {'text': '广州德哥', 'value': 'zh_male_guozhoudege_moon_bigtts'},\n            {'text': '北京小爷', 'value': 'zh_male_beijingxiaoye_moon_bigtts'},\n            {'text': '少年梓辛/Brayan', 'value': 'zh_male_shaonianzixin_moon_bigtts'},\n            {'text': '魅力女友', 'value': 'zh_female_meilinvyou_moon_bigtts'},\n        ])\n    speed_ratio = forms.SliderField(\n        TooltipLabel(_('speaking speed'), _('[0.2,3], the default is 1, usually one decimal place is enough')),\n        required=True, default_value=1,\n        _min=0.2,\n        _max=3,\n        _step=0.1,\n        precision=1)\n\n\nclass VolcanicEngineTTSModelCredential(BaseForm, BaseModelCredential):\n    volcanic_api_url = forms.TextInputField('API URL', required=True,\n                                            default_value='wss://openspeech.bytedance.com/api/v1/tts/ws_binary')\n    volcanic_app_id = forms.TextInputField('App ID', required=True)\n    volcanic_token = forms.PasswordInputField('Access Token', required=True)\n    volcanic_cluster = forms.TextInputField('Cluster ID', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['volcanic_api_url', 'volcanic_app_id', 'volcanic_token', 'volcanic_cluster']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value, gettext(\n                    'Verification failed, please check whether the parameters are correct: {error}').format(\n                    error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'volcanic_token': super().encryption(model.get('volcanic_token', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return VolcanicEngineTTSModelGeneralParams()\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/credential/ttv.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel, SingleSelect, TextInputField\nfrom common.forms.switch_field import SwitchField\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass VolcanicEngineTTVModelGeneralParams(BaseForm):\n    resolution = SingleSelect(\n        TooltipLabel(_('Resolution'), _('Resolution')),\n        required=True,\n        default_value='480P',\n        option_list=[\n            {'value': '480P', 'label': '480P'},\n            {'value': '720P', 'label': '720P'},\n            {'value': '1080P', 'label': '1080P'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n    ratio = SingleSelect(\n        TooltipLabel(_('Ratio'), _('Ratio')),\n        required=True,\n        default_value='16:9',\n        option_list=[\n            {'value': '16:9', 'label': '16:9'},\n            {'value': '9:16', 'label': '9:16'},\n            {'value': '1:1', 'label': '1:1'},\n            {'value': '4:3', 'label': '4:3'},\n            {'value': '3:4', 'label': '3:4'},\n            {'value': '21:9', 'label': '21:9'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n    duration = TextInputField(\n        TooltipLabel(_('Duration'), _('Duration')),\n        required=True,\n        default_value=5,\n    )\n\n    watermark = SwitchField(\n        TooltipLabel(_('Watermark'), _('Whether to add watermark')),\n        attrs={\"active-value\": \"true\", \"inactive-value\": \"false\"},\n        default_value=False,\n    )\n\n\nclass VolcanicEngineTTVModelCredential(BaseForm, BaseModelCredential):\n    api_key = forms.PasswordInputField('Api key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value, gettext(\n                    'Verification failed, please check whether the parameters are correct: {error}').format(\n                    error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return VolcanicEngineTTVModelGeneralParams()\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/model/bigModel_stt.py",
    "content": "# coding=utf-8\n\n\"\"\"\nrequires Python 3.6 or later\n\npip install asyncio\npip install websockets\n\"\"\"\n\nimport base64\nimport json\nimport os\nimport time\nimport uuid\nimport requests\nimport uuid_utils.compat as uuid\n\nfrom typing import Dict\n\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\naudio_format = \"mp3\"  # wav 或者 mp3，根据实际音频格式设置\n\n\ndef determine_api_mode(url):\n    \"\"\"\n    根据URL判断API模式\n    \"\"\"\n    if '/recognize/flash' in url:\n        return 'sync'\n    elif '/submit' in url:\n        return 'async_submit'\n    elif '/query' in url:\n        return 'async_query'\n    else:\n        return 'unknown'\n\n\nclass VolcanicASRClient:\n    def __init__(self, appid, token):\n        self.appid = appid\n        self.token = token\n\n    def _build_headers(self, url, task_id=None, x_tt_logid=None):\n        \"\"\"根据URL构建请求头\"\"\"\n        mode = determine_api_mode(url)\n\n        headers = {\n            \"X-Api-App-Key\": self.appid,\n            \"X-Api-Access-Key\": self.token,\n        }\n\n        if mode == 'sync':\n            headers.update({\n                \"X-Api-Resource-Id\": \"volc.bigasr.auc_turbo\",\n                \"X-Api-Request-Id\": str(uuid.uuid4()),\n                \"X-Api-Sequence\": \"-1\",\n            })\n        elif mode == 'async_submit':\n            headers.update({\n                \"X-Api-Resource-Id\": \"volc.bigasr.auc\",\n                \"X-Api-Request-Id\": task_id or str(uuid.uuid4()),\n                \"X-Api-Sequence\": \"-1\",\n            })\n        elif mode == 'async_query':\n            headers.update({\n                \"X-Api-Resource-Id\": \"volc.bigasr.auc\",\n                \"X-Api-Request-Id\": task_id or str(uuid.uuid4()),\n                \"X-Tt-Logid\": x_tt_logid or \"\",\n            })\n\n        return headers\n\n    def _create_request_body(self, audio_data, mode='sync'):\n        \"\"\"创建请求体\"\"\"\n        base_request = {\n            \"user\": {\"uid\": self.appid if mode == 'sync' else \"fake_uid\"},\n            \"audio\": audio_data,\n        }\n\n        if mode == 'sync':\n            base_request[\"request\"] = {\n                \"model_name\": \"bigmodel\",\n                \"enable_itn\": True,\n                \"enable_punc\": True,\n                \"enable_ddc\": True,\n            }\n        else:  # async\n            base_request[\"request\"] = {\n                \"model_name\": \"bigmodel\",\n                \"enable_channel_split\": True,\n                \"enable_ddc\": True,\n                \"enable_speaker_info\": True,\n                \"enable_punc\": True,\n                \"enable_itn\": True,\n                \"corpus\": {\n                    \"correct_table_name\": \"\",\n                    \"context\": \"\"\n                }\n            }\n\n        return base_request\n\n    def process_audio(self, audio_file=None, submit_url=None):\n        \"\"\"\n        根据submit_url自动选择处理模式\n        \"\"\"\n        # 获取音频数据\n        base64_audio = base64.b64encode(audio_file.read()).decode(\"utf-8\")\n        audio_data = {\"data\": base64_audio}\n\n        # 根据URL判断API模式\n        mode = determine_api_mode(submit_url)\n\n        if mode == 'sync':\n            return self._sync_recognize(audio_data, submit_url)\n        elif mode == 'async_submit':\n            return self._async_process(audio_data, submit_url)\n        else:\n            raise ValueError(f\"Unsupported URL pattern: {submit_url}\")\n\n    def _get_audio_data(self, audio_file):\n        \"\"\"构建音频数据对象\"\"\"\n        base64_audio = base64.b64encode(audio_file.read()).decode(\"utf-8\")\n        return {\"data\": base64_audio}\n\n    def _sync_recognize(self, audio_data, submit_url):\n        \"\"\"同步识别模式\"\"\"\n        headers = self._build_headers(submit_url)\n        request_body = self._create_request_body(audio_data, mode='sync')\n\n        response = requests.post(submit_url, json=request_body, headers=headers)\n        return self._handle_response(response, \"sync_recognize\")\n\n    def _async_process(self, audio_data, submit_url):\n        \"\"\"异步处理模式\"\"\"\n        # 提交任务\n        task_id = str(uuid.uuid4())\n        headers = self._build_headers(submit_url, task_id=task_id)\n        request_body = self._create_request_body(audio_data, mode='async')\n\n        submit_response = requests.post(submit_url, data=json.dumps(request_body), headers=headers)\n\n        if submit_response.headers.get(\"X-Api-Status-Code\") == \"20000000\":\n            x_tt_logid = submit_response.headers.get(\"X-Tt-Logid\", \"\")\n            # 查询结果\n            return self._poll_for_result(task_id, x_tt_logid)\n        else:\n            print(f\"Submit task failed: {submit_response.headers}\")\n            return None\n\n    def _poll_for_result(self, task_id, x_tt_logid):\n        \"\"\"轮询查询异步任务结果\"\"\"\n        query_url = \"https://openspeech-direct.zijieapi.com/api/v3/auc/bigmodel/query\"\n\n        while True:\n            query_response = self._query_task(task_id, x_tt_logid, query_url)\n            code = query_response.headers.get('X-Api-Status-Code', \"\")\n\n            if code == '20000000':  # 任务完成\n                return query_response\n            elif code != '20000001' and code != '20000002':  # 任务失败\n                print(f\"Async task failed with code: {code}\")\n                return None\n            time.sleep(1)\n\n    def _query_task(self, task_id, x_tt_logid, query_url):\n        \"\"\"执行单次查询请求\"\"\"\n        headers = self._build_headers(query_url, task_id=task_id, x_tt_logid=x_tt_logid)\n        response = requests.post(query_url, json.dumps({}), headers=headers)\n        return self._handle_response(response, \"async_query\", silent=True)\n\n    def _handle_response(self, response, operation, silent=False):\n        \"\"\"处理响应\"\"\"\n        if 'X-Api-Status-Code' in response.headers:\n            if not silent:\n                print(f'{operation} response header X-Api-Status-Code: {response.headers[\"X-Api-Status-Code\"]}')\n                print(f'{operation} response header X-Api-Message: {response.headers[\"X-Api-Message\"]}')\n                print(f'{operation} response header X-Tt-Logid: {response.headers[\"X-Tt-Logid\"]}')\n\n                if operation == \"sync_recognize\":\n                    print(f'sync response content: {response.json()}\\n')\n\n            return response\n        else:\n            print(f'{operation} failed: {response.headers}\\n')\n            return None\n\n\nclass VolcanicEngineBigModelSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    volcanic_app_id: str\n    volcanic_api_url: str\n    volcanic_token: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.volcanic_api_url = kwargs.get('volcanic_api_url')\n        self.volcanic_token = kwargs.get('volcanic_token')\n        self.volcanic_app_id = kwargs.get('volcanic_app_id')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {}\n        if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None:\n            optional_params['max_tokens'] = model_kwargs['max_tokens']\n        if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None:\n            optional_params['temperature'] = model_kwargs['temperature']\n        return VolcanicEngineBigModelSpeechToText(\n            volcanic_api_url=model_credential.get('volcanic_api_url'),\n            volcanic_token=model_credential.get('volcanic_token'),\n            volcanic_app_id=model_credential.get('volcanic_app_id'),\n            params=model_kwargs,\n        )\n\n    def check_auth(self):\n        cwd = os.path.dirname(os.path.abspath(__file__))\n        with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as audio_file:\n            self.speech_to_text(audio_file)\n\n    def speech_to_text(self, audio_file):\n        try:\n            client = VolcanicASRClient(self.volcanic_app_id, self.volcanic_token)\n            result = client.process_audio(audio_file, self.volcanic_api_url)\n            if result.status_code == 200:\n                return result.json().get('result').get('text')\n        except Exception as e:\n            maxkb_logger.error(f'Error getting speech to text: {e}')\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/model/embedding.py",
    "content": "from typing import Dict, List\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom volcenginesdkarkruntime import Ark\n\n\nclass VolcanicEngineEmbeddingModel(MaxKBBaseModel):\n    api_key: str\n    model_name: str\n    api_base: str\n    params: Dict[str, object]\n\n    def __init__(self, api_key: str, model: str, api_base: str, params: Dict[str, object] = None):\n        self.client = Ark(\n            api_key=api_key,\n            base_url=api_base\n        )\n        self.model_name = model\n        self.params = params\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return VolcanicEngineEmbeddingModel(\n            api_key=model_credential.get(\"api_key\"),\n            model=model_name,\n            api_base=model_credential.get(\"api_base\"),\n            **optional_params\n        )\n\n    def embed_query(self, text: str):\n        res = self.embed_documents([text])\n        return res[0]\n\n    def embed_documents(\n            self, texts: List[str], chunk_size: int | None = None\n    ) -> List[List[float]]:\n        if self.model_name.startswith(\"doubao-embedding-vision-\"):\n            multimodal_inputs = []\n            for text in texts:\n                multimodal_inputs.append({\n                    \"type\": \"text\",\n                    \"text\": text\n                })\n            resp = self.client.multimodal_embeddings.create(\n                model=self.model_name,\n                input=multimodal_inputs,\n                **(self.params or {})\n            )\n            return [resp.data.get('embedding')]\n        else:\n            resp = self.client.embeddings.create(\n                model=self.model_name,\n                input=texts,\n                **(self.params or {})\n            )\n            return [e.embedding for e in resp.data]\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/model/image.py",
    "content": "import base64\nimport mimetypes\nfrom typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass VolcanicEngineImage(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return VolcanicEngineImage(\n            model_name=model_name,\n            openai_api_key=model_credential.get('api_key'),\n            openai_api_base=model_credential.get('api_base'),\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n\n\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/model/llm.py",
    "content": "from typing import List, Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass VolcanicEngineChatModel(MaxKBBaseModel, BaseChatOpenAI):\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return VolcanicEngineChatModel(\n            model=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            extra_body=optional_params\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/model/stt.py",
    "content": "# coding=utf-8\n\n\"\"\"\nrequires Python 3.6 or later\n\npip install asyncio\npip install websockets\n\"\"\"\nimport asyncio\nimport base64\nimport gzip\nimport hmac\nimport json\nimport logging\nimport os\nimport ssl\nimport uuid_utils.compat as uuid\nimport wave\nfrom hashlib import sha256\nfrom io import BytesIO\nfrom typing import Dict\nfrom urllib.parse import urlparse\n\nimport websockets\n\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\naudio_format = \"mp3\"  # wav 或者 mp3，根据实际音频格式设置\n\nPROTOCOL_VERSION = 0b0001\nDEFAULT_HEADER_SIZE = 0b0001\n\nPROTOCOL_VERSION_BITS = 4\nHEADER_BITS = 4\nMESSAGE_TYPE_BITS = 4\nMESSAGE_TYPE_SPECIFIC_FLAGS_BITS = 4\nMESSAGE_SERIALIZATION_BITS = 4\nMESSAGE_COMPRESSION_BITS = 4\nRESERVED_BITS = 8\n\n# Message Type:\nCLIENT_FULL_REQUEST = 0b0001\nCLIENT_AUDIO_ONLY_REQUEST = 0b0010\nSERVER_FULL_RESPONSE = 0b1001\nSERVER_ACK = 0b1011\nSERVER_ERROR_RESPONSE = 0b1111\n\n# Message Type Specific Flags\nNO_SEQUENCE = 0b0000  # no check sequence\nPOS_SEQUENCE = 0b0001\nNEG_SEQUENCE = 0b0010\nNEG_SEQUENCE_1 = 0b0011\n\n# Message Serialization\nNO_SERIALIZATION = 0b0000\nJSON = 0b0001\nTHRIFT = 0b0011\nCUSTOM_TYPE = 0b1111\n\n# Message Compression\nNO_COMPRESSION = 0b0000\nGZIP = 0b0001\nCUSTOM_COMPRESSION = 0b1111\n\nssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\nssl_context.check_hostname = False\nssl_context.verify_mode = ssl.CERT_NONE\n\n\ndef generate_header(\n        version=PROTOCOL_VERSION,\n        message_type=CLIENT_FULL_REQUEST,\n        message_type_specific_flags=NO_SEQUENCE,\n        serial_method=JSON,\n        compression_type=GZIP,\n        reserved_data=0x00,\n        extension_header=bytes()\n):\n    \"\"\"\n    protocol_version(4 bits), header_size(4 bits),\n    message_type(4 bits), message_type_specific_flags(4 bits)\n    serialization_method(4 bits) message_compression(4 bits)\n    reserved （8bits) 保留字段\n    header_extensions 扩展头(大小等于 8 * 4 * (header_size - 1) )\n    \"\"\"\n    header = bytearray()\n    header_size = int(len(extension_header) / 4) + 1\n    header.append((version << 4) | header_size)\n    header.append((message_type << 4) | message_type_specific_flags)\n    header.append((serial_method << 4) | compression_type)\n    header.append(reserved_data)\n    header.extend(extension_header)\n    return header\n\n\ndef generate_full_default_header():\n    return generate_header()\n\n\ndef generate_audio_default_header():\n    return generate_header(\n        message_type=CLIENT_AUDIO_ONLY_REQUEST\n    )\n\n\ndef generate_last_audio_default_header():\n    return generate_header(\n        message_type=CLIENT_AUDIO_ONLY_REQUEST,\n        message_type_specific_flags=NEG_SEQUENCE\n    )\n\n\ndef parse_response(res):\n    \"\"\"\n    protocol_version(4 bits), header_size(4 bits),\n    message_type(4 bits), message_type_specific_flags(4 bits)\n    serialization_method(4 bits) message_compression(4 bits)\n    reserved （8bits) 保留字段\n    header_extensions 扩展头(大小等于 8 * 4 * (header_size - 1) )\n    payload 类似与http 请求体\n    \"\"\"\n    protocol_version = res[0] >> 4\n    header_size = res[0] & 0x0f\n    message_type = res[1] >> 4\n    message_type_specific_flags = res[1] & 0x0f\n    serialization_method = res[2] >> 4\n    message_compression = res[2] & 0x0f\n    reserved = res[3]\n    header_extensions = res[4:header_size * 4]\n    payload = res[header_size * 4:]\n    result = {}\n    payload_msg = None\n    payload_size = 0\n    if message_type == SERVER_FULL_RESPONSE:\n        payload_size = int.from_bytes(payload[:4], \"big\", signed=True)\n        payload_msg = payload[4:]\n    elif message_type == SERVER_ACK:\n        seq = int.from_bytes(payload[:4], \"big\", signed=True)\n        result['seq'] = seq\n        if len(payload) >= 8:\n            payload_size = int.from_bytes(payload[4:8], \"big\", signed=False)\n            payload_msg = payload[8:]\n    elif message_type == SERVER_ERROR_RESPONSE:\n        code = int.from_bytes(payload[:4], \"big\", signed=False)\n        result['code'] = code\n        payload_size = int.from_bytes(payload[4:8], \"big\", signed=False)\n        payload_msg = payload[8:]\n        maxkb_logger.error(f\"Error code: {code}, message: {payload_msg}\")\n    if payload_msg is None:\n        return result\n    if message_compression == GZIP:\n        payload_msg = gzip.decompress(payload_msg)\n    if serialization_method == JSON:\n        payload_msg = json.loads(str(payload_msg, \"utf-8\"))\n    elif serialization_method != NO_SERIALIZATION:\n        payload_msg = str(payload_msg, \"utf-8\")\n    result['payload_msg'] = payload_msg\n    result['payload_size'] = payload_size\n    return result\n\n\ndef read_wav_info(data: bytes = None) -> (int, int, int, int, int):\n    with BytesIO(data) as _f:\n        wave_fp = wave.open(_f, 'rb')\n        nchannels, sampwidth, framerate, nframes = wave_fp.getparams()[:4]\n        wave_bytes = wave_fp.readframes(nframes)\n    return nchannels, sampwidth, framerate, nframes, len(wave_bytes)\n\n\nclass VolcanicEngineSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    workflow: str = \"audio_in,resample,partition,vad,fe,decode,itn,nlu_punctuate\"\n    show_language: bool = False\n    show_utterances: bool = False\n    result_type: str = \"full\"\n    format: str = \"mp3\"\n    rate: int = 16000\n    language: str = \"zh-CN\"\n    bits: int = 16\n    channel: int = 1\n    codec: str = \"raw\"\n    audio_type: int = 1\n    secret: str = \"access_secret\"\n    auth_method: str = \"token\"\n    mp3_seg_size: int = 10000\n    success_code: int = 1000  # success code, default is 1000\n    seg_duration: int = 15000\n    nbest: int = 1\n\n    volcanic_app_id: str\n    volcanic_cluster: str\n    volcanic_api_url: str\n    volcanic_token: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.volcanic_api_url = kwargs.get('volcanic_api_url')\n        self.volcanic_token = kwargs.get('volcanic_token')\n        self.volcanic_app_id = kwargs.get('volcanic_app_id')\n        self.volcanic_cluster = kwargs.get('volcanic_cluster')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {}\n        if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None:\n            optional_params['max_tokens'] = model_kwargs['max_tokens']\n        if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None:\n            optional_params['temperature'] = model_kwargs['temperature']\n        return VolcanicEngineSpeechToText(\n            volcanic_api_url=model_credential.get('volcanic_api_url'),\n            volcanic_token=model_credential.get('volcanic_token'),\n            volcanic_app_id=model_credential.get('volcanic_app_id'),\n            volcanic_cluster=model_credential.get('volcanic_cluster'),\n            params=model_kwargs,\n            **model_kwargs,\n            **optional_params\n        )\n\n    def construct_request(self, reqid):\n\n        params = self.params or {}\n        req = {\n            'app': {\n                'appid': self.volcanic_app_id,\n                'cluster': self.volcanic_cluster,\n                'token': self.volcanic_token,\n            },\n            'user': {\n                'uid': params.get(\"uid\", \"streaming_asr_demo\")\n            },\n            'request': {\n                'reqid': reqid,\n                'nbest': params.get('nbest', self.nbest),\n                'workflow': params.get('workflow', self.workflow),\n                'show_language': params.get('show_language', self.show_language),\n                'show_utterances': params.get('show_utterances', self.show_utterances),\n                'result_type': params.get('result_type', self.result_type),\n                'sequence': params.get('sequence', 1)\n            },\n            'audio': {\n                'format': params.get('format', self.format),\n                'rate': params.get('rate', self.rate),\n                'language': params.get('language', self.language),\n                'bits': params.get('bits', self.bits),\n                'channel': params.get('channel', self.channel),\n                'codec': params.get('codec', self.codec)\n            }\n        }\n        return req\n\n    @staticmethod\n    def slice_data(data: bytes, chunk_size: int) -> (list, bool):\n        \"\"\"\n        slice data\n        :param data: wav data\n        :param chunk_size: the segment size in one request\n        :return: segment data, last flag\n        \"\"\"\n        data_len = len(data)\n        offset = 0\n        while offset + chunk_size < data_len:\n            yield data[offset: offset + chunk_size], False\n            offset += chunk_size\n        else:\n            yield data[offset: data_len], True\n\n    def _real_processor(self, request_params: dict) -> dict:\n        pass\n\n    def token_auth(self):\n        return {'Authorization': 'Bearer; {}'.format(self.volcanic_token)}\n\n    def signature_auth(self, data):\n        header_dicts = {\n            'Custom': 'auth_custom',\n        }\n\n        url_parse = urlparse(self.volcanic_api_url)\n        input_str = 'GET {} HTTP/1.1\\n'.format(url_parse.path)\n        auth_headers = 'Custom'\n        for header in auth_headers.split(','):\n            input_str += '{}\\n'.format(header_dicts[header])\n        input_data = bytearray(input_str, 'utf-8')\n        input_data += data\n        mac = base64.urlsafe_b64encode(\n            hmac.new(self.secret.encode('utf-8'), input_data, digestmod=sha256).digest())\n        header_dicts['Authorization'] = 'HMAC256; access_token=\"{}\"; mac=\"{}\"; h=\"{}\"'.format(self.volcanic_token,\n                                                                                              str(mac, 'utf-8'),\n                                                                                              auth_headers)\n        return header_dicts\n\n    async def segment_data_processor(self, wav_data: bytes, segment_size: int):\n        reqid = str(uuid.uuid7())\n        # 构建 full client request，并序列化压缩\n        request_params = self.construct_request(reqid)\n        payload_bytes = str.encode(json.dumps(request_params))\n        payload_bytes = gzip.compress(payload_bytes)\n        full_client_request = bytearray(generate_full_default_header())\n        full_client_request.extend((len(payload_bytes)).to_bytes(4, 'big'))  # payload size(4 bytes)\n        full_client_request.extend(payload_bytes)  # payload\n        header = None\n        if self.auth_method == \"token\":\n            header = self.token_auth()\n        elif self.auth_method == \"signature\":\n            header = self.signature_auth(full_client_request)\n        async with websockets.connect(self.volcanic_api_url, additional_headers=header, max_size=1000000000,\n                                      ssl=ssl_context) as ws:\n            # 发送 full client request\n            await ws.send(full_client_request)\n            res = await ws.recv()\n            result = parse_response(res)\n            if 'payload_msg' in result and result['payload_msg']['code'] != self.success_code:\n                raise Exception(\n                    f\"Error code: {result['payload_msg']['code']}, message: {result['payload_msg']['message']}\")\n            for seq, (chunk, last) in enumerate(VolcanicEngineSpeechToText.slice_data(wav_data, segment_size), 1):\n                # if no compression, comment this line\n                payload_bytes = gzip.compress(chunk)\n                audio_only_request = bytearray(generate_audio_default_header())\n                if last:\n                    audio_only_request = bytearray(generate_last_audio_default_header())\n                audio_only_request.extend((len(payload_bytes)).to_bytes(4, 'big'))  # payload size(4 bytes)\n                audio_only_request.extend(payload_bytes)  # payload\n                # 发送 audio-only client request\n                await ws.send(audio_only_request)\n                res = await ws.recv()\n                result = parse_response(res)\n                if 'payload_msg' in result and result['payload_msg']['code'] != self.success_code:\n                    return result\n        return result['payload_msg']['result'][0]['text']\n\n    def check_auth(self):\n        cwd = os.path.dirname(os.path.abspath(__file__))\n        with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as f:\n            self.speech_to_text(f)\n\n    def speech_to_text(self, file):\n        data = file.read()\n        audio_data = bytes(data)\n        if self.format == \"mp3\":\n            segment_size = self.mp3_seg_size\n            return asyncio.run(self.segment_data_processor(audio_data, segment_size))\n        if self.format != \"wav\":\n            raise Exception(\"format should in wav or mp3\")\n        nchannels, sampwidth, framerate, nframes, wav_len = read_wav_info(\n            audio_data)\n        size_per_sec = nchannels * sampwidth * framerate\n        segment_size = int(size_per_sec * self.seg_duration / 1000)\n        return asyncio.run(self.segment_data_processor(audio_data, segment_size))\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/model/tti.py",
    "content": "# coding=utf-8\n\n'''\nrequires Python 3.6 or later\n\npip install asyncio\npip install websockets\n\n'''\nfrom typing import Dict\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\n\nfrom volcenginesdkarkruntime import Ark\n\n\nclass VolcanicEngineTextToImage(MaxKBBaseModel, BaseTextToImage):\n    api_key: str\n    api_base: str\n    model_version: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model_version = kwargs.get('model_version')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return VolcanicEngineTextToImage(\n            model_version=model_name,\n            api_key=model_credential.get('api_key'),\n            api_base=model_credential.get('volcanic_api_url') or 'https://ark-api.volcengine.com',\n            **optional_params\n        )\n\n    def check_auth(self):\n        return True\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        client = Ark(\n            # 此为默认路径，您可根据业务所在地域进行配置\n            base_url=self.api_base,\n            # 从环境变量中获取您的 API Key。此为默认方式，您可根据需要进行修改\n            api_key=self.api_key,\n        )\n        file_urls = []\n        imagesResponse = client.images.generate(\n            model=self.model_version,\n            prompt=prompt,\n            **self.params\n        )\n        if imagesResponse.data[0].url:\n            file_urls.append(imagesResponse.data[0].url)\n        elif imagesResponse.data[0].b64_json:\n            file_urls.append(imagesResponse.data[0].b64_json)\n        return file_urls\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/model/tts.py",
    "content": "# coding=utf-8\n\n'''\nrequires Python 3.6 or later\n\npip install asyncio\npip install websockets\n\n'''\n\nimport asyncio\nimport copy\nimport gzip\nimport json\nimport re\nimport ssl\n\nimport requests\nimport uuid_utils.compat as uuid\nfrom typing import Dict\n\nimport websockets\nfrom django.utils.translation import gettext as _\n\nfrom common.utils.common import _remove_empty_lines\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tts import BaseTextToSpeech\n\nMESSAGE_TYPES = {11: \"audio-only server response\", 12: \"frontend server response\", 15: \"error message from server\"}\nMESSAGE_TYPE_SPECIFIC_FLAGS = {0: \"no sequence number\", 1: \"sequence number > 0\",\n                               2: \"last message from server (seq < 0)\", 3: \"sequence number < 0\"}\nMESSAGE_SERIALIZATION_METHODS = {0: \"no serialization\", 1: \"JSON\", 15: \"custom type\"}\nMESSAGE_COMPRESSIONS = {0: \"no compression\", 1: \"gzip\", 15: \"custom compression method\"}\n\n# version: b0001 (4 bits)\n# header size: b0001 (4 bits)\n# message type: b0001 (Full client request) (4bits)\n# message type specific flags: b0000 (none) (4bits)\n# message serialization method: b0001 (JSON) (4 bits)\n# message compression: b0001 (gzip) (4bits)\n# reserved data: 0x00 (1 byte)\ndefault_header = bytearray(b'\\x11\\x10\\x11\\x00')\n\nssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\nssl_context.check_hostname = False\nssl_context.verify_mode = ssl.CERT_NONE\n\n\nclass VolcanicEngineTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):\n    volcanic_app_id: str\n    volcanic_cluster: str\n    volcanic_api_url: str\n    volcanic_token: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.volcanic_api_url = kwargs.get('volcanic_api_url')\n        self.volcanic_token = kwargs.get('volcanic_token')\n        self.volcanic_app_id = kwargs.get('volcanic_app_id')\n        self.volcanic_cluster = kwargs.get('volcanic_cluster')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'voice_type': 'zh_female_cancan_mars_bigtts', 'speed_ratio': 1.0}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return VolcanicEngineTextToSpeech(\n            volcanic_api_url=model_credential.get('volcanic_api_url'),\n            volcanic_token=model_credential.get('volcanic_token'),\n            volcanic_app_id=model_credential.get('volcanic_app_id'),\n            volcanic_cluster=model_credential.get('volcanic_cluster'),\n            **optional_params\n        )\n\n    def check_auth(self):\n        self.text_to_speech(_('Hello'))\n\n    def text_to_speech(self, text):\n        request_json = {\n            \"app\": {\n                \"appid\": self.volcanic_app_id,\n                \"token\": \"access_token\",\n                \"cluster\": self.volcanic_cluster\n            },\n            \"user\": {\n                \"uid\": \"uid\"\n            },\n            \"audio\": {\n                         \"encoding\": \"mp3\",\n                         \"volume_ratio\": 1.0,\n                         \"pitch_ratio\": 1.0,\n                     } | self.params,\n            \"request\": {\n                \"reqid\": str(uuid.uuid7()),\n                \"text\": '',\n                \"text_type\": \"plain\",\n                \"operation\": \"xxx\"\n            }\n        }\n        text = _remove_empty_lines(text)\n\n        return asyncio.run(self.submit(request_json, text))\n\n    def is_cache_model(self):\n        return False\n\n    def token_auth(self):\n        return {'Authorization': 'Bearer; {}'.format(self.volcanic_token)}\n\n    async def submit(self, request_json, text):\n        submit_request_json = copy.deepcopy(request_json)\n        submit_request_json[\"request\"][\"operation\"] = \"submit\"\n        header = {\"Authorization\": f\"Bearer; {self.volcanic_token}\"}\n        result = b''\n        async with websockets.connect(self.volcanic_api_url, additional_headers=header, ping_interval=None,\n                                      ssl=ssl_context) as ws:\n            lines = [text[i:i + 200] for i in range(0, len(text), 200)]\n            for line in lines:\n                if self.is_table_format_chars_only(line):\n                    continue\n                submit_request_json[\"request\"][\"reqid\"] = str(uuid.uuid7())\n                submit_request_json[\"request\"][\"text\"] = line\n                payload_bytes = str.encode(json.dumps(submit_request_json))\n                payload_bytes = gzip.compress(payload_bytes)  # if no compression, comment this line\n                full_client_request = bytearray(default_header)\n                full_client_request.extend((len(payload_bytes)).to_bytes(4, 'big'))  # payload size(4 bytes)\n                full_client_request.extend(payload_bytes)  # payload\n                await ws.send(full_client_request)\n                result += await self.parse_response(ws)\n        return result\n\n    @staticmethod\n    def is_table_format_chars_only(s):\n        # 检查是否仅包含 \"|\", \"-\", 和空格字符\n        return bool(s) and re.fullmatch(r'[|\\-\\s]+', s)\n\n    @staticmethod\n    async def parse_response(ws):\n        result = b''\n        while True:\n            res = await ws.recv()\n            protocol_version = res[0] >> 4\n            header_size = res[0] & 0x0f\n            message_type = res[1] >> 4\n            message_type_specific_flags = res[1] & 0x0f\n            serialization_method = res[2] >> 4\n            message_compression = res[2] & 0x0f\n            reserved = res[3]\n            header_extensions = res[4:header_size * 4]\n            payload = res[header_size * 4:]\n            if header_size != 1:\n                # print(f\"           Header extensions: {header_extensions}\")\n                pass\n            if message_type == 0xb:  # audio-only server response\n                if message_type_specific_flags == 0:  # no sequence number as ACK\n                    continue\n                else:\n                    sequence_number = int.from_bytes(payload[:4], \"big\", signed=True)\n                    payload_size = int.from_bytes(payload[4:8], \"big\", signed=False)\n                    payload = payload[8:]\n                result += payload\n                if sequence_number < 0:\n                    break\n                else:\n                    continue\n            elif message_type == 0xf:\n                code = int.from_bytes(payload[:4], \"big\", signed=False)\n                msg_size = int.from_bytes(payload[4:8], \"big\", signed=False)\n                error_msg = payload[8:]\n                if message_compression == 1:\n                    error_msg = gzip.decompress(error_msg)\n                error_msg = str(error_msg, \"utf-8\")\n                raise Exception(f\"Error code: {code}, message: {error_msg}\")\n            elif message_type == 0xc:\n                msg_size = int.from_bytes(payload[:4], \"big\", signed=False)\n                payload = payload[4:]\n                if message_compression == 1:\n                    payload = gzip.decompress(payload)\n            else:\n                break\n        return result\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/model/ttv.py",
    "content": "import base64\nimport time\nfrom typing import Dict, Optional\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.base_ttv import BaseGenerationVideo\nfrom common.utils.logger import maxkb_logger\nfrom volcenginesdkarkruntime import Ark\n\n\nclass GenerationVideoModel(MaxKBBaseModel, BaseGenerationVideo):\n    api_key: str\n    model_name: str\n    params: dict\n    max_retries: int = 3\n    retry_delay: int = 5  # seconds\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.model_name = kwargs.get('model_name')\n        self.params = kwargs.get('params', {})\n        self.retry_delay = 5\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return GenerationVideoModel(\n            model_name=model_name,\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        return True\n\n    def _build_prompt(self, prompt: str) -> str:\n        \"\"\"拼接参数到 prompt 文本\"\"\"\n        param_map = {\n            \"ratio\": \"rt\",\n            \"duration\": \"dur\",\n            \"framespersecond\": \"fps\",\n            \"resolution\": \"rs\",\n            \"watermark\": \"wm\",\n            \"camerafixed\": \"cf\",\n        }\n        for key, value in self.params.items():\n            if key in param_map:\n                prompt += f\" --{param_map[key]} {value}\"\n        return prompt\n\n    def _poll_task(self, client: Ark, task_id: str, max_wait: int = 60, interval: int = 5):\n        \"\"\"轮询任务状态，直到完成或超时\"\"\"\n        elapsed = 0\n        while elapsed < max_wait:\n            result = client.content_generation.tasks.get(task_id=task_id)\n            status = getattr(result, \"status\", None)\n            maxkb_logger.info(f\"[ArkVideo] Task {task_id} status={status}\")\n\n            if status in (\"succeeded\", \"failed\", \"cancelled\"):\n                return result\n\n            time.sleep(interval)\n            elapsed += interval\n        maxkb_logger.warning(f\"[ArkVideo] Task {task_id} wait timeout\")\n        return None\n\n    # --- 通用异步生成函数 ---\n    def generate_video(self, prompt, negative_prompt=None, first_frame_url=None, last_frame_url=None, **kwargs):\n        client = Ark(api_key=self.api_key)\n        # 根据params设置其他参数 豆包的参数和别的不一样  需要拼接在text里\n        # --rt 16:9 --dur 5 --fps 24 --rs 720p --wm true --cf false\n        prompt = self._build_prompt(prompt)\n        content = [{\"type\": \"text\", \"text\": prompt}]\n\n        if first_frame_url:\n            content.append({\n                \"type\": \"image_url\",\n                \"image_url\": {\n                    \"url\": first_frame_url\n                },\n                \"role\": \"first_frame\"\n            })\n        if last_frame_url:\n            content.append({\n                \"type\": \"image_url\",\n                \"image_url\": {\n                    \"url\": last_frame_url\n                },\n                \"role\": \"last_frame\"\n            })\n        create_result = client.content_generation.tasks.create(\n            model=self.model_name,\n            content=content\n        )\n\n        task = client.content_generation.tasks.create(model=self.model_name, content=content)\n        task_id = task.id\n        maxkb_logger.info(f\"[ArkVideo] Created task {task_id}\")\n\n        # 轮询获取结果\n        result = self._poll_task(client, task_id)\n        if not result:\n            return {\"status\": \"timeout\", \"task_id\": task_id}\n\n        try:\n            if getattr(result, \"status\", None) in (\"succeeded\", \"failed\", \"cancelled\"):\n                client.content_generation.tasks.delete(task_id=task_id)\n                maxkb_logger.info(f\"[ArkVideo] Deleted task {task_id}\")\n        except Exception as e:\n            maxkb_logger.error(f\"[ArkVideo] Failed to delete task {task_id}: {e}\")\n            raise e\n        maxkb_logger.info(\"视频地址\", result.content.video_url)\n        return result.content.video_url\n"
  },
  {
    "path": "apps/models_provider/impl/volcanic_engine_model_provider/volcanic_engine_model_provider.py",
    "content": "#!/usr/bin/env python\n# -*- coding: UTF-8 -*-\n\"\"\"\n@Project ：MaxKB \n@File    ：gemini_model_provider.py\n@Author  ：Brian Yang\n@Date    ：5/13/24 7:47 AM \n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \\\n    ModelInfoManage\nfrom models_provider.impl.openai_model_provider.credential.llm import OpenAILLMModelCredential\nfrom models_provider.impl.volcanic_engine_model_provider.credential.bigModel_stt import \\\n    VolcanicEngineBigModelSTTModelCredential\nfrom models_provider.impl.volcanic_engine_model_provider.credential.embedding import VolcanicEmbeddingCredential\nfrom models_provider.impl.volcanic_engine_model_provider.credential.image import \\\n    VolcanicEngineImageModelCredential\nfrom models_provider.impl.volcanic_engine_model_provider.credential.tti import VolcanicEngineTTIModelCredential\nfrom models_provider.impl.volcanic_engine_model_provider.credential.tts import VolcanicEngineTTSModelCredential\nfrom models_provider.impl.volcanic_engine_model_provider.credential.ttv import VolcanicEngineTTVModelCredential\nfrom models_provider.impl.volcanic_engine_model_provider.model.bigModel_stt import VolcanicEngineBigModelSpeechToText\nfrom models_provider.impl.volcanic_engine_model_provider.model.embedding import VolcanicEngineEmbeddingModel\nfrom models_provider.impl.volcanic_engine_model_provider.model.image import VolcanicEngineImage\nfrom models_provider.impl.volcanic_engine_model_provider.model.llm import VolcanicEngineChatModel\nfrom models_provider.impl.volcanic_engine_model_provider.credential.stt import VolcanicEngineSTTModelCredential\nfrom models_provider.impl.volcanic_engine_model_provider.model.stt import VolcanicEngineSpeechToText\nfrom models_provider.impl.volcanic_engine_model_provider.model.tti import VolcanicEngineTextToImage\nfrom models_provider.impl.volcanic_engine_model_provider.model.tts import VolcanicEngineTextToSpeech\n\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\nfrom models_provider.impl.volcanic_engine_model_provider.model.ttv import GenerationVideoModel\n\nvolcanic_engine_llm_model_credential = OpenAILLMModelCredential()\nvolcanic_engine_stt_model_credential = VolcanicEngineSTTModelCredential()\nvolcanic_engine_big_stt_model_credential = VolcanicEngineBigModelSTTModelCredential()\nvolcanic_engine_tts_model_credential = VolcanicEngineTTSModelCredential()\nvolcanic_engine_image_model_credential = VolcanicEngineImageModelCredential()\nvolcanic_engine_tti_model_credential = VolcanicEngineTTIModelCredential()\n\nmodel_info_list = [\n    ModelInfo('ep-xxxxxxxxxx-yyyy',\n              _('The user goes to the model inference page of Volcano Ark to create an inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call it.'),\n              ModelTypeConst.LLM,\n              volcanic_engine_llm_model_credential, VolcanicEngineChatModel\n              ),\n    ModelInfo('ep-xxxxxxxxxx-yyyy',\n              _('The user goes to the model inference page of Volcano Ark to create an inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call it.'),\n              ModelTypeConst.IMAGE,\n              volcanic_engine_image_model_credential, VolcanicEngineImage\n              ),\n    ModelInfo('asr',\n              '',\n              ModelTypeConst.STT,\n              volcanic_engine_stt_model_credential, VolcanicEngineSpeechToText\n              ),\n    ModelInfo('bigmodel',\n              '',\n              ModelTypeConst.STT,\n              volcanic_engine_big_stt_model_credential, VolcanicEngineBigModelSpeechToText\n              ),\n    ModelInfo('tts',\n              '',\n              ModelTypeConst.TTS,\n              volcanic_engine_tts_model_credential, VolcanicEngineTextToSpeech\n              ),\n    ModelInfo('doubao-seedream-3-0-t2i-250415',\n              _(''),\n              ModelTypeConst.TTI,\n              volcanic_engine_tti_model_credential, VolcanicEngineTextToImage\n              ),\n]\n\nopen_ai_embedding_credential = VolcanicEmbeddingCredential()\nmodel_info_embedding_list = [\n    ModelInfo('ep-xxxxxxxxxx-yyyy',\n              _('The user goes to the model inference page of Volcano Ark to create an inference access point. Here, you need to enter ep-xxxxxxxxxx-yyyy to call it.'),\n              ModelTypeConst.EMBEDDING, open_ai_embedding_credential,\n              VolcanicEngineEmbeddingModel)\n]\nttv_credential = VolcanicEngineTTVModelCredential()\nmodel_info_ttv_list = [\n    ModelInfo('doubao-seedance-1-0-pro-250528',\n              _(''),\n              ModelTypeConst.TTV,\n              ttv_credential, GenerationVideoModel)\n    ,\n    ModelInfo('doubao-seedance-1-0-lite-t2v-250428',\n              _(''),\n              ModelTypeConst.TTV,\n              ttv_credential, GenerationVideoModel)\n    ,\n    ModelInfo('wan2-1-14b-t2v-250225',\n              _(''),\n              ModelTypeConst.TTV,\n              ttv_credential, GenerationVideoModel)\n]\nmodel_info_itv_list = [\n    ModelInfo('doubao-seedance-1-0-pro-250528',\n              _(''),\n              ModelTypeConst.ITV,\n              ttv_credential,\n              GenerationVideoModel),\n    ModelInfo('doubao-seedance-1-0-lite-i2v-250428',\n              _(''),\n              ModelTypeConst.ITV,\n              ttv_credential,\n              GenerationVideoModel),\n    ModelInfo('wan2-1-14b-i2v-250225',\n              _(''),\n              ModelTypeConst.ITV,\n              ttv_credential,\n              GenerationVideoModel),\n    ModelInfo('wan2-1-14b-flf2v-250417',\n              _(''),\n              ModelTypeConst.ITV,\n              ttv_credential,\n              GenerationVideoModel),\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_default_model_info(model_info_list[0])\n    .append_default_model_info(model_info_list[1])\n    .append_default_model_info(model_info_list[2])\n    .append_default_model_info(model_info_list[3])\n    .append_default_model_info(model_info_list[4])\n    .append_default_model_info(model_info_list[5])\n    .append_model_info_list(model_info_embedding_list)\n    .append_default_model_info(model_info_embedding_list[0])\n    .append_model_info_list(model_info_ttv_list)\n    .append_default_model_info(model_info_ttv_list[0])\n    .append_model_info_list(model_info_itv_list)\n    .append_default_model_info(model_info_itv_list[0])\n    .build()\n)\n\n\nclass VolcanicEngineModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_volcanic_engine_provider', name=_('volcano engine'),\n                                icon=get_file_content(\n                                    os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl',\n                                                 'volcanic_engine_model_provider',\n                                                 'icon',\n                                                 'volcanic_engine_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/wenxin_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2023/10/31 17:16\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/models_provider/impl/wenxin_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/wenxin_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/10/17 15:40\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass QianfanEmbeddingCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        api_version = model_credential.get('api_version', 'v1')\n        model = provider.get_model(model_type, model_name, model_credential, **model_params)\n        if api_version == 'v1':\n            model_type_list = provider.get_model_type_list()\n            if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('{model_type} Model type is not supported').format(model_type=model_type))\n            model_info = [model.lower() for model in model.client.models()]\n            if not model_info.__contains__(model_name.lower()):\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('{model_name} The model does not support').format(model_name=model_name))\n        required_keys = ['qianfan_ak', 'qianfan_sk']\n        if api_version == 'v2':\n            required_keys = ['api_base', 'qianfan_ak']\n\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        api_version = model.get('api_version', 'v1')\n        if api_version == 'v1':\n            return {**model, 'qianfan_sk': super().encryption(model.get('qianfan_sk', ''))}\n        else:  # v2\n            return {**model, 'qianfan_ak': super().encryption(model.get('qianfan_ak', ''))}\n\n    api_version = forms.Radio('API Version', required=True, text_field='label', value_field='value',\n                              option_list=[\n                                  {'label': 'v1', 'value': 'v1'},\n                                  {'label': 'v2', 'value': 'v2'}\n                              ],\n                              default_value='v1',\n                              provider='',\n                              method='', )\n\n    # v2版本字段\n    api_base = forms.TextInputField(\"API URL\", required=True, relation_show_field_dict={\"api_version\": [\"v2\"]})\n\n    # v1版本字段\n    qianfan_ak = forms.PasswordInputField('API Key', required=True)\n    qianfan_sk = forms.PasswordInputField(\"Secret Key\", required=True,\n                                          relation_show_field_dict={\"api_version\": [\"v1\"]})\n"
  },
  {
    "path": "apps/models_provider/impl/wenxin_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/12 10:19\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass WenxinLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.95,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=1024,\n        _min=2,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass WenxinLLMModelCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        # 根据api_version检查必需字段\n        api_version = model_credential.get('api_version', 'v1')\n        model = provider.get_model(model_type, model_name, model_credential, **model_params)\n        if api_version == 'v1':\n            model_type_list = provider.get_model_type_list()\n            if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('{model_type} Model type is not supported').format(model_type=model_type))\n            model_info = [model.lower() for model in model.client.models()]\n            if not model_info.__contains__(model_name.lower()):\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext('{model_name} The model does not support').format(model_name=model_name))\n        required_keys = ['api_key', 'secret_key']\n        if api_version == 'v2':\n            required_keys = ['api_base', 'api_key']\n\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model.invoke(\n                [HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            raise e\n        return True\n\n    def encryption_dict(self, model_info: Dict[str, object]):\n        # 根据api_version加密不同字段\n        api_version = model_info.get('api_version', 'v1')\n        if api_version == 'v1':\n            return {**model_info, 'secret_key': super().encryption(model_info.get('secret_key', ''))}\n        else:  # v2\n            return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))}\n\n    def build_model(self, model_info: Dict[str, object]):\n        api_version = model_info.get('api_version', 'v1')\n        # 根据api_version检查必需字段\n        if api_version == 'v1':\n            for key in ['api_version', 'api_key', 'secret_key', 'model']:\n                if key not in model_info:\n                    raise AppApiException(500, gettext('{key}  is required').format(key=key))\n            self.api_key = model_info.get('api_key')\n            self.secret_key = model_info.get('secret_key')\n        else:  # v2\n            for key in ['api_version', 'api_base', 'api_key', 'model', ]:\n                if key not in model_info:\n                    raise AppApiException(500, gettext('{key}  is required').format(key=key))\n            self.api_base = model_info.get('api_base')\n            self.api_key = model_info.get('api_key')\n        return self\n\n    # 动态字段定义 - 根据api_version显示不同字段\n    api_version = forms.Radio('API Version', required=True, text_field='label', value_field='value',\n                              option_list=[\n                                  {'label': 'v1', 'value': 'v1'},\n                                  {'label': 'v2', 'value': 'v2'}\n                              ],\n                              default_value='v1',\n                              provider='',\n                              method='', )\n\n    # v2版本字段\n    api_base = forms.TextInputField(\"API URL\", required=True, relation_show_field_dict={\"api_version\": [\"v2\"]})\n\n    # v1版本字段\n    api_key = forms.PasswordInputField('API Key', required=True)\n    secret_key = forms.PasswordInputField(\"Secret Key\", required=True,\n                                          relation_show_field_dict={\"api_version\": [\"v1\"]})\n\n    def get_model_params_setting_form(self, model_name):\n        return WenxinLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/wenxin_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/wenxin_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/10/17 16:48\n    @desc:\n\"\"\"\nfrom typing import Dict, List\nfrom langchain_community.embeddings import QianfanEmbeddingsEndpoint\nimport openai\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass QianfanV1Embeddings(MaxKBBaseModel, QianfanEmbeddingsEndpoint):\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return QianfanV1Embeddings(\n            model=model_name,\n            qianfan_ak=model_credential.get('qianfan_ak'),\n            qianfan_sk=model_credential.get('qianfan_sk'),\n        )\n\n\nclass QianfanV2EmbeddingModel(MaxKBBaseModel):\n    model_name: str\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    def __init__(self, api_key, base_url, model_name: str):\n        self.client = openai.OpenAI(api_key=api_key, base_url=base_url).embeddings\n        self.model_name = model_name\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return QianfanV2EmbeddingModel(\n            api_key=model_credential.get('qianfan_ak'),\n            model_name=model_name,\n            base_url=model_credential.get('api_base'),\n        )\n\n    def embed_query(self, text: str):\n        res = self.embed_documents([text])\n        return res[0]\n\n    def embed_documents(\n            self, texts: List[ str],\n    ) -> List[List[float]]:\n        res = self.client.create(input=texts, model=self.model_name, encoding_format=\"float\")\n        return [e.embedding for e in res.data]\n\n\nclass QianfanEmbeddings(MaxKBBaseModel):\n\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        api_version = model_credential.get('api_version', 'v1')\n\n        if api_version == \"v1\":\n            return QianfanV1Embeddings.new_instance(model_type, model_name, model_credential, **model_kwargs)\n        elif api_version == \"v2\":\n            return QianfanV2EmbeddingModel.new_instance(model_type, model_name, model_credential, **model_kwargs)\n"
  },
  {
    "path": "apps/models_provider/impl/wenxin_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： llm.py\n    @date：2023/11/10 17:45\n    @desc:\n\"\"\"\nfrom typing import List, Dict, Optional, Any, Iterator\n\nfrom langchain_community.chat_models.baidu_qianfan_endpoint import _convert_dict_to_message, QianfanChatEndpoint\nfrom langchain_core.callbacks import CallbackManagerForLLMRun\nfrom langchain_core.messages import (\n    AIMessageChunk,\n    BaseMessage,\n)\nfrom langchain_core.outputs import ChatGenerationChunk\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass QianfanChatModelQianfan(MaxKBBaseModel, QianfanChatEndpoint):\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return QianfanChatModelQianfan(model=model_name,\n                                       qianfan_ak=model_credential.get('api_key'),\n                                       qianfan_sk=model_credential.get('secret_key'),\n                                       streaming=model_kwargs.get('streaming', False),\n                                       init_kwargs=optional_params)\n\n    usage_metadata: dict = {}\n\n    def get_last_generation_info(self) -> Optional[Dict[str, Any]]:\n        return self.usage_metadata\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        return self.usage_metadata.get('prompt_tokens', 0)\n\n    def get_num_tokens(self, text: str) -> int:\n        return self.usage_metadata.get('completion_tokens', 0)\n\n    def _stream(\n            self,\n            messages: List[BaseMessage],\n            stop: Optional[List[str]] = None,\n            run_manager: Optional[CallbackManagerForLLMRun] = None,\n            **kwargs: Any,\n    ) -> Iterator[ChatGenerationChunk]:\n        kwargs = {**self.init_kwargs, **kwargs}\n        params = self._convert_prompt_msg_params(messages, **kwargs)\n        params[\"stop\"] = stop\n        params[\"stream\"] = True\n        for res in self.client.do(**params):\n            if res:\n                msg = _convert_dict_to_message(res)\n                additional_kwargs = msg.additional_kwargs.get(\"function_call\", {})\n                if msg.content == \"\" or res.get(\"body\").get(\"is_end\"):\n                    token_usage = res.get(\"body\").get(\"usage\")\n                    self.usage_metadata = token_usage\n                chunk = ChatGenerationChunk(\n                    text=res[\"result\"],\n                    message=AIMessageChunk(  # type: ignore[call-arg]\n                        content=msg.content,\n                        role=\"assistant\",\n                        additional_kwargs=additional_kwargs,\n                    ),\n                    generation_info=msg.additional_kwargs,\n                )\n                if run_manager:\n                    run_manager.on_llm_new_token(chunk.text, chunk=chunk)\n                yield chunk\n\n\nclass QianfanChatModelOpenai(MaxKBBaseModel, BaseChatOpenAI):\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return QianfanChatModelOpenai(\n            model=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            extra_body=optional_params\n        )\n\n\nclass QianfanChatModel(MaxKBBaseModel):\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        api_version = model_credential.get('api_version', 'v1')\n\n        if api_version == \"v1\":\n            return QianfanChatModelQianfan.new_instance(model_type, model_name, model_credential, **model_kwargs)\n        elif api_version == \"v2\":\n            return QianfanChatModelOpenai.new_instance(model_type, model_name, model_credential, **model_kwargs)\n"
  },
  {
    "path": "apps/models_provider/impl/wenxin_model_provider/wenxin_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： wenxin_model_provider.py\n    @date：2023/10/31 16:19\n    @desc:\n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import ModelProvideInfo, ModelTypeConst, ModelInfo, IModelProvider, \\\n    ModelInfoManage\nfrom models_provider.impl.wenxin_model_provider.credential.embedding import QianfanEmbeddingCredential\nfrom models_provider.impl.wenxin_model_provider.credential.llm import WenxinLLMModelCredential\nfrom models_provider.impl.wenxin_model_provider.model.embedding import QianfanEmbeddings\nfrom models_provider.impl.wenxin_model_provider.model.llm import QianfanChatModel\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\nwin_xin_llm_model_credential = WenxinLLMModelCredential()\nqianfan_embedding_credential = QianfanEmbeddingCredential()\nmodel_info_list = [ModelInfo('ERNIE-Bot-4',\n                             _('ERNIE-Bot-4 is a large language model independently developed by Baidu. It covers massive Chinese data and has stronger capabilities in dialogue Q&A, content creation and generation.'),\n                             ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel),\n                   ModelInfo('ERNIE-Bot',\n                             _('ERNIE-Bot is a large language model independently developed by Baidu. It covers massive Chinese data and has stronger capabilities in dialogue Q&A, content creation and generation.'),\n                             ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel),\n                   ModelInfo('ERNIE-Bot-turbo',\n                             _('ERNIE-Bot-turbo is a large language model independently developed by Baidu. It covers massive Chinese data, has stronger capabilities in dialogue Q&A, content creation and generation, and has a faster response speed.'),\n                             ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel),\n                   ModelInfo('qianfan-chinese-llama-2-13b',\n                             '',\n                             ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel),\n                   ModelInfo('ernie-4.5-turbo-32k', '', ModelTypeConst.LLM, win_xin_llm_model_credential,\n                             QianfanChatModel),\n                   ModelInfo('ernie-speed-8k', '', ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel),\n                   ModelInfo('ernie-4.5-0.3b', '', ModelTypeConst.LLM, win_xin_llm_model_credential, QianfanChatModel)\n\n                   ]\nembedding_model_info_list = [ModelInfo('Embedding-V1',\n                                       _('Embedding-V1 is a text representation model based on Baidu Wenxin large model technology. It can convert text into a vector form represented by numerical values and can be used in text retrieval, information recommendation, knowledge mining and other scenarios. Embedding-V1 provides the Embeddings interface, which can generate corresponding vector representations based on input content. You can call this interface to input text into the model and obtain the corresponding vector representation for subsequent text processing and analysis.'),\n                                       ModelTypeConst.EMBEDDING, qianfan_embedding_credential, QianfanEmbeddings),\n                             ModelInfo('tao-8k', '', ModelTypeConst.EMBEDDING, qianfan_embedding_credential,\n                                       QianfanEmbeddings),\n                             ModelInfo('bge-large-zh', '', ModelTypeConst.EMBEDDING, qianfan_embedding_credential,\n                                       QianfanEmbeddings)\n                             ]\nmodel_info_manage = ModelInfoManage.builder().append_model_info_list(model_info_list).append_default_model_info(\n    ModelInfo('ERNIE-Bot-4',\n              _('ERNIE-Bot-4 is a large language model independently developed by Baidu. It covers massive Chinese data and has stronger capabilities in dialogue Q&A, content creation and generation.'),\n              ModelTypeConst.LLM,\n              win_xin_llm_model_credential,\n              QianfanChatModel)).append_model_info_list(embedding_model_info_list).append_default_model_info(\n    embedding_model_info_list[0]).build()\n\n\nclass WenxinModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_wenxin_provider', name=_('Thousand sails large model'),\n                                icon=get_file_content(\n                                    os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl',\n                                                 'wenxin_model_provider', 'icon',\n                                                 'azure_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/04/19 15:55\n    @desc:\n\"\"\""
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/10/17 15:40\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass XFEmbeddingCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n        self.valid_form(model_credential)\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.embed_query(_('Hello'))\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))}\n\n    base_url = forms.TextInputField('API URL', required=True, default_value=\"https://emb-cn-huabei-1.xf-yun.com/\")\n    spark_app_id = forms.TextInputField('APP ID', required=True)\n    spark_api_key = forms.PasswordInputField(\"API Key\", required=True)\n    spark_api_secret = forms.PasswordInputField('API Secret', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/credential/image.py",
    "content": "# coding=utf-8\nimport base64\nimport os\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.xf_model_provider.model.image import ImageMessage\nfrom common.utils.logger import maxkb_logger\n\nclass XunFeiImageModelCredential(BaseForm, BaseModelCredential):\n    spark_api_url = forms.TextInputField('API URL', required=True,\n                                         default_value='wss://spark-api.cn-huabei-1.xf-yun.com/v2.1/image')\n    spark_app_id = forms.TextInputField('APP ID', required=True)\n    spark_api_key = forms.PasswordInputField(\"API Key\", required=True)\n    spark_api_secret = forms.PasswordInputField('API Secret', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            cwd = os.path.dirname(os.path.abspath(__file__))\n            with open(f'{cwd}/img_1.png', 'rb') as f:\n                message_list = [ImageMessage(str(base64.b64encode(f.read()), 'utf-8')),\n                                HumanMessage(_('Please outline this picture'))]\n                model.stream(message_list)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        pass\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/12 10:29\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass XunFeiLLMModelGeneralParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.5,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=4096,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass XunFeiLLMModelProParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.5,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=4096,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass XunFeiLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))}\n\n    spark_api_url = forms.TextInputField('API URL', required=True)\n    spark_app_id = forms.TextInputField('APP ID', required=True)\n    spark_api_key = forms.PasswordInputField(\"API Key\", required=True)\n    spark_api_secret = forms.PasswordInputField('API Secret', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        if model_name == 'general' or model_name == 'pro-128k':\n            return XunFeiLLMModelGeneralParams()\n        return XunFeiLLMModelProParams()\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/credential/stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass XunFeiSTTModelParams(BaseForm):\n    language = forms.TextInputField(\n        TooltipLabel(_('language'), _('If not passed, the default value is zh_cn')),\n        required=True,\n        default_value='zh_cn'\n    )\n    domain = forms.TextInputField(\n        TooltipLabel(_('domain'), _('If not passed, the default value is iat')),\n        required=True,\n        default_value='iat'\n    )\n    accent = forms.TextInputField(\n        TooltipLabel(_('accent'), _('If not passed, the default value is mandarin')),\n        required=True,\n        default_value='mandarin'\n    )\n\n\nclass XunFeiSTTModelCredential(BaseForm, BaseModelCredential):\n    spark_api_url = forms.TextInputField('API URL', required=True, default_value='wss://iat-api.xfyun.cn/v2/iat')\n    spark_app_id = forms.TextInputField('APP ID', required=True)\n    spark_api_key = forms.PasswordInputField(\"API Key\", required=True)\n    spark_api_secret = forms.PasswordInputField('API Secret', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return XunFeiSTTModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/credential/tts/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2025/12/10 14:13\n    @desc:\n\"\"\"\nfrom .tts import *\nfrom .default_tts import *\nfrom .super_humanoid_tts import *"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/credential/tts/default_tts.py",
    "content": "# coding=utf-8\n\"\"\"\n讯飞 TTS 工厂类 Credential，根据 api_version 路由到具体 Credential\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\n\nclass XunFeiDefaultTTSModelCredential(BaseForm, BaseModelCredential):\n    \"\"\"讯飞 TTS 工厂类 Credential，根据 api_version 参数路由到具体实现\"\"\"\n\n    api_version = forms.SingleSelect(\n        _(\"API Version\"), required=True,\n        text_field='label',\n        value_field='value',\n        default_value='online',\n        option_list=[\n            {'label': _('Online TTS'), 'value': 'online'},\n            {'label': _('Super Humanoid TTS'), 'value': 'super_humanoid'}\n        ])\n\n    spark_api_url = forms.TextInputField(_('API URL'), required=True)\n    spark_app_id = forms.TextInputField('APP ID', required=True)\n    spark_api_key = forms.PasswordInputField(\"API Key\", required=True)\n    spark_api_secret = forms.PasswordInputField('API Secret', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        api_version = model_credential.get('api_version', 'online')\n\n        for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        # params 只包含通用参数，vcn 已在 credential 中\n        return XunFeiDefaultTTSModelParams()\n\n\nclass XunFeiDefaultTTSModelParams(BaseForm):\n    \"\"\"工厂类的参数表单，只包含通用参数\"\"\"\n\n    speed = forms.SliderField(\n        TooltipLabel(_('speaking speed'), _('Speech speed, optional value: [0-100], default is 50')),\n        required=True, default_value=50,\n        _min=1,\n        _max=100,\n        _step=5,\n        precision=1)\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/credential/tts/super_humanoid_tts.py",
    "content": "# coding=utf-8\n\"\"\"\n讯飞超拟人语音合成 (Super Humanoid TTS) Credential\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass XunFeiSuperHumanoidTTSModelParams(BaseForm):\n    \"\"\"超拟人语音合成参数\"\"\"\n    vcn = forms.SingleSelect(\n        TooltipLabel(_('Speaker'), _('Speaker selection for super-humanoid TTS service')),\n        required=True, default_value='x5_lingxiaoxuan_flow',\n        text_field='label',\n        value_field='value',\n        option_list=[\n            {'label': _('Super-humanoid: Lingxiaoxuan Flow'), 'value': 'x5_lingxiaoxuan_flow'},\n            {'label': _('Super-humanoid: Lingyuyan Flow'), 'value': 'x5_lingyuyan_flow'},\n            {'label': _('Super-humanoid: Lingfeiyi Flow'), 'value': 'x5_lingfeiyi_flow'},\n            {'label': _('Super-humanoid: Lingxiaoyue Flow'), 'value': 'x5_lingxiaoyue_flow'},\n            {'label': _('Super-humanoid: Sun Dasheng Flow'), 'value': 'x5_sundasheng_flow'},\n            {'label': _('Super-humanoid: Lingyuzhao Flow'), 'value': 'x5_lingyuzhao_flow'},\n            {'label': _('Super-humanoid: Lingxiaotang Flow'), 'value': 'x5_lingxiaotang_flow'},\n            {'label': _('Super-humanoid: Lingxiaorong Flow'), 'value': 'x5_lingxiaorong_flow'},\n            {'label': _('Super-humanoid: Xinyun Flow'), 'value': 'x5_xinyun_flow'},\n            {'label': _('Super-humanoid: Grant (EN)'), 'value': 'x5_EnUs_Grant_flow'},\n            {'label': _('Super-humanoid: Lila (EN)'), 'value': 'x5_EnUs_Lila_flow'},\n            {'label': _('Super-humanoid: Lingwanwan Pro'), 'value': 'x6_lingwanwan_pro'},\n            {'label': _('Super-humanoid: Yiyi Pro'), 'value': 'x6_yiyi_pro'},\n            {'label': _('Super-humanoid: Huifangnv Pro'), 'value': 'x6_huifangnv_pro'},\n            {'label': _('Super-humanoid: Lingxiaoying Pro'), 'value': 'x6_lingxiaoying_pro'},\n            {'label': _('Super-humanoid: Lingfeibo Pro'), 'value': 'x6_lingfeibo_pro'},\n            {'label': _('Super-humanoid: Lingyuyan Pro'), 'value': 'x6_lingyuyan_pro'},\n        ])\n\n    speed = forms.SliderField(\n        TooltipLabel(_('speaking speed'), _('Speech speed, optional value: [0-100], default is 50')),\n        required=True, default_value=50,\n        _min=1,\n        _max=100,\n        _step=5,\n        precision=1)\n\n\nclass XunFeiSuperHumanoidTTSModelCredential(BaseForm, BaseModelCredential):\n    \"\"\"讯飞超拟人语音合成 Credential\"\"\"\n    spark_api_url = forms.TextInputField('API URL', required=True,\n                                         default_value='wss://cbm01.cn-huabei-1.xf-yun.com/v1/private/mcd9m97e6')\n    spark_app_id = forms.TextInputField('APP ID', required=True)\n    spark_api_key = forms.PasswordInputField(\"API Key\", required=True)\n    spark_api_secret = forms.PasswordInputField('API Secret', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        required_keys = ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']\n\n        for key in required_keys:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key} is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return XunFeiSuperHumanoidTTSModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/credential/tts/tts.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass XunFeiTTSModelGeneralParams(BaseForm):\n    vcn = forms.SingleSelect(\n        TooltipLabel(_('Speaker'),\n                     _('Speaker, optional value: Please go to the console to add a trial or purchase speaker. After adding, the speaker parameter value will be displayed.')),\n        required=True, default_value='xiaoyan',\n        text_field='value',\n        value_field='value',\n        option_list=[\n            {'text': _('iFlytek Xiaoyan'), 'value': 'xiaoyan'},\n            {'text': _('iFlytek Xujiu'), 'value': 'aisjiuxu'},\n            {'text': _('iFlytek Xiaoping'), 'value': 'aisxping'},\n            {'text': _('iFlytek Xiaojing'), 'value': 'aisjinger'},\n            {'text': _('iFlytek Xuxiaobao'), 'value': 'aisbabyxu'},\n        ])\n    speed = forms.SliderField(\n        TooltipLabel(_('speaking speed'), _('Speech speed, optional value: [0-100], default is 50')),\n        required=True, default_value=50,\n        _min=1,\n        _max=100,\n        _step=5,\n        precision=1)\n\n\nclass XunFeiTTSModelCredential(BaseForm, BaseModelCredential):\n    spark_api_url = forms.TextInputField('API URL', required=True, default_value='wss://tts-api.xfyun.cn/v2/tts')\n    spark_app_id = forms.TextInputField('APP ID', required=True)\n    spark_api_key = forms.PasswordInputField(\"API Key\", required=True)\n    spark_api_secret = forms.PasswordInputField('API Secret', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return XunFeiTTSModelGeneralParams()\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/credential/zh_en_stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\n\nclass ZhEnXunFeiSTTModelCredential(BaseForm, BaseModelCredential):\n    spark_api_url = forms.TextInputField('API URL', required=True, default_value='wss://iat.xf-yun.com/v1')\n    spark_app_id = forms.TextInputField('APP ID', required=True)\n    spark_api_key = forms.PasswordInputField(\"API Key\", required=True)\n    spark_api_secret = forms.PasswordInputField('API Secret', required=True)\n\n    def is_valid(self,\n                 model_type: str,\n                 model_name,\n                 model_credential: Dict[str, object],\n                 model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['spark_api_url', 'spark_app_id', 'spark_api_key', 'spark_api_secret']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'spark_api_secret': super().encryption(model.get('spark_api_secret', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        pass"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/model/embedding.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： embedding.py\n    @date：2024/10/17 15:29\n    @desc:\n\"\"\"\n\nimport base64\nimport json\nfrom typing import Dict, Optional\nfrom langchain_community.embeddings import SparkLLMTextEmbeddings\nfrom numpy import ndarray\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nimport time\nimport json\nimport base64\nimport numpy as np\nimport threading\nimport queue\n\n_task_queue = queue.Queue()\n\n\ndef _worker():\n    while True:\n        message, future = _task_queue.get()\n\n        for i in range(3):\n            try:\n                data = json.loads(message)\n                code = data[\"header\"][\"code\"]\n\n                if code != 0:\n                    raise Exception(f\"Request error: {code}, {data}\")\n\n                text_base = data[\"payload\"][\"feature\"][\"text\"]\n                text_data = base64.b64decode(text_base)\n\n                dt = np.dtype(np.float32)\n                dt = dt.newbyteorder(\"<\")\n\n                text = np.frombuffer(text_data, dtype=dt)\n\n                if len(text) > 2560:\n                    array = text[:2560]\n                else:\n                    array = text\n\n                future[\"result\"] = array\n                future[\"event\"].set()\n                break\n\n            except Exception as e:\n\n                if i == 2:\n                    future[\"error\"] = e\n                    future[\"event\"].set()\n                else:\n                    time.sleep(0.5)\n\n        time.sleep(0.5)  # QPS=2\n\n\nthreading.Thread(target=_worker, daemon=True).start()\n\n\nclass XFEmbedding(MaxKBBaseModel, SparkLLMTextEmbeddings):\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return XFEmbedding(\n            base_url=model_credential.get('base_url'),\n            spark_app_id=model_credential.get('spark_app_id'),\n            spark_api_key=model_credential.get('spark_api_key'),\n            spark_api_secret=model_credential.get('spark_api_secret')\n        )\n\n    @staticmethod\n    def _parser_message(\n            message: str,\n    ) -> Optional[ndarray]:\n        future = {\n            \"event\": threading.Event(),\n            \"result\": None,\n            \"error\": None\n        }\n\n        _task_queue.put((message, future))\n\n        future[\"event\"].wait()\n\n        if future[\"error\"]:\n            raise future[\"error\"]\n\n        return future[\"result\"]\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/model/image.py",
    "content": "# coding=utf-8\nimport base64\nimport os\nfrom typing import Dict, Any, List, Optional, Iterator\n\n#from docutils.utils import SystemMessage\nfrom langchain_community.chat_models.sparkllm import ChatSparkLLM, _convert_delta_to_message_chunk\nfrom langchain_core.callbacks import CallbackManagerForLLMRun\nfrom langchain_core.messages import BaseMessage, ChatMessage, HumanMessage, AIMessage, AIMessageChunk\nfrom langchain_core.outputs import ChatGenerationChunk\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass ImageMessage(HumanMessage):\n    content: str\n\n\ndef convert_message_to_dict(message: BaseMessage) -> dict:\n    message_dict: Dict[str, Any]\n    if isinstance(message, ChatMessage):\n        message_dict = {\"role\": \"user\", \"content\": message.content}\n    elif isinstance(message, ImageMessage):\n        message_dict = {\"role\": \"user\", \"content\": message.content, \"content_type\": \"image\"}\n    elif isinstance(message, HumanMessage):\n        message_dict = {\"role\": \"user\", \"content\": message.content}\n    elif isinstance(message, AIMessage):\n        message_dict = {\"role\": \"assistant\", \"content\": message.content}\n        if \"function_call\" in message.additional_kwargs:\n            message_dict[\"function_call\"] = message.additional_kwargs[\"function_call\"]\n            # If function call only, content is None not empty string\n            if message_dict[\"content\"] == \"\":\n                message_dict[\"content\"] = None\n        if \"tool_calls\" in message.additional_kwargs:\n            message_dict[\"tool_calls\"] = message.additional_kwargs[\"tool_calls\"]\n            # If tool calls only, content is None not empty string\n            if message_dict[\"content\"] == \"\":\n                message_dict[\"content\"] = None\n    # elif isinstance(message, SystemMessage):\n    #     message_dict = {\"role\": \"system\", \"content\": message.content}\n    else:\n        raise ValueError(f\"Got unknown type {message}\")\n\n    return message_dict\n\n\nclass XFSparkImage(MaxKBBaseModel, ChatSparkLLM):\n    spark_app_id: str\n    spark_api_key: str\n    spark_api_secret: str\n    spark_api_url: str\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return XFSparkImage(\n            spark_app_id=model_credential.get('spark_app_id'),\n            spark_api_key=model_credential.get('spark_api_key'),\n            spark_api_secret=model_credential.get('spark_api_secret'),\n            spark_api_url=model_credential.get('spark_api_url'),\n            **optional_params\n        )\n\n    @staticmethod\n    def generate_message(prompt: str, image) -> list[BaseMessage]:\n        if image is None:\n            cwd = os.path.dirname(os.path.abspath(__file__))\n            with open(f'{cwd}/img_1.png', 'rb') as f:\n                base64_image = base64.b64encode(f.read()).decode(\"utf-8\")\n                return [ImageMessage(f'data:image/jpeg;base64,{base64_image}'), HumanMessage(prompt)]\n        return [HumanMessage(prompt)]\n\n    def _stream(\n            self,\n            messages: List[BaseMessage],\n            stop: Optional[List[str]] = None,\n            run_manager: Optional[CallbackManagerForLLMRun] = None,\n            **kwargs: Any,\n    ) -> Iterator[ChatGenerationChunk]:\n        default_chunk_class = AIMessageChunk\n\n        self.client.arun(\n            [convert_message_to_dict(m) for m in messages],\n            self.spark_user_id,\n            self.model_kwargs,\n            streaming=True,\n        )\n        for content in self.client.subscribe(timeout=self.request_timeout):\n            if \"data\" not in content:\n                continue\n            delta = content[\"data\"]\n            chunk = _convert_delta_to_message_chunk(delta, default_chunk_class)\n            cg_chunk = ChatGenerationChunk(message=chunk)\n            if run_manager:\n                run_manager.on_llm_new_token(str(chunk.content), chunk=cg_chunk)\n            yield cg_chunk\n\n    @staticmethod\n    def is_cache_model():\n        return False\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/04/19 15:55\n    @desc:\n\"\"\"\nfrom typing import List, Optional, Any, Iterator, Dict\n\nfrom langchain_community.chat_models.sparkllm import \\\n    ChatSparkLLM, convert_message_to_dict, _convert_delta_to_message_chunk\nfrom langchain_core.callbacks import CallbackManagerForLLMRun\nfrom langchain_core.messages import BaseMessage, AIMessageChunk\nfrom langchain_core.outputs import ChatGenerationChunk\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass XFChatSparkLLM(MaxKBBaseModel, ChatSparkLLM):\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return XFChatSparkLLM(\n            spark_app_id=model_credential.get('spark_app_id'),\n            spark_api_key=model_credential.get('spark_api_key'),\n            spark_api_secret=model_credential.get('spark_api_secret'),\n            spark_api_url=model_credential.get('spark_api_url'),\n            spark_llm_domain=model_name,\n            streaming=model_kwargs.get('streaming', False),\n            **optional_params\n        )\n\n    usage_metadata: dict = {}\n\n    def get_last_generation_info(self) -> Optional[Dict[str, Any]]:\n        return self.usage_metadata\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        return self.usage_metadata.get('prompt_tokens', 0)\n\n    def get_num_tokens(self, text: str) -> int:\n        return self.usage_metadata.get('completion_tokens', 0)\n\n    def _stream(\n            self,\n            messages: List[BaseMessage],\n            stop: Optional[List[str]] = None,\n            run_manager: Optional[CallbackManagerForLLMRun] = None,\n            **kwargs: Any,\n    ) -> Iterator[ChatGenerationChunk]:\n        default_chunk_class = AIMessageChunk\n\n        self.client.arun(\n            [convert_message_to_dict(m) for m in messages],\n            self.spark_user_id,\n            self.model_kwargs,\n            True,\n        )\n        for content in self.client.subscribe(timeout=self.request_timeout):\n            if \"data\" in content:\n                delta = content[\"data\"]\n                chunk = _convert_delta_to_message_chunk(delta, default_chunk_class)\n                cg_chunk = ChatGenerationChunk(message=chunk)\n            elif \"usage\" in content:\n                generation_info = content[\"usage\"]\n                self.usage_metadata = generation_info\n                continue\n            else:\n                continue\n            if cg_chunk is not None:\n                if run_manager:\n                    run_manager.on_llm_new_token(str(cg_chunk.message.content), chunk=cg_chunk)\n            yield cg_chunk\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/model/stt.py",
    "content": "# -*- coding:utf-8 -*-\n#\n#  错误码链接：https://www.xfyun.cn/document/error-code （code返回错误码时必看）\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\nimport asyncio\nimport base64\nimport datetime\nimport hashlib\nimport hmac\nimport json\nimport logging\nimport os\nimport ssl\nfrom datetime import datetime, UTC\nfrom typing import Dict\nfrom urllib.parse import urlencode, urlparse\n\nimport websockets\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\nSTATUS_FIRST_FRAME = 0  # 第一帧的标识\nSTATUS_CONTINUE_FRAME = 1  # 中间帧标识\nSTATUS_LAST_FRAME = 2  # 最后一帧的标识\n\nssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\nssl_context.check_hostname = False\nssl_context.verify_mode = ssl.CERT_NONE\n\n\nclass XFSparkSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    spark_app_id: str\n    spark_api_key: str\n    spark_api_secret: str\n    spark_api_url: str\n    params: dict\n    model_name: str\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.spark_api_url = kwargs.get('spark_api_url')\n        self.spark_app_id = kwargs.get('spark_app_id')\n        self.spark_api_key = kwargs.get('spark_api_key')\n        self.spark_api_secret = kwargs.get('spark_api_secret')\n        self.params = kwargs.get('params')\n        self.model_name = kwargs.get('model_name')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {}\n        if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None:\n            optional_params['max_tokens'] = model_kwargs['max_tokens']\n        if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None:\n            optional_params['temperature'] = model_kwargs['temperature']\n        return XFSparkSpeechToText(\n            spark_app_id=model_credential.get('spark_app_id'),\n            spark_api_key=model_credential.get('spark_api_key'),\n            spark_api_secret=model_credential.get('spark_api_secret'),\n            spark_api_url=model_credential.get('spark_api_url'),\n            params=model_kwargs,\n            model_name=model_name,\n            **optional_params\n        )\n\n    # 生成url\n    def create_url(self):\n        url = self.spark_api_url\n        host = urlparse(url).hostname\n        # 生成RFC1123格式的时间戳\n        gmt_format = '%a, %d %b %Y %H:%M:%S GMT'\n        date = datetime.now(UTC).strftime(gmt_format)\n\n        # 拼接字符串\n        signature_origin = \"host: \" + host + \"\\n\"\n        signature_origin += \"date: \" + date + \"\\n\"\n        signature_origin += \"GET \" + \"/v2/iat \" + \"HTTP/1.1\"\n        # 进行hmac-sha256进行加密\n        signature_sha = hmac.new(self.spark_api_secret.encode('utf-8'), signature_origin.encode('utf-8'),\n                                 digestmod=hashlib.sha256).digest()\n        signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')\n\n        authorization_origin = \"api_key=\\\"%s\\\", algorithm=\\\"%s\\\", headers=\\\"%s\\\", signature=\\\"%s\\\"\" % (\n            self.spark_api_key, \"hmac-sha256\", \"host date request-line\", signature_sha)\n        authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')\n        # 将请求的鉴权参数组合为字典\n        v = {\n            \"authorization\": authorization,\n            \"date\": date,\n            \"host\": host\n        }\n        # 拼接鉴权参数，生成url\n        url = url + '?' + urlencode(v)\n        # print(\"date: \",date)\n        # print(\"v: \",v)\n        # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释，比对相同参数时生成的url与自己代码生成的url是否一致\n        # print('websocket url :', url)\n        return url\n\n    def check_auth(self):\n        cwd = os.path.dirname(os.path.abspath(__file__))\n        with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as f:\n            self.speech_to_text(f)\n\n    def speech_to_text(self, file):\n        async def handle():\n            async with websockets.connect(self.create_url(), max_size=1000000000, ssl=ssl_context) as ws:\n                # 发送 full client request\n                await self.send(ws, file)\n                return await self.handle_message(ws)\n\n        return asyncio.run(handle())\n\n    @staticmethod\n    async def handle_message(ws):\n        res = await ws.recv()\n        message = json.loads(res)\n        code = message[\"code\"]\n        sid = message[\"sid\"]\n        if code != 0:\n            errMsg = message[\"message\"]\n            raise Exception(f\"sid: {sid} call error: {errMsg} code is: {code}\")\n        else:\n            data = message[\"data\"][\"result\"][\"ws\"]\n            result = \"\"\n            for i in data:\n                for w in i[\"cw\"]:\n                    result += w[\"w\"]\n            # print(\"sid:%s call success!,data is:%s\" % (sid, json.dumps(data, ensure_ascii=False)))\n            return result\n\n    # 收到websocket连接建立的处理\n    async def send(self, ws, file):\n        frameSize = 8000  # 每一帧的音频大小\n        status = STATUS_FIRST_FRAME  # 音频的状态信息，标识音频是第一帧，还是中间帧、最后一帧\n\n        allowed_params = {'language', 'domain', 'accent', 'vad_eos', 'dwa', 'pd', 'ptt',\n                          'pcm', 'ltc', 'rlang', 'vinfo', 'nunum', 'speex_size', 'nbest', 'wbest'}\n\n        business_params = {k: v for k, v in self.params.items() if k in allowed_params}\n        if not business_params:\n            business_params = {\n                \"domain\": f'{self.model_name}',\n                \"language\": \"zh_cn\",\n                \"accent\": \"mandarin\",\n                \"vinfo\": 1,\n                \"vad_eos\": 10000\n            }\n        while True:\n            buf = file.read(frameSize)\n            # 文件结束\n            if not buf:\n                status = STATUS_LAST_FRAME\n            # 第一帧处理\n            # 发送第一帧音频，带business 参数\n            # appid 必须带上，只需第一帧发送\n            if status == STATUS_FIRST_FRAME:\n                d = {\n                    \"common\": {\"app_id\": self.spark_app_id},\n                    \"business\": {\n                        **business_params\n                    },\n                    \"data\": {\n                        \"status\": 0, \"format\": \"audio/L16;rate=16000\",\n                        \"audio\": str(base64.b64encode(buf), 'utf-8'),\n                        \"encoding\": \"lame\"}\n                }\n                d = json.dumps(d)\n                await ws.send(d)\n                status = STATUS_CONTINUE_FRAME\n            # 中间帧处理\n            elif status == STATUS_CONTINUE_FRAME:\n                d = {\"data\": {\"status\": 1, \"format\": \"audio/L16;rate=16000\",\n                              \"audio\": str(base64.b64encode(buf), 'utf-8'),\n                              \"encoding\": \"lame\"}}\n                await ws.send(json.dumps(d))\n            # 最后一帧处理\n            elif status == STATUS_LAST_FRAME:\n                d = {\"data\": {\"status\": 2, \"format\": \"audio/L16;rate=16000\",\n                              \"audio\": str(base64.b64encode(buf), 'utf-8'),\n                              \"encoding\": \"lame\"}}\n                await ws.send(json.dumps(d))\n                break\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/model/tts/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2025/12/10 14:14\n    @desc:\n\"\"\"\nfrom .super_humanoid_tts import *\nfrom .tts import *\nfrom .default_tts import *"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/model/tts/default_tts.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：\n    @file： default_tts.py\n    @date：2025/12/9\n    @desc: 讯飞 TTS 工厂类，根据 api_version 路由到具体实现\n\"\"\"\nfrom typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tts import BaseTextToSpeech\n\n\nclass XFSparkDefaultTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):\n    \"\"\"讯飞 TTS 工厂类，根据 api_version 参数路由到具体实现\"\"\"\n\n    def check_auth(self):\n        pass\n\n    def text_to_speech(self, text):\n        pass\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        from models_provider.impl.xf_model_provider.model.tts import XFSparkTextToSpeech\n        from models_provider.impl.xf_model_provider.model.tts.super_humanoid_tts import XFSparkSuperHumanoidTextToSpeech\n\n        api_version = model_credential.get('api_version', 'online')\n\n        if api_version == 'super_humanoid':\n            return XFSparkSuperHumanoidTextToSpeech(\n                spark_app_id=model_credential.get('spark_app_id'),\n                spark_api_key=model_credential.get('spark_api_key'),\n                spark_api_secret=model_credential.get('spark_api_secret'),\n                spark_api_url=model_credential.get('spark_api_url'),\n                params = model_kwargs,\n                **model_kwargs\n            )\n        else:\n            # 在线语音：从 credential 获取 vcn_online\n            return XFSparkTextToSpeech(\n                spark_app_id=model_credential.get('spark_app_id'),\n                spark_api_key=model_credential.get('spark_api_key'),\n                spark_api_secret=model_credential.get('spark_api_secret'),\n                spark_api_url=model_credential.get('spark_api_url'),\n                params={key: v for key, v in model_kwargs.items() if\n            not ['parameter', 'streaming', 'model_id', 'use_local'].__contains__(key)},\n                **model_kwargs\n            )"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/model/tts/super_humanoid_tts.py",
    "content": "# -*- coding:utf-8 -*-\n#\n#   author: iflytek\n#\n#  错误码链接：https://www.xfyun.cn/document/error-code （code返回错误码时必看）\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\nimport asyncio\nimport base64\nimport hashlib\nimport hmac\nimport json\nimport ssl\nfrom datetime import datetime, UTC\nfrom typing import Dict\nfrom urllib.parse import urlencode, urlparse\n\nimport websockets\nfrom django.utils.translation import gettext as _\n\nfrom common.utils.common import _remove_empty_lines\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tts import BaseTextToSpeech\n\nssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\nssl_context.check_hostname = False\nssl_context.verify_mode = ssl.CERT_NONE\n\n\nclass XFSparkSuperHumanoidTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):\n    \"\"\"讯飞超拟人语音合成 (Super Humanoid TTS)\"\"\"\n    spark_app_id: str\n    spark_api_key: str\n    spark_api_secret: str\n    spark_api_url: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.spark_api_url = kwargs.get('spark_api_url')\n        self.spark_app_id = kwargs.get('spark_app_id')\n        self.spark_api_key = kwargs.get('spark_api_key')\n        self.spark_api_secret = kwargs.get('spark_api_secret')\n        self.params = kwargs.get('params') or {}\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        # vcn = model_kwargs.get('vcn', 'x5_lingxiaoxuan_flow')\n\n        params = {}\n        for k, v in model_kwargs.items():\n            if k not in ['model_id', 'use_local', 'streaming']:\n                params[k] = v\n\n        return XFSparkSuperHumanoidTextToSpeech(\n            spark_app_id=model_credential.get('spark_app_id'),\n            spark_api_key=model_credential.get('spark_api_key'),\n            spark_api_secret=model_credential.get('spark_api_secret'),\n            spark_api_url=model_credential.get('spark_api_url'),\n            params=params,\n            **model_kwargs\n        )\n\n    def create_url(self):\n        url = self.spark_api_url\n        host = urlparse(url).hostname\n\n        gmt_format = '%a, %d %b %Y %H:%M:%S GMT'\n        date = datetime.now(UTC).strftime(gmt_format)\n\n        signature_origin = f\"host: {host}\\n\"\n        signature_origin += f\"date: {date}\\n\"\n        signature_origin += f\"GET {urlparse(url).path} HTTP/1.1\"\n\n        signature_sha = hmac.new(\n            self.spark_api_secret.encode('utf-8'),\n            signature_origin.encode('utf-8'),\n            digestmod=hashlib.sha256\n        ).digest()\n\n        signature_sha = base64.b64encode(signature_sha).decode('utf-8')\n\n        authorization_origin = \\\n            f'api_key=\"{self.spark_api_key}\", algorithm=\"hmac-sha256\", headers=\"host date request-line\", signature=\"{signature_sha}\"'\n\n        authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode('utf-8')\n\n        v = {\n            \"authorization\": authorization,\n            \"date\": date,\n            \"host\": host\n        }\n\n        url = url + '?' + urlencode(v)\n        return url\n\n    def check_auth(self):\n        self.text_to_speech(_('Hello'))\n\n    def text_to_speech(self, text):\n        text = _remove_empty_lines(text)\n\n        async def handle():\n            try:\n                async with websockets.connect(self.create_url(), max_size=1000000000, ssl=ssl_context) as ws:\n                    await self.send(ws, text)\n                    return await self.handle_message(ws)\n            except websockets.exceptions.InvalidStatus as e:\n                if e.response.status_code == 401:\n                    raise Exception(\n                        _(\"Authentication failed (HTTP 401). Please check: \"\n                          \"1) API URL is correct for TTS service; \"\n                          \"2) APP ID, API Key, and API Secret are correct; \"\n                          \"3) Your iFlytek account has TTS service enabled.\")\n                    )\n                else:\n                    raise Exception(f\"WebSocket connection failed: HTTP {e.response.status_code}\")\n            except Exception as e:\n                if \"Authentication failed\" in str(e):\n                    raise\n                raise Exception(f\"iFlytek TTS service error: {str(e)}\")\n\n        return asyncio.run(handle())\n\n    @staticmethod\n    async def handle_message(ws):\n        audio_bytes: bytes = b''\n        while True:\n            res = await ws.recv()\n            message = json.loads(res)\n\n            if \"header\" in message and \"code\" in message[\"header\"]:\n                code = message[\"header\"][\"code\"]\n                sid = message[\"header\"].get(\"sid\", \"unknown\")\n\n                if code != 0:\n                    errMsg = message[\"header\"].get(\"message\", \"Unknown error\")\n                    raise Exception(f\"sid: {sid} call error: {errMsg} code is: {code}\")\n\n                if \"payload\" in message and \"audio\" in message[\"payload\"]:\n                    audio = base64.b64decode(message[\"payload\"][\"audio\"][\"audio\"])\n                    audio_bytes += audio\n\n                    if message[\"payload\"][\"audio\"].get(\"status\") == 2:\n                        break\n            else:\n                raise Exception(\n                    f\"Unexpected response from iFlytek API. Response: {json.dumps(message, ensure_ascii=False)}\"\n                )\n\n        return audio_bytes\n\n    async def send(self, ws, text):\n\n        audio_params = {\n            \"encoding\": self.params.get(\"encoding\", \"lame\"),\n            \"sample_rate\": self.params.get(\"sample_rate\", 24000),\n            \"channels\": self.params.get(\"channels\", 1),\n            \"bit_depth\": self.params.get(\"bit_depth\", 16),\n            \"frame_size\": self.params.get(\"frame_size\", 0)\n        }\n\n        tts_params = {\n            **{key: v for key, v in self.params.items() if\n               not ['parameter', 'streaming', 'model_id', 'use_local'].__contains__(key)},\n            \"vcn\": self.params.get(\"vcn\") or \"x5_lingxiaoxuan_flow\",\n            \"audio\": audio_params,\n            \"volume\": self.params.get(\"volume\", 50),\n            \"speed\": self.params.get(\"speed\", 50),\n            \"pitch\": self.params.get(\"pitch\", 50)\n        }\n\n        encoded_text = base64.b64encode(text.encode('utf-8')).decode('utf-8')\n        payload_text_obj = {\n            \"encoding\": \"utf8\",\n            \"compress\": \"raw\",\n            \"format\": \"plain\",\n            \"status\": 2,\n            \"seq\": 0,\n            \"text\": encoded_text\n        }\n        s = {\"tts\": tts_params}\n        # \"parameter\": {\"oar\":\"xxxx\"}\n        parameter = self.params.get(\"parameter\") or {}\n\n        d = {\n            \"header\": {\"app_id\": self.spark_app_id, \"status\": 2},\n            \"parameter\": {\"tts\": tts_params} | parameter,\n            \"payload\": {\"text\": payload_text_obj}\n        }\n\n        await ws.send(json.dumps(d))\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/model/tts/tts.py",
    "content": "# -*- coding:utf-8 -*-\n#\n#   author: iflytek\n#\n#  错误码链接：https://www.xfyun.cn/document/error-code （code返回错误码时必看）\n# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #\nimport asyncio\nimport base64\nimport datetime\nimport hashlib\nimport hmac\nimport json\nimport logging\nimport ssl\nfrom datetime import datetime, UTC\nfrom typing import Dict\nfrom urllib.parse import urlencode, urlparse\n\nimport websockets\nfrom django.utils.translation import gettext as _\n\nfrom common.utils.common import _remove_empty_lines\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tts import BaseTextToSpeech\n\nSTATUS_FIRST_FRAME = 0  # 第一帧的标识\nSTATUS_CONTINUE_FRAME = 1  # 中间帧标识\nSTATUS_LAST_FRAME = 2  # 最后一帧的标识\n\nssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\nssl_context.check_hostname = False\nssl_context.verify_mode = ssl.CERT_NONE\n\n\nclass XFSparkTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):\n    spark_app_id: str\n    spark_api_key: str\n    spark_api_secret: str\n    spark_api_url: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.spark_api_url = kwargs.get('spark_api_url')\n        self.spark_app_id = kwargs.get('spark_app_id')\n        self.spark_api_key = kwargs.get('spark_api_key')\n        self.spark_api_secret = kwargs.get('spark_api_secret')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'vcn': 'xiaoyan', 'speed': 50}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return XFSparkTextToSpeech(\n            spark_app_id=model_credential.get('spark_app_id'),\n            spark_api_key=model_credential.get('spark_api_key'),\n            spark_api_secret=model_credential.get('spark_api_secret'),\n            spark_api_url=model_credential.get('spark_api_url'),\n            **optional_params\n        )\n\n    # 生成url\n    def create_url(self):\n        url = self.spark_api_url\n        host = urlparse(url).hostname\n        # 生成RFC1123格式的时间戳\n        gmt_format = '%a, %d %b %Y %H:%M:%S GMT'\n        date = datetime.now(UTC).strftime(gmt_format)\n\n        # 拼接字符串\n        signature_origin = \"host: \" + host + \"\\n\"\n        signature_origin += \"date: \" + date + \"\\n\"\n        signature_origin += \"GET \" + \"/v2/tts \" + \"HTTP/1.1\"\n        # 进行hmac-sha256进行加密\n        signature_sha = hmac.new(self.spark_api_secret.encode('utf-8'), signature_origin.encode('utf-8'),\n                                 digestmod=hashlib.sha256).digest()\n        signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')\n\n        authorization_origin = \"api_key=\\\"%s\\\", algorithm=\\\"%s\\\", headers=\\\"%s\\\", signature=\\\"%s\\\"\" % (\n            self.spark_api_key, \"hmac-sha256\", \"host date request-line\", signature_sha)\n        authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')\n        # 将请求的鉴权参数组合为字典\n        v = {\n            \"authorization\": authorization,\n            \"date\": date,\n            \"host\": host\n        }\n        # 拼接鉴权参数，生成url\n        url = url + '?' + urlencode(v)\n        # print(\"date: \",date)\n        # print(\"v: \",v)\n        # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释，比对相同参数时生成的url与自己代码生成的url是否一致\n        # print('websocket url :', url)\n        return url\n\n    def check_auth(self):\n        self.text_to_speech(_('Hello'))\n\n    def text_to_speech(self, text):\n\n        # 使用小语种须使用以下方式，此处的unicode指的是 utf16小端的编码方式，即\"UTF-16LE\"”\n        # self.Data = {\"status\": 2, \"text\": str(base64.b64encode(self.Text.encode('utf-16')), \"UTF8\")}\n        text = _remove_empty_lines(text)\n\n        async def handle():\n            async with websockets.connect(self.create_url(), max_size=1000000000, ssl=ssl_context) as ws:\n                # 发送 full client request\n                await self.send(ws, text)\n                return await self.handle_message(ws)\n\n        return asyncio.run(handle())\n\n    def is_cache_model(self):\n        return False\n\n    @staticmethod\n    async def handle_message(ws):\n        audio_bytes: bytes = b''\n        while True:\n            res = await ws.recv()\n            message = json.loads(res)\n            # print(message)\n            code = message[\"code\"]\n            sid = message[\"sid\"]\n\n            if code != 0:\n                errMsg = message[\"message\"]\n                raise Exception(f\"sid: {sid} call error: {errMsg} code is: {code}\")\n            else:\n                audio = message[\"data\"][\"audio\"]\n                audio = base64.b64decode(audio)\n                audio_bytes += audio\n            # 退出\n            if message[\"data\"][\"status\"] == 2:\n                break\n        return audio_bytes\n\n    async def send(self, ws, text):\n        business = {\"aue\": \"lame\", \"sfl\": 1, \"auf\": \"audio/L16;rate=16000\", \"tte\": \"utf8\"}\n        d = {\n            \"common\": {\"app_id\": self.spark_app_id},\n            \"business\": business | self.params,\n            \"data\": {\"status\": 2, \"text\": str(base64.b64encode(text.encode('utf-8')), \"UTF8\")},\n        }\n        d = json.dumps(d)\n        await ws.send(d)\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/model/zh_en_stt.py",
    "content": "import asyncio\nimport json\nimport base64\nimport hmac\nimport hashlib\nimport ssl\nimport traceback\nfrom typing import Dict\nfrom urllib.parse import urlencode\nfrom datetime import datetime, timezone, UTC\nimport websockets\nimport os\n\nfrom future.backports.urllib.parse import urlparse\n\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\nssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\nssl_context.check_hostname = False\nssl_context.verify_mode = ssl.CERT_NONE\n\n\ndef deep_merge_dict(target_dict, source_dict):\n\n    if not isinstance(source_dict, dict):\n        return source_dict\n    result = target_dict.copy() if isinstance(target_dict, dict) else {}\n    for key, value in source_dict.items():\n        if key in result and isinstance(result[key], dict) and isinstance(value, dict):\n            result[key] = deep_merge_dict(result[key], value)\n        else:\n            result[key] = value\n    return result\n\n\nclass XFZhEnSparkSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    spark_app_id: str\n    spark_api_key: str\n    spark_api_secret: str\n    spark_api_url: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.spark_api_url = kwargs.get('spark_api_url')\n        self.spark_app_id = kwargs.get('spark_app_id')\n        self.spark_api_key = kwargs.get('spark_api_key')\n        self.spark_api_secret = kwargs.get('spark_api_secret')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n\n        return XFZhEnSparkSpeechToText(\n            spark_app_id=model_credential.get('spark_app_id'),\n            spark_api_key=model_credential.get('spark_api_key'),\n            spark_api_secret=model_credential.get('spark_api_secret'),\n            spark_api_url=model_credential.get('spark_api_url'),\n            params=model_kwargs,\n            **model_kwargs\n        )\n\n    # 生成url\n    def create_url(self):\n        url = self.spark_api_url\n        host = urlparse(url).hostname\n\n        gmt_format = '%a, %d %b %Y %H:%M:%S GMT'\n        date = datetime.now(UTC).strftime(gmt_format)\n        # 拼接字符串\n        signature_origin = \"host: \" + host + \"\\n\"\n        signature_origin += \"date: \" + date + \"\\n\"\n        signature_origin += \"GET \" + \"/v1 HTTP/1.1\"\n        # 进行hmac-sha256进行加密\n        signature_sha = hmac.new(\n            self.spark_api_secret.encode('utf-8'),\n            signature_origin.encode('utf-8'),\n            hashlib.sha256\n        ).digest()\n        signature = base64.b64encode(signature_sha).decode(encoding='utf-8')\n\n        authorization_origin = (\n            f'api_key=\"{self.spark_api_key}\", algorithm=\"hmac-sha256\", '\n            f'headers=\"host date request-line\", signature=\"{signature}\"'\n        )\n        authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')\n\n        params = {\n            'authorization': authorization,\n            'date': date,\n            'host': host\n        }\n        auth_url = url + '?' + urlencode(params)\n        return auth_url\n\n    def check_auth(self):\n        cwd = os.path.dirname(os.path.abspath(__file__))\n        with open(f'{cwd}/iat_mp3_16k.mp3', 'rb') as f:\n            self.speech_to_text(f)\n\n    def speech_to_text(self, audio_file_path):\n        async def handle():\n            async with websockets.connect(self.create_url(), max_size=1000000000, ssl=ssl_context) as ws:\n                # print(\"连接成功\")\n                # 发送音频数据\n                await self.send_audio(ws, audio_file_path)\n                # 接收识别结果\n                return await self.handle_message(ws)\n        try:\n            return asyncio.run(handle())\n        except Exception as err:\n            maxkb_logger.error(f\"语音识别错误: {str(err)}: {traceback.format_exc()}\")\n            raise\n\n    def merge_params_to_frame(self, frame,params):\n\n        return deep_merge_dict(frame, params)\n\n    async def send_audio(self, ws, audio_file):\n        \"\"\"发送音频数据\"\"\"\n        chunk_size = 4000\n        seq = 1\n        max_chunks = 10000\n        while True:\n            chunk = audio_file.read(chunk_size)\n            if not chunk or seq > max_chunks:\n                break\n\n            chunk_base64 = base64.b64encode(chunk).decode('utf-8')\n            # 第一帧\n            if seq == 1:\n                frame = {\n                    \"header\": {\"app_id\": self.spark_app_id, \"status\": 0},\n                    \"parameter\": {\n                        \"iat\": {\n                            \"domain\": \"slm\",\n                            \"language\": \"zh_cn\",\n                            \"accent\": \"mandarin\",\n                            \"eos\": 10000,\n                            \"vinfo\": 1,\n                            \"result\": {\"encoding\": \"utf8\", \"compress\": \"raw\", \"format\": \"json\"}\n                        }\n                    },\n                    \"payload\": {\n                        \"audio\": {\n                            \"encoding\": \"lame\", \"sample_rate\": 16000, \"channels\": 1,\n                            \"bit_depth\": 16, \"seq\": seq, \"status\": 0, \"audio\": chunk_base64\n                        }\n                    }\n                }\n                frame = self.merge_params_to_frame(frame,{key: value for key, value in self.params.items() if\n                                            not ['model_id', 'use_local', 'streaming'].__contains__(key)})\n\n            # 中间帧\n            else:\n                frame = {\n                    \"header\": {\"app_id\": self.spark_app_id, \"status\": 1},\n                    \"payload\": {\n                        \"audio\": {\n                            \"encoding\": \"lame\", \"sample_rate\": 16000, \"channels\": 1,\n                            \"bit_depth\": 16, \"seq\": seq, \"status\": 1, \"audio\": chunk_base64\n                        }\n                    }\n                }\n\n                frame = self.merge_params_to_frame(frame,{key: value for key, value in self.params.items() if\n                                            not ['model_id', 'use_local', 'streaming','parameter'].__contains__(key)})\n\n            await ws.send(json.dumps(frame))\n            seq += 1\n\n        # 发送结束帧\n        end_frame = {\n            \"header\": {\"app_id\": self.spark_app_id, \"status\": 2},\n            \"payload\": {\n                \"audio\": {\n                    \"encoding\": \"lame\", \"sample_rate\": 16000, \"channels\": 1,\n                    \"bit_depth\": 16, \"seq\": seq, \"status\": 2, \"audio\": \"\"\n                }\n            }\n        }\n\n        end_frame = self.merge_params_to_frame(end_frame,{key: value for key, value in self.params.items() if\n                                            not ['model_id', 'use_local', 'streaming','parameter'].__contains__(key)})\n\n        await ws.send(json.dumps(end_frame))\n\n    # 接受信息处理器\n    async def handle_message(self, ws):\n        result_text = \"\"\n        while True:\n            try:\n                message = await asyncio.wait_for(ws.recv(), timeout=30.0)\n                data = json.loads(message)\n                if data['header']['code'] != 0:\n                    raise Exception(\"\")\n\n                if 'payload' in data and 'result' in data['payload']:\n                    result = data['payload']['result']\n                    text = result.get('text', '')\n                    if text:\n                        text_data = json.loads(base64.b64decode(text).decode('utf-8'))\n                        for ws_item in text_data.get('ws', []):\n                            for cw in ws_item.get('cw', []):\n                                for sw in cw.get('w', []):\n                                    result_text += sw\n\n                if data['header'].get('status') == 2:\n                    break\n            except asyncio.TimeoutError:\n                break\n\n        return result_text\n"
  },
  {
    "path": "apps/models_provider/impl/xf_model_provider/xf_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： xf_model_provider.py\n    @date：2024/04/19 14:47\n    @desc:\n\"\"\"\nimport os\nimport ssl\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import ModelProvideInfo, ModelTypeConst, ModelInfo, IModelProvider, \\\n    ModelInfoManage\nfrom models_provider.impl.xf_model_provider.credential.embedding import XFEmbeddingCredential\nfrom models_provider.impl.xf_model_provider.credential.image import XunFeiImageModelCredential\nfrom models_provider.impl.xf_model_provider.credential.llm import XunFeiLLMModelCredential\nfrom models_provider.impl.xf_model_provider.credential.stt import XunFeiSTTModelCredential\nfrom models_provider.impl.xf_model_provider.credential.tts import XunFeiTTSModelCredential\nfrom models_provider.impl.xf_model_provider.credential.tts.super_humanoid_tts import XunFeiSuperHumanoidTTSModelCredential\nfrom models_provider.impl.xf_model_provider.credential.tts.default_tts import XunFeiDefaultTTSModelCredential\nfrom models_provider.impl.xf_model_provider.credential.zh_en_stt import ZhEnXunFeiSTTModelCredential\nfrom models_provider.impl.xf_model_provider.model.embedding import XFEmbedding\nfrom models_provider.impl.xf_model_provider.model.llm import XFChatSparkLLM\nfrom models_provider.impl.xf_model_provider.model.stt import XFSparkSpeechToText\nfrom models_provider.impl.xf_model_provider.model.tts import XFSparkTextToSpeech\nfrom models_provider.impl.xf_model_provider.model.tts.super_humanoid_tts import XFSparkSuperHumanoidTextToSpeech\nfrom models_provider.impl.xf_model_provider.model.tts.default_tts import XFSparkDefaultTextToSpeech\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\nfrom models_provider.impl.xf_model_provider.model.zh_en_stt import XFZhEnSparkSpeechToText\n\nssl._create_default_https_context = ssl.create_default_context()\n\nxunfei_model_credential = XunFeiLLMModelCredential()\nstt_model_credential = XunFeiSTTModelCredential()\nzh_en_stt_credential = ZhEnXunFeiSTTModelCredential()\nimage_model_credential = XunFeiImageModelCredential()\n# TTS credentials\ntts_model_credential = XunFeiTTSModelCredential()\nsuper_humanoid_tts_credential = XunFeiSuperHumanoidTTSModelCredential()\ndefault_tts_credential = XunFeiDefaultTTSModelCredential()\nembedding_model_credential = XFEmbeddingCredential()\n\nmodel_info_list = [\n    ModelInfo('generalv3.5', '', ModelTypeConst.LLM, xunfei_model_credential, XFChatSparkLLM),\n    ModelInfo('generalv3', '', ModelTypeConst.LLM, xunfei_model_credential, XFChatSparkLLM),\n    ModelInfo('generalv2', '', ModelTypeConst.LLM, xunfei_model_credential, XFChatSparkLLM),\n    ModelInfo('iat', _('Chinese and English recognition'), ModelTypeConst.STT, stt_model_credential,\n              XFSparkSpeechToText),\n    ModelInfo('slm', _('Chinese and English recognition'), ModelTypeConst.STT, zh_en_stt_credential,\n              XFZhEnSparkSpeechToText),\n    # 具体 TTS 模型\n    ModelInfo('tts', _('Online TTS'), ModelTypeConst.TTS, tts_model_credential, XFSparkTextToSpeech),\n    ModelInfo('tts-super-humanoid', _('Super Humanoid TTS'), ModelTypeConst.TTS, super_humanoid_tts_credential,\n              XFSparkSuperHumanoidTextToSpeech),\n    ModelInfo('embedding', '', ModelTypeConst.EMBEDDING, embedding_model_credential, XFEmbedding)\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_default_model_info(\n        ModelInfo('generalv3.5', '', ModelTypeConst.LLM, xunfei_model_credential, XFChatSparkLLM))\n    .append_default_model_info(\n        ModelInfo('iat', _('Chinese and English recognition'), ModelTypeConst.STT, stt_model_credential,\n                  XFSparkSpeechToText),\n    )\n    # default TTS 工厂入口\n    .append_default_model_info(\n        ModelInfo('default', _('default'), ModelTypeConst.TTS, default_tts_credential, XFSparkDefaultTextToSpeech))\n    .append_default_model_info(\n        ModelInfo('embedding', '', ModelTypeConst.EMBEDDING, embedding_model_credential, XFEmbedding))\n    .build()\n)\n\n\nclass XunFeiModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_xf_provider', name=_('iFlytek Spark'), icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'xf_model_provider', 'icon',\n                         'xf_icon_svg')))"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/__init__.py",
    "content": "# coding=utf-8\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/credential/embedding.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom models_provider.impl.local_model_provider.model.embedding import LocalEmbedding\n\n\nclass XinferenceEmbeddingModelCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n        try:\n            model_list = provider.get_base_model_list(model_credential.get('api_base'), model_credential.get('api_key'),\n                                                      'embedding')\n        except Exception as e:\n            raise AppApiException(ValidCode.valid_error.value, _('API domain name is invalid'))\n        exist = provider.get_model_info_by_name(model_list, model_name)\n        model: LocalEmbedding = provider.get_model(model_type, model_name, model_credential)\n        if len(exist) == 0:\n            model.start_down_model_thread()\n            raise AppApiException(ValidCode.model_not_fount,\n                                  _('The model does not exist, please download the model first'))\n        model.embed_query(_('Hello'))\n        return True\n\n    def encryption_dict(self, model_info: Dict[str, object]):\n        return model_info\n\n    def build_model(self, model_info: Dict[str, object]):\n        for key in ['model']:\n            if key not in model_info:\n                raise AppApiException(500, _('{key}  is required').format(key=key))\n        return self\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/credential/image.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass XinferenceImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass XinferenceImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext('Hello')}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return XinferenceImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass XinferenceLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.7,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=800,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass XinferenceLLMModelCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n        try:\n            model_list = provider.get_base_model_list(model_credential.get('api_base'), model_credential.get('api_key'),\n                                                      model_type)\n        except Exception as e:\n            raise AppApiException(ValidCode.valid_error.value, gettext('API domain name is invalid'))\n        exist = provider.get_model_info_by_name(model_list, model_name)\n        if len(exist) == 0:\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('The model does not exist, please download the model first'))\n        model = provider.get_model(model_type, model_name, model_credential, **model_params)\n        model.invoke([HumanMessage(content=gettext('Hello'))])\n        return True\n\n    def encryption_dict(self, model_info: Dict[str, object]):\n        return {**model_info, 'api_key': super().encryption(model_info.get('api_key', ''))}\n\n    def build_model(self, model_info: Dict[str, object]):\n        for key in ['api_key', 'model']:\n            if key not in model_info:\n                raise AppApiException(500, gettext('{key}  is required').format(key=key))\n        self.api_key = model_info.get('api_key')\n        return self\n\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return XinferenceLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/credential/reranker.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： reranker.py\n    @date：2024/9/10 9:46\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\nfrom langchain_core.documents import Document\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass XInferenceRerankerModelCredential(BaseForm, BaseModelCredential):\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=True):\n        if not model_type == 'RERANKER':\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n        for key in ['server_url']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential)\n            model.compress_documents([Document(page_content=_('Hello'))], _('Hello'))\n        except Exception as e:\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model_info: Dict[str, object]):\n        return model_info\n\n    server_url = forms.TextInputField('API URL', required=True)\n\n    api_key = forms.PasswordInputField('API Key', required=False)\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/credential/stt.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext as _\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass XInferenceSTTModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  _('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, _('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      _('Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        pass\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/credential/tti.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass XinferenceTTIModelParams(BaseForm):\n    size = forms.SingleSelect(\n        TooltipLabel(_('Image size'),\n                     _('The image generation endpoint allows you to create raw images based on text prompts. The dimensions of the image can be 1024x1024, 1024x1792, or 1792x1024 pixels.')),\n        required=True,\n        default_value='1024x1024',\n        option_list=[\n            {'value': '1024x1024', 'label': '1024x1024'},\n            {'value': '1024x1792', 'label': '1024x1792'},\n            {'value': '1792x1024', 'label': '1792x1024'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    quality = forms.SingleSelect(\n        TooltipLabel(_('Picture quality'),\n                     _('By default, images are generated in standard quality, you can set quality: \"hd\" to enhance detail. Square, standard quality images are generated fastest.')),\n        required=True,\n        default_value='standard',\n        option_list=[\n            {'value': 'standard', 'label': 'standard'},\n            {'value': 'hd', 'label': 'hd'},\n        ],\n        text_field='label',\n        value_field='value'\n    )\n\n    n = forms.SliderField(\n        TooltipLabel(_('Number of pictures'),\n                     _('You can request 1 image at a time (requesting more images by making parallel requests), or up to 10 images at a time using the n parameter.')),\n        required=True, default_value=1,\n        _min=1,\n        _max=10,\n        _step=1,\n        precision=0)\n\n\nclass XinferenceTextToImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return XinferenceTTIModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/credential/tts.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass XInferenceTTSModelGeneralParams(BaseForm):\n    # ['中文女', '中文男', '日语男', '粤语女', '英文女', '英文男', '韩语女']\n    voice = forms.SingleSelect(\n        TooltipLabel(_('timbre'), ''),\n        required=True, default_value='中文女',\n        text_field='value',\n        value_field='value',\n        option_list=[\n            {'text': _('Chinese female'), 'value': '中文女'},\n            {'text': _('Chinese male'), 'value': '中文男'},\n            {'text': _('Japanese male'), 'value': '日语男'},\n            {'text': _('Cantonese female'), 'value': '粤语女'},\n            {'text': _('English female'), 'value': '英文女'},\n            {'text': _('English male'), 'value': '英文男'},\n            {'text': _('Korean female'), 'value': '韩语女'},\n        ])\n\n\nclass XInferenceTTSModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True)\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_base', 'api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.check_auth()\n        except Exception as e:\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return XInferenceTTSModelGeneralParams()\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/model/embedding.py",
    "content": "# coding=utf-8\nimport threading\nfrom typing import Dict, Optional, List, Any\n\nfrom langchain_core.embeddings import Embeddings\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass XinferenceEmbedding(MaxKBBaseModel, Embeddings):\n    client: Any\n    server_url: Optional[str]\n    \"\"\"URL of the xinference server\"\"\"\n    model_uid: Optional[str]\n    \"\"\"UID of the launched model\"\"\"\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return XinferenceEmbedding(\n            model_uid=model_name,\n            server_url=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n        )\n\n    def down_model(self):\n        self.client.launch_model(model_name=self.model_uid, model_type=\"embedding\")\n\n    def start_down_model_thread(self):\n        thread = threading.Thread(target=self.down_model)\n        thread.daemon = True\n        thread.start()\n\n    def __init__(\n            self, server_url: Optional[str] = None, model_uid: Optional[str] = None,\n            api_key: Optional[str] = None\n    ):\n        try:\n            from xinference.client import RESTfulClient\n        except ImportError:\n            try:\n                from xinference_client import RESTfulClient\n            except ImportError as e:\n                raise ImportError(\n                    \"Could not import RESTfulClient from xinference. Please install it\"\n                    \" with `pip install xinference` or `pip install xinference_client`.\"\n                ) from e\n\n        if server_url is None:\n            raise ValueError(\"Please provide server URL\")\n\n        if model_uid is None:\n            raise ValueError(\"Please provide the model UID\")\n\n        self.server_url = server_url\n\n        self.model_uid = model_uid\n\n        self.api_key = api_key\n\n        self.client = RESTfulClient(server_url, api_key)\n\n    def embed_documents(self, texts: List[str]) -> List[List[float]]:\n        \"\"\"Embed a list of documents using Xinference.\n        Args:\n            texts: The list of texts to embed.\n        Returns:\n            List of embeddings, one for each text.\n        \"\"\"\n\n        model = self.client.get_model(self.model_uid)\n\n        embeddings = [\n            model.create_embedding(text)[\"data\"][0][\"embedding\"] for text in texts\n        ]\n        return [list(map(float, e)) for e in embeddings]\n\n    def embed_query(self, text: str) -> List[float]:\n        \"\"\"Embed a query of documents using Xinference.\n        Args:\n            text: The text to embed.\n        Returns:\n            Embeddings for the text.\n        \"\"\"\n\n        model = self.client.get_model(self.model_uid)\n\n        embedding_res = model.create_embedding(text)\n\n        embedding = embedding_res[\"data\"][0][\"embedding\"]\n\n        return list(map(float, embedding))\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/model/image.py",
    "content": "from typing import Dict, List\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\nclass XinferenceImage(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return XinferenceImage(\n            model_name=model_name,\n            openai_api_base=model_credential.get('api_base'),\n            openai_api_key=model_credential.get('api_key'),\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        if self.usage_metadata is None or self.usage_metadata == {}:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n        return self.usage_metadata.get('input_tokens', 0)\n\n    def get_num_tokens(self, text: str) -> int:\n        if self.usage_metadata is None or self.usage_metadata == {}:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n        return self.get_last_generation_info().get('output_tokens', 0)\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\nfrom typing import Dict, List\nfrom urllib.parse import urlparse, ParseResult\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\ndef get_base_url(url: str):\n    parse = urlparse(url)\n    result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='',\n                             query='',\n                             fragment='').geturl()\n    return result_url[:-1] if result_url.endswith(\"/\") else result_url\n\n\nclass XinferenceChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        api_base = model_credential.get('api_base', '')\n        base_url = get_base_url(api_base)\n        base_url = base_url if base_url.endswith('/v1') else (base_url + '/v1')\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return XinferenceChatModel(\n            model=model_name,\n            openai_api_base=base_url,\n            openai_api_key=model_credential.get('api_key'),\n            extra_body=optional_params\n        )\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        if self.usage_metadata is None or self.usage_metadata == {}:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n        return self.usage_metadata.get('input_tokens', 0)\n\n    def get_num_tokens(self, text: str) -> int:\n        if self.usage_metadata is None or self.usage_metadata == {}:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n        return self.get_last_generation_info().get('output_tokens', 0)\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/model/reranker.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： reranker.py\n    @date：2024/9/10 9:45\n    @desc:\n\"\"\"\nfrom typing import Sequence, Optional, Any, Dict\n\nfrom langchain_core.callbacks import Callbacks\nfrom langchain_core.documents import BaseDocumentCompressor, Document\nfrom xinference_client.client.restful.restful_client import RESTfulRerankModelHandle\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\n\n\nclass XInferenceReranker(MaxKBBaseModel, BaseDocumentCompressor):\n    server_url: Optional[str]\n    \"\"\"URL of the xinference server\"\"\"\n    model_uid: Optional[str]\n    \"\"\"UID of the launched model\"\"\"\n    api_key: Optional[str]\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        return XInferenceReranker(server_url=model_credential.get('server_url'), model_uid=model_name,\n                                  api_key=model_credential.get('api_key'), top_n=model_kwargs.get('top_n', 3))\n\n    top_n: Optional[int] = 3\n\n    def compress_documents(self, documents: Sequence[Document], query: str, callbacks: Optional[Callbacks] = None) -> \\\n            Sequence[Document]:\n        if documents is None or len(documents) == 0:\n            return []\n        client: Any\n        if documents is None or len(documents) == 0:\n            return []\n        try:\n            from xinference.client import RESTfulClient\n        except ImportError:\n            try:\n                from xinference_client import RESTfulClient\n            except ImportError as e:\n                raise ImportError(\n                    \"Could not import RESTfulClient from xinference. Please install it\"\n                    \" with `pip install xinference` or `pip install xinference_client`.\"\n                ) from e\n\n        client = RESTfulClient(self.server_url, self.api_key)\n        model: RESTfulRerankModelHandle = client.get_model(self.model_uid)\n        res = model.rerank([document.page_content for document in documents], query, self.top_n, return_documents=True)\n        return [Document(page_content=d.get('document', {}).get('text'),\n                         metadata={'relevance_score': d.get('relevance_score')}) for d in res.get('results', [])]\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/model/stt.py",
    "content": "import io\nfrom typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_stt import BaseSpeechToText\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass XInferenceSpeechToText(MaxKBBaseModel, BaseSpeechToText):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {}\n        if 'max_tokens' in model_kwargs and model_kwargs['max_tokens'] is not None:\n            optional_params['max_tokens'] = model_kwargs['max_tokens']\n        if 'temperature' in model_kwargs and model_kwargs['temperature'] is not None:\n            optional_params['temperature'] = model_kwargs['temperature']\n        return XInferenceSpeechToText(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            params=model_kwargs,\n            **optional_params,\n        )\n\n    def check_auth(self):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        response_list = client.models.with_raw_response.list()\n        # print(response_list)\n\n    def speech_to_text(self, audio_file):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        audio_data = audio_file.read()\n        buffer = io.BytesIO(audio_data)\n        buffer.name = \"file.mp3\"  # this is the important line\n\n        filter_params = {k: v for k, v in self.params.items() if k not in {'model_id', 'use_local', 'streaming'}}\n        transcription_params = {\n            'model': self.model,\n            'file': buffer,\n            'language': 'zh',\n            **filter_params\n        }\n\n        res = client.audio.transcriptions.create(**transcription_params)\n        return res.text\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/model/tti.py",
    "content": "import base64\nfrom typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom common.utils.common import bytes_to_uploaded_file\nfrom knowledge.models import FileSourceType\n# from dataset.serializers.file_serializers import FileSerializer\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\nfrom oss.serializers.file import FileSerializer\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass XinferenceTextToImage(MaxKBBaseModel, BaseTextToImage):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'size': '1024x1024', 'quality': 'standard', 'n': 1}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return XinferenceTextToImage(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        self.generate_image('生成一个小猫图片')\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        chat = OpenAI(api_key=self.api_key, base_url=self.api_base)\n        res = chat.images.generate(model=self.model, prompt=prompt, response_format='b64_json', **self.params)\n        file_urls = []\n        # 临时文件\n        for img in res.data:\n            file_urls.append(base64.b64decode(img.b64_json))\n\n        return file_urls\n"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/model/tts.py",
    "content": "from typing import Dict\n\nfrom openai import OpenAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom common.utils.common import _remove_empty_lines\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tts import BaseTextToSpeech\nfrom django.utils.translation import gettext as _\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass XInferenceTextToSpeech(MaxKBBaseModel, BaseTextToSpeech):\n    api_base: str\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.api_base = kwargs.get('api_base')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'voice': '中文女'}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return XInferenceTextToSpeech(\n            model=model_name,\n            api_base=model_credential.get('api_base'),\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def check_auth(self):\n        self.text_to_speech(_('Hello'))\n\n    def text_to_speech(self, text):\n        client = OpenAI(\n            base_url=self.api_base,\n            api_key=self.api_key\n        )\n        # ['中文女', '中文男', '日语男', '粤语女', '英文女', '英文男', '韩语女']\n        text = _remove_empty_lines(text)\n        with client.audio.speech.with_streaming_response.create(\n                model=self.model,\n                input=text,\n                **self.params\n        ) as response:\n            return response.read()"
  },
  {
    "path": "apps/models_provider/impl/xinference_model_provider/xinference_model_provider.py",
    "content": "# coding=utf-8\nimport os\nfrom urllib.parse import urlparse, ParseResult\n\nimport requests\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import IModelProvider, ModelProvideInfo, ModelInfo, ModelTypeConst, \\\n    ModelInfoManage\nfrom models_provider.impl.xinference_model_provider.credential.embedding import \\\n    XinferenceEmbeddingModelCredential\nfrom models_provider.impl.xinference_model_provider.credential.image import XinferenceImageModelCredential\nfrom models_provider.impl.xinference_model_provider.credential.llm import XinferenceLLMModelCredential\nfrom models_provider.impl.xinference_model_provider.credential.reranker import XInferenceRerankerModelCredential\nfrom models_provider.impl.xinference_model_provider.credential.stt import XInferenceSTTModelCredential\nfrom models_provider.impl.xinference_model_provider.credential.tti import XinferenceTextToImageModelCredential\nfrom models_provider.impl.xinference_model_provider.credential.tts import XInferenceTTSModelCredential\nfrom models_provider.impl.xinference_model_provider.model.embedding import XinferenceEmbedding\nfrom models_provider.impl.xinference_model_provider.model.image import XinferenceImage\nfrom models_provider.impl.xinference_model_provider.model.llm import XinferenceChatModel\nfrom models_provider.impl.xinference_model_provider.model.reranker import XInferenceReranker\nfrom models_provider.impl.xinference_model_provider.model.stt import XInferenceSpeechToText\nfrom models_provider.impl.xinference_model_provider.model.tti import XinferenceTextToImage\nfrom models_provider.impl.xinference_model_provider.model.tts import XInferenceTextToSpeech\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\nxinference_llm_model_credential = XinferenceLLMModelCredential()\nxinference_stt_model_credential = XInferenceSTTModelCredential()\nxinference_tts_model_credential = XInferenceTTSModelCredential()\nxinference_image_model_credential = XinferenceImageModelCredential()\nxinference_tti_model_credential = XinferenceTextToImageModelCredential()\n\nmodel_info_list = [\n    ModelInfo(\n        'code-llama',\n        _('Code Llama is a language model specifically designed for code generation.'),\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'code-llama-instruct',\n        _('''       \nCode Llama Instruct is a fine-tuned version of Code Llama's instructions, designed to perform specific tasks.\n        '''),\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'code-llama-python',\n        _('Code Llama Python is a language model specifically designed for Python code generation.'),\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'codeqwen1.5',\n        _('CodeQwen 1.5 is a language model for code generation with high performance.'),\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'codeqwen1.5-chat',\n        _('CodeQwen 1.5 Chat is a chat model version of CodeQwen 1.5.'),\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'deepseek',\n        _('Deepseek is a large-scale language model with 13 billion parameters.'),\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'deepseek-chat',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'deepseek-coder',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'deepseek-coder-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'deepseek-vl-chat',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'gpt-3.5-turbo',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'gpt-4',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'gpt-4-vision-preview',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'gpt4all',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'llama2',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'llama2-chat',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'llama2-chat-32k',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen-chat',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen-chat-32k',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen-code',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen-code-chat',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen-vl',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen-vl-chat',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2-72b-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2-57b-a14b-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2-7b-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2.5-72b-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2.5-32b-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2.5-14b-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2.5-7b-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2.5-1.5b-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2.5-0.5b-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'qwen2.5-3b-instruct',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n    ModelInfo(\n        'minicpm-llama3-v-2_5',\n        '',\n        ModelTypeConst.LLM,\n        xinference_llm_model_credential,\n        XinferenceChatModel\n    ),\n]\n\nvoice_model_info = [\n    ModelInfo(\n        'CosyVoice-300M-SFT',\n        '',\n        ModelTypeConst.TTS,\n        xinference_tts_model_credential,\n        XInferenceTextToSpeech\n    ),\n    ModelInfo(\n        'Belle-whisper-large-v3-zh',\n        '',\n        ModelTypeConst.STT,\n        xinference_stt_model_credential,\n        XInferenceSpeechToText\n    ),\n]\n\nimage_model_info = [\n    ModelInfo(\n        'qwen-vl-chat',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'deepseek-vl-chat',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'yi-vl-chat',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'omnilmm',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'internvl-chat',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'cogvlm2',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'MiniCPM-Llama3-V-2_5',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'GLM-4V',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'MiniCPM-V-2.6',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'internvl2',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'qwen2-vl-instruct',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'llama-3.2-vision',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'llama-3.2-vision-instruct',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n    ModelInfo(\n        'glm-edge-v',\n        '',\n        ModelTypeConst.IMAGE,\n        xinference_image_model_credential,\n        XinferenceImage\n    ),\n]\n\ntti_model_info = [\n    ModelInfo(\n        'sd-turbo',\n        '',\n        ModelTypeConst.TTI,\n        xinference_tti_model_credential,\n        XinferenceTextToImage\n    ),\n    ModelInfo(\n        'sdxl-turbo',\n        '',\n        ModelTypeConst.TTI,\n        xinference_tti_model_credential,\n        XinferenceTextToImage\n    ),\n    ModelInfo(\n        'stable-diffusion-v1.5',\n        '',\n        ModelTypeConst.TTI,\n        xinference_tti_model_credential,\n        XinferenceTextToImage\n    ),\n    ModelInfo(\n        'stable-diffusion-xl-base-1.0',\n        '',\n        ModelTypeConst.TTI,\n        xinference_tti_model_credential,\n        XinferenceTextToImage\n    ),\n    ModelInfo(\n        'sd3-medium',\n        '',\n        ModelTypeConst.TTI,\n        xinference_tti_model_credential,\n        XinferenceTextToImage\n    ),\n    ModelInfo(\n        'FLUX.1-schnell',\n        '',\n        ModelTypeConst.TTI,\n        xinference_tti_model_credential,\n        XinferenceTextToImage\n    ),\n    ModelInfo(\n        'FLUX.1-dev',\n        '',\n        ModelTypeConst.TTI,\n        xinference_tti_model_credential,\n        XinferenceTextToImage\n    ),\n]\n\nxinference_embedding_model_credential = XinferenceEmbeddingModelCredential()\n\n# 生成embedding_model_info列表\nembedding_model_info = [\n    ModelInfo('bce-embedding-base_v1', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-base-en', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-base-en-v1.5', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-base-zh', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-base-zh-v1.5', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-large-en', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-large-en-v1.5', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-large-zh', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-large-zh-noinstruct', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-large-zh-v1.5', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-m3', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential,\n              XinferenceEmbedding),\n    ModelInfo('bge-small-en-v1.5', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-small-zh', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('bge-small-zh-v1.5', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('e5-large-v2', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('gte-base', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential,\n              XinferenceEmbedding),\n    ModelInfo('gte-large', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential,\n              XinferenceEmbedding),\n    ModelInfo('jina-embeddings-v2-base-en', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('jina-embeddings-v2-base-zh', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('jina-embeddings-v2-small-en', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('m3e-base', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential,\n              XinferenceEmbedding),\n    ModelInfo('m3e-large', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential,\n              XinferenceEmbedding),\n    ModelInfo('m3e-small', '', ModelTypeConst.EMBEDDING, xinference_embedding_model_credential,\n              XinferenceEmbedding),\n    ModelInfo('multilingual-e5-large', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('text2vec-base-chinese', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('text2vec-base-chinese-paraphrase', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('text2vec-base-chinese-sentence', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('text2vec-base-multilingual', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n    ModelInfo('text2vec-large-chinese', '', ModelTypeConst.EMBEDDING,\n              xinference_embedding_model_credential, XinferenceEmbedding),\n]\nrerank_list = [ModelInfo('bce-reranker-base_v1',\n                         '',\n                         ModelTypeConst.RERANKER, XInferenceRerankerModelCredential(), XInferenceReranker)]\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_model_info_list(voice_model_info)\n    .append_default_model_info(voice_model_info[0])\n    .append_default_model_info(voice_model_info[1])\n    .append_default_model_info(ModelInfo('phi3',\n                                         '',\n                                         ModelTypeConst.LLM, xinference_llm_model_credential,\n                                         XinferenceChatModel))\n    .append_model_info_list(embedding_model_info)\n    .append_default_model_info(ModelInfo('',\n                                         '',\n                                         ModelTypeConst.EMBEDDING,\n                                         xinference_embedding_model_credential, XinferenceEmbedding))\n    .append_model_info_list(rerank_list)\n    .append_model_info_list(image_model_info)\n    .append_default_model_info(image_model_info[0])\n    .append_model_info_list(tti_model_info)\n    .append_default_model_info(tti_model_info[0])\n    .append_default_model_info(rerank_list[0])\n    .build()\n)\n\n\ndef get_base_url(url: str):\n    parse = urlparse(url)\n    result_url = ParseResult(scheme=parse.scheme, netloc=parse.netloc, path=parse.path, params='',\n                             query='',\n                             fragment='').geturl()\n    return result_url[:-1] if result_url.endswith(\"/\") else result_url\n\n\nclass XinferenceModelProvider(IModelProvider):\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_xinference_provider', name='Xorbits Inference', icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'xinference_model_provider', 'icon',\n                         'xinference_icon_svg')))\n\n    @staticmethod\n    def get_base_model_list(api_base, api_key, model_type):\n        base_url = get_base_url(api_base)\n        base_url = base_url if base_url.endswith('/v1') else (base_url + '/v1')\n        headers = {}\n        if api_key:\n            headers['Authorization'] = f\"Bearer {api_key}\"\n        r = requests.request(method=\"GET\", url=f\"{base_url}/models\", headers=headers, timeout=5)\n        r.raise_for_status()\n        model_list = r.json().get('data')\n        return [model for model in model_list if model.get('model_type') == model_type]\n\n    @staticmethod\n    def get_model_info_by_name(model_list, model_name):\n        if model_list is None:\n            return []\n        return [model for model in model_list if model.get('model_name') == model_name or model.get('id') == model_name]\n"
  },
  {
    "path": "apps/models_provider/impl/zhipu_model_provider/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/zhipu_model_provider/credential/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/zhipu_model_provider/credential/image.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom common.utils.logger import maxkb_logger\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\n\n\nclass ZhiPuImageModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.95,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=1024,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass ZhiPuImageModelCredential(BaseForm, BaseModelCredential):\n    api_base = forms.TextInputField('API URL', required=True, default_value='https://open.bigmodel.cn/api/paas/v4')\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key', 'api_base']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.stream([HumanMessage(content=[{\"type\": \"text\", \"text\": gettext('Hello')}])])\n            for chunk in res:\n                maxkb_logger.info(chunk)\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return ZhiPuImageModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/zhipu_model_provider/credential/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： llm.py\n    @date：2024/7/12 10:46\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\nfrom langchain_core.messages import HumanMessage\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass ZhiPuLLMModelParams(BaseForm):\n    temperature = forms.SliderField(TooltipLabel(_('Temperature'),\n                                                 _('Higher values make the output more random, while lower values make it more focused and deterministic')),\n                                    required=True, default_value=0.95,\n                                    _min=0.1,\n                                    _max=1.0,\n                                    _step=0.01,\n                                    precision=2)\n\n    max_tokens = forms.SliderField(\n        TooltipLabel(_('Output the maximum Tokens'),\n                     _('Specify the maximum number of tokens that the model can generate')),\n        required=True, default_value=1024,\n        _min=1,\n        _max=100000,\n        _step=1,\n        precision=0)\n\n\nclass ZhiPuLLMModelCredential(BaseForm, BaseModelCredential):\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n        for key in ['api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            model.invoke([HumanMessage(content=gettext('Hello'))])\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    api_base = forms.TextInputField('API URL', required=True, default_value='https://open.bigmodel.cn/api/paas/v4')\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def get_model_params_setting_form(self, model_name):\n        return ZhiPuLLMModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/zhipu_model_provider/credential/tti.py",
    "content": "# coding=utf-8\nfrom typing import Dict\n\nfrom django.utils.translation import gettext_lazy as _, gettext\n\nfrom common import forms\nfrom common.exception.app_exception import AppApiException\nfrom common.forms import BaseForm, TooltipLabel\nfrom models_provider.base_model_provider import BaseModelCredential, ValidCode\nfrom common.utils.logger import maxkb_logger\n\nclass ZhiPuTTIModelParams(BaseForm):\n    size = forms.SingleSelect(\n        TooltipLabel(_('Image size'),\n                     _('Image size, only cogview-3-plus supports this parameter. Optional range: [1024x1024,768x1344,864x1152,1344x768,1152x864,1440x720,720x1440], the default is 1024x1024.')),\n        required=True,\n        default_value='1024x1024',\n        option_list=[\n            {'value': '1024x1024', 'label': '1024x1024'},\n            {'value': '768x1344', 'label': '768x1344'},\n            {'value': '864x1152', 'label': '864x1152'},\n            {'value': '1344x768', 'label': '1344x768'},\n            {'value': '1152x864', 'label': '1152x864'},\n            {'value': '1440x720', 'label': '1440x720'},\n            {'value': '720x1440', 'label': '720x1440'},\n        ],\n        text_field='label',\n        value_field='value')\n\n\nclass ZhiPuTextToImageModelCredential(BaseForm, BaseModelCredential):\n    api_key = forms.PasswordInputField('API Key', required=True)\n\n    def is_valid(self, model_type: str, model_name, model_credential: Dict[str, object], model_params, provider,\n                 raise_exception=False):\n        model_type_list = provider.get_model_type_list()\n        if not any(list(filter(lambda mt: mt.get('value') == model_type, model_type_list))):\n            raise AppApiException(ValidCode.valid_error.value,\n                                  gettext('{model_type} Model type is not supported').format(model_type=model_type))\n\n        for key in ['api_key']:\n            if key not in model_credential:\n                if raise_exception:\n                    raise AppApiException(ValidCode.valid_error.value, gettext('{key}  is required').format(key=key))\n                else:\n                    return False\n        try:\n            model = provider.get_model(model_type, model_name, model_credential, **model_params)\n            res = model.check_auth()\n        except Exception as e:\n            maxkb_logger.error(f'Exception: {e}', exc_info=True)\n            if isinstance(e, AppApiException):\n                raise e\n            if raise_exception:\n                raise AppApiException(ValidCode.valid_error.value,\n                                      gettext(\n                                          'Verification failed, please check whether the parameters are correct: {error}').format(\n                                          error=str(e)))\n            else:\n                return False\n        return True\n\n    def encryption_dict(self, model: Dict[str, object]):\n        return {**model, 'api_key': super().encryption(model.get('api_key', ''))}\n\n    def get_model_params_setting_form(self, model_name):\n        return ZhiPuTTIModelParams()\n"
  },
  {
    "path": "apps/models_provider/impl/zhipu_model_provider/model/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/impl/zhipu_model_provider/model/image.py",
    "content": "from typing import Dict\n\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\nclass ZhiPuImage(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        return ZhiPuImage(\n            model_name=model_name,\n            openai_api_key=model_credential.get('api_key'),\n            openai_api_base=model_credential.get('api_base') or 'https://open.bigmodel.cn/api/paas/v4',\n            # stream_options={\"include_usage\": True},\n            streaming=True,\n            stream_usage=True,\n            extra_body=optional_params\n        )\n"
  },
  {
    "path": "apps/models_provider/impl/zhipu_model_provider/model/llm.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： llm.py\n    @date：2024/4/28 11:42\n    @desc:\n\"\"\"\n\nfrom typing import Dict, List\n\nfrom langchain_core.messages import BaseMessage, get_buffer_string\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_chat_open_ai import BaseChatOpenAI\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass ZhipuChatModel(MaxKBBaseModel, BaseChatOpenAI):\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = MaxKBBaseModel.filter_optional_params(model_kwargs)\n        zhipuai_chat = ZhipuChatModel(\n            api_key=model_credential.get('api_key'),\n            model=model_name,\n            base_url=model_credential.get('api_base') or 'https://open.bigmodel.cn/api/paas/v4',\n            extra_body=optional_params,\n            streaming=model_kwargs.get('streaming', False),\n            custom_get_token_ids=custom_get_token_ids\n        )\n        return zhipuai_chat\n\n    def get_num_tokens_from_messages(self, messages: List[BaseMessage]) -> int:\n        try:\n            return super().get_num_tokens_from_messages(messages)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return sum([len(tokenizer.encode(get_buffer_string([m]))) for m in messages])\n\n    def get_num_tokens(self, text: str) -> int:\n        try:\n            return super().get_num_tokens(text)\n        except Exception as e:\n            tokenizer = TokenizerManage.get_tokenizer()\n            return len(tokenizer.encode(text))\n"
  },
  {
    "path": "apps/models_provider/impl/zhipu_model_provider/model/tti.py",
    "content": "from typing import Dict\n\nfrom django.utils.translation import gettext\nfrom langchain_core.messages import HumanMessage\nfrom langchain_openai import ChatOpenAI\nfrom zhipuai import ZhipuAI\n\nfrom common.config.tokenizer_manage_config import TokenizerManage\nfrom models_provider.base_model_provider import MaxKBBaseModel\nfrom models_provider.impl.base_tti import BaseTextToImage\n\n\ndef custom_get_token_ids(text: str):\n    tokenizer = TokenizerManage.get_tokenizer()\n    return tokenizer.encode(text)\n\n\nclass ZhiPuTextToImage(MaxKBBaseModel, BaseTextToImage):\n    api_key: str\n    model: str\n    params: dict\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n        self.api_key = kwargs.get('api_key')\n        self.model = kwargs.get('model')\n        self.params = kwargs.get('params')\n\n    @staticmethod\n    def is_cache_model():\n        return False\n\n    @staticmethod\n    def new_instance(model_type, model_name, model_credential: Dict[str, object], **model_kwargs):\n        optional_params = {'params': {'size': '1024x1024'}}\n        for key, value in model_kwargs.items():\n            if key not in ['model_id', 'use_local', 'streaming']:\n                optional_params['params'][key] = value\n        return ZhiPuTextToImage(\n            model=model_name,\n            api_key=model_credential.get('api_key'),\n            **optional_params,\n        )\n\n    def is_cache_model(self):\n        return False\n\n    def check_auth(self):\n        chat = ChatOpenAI(\n            api_key=self.api_key,\n            base_url='https://open.bigmodel.cn/api/paas/v4',\n            model=self.model,\n        )\n        chat.invoke([HumanMessage([{\"type\": \"text\", \"text\": gettext('Hello')}])])\n\n        # self.generate_image('生成一个小猫图片')\n\n    def generate_image(self, prompt: str, negative_prompt: str = None):\n        # chat = ChatZhipuAI(\n        #     zhipuai_api_key=self.api_key,\n        #     model_name=self.model,\n        # )\n        chat = ZhipuAI(api_key=self.api_key)\n        response = chat.images.generations(\n            model=self.model,  # 填写需要调用的模型编码\n            prompt=prompt,  # 填写需要生成图片的文本\n            **self.params  # 填写额外参数\n        )\n        file_urls = []\n        try:\n            for content in response.data:\n                url = content.url\n                file_urls.append(url)\n\n            return file_urls\n        except Exception as e:\n            raise e\n"
  },
  {
    "path": "apps/models_provider/impl/zhipu_model_provider/zhipu_model_provider.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： zhipu_model_provider.py\n    @date：2024/04/19 13:5\n    @desc:\n\"\"\"\nimport os\n\nfrom common.utils.common import get_file_content\nfrom models_provider.base_model_provider import ModelProvideInfo, ModelTypeConst, ModelInfo, IModelProvider, \\\n    ModelInfoManage\nfrom models_provider.impl.zhipu_model_provider.credential.image import ZhiPuImageModelCredential\nfrom models_provider.impl.zhipu_model_provider.credential.llm import ZhiPuLLMModelCredential\nfrom models_provider.impl.zhipu_model_provider.credential.tti import ZhiPuTextToImageModelCredential\nfrom models_provider.impl.zhipu_model_provider.model.image import ZhiPuImage\nfrom models_provider.impl.zhipu_model_provider.model.llm import ZhipuChatModel\nfrom models_provider.impl.zhipu_model_provider.model.tti import ZhiPuTextToImage\nfrom maxkb.conf import PROJECT_DIR\nfrom django.utils.translation import gettext as _\n\nzhipu_model_credential = ZhiPuLLMModelCredential()\nzhipu_image_model_credential = ZhiPuImageModelCredential()\nzhipu_tti_model_credential = ZhiPuTextToImageModelCredential()\n\nmodel_info_list = [\n    ModelInfo('glm-4', '', ModelTypeConst.LLM, zhipu_model_credential, ZhipuChatModel),\n    ModelInfo('glm-4v', '', ModelTypeConst.LLM, zhipu_model_credential, ZhipuChatModel),\n    ModelInfo('glm-3-turbo', '', ModelTypeConst.LLM, zhipu_model_credential, ZhipuChatModel)\n]\n\nmodel_info_image_list = [\n    ModelInfo('glm-4v-plus', _('Have strong multi-modal understanding capabilities. Able to understand up to five images simultaneously and supports video content understanding'),\n              ModelTypeConst.IMAGE, zhipu_image_model_credential,\n              ZhiPuImage),\n    ModelInfo('glm-4v', _('Focus on single picture understanding. Suitable for scenarios requiring efficient image analysis'),\n              ModelTypeConst.IMAGE, zhipu_image_model_credential,\n              ZhiPuImage),\n    ModelInfo('glm-4v-flash', _('Focus on single picture understanding. Suitable for scenarios requiring efficient image analysis (free)'),\n              ModelTypeConst.IMAGE, zhipu_image_model_credential,\n              ZhiPuImage),\n]\n\nmodel_info_tti_list = [\n    ModelInfo('cogview-3', _('Quickly and accurately generate images based on user text descriptions. Resolution supports 1024x1024'),\n              ModelTypeConst.TTI, zhipu_tti_model_credential,\n              ZhiPuTextToImage),\n    ModelInfo('cogview-3-plus', _('Generate high-quality images based on user text descriptions, supporting multiple image sizes'),\n              ModelTypeConst.TTI, zhipu_tti_model_credential,\n              ZhiPuTextToImage),\n    ModelInfo('cogview-3-flash', _('Generate high-quality images based on user text descriptions, supporting multiple image sizes (free)'),\n              ModelTypeConst.TTI, zhipu_tti_model_credential,\n              ZhiPuTextToImage),\n]\n\nmodel_info_manage = (\n    ModelInfoManage.builder()\n    .append_model_info_list(model_info_list)\n    .append_default_model_info(ModelInfo('glm-4', '', ModelTypeConst.LLM, zhipu_model_credential, ZhipuChatModel))\n    .append_model_info_list(model_info_image_list)\n    .append_default_model_info(model_info_image_list[0])\n    .append_model_info_list(model_info_tti_list)\n    .append_default_model_info(model_info_tti_list[0])\n    .build()\n)\n\n\nclass ZhiPuModelProvider(IModelProvider):\n\n    def get_model_info_manage(self):\n        return model_info_manage\n\n    def get_model_provide_info(self):\n        return ModelProvideInfo(provider='model_zhipu_provider', name=_('zhipu AI'), icon=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", 'models_provider', 'impl', 'zhipu_model_provider', 'icon',\n                         'zhipuai_icon_svg')))\n"
  },
  {
    "path": "apps/models_provider/migrations/0001_initial.py",
    "content": "# Generated by Django 5.2.3 on 2025-06-23 02:14\nimport json\n\nimport django.db.models.deletion\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\nfrom common.utils.rsa_util import rsa_long_encrypt\nfrom maxkb.const import CONFIG\nfrom models_provider.models import Status\n\ndefault_embedding_model_id = '42f63a3d-427e-11ef-b3ec-a8a1595801ab'\n\n\ndef save_default_embedding_model(apps, schema_editor):\n    ModelModel = apps.get_model('models_provider', 'Model')\n    cache_folder = CONFIG.get('EMBEDDING_MODEL_PATH')\n    model_name = CONFIG.get('EMBEDDING_MODEL_NAME')\n    credential = {'cache_folder': cache_folder}\n    model_credential_str = json.dumps(credential)\n    model = ModelModel(id=default_embedding_model_id, name='maxkb-embedding', status=Status.SUCCESS,\n                       model_type=\"EMBEDDING\", model_name=model_name, user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab',\n                       provider='model_local_provider',\n                       credential=rsa_long_encrypt(model_credential_str), meta={},\n                       workspace_id='default')\n    model.save()\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('users', '0001_initial'),\n        ('system_manage', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Model',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('name', models.CharField(db_index=True, max_length=128, verbose_name='名称')),\n                ('status', models.CharField(choices=[('SUCCESS', '成功'), ('ERROR', '失败'), ('DOWNLOAD', '下载中'), ('PAUSE_DOWNLOAD', '暂停下载')], db_index=True, default='SUCCESS', max_length=20, verbose_name='设置类型')),\n                ('model_type', models.CharField(db_index=True, max_length=128, verbose_name='模型类型')),\n                ('model_name', models.CharField(db_index=True, max_length=128, verbose_name='模型名称')),\n                ('provider', models.CharField(db_index=True, max_length=128, verbose_name='供应商')),\n                ('credential', models.CharField(max_length=102400, verbose_name='模型认证信息')),\n                ('meta', models.JSONField(default=dict, verbose_name='模型元数据,用于存储下载,或者错误信息')),\n                ('model_params_form', models.JSONField(default=list, verbose_name='模型参数配置')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),\n            ],\n            options={\n                'db_table': 'model',\n                'unique_together': {('name', 'workspace_id')},\n            },\n        ),\n        migrations.RunPython(save_default_embedding_model),\n    ]\n"
  },
  {
    "path": "apps/models_provider/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "apps/models_provider/models/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： __init__.py\n    @date：2023/9/25 15:04\n    @desc:\n\"\"\"\n\nfrom .model_management import *\n"
  },
  {
    "path": "apps/models_provider/models/model_management.py",
    "content": "# coding=utf-8\nimport uuid_utils.compat as uuid\n\nfrom django.db import models\n\nfrom common.mixins.app_model_mixin import AppModelMixin\nfrom users.models import User\n\n\nclass Status(models.TextChoices):\n    \"\"\"系统设置类型\"\"\"\n    SUCCESS = \"SUCCESS\", '成功'\n\n    ERROR = \"ERROR\", \"失败\"\n\n    DOWNLOAD = \"DOWNLOAD\", '下载中'\n\n    PAUSE_DOWNLOAD = \"PAUSE_DOWNLOAD\", '暂停下载'\n\n\nclass Model(AppModelMixin):\n    \"\"\"\n    模型数据\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n\n    name = models.CharField(max_length=128, verbose_name=\"名称\", db_index=True)\n\n    status = models.CharField(max_length=20, verbose_name='设置类型', choices=Status.choices,\n                              default=Status.SUCCESS, db_index=True)\n\n    model_type = models.CharField(max_length=128, verbose_name=\"模型类型\", db_index=True)\n\n    model_name = models.CharField(max_length=128, verbose_name=\"模型名称\", db_index=True)\n\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n\n    provider = models.CharField(max_length=128, verbose_name='供应商', db_index=True)\n\n    credential = models.CharField(max_length=102400, verbose_name=\"模型认证信息\")\n\n    meta = models.JSONField(verbose_name=\"模型元数据,用于存储下载,或者错误信息\", default=dict)\n\n    model_params_form = models.JSONField(verbose_name=\"模型参数配置\", default=list)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n\n    class Meta:\n        db_table = \"model\"\n        unique_together = ['name', 'workspace_id']\n"
  },
  {
    "path": "apps/models_provider/serializers/__init__.py",
    "content": "# coding=utf-8\n"
  },
  {
    "path": "apps/models_provider/serializers/model_apply_serializers.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： model_apply_serializers.py\n    @date：2024/8/20 20:39\n    @desc:\n\"\"\"\nfrom django.db import connection\nfrom django.db.models import QuerySet\nfrom langchain_core.documents import Document\nfrom rest_framework import serializers\n\nfrom common.config.embedding_config import ModelManage\nfrom django.utils.translation import gettext_lazy as _\n\nfrom models_provider.models import Model\nfrom models_provider.tools import get_model\n\n\ndef get_embedding_model(model_id):\n    model = QuerySet(Model).filter(id=model_id).first()\n    # 手动关闭数据库连接\n    connection.close()\n    embedding_model = ModelManage.get_model(model_id,\n                                            lambda _id: get_model(model, use_local=True))\n    return embedding_model\n\n\nclass EmbedDocuments(serializers.Serializer):\n    texts = serializers.ListField(required=True, child=serializers.CharField(required=True,\n                                                                             label=_('vector text')),\n                                  label=_('vector text list')),\n\n\nclass EmbedQuery(serializers.Serializer):\n    text = serializers.CharField(required=True, label=_('vector text'))\n\n\nclass CompressDocument(serializers.Serializer):\n    page_content = serializers.CharField(required=True, label=_('text'))\n    metadata = serializers.DictField(required=False, label=_('metadata'))\n\n\nclass CompressDocuments(serializers.Serializer):\n    documents = CompressDocument(required=True, many=True)\n    query = serializers.CharField(required=True, label=_('query'))\n\n\nclass ModelApplySerializers(serializers.Serializer):\n    model_id = serializers.UUIDField(required=True, label=_('model id'))\n\n    def embed_documents(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            EmbedDocuments(data=instance).is_valid(raise_exception=True)\n\n        model = get_embedding_model(self.data.get('model_id'))\n        return model.embed_documents(instance.getlist('texts'))\n\n    def embed_query(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            EmbedQuery(data=instance).is_valid(raise_exception=True)\n\n        model = get_embedding_model(self.data.get('model_id'))\n        return model.embed_query(instance.get('text'))\n\n    def compress_documents(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            CompressDocuments(data=instance).is_valid(raise_exception=True)\n        model = get_embedding_model(self.data.get('model_id'))\n        return [{'page_content': d.page_content, 'metadata': d.metadata} for d in model.compress_documents(\n            [Document(page_content=document.get('page_content'), metadata=document.get('metadata')) for document in\n             instance.get('documents')], instance.get('query'))]\n"
  },
  {
    "path": "apps/models_provider/serializers/model_serializer.py",
    "content": "# -*- coding: utf-8 -*-\nimport json\nimport os\nimport threading\nimport time\nfrom typing import Dict\n\nimport uuid_utils.compat as uuid\nfrom django.core.cache import cache\nfrom django.db import transaction\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom common.config.embedding_config import ModelManage\nfrom common.constants.cache_version import Cache_Version\nfrom common.constants.permission_constants import ResourcePermission, ResourceAuthType\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.db.search import native_search\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import get_file_content\nfrom common.utils.rsa_util import rsa_long_encrypt, rsa_long_decrypt\nfrom maxkb.conf import PROJECT_DIR\nfrom models_provider.base_model_provider import ValidCode, DownModelChunkStatus\nfrom models_provider.constants.model_provider_constants import ModelProvideConstants\nfrom models_provider.models import Model, Status\nfrom models_provider.tools import get_model_credential\nfrom system_manage.models import WorkspaceUserResourcePermission, AuthTargetType\nfrom system_manage.models.resource_mapping import ResourceMapping\nfrom system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer\nfrom system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer\nfrom users.serializers.user import is_workspace_manage\n\n\ndef get_default_model_params_setting(provider, model_type, model_name):\n    credential = get_model_credential(provider, model_type, model_name)\n    setting_form = credential.get_model_params_setting_form(model_name)\n    if setting_form is not None:\n        return setting_form.to_form_list()\n    return []\n\n\nclass ModelModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Model\n        fields = [\n            'id', 'name', 'status', 'model_type', 'model_name',\n            'user', 'provider', 'credential', 'meta',\n            'model_params_form', 'workspace_id', 'create_time', 'update_time'\n        ]\n\n\nclass ModelCreateRequest(serializers.Serializer):\n    name = serializers.CharField(required=True, max_length=64, label=_(\"model name\"))\n    provider = serializers.CharField(required=True, label=_(\"provider\"))\n    model_type = serializers.CharField(required=True, label=_(\"model type\"))\n    model_name = serializers.CharField(required=True, label=_(\"base model\"))\n    model_params_form = serializers.ListField(required=False, default=list, label=_(\"parameter configuration\"))\n    credential = serializers.DictField(required=True, label=_(\"certification information\"))\n\n\nclass ModelPullManage:\n    @staticmethod\n    def pull(model: Model, credential: Dict):\n        try:\n            response = ModelProvideConstants[model.provider].value.down_model(\n                model.model_type, model.model_name, credential\n            )\n            down_model_chunk = {}\n            last_update_time = time.time()\n\n            for chunk in response:\n                down_model_chunk[chunk.digest] = chunk.to_dict()\n                if time.time() - last_update_time > 5:\n                    current_model = QuerySet(Model).filter(id=model.id).first()\n                    if current_model and current_model.status == Status.PAUSE_DOWNLOAD:\n                        return\n                    QuerySet(Model).filter(id=model.id).update(\n                        meta={\"down_model_chunk\": list(down_model_chunk.values())}\n                    )\n                    last_update_time = time.time()\n\n            status = Status.ERROR\n            message = \"\"\n            for chunk in down_model_chunk.values():\n                if chunk.get('status') == DownModelChunkStatus.success.value:\n                    status = Status.SUCCESS\n                elif chunk.get('status') == DownModelChunkStatus.error.value:\n                    message = chunk.get(\"digest\")\n\n            QuerySet(Model).filter(id=model.id).update(\n                meta={\"down_model_chunk\": [], \"message\": message},\n                status=status\n            )\n        except Exception as e:\n            QuerySet(Model).filter(id=model.id).update(\n                meta={\"down_model_chunk\": [], \"message\": str(e)},\n                status=Status.ERROR\n            )\n\n\nclass ModelSerializer(serializers.Serializer):\n    @staticmethod\n    def model_to_dict(model: Model):\n        credential = json.loads(rsa_long_decrypt(model.credential))\n        return {\n            'id': str(model.id),\n            'provider': model.provider,\n            'name': model.name,\n            'model_type': model.model_type,\n            'model_name': model.model_name,\n            'status': model.status,\n            'meta': model.meta,\n            'credential': ModelProvideConstants[model.provider].value.get_model_credential(\n                model.model_type, model.model_name\n            ).encryption_dict(credential),\n            'workspace_id': model.workspace_id,\n            'nick_name': model.user.nick_name if model.user else '',\n            'username': model.user.username if model.user else ''\n        }\n\n    class Operate(serializers.Serializer):\n        id = serializers.UUIDField(required=True, label=_(\"model id\"))\n        user_id = serializers.UUIDField(required=False, label=_(\"user id\"))\n        workspace_id = serializers.CharField(required=False, label=_(\"workspace id\"))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get(\"workspace_id\")\n            model_query = QuerySet(Model).filter(id=self.data.get(\"id\"))\n            if workspace_id is not None:\n                model_query = model_query.filter(workspace_id=workspace_id)\n            model = model_query.first()\n            if model is None:\n                raise AppApiException(500, _('Model does not exist'))\n            if model.workspace_id == 'None':\n                raise AppApiException(500, _('Shared models cannot be deleted or modified'))\n\n        def one(self, with_valid=False):\n            if with_valid:\n                super().is_valid(raise_exception=True)\n            model = QuerySet(Model).get(\n                id=self.data.get('id'), workspace_id=self.data.get('workspace_id', 'None')\n            )\n            return ModelSerializer.model_to_dict(model)\n\n        def one_meta(self, with_valid=False):\n            model = None\n            if with_valid:\n                super().is_valid(raise_exception=True)\n                model = QuerySet(Model).filter(id=self.data.get(\"id\"),\n                                               workspace_id=self.data.get('workspace_id', 'None')).first()\n                if model is None:\n                    raise AppApiException(500, _('Model does not exist'))\n            return {'id': str(model.id), 'provider': model.provider, 'name': model.name, 'model_type': model.model_type,\n                    'model_name': model.model_name,\n                    'status': model.status,\n                    'meta': model.meta,\n                    'workspace_id': model.workspace_id,\n                    }\n\n        def pause_download(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            QuerySet(Model).filter(id=self.data.get('id')).update(status=Status.PAUSE_DOWNLOAD)\n            return True\n\n        @transaction.atomic\n        def delete(self, with_valid=True):\n            if with_valid:\n                super().is_valid(raise_exception=True)\n            model_id = self.data.get('id')\n            model = Model.objects.filter(id=model_id).first()\n            if model is None:\n                return True\n            QuerySet(WorkspaceUserResourcePermission).filter(target=model_id).delete()\n            # TODO : 这里可以添加模型删除的逻辑,需要注意删除模型时的权限和关联关系\n            # if model.model_type == 'LLM':\n            #     application_count = Application.objects.filter(model_id=model_id).count()\n            #     if application_count > 0:\n            #         raise AppApiException(500, f\"该模型关联了{application_count} 个应用，无法删除该模型。\")\n            # elif model.model_type == 'EMBEDDING':\n            #     dataset_count = DataSet.objects.filter(embedding_model_id=model_id).count()\n            #     if dataset_count > 0:\n            #         raise AppApiException(500, f\"该模型关联了{dataset_count} 个知识库，无法删除该模型。\")\n            # elif model.model_type == 'TTS':\n            #     dataset_count = Application.objects.filter(tts_model_id=model_id).count()\n            #     if dataset_count > 0:\n            #         raise AppApiException(500, f\"该模型关联了{dataset_count} 个应用，无法删除该模型。\")\n            # elif model.model_type == 'STT':\n            #     dataset_count = Application.objects.filter(stt_model_id=model_id).count()\n            #     if dataset_count > 0:\n            #         raise AppApiException(500, f\"该模型关联了{dataset_count} 个应用，无法删除该模型。\")\n            model.delete()\n            ResourceMapping.objects.filter(target_id=model_id).delete()\n            return True\n\n        def edit(self, instance: Dict, user_id: str, with_valid=True):\n            if with_valid:\n                super().is_valid(raise_exception=True)\n            model = QuerySet(Model).filter(id=self.data.get('id')).first()\n\n            credential, model_credential, provider_handler = ModelSerializer.Edit(\n                data={**instance}).is_valid(\n                model=model)\n            try:\n                model.status = Status.SUCCESS\n                default_params = {item['field']: item['default_value'] for item in model.model_params_form}\n                # 校验模型认证数据\n                provider_handler.is_valid_credential(model.model_type,\n                                                     instance.get(\"model_name\"),\n                                                     credential,\n                                                     default_params,\n                                                     raise_exception=True)\n\n            except AppApiException as e:\n                if e.code == ValidCode.model_not_fount:\n                    model.status = Status.DOWNLOAD\n                else:\n                    raise e\n            update_keys = ['credential', 'name', 'model_type', 'model_name']\n            for update_key in update_keys:\n                if update_key in instance and instance.get(update_key) is not None:\n                    if update_key == 'credential':\n                        model_credential_str = json.dumps(credential)\n                        model.__setattr__(update_key, rsa_long_encrypt(model_credential_str))\n                    else:\n                        model.__setattr__(update_key, instance.get(update_key))\n\n            ModelManage.delete_key(str(model.id))\n            model.save()\n            if model.status == Status.DOWNLOAD:\n                thread = threading.Thread(target=ModelPullManage.pull, args=(model, credential))\n                thread.start()\n            return self.one(with_valid=False)\n\n    class Edit(serializers.Serializer):\n        user_id = serializers.CharField(required=False, label=(_('user id')))\n\n        name = serializers.CharField(required=False, max_length=64,\n                                     label=(_(\"model name\")))\n\n        model_type = serializers.CharField(required=False, label=(_(\"model type\")))\n\n        model_name = serializers.CharField(required=False, label=(_(\"base model\")))\n\n        credential = serializers.DictField(required=False,\n                                           label=(_(\"certification information\")))\n        workspace_id = serializers.CharField(required=False, label=(_(\"workspace id\")))\n\n        def is_valid(self, model=None, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            filter_params = {'workspace_id': model.workspace_id}\n            if 'name' in self.data and self.data.get('name') is not None:\n                filter_params['name'] = self.data.get('name')\n                if QuerySet(Model).exclude(id=model.id).filter(**filter_params).exists():\n                    raise AppApiException(500, _('base model【{model_name}】already exists').format(\n                        model_name=self.data.get(\"name\")))\n\n            ModelSerializer.model_to_dict(model)\n\n            provider = model.provider\n            model_type = self.data.get('model_type')\n            model_name = self.data.get(\n                'model_name')\n            credential = self.data.get('credential')\n            provider_handler = ModelProvideConstants[provider].value\n            model_credential = ModelProvideConstants[provider].value.get_model_credential(model_type,\n                                                                                          model_name)\n            source_model_credential = json.loads(rsa_long_decrypt(model.credential))\n            source_encryption_model_credential = model_credential.encryption_dict(source_model_credential)\n            if credential is not None:\n                for k in source_encryption_model_credential.keys():\n                    if k in credential and credential[k] == source_encryption_model_credential[k]:\n                        credential[k] = source_model_credential[k]\n            return credential, model_credential, provider_handler\n\n    class Create(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        name = serializers.CharField(required=True, max_length=64, label=_(\"model name\"))\n        provider = serializers.CharField(required=True, label=_(\"provider\"))\n        model_type = serializers.CharField(required=True, label=_(\"model type\"))\n        model_name = serializers.CharField(required=True, label=_(\"base model\"))\n        model_params_form = serializers.ListField(required=False, default=list, label=_(\"parameter configuration\"))\n        credential = serializers.DictField(required=True, label=_(\"certification information\"))\n        workspace_id = serializers.CharField(required=False, label=_(\"workspace id\"), max_length=128)\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            if QuerySet(Model).filter(\n                    name=self.data.get('name'),\n                    workspace_id=self.data.get('workspace_id', 'None')\n            ).exists():\n                raise AppApiException(\n                    500,\n                    _('base model【{model_name}】already exists').format(model_name=self.data.get(\"name\"))\n                )\n            default_params = {item['field']: item['default_value'] for item in self.data.get('model_params_form')}\n            ModelProvideConstants[self.data.get('provider')].value.is_valid_credential(\n                self.data.get('model_type'),\n                self.data.get('model_name'),\n                self.data.get('credential'),\n                default_params,\n                raise_exception=True\n            )\n\n        def insert(self, workspace_id, with_valid=True):\n            status = Status.SUCCESS\n            if with_valid:\n                try:\n                    self.is_valid(raise_exception=True)\n                except AppApiException as e:\n                    if e.code == ValidCode.model_not_fount:\n                        status = Status.DOWNLOAD\n                    else:\n                        raise e\n\n            credential = self.data.get('credential')\n            model_data = {\n                'id': uuid.uuid7(),\n                'status': status,\n                'user_id': self.data.get('user_id'),\n                'name': self.data.get('name'),\n                'credential': rsa_long_encrypt(json.dumps(credential)),\n                'provider': self.data.get('provider'),\n                'model_type': self.data.get('model_type'),\n                'model_name': self.data.get('model_name'),\n                'model_params_form': self.data.get('model_params_form'),\n                'workspace_id': workspace_id\n            }\n            model = Model(**model_data)\n            try:\n                model.save()\n                if workspace_id != 'None':\n                    UserResourcePermissionSerializer(data={\n                        'workspace_id': workspace_id,\n                        'user_id': self.data.get('user_id'),\n                        'auth_target_type': AuthTargetType.MODEL.value\n                    }).auth_resource(str(model.id))\n            except Exception as save_error:\n                # 可添加日志记录\n                raise AppApiException(500, _(\"Model saving failed\")) from save_error\n\n            if status == Status.DOWNLOAD:\n                thread = threading.Thread(target=ModelPullManage.pull, args=(model, credential))\n                thread.start()\n\n            return ModelModelSerializer(model).data\n\n    class Query(serializers.Serializer):\n        user_id = serializers.CharField(required=True, label=_(\"User ID\"))\n        name = serializers.CharField(required=False, max_length=64, label=_('model name'))\n        model_type = serializers.CharField(required=False, label=_('model type'))\n        model_name = serializers.CharField(required=False, label=_('base model'))\n        provider = serializers.CharField(required=False, label=_('provider'))\n        create_user = serializers.CharField(required=False, label=_('create user'))\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'))\n\n        @staticmethod\n        def is_x_pack_ee():\n            workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n            role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n            return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None\n\n        def list(self, workspace_id, with_valid):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            user_id = self.data.get(\"user_id\")\n            workspace_manage = is_workspace_manage(user_id, workspace_id)\n            query_params = self._build_query_params(workspace_id, workspace_manage, user_id)\n            is_x_pack_ee = self.is_x_pack_ee()\n            result = native_search(query_params,\n                                   select_string=get_file_content(\n                                       os.path.join(PROJECT_DIR, \"apps\", \"models_provider\", 'sql',\n                                                    'list_model.sql' if workspace_manage else (\n                                                        'list_model_user_ee.sql' if is_x_pack_ee else 'list_model_user.sql')\n                                                    )))\n            return ResourceMappingSerializer().get_resource_count(result)\n\n        def share_list(self, workspace_id, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            user_id = self.data.get(\"user_id\")\n            query_params = self._build_query_params(workspace_id, False, user_id)\n            result = [\n                self._build_model_data(\n                    model\n                ) for model in query_params.get('model_query_set')\n            ]\n            return ResourceMappingSerializer().get_resource_count(result)\n\n        def model_list(self, workspace_id, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            user_id = self.data.get(\"user_id\")\n            workspace_manage = is_workspace_manage(user_id, workspace_id)\n            queryset = self._build_query_params(workspace_id, workspace_manage, user_id)\n            get_authorized_model = DatabaseModelManage.get_model(\"get_authorized_model\")\n\n            shared_queryset = QuerySet(Model).none()\n            if get_authorized_model is not None:\n                shared_queryset = self._build_query_params('None', False, user_id)['model_query_set']\n                shared_queryset = get_authorized_model(shared_queryset, workspace_id)\n\n            # 构建共享模型和普通模型列表\n            shared_model = [self._build_model_data(model) for model in shared_queryset]\n\n            is_x_pack_ee = self.is_x_pack_ee()\n            normal_model = native_search(\n                queryset,\n                select_string=get_file_content(\n                    os.path.join(\n                        PROJECT_DIR, \"apps\", \"models_provider\", 'sql',\n                        'list_model.sql' if workspace_manage else (\n                            'list_model_user_ee.sql' if is_x_pack_ee else 'list_model_user.sql')\n                    )\n                )\n            )\n            return {\n                \"shared_model\": shared_model,\n                \"model\": normal_model\n            }\n\n        def _build_query_params(self, workspace_id, workspace_manage: bool, user_id):\n            queryset = QuerySet(Model)\n            if workspace_id:\n                queryset = queryset.filter(workspace_id=workspace_id)\n            for field in ['name', 'model_type', 'model_name', 'provider', 'create_user']:\n                value = self.data.get(field)\n                if value is not None:\n                    if field == 'name':\n                        queryset = queryset.filter(**{f'{field}__icontains': value})\n                    elif field == 'create_user':\n                        queryset = queryset.filter(user_id=value)\n                    else:\n                        queryset = queryset.filter(**{field: value})\n            queryset = queryset.order_by(\"-create_time\")\n            return {\n                'model_query_set': queryset,\n                'workspace_user_resource_permission_query_set': QuerySet(WorkspaceUserResourcePermission).filter(\n                    auth_target_type=\"MODEL\",\n                    workspace_id=workspace_id,\n                    user_id=user_id)} if (\n                not workspace_manage) else {\n                'model_query_set': queryset,\n            }\n\n        def _build_model_data(self, model):\n            return {\n                'id': str(model.id),\n                'provider': model.provider,\n                'name': model.name,\n                'model_type': model.model_type,\n                'model_name': model.model_name,\n                'status': model.status,\n                'meta': model.meta,\n                'user_id': model.user_id,\n                'username': model.user.username,\n                'nick_name': model.user.nick_name,\n            }\n\n        def page(self, current_page, page_size):\n            pass\n\n    class ModelParams(serializers.Serializer):\n        id = serializers.UUIDField(required=True, label=_('model id'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            model = QuerySet(Model).filter(id=self.data.get(\"id\")).first()\n            if model is None:\n                raise AppApiException(500, _(\"Model does not exist\"))\n\n        def get_model_params(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            model_id = self.data.get('id')\n            model = QuerySet(Model).filter(id=model_id).first()\n            return model.model_params_form\n\n        def save_model_params_form(self, model_params_form, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            if model_params_form is None:\n                model_params_form = []\n            model_id = self.data.get('id')\n            model = QuerySet(Model).filter(id=model_id).first()\n            model.model_params_form = model_params_form\n            model.save()\n            return True\n\n\nclass WorkspaceSharedModelSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n    name = serializers.CharField(required=False, max_length=64, label=_('model name'))\n    model_type = serializers.CharField(required=False, label=_('model type'))\n    model_name = serializers.CharField(required=False, label=_('base model'))\n    provider = serializers.CharField(required=False, label=_('provider'))\n    create_user = serializers.CharField(required=False, label=_('create user'))\n\n    def get_share_model_list(self):\n        self.is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n\n        queryset = self._build_queryset(workspace_id)\n\n        return [\n            {\n                'id': str(model.id),\n                'provider': model.provider,\n                'name': model.name,\n                'model_type': model.model_type,\n                'model_name': model.model_name,\n                'status': model.status,\n                'meta': model.meta,\n                'user_id': model.user_id,\n                'nick_name': model.user.nick_name,\n                'username': model.user.username\n            }\n            for model in queryset.order_by(\"-create_time\")\n        ]\n\n    def _build_queryset(self, workspace_id):\n        queryset = QuerySet(Model)\n        if workspace_id:\n            get_authorized_model = DatabaseModelManage.get_model(\"get_authorized_model\")\n            if get_authorized_model is not None:\n                queryset = get_authorized_model(queryset, workspace_id)\n\n        for field in ['name', 'model_type', 'model_name', 'provider', 'create_user']:\n            value = self.data.get(field)\n            if value is not None:\n                if field == 'name':\n                    queryset = queryset.filter(**{f'{field}__icontains': value})\n                elif field == 'create_user':\n                    queryset = queryset.filter(user_id=value)\n                else:\n                    queryset = queryset.filter(**{field: value})\n\n        return queryset\n"
  },
  {
    "path": "apps/models_provider/sql/list_model.sql",
    "content": "SELECT model.\"id\"::text, model.\"name\",\n       model.model_name,\n       model.meta::json as meta,\n       model.credential,\n       model.model_params_form,\n       model.model_type,\n       model.provider,\n       model.status,\n       model.create_time,\n       model.update_time,\n       model.user_id,\n       \"user\".\"nick_name\" as \"nick_name\",\n       model.workspace_id\nfrom model\n         left join \"user\" on user_id = \"user\".id\n    ${model_query_set}"
  },
  {
    "path": "apps/models_provider/sql/list_model_user.sql",
    "content": "SELECT *\nFROM (SELECT model.\"id\"::text, model.\"name\",\n             model.model_name,\n             model.meta::json as meta, model.credential,\n             model.model_params_form,\n             model.model_type,\n             model.provider,\n             model.status,\n             model.create_time,\n             model.update_time,\n             model.user_id,\n             \"user\".\"nick_name\" as \"nick_name\",\n             model.workspace_id\n      from model\n               left join \"user\" on user_id = \"user\".id\n      where model.\"id\"::text in (select target\n                           from workspace_user_resource_permission ${workspace_user_resource_permission_query_set}\n        and 'VIEW' = any (permission_list)) ) temp ${model_query_set}\n"
  },
  {
    "path": "apps/models_provider/sql/list_model_user_ee.sql",
    "content": "SELECT *\nFROM (SELECT model.\"id\"::text, model.\"name\",\n             model.model_name,\n             model.meta::json as meta, model.credential,\n             model.model_params_form,\n             model.model_type,\n             model.provider,\n             model.status,\n             model.create_time,\n             model.update_time,\n             model.user_id,\n             \"user\".\"nick_name\" as \"nick_name\",\n             model.workspace_id\n      from model\n               left join \"user\" on user_id = \"user\".id\n      where model.\"id\"::text in (select target\n                           from workspace_user_resource_permission ${workspace_user_resource_permission_query_set}\n        and case\n                when auth_type = 'ROLE' then\n                    'ROLE' = any (permission_list)\n                        and\n                    'MODEL:READ' in (select (case\n                                                 when user_role_relation.role_id = any (array['USER'])\n                                                     THEN 'MODEL:READ'\n                                                 else role_permission.permission_id END)\n                                     from role_permission role_permission\n                                              right join user_role_relation user_role_relation\n                                                         on user_role_relation.role_id = role_permission.role_id\n                                     where user_role_relation.user_id = workspace_user_resource_permission.user_id\n                                       and user_role_relation.workspace_id =\n                                           workspace_user_resource_permission.workspace_id)\n\n                else\n                    'VIEW' = any (permission_list)\n          end) ) temp ${model_query_set}\n\n\n"
  },
  {
    "path": "apps/models_provider/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/models_provider/tools.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： tools.py\n    @date：2024/7/22 11:18\n    @desc:\n\"\"\"\nfrom django.db import connection\nfrom django.db.models import QuerySet\n\nfrom common.config.embedding_config import ModelManage\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom models_provider.models import Model\nfrom django.utils.translation import gettext_lazy as _\n\nimport json\nfrom typing import Dict\n\nfrom common.utils.rsa_util import rsa_long_decrypt\nfrom models_provider.constants.model_provider_constants import ModelProvideConstants\n\n\ndef get_model_(provider, model_type, model_name, credential, model_id, use_local=False, **kwargs):\n    \"\"\"\n    获取模型实例\n    @param provider:   供应商\n    @param model_type: 模型类型\n    @param model_name: 模型名称\n    @param credential: 认证信息\n    @param model_id:   模型id\n    @param use_local:  是否调用本地模型 只适用于本地供应商\n    @return: 模型实例\n    \"\"\"\n    model = get_provider(provider).get_model(model_type, model_name,\n                                             json.loads(\n                                                 rsa_long_decrypt(credential)),\n                                             model_id=model_id,\n                                             use_local=use_local,\n                                             streaming=True, **kwargs)\n    return model\n\n\ndef get_model(model, **kwargs):\n    \"\"\"\n    获取模型实例\n    @param model: model 数据库Model实例对象\n    @return: 模型实例\n    \"\"\"\n    return get_model_(model.provider, model.model_type, model.model_name, model.credential, str(model.id), **kwargs)\n\n\ndef get_provider(provider):\n    \"\"\"\n    获取供应商实例\n    @param provider: 供应商字符串\n    @return: 供应商实例\n    \"\"\"\n    return ModelProvideConstants[provider].value\n\n\ndef get_model_list(provider, model_type):\n    \"\"\"\n    获取模型列表\n    @param provider:   供应商字符串\n    @param model_type: 模型类型\n    @return:  模型列表\n    \"\"\"\n    return get_provider(provider).get_model_list(model_type)\n\n\ndef get_model_credential(provider, model_type, model_name):\n    \"\"\"\n    获取模型认证实例\n    @param provider:   供应商字符串\n    @param model_type: 模型类型\n    @param model_name: 模型名称\n    @return:  认证实例对象\n    \"\"\"\n    return get_provider(provider).get_model_credential(model_type, model_name)\n\n\ndef get_model_type_list(provider):\n    \"\"\"\n    获取模型类型列表\n    @param provider:  供应商字符串\n    @return:  模型类型列表\n    \"\"\"\n    return get_provider(provider).get_model_type_list()\n\n\ndef is_valid_credential(provider, model_type, model_name, model_credential: Dict[str, object], model_params,\n                        raise_exception=False):\n    \"\"\"\n    校验模型认证参数\n    @param provider:         供应商字符串\n    @param model_type:       模型类型\n    @param model_name:       模型名称\n    @param model_credential: 模型认证数据\n    @param raise_exception:  是否抛出错误\n    @return: True|False\n    \"\"\"\n    return get_provider(provider).is_valid_credential(model_type, model_name, model_credential, model_params,\n                                                      raise_exception)\n\n\ndef get_model_by_id(_id, workspace_id):\n    model = QuerySet(Model).filter(id=_id).first()\n    # 归还链接到连接池\n    connection.close()\n    get_authorized_model = DatabaseModelManage.get_model(\"get_authorized_model\")\n    if model and model.workspace_id != workspace_id and get_authorized_model is not None:\n        model = get_authorized_model(QuerySet(Model).filter(id=_id), workspace_id).first()\n    if model is None:\n        raise Exception(_(\"Model does not exist\"))\n    return model\n\ndef get_model_default_params(model):\n    def convert_to_int(value):\n        if isinstance(value, str):\n            try:\n                return int(value)\n            except ValueError:\n                return value\n        return value\n\n    return {\n        p.get('field'): convert_to_int(p.get('default_value'))\n        for p in model.model_params_form\n        if p.get('default_value') is not None\n    }\n\n\ndef get_model_instance_by_model_workspace_id(model_id, workspace_id, **kwargs):\n    \"\"\"\n    获取模型实例,根据模型相关数据\n    @param model_id:        模型id\n    @param workspace_id:    工作空间id\n    @return:                模型实例\n    \"\"\"\n    model = get_model_by_id(model_id, workspace_id)\n    s = get_model_default_params(model)\n    return ModelManage.get_model(model_id, lambda _id: get_model(model, **{**s, **kwargs}))\n"
  },
  {
    "path": "apps/models_provider/urls.py",
    "content": "import os\n\nfrom django.urls import path\n\nfrom . import views\n\napp_name = \"models_provider\"\n# @formatter:off\nurlpatterns = [\n    path('provider', views.Provide.as_view()),\n    path('provider/model_type_list', views.Provide.ModelTypeList.as_view()),\n    path('provider/model_list', views.Provide.ModelList.as_view()),\n    path('provider/model_params_form', views.Provide.ModelParamsForm.as_view()),\n    path('provider/model_form', views.Provide.ModelForm.as_view()),\n    path('workspace/<str:workspace_id>/model', views.ModelSetting.as_view()),\n    path('workspace/<str:workspace_id>/model_list', views.ModelList.as_view()),\n    path('workspace/<str:workspace_id>/model/<str:model_id>/model_params_form', views.ModelSetting.ModelParamsForm.as_view()),\n    path('workspace/<str:workspace_id>/model/<str:model_id>', views.ModelSetting.Operate.as_view()),\n    path('workspace/<str:workspace_id>/model/<str:model_id>/pause_download', views.ModelSetting.PauseDownload.as_view()),\n    path('workspace/<str:workspace_id>/model/<str:model_id>/meta', views.ModelSetting.ModelMeta.as_view()),\n    path('system/shared/workspace/<str:workspace_id>/model', views.WorkspaceSharedModelSetting.as_view()),\n]\n\nif os.environ.get('SERVER_NAME', 'web') == 'local_model':\n    urlpatterns += [\n        path('model/<str:model_id>/embed_documents', views.ModelApply.EmbedDocuments.as_view()),\n        path('model/<str:model_id>/embed_query', views.ModelApply.EmbedQuery.as_view()),\n        path('model/<str:model_id>/compress_documents', views.ModelApply.CompressDocuments.as_view()),\n    ]\n"
  },
  {
    "path": "apps/models_provider/views/__init__.py",
    "content": "# coding=utf-8\n\nfrom .model import *\nfrom .provide import *\nfrom .model_apply import *"
  },
  {
    "path": "apps/models_provider/views/model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： user.py\n    @date：2025/4/14 19:25\n    @desc:\n\"\"\"\nfrom django.db.models import QuerySet\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.views import APIView\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework.request import Request\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result\nfrom common.utils.common import query_params_to_single_dict\nfrom models_provider.api.model import ModelCreateAPI, GetModelApi, ModelEditApi, ModelListResponse, DefaultModelResponse\nfrom models_provider.api.provide import ProvideApi\nfrom models_provider.models import Model\nfrom models_provider.serializers.model_serializer import ModelSerializer, \\\n    WorkspaceSharedModelSerializer\nfrom system_manage.views import encryption_str\n\n\ndef encryption_credential(credential):\n    if isinstance(credential, dict):\n        return {key: encryption_str(credential.get(key)) for key in credential}\n    return credential\n\n\ndef get_edit_model_details(request):\n    path = request.path\n    body = request.data\n    query = request.query_params\n    credential = body.get('credential', {})\n    credential_encryption_ed = encryption_credential(credential)\n    return {\n        'path': path,\n        'body': {**body, 'credential': credential_encryption_ed},\n        'query': query\n    }\n\n\ndef get_model_operation_object(model_id):\n    model_model = QuerySet(model=Model).filter(id=model_id).first()\n    if model_model is not None:\n        return {\n            \"name\": model_model.name\n        }\n    return {}\n\n\nclass ModelSetting(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['POST'],\n                   summary=_(\"Create model\"),\n                   description=_(\"Create model\"),\n                   operation_id=_(\"Create model\"),  # type: ignore\n                   tags=[_(\"Model\")],  # type: ignore\n                   parameters=ModelCreateAPI.get_parameters(),\n                   request=ModelCreateAPI.get_request(),\n                   responses=ModelCreateAPI.get_response())\n    @has_permissions(PermissionConstants.MODEL_CREATE.get_workspace_permission(),\n                     PermissionConstants.MODEL_EDIT.get_workspace_permission_workspace_manage_role(),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role())\n    @log(menu='model', operate='Create model',\n         get_operation_object=lambda r, k: {'name': r.date.get('name')},\n         get_details=get_edit_model_details,\n         )\n    def post(self, request: Request, workspace_id: str):\n        return result.success(\n            ModelSerializer.Create(\n                data={**request.data, 'user_id': request.user.id, 'workspace_id': workspace_id}).insert(workspace_id,\n                                                                                                        with_valid=True))\n\n    # @extend_schema(methods=['PUT'],\n    #                summary=_('Update model'),\n    #                operation_id=_('Update model'),  # type: ignore\n    #                request=ModelEditApi.get_request(),\n    #                responses=ModelCreateApi.get_response(),\n    #                tags=[_('Model')])  # type: ignore\n    # @has_permissions(PermissionConstants.MODEL_CREATE)\n    # def put(self, request: Request):\n    #     return result.success(\n    #         ModelSerializer.Create(data={**request.data, 'user_id': str(request.user.id)}).insert(request.user.id,\n    #                                                                                               with_valid=True))\n\n    @extend_schema(methods=['GET'],\n                   summary=_('Query model list'),\n                   description=_('Query model list'),\n                   operation_id=_('Query model list'),  # type: ignore\n                   parameters=ModelListResponse.get_parameters(),\n                   responses=ModelListResponse.get_response(),\n                   tags=[_('Model')])  # type: ignore\n    @has_permissions(PermissionConstants.MODEL_READ.get_workspace_permission(),\n                     PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role())\n    def get(self, request: Request, workspace_id: str):\n        return result.success(\n            ModelSerializer.Query(\n                data={**query_params_to_single_dict(request.query_params), 'user_id': str(request.user.id)}).list(\n                workspace_id=workspace_id,\n                with_valid=True))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['PUT'],\n                       summary=_('Update model'),\n                       description=_('Update model'),\n                       operation_id=_('Update model'),  # type: ignore\n                       request=ModelEditApi.get_request(),\n                       parameters=GetModelApi.get_parameters(),\n                       responses=ModelEditApi.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        @has_permissions(PermissionConstants.MODEL_EDIT.get_workspace_model_permission(),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                         PermissionConstants.MODEL_EDIT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.MODEL.get_workspace_model_permission()],\n                                        CompareConstants.AND), )\n        @log(menu='model', operate='Update model',\n             get_operation_object=lambda r, k: get_model_operation_object(k.get('model_id')),\n             get_details=get_edit_model_details,\n             )\n        def put(self, request: Request, workspace_id, model_id: str):\n            return result.success(\n                ModelSerializer.Operate(\n                    data={'id': model_id, 'user_id': request.user.id, 'workspace_id': workspace_id}).edit(request.data,\n                                                                                                          str(request.user.id)))\n\n        @extend_schema(methods=['DELETE'],\n                       summary=_('Delete model'),\n                       description=_('Delete model'),\n                       operation_id=_('Delete model'),  # type: ignore\n                       parameters=GetModelApi.get_parameters(),\n                       responses=DefaultModelResponse.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        @has_permissions(PermissionConstants.MODEL_DELETE.get_workspace_model_permission(),\n                         PermissionConstants.MODEL_DELETE.get_workspace_permission_workspace_manage_role(),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.MODEL.get_workspace_model_permission()],\n                                        CompareConstants.AND), )\n        @log(menu='model', operate='Delete model',\n             get_operation_object=lambda r, k: get_model_operation_object(k.get('model_id')),\n             )\n        def delete(self, request: Request, workspace_id: str, model_id: str):\n            return result.success(\n                ModelSerializer.Operate(\n                    data={'id': model_id, 'user_id': request.user.id, 'workspace_id': workspace_id}).delete())\n\n        @extend_schema(methods=['GET'],\n                       summary=_('Query model details'),\n                       description=_('Query model details'),\n                       operation_id=_('Query model details'),  # type: ignore\n                       parameters=GetModelApi.get_parameters(),\n                       responses=GetModelApi.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        @has_permissions(PermissionConstants.MODEL_READ.get_workspace_model_permission(),\n                         PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.MODEL.get_workspace_model_permission()],\n                                        CompareConstants.AND), )\n        def get(self, request: Request, workspace_id: str, model_id: str):\n            return result.success(\n                ModelSerializer.Operate(\n                    data={'id': model_id, 'user_id': request.user.id, 'workspace_id': workspace_id}).one(\n                    with_valid=True))\n\n    class ModelParamsForm(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['GET'],\n                       summary=_('Get model parameter form'),\n                       description=_('Get model parameter form'),\n                       operation_id=_('Get model parameter form'),  # type: ignore\n                       parameters=GetModelApi.get_parameters(),\n                       responses=ProvideApi.ModelParamsForm.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        @has_permissions(PermissionConstants.MODEL_READ.get_workspace_model_permission(),\n                         PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(),\n                         PermissionConstants.APPLICATION_READ.get_workspace_permission(),\n                         PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(),\n                         PermissionConstants.KNOWLEDGE_READ.get_workspace_permission_workspace_manage_role(),\n                         PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                         PermissionConstants.MODEL_READ.get_workspace_permission(),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                         RoleConstants.USER.get_workspace_role(),)\n        def get(self, request: Request, workspace_id: str, model_id: str):\n            return result.success(\n                ModelSerializer.ModelParams(data={'id': model_id}).get_model_params())\n\n        @extend_schema(methods=['PUT'],\n                       summary=_('Save model parameter form'),\n                       description=_('Save model parameter form'),\n                       operation_id=_('Save model parameter form'),  # type: ignore\n                       parameters=GetModelApi.get_parameters(),\n                       request=GetModelApi.get_request(),\n                       responses=ProvideApi.ModelParamsForm.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        @has_permissions(PermissionConstants.MODEL_EDIT.get_workspace_model_permission(),\n                         PermissionConstants.MODEL_EDIT.get_workspace_permission_workspace_manage_role(),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                         PermissionConstants.MODEL_READ.get_workspace_permission(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.MODEL.get_workspace_model_permission()],\n                                        CompareConstants.AND), )\n        @log(menu='model', operate='Save model parameter form',\n             get_operation_object=lambda r, k: get_model_operation_object(k.get('model_id')),\n             )\n        def put(self, request: Request, workspace_id: str, model_id: str):\n            return result.success(\n                ModelSerializer.ModelParams(data={'id': model_id}).save_model_params_form(request.data))\n\n    class ModelMeta(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['GET'],\n                       summary=_(\n                           'Query model meta information, this interface does not carry authentication information'),\n                       description=_(\n                           'Query model meta information, this interface does not carry authentication information'),\n                       operation_id=_(\n                           'Query model meta information, this interface does not carry authentication information'),\n                       parameters=GetModelApi.get_parameters(),\n                       responses=GetModelApi.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        @has_permissions(PermissionConstants.MODEL_READ.get_workspace_model_permission(),\n                         PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                         PermissionConstants.MODEL_READ.get_workspace_permission(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.MODEL.get_workspace_model_permission()],\n                                        CompareConstants.AND), )\n        def get(self, request: Request, workspace_id: str, model_id: str):\n            return result.success(\n                ModelSerializer.Operate(data={'id': model_id, 'workspace_id': workspace_id}).one_meta(with_valid=True))\n\n    class PauseDownload(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['PUT'],\n                       summary=_('Pause model download'),\n                       description=_('Pause model download'),\n                       operation_id=_('Pause model download'),  # type: ignore\n                       parameters=GetModelApi.get_parameters(),\n                       request=GetModelApi.get_request(),\n                       responses=DefaultModelResponse.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        @has_permissions(PermissionConstants.MODEL_CREATE.get_workspace_model_permission(),\n                         PermissionConstants.MODEL_CREATE.get_workspace_permission_workspace_manage_role(),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.MODEL.get_workspace_model_permission()],\n                                        CompareConstants.AND), )\n        def put(self, request: Request, workspace_id: str, model_id: str):\n            return result.success(\n                ModelSerializer.Operate(data={'id': model_id, 'workspace_id': workspace_id}).pause_download())\n\n\nclass WorkspaceSharedModelSetting(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['Get'],\n        summary=_('Get Share model by workspace id'),\n        description=_('Get Share model by workspace id'),\n        operation_id=_('Get Share model by workspace id'),  # type: ignore\n        parameters=ModelListResponse.get_parameters(),\n        responses=DefaultModelResponse.get_response(),\n        tags=[_('Shared Model')]\n    )  # type: ignore\n    @has_permissions(\n        PermissionConstants.MODEL_READ.get_workspace_permission(),\n        PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        RoleConstants.USER.get_workspace_role(),\n    )\n    def get(self, request: Request, workspace_id: str):\n        return result.success(\n            WorkspaceSharedModelSerializer(data={**query_params_to_single_dict(request.query_params),\n                                                 'workspace_id': workspace_id}).get_share_model_list())\n\n\nclass ModelList(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['GET'],\n                   summary=_('Query all model list'),\n                   description=_('Query all model list'),\n                   operation_id=_('Query all model list'),  # type: ignore\n                   parameters=ModelListResponse.get_parameters(),\n                   responses=ModelListResponse.get_response(),\n                   tags=[_('Model')])  # type: ignore\n    @has_permissions(PermissionConstants.MODEL_READ.get_workspace_permission(),\n                     PermissionConstants.KNOWLEDGE_READ.get_workspace_permission(),\n                     PermissionConstants.APPLICATION_READ.get_workspace_permission(),\n                     PermissionConstants.MODEL_READ.get_workspace_permission_workspace_manage_role(),\n                     PermissionConstants.KNOWLEDGE_READ.get_workspace_permission_workspace_manage_role(),\n                     PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role())\n    def get(self, request: Request, workspace_id: str):\n        return result.success(\n            ModelSerializer.Query(\n                data={**query_params_to_single_dict(request.query_params), 'user_id': str(request.user.id)}).model_list(\n                workspace_id=workspace_id,\n                with_valid=True))\n"
  },
  {
    "path": "apps/models_provider/views/model_apply.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： model_apply.py\n    @date：2024/8/20 20:38\n    @desc:\n\"\"\"\nfrom urllib.request import Request\n\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.views import APIView\n\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants\nfrom common.result import result\nfrom models_provider.api.model import DefaultModelResponse\nfrom models_provider.serializers.model_apply_serializers import ModelApplySerializers\n\n\nclass ModelApply(APIView):\n    class EmbedDocuments(APIView):\n        @extend_schema(methods=['POST'],\n                       summary=_('Vectorization documentation'),\n                       description=_('Vectorization documentation'),\n                       operation_id=_('Vectorization documentation'),  # type: ignore\n                       responses=DefaultModelResponse.get_response(),\n                       tags=[_('Model')]  # type: ignore\n                       )\n        def post(self, request: Request, model_id):\n            return result.success(\n                ModelApplySerializers(data={'model_id': model_id}).embed_documents(request.data))\n\n    class EmbedQuery(APIView):\n        @extend_schema(methods=['POST'],\n                       summary=_('Vectorization documentation'),\n                       description=_('Vectorization documentation'),\n                       operation_id=_('Vectorization documentation'),  # type: ignore\n                       responses=DefaultModelResponse.get_response(),\n                       tags=[_('Model')]  # type: ignore\n                       )\n        def post(self, request: Request, model_id):\n            return result.success(\n                ModelApplySerializers(data={'model_id': model_id}).embed_query(request.data))\n\n    class CompressDocuments(APIView):\n        @extend_schema(methods=['POST'],\n                       summary=_('Reorder documents'),\n                       description=_('Reorder documents'),\n                       operation_id=_('Reorder documents'),  # type: ignore\n                       responses=DefaultModelResponse.get_response(),\n                       tags=[_('Model')]  # type: ignore\n                       )\n        def post(self, request: Request, model_id):\n            return result.success(\n                ModelApplySerializers(data={'model_id': model_id}).compress_documents(request.data))\n"
  },
  {
    "path": "apps/models_provider/views/provide.py",
    "content": "# coding=utf-8\n\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants\nfrom models_provider.api.provide import ProvideApi\nfrom models_provider.constants.model_provider_constants import ModelProvideConstants\nfrom models_provider.serializers.model_serializer import get_default_model_params_setting\n\n\nclass Provide(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['GET'],\n                   summary=_('Get a list of model suppliers'),\n                   description=_('Get a list of model suppliers'),\n                   operation_id=_('Get a list of model suppliers'),  # type: ignore\n                   responses=ProvideApi.get_response(),\n                   tags=[_('Model')])  # type: ignore\n    def get(self, request: Request):\n        model_type = request.query_params.get('model_type')\n        if model_type:\n            providers = []\n            for key in ModelProvideConstants.__members__:\n                if len([item for item in ModelProvideConstants[key].value.get_model_type_list() if\n                        item['value'] == model_type]) > 0:\n                    providers.append(ModelProvideConstants[key].value.get_model_provide_info().to_dict())\n            return result.success(providers)\n        return result.success(\n            [ModelProvideConstants[key].value.get_model_provide_info().to_dict() for key in\n             ModelProvideConstants.__members__])\n\n    class ModelTypeList(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['GET'],\n                       summary=_('Get a list of model types'),\n                       description=_('Get a list of model types'),\n                       operation_id=_('Get a list of model types'),  # type: ignore\n                       parameters=ProvideApi.ModelTypeList.get_query_params_api(),\n                       responses=ProvideApi.ModelTypeList.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        def get(self, request: Request):\n            provider = request.query_params.get('provider')\n            return result.success(ModelProvideConstants[provider].value.get_model_type_list())\n\n    class ModelList(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['GET'],\n                       summary=_('Example of obtaining model list'),\n                       description=_('Example of obtaining model list'),\n                       operation_id=_('Example of obtaining model list'),  # type: ignore\n                       parameters=ProvideApi.ModelList.get_query_params_api(),\n                       responses=ProvideApi.ModelList.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        def get(self, request: Request):\n            provider = request.query_params.get('provider')\n            model_type = request.query_params.get('model_type')\n\n            return result.success(\n                ModelProvideConstants[provider].value.get_model_list(\n                    model_type))\n\n    class ModelParamsForm(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['GET'],\n                       summary=_('Get model default parameters'),\n                       description=_('Get model default parameters'),\n                       operation_id=_('Get model default parameters'),  # type: ignore\n                       parameters=ProvideApi.ModelParamsForm.get_query_params_api(),\n                       responses=ProvideApi.ModelParamsForm.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        def get(self, request: Request):\n            provider = request.query_params.get('provider')\n            model_type = request.query_params.get('model_type')\n            model_name = request.query_params.get('model_name')\n\n            return result.success(get_default_model_params_setting(provider, model_type, model_name))\n\n    class ModelForm(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['GET'],\n                       summary=_('Get the model creation form'),\n                       description=_('Get the model creation form'),\n                       operation_id=_('Get the model creation form'),  # type: ignore\n                       parameters=ProvideApi.ModelParamsForm.get_query_params_api(),\n                       responses=ProvideApi.ModelParamsForm.get_response(),\n                       tags=[_('Model')])  # type: ignore\n        def get(self, request: Request):\n            provider = request.query_params.get('provider')\n            model_type = request.query_params.get('model_type')\n            model_name = request.query_params.get('model_name')\n            return result.success(\n                ModelProvideConstants[provider].value.get_model_credential(model_type, model_name).to_form_list())\n"
  },
  {
    "path": "apps/ops/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： __init__.py.py\n    @date：2024/8/16 14:47\n    @desc:\n\"\"\"\nfrom .celery import app as celery_app\n"
  },
  {
    "path": "apps/ops/celery/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\nimport os\n\nfrom celery import Celery\nfrom kombu import Exchange, Queue\nfrom maxkb import settings\nfrom .heartbeat import *\nfrom .hmac_signed_serializer import register_hmac_signed_serializer\n\n# set the default Django settings module for the 'celery' program.\nos.environ.setdefault('DJANGO_SETTINGS_MODULE', 'maxkb.settings')\n\nregister_hmac_signed_serializer()\n\napp = Celery('MaxKB')\n\nconfigs = {k: v for k, v in settings.__dict__.items() if k.startswith('CELERY')}\nconfigs['worker_concurrency'] = 5\n# Using a string here means the worker will not have to\n# pickle the object when using Windows.\n# app.config_from_object('django.conf:settings', namespace='CELERY')\n\nconfigs[\"task_queues\"] = [\n    Queue(\"celery\", Exchange(\"celery\"), routing_key=\"celery\"),\n    Queue(\"model\", Exchange(\"model\"), routing_key=\"model\")\n]\napp.namespace = 'CELERY'\napp.conf.update(\n    {key.replace('CELERY_', '') if key.replace('CELERY_', '').lower() == key.replace('CELERY_',\n                                                                                     '') else key: configs.get(\n        key) for\n        key\n        in configs.keys()})\napp.autodiscover_tasks(lambda: [app_config.split('.')[0] for app_config in settings.INSTALLED_APPS])\n"
  },
  {
    "path": "apps/ops/celery/const.py",
    "content": "# -*- coding: utf-8 -*-\n#\n\nCELERY_LOG_MAGIC_MARK = b'\\x00\\x00\\x00\\x00\\x00'"
  },
  {
    "path": "apps/ops/celery/decorator.py",
    "content": "# -*- coding: utf-8 -*-\n#\nfrom functools import wraps\n\n_need_registered_period_tasks = []\n_after_app_ready_start_tasks = []\n_after_app_shutdown_clean_periodic_tasks = []\n\n\ndef add_register_period_task(task):\n    _need_registered_period_tasks.append(task)\n\n\ndef get_register_period_tasks():\n    return _need_registered_period_tasks\n\n\ndef add_after_app_shutdown_clean_task(name):\n    _after_app_shutdown_clean_periodic_tasks.append(name)\n\n\ndef get_after_app_shutdown_clean_tasks():\n    return _after_app_shutdown_clean_periodic_tasks\n\n\ndef add_after_app_ready_task(name):\n    _after_app_ready_start_tasks.append(name)\n\n\ndef get_after_app_ready_tasks():\n    return _after_app_ready_start_tasks\n\n\ndef register_as_period_task(\n        crontab=None, interval=None, name=None,\n        args=(), kwargs=None,\n        description=''):\n    \"\"\"\n    Warning: Task must have not any args and kwargs\n    :param crontab:  \"* * * * *\"\n    :param interval:  60*60*60\n    :param args: ()\n    :param kwargs: {}\n    :param description: \"\n    :param name: \"\"\n    :return:\n    \"\"\"\n    if crontab is None and interval is None:\n        raise SyntaxError(\"Must set crontab or interval one\")\n\n    def decorate(func):\n        if crontab is None and interval is None:\n            raise SyntaxError(\"Interval and crontab must set one\")\n\n        # Because when this decorator run, the task was not created,\n        # So we can't use func.name\n        task = '{func.__module__}.{func.__name__}'.format(func=func)\n        _name = name if name else task\n        add_register_period_task({\n            _name: {\n                'task': task,\n                'interval': interval,\n                'crontab': crontab,\n                'args': args,\n                'kwargs': kwargs if kwargs else {},\n                'description': description\n            }\n        })\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            return func(*args, **kwargs)\n\n        return wrapper\n\n    return decorate\n\n\ndef after_app_ready_start(func):\n    # Because when this decorator run, the task was not created,\n    # So we can't use func.name\n    name = '{func.__module__}.{func.__name__}'.format(func=func)\n    if name not in _after_app_ready_start_tasks:\n        add_after_app_ready_task(name)\n\n    @wraps(func)\n    def decorate(*args, **kwargs):\n        return func(*args, **kwargs)\n\n    return decorate\n\n\ndef after_app_shutdown_clean_periodic(func):\n    # Because when this decorator run, the task was not created,\n    # So we can't use func.name\n    name = '{func.__module__}.{func.__name__}'.format(func=func)\n    if name not in _after_app_shutdown_clean_periodic_tasks:\n        add_after_app_shutdown_clean_task(name)\n\n    @wraps(func)\n    def decorate(*args, **kwargs):\n        return func(*args, **kwargs)\n\n    return decorate\n"
  },
  {
    "path": "apps/ops/celery/heartbeat.py",
    "content": "from pathlib import Path\n\nfrom celery.signals import heartbeat_sent, worker_ready, worker_shutdown\n\n\n@heartbeat_sent.connect\ndef heartbeat(sender, **kwargs):\n    worker_name = sender.eventer.hostname.split('@')[0]\n    heartbeat_path = Path('/opt/maxkb-app/tmp/worker_heartbeat_{}'.format(worker_name))\n    heartbeat_path.touch()\n\n\n@worker_ready.connect\ndef worker_ready(sender, **kwargs):\n    worker_name = sender.hostname.split('@')[0]\n    ready_path = Path('/opt/maxkb-app/tmp/worker_ready_{}'.format(worker_name))\n    ready_path.touch()\n\n\n@worker_shutdown.connect\ndef worker_shutdown(sender, **kwargs):\n    worker_name = sender.hostname.split('@')[0]\n    for signal in ['ready', 'heartbeat']:\n        path = Path('/opt/maxkb-app/tmp/worker_{}_{}'.format(signal, worker_name))\n        path.unlink(missing_ok=True)\n"
  },
  {
    "path": "apps/ops/celery/hmac_signed_serializer.py",
    "content": "import hmac\nimport hashlib\nimport pickle\nimport os\nimport socket\nfrom kombu.serialization import register\n\n_local_secret_key = os.environ.get('MAXKB_HMAC_SIGNED_SERIALIZER_SECRET_KEY', 'default_hmac_signed_serializer_secret_key:' + os.getenv('MAXKB_VERSION', socket.gethostname()))\ntry:\n    from xpack import get_md5\n    _local_secret_key = get_md5()\nexcept ImportError:\n    pass\n\ndef secure_dumps(obj):\n    data = pickle.dumps(obj)\n    signature = hmac.new(_local_secret_key.encode(), data, hashlib.sha256).digest()\n    return signature + data\n\ndef secure_loads(signed_data):\n    if len(signed_data) < 32:\n        raise ValueError(\"Invalid signed data packet\")\n    signature = signed_data[:32]\n    payload = signed_data[32:]\n    expected_signature = hmac.new(_local_secret_key.encode(), payload, hashlib.sha256).digest()\n    if hmac.compare_digest(signature, expected_signature):\n        return pickle.loads(payload)\n    else:\n        raise ValueError(\"Security Alert: Task signature mismatch! Potential tampering detected.\")\n\ndef register_hmac_signed_serializer():\n    register(\n        'hmac_signed_serializer',\n        secure_dumps,\n        secure_loads,\n        content_type='application/x-python-hmac-signed-serialize',\n        content_encoding='binary'\n    )"
  },
  {
    "path": "apps/ops/celery/logger.py",
    "content": "import logging\nfrom logging import StreamHandler\nfrom threading import get_ident\n\nfrom celery import current_task\nfrom celery.signals import task_prerun, task_postrun\nfrom django.conf import settings\nfrom kombu import Connection, Exchange, Queue, Producer\nfrom kombu.mixins import ConsumerMixin\n\nfrom common.utils.logger import maxkb_logger\nfrom .utils import get_celery_task_log_path\nfrom .const import CELERY_LOG_MAGIC_MARK\n\nrouting_key = 'celery_log'\ncelery_log_exchange = Exchange('celery_log_exchange', type='direct')\ncelery_log_queue = [Queue('celery_log', celery_log_exchange, routing_key=routing_key)]\n\n\nclass CeleryLoggerConsumer(ConsumerMixin):\n    def __init__(self):\n        self.connection = Connection(settings.CELERY_LOG_BROKER_URL)\n\n    def get_consumers(self, Consumer, channel):\n        return [Consumer(queues=celery_log_queue,\n                         accept=['pickle', 'json'],\n                         callbacks=[self.process_task])\n                ]\n\n    def handle_task_start(self, task_id, message):\n        pass\n\n    def handle_task_end(self, task_id, message):\n        pass\n\n    def handle_task_log(self, task_id, msg, message):\n        pass\n\n    def process_task(self, body, message):\n        action = body.get('action')\n        task_id = body.get('task_id')\n        msg = body.get('msg')\n        if action == CeleryLoggerProducer.ACTION_TASK_LOG:\n            self.handle_task_log(task_id, msg, message)\n        elif action == CeleryLoggerProducer.ACTION_TASK_START:\n            self.handle_task_start(task_id, message)\n        elif action == CeleryLoggerProducer.ACTION_TASK_END:\n            self.handle_task_end(task_id, message)\n\n\nclass CeleryLoggerProducer:\n    ACTION_TASK_START, ACTION_TASK_LOG, ACTION_TASK_END = range(3)\n\n    def __init__(self):\n        self.connection = Connection(settings.CELERY_LOG_BROKER_URL)\n\n    @property\n    def producer(self):\n        return Producer(self.connection)\n\n    def publish(self, payload):\n        self.producer.publish(\n            payload, serializer='json', exchange=celery_log_exchange,\n            declare=[celery_log_exchange], routing_key=routing_key\n        )\n\n    def log(self, task_id, msg):\n        payload = {'task_id': task_id, 'msg': msg, 'action': self.ACTION_TASK_LOG}\n        return self.publish(payload)\n\n    def read(self):\n        pass\n\n    def flush(self):\n        pass\n\n    def task_end(self, task_id):\n        payload = {'task_id': task_id, 'action': self.ACTION_TASK_END}\n        return self.publish(payload)\n\n    def task_start(self, task_id):\n        payload = {'task_id': task_id, 'action': self.ACTION_TASK_START}\n        return self.publish(payload)\n\n\nclass CeleryTaskLoggerHandler(StreamHandler):\n    terminator = '\\r\\n'\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        task_prerun.connect(self.on_task_start)\n        task_postrun.connect(self.on_start_end)\n\n    @staticmethod\n    def get_current_task_id():\n        if not current_task:\n            return\n        task_id = current_task.request.root_id\n        return task_id\n\n    def on_task_start(self, sender, task_id, **kwargs):\n        return self.handle_task_start(task_id)\n\n    def on_start_end(self, sender, task_id, **kwargs):\n        return self.handle_task_end(task_id)\n\n    def after_task_publish(self, sender, body, **kwargs):\n        pass\n\n    def emit(self, record):\n        task_id = self.get_current_task_id()\n        if not task_id:\n            return\n        try:\n            self.write_task_log(task_id, record)\n            self.flush()\n        except Exception:\n            self.handleError(record)\n\n    def write_task_log(self, task_id, msg):\n        pass\n\n    def handle_task_start(self, task_id):\n        pass\n\n    def handle_task_end(self, task_id):\n        pass\n\n\nclass CeleryThreadingLoggerHandler(CeleryTaskLoggerHandler):\n    @staticmethod\n    def get_current_thread_id():\n        return str(get_ident())\n\n    def emit(self, record):\n        thread_id = self.get_current_thread_id()\n        try:\n            self.write_thread_task_log(thread_id, record)\n            self.flush()\n        except ValueError:\n            self.handleError(record)\n\n    def write_thread_task_log(self, thread_id, msg):\n        pass\n\n    def handle_task_start(self, task_id):\n        pass\n\n    def handle_task_end(self, task_id):\n        pass\n\n    def handleError(self, record) -> None:\n        pass\n\n\nclass CeleryTaskMQLoggerHandler(CeleryTaskLoggerHandler):\n    def __init__(self):\n        self.producer = CeleryLoggerProducer()\n        super().__init__(stream=None)\n\n    def write_task_log(self, task_id, record):\n        msg = self.format(record)\n        self.producer.log(task_id, msg)\n\n    def flush(self):\n        self.producer.flush()\n\n\nclass CeleryTaskFileHandler(CeleryTaskLoggerHandler):\n    def __init__(self, *args, **kwargs):\n        self.f = None\n        super().__init__(*args, **kwargs)\n\n    def emit(self, record):\n        msg = self.format(record)\n        if not self.f or self.f.closed:\n            return\n        self.f.write(msg)\n        self.f.write(self.terminator)\n        self.flush()\n\n    def flush(self):\n        self.f and self.f.flush()\n\n    def handle_task_start(self, task_id):\n        log_path = get_celery_task_log_path(task_id)\n        self.f = open(log_path, 'a')\n\n    def handle_task_end(self, task_id):\n        self.f and self.f.close()\n\n\nclass CeleryThreadTaskFileHandler(CeleryThreadingLoggerHandler):\n    def __init__(self, *args, **kwargs):\n        self.thread_id_fd_mapper = {}\n        self.task_id_thread_id_mapper = {}\n        super().__init__(*args, **kwargs)\n\n    def write_thread_task_log(self, thread_id, record):\n        f = self.thread_id_fd_mapper.get(thread_id, None)\n        if not f:\n            raise ValueError('Not found thread task file')\n        msg = self.format(record)\n        f.write(msg.encode())\n        f.write(self.terminator.encode())\n        f.flush()\n\n    def flush(self):\n        for f in self.thread_id_fd_mapper.values():\n            f.flush()\n\n    def handle_task_start(self, task_id):\n        maxkb_logger.info('handle_task_start')\n        log_path = get_celery_task_log_path(task_id)\n        thread_id = self.get_current_thread_id()\n        self.task_id_thread_id_mapper[task_id] = thread_id\n        f = open(log_path, 'ab')\n        self.thread_id_fd_mapper[thread_id] = f\n\n    def handle_task_end(self, task_id):\n        maxkb_logger.info('handle_task_end')\n        ident_id = self.task_id_thread_id_mapper.get(task_id, '')\n        f = self.thread_id_fd_mapper.pop(ident_id, None)\n        if f and not f.closed:\n            f.write(CELERY_LOG_MAGIC_MARK)\n            f.close()\n        self.task_id_thread_id_mapper.pop(task_id, None)\n"
  },
  {
    "path": "apps/ops/celery/signal_handler.py",
    "content": "# -*- coding: utf-8 -*-\n#\nimport logging\nimport os\nimport uuid_utils.compat as uuid\n\nfrom celery import subtask\nfrom celery.signals import (\n    worker_ready, worker_shutdown, after_setup_logger, task_revoked, task_prerun\n)\nfrom django.core.cache import cache\nfrom django_apscheduler.models import DjangoJob\nfrom django_celery_beat.models import PeriodicTask\n\nfrom common.utils.logger import maxkb_logger\nfrom .decorator import get_after_app_ready_tasks, get_after_app_shutdown_clean_tasks\nfrom .logger import CeleryThreadTaskFileHandler\n\nlogger = logging.getLogger(__file__)\nsafe_str = lambda x: x\n\n\ndef init_scheduler():\n    from common import job\n    from common.init import init_template\n    from trigger.models import Trigger\n\n    job.run()\n    init_template.run()\n\n    # 清理已经不存在的 trigger job\n    trigger_jobs = DjangoJob.objects.filter(id__startswith=\"trigger:\")\n    # 从 job id 中提取 trigger_id (格式: trigger:<trigger_id>:task:...)\n    trigger_ids_from_jobs = set()\n    job_id_to_trigger_id = {}  # 映射 job_id -> trigger_id\n\n    for job in trigger_jobs:\n        parts = job.id.split(':')\n        if len(parts) >= 2:\n            trigger_id = uuid.UUID(parts[1])  # 提取 trigger_id\n            trigger_ids_from_jobs.add(trigger_id)\n            job_id_to_trigger_id[job.id] = trigger_id\n\n    # 获取所有有效的 Trigger ID\n    valid_trigger_ids = set(Trigger.objects.filter(\n        id__in=trigger_ids_from_jobs, is_active=True\n    ).values_list('id', flat=True))\n\n    # 找出需要删除的 job (trigger 已不存在的)\n    jobs_to_delete = [\n        job_id for job_id, trigger_id in job_id_to_trigger_id.items()\n        if trigger_id not in valid_trigger_ids\n    ]\n\n    if jobs_to_delete:\n        DjangoJob.objects.filter(id__in=jobs_to_delete).delete()\n        logger.info(f\"Cleaned up {len(jobs_to_delete)} orphaned trigger jobs\")\n\n    try:\n        from xpack import job as xpack_job\n\n        xpack_job.run()\n    except ImportError:\n        pass\n\n\n@worker_ready.connect\ndef on_app_ready(sender=None, headers=None, **kwargs):\n    if cache.get(\"CELERY_APP_READY\", 0) == 1:\n        return\n    cache.set(\"CELERY_APP_READY\", 1, 10)\n    # 初始化定时任务\n    init_scheduler()\n\n    tasks = get_after_app_ready_tasks()\n    logger.debug(\"Work ready signal recv\")\n    logger.debug(\"Start need start task: [{}]\".format(\", \".join(tasks)))\n    for task in tasks:\n        periodic_task = PeriodicTask.objects.filter(task=task).first()\n        if periodic_task and not periodic_task.enabled:\n            logger.debug(\"Periodic task [{}] is disabled!\".format(task))\n            continue\n        subtask(task).delay()\n\n\ndef delete_files(directory):\n    if os.path.isdir(directory):\n        for filename in os.listdir(directory):\n            file_path = os.path.join(directory, filename)\n            if os.path.isfile(file_path):\n                os.remove(file_path)\n\n\n@worker_shutdown.connect\ndef after_app_shutdown_periodic_tasks(sender=None, **kwargs):\n    if cache.get(\"CELERY_APP_SHUTDOWN\", 0) == 1:\n        return\n    cache.set(\"CELERY_APP_SHUTDOWN\", 1, 10)\n    tasks = get_after_app_shutdown_clean_tasks()\n    logger.debug(\"Worker shutdown signal recv\")\n    logger.debug(\"Clean period tasks: [{}]\".format(', '.join(tasks)))\n    PeriodicTask.objects.filter(name__in=tasks).delete()\n\n\n@after_setup_logger.connect\ndef add_celery_logger_handler(sender=None, logger=None, loglevel=None, format=None, **kwargs):\n    if not logger:\n        return\n    task_handler = CeleryThreadTaskFileHandler()\n    task_handler.setLevel(loglevel)\n    formatter = logging.Formatter(format)\n    task_handler.setFormatter(formatter)\n    logger.addHandler(task_handler)\n\n\n@task_revoked.connect\ndef on_task_revoked(request, terminated, signum, expired, **kwargs):\n    maxkb_logger.info('task_revoked', terminated)\n\n\n@task_prerun.connect\ndef on_taskaa_start(sender, task_id, **kwargs):\n    pass\n    # sender.update_state(state='REVOKED',\n#                     meta={'exc_type': 'Exception', 'exc': 'Exception', 'message': '暂停任务', 'exc_message': ''})\n"
  },
  {
    "path": "apps/ops/celery/utils.py",
    "content": "# -*- coding: utf-8 -*-\n#\nimport logging\nimport os\nimport uuid\n\nfrom django.conf import settings\nfrom django_celery_beat.models import (\n    PeriodicTasks\n)\n\nfrom common.utils.logger import maxkb_logger\nfrom maxkb.const import PROJECT_DIR\n\nlogger = logging.getLogger(__file__)\n\n\ndef disable_celery_periodic_task(task_name):\n    from django_celery_beat.models import PeriodicTask\n    PeriodicTask.objects.filter(name=task_name).update(enabled=False)\n    PeriodicTasks.update_changed()\n\n\ndef delete_celery_periodic_task(task_name):\n    from django_celery_beat.models import PeriodicTask\n    PeriodicTask.objects.filter(name=task_name).delete()\n    PeriodicTasks.update_changed()\n\n\ndef get_celery_periodic_task(task_name):\n    from django_celery_beat.models import PeriodicTask\n    task = PeriodicTask.objects.filter(name=task_name).first()\n    return task\n\n\ndef make_dirs(name, mode=0o700, exist_ok=False):\n    \"\"\" 默认权限设置为 0o700 \"\"\"\n    return os.makedirs(name, mode=mode, exist_ok=exist_ok)\n\n\ndef get_task_log_path(base_path, task_id, level=2):\n    task_id = str(task_id)\n    try:\n        uuid.UUID(task_id)\n    except:\n        return os.path.join(PROJECT_DIR, 'data', 'caution.txt')\n\n    rel_path = os.path.join(*task_id[:level], task_id + '.log')\n    path = os.path.join(base_path, rel_path)\n    make_dirs(os.path.dirname(path), exist_ok=True)\n    return path\n\n\ndef get_celery_task_log_path(task_id):\n    return get_task_log_path(settings.CELERY_LOG_DIR, task_id)\n\n\ndef get_celery_status():\n    from . import app\n    i = app.control.inspect()\n    ping_data = i.ping() or {}\n    active_nodes = [k for k, v in ping_data.items() if v.get('ok') == 'pong']\n    active_queue_worker = set([n.split('@')[0] for n in active_nodes if n])\n    # Celery Worker 数量: 2\n    if len(active_queue_worker) < 2:\n        maxkb_logger.info(\"Not all celery worker worked\")\n        return False\n    else:\n        return True\n"
  },
  {
    "path": "apps/oss/__init__.py",
    "content": ""
  },
  {
    "path": "apps/oss/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "apps/oss/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass OssConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'oss'\n"
  },
  {
    "path": "apps/oss/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "apps/oss/models.py",
    "content": "from django.db import models\n\n# Create your models here.\n"
  },
  {
    "path": "apps/oss/retrieval_urls.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： retrieval_urls.py\n    @date：2025/7/2 19:01\n    @desc:\n\"\"\"\nfrom django.urls import re_path\n\nfrom . import views\n\napp_name = 'oss'\n\nurlpatterns = [\n    re_path(rf'^(.*)/oss/file/(?P<file_id>[\\w-]+)/?$',\n            views.FileRetrievalView.as_view()),\n    re_path(rf'oss/file/(?P<file_id>[\\w-]+)/?$',\n            views.FileRetrievalView.as_view()),\n    re_path(rf'^/oss/get_url/(?P<url>[\\w-]+)?$',\n            views.GetUrlView.as_view()),\n\n]\n"
  },
  {
    "path": "apps/oss/serializers/__init__.py",
    "content": "# coding=utf-8"
  },
  {
    "path": "apps/oss/serializers/file.py",
    "content": "# coding=utf-8\nimport base64\nimport ipaddress\nimport re\nimport socket\nimport urllib\nfrom urllib.parse import urlparse\n\nimport requests\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\nfrom django.http import HttpResponse\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import Application\nfrom common.exception.app_exception import NotFound404, AppApiException\nfrom knowledge.models import File, FileSourceType\nfrom tools.serializers.tool import UploadedFileField\n\nmime_types = {\n    \"html\": \"text/html\", \"htm\": \"text/html\", \"shtml\": \"text/html\", \"css\": \"text/css\", \"xml\": \"text/xml\",\n    \"gif\": \"image/gif\", \"jpeg\": \"image/jpeg\", \"jpg\": \"image/jpeg\", \"js\": \"application/javascript\",\n    \"atom\": \"application/atom+xml\", \"rss\": \"application/rss+xml\", \"mml\": \"text/mathml\", \"txt\": \"text/plain\",\n    \"jad\": \"text/vnd.sun.j2me.app-descriptor\", \"wml\": \"text/vnd.wap.wml\", \"htc\": \"text/x-component\",\n    \"avif\": \"image/avif\", \"png\": \"image/png\", \"svg\": \"image/svg+xml\", \"svgz\": \"image/svg+xml\",\n    \"tif\": \"image/tiff\", \"tiff\": \"image/tiff\", \"wbmp\": \"image/vnd.wap.wbmp\", \"webp\": \"image/webp\",\n    \"ico\": \"image/x-icon\", \"jng\": \"image/x-jng\", \"bmp\": \"image/x-ms-bmp\", \"woff\": \"font/woff\",\n    \"woff2\": \"font/woff2\", \"jar\": \"application/java-archive\", \"war\": \"application/java-archive\",\n    \"ear\": \"application/java-archive\", \"json\": \"application/json\", \"hqx\": \"application/mac-binhex40\",\n    \"doc\": \"application/msword\", \"pdf\": \"application/pdf\", \"ps\": \"application/postscript\",\n    \"docx\": \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n    \"xlsx\": \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n    \"pptx\": \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n    \"eps\": \"application/postscript\", \"ai\": \"application/postscript\", \"rtf\": \"application/rtf\",\n    \"m3u8\": \"application/vnd.apple.mpegurl\", \"kml\": \"application/vnd.google-earth.kml+xml\",\n    \"kmz\": \"application/vnd.google-earth.kmz\", \"xls\": \"application/vnd.ms-excel\",\n    \"eot\": \"application/vnd.ms-fontobject\", \"ppt\": \"application/vnd.ms-powerpoint\",\n    \"odg\": \"application/vnd.oasis.opendocument.graphics\",\n    \"odp\": \"application/vnd.oasis.opendocument.presentation\",\n    \"ods\": \"application/vnd.oasis.opendocument.spreadsheet\", \"odt\": \"application/vnd.oasis.opendocument.text\",\n    \"wmlc\": \"application/vnd.wap.wmlc\", \"wasm\": \"application/wasm\", \"7z\": \"application/x-7z-compressed\",\n    \"cco\": \"application/x-cocoa\", \"jardiff\": \"application/x-java-archive-diff\",\n    \"jnlp\": \"application/x-java-jnlp-file\", \"run\": \"application/x-makeself\", \"pl\": \"application/x-perl\",\n    \"pm\": \"application/x-perl\", \"prc\": \"application/x-pilot\", \"pdb\": \"application/x-pilot\",\n    \"rar\": \"application/x-rar-compressed\", \"rpm\": \"application/x-redhat-package-manager\",\n    \"sea\": \"application/x-sea\", \"swf\": \"application/x-shockwave-flash\", \"sit\": \"application/x-stuffit\",\n    \"tcl\": \"application/x-tcl\", \"tk\": \"application/x-tcl\", \"der\": \"application/x-x509-ca-cert\",\n    \"pem\": \"application/x-x509-ca-cert\", \"crt\": \"application/x-x509-ca-cert\",\n    \"xpi\": \"application/x-xpinstall\", \"xhtml\": \"application/xhtml+xml\", \"xspf\": \"application/xspf+xml\",\n    \"zip\": \"application/zip\", \"bin\": \"application/octet-stream\", \"exe\": \"application/octet-stream\",\n    \"dll\": \"application/octet-stream\", \"deb\": \"application/octet-stream\", \"dmg\": \"application/octet-stream\",\n    \"iso\": \"application/octet-stream\", \"img\": \"application/octet-stream\", \"msi\": \"application/octet-stream\",\n    \"msp\": \"application/octet-stream\", \"msm\": \"application/octet-stream\", \"mid\": \"audio/midi\",\n    \"midi\": \"audio/midi\", \"kar\": \"audio/midi\", \"mp3\": \"audio/mp3\", \"ogg\": \"audio/ogg\", \"m4a\": \"audio/x-m4a\",\n    \"ra\": \"audio/x-realaudio\", \"3gpp\": \"video/3gpp\", \"3gp\": \"video/3gpp\", \"ts\": \"video/mp2t\",\n    \"mp4\": \"video/mp4\", \"mpeg\": \"video/mpeg\", \"mpg\": \"video/mpeg\", \"mov\": \"video/quicktime\",\n    \"webm\": \"video/webm\", \"flv\": \"video/x-flv\", \"m4v\": \"video/x-m4v\", \"mng\": \"video/x-mng\",\n    \"asx\": \"video/x-ms-asf\", \"asf\": \"video/x-ms-asf\", \"wmv\": \"video/x-ms-wmv\", \"avi\": \"video/x-msvideo\",\n    \"wav\": \"audio/wav\", \"flac\": \"audio/flac\", \"aac\": \"audio/aac\", \"opus\": \"audio/opus\",\n    \"csv\": \"text/csv\", \"tsv\": \"text/tab-separated-values\", \"ics\": \"text/calendar\",\n}\n\n# 如果是音频文件并且有range请求，处理部分内容\naudio_types = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'opus', 'm4a']\n\n\nclass FileSerializer(serializers.Serializer):\n    file = UploadedFileField(required=True, label=_('file'))\n    meta = serializers.JSONField(required=False, allow_null=True)\n    source_id = serializers.CharField(\n        required=False, allow_null=True, label=_('source id'), default=FileSourceType.TEMPORARY_120_MINUTE.value\n    )\n    source_type = serializers.ChoiceField(\n        choices=FileSourceType.choices, required=False, allow_null=True, label=_('source type'),\n        default=FileSourceType.TEMPORARY_120_MINUTE\n    )\n\n    def upload(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        meta = self.data.get('meta', None)\n        if not meta:\n            meta = {'debug': True}\n        file_id = meta.get('file_id', uuid.uuid7())\n        file = File(\n            id=file_id,\n            file_name=self.data.get('file').name,\n            meta=meta,\n            source_id=self.data.get('source_id') or FileSourceType.TEMPORARY_120_MINUTE.value,\n            source_type=self.data.get('source_type') or FileSourceType.TEMPORARY_120_MINUTE\n        )\n        file.save(self.data.get('file').read())\n        return f'./oss/file/{file_id}'\n\n    class Operate(serializers.Serializer):\n        id = serializers.UUIDField(required=True)\n        http_range = serializers.CharField(\n            required=False, allow_blank=True, allow_null=True, label=_('HTTP Range'),\n            help_text=_('HTTP Range header for partial content requests, e.g., \"bytes=0-1023\"')\n        )\n\n        def get(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            file_id = self.data.get('id')\n            file = QuerySet(File).filter(id=file_id).first()\n            if file is None:\n                raise NotFound404(404, _('File not found'))\n            file_type = file.file_name.split(\".\")[-1].lower()\n            content_type = mime_types.get(file_type, 'application/octet-stream')\n            encoded_filename = urllib.parse.quote(file.file_name)\n            # 获取文件内容\n            file_bytes = file.get_bytes()\n            file_size = len(file_bytes)\n\n            response = None\n            if file_type in audio_types and self.data.get('http_range'):\n                response = self.handle_audio(file_size, file_bytes, content_type, encoded_filename)\n            if response:\n                return response\n\n            # 对于非范围请求或其他类型文件，返回完整内容\n            headers = {\n                'Content-Type': content_type,\n                'Content-Disposition': f'{\"inline\" if file_type == \"pdf\" else \"attachment\"}; filename={encoded_filename}'\n            }\n            return HttpResponse(\n                file_bytes,\n                status=200,\n                headers=headers\n            )\n\n        def handle_audio(self, file_size, file_bytes, content_type, encoded_filename):\n\n            # 解析range请求 (格式如 \"bytes=0-1023\")\n            range_match = re.match(r'bytes=(\\d+)-(\\d*)', self.data.get('http_range', ''))\n            if range_match:\n                start = int(range_match.group(1))\n                end = int(range_match.group(2)) if range_match.group(2) else file_size - 1\n\n                # 确保范围合法\n                end = min(end, file_size - 1)\n                length = end - start + 1\n\n                # 创建部分响应\n                response = HttpResponse(\n                    file_bytes[start:start + length],\n                    status=206,\n                    content_type=content_type\n                )\n\n                # 设置部分内容响应头\n                response['Content-Range'] = f'bytes {start}-{end}/{file_size}'\n                response['Accept-Ranges'] = 'bytes'\n                response['Content-Length'] = str(length)\n                response['Content-Disposition'] = f'inline; filename={encoded_filename}'\n                return response\n\n        def delete(self):\n            self.is_valid(raise_exception=True)\n            file_id = self.data.get('id')\n            file = QuerySet(File).filter(id=file_id).first()\n            if file is not None:\n                file.delete()\n            return True\n\n\ndef get_url_content(url, application_id: str):\n    application = Application.objects.filter(id=application_id).first()\n    if application is None:\n        return AppApiException(500, _('Application does not exist'))\n    if not application.file_upload_enable:\n        return AppApiException(500, _('File upload is not enabled'))\n    file_limit = 50 * 1024 * 1024\n    if application.file_upload_setting and application.file_upload_setting.get('fileLimit'):\n        file_limit = application.file_upload_setting.get('fileLimit') * 1024 * 1024\n    parsed = validate_url(url)\n\n    response = requests.get(\n        url,\n        timeout=3,\n        allow_redirects=False\n    )\n    final_host = urlparse(response.url).hostname\n    if is_private_ip(final_host):\n        raise ValueError(\"Blocked unsafe redirect to internal host\")\n    # 判断文件大小\n    if int(response.headers.get('Content-Length', 0)) > file_limit:\n        raise AppApiException(500, _('File size exceeds limit'))\n    # 返回状态码 响应内容大小  响应的contenttype 还有字节流\n    content_type = response.headers.get('Content-Type', '')\n    # 根据内容类型决定如何处理\n    if 'text' in content_type or 'json' in content_type:\n        content = response.text\n    else:\n        # 二进制内容使用Base64编码\n        content = base64.b64encode(response.content).decode('utf-8')\n\n    return {\n        'status_code': response.status_code,\n        'Content-Length': response.headers.get('Content-Length', 0),\n        'Content-Type': content_type,\n        'content': content,\n    }\n\n\ndef is_private_ip(host: str) -> bool:\n    \"\"\"检测 IP 是否属于内网、环回、云 metadata 的危险地址\"\"\"\n    try:\n        ip = ipaddress.ip_address(socket.gethostbyname(host))\n        return (\n                ip.is_private or\n                ip.is_loopback or\n                ip.is_reserved or\n                ip.is_link_local or\n                ip.is_multicast\n        )\n    except Exception:\n        return True\n\n\ndef validate_url(url: str):\n    \"\"\"验证 URL 是否安全\"\"\"\n    if not url:\n        raise ValueError(\"URL is required\")\n\n    parsed = urlparse(url)\n\n    # 仅允许 http / https\n    if parsed.scheme not in (\"http\", \"https\"):\n        raise ValueError(\"Only http and https are allowed\")\n\n    host = parsed.hostname\n    # 域名不能为空\n    if not host:\n        raise ValueError(\"Invalid URL\")\n\n    # 禁止访问内部、保留、环回、云 metadata\n    if is_private_ip(host):\n        raise ValueError(\"Access to internal IP addresses is blocked\")\n\n    return parsed\n"
  },
  {
    "path": "apps/oss/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/oss/urls.py",
    "content": "from django.urls import path\n\nfrom . import views\n\napp_name = 'oss'\n\nurlpatterns = [\n    path('oss/file', views.FileView.as_view()),\n    path('oss/get_url/<str:application_id>', views.GetUrlView.as_view()),\n]\n"
  },
  {
    "path": "apps/oss/views/__init__.py",
    "content": "from .file import *"
  },
  {
    "path": "apps/oss/views/file.py",
    "content": "# coding=utf-8\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.parsers import MultiPartParser\nfrom rest_framework.views import APIView\nfrom rest_framework.views import Request\nfrom common.auth import TokenAuth, AllTokenAuth\nfrom common.log.log import log\nfrom common.result import result\nfrom knowledge.api.file import FileUploadAPI, FileGetAPI\nfrom oss.serializers.file import FileSerializer, get_url_content\n\n\nclass FileRetrievalView(APIView):\n    @extend_schema(\n        methods=['GET'],\n        summary=_('Get file'),\n        description=_('Get file'),\n        operation_id=_('Get file'),  # type: ignore\n        parameters=FileGetAPI.get_parameters(),\n        responses=FileGetAPI.get_response(),\n        tags=[_('File')]  # type: ignore\n    )\n    def get(self, request: Request, file_id: str):\n        return FileSerializer.Operate(data={\n            'id': file_id,\n            'http_range': request.headers.get('Range', ''),\n        }).get()\n\n\nclass FileView(APIView):\n    authentication_classes = [AllTokenAuth]\n    parser_classes = [MultiPartParser]\n\n    @extend_schema(\n        methods=['POST'],\n        summary=_('Upload file'),\n        description=_('Upload file'),\n        operation_id=_('Upload file'),  # type: ignore\n        parameters=FileUploadAPI.get_parameters(),\n        request=FileUploadAPI.get_request(),\n        responses=FileUploadAPI.get_response(),\n        tags=[_('File')]  # type: ignore\n    )\n    @log(menu='file', operate='Upload file')\n    def post(self, request: Request):\n        return result.success(FileSerializer(data={\n            'file': request.FILES.get('file'),\n            'source_id': request.data.get('source_id'),\n            'source_type': request.data.get('source_type'),\n        }).upload())\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['DELETE'],\n            summary=_('Delete file'),\n            description=_('Delete file'),\n            operation_id=_('Delete file'),  # type: ignore\n            parameters=FileGetAPI.get_parameters(),\n            responses=FileGetAPI.get_response(),\n            tags=[_('File')]  # type: ignore\n        )\n        @log(menu='file', operate='Delete file')\n        def delete(self, request: Request, file_id: str):\n            return result.success(FileSerializer.Operate(data={'id': file_id}).delete())\n\n\nclass GetUrlView(APIView):\n    authentication_classes = [AllTokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        summary=_('Get url'),\n        description=_('Get url'),\n        operation_id=_('Get url'),  # type: ignore\n        tags=[_('Chat')]  # type: ignore\n    )\n    def get(self, request: Request, application_id: str):\n        url = request.query_params.get('url')\n        result_data = get_url_content(url, application_id)\n        return result.success(result_data)\n"
  },
  {
    "path": "apps/oss/views.py",
    "content": "from django.shortcuts import render\n\n# Create your views here.\n"
  },
  {
    "path": "apps/system_manage/__init__.py",
    "content": ""
  },
  {
    "path": "apps/system_manage/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "apps/system_manage/api/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py\n    @date：2025/4/28 17:05\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/system_manage/api/email_setting.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： workspace_user_resource_permission.py\n    @date：2025/4/28 18:13\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer\nfrom system_manage.serializers.email_setting import EmailSettingSerializer\nfrom system_manage.serializers.user_resource_permission import UserResourcePermissionResponse, \\\n    UpdateUserResourcePermissionRequest\n\n\nclass EmailResponse(ResultSerializer):\n    def get_data(self):\n        return EmailSettingSerializer.Create()\n\n\nclass EmailSettingAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return EmailSettingSerializer.Create()\n\n    @staticmethod\n    def get_response():\n        return EmailResponse\n"
  },
  {
    "path": "apps/system_manage/api/resource_mapping.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： resource_mapping.py\n    @date：2025/12/26 14:07\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\nfrom rest_framework import serializers\n\nfrom common.mixins.api_mixin import APIMixin\n\n\nclass ResourceMappingResponse(serializers.Serializer):\n    id = serializers.UUIDField(required=True, label=\"主键id\")\n    target_id = serializers.CharField(required=True, label=\"被关联资源名称\")\n    target_type = serializers.CharField(required=True, label=\"被关联资源类型\")\n    source_id = serializers.CharField(required=True, label=\"关联资源Id\")\n    source_type = serializers.CharField(required=True, label=\"关联资源类型\")\n    name = serializers.CharField(required=True, label=\"名称\")\n    desc = serializers.CharField(required=False, label=\"描述\")\n    user_id = serializers.UUIDField(required=True, label=\"主键id\")\n\n\nclass ResourceMappingAPI(APIMixin):\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source\",\n                description=\"资源类型\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source_id\",\n                description=\"资源id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"current_page\",\n                description=_(\"Current page\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"page_size\",\n                description=_(\"Page size\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"resource_name\",\n                description=\"名称\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False\n            ),\n\n        ]\n\n    @staticmethod\n    def get_response():\n        return ResourceMappingResponse(many=True)\n"
  },
  {
    "path": "apps/system_manage/api/system.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： system_setting.py\n    @date：2025/6/4 16:34\n    @desc:\n\"\"\"\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer\nfrom system_manage.serializers.system import SystemProfileResponseSerializer\n\n\nclass SystemProfileResult(ResultSerializer):\n    def get_data(self):\n        return SystemProfileResponseSerializer()\n\n\nclass SystemProfileAPI(APIMixin):\n    @staticmethod\n    def get_response():\n        return SystemProfileResult\n"
  },
  {
    "path": "apps/system_manage/api/user_resource_permission.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： workspace_user_resource_permission.py\n    @date：2025/4/28 18:13\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\nfrom rest_framework import serializers\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, ResultPageSerializer, PageDataResponse\nfrom system_manage.serializers.user_resource_permission import ResourceUserPermissionEditRequest, UpdateTeamMemberItemPermissionSerializer\n\n\nclass UserResourcePermissionResponse0(serializers.Serializer):\n    id = serializers.UUIDField(required=True, label=\"主键id\")\n    name = serializers.CharField(required=True, label=\"资源名称\")\n    auth_target_type = serializers.CharField(required=True, label=\"授权资源\")\n    user_id = serializers.UUIDField(required=True, label=\"用户id\")\n    icon = serializers.CharField(required=True, label=\"资源图标\")\n    auth_type = serializers.CharField(required=True, label=\"授权类型\")\n    permission = serializers.ChoiceField(required=False, allow_null=True, allow_blank=True,\n                                         choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'],\n                                         label=_('permission'))\n\nclass NewAPIUserResourcePermissionResponse(ResultSerializer):\n    def get_data(self):\n        return UserResourcePermissionResponse0(many=True)\n\nclass NewAPIUserResourcePermissionPageResponse(ResultPageSerializer):\n\n    def get_data(self):\n        return UserResourcePermissionResponse0(many=True)\n\nclass UserResourcePermissionAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"user_id\",\n                description=\"用户id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"name\",\n                description=\"名称\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False\n            ),\n            OpenApiParameter(\n                name=\"permission\",\n                description=\"权限\",\n                type=OpenApiTypes.STR,\n                location='query',\n                many=True,\n                required=False\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return NewAPIUserResourcePermissionResponse\n\n\nclass EditUserResourcePermissionAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"user_id\",\n                description=\"用户id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"resource\",\n                description=\"资源类型\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return UpdateTeamMemberItemPermissionSerializer(many=True)\n\n    @staticmethod\n    def get_response():\n        return NewAPIUserResourcePermissionResponse\n\n\nclass ResourceUserPermissionResponse(serializers.Serializer):\n    id = serializers.CharField(required=True, label=_('user id'))\n    nick_name = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('nick_name'))\n    username = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('username'))\n    permission = serializers.CharField(required=True, label=_('permission'))\n\n\nclass APIResourceUserPermissionResponse(ResultSerializer):\n    def get_data(self):\n        return ResourceUserPermissionResponse(many=True)\n\n\nclass ResourceUserPermissionAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"target\",\n                description=\"资源id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"resource\",\n                description=\"资源类型\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"username\",\n                description=\"用户名\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False\n            ),\n            OpenApiParameter(\n                name=\"nick_name\",\n                description=\"姓名\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False\n            ),\n            OpenApiParameter(\n                name=\"permission\",\n                description=\"权限\",\n                type=OpenApiTypes.STR,\n                location='query',\n                many=True,\n                required=False\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return APIResourceUserPermissionResponse\n\nclass UserResourcePermissionPageAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"user_id\",\n                description=\"用户id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"resource\",\n                description=\"资源类型\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"current_page\",\n                description=_(\"Current page\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"page_size\",\n                description=_(\"Page size\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"name\",\n                description=\"资源名称\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False\n            ),\n            OpenApiParameter(\n                name=\"permission[]\",\n                description=\"权限\",\n                type=OpenApiTypes.STR,\n                location='query',\n                many=True,\n                required=False\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return NewAPIUserResourcePermissionPageResponse\n\n\nclass APIResourceUserPermissionPageResponse(ResultPageSerializer):\n    def get_data(self):\n        return PageDataResponse(ResourceUserPermissionResponse(many=True))\n\n\nclass ResourceUserPermissionPageAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"target\",\n                description=\"资源id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"resource\",\n                description=\"资源类型\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"current_page\",\n                description=_(\"Current page\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"page_size\",\n                description=_(\"Page size\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"username\",\n                description=\"用户名\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False\n            ),\n            OpenApiParameter(\n                name=\"nick_name\",\n                description=\"姓名\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False\n            ),\n            OpenApiParameter(\n                name=\"permission[]\",\n                description=\"权限\",\n                type=OpenApiTypes.STR,\n                location='query',\n                many=True,\n                required=False\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return APIResourceUserPermissionPageResponse\n\n\n\nclass ResourceUserPermissionEditAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"target\",\n                description=\"资源id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n            OpenApiParameter(\n                name=\"resource\",\n                description=\"资源类型\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True\n            ),\n        ]\n    @staticmethod\n    def get_request():\n        return ResourceUserPermissionEditRequest(required=True, many=True, label=_('users_permission'))\n\n    @staticmethod\n    def get_response():\n        return APIResourceUserPermissionResponse()"
  },
  {
    "path": "apps/system_manage/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass SystemManageConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'system_manage'\n"
  },
  {
    "path": "apps/system_manage/migrations/0001_initial.py",
    "content": "# Generated by Django 5.2.4 on 2025-07-14 03:50\n\nimport common.encoder.encoder\nimport django.contrib.postgres.fields\nimport django.db.models.deletion\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('users', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ChatUser',\n            fields=[\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('email', models.EmailField(blank=True, db_index=True, max_length=254, null=True, verbose_name='邮箱')),\n                ('phone', models.CharField(default='', max_length=20, verbose_name='电话')),\n                ('nick_name', models.CharField(db_index=True, max_length=150, unique=True, verbose_name='昵称')),\n                ('username', models.CharField(db_index=True, max_length=150, unique=True, verbose_name='用户名')),\n                ('password', models.CharField(max_length=150, verbose_name='密码')),\n                ('source', models.CharField(db_index=True, default='LOCAL', max_length=10, verbose_name='来源')),\n                ('is_active', models.BooleanField(db_index=True, default=True)),\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, null=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, null=True, verbose_name='修改时间')),\n            ],\n            options={\n                'db_table': 'chat_user',\n            },\n        ),\n        migrations.CreateModel(\n            name='Log',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('menu', models.CharField(max_length=128, verbose_name='操作菜单')),\n                ('operate', models.CharField(db_index=True, max_length=128, verbose_name='操作')),\n                ('operation_object', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='操作对象')),\n                ('user', models.JSONField(default=dict, verbose_name='用户信息')),\n                ('status', models.IntegerField(db_index=True, verbose_name='状态')),\n                ('ip_address', models.CharField(max_length=128, verbose_name='ip地址')),\n                ('details', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder, verbose_name='详情')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n            ],\n            options={\n                'db_table': 'log',\n            },\n        ),\n        migrations.CreateModel(\n            name='SystemSetting',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('type', models.IntegerField(choices=[(0, '邮箱'), (1, '私钥秘钥')], default=0, primary_key=True, serialize=False, verbose_name='设置类型')),\n                ('meta', models.JSONField(default=dict, verbose_name='配置数据')),\n            ],\n            options={\n                'db_table': 'system_setting',\n            },\n        ),\n        migrations.CreateModel(\n            name='UserGroup',\n            fields=[\n                ('id', models.CharField(default=uuid_utils.compat.uuid7, editable=False, max_length=128, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('name', models.CharField(db_index=True, max_length=150, unique=True, verbose_name='名称')),\n            ],\n            options={\n                'db_table': 'user_group',\n            },\n        ),\n        migrations.CreateModel(\n            name='UserGroupRelation',\n            fields=[\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system_manage.usergroup', verbose_name='用户组')),\n                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system_manage.chatuser', verbose_name='用户')),\n            ],\n            options={\n                'db_table': 'user_group_relation',\n            },\n        ),\n        migrations.CreateModel(\n            name='WorkspaceUserResourcePermission',\n            fields=[\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=128, verbose_name='工作空间id')),\n                ('auth_target_type', models.CharField(choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用'), ('TOOL', '工具'), ('MODEL', '模型')], db_index=True, default='KNOWLEDGE', max_length=128, verbose_name='授权目标')),\n                ('target', models.UUIDField(db_index=True, verbose_name='知识库/应用id')),\n                ('auth_type', models.CharField(choices=[('ROLE', 'Role'), ('RESOURCE_PERMISSION_GROUP', 'Resource Permission Group')], db_default='ROLE', db_index=True, default=False, verbose_name='授权类型')),\n                ('permission_list', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, choices=[('VIEW', 'View'), ('MANAGE', 'Manage'), ('ROLE', 'Role')], default='VIEW', max_length=256), default=list, size=None, verbose_name='权限列表')),\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.user', verbose_name='工作空间下的用户')),\n            ],\n            options={\n                'db_table': 'workspace_user_resource_permission',\n            },\n        ),\n        migrations.CreateModel(\n            name='ResourceChatUserGroupAuthorize',\n            fields=[\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, null=True, verbose_name='工作空间id')),\n                ('resource_id', models.UUIDField(db_index=True, verbose_name='资源id')),\n                ('resource_type', models.CharField(choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用')], db_index=True, verbose_name='资源类型')),\n                ('is_auth', models.BooleanField(verbose_name='是否授权')),\n                ('user_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system_manage.usergroup', verbose_name='用户组')),\n            ],\n            options={\n                'db_table': 'resource_chat_user_group_authorize',\n                'unique_together': {('user_group_id', 'resource_type', 'resource_id')},\n            },\n        ),\n        migrations.CreateModel(\n            name='ResourceChatUserAuthorize',\n            fields=[\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, null=True, verbose_name='工作空间id')),\n                ('resource_id', models.UUIDField(db_index=True, verbose_name='资源id')),\n                ('resource_type', models.CharField(choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用')], db_index=True, verbose_name='资源类型')),\n                ('is_auth', models.BooleanField(verbose_name='是否授权')),\n                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system_manage.chatuser', verbose_name='用户')),\n                ('user_group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system_manage.usergroup', verbose_name='用户组')),\n            ],\n            options={\n                'db_table': 'resource_chat_user_authorize',\n                'unique_together': {('user_group_id', 'resource_type', 'resource_id', 'user_id')},\n            },\n        ),\n    ]\n"
  },
  {
    "path": "apps/system_manage/migrations/0002_refresh_collation_reindex.py",
    "content": "import logging\n\nimport psycopg\nfrom django.db import migrations\n\nfrom maxkb.const import CONFIG\n\n\ndef get_connect(db_name):\n    conn_params = {\n        \"dbname\": db_name,\n        \"user\": CONFIG.get('DB_USER'),\n        \"password\": CONFIG.get('DB_PASSWORD'),\n        \"host\": CONFIG.get('DB_HOST'),\n        \"port\": CONFIG.get('DB_PORT')\n    }\n    # 建立连接\n    connect = psycopg.connect(**conn_params)\n    return connect\n\n\ndef sql_execute(conn, reindex_sql: str, alter_database_sql: str):\n    \"\"\"\n    执行一条sql\n    @param reindex_sql:\n    @param conn:\n    @param alter_database_sql:\n    \"\"\"\n    conn.autocommit = True\n    with conn.cursor() as cursor:\n        cursor.execute(reindex_sql, [])\n        cursor.execute(alter_database_sql, [])\n        cursor.close()\n\ndef re_index(apps, schema_editor):\n    app_db_name = CONFIG.get('DB_NAME')\n    try:\n        re_index_database(app_db_name)\n    except Exception as e:\n        logging.error(f'reindex database {app_db_name}发送错误:{str(e)}')\n    try:\n        re_index_database('root')\n    except Exception as e:\n        logging.error(f'reindex database root 发送错误:{str(e)}')\n\n\ndef re_index_database(db_name):\n    db_conn = get_connect(db_name)\n    sql_execute(db_conn, f'REINDEX DATABASE \"{db_name}\";', f'ALTER DATABASE \"{db_name}\" REFRESH COLLATION VERSION;')\n    db_conn.close()\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        (\"system_manage\", \"0001_initial\"),\n    ]\n\n    operations = [\n        migrations.RunPython(re_index, atomic=False)\n    ]\n"
  },
  {
    "path": "apps/system_manage/migrations/0003_alter_workspaceuserresourcepermission_target.py",
    "content": "# Generated by Django 5.2.6 on 2025-10-11 02:54\nfrom concurrent.futures import ThreadPoolExecutor\nfrom functools import reduce\n\nfrom django.db import migrations, models\nfrom django.db.models import QuerySet\n\nfrom common.constants.permission_constants import WorkspaceUserRoleMapping\nfrom common.utils.common import group_by\n\n\ndef workspace_user_role_mapping_model_exists(workspace_user_role_mapping_model):\n    try:\n        QuerySet(workspace_user_role_mapping_model).first()\n    except Exception as e:\n        return False\n    return False\n\ndef delete_auth(apps,folder_model):\n    workspace_user_resource_permission_model = apps.get_model('system_manage', 'WorkspaceUserResourcePermission')\n    QuerySet(workspace_user_resource_permission_model).filter(target__in=QuerySet(folder_model).values_list('id')).delete()\n\n\ndef get_workspace_user_resource_permission_list(apps, auth_target_type, workspace_user_role_mapping_model_workspace_dict,\n                                                folder_model):\n    workspace_user_resource_permission_model = apps.get_model('system_manage', 'WorkspaceUserResourcePermission')\n    return reduce(lambda x, y: [*x, *y], [\n        [workspace_user_resource_permission_model(target=f.id, workspace_id=f.workspace_id, user_id=wurm.user_id,\n                                         auth_target_type=auth_target_type, auth_type=\"RESOURCE_PERMISSION_GROUP\",\n                                         permission_list=['VIEW','MANAGE'] if wurm.user_id == f.user_id else ['VIEW']) for wurm in\n         workspace_user_role_mapping_model_workspace_dict.get(f.workspace_id, [])] for f in\n        QuerySet(folder_model).all()], [])\n\n\ndef auth_folder(apps, schema_editor):\n    from common.database_model_manage.database_model_manage import DatabaseModelManage\n    DatabaseModelManage.init()\n\n    user_model = apps.get_model('users', 'User')\n    application_folder_model = apps.get_model('application', 'ApplicationFolder')\n    knowledge_folder_model = apps.get_model('knowledge', 'KnowledgeFolder')\n    tool_folder_model = apps.get_model('tools', 'ToolFolder')\n    workspace_user_resource_permission_model = apps.get_model('system_manage', 'WorkspaceUserResourcePermission')\n\n    workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n\n    with ThreadPoolExecutor(max_workers=3) as executor:\n        future = executor.submit(workspace_user_role_mapping_model_exists, workspace_user_role_mapping_model)\n        exists = future.result()\n        if not exists:\n            workspace_user_role_mapping_model = None\n\n    if workspace_user_role_mapping_model is None:\n        workspace_user_role_mapping_model_workspace_dict = {\n            'default': [WorkspaceUserRoleMapping('default', '', u.id) for u in QuerySet(user_model).all()]}\n    else:\n        workspace_user_role_mapping_model_workspace_dict = group_by(\n            [v for v in {str(wurm.user_id) + str(wurm.workspace_id): wurm for wurm in\n                         QuerySet(workspace_user_role_mapping_model)}.values()],\n            lambda item: item.workspace_id)\n\n    workspace_user_resource_permission_list = get_workspace_user_resource_permission_list(apps,\"APPLICATION\",\n                                                                                          workspace_user_role_mapping_model_workspace_dict,\n                                                                                          application_folder_model)\n\n    workspace_user_resource_permission_list += get_workspace_user_resource_permission_list(apps,\"TOOL\",\n                                                                                           workspace_user_role_mapping_model_workspace_dict,\n                                                                                           tool_folder_model)\n\n    workspace_user_resource_permission_list += get_workspace_user_resource_permission_list(apps,\"KNOWLEDGE\",\n                                                                                           workspace_user_role_mapping_model_workspace_dict,\n                                                                                           knowledge_folder_model)\n    delete_auth(apps,application_folder_model)\n    delete_auth(apps,knowledge_folder_model)\n    delete_auth(apps,tool_folder_model)\n    QuerySet(workspace_user_resource_permission_model).bulk_create(workspace_user_resource_permission_list)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        ('system_manage', '0002_refresh_collation_reindex'),\n        ('tools', '0001_initial'),\n        ('application', '0001_initial'),\n        ('knowledge', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='workspaceuserresourcepermission',\n            name='target',\n            field=models.CharField(db_index=True, max_length=128, verbose_name='知识库/应用id'),\n        ),\n        migrations.RunPython(auth_folder, atomic=False)\n    ]\n"
  },
  {
    "path": "apps/system_manage/migrations/0004_alter_systemsetting_type_and_more.py",
    "content": "# Generated by Django 5.2.7 on 2025-10-16 03:21\n\nfrom django.db import migrations, models\nfrom django.db.models.functions import RowNumber\n\n\ndef remove_duplicates(apps, schema_editor):\n    from django.db.models import Window, F\n    workspace_user_resource_permission_model = apps.get_model('system_manage', 'WorkspaceUserResourcePermission')\n\n    duplicates = workspace_user_resource_permission_model.objects.annotate(\n        row_num=Window(\n            expression=RowNumber(),\n            partition_by=[F('workspace_id'), F('user'), F('auth_target_type'), F('target')],\n            order_by=[F('create_time').desc()],\n        )\n    ).filter(row_num__gt=1)\n\n    ids_to_delete = list(duplicates.values_list('id', flat=True))\n    if ids_to_delete:\n        workspace_user_resource_permission_model.objects.filter(id__in=ids_to_delete).delete()\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        ('system_manage', '0003_alter_workspaceuserresourcepermission_target'),\n        ('users', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.RunPython(remove_duplicates),\n        migrations.AlterUniqueTogether(\n            name='workspaceuserresourcepermission',\n            unique_together={('workspace_id', 'user', 'auth_target_type', 'target')},\n        ),\n        migrations.AlterField(\n            model_name='systemsetting',\n            name='type',\n            field=models.IntegerField(choices=[(0, '邮箱'), (1, '私钥秘钥'), (2, '日志清理时间')], default=0,\n                                      primary_key=True, serialize=False, verbose_name='设置类型'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/system_manage/migrations/0005_resourcemapping.py",
    "content": "# Generated by Django 5.2.8 on 2025-12-19 09:37\nfrom concurrent.futures import ThreadPoolExecutor\n\nimport uuid_utils.compat\nfrom django.db import migrations, models\nfrom django.db.models import QuerySet\n\nfrom knowledge.models import Knowledge\n\n\ndef get_initialization_resource_mapping():\n    from django.db.models import QuerySet\n    from application.flow.tools import get_workflow_resource, get_node_handle_callback, \\\n        get_instance_resource\n    from system_manage.models.resource_mapping import ResourceType\n    from application.models import Application\n    from knowledge.models import KnowledgeWorkflow\n    from application.flow.tools import application_instance_field_call_dict, knowledge_instance_field_call_dict\n    from application.models.application import ApplicationKnowledgeMapping\n    from system_manage.models.resource_mapping import ResourceMapping\n    resource_mapping_list = []\n    ids = list(Application.objects.values_list('id', flat=True))\n    for app_id in ids:\n        try:\n            application = Application.objects.get(id=app_id)\n            workflow_mapping = get_workflow_resource(application.work_flow,\n                                                     get_node_handle_callback(ResourceType.APPLICATION,\n                                                                              application.id))\n            instance_mapping = get_instance_resource(application, ResourceType.APPLICATION, str(application.id),\n                                                     application_instance_field_call_dict)\n            resource_mapping_list += workflow_mapping\n            resource_mapping_list += instance_mapping\n        except:\n            pass\n    knowledge_ids = list(Knowledge.objects.values_list('id', flat=True))\n    for knowledge_id in knowledge_ids:\n        try:\n            knowledge = Knowledge.objects.get(id=knowledge_id)\n            if knowledge.type == 4:\n                knowledge_workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=knowledge_id).first()\n                if knowledge_workflow:\n                    workflow_mapping = get_workflow_resource(knowledge_workflow.work_flow,\n                                                             get_node_handle_callback(ResourceType.KNOWLEDGE,\n                                                                                      str(knowledge_workflow.knowledge_id)))\n                    resource_mapping_list += workflow_mapping\n            instance_mapping = get_instance_resource(knowledge, ResourceType.KNOWLEDGE, str(knowledge.id),\n                                                     knowledge_instance_field_call_dict)\n\n            resource_mapping_list += instance_mapping\n        except:\n            pass\n    application_knowledge_mapping = [\n        ResourceMapping(source_type=ResourceType.APPLICATION, target_type=ResourceType.KNOWLEDGE,\n                        source_id=str(akm.application_id), target_id=str(akm.knowledge_id)) for akm in\n        QuerySet(ApplicationKnowledgeMapping).all()]\n    resource_mapping_list += application_knowledge_mapping\n    return {(str(item.target_type) + str(item.target_id) + str(item.source_type) + str(item.source_id)): item for item\n            in resource_mapping_list}.values()\n\n\ndef resource_mapping(apps, schema_editor):\n    from system_manage.models.resource_mapping import ResourceMapping\n    with ThreadPoolExecutor(max_workers=3) as executor:\n        future = executor.submit(get_initialization_resource_mapping)\n        resource_mapping_list = future.result()\n        QuerySet(ResourceMapping).bulk_create(resource_mapping_list)\n\n\nclass Migration(migrations.Migration):\n    dependencies = [\n        ('system_manage', '0004_alter_systemsetting_type_and_more'),\n        ('knowledge', '0007_remove_knowledgeworkflowversion_workflow_and_more'),\n        ('application', '0003_application_stt_model_params_setting_and_more'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ResourceMapping',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id',\n                 models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False,\n                                  verbose_name='主键id')),\n                ('source_type', models.CharField(\n                    choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用'), ('TOOL', '工具'), ('MODEL', '模型')],\n                    db_index=True, verbose_name='关联资源类型')),\n                ('target_type', models.CharField(\n                    choices=[('KNOWLEDGE', '知识库'), ('APPLICATION', '应用'), ('TOOL', '工具'), ('MODEL', '模型')],\n                    db_index=True, verbose_name='被关联资源类型')),\n                ('source_id', models.CharField(db_index=True, max_length=128, verbose_name='关联资源id')),\n                ('target_id', models.CharField(db_index=True, max_length=128, verbose_name='被关联资源id')),\n            ],\n            options={\n                'db_table': 'resource_mapping',\n            },\n        ),\n        migrations.RunPython(resource_mapping, atomic=False)\n    ]\n"
  },
  {
    "path": "apps/system_manage/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "apps/system_manage/models/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/4/16 18:23\n    @desc:\n\"\"\"\nfrom .workspace_user_permission import *\nfrom .system_setting import *\nfrom .log_management import *\nfrom .chat_user import *"
  },
  {
    "path": "apps/system_manage/models/chat_user.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： user.py\n    @date：2025/4/14 10:20\n    @desc:\n\"\"\"\nimport uuid_utils.compat as uuid\nfrom django.db import models\n\nfrom common.constants.permission_constants import Group\n\n\nclass ChatUser(models.Model):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    email = models.EmailField(null=True, blank=True, verbose_name=\"邮箱\", db_index=True)\n    phone = models.CharField(max_length=20, verbose_name=\"电话\", default=\"\")\n    nick_name = models.CharField(max_length=150, verbose_name=\"昵称\", unique=True, db_index=True)\n    username = models.CharField(max_length=150, unique=True, verbose_name=\"用户名\", db_index=True)\n    password = models.CharField(max_length=150, verbose_name=\"密码\")\n    source = models.CharField(max_length=10, verbose_name=\"来源\", default=\"LOCAL\", db_index=True)\n    is_active = models.BooleanField(default=True, db_index=True)\n    create_time = models.DateTimeField(verbose_name=\"创建时间\", auto_now_add=True, null=True, db_index=True)\n    update_time = models.DateTimeField(verbose_name=\"修改时间\", auto_now=True, null=True, db_index=True)\n\n    USERNAME_FIELD = 'username'\n    REQUIRED_FIELDS = []\n\n    class Meta:\n        db_table = \"chat_user\"\n\n\nclass UserGroup(models.Model):\n    id = models.CharField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    name = models.CharField(max_length=150, verbose_name=\"名称\", unique=True, db_index=True)\n\n    class Meta:\n        db_table = \"user_group\"\n\n\nclass UserGroupRelation(models.Model):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    user = models.ForeignKey(ChatUser, on_delete=models.CASCADE, verbose_name=\"用户\")\n    group = models.ForeignKey(UserGroup, on_delete=models.CASCADE, verbose_name=\"用户组\")\n\n    class Meta:\n        db_table = \"user_group_relation\"\n\n\nclass ResourceType(models.TextChoices):\n    \"\"\"资源类型\"\"\"\n    KNOWLEDGE = Group.KNOWLEDGE.value, '知识库'\n    APPLICATION = Group.APPLICATION.value, '应用'\n\n\nclass ResourceChatUserAuthorize(models.Model):\n    \"\"\"\n    资源对话用户授权表\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True,\n                                    null=True)\n    user_group = models.ForeignKey(UserGroup, on_delete=models.CASCADE, verbose_name=\"用户组\")\n    user = models.ForeignKey(ChatUser, on_delete=models.CASCADE, verbose_name=\"用户\")\n    resource_id = models.UUIDField(max_length=128, verbose_name=\"资源id\", db_index=True)\n    resource_type = models.CharField(verbose_name=\"资源类型\", choices=ResourceType.choices, db_index=True)\n    is_auth = models.BooleanField(verbose_name=\"是否授权\")\n\n    class Meta:\n        db_table = \"resource_chat_user_authorize\"\n        unique_together = ('user_group_id', 'resource_type', 'resource_id', 'user_id')\n\n\nclass ResourceChatUserGroupAuthorize(models.Model):\n    \"\"\"\n    资源对话用户组授权表\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True,\n                                    null=True)\n    user_group = models.ForeignKey(UserGroup, on_delete=models.CASCADE, verbose_name=\"用户组\")\n    resource_id = models.UUIDField(max_length=128, verbose_name=\"资源id\", db_index=True)\n    resource_type = models.CharField(verbose_name=\"资源类型\", choices=ResourceType.choices, db_index=True)\n    is_auth = models.BooleanField(verbose_name=\"是否授权\")\n\n    class Meta:\n        db_table = \"resource_chat_user_group_authorize\"\n        unique_together = ('user_group_id', 'resource_type', 'resource_id')\n"
  },
  {
    "path": "apps/system_manage/models/log_management.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： log_management.py\n    @date：2025/6/4 14:15\n    @desc:\n\"\"\"\nimport uuid_utils.compat as uuid\n\nfrom django.db import models\n\nfrom common.encoder.encoder import SystemEncoder\nfrom common.mixins.app_model_mixin import AppModelMixin\n\n\nclass Log(AppModelMixin):\n    \"\"\"\n    审计日志\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n\n    menu = models.CharField(max_length=128, verbose_name=\"操作菜单\")\n\n    operate = models.CharField(max_length=128, verbose_name=\"操作\", db_index=True)\n\n    operation_object = models.JSONField(verbose_name=\"操作对象\", default=dict, encoder=SystemEncoder)\n\n    user = models.JSONField(verbose_name=\"用户信息\", default=dict)\n\n    status = models.IntegerField(verbose_name=\"状态\", db_index=True)\n\n    ip_address = models.CharField(max_length=128, verbose_name=\"ip地址\")\n\n    details = models.JSONField(verbose_name=\"详情\", default=dict, encoder=SystemEncoder)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n\n    class Meta:\n        db_table = \"log\"\n"
  },
  {
    "path": "apps/system_manage/models/resource_mapping.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： resource_mapping.py\n    @date：2025/12/19 15:41\n    @desc:\n\"\"\"\nfrom django.db import models\nimport uuid_utils.compat as uuid\n\nfrom common.constants.permission_constants import Group\nfrom common.mixins.app_model_mixin import AppModelMixin\n\n\nclass ResourceType(models.TextChoices):\n    KNOWLEDGE = Group.KNOWLEDGE.value, '知识库'\n    APPLICATION = Group.APPLICATION.value, '应用'\n    TOOL = Group.TOOL.value, '工具'\n    MODEL = Group.MODEL.value, '模型'\n\n\nclass ResourceMapping(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    source_type = models.CharField(verbose_name=\"关联资源类型\", choices=ResourceType.choices, db_index=True)\n    target_type = models.CharField(verbose_name=\"被关联资源类型\", choices=ResourceType.choices, db_index=True)\n    source_id = models.CharField(max_length=128, verbose_name=\"关联资源id\", db_index=True)\n    target_id = models.CharField(max_length=128, verbose_name=\"被关联资源id\", db_index=True)\n\n    class Meta:\n        db_table = \"resource_mapping\"\n"
  },
  {
    "path": "apps/system_manage/models/system_setting.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： system_management.py\n    @date：2024/3/19 13:47\n    @desc: 邮箱管理\n\"\"\"\n\nfrom django.db import models\n\nfrom common.mixins.app_model_mixin import AppModelMixin\n\n\nclass SettingType(models.IntegerChoices):\n    \"\"\"系统设置类型\"\"\"\n    EMAIL = 0, '邮箱'\n\n    RSA = 1, \"私钥秘钥\"\n\n    LOG = 2, \"日志清理时间\"\n\n\nclass SystemSetting(AppModelMixin):\n    \"\"\"\n     系统设置\n    \"\"\"\n    type = models.IntegerField(primary_key=True, verbose_name='设置类型', choices=SettingType.choices,\n                               default=SettingType.EMAIL)\n\n    meta = models.JSONField(verbose_name=\"配置数据\", default=dict)\n\n    class Meta:\n        db_table = \"system_setting\"\n"
  },
  {
    "path": "apps/system_manage/models/workspace_user_permission.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： workspace_permission.py\n    @date：2025/4/16 18:25\n    @desc:\n\"\"\"\n\nimport uuid_utils.compat as uuid\nfrom django.contrib.postgres.fields import ArrayField\nfrom django.db import models\n\nfrom common.constants.permission_constants import Group, ResourcePermissionGroup, ResourceAuthType, \\\n    ResourcePermissionRole, ResourcePermission\nfrom users.models import User\n\n\nclass AuthTargetType(models.TextChoices):\n    \"\"\"授权目标\"\"\"\n    KNOWLEDGE = Group.KNOWLEDGE.value, '知识库'\n    APPLICATION = Group.APPLICATION.value, '应用'\n    TOOL = Group.TOOL.value, '工具'\n    MODEL = Group.MODEL.value, '模型'\n\n\nclass WorkspaceUserResourcePermission(models.Model):\n    \"\"\"\n    工作空间用户资源权限表\n    用于管理当前工作空间是否有权限操作 某一个应用或者知识库\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n\n    workspace_id = models.CharField(max_length=128, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n\n    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=\"工作空间下的用户\")\n\n    auth_target_type = models.CharField(verbose_name='授权目标', max_length=128, choices=AuthTargetType.choices,\n                                        default=AuthTargetType.KNOWLEDGE, db_index=True)\n    # 授权的知识库或者应用的id\n    target = models.CharField(max_length=128, verbose_name=\"知识库/应用id\", db_index=True)\n\n    # 授权类型 如果是Role那么就是角色的权限  如果是PERMISSION\n    auth_type = models.CharField(default=False, verbose_name=\"授权类型\", choices=ResourceAuthType.choices,\n                                 db_default=ResourceAuthType.ROLE, db_index=True)\n    # 资源权限列表\n    permission_list = ArrayField(verbose_name=\"权限列表\",\n                                 default=list,\n                                 base_field=models.CharField(max_length=256,\n                                                             blank=True,\n                                                             choices=ResourcePermission.choices + ResourcePermissionRole.choices,\n                                                             default=ResourcePermission.VIEW))\n\n    create_time = models.DateTimeField(verbose_name=\"创建时间\", auto_now_add=True, db_index=True)\n\n    update_time = models.DateTimeField(verbose_name=\"修改时间\", auto_now=True, db_index=True)\n\n    class Meta:\n        db_table = \"workspace_user_resource_permission\"\n        unique_together = ('workspace_id', 'user', 'auth_target_type', 'target')\n"
  },
  {
    "path": "apps/system_manage/serializers/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py\n    @date：2025/4/28 17:05\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/system_manage/serializers/email_setting.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： system_setting.py\n    @date：2024/3/19 16:29\n    @desc:\n\"\"\"\nimport logging\n\nfrom django.core.mail.backends.smtp import EmailBackend\nfrom django.db.models import QuerySet\nfrom rest_framework import serializers\n\nfrom common.exception.app_exception import AppApiException\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.utils.logger import maxkb_logger\nfrom system_manage.models import SystemSetting, SettingType\n\n\nclass EmailSettingSerializer(serializers.Serializer):\n    @staticmethod\n    def one():\n        system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first()\n        if system_setting is None:\n            return {}\n        return system_setting.meta\n\n    class Create(serializers.Serializer):\n        email_host = serializers.CharField(required=True, label=_('SMTP host'))\n        email_port = serializers.IntegerField(required=True, label=_('SMTP port'))\n        email_host_user = serializers.CharField(required=True, label=_('Sender\\'s email'))\n        email_host_password = serializers.CharField(required=True, label=_('Password'))\n        email_use_tls = serializers.BooleanField(required=True, label=_('Whether to enable TLS'))\n        email_use_ssl = serializers.BooleanField(required=True, label=_('Whether to enable SSL'))\n        from_email = serializers.EmailField(required=True, label=_('Sender\\'s email'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            try:\n                EmailBackend(self.data.get(\"email_host\"),\n                             self.data.get(\"email_port\"),\n                             self.data.get(\"email_host_user\"),\n                             self.data.get(\"email_host_password\"),\n                             self.data.get(\"email_use_tls\"),\n                             False,\n                             self.data.get(\"email_use_ssl\")\n                             ).open()\n            except Exception as e:\n                maxkb_logger.error(f'Exception: {e}')\n                raise AppApiException(1004, _('Email verification failed'))\n\n        def update_or_save(self):\n            self.is_valid(raise_exception=True)\n            system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first()\n            if system_setting is None:\n                system_setting = SystemSetting(type=SettingType.EMAIL.value)\n            system_setting.meta = self.to_email_meta()\n            system_setting.save()\n            return system_setting.meta\n\n        def to_email_meta(self):\n            return {'email_host': self.data.get('email_host'),\n                    'email_port': self.data.get('email_port'),\n                    'email_host_user': self.data.get('email_host_user'),\n                    'email_host_password': self.data.get('email_host_password'),\n                    'email_use_tls': self.data.get('email_use_tls'),\n                    'email_use_ssl': self.data.get('email_use_ssl'),\n                    'from_email': self.data.get('from_email')\n                    }\n"
  },
  {
    "path": "apps/system_manage/serializers/resource_mapping_serializers.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： workspace_user_resource_permission.py\n    @date：2025/4/28 17:17\n    @desc:\n\"\"\"\nimport json\nimport os\n\nfrom django.db import models\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.db.search import native_page_search, get_dynamics_model\nfrom common.result import Page\nfrom common.utils.common import get_file_content\nfrom maxkb.conf import PROJECT_DIR\nfrom system_manage.models.resource_mapping import ResourceMapping\n\n\nclass ResourceMappingSerializer(serializers.Serializer):\n    resource = serializers.CharField(required=True, label=_('resource'))\n    resource_id = serializers.UUIDField(required=True, label=_('resource Id'))\n    resource_name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('resource Name'))\n    source_type = serializers.ListField(\n        label=_('source Type'),\n        child=serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('source Type')))\n    user_name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('creator'))\n    workspace_ids = serializers.CharField(required=False, label=_('workspace_ids'))\n\n    def get_query_set(self):\n        queryset = QuerySet(model=get_dynamics_model({\n            'sdc.name': models.CharField(),\n            'target_id': models.CharField(),\n            \"target_type\": models.CharField(),\n            \"u.username\": models.CharField(),\n            'rm.source_type': models.CharField(),\n            'workspace_id': models.CharField(),\n        }))\n\n        queryset = queryset.filter(target_id=self.data.get('resource_id'),\n                                   target_type=self.data.get('resource'))\n\n        if self.data.get('resource_name'):\n            queryset = queryset.filter(**{'sdc.name__icontains': self.data.get('resource_name')})\n        if self.data.get('user_name'):\n            queryset = queryset.filter(**{'u.username__icontains': self.data.get('user_name')})\n        if self.data.get(\"source_type\"):\n            queryset = queryset.filter(**{'rm.source_type__in': self.data.get('source_type')})\n        if self.data.get('workspace_ids') is not None and len(self.data.get('workspace_ids')) > 0:\n            workspace_ids = json.loads(self.data.get('workspace_ids'))\n            queryset = queryset.filter(**{'workspace_id__in': workspace_ids})\n\n        return queryset\n\n    @staticmethod\n    def is_x_pack_ee():\n        workspace_model = DatabaseModelManage.get_model(\"workspace_model\")\n        return workspace_model is not None\n\n    def page(self, current_page, page_size):\n        is_x_pack_ee = self.is_x_pack_ee()\n        return native_page_search(current_page, page_size, self.get_query_set(), get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", \"system_manage\",\n                         'sql', 'list_resource_mapping_ee.sql' if is_x_pack_ee else 'list_resource_mapping.sql')),\n                                  with_table_name=False)\n\n    def get_resource_count(self, result_list):\n        \"\"\"\n        获取资源映射计数\n        \"\"\"\n        if not result_list:\n            return result_list\n        is_paginated = isinstance(result_list, Page)\n\n        data_to_process = result_list.get('records') if is_paginated else result_list\n\n        if isinstance(data_to_process, list) and data_to_process:\n            # 提取ID列表，确保每个项目都是字典且包含'id'键\n            ids = [item['id'] for item in data_to_process\n                   if isinstance(item, dict) and 'id' in item and item['id']]\n\n            if ids:  # 只有在ids非空时才执行查询\n                mapping_counts = ResourceMapping.objects.filter(\n                    target_id__in=ids\n                ).values('target_id').annotate(\n                    count=models.Count('id')\n                )\n\n                # 构建目标ID到计数的映射\n                count_dict = {str(item['target_id']): item['count'] for item in mapping_counts}\n\n                # 为每个结果项添加资源计数\n                for model in data_to_process:\n                    if isinstance(model, dict) and 'id' in model:\n                        model_id = str(model['id'])\n                        model['resource_count'] = count_dict.get(model_id, 0)\n\n        return result_list\n"
  },
  {
    "path": "apps/system_manage/serializers/system.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： system.py\n    @date：2025/6/4 16:01\n    @desc:\n\"\"\"\nimport os\n\nfrom django.db import models\nfrom rest_framework import serializers\nfrom django.core.cache import cache\n\nfrom common.constants.cache_version import Cache_Version\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.utils.rsa_util import get_key_pair_by_sql\nfrom maxkb import settings\nfrom system_manage.models import SystemSetting\n\n\nclass SettingType(models.CharField):\n    # Community Edition\n    CE = \"CE\", \"社区\"\n    # Enterprise Edition\n    PE = \"PE\", \"专业版\"\n    # Professional Edition\n    EE = \"EE\", '企业版'\n\n\nclass SystemProfileResponseSerializer(serializers.Serializer):\n    version = serializers.CharField(required=True, label=\"version\")\n    edition = serializers.CharField(required=True, label=\"edition\")\n    license_is_valid = serializers.BooleanField(required=True, label=\"License is valid\")\n\n\nclass SystemProfileSerializer(serializers.Serializer):\n    @staticmethod\n    def profile():\n        version = os.environ.get('MAXKB_VERSION')\n        license_is_valid = DatabaseModelManage.get_model('license_is_valid') or (lambda: False)\n        return {'version': version, 'edition': settings.edition,\n                'license_is_valid': license_is_valid() if license_is_valid() is not None else False,\n                'ras': get_key_pair_by_sql().get('key')}\n"
  },
  {
    "path": "apps/system_manage/serializers/user_resource_permission.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： workspace_user_resource_permission.py\n    @date：2025/4/28 17:17\n    @desc:\n\"\"\"\nimport json\nimport os\n\nfrom django.contrib.postgres.fields import ArrayField\nfrom django.core.cache import cache\nfrom django.db import models\nfrom django.db.models import QuerySet, Q, TextField\nfrom django.db.models.functions import Cast\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import Application\nfrom common.constants.cache_version import Cache_Version\nfrom common.constants.permission_constants import get_default_workspace_user_role_mapping_list, RoleConstants, \\\n    ResourcePermission, ResourcePermissionRole, ResourceAuthType\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.db.search import native_search, native_page_search, get_dynamics_model\nfrom common.db.sql_execute import select_list\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import get_file_content\nfrom knowledge.models import Knowledge\nfrom maxkb.conf import PROJECT_DIR\nfrom maxkb.settings import edition\nfrom models_provider.models import Model\nfrom system_manage.models import WorkspaceUserResourcePermission\nfrom tools.models import Tool\nfrom users.serializers.user import is_workspace_manage\n\n\nclass PermissionSerializer(serializers.Serializer):\n    VIEW = serializers.BooleanField(required=True, label=\"可读\")\n    MANAGE = serializers.BooleanField(required=True, label=\"管理\")\n    ROLE = serializers.BooleanField(required=True, label=\"跟随角色\")\n\n\nclass UserResourcePermissionItemResponse(serializers.Serializer):\n    id = serializers.UUIDField(required=True, label=\"主键id\")\n    name = serializers.CharField(required=True, label=\"资源名称\")\n    auth_target_type = serializers.CharField(required=True, label=\"授权资源\")\n    user_id = serializers.UUIDField(required=True, label=\"用户id\")\n    icon = serializers.CharField(required=True, label=\"资源图标\")\n    auth_type = serializers.CharField(required=True, label=\"授权类型\")\n    permission = serializers.ChoiceField(required=False, allow_null=True, allow_blank=True,\n                                         choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'],\n                                         label=_('permission'))\n\n\nclass UserResourcePermissionResponse(serializers.Serializer):\n    KNOWLEDGE = UserResourcePermissionItemResponse(many=True)\n\n\nclass UpdateTeamMemberItemPermissionSerializer(serializers.Serializer):\n    target_id = serializers.CharField(required=True, label=_('target id'))\n    permission = serializers.ChoiceField(required=False, allow_null=True, allow_blank=True,\n                                         choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'],\n                                         label=_('permission'))\n\n\nclass UpdateUserResourcePermissionRequest(serializers.Serializer):\n    user_resource_permission_list = UpdateTeamMemberItemPermissionSerializer(required=True, many=True)\n\n    def is_valid(self, *, auth_target_type=None, workspace_id=None, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        user_resource_permission_list = [{'target_id': urp.get('target_id'), 'auth_target_type': auth_target_type} for\n                                         urp in\n                                         self.data.get(\"user_resource_permission_list\")]\n        illegal_target_id_list = select_list(\n            get_file_content(\n                os.path.join(PROJECT_DIR, \"apps\", \"system_manage\", 'sql', 'check_member_permission_target_exists.sql')),\n            [json.dumps(user_resource_permission_list), workspace_id, workspace_id, workspace_id, workspace_id,\n             workspace_id, workspace_id, workspace_id])\n        if illegal_target_id_list is not None and len(illegal_target_id_list) > 0:\n            raise AppApiException(500,\n                                  _('Non-existent id')+'[' + str(illegal_target_id_list) + ']')\n\n\nm_map = {\n    \"KNOWLEDGE\": Knowledge,\n    'TOOL': Tool,\n    'MODEL': Model,\n    'APPLICATION': Application,\n}\n\nsql_map = {\n    \"KNOWLEDGE\": 'get_knowledge_user_resource_permission.sql',\n    'TOOL': 'get_tool_user_resource_permission.sql',\n    'MODEL': 'get_model_user_resource_permission.sql',\n    'APPLICATION': 'get_application_user_resource_permission.sql'\n}\n\n\nclass UserResourcePermissionUserListRequest(serializers.Serializer):\n    name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('resource name'))\n    permission = serializers.MultipleChoiceField(required=False, allow_null=True, allow_blank=True,\n                                                 choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'],\n                                                 label=_('permission'))\n\n\nclass UserResourcePermissionSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n    user_id = serializers.CharField(required=True, label=_('user id'))\n    auth_target_type = serializers.CharField(required=True, label=_('resource'))\n\n    def get_queryset(self, instance):\n        resource_query_set = QuerySet(\n            model=get_dynamics_model({\n                'name': models.CharField(),\n                \"permission\": models.CharField(),\n            }))\n        name = instance.get('name')\n        permission = instance.get('permission')\n        query_p_list = [None if p == \"NOT_AUTH\" else p for p in permission]\n\n        if name:\n            resource_query_set = resource_query_set.filter(name__contains=name)\n        if permission:\n            if all([p is None for p in query_p_list]):\n                resource_query_set = resource_query_set.filter(permission=None)\n            else:\n                if any([p is None for p in query_p_list]):\n                    resource_query_set = resource_query_set.filter(\n                        Q(permission__in=query_p_list) | Q(permission=None))\n                else:\n                    resource_query_set = resource_query_set.filter(\n                        permission__in=query_p_list)\n        return {\n            'query_set': QuerySet(m_map.get(self.data.get('auth_target_type'))).filter(\n                workspace_id=self.data.get('workspace_id')),\n            'folder_query_set': QuerySet(m_map.get(self.data.get('auth_target_type'))).filter(\n                workspace_id=self.data.get('workspace_id')),\n            'workspace_user_resource_permission_query_set': QuerySet(WorkspaceUserResourcePermission).filter(\n                workspace_id=self.data.get('workspace_id'), user=self.data.get('user_id'),\n                auth_target_type=self.data.get('auth_target_type')),\n            'resource_query_set': resource_query_set\n        }\n\n    def is_auth(self, resource_id: str):\n        self.is_valid(raise_exception=True)\n        auth_target_type = self.data.get('auth_target_type')\n        workspace_id = self.data.get('workspace_id')\n        user_id = self.data.get('user_id')\n        workspace_manage = is_workspace_manage(user_id, workspace_id)\n        if workspace_manage:\n            return True\n        wurp = QuerySet(WorkspaceUserResourcePermission).filter(auth_target_type=auth_target_type,\n                                                                workspace_id=workspace_id, user=user_id,\n                                                                target=resource_id).first()\n        if wurp is None:\n            return False\n        workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n        role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n\n        if wurp.auth_type == ResourceAuthType.ROLE.value:\n            if workspace_user_role_mapping_model and role_permission_mapping_model:\n                inner = QuerySet(workspace_user_role_mapping_model).filter(workspace_id=workspace_id, user_id=user_id)\n                return QuerySet(role_permission_mapping_model).filter(role_id__in=inner,\n                                                                      permission_id=(\n                                                                              auth_target_type + ':READ')).exists()\n            else:\n                return False\n        else:\n            return wurp.permission_list.__contains__(ResourcePermission.VIEW.value)\n\n    def auth_resource_batch(self, resource_id_list: list):\n        self.is_valid(raise_exception=True)\n        auth_target_type = self.data.get('auth_target_type')\n        workspace_id = self.data.get('workspace_id')\n        user_id = self.data.get('user_id')\n        wurp = QuerySet(WorkspaceUserResourcePermission).filter(auth_target_type=auth_target_type,\n                                                                workspace_id=workspace_id, user_id=user_id).first()\n        auth_type = wurp.auth_type if wurp else (\n            ResourceAuthType.RESOURCE_PERMISSION_GROUP if edition == 'CE' else ResourceAuthType.ROLE)\n        workspace_user_resource_permission = [WorkspaceUserResourcePermission(\n            target=resource_id,\n            auth_target_type=auth_target_type,\n            permission_list=[ResourcePermission.VIEW,\n                             ResourcePermission.MANAGE] if auth_type == ResourceAuthType.RESOURCE_PERMISSION_GROUP else [\n                ResourcePermissionRole.ROLE],\n            workspace_id=workspace_id,\n            user_id=user_id,\n            auth_type=auth_type\n        ) for resource_id in resource_id_list]\n        QuerySet(WorkspaceUserResourcePermission).bulk_create(workspace_user_resource_permission)\n        # 刷新缓存\n        version = Cache_Version.PERMISSION_LIST.get_version()\n        key = Cache_Version.PERMISSION_LIST.get_key(user_id=user_id)\n        cache.delete(key, version=version)\n        return True\n\n    def auth_resource(self, resource_id: str, is_folder=False):\n        self.is_valid(raise_exception=True)\n        auth_target_type = self.data.get('auth_target_type')\n        workspace_id = self.data.get('workspace_id')\n        user_id = self.data.get('user_id')\n\n        WorkspaceUserResourcePermission(\n            target=resource_id,\n            auth_target_type=auth_target_type,\n            permission_list=[ResourcePermission.VIEW,\n                             ResourcePermission.MANAGE],\n            workspace_id=workspace_id,\n            user_id=user_id,\n            auth_type=ResourceAuthType.RESOURCE_PERMISSION_GROUP\n        ).save()\n        # 刷新缓存\n        version = Cache_Version.PERMISSION_LIST.get_version()\n        key = Cache_Version.PERMISSION_LIST.get_key(user_id=user_id)\n        cache.delete(key, version=version)\n        return True\n\n    def list(self, instance, user, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            UserResourcePermissionUserListRequest(data=instance).is_valid(raise_exception=True)\n        workspace_id = self.data.get(\"workspace_id\")\n        user_id = self.data.get(\"user_id\")\n        # 用户权限列表\n        user_resource_permission_list = native_search(self.get_queryset(instance), get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", \"system_manage\", 'sql', sql_map.get(self.data.get('auth_target_type')))))\n\n        return [{**user_resource_permission}\n                for user_resource_permission in user_resource_permission_list]\n\n    def page(self, instance, current_page: int, page_size: int, user, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            UserResourcePermissionUserListRequest(data=instance).is_valid(raise_exception=True)\n        workspace_id = self.data.get(\"workspace_id\")\n        user_id = self.data.get(\"user_id\")\n        # 用户对应的资源权限分页列表\n        user_resource_permission_page_list = native_page_search(current_page, page_size, self.get_queryset(instance),\n                                                                get_file_content(\n                                                                    os.path.join(PROJECT_DIR, \"apps\", \"system_manage\",\n                                                                                 'sql', sql_map.get(\n                                                                            self.data.get('auth_target_type')))\n                                                                ))\n\n        return user_resource_permission_page_list\n\n    def edit(self, instance, user, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            UpdateUserResourcePermissionRequest(data={'user_resource_permission_list': instance}).is_valid(\n                raise_exception=True,\n                auth_target_type=self.data.get(\n                    'auth_target_type'),\n                workspace_id=self.data.get('workspace_id'))\n        workspace_id = self.data.get(\"workspace_id\")\n        user_id = self.data.get(\"user_id\")\n        update_list = []\n        save_list = []\n        targets = [item['target_id'] for item in instance]\n        QuerySet(WorkspaceUserResourcePermission).filter(\n            workspace_id=workspace_id,\n            user_id=user_id,\n            auth_target_type=self.data.get('auth_target_type'),\n            target__in=targets\n        ).delete()\n        workspace_user_resource_permission_exist_list = []\n        for user_resource_permission in instance:\n            permission = user_resource_permission['permission']\n            auth_type, permission_list = permission_map[permission]\n            exist_list = [user_resource_permission_exist for user_resource_permission_exist in\n                          workspace_user_resource_permission_exist_list if\n                          user_resource_permission.get('target_id') == str(user_resource_permission_exist.target)]\n            if len(exist_list) > 0:\n                exist_list[0].permission_list = [key for key in user_resource_permission.get('permission').keys() if\n                                                 user_resource_permission.get('permission').get(key)]\n                exist_list[0].auth_type = user_resource_permission.get('auth_type')\n                update_list.append(exist_list[0])\n            else:\n                save_list.append(WorkspaceUserResourcePermission(target=user_resource_permission.get('target_id'),\n                                                                 auth_target_type=self.data.get('auth_target_type'),\n                                                                 permission_list=permission_list,\n                                                                 workspace_id=workspace_id,\n                                                                 user_id=user_id,\n                                                                 auth_type=auth_type))\n        # 批量更新\n        QuerySet(WorkspaceUserResourcePermission).bulk_update(update_list, ['permission_list', 'auth_type']) if len(\n            update_list) > 0 else None\n        # 批量插入\n        QuerySet(WorkspaceUserResourcePermission).bulk_create(save_list) if len(save_list) > 0 else None\n        version = Cache_Version.PERMISSION_LIST.get_version()\n        key = Cache_Version.PERMISSION_LIST.get_key(user_id=user_id)\n        cache.delete(key, version=version)\n        return instance\n\n\nclass ResourceUserPermissionUserListRequest(serializers.Serializer):\n    nick_name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('workspace id'))\n    username = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('workspace id'))\n    permission = serializers.MultipleChoiceField(required=False, allow_null=True, allow_blank=True,\n                                                 choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'],\n                                                 label=_('permission'))\n\n\nclass ResourceUserPermissionEditRequest(serializers.Serializer):\n    user_id = serializers.CharField(required=True, label=_('workspace id'))\n    permission = serializers.ChoiceField(required=True, choices=['NOT_AUTH', 'MANAGE', 'VIEW', 'ROLE'],\n                                         label=_('permission'))\n\n\npermission_map = {\n    \"ROLE\": (\"ROLE\", [\"ROLE\"]),\n    \"MANAGE\": (\"RESOURCE_PERMISSION_GROUP\", [\"MANAGE\", \"VIEW\"]),\n    \"VIEW\": (\"RESOURCE_PERMISSION_GROUP\", [\"VIEW\"]),\n    \"NOT_AUTH\": (\"RESOURCE_PERMISSION_GROUP\", []),\n}\n\n\nclass ResourceUserPermissionSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n    target = serializers.CharField(required=True, label=_('resource id'))\n    auth_target_type = serializers.CharField(required=True, label=_('resource'))\n    users_permission = ResourceUserPermissionEditRequest(required=False, many=True, label=_('users_permission'))\n\n    RESOURCE_MODEL_MAP = {\n        'APPLICATION': Application,\n        'KNOWLEDGE': Knowledge,\n        'TOOL': Tool\n    }\n\n    def get_queryset(self, instance, is_x_pack_ee: bool):\n\n        user_query_set = QuerySet(model=get_dynamics_model({\n            'nick_name': models.CharField(),\n            'username': models.CharField(),\n            \"permission\": models.CharField(),\n            \"u.id\": models.UUIDField(),\n            \"role\": models.CharField(),\n            \"role_setting.type\": models.CharField(),\n            \"user_role_relation.workspace_id\": models.CharField(),\n            'tmp.type_list': ArrayField(models.CharField()),\n            'tmp.role_name_list_str': models.CharField()\n\n        }))\n        nick_name = instance.get('nick_name')\n        username = instance.get('username')\n        role_name = instance.get('role')\n        permission = instance.get('permission')\n        query_p_list = [None if p == \"NOT_AUTH\" else p for p in permission]\n\n        workspace_user_resource_permission_query_set = QuerySet(WorkspaceUserResourcePermission).filter(\n            workspace_id=self.data.get('workspace_id'),\n            auth_target_type=self.data.get('auth_target_type'),\n            target=self.data.get('target'))\n        if nick_name:\n            user_query_set = user_query_set.filter(nick_name__contains=nick_name)\n        if username:\n            user_query_set = user_query_set.filter(username__contains=username)\n        if permission:\n            if all([p is None for p in query_p_list]):\n                user_query_set = user_query_set.filter(\n                    permission=None)\n            else:\n                if any([p is None for p in query_p_list]):\n                    user_query_set = user_query_set.filter(\n                        Q(permission__in=query_p_list) | Q(permission=None))\n                else:\n                    user_query_set = user_query_set.filter(\n                        permission__in=query_p_list)\n        workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n        if workspace_user_role_mapping_model:\n            user_query_set = user_query_set.filter(\n                **{\"u.id__in\": QuerySet(workspace_user_role_mapping_model).filter(\n                    workspace_id=self.data.get('workspace_id')).values(\"user_id\")})\n        if is_x_pack_ee:\n            user_query_set = user_query_set.filter(**{\n                \"tmp.type_list__contains\": [\"USER\"]\n            })\n            role_name_and_type_query_set = QuerySet(model=get_dynamics_model({\n            'user_role_relation.workspace_id': models.CharField(),\n            'role_setting.type': models.CharField(),\n        })).filter(**{\n                \"user_role_relation.workspace_id\": self.data.get('workspace_id'),\n                \"role_setting.type\": \"USER\",\n            })\n            if role_name:\n                user_query_set = user_query_set.filter(\n                    **{'tmp.role_name_list_str__icontains': str(role_name)}\n                )\n\n            return {\n                'workspace_user_resource_permission_query_set': workspace_user_resource_permission_query_set,\n                'user_query_set': user_query_set,\n                'role_name_and_type_query_set': role_name_and_type_query_set\n            }\n        else:\n            user_query_set = user_query_set.filter(\n                **{'role': \"USER\"})\n            return {\n                'workspace_user_resource_permission_query_set': workspace_user_resource_permission_query_set,\n                'user_query_set': user_query_set\n            }\n\n    def list(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            ResourceUserPermissionUserListRequest(data=instance).is_valid(raise_exception=True)\n        is_x_pack_ee = self.is_x_pack_ee()\n        # 资源的用户授权列表\n        resource_user_permission_list = native_search(self.get_queryset(instance, is_x_pack_ee), get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", \"system_manage\",\n                         'sql',\n                         ('get_resource_user_permission_detail_ee.sql' if is_x_pack_ee else\n                          'get_resource_user_permission_detail.sql')\n                         )\n        ))\n        return resource_user_permission_list\n\n    @staticmethod\n    def is_x_pack_ee():\n        workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n        role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n        return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None\n\n    def page(self, instance, current_page: int, page_size: int, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            ResourceUserPermissionUserListRequest(data=instance).is_valid(raise_exception=True)\n        # 分页列表\n        is_x_pack_ee = self.is_x_pack_ee()\n        resource_user_permission_page_list = native_page_search(current_page, page_size,\n                                                                self.get_queryset(instance, is_x_pack_ee),\n                                                                get_file_content(\n                                                                    os.path.join(PROJECT_DIR, \"apps\", \"system_manage\",\n                                                                                 'sql',\n                                                                                 (\n                                                                                     'get_resource_user_permission_detail_ee.sql' if is_x_pack_ee else\n                                                                                     'get_resource_user_permission_detail.sql')\n                                                                                 )\n                                                                ))\n        return resource_user_permission_page_list\n\n    def get_has_manage_permission_resource_under_folders(self, current_user_id, folder_ids):\n\n        workspace_id = self.data.get(\"workspace_id\")\n        auth_target_type = self.data.get(\"auth_target_type\")\n        workspace_manage = is_workspace_manage(current_user_id, workspace_id)\n        resource_model = self.RESOURCE_MODEL_MAP[auth_target_type]\n\n        from folders.serializers.folder import has_exact_permission_by_role\n\n        permission_id = f\"{auth_target_type}:READ+AUTH\"\n        if workspace_manage:\n            role_type = RoleConstants.WORKSPACE_MANAGE.value.__str__()\n            has_user_role_exact_permission = has_exact_permission_by_role(current_user_id, workspace_id, permission_id,role_type)\n            if has_user_role_exact_permission:\n                current_user_managed_resources_ids = QuerySet(resource_model).filter(workspace_id=workspace_id,\n                                                                                     folder__in=folder_ids).annotate(\n                    id_str=Cast('id', TextField())\n                ).values_list(\"id_str\", flat=True)\n            else:\n                current_user_managed_resources_ids = []\n        else:\n            role_type = RoleConstants.USER.value.__str__()\n            has_user_role_exact_permission = has_exact_permission_by_role(current_user_id, workspace_id, permission_id,role_type)\n\n            permission_list = ['MANAGE']\n            if has_user_role_exact_permission:\n                permission_list = ['MANAGE','ROLE']\n\n            current_user_managed_resources_ids = QuerySet(WorkspaceUserResourcePermission).filter(\n                workspace_id=workspace_id, user_id=current_user_id, auth_target_type=auth_target_type,\n                target__in=QuerySet(resource_model).filter(workspace_id=workspace_id, folder__in=folder_ids).annotate(\n                    id_str=Cast('id', TextField())\n                ).values_list(\"id_str\", flat=True),\n                permission_list__overlap= permission_list).values_list('target', flat=True)\n\n        return current_user_managed_resources_ids\n\n    def edit(self, instance, with_valid=True, current_user_id=None):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n            ResourceUserPermissionEditRequest(data=instance, many=True).is_valid(\n                raise_exception=True)\n\n        workspace_id = self.data.get(\"workspace_id\")\n        target = self.data.get(\"target\")\n        auth_target_type = self.data.get(\"auth_target_type\")\n        users_permission = instance\n\n        users_id = [item[\"user_id\"] for item in users_permission]\n        include_children = users_permission[0].get('include_children')\n        folder_ids = users_permission[0].get('folder_ids')\n        # 删除已存在的对应的用户在该资源下的权限\n\n        if include_children:\n            managed_resource_ids = list(\n                self.get_has_manage_permission_resource_under_folders(current_user_id, folder_ids,)) + folder_ids\n\n        else:\n            managed_resource_ids = [target]\n        QuerySet(WorkspaceUserResourcePermission).filter(\n            workspace_id=workspace_id,\n            target__in=managed_resource_ids,\n            auth_target_type=auth_target_type,\n            user_id__in=users_id\n        ).delete()\n\n        save_list = [\n            WorkspaceUserResourcePermission(\n                target=resource_id,\n                auth_target_type=auth_target_type,\n                workspace_id=workspace_id,\n                auth_type=permission_map[item['permission']][0],\n                user_id=item[\"user_id\"],\n                permission_list=permission_map[item['permission']][1]\n            )\n            for resource_id in managed_resource_ids\n            for item in users_permission\n        ]\n\n        if save_list:\n            QuerySet(WorkspaceUserResourcePermission).bulk_create(save_list)\n\n        version = Cache_Version.PERMISSION_LIST.get_version()\n        for user_id in users_id:\n            key = Cache_Version.PERMISSION_LIST.get_key(user_id=user_id)\n            cache.delete(key, version=version)\n        return instance\n"
  },
  {
    "path": "apps/system_manage/serializers/valid_serializers.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： valid_serializers.py\n    @date：2024/7/8 18:00\n    @desc:\n\"\"\"\nimport re\n\nfrom django.core import validators\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom rest_framework import serializers\n\nfrom application.models import Application\nfrom common.constants.cache_version import Cache_Version\nfrom common.exception.app_exception import AppApiException\nfrom knowledge.models import Knowledge\nfrom users.models import User\nfrom django.utils.translation import gettext_lazy as _\n\nmodel_message_dict = {\n    'dataset': {'model': Knowledge, 'count': 50,\n                'message': _(\n                    'The community version supports up to 50 knowledge bases. If you need more knowledge bases, please contact us (https://fit2cloud.com/).')},\n    'application': {'model': Application, 'count': 5,\n                    'message': _(\n                        'The community version supports up to 5 applications. If you need more applications, please contact us (https://fit2cloud.com/).')},\n    'user': {'model': User, 'count': 2,\n             'message': _(\n                 'The community version supports up to 2 users. If you need more users, please contact us (https://fit2cloud.com/).')}\n}\n\n\nclass ValidSerializer(serializers.Serializer):\n    valid_type = serializers.CharField(required=True, label=_('type'), validators=[\n        validators.RegexValidator(regex=re.compile(\"^application|knowledge|user$\"),\n                                  message=\"类型只支持:application|knowledge|user\", code=500)\n    ])\n    valid_count = serializers.IntegerField(required=True, label=_('check quantity'))\n\n    def valid(self, is_valid=True):\n        if is_valid:\n            self.is_valid(raise_exception=True)\n        model_value = model_message_dict.get(self.data.get('valid_type'))\n        license_is_valid = cache.get(Cache_Version.SYSTEM.get_key(key='license_is_valid'),\n                                     version=Cache_Version.SYSTEM.get_version())\n        is_license_valid = license_is_valid if license_is_valid is not None else False\n        if not is_license_valid:\n            if self.data.get('valid_count') != model_value.get('count'):\n                raise AppApiException(400, model_value.get('message'))\n            if QuerySet(\n                    model_value.get('model')).count() >= model_value.get('count'):\n                raise AppApiException(400, model_value.get('message'))\n        return True\n"
  },
  {
    "path": "apps/system_manage/sql/check_member_permission_target_exists.sql",
    "content": "SELECT static_temp.\"target_id\"::text\nFROM (SELECT *\n      FROM json_to_recordset(\n               %s\n  ) AS x(target_id text, auth_target_type text)) static_temp\n         LEFT JOIN (SELECT id::text AS id,\n                           auth_target_type\n                    FROM (SELECT \"id\"::text,\n                                 'KNOWLEDGE' AS \"auth_target_type\"\n                          FROM knowledge\n                          WHERE workspace_id = %s\n                          UNION\n                          SELECT \"id\"::text,\n                                 'KNOWLEDGE' AS \"auth_target_type\"\n                          FROM knowledge_folder\n                          WHERE workspace_id = %s\n                          UNION\n                          SELECT \"id\"::text,\n                                 'APPLICATION' AS \"auth_target_type\"\n                          FROM application\n                          WHERE workspace_id = %s\n                          UNION\n                          SELECT \"id\"::text,\n                                 'APPLICATION' AS \"auth_target_type\"\n                          FROM application_folder\n                          WHERE workspace_id = %s\n                          UNION\n                          SELECT \"id\"::text,\n                                 'MODEL' AS \"auth_target_type\"\n                          FROM model\n                          WHERE workspace_id = %s\n                          UNION\n                          SELECT \"id\"::text,\n                                 'TOOL' AS \"auth_target_type\"\n                          FROM tool\n                          WHERE workspace_id = %s\n                          UNION\n                          SELECT \"id\"::text,\n                                 'TOOL' AS \"auth_target_type\"\n                          FROM tool_folder\n                          WHERE workspace_id = %s\n                          ) \"union_temp\") \"app_and_knowledge_temp\"\n                   ON \"app_and_knowledge_temp\".\"id\" = static_temp.\"target_id\" and\n                      app_and_knowledge_temp.\"auth_target_type\" = static_temp.\"auth_target_type\"\nWHERE app_and_knowledge_temp.id is NULL;"
  },
  {
    "path": "apps/system_manage/sql/get_application_user_resource_permission.sql",
    "content": "SELECT resource_or_folder.*,\n       CASE\n           WHEN wurp.permission IS NULL THEN 'NOT_AUTH'\n           ELSE wurp.permission\n       END\nFROM (\n         SELECT id::text,\n                \"name\",\n                'APPLICATION' AS \"auth_target_type\",\n                'application' AS \"resource_type\",\n                user_id,\n                workspace_id,\n                icon,\n                folder_id,\n                create_time\n         FROM application\n         ${query_set}\n         UNION\n         SELECT application_folder.\"id\"::text,\n                application_folder.\"name\",\n                'APPLICATION'                  AS \"auth_target_type\",\n                'folder'                       AS \"resource_type\",\n                application_folder.\"user_id\",\n                application_folder.\"workspace_id\",\n                NULL                           AS \"icon\",\n                application_folder.\"parent_id\" AS \"folder_id\",\n                application_folder.\"create_time\"\n         FROM application_folder\n         ${folder_query_set}\n     ) resource_or_folder\nLEFT JOIN (\n    SELECT target,\n           CASE\n               WHEN auth_type = 'ROLE'\n                   AND 'ROLE' = ANY (permission_list) THEN 'ROLE'\n               WHEN auth_type = 'RESOURCE_PERMISSION_GROUP'\n                   AND 'MANAGE' = ANY (permission_list) THEN 'MANAGE'\n               WHEN auth_type = 'RESOURCE_PERMISSION_GROUP'\n                   AND 'VIEW' = ANY (permission_list) THEN 'VIEW'\n               ELSE NULL\n               END AS permission\n    FROM workspace_user_resource_permission\n    ${workspace_user_resource_permission_query_set}\n) wurp\nON wurp.target::text = resource_or_folder.id\n${resource_query_set}\nORDER BY resource_or_folder.create_time DESC\n"
  },
  {
    "path": "apps/system_manage/sql/get_knowledge_user_resource_permission.sql",
    "content": "SELECT resource_or_folder.*,\n    CASE\n        WHEN wurp.permission IS NULL THEN 'NOT_AUTH'\n        ELSE wurp.permission\n    END\nFROM (\n        SELECT\n            id::text,\n            \"name\",\n            'KNOWLEDGE' AS \"auth_target_type\",\n            'knowledge' AS \"resource_type\",\n            user_id,\n            workspace_id,\n            \"type\"::varchar AS \"icon\",\n            folder_id,\n            create_time\n        FROM knowledge\n            ${query_set}\n        UNION\n        SELECT knowledge_folder.\"id\"::text,\n               knowledge_folder.\"name\",\n               'KNOWLEDGE'                  AS \"auth_target_type\",\n                    'folder'                       AS \"resource_type\",\n                    knowledge_folder.\"user_id\",\n                    knowledge_folder.\"workspace_id\",\n                    NULL                           AS \"icon\",\n                    knowledge_folder.\"parent_id\" AS \"folder_id\",\n                    knowledge_folder.\"create_time\"\n        FROM knowledge_folder\n        ${folder_query_set}\n    ) resource_or_folder\nLEFT JOIN (\n    SELECT\n        target,\n        CASE\n            WHEN auth_type = 'ROLE'\n                AND 'ROLE' = ANY(permission_list) THEN 'ROLE'\n            WHEN auth_type = 'RESOURCE_PERMISSION_GROUP'\n                AND 'MANAGE' = ANY(permission_list) THEN 'MANAGE'\n            WHEN auth_type = 'RESOURCE_PERMISSION_GROUP'\n                AND 'VIEW' = ANY(permission_list) THEN 'VIEW'\n            ELSE null\n        END AS permission\n    FROM\n        workspace_user_resource_permission\n        ${workspace_user_resource_permission_query_set}\n) wurp\nON wurp.target::text = resource_or_folder.id\n${resource_query_set}\nORDER BY resource_or_folder.create_time DESC"
  },
  {
    "path": "apps/system_manage/sql/get_model_user_resource_permission.sql",
    "content": "SELECT\n    resource_or_folder.*,\n    CASE\n\t\tWHEN\n\t      wurp.\"permission\" is null then 'NOT_AUTH'\n\t\tELSE wurp.\"permission\"\n\tEND\nFROM (\n    SELECT\n        \"id\"::text,\n        \"name\",\n        'MODEL' AS \"auth_target_type\",\n        'model' AS \"resource_type\",\n        user_id,\n        workspace_id,\n        provider as icon,\n        'default' as folder_id,\n        create_time\n    FROM\n        model\n        ${query_set}\n    UNION\n    SELECT\n        \"id\"::text,\n        \"name\",\n        'MODEL' AS \"auth_target_type\",\n        'folder' AS \"resource_type\",\n        user_id,\n        workspace_id,\n        provider as icon,\n        'default' as folder_id,\n        create_time\n    FROM model\n    ${folder_query_set}\n    AND 1=0\n) resource_or_folder\nLEFT JOIN (\n    SELECT\n        target,\n        CASE\n            WHEN auth_type = 'ROLE'\n                AND 'ROLE' = ANY(permission_list) THEN 'ROLE'\n            WHEN auth_type = 'RESOURCE_PERMISSION_GROUP'\n                AND 'MANAGE' = ANY(permission_list) THEN 'MANAGE'\n            WHEN auth_type = 'RESOURCE_PERMISSION_GROUP'\n                AND 'VIEW' = ANY(permission_list) THEN 'VIEW'\n            ELSE null\n        END AS permission\n    FROM\n        workspace_user_resource_permission\n        ${workspace_user_resource_permission_query_set}\n) wurp\nON wurp.target = resource_or_folder.\"id\"\n${resource_query_set}\nORDER BY resource_or_folder.create_time DESC"
  },
  {
    "path": "apps/system_manage/sql/get_resource_user_permission_detail.sql",
    "content": "SELECT\n    u.id,\n    u.nick_name,\n    u.username,\n    case\n\t\twhen\n\t      wurp.\"permission\" is null then 'NOT_AUTH'\n\t\telse wurp.\"permission\"\n\tend\nFROM\n    public.\"user\" u\nLEFT JOIN (\n    SELECT\n        user_id ,\n\t(case\n\t\twhen  auth_type = 'ROLE'\n\t\tand  'ROLE' = any( permission_list) then 'ROLE'\n\t\t\twhen  auth_type = 'RESOURCE_PERMISSION_GROUP'\n\t\t\tand 'MANAGE'= any(permission_list)   then 'MANAGE'\n\t\t\t  when  auth_type = 'RESOURCE_PERMISSION_GROUP'\n\t\t\tand 'VIEW' = any( permission_list) then 'VIEW'\n\t\t\telse null\n\t\tend) as \"permission\"\n    FROM\n        workspace_user_resource_permission\n        ${workspace_user_resource_permission_query_set}\n        ) wurp\nON\n    u.id = wurp.user_id\n${user_query_set}"
  },
  {
    "path": "apps/system_manage/sql/get_resource_user_permission_detail_ee.sql",
    "content": "SELECT\n    DISTINCT u.id,\n    u.nick_name,\n    u.username,\n    tmp.role_name_list AS role_name,\n    CASE\n        WHEN wurp.\"permission\" IS NULL THEN 'NOT_AUTH'\n        ELSE wurp.\"permission\"\n    END AS permission\nFROM\n    public.\"user\" u\nLEFT JOIN (\n    SELECT\n        user_id,\n        CASE\n            WHEN auth_type = 'ROLE'\n                 AND 'ROLE' = ANY(permission_list) THEN 'ROLE'\n            WHEN auth_type = 'RESOURCE_PERMISSION_GROUP'\n                 AND 'MANAGE' = ANY(permission_list) THEN 'MANAGE'\n            WHEN auth_type = 'RESOURCE_PERMISSION_GROUP'\n                 AND 'VIEW' = ANY(permission_list) THEN 'VIEW'\n            ELSE NULL\n        END AS \"permission\"\n    FROM\n        workspace_user_resource_permission\n    ${workspace_user_resource_permission_query_set}\n) wurp ON u.id = wurp.user_id\nLEFT JOIN (\n    SELECT\n        ARRAY_AGG(role_setting.role_name) AS role_name_list,\n        ARRAY_AGG(role_setting.role_name)::text AS role_name_list_str,\n        ARRAY_AGG(role_setting.type) AS type_list,\n        user_role_relation.user_id\n    FROM user_role_relation user_role_relation\n    LEFT JOIN role_setting role_setting\n    ON role_setting.id = user_role_relation.role_id\n    ${role_name_and_type_query_set}\n    GROUP BY\n    user_role_relation.user_id) tmp\nON u.id = tmp.user_id\n${user_query_set}"
  },
  {
    "path": "apps/system_manage/sql/get_tool_user_resource_permission.sql",
    "content": "SELECT resource_or_folder.*,\n    CASE\n\t\tWHEN wurp.\"permission\" IS NULL THEN 'NOT_AUTH'\n\t\tELSE wurp.\"permission\"\n\tEND\nFROM (\n        SELECT \"id\"::text,\n                \"name\",\n                'TOOL' AS \"auth_target_type\",\n                'tool' AS \"resource_type\",\n                user_id,\n                workspace_id,\n                icon,\n                folder_id,\n                tool_type,\n                create_time\n        FROM tool\n        ${query_set}\n        UNION\n        SELECT tool_folder.\"id\"::text,\n               tool_folder.\"name\",\n               'TOOL'                  AS \"auth_target_type\",\n               'folder'                AS \"resource_type\",\n               tool_folder.\"user_id\",\n               tool_folder.\"workspace_id\",\n               NULL                    AS \"icon\",\n               tool_folder.\"parent_id\" AS \"folder_id\",\n               NULL                    AS \"tool_type\",\n               tool_folder.\"create_time\"\n        FROM tool_folder\n        ${folder_query_set}\n    ) resource_or_folder\nLEFT JOIN (\n    SELECT target,\n            CASE\n                WHEN auth_type = 'ROLE'\n                    AND 'ROLE' = ANY(permission_list) THEN 'ROLE'\n                WHEN auth_type = 'RESOURCE_PERMISSION_GROUP'\n                    AND 'MANAGE' = ANY(permission_list) THEN 'MANAGE'\n                WHEN auth_type = 'RESOURCE_PERMISSION_GROUP'\n                    AND 'VIEW' = ANY(permission_list) THEN 'VIEW'\n                ELSE null\n            END AS permission\n    FROM\n        workspace_user_resource_permission\n        ${workspace_user_resource_permission_query_set}\n) wurp\nON wurp.target::text = resource_or_folder.\"id\"\n${resource_query_set}\nORDER BY resource_or_folder.create_time DESC\n\n"
  },
  {
    "path": "apps/system_manage/sql/get_user_resource_permission.sql",
    "content": "SELECT app_or_knowledge.*,\n      COALESCE(workspace_user_resource_permission.permission_list,'{}')::varchar[] as permission_list,\n      COALESCE(workspace_user_resource_permission.auth_type,'ROLE') as auth_type\nFROM (SELECT \"id\",\n             \"name\",\n             'KNOWLEDGE' AS \"auth_target_type\",\n             user_id,\n             workspace_id,\n             \"type\"::varchar    AS \"icon\",\n             folder_id\n      FROM knowledge\n      ${knowledge_query_set}\n     UNION\n     SELECT \"id\",\n             \"name\",\n             'APPLICATION' AS \"auth_target_type\",\n             user_id,\n             workspace_id,\n             icon,\n             folder_id\n      FROM application\n      ${application_query_set}\n      UNION\n     SELECT \"id\",\n             \"name\",\n             'TOOL' AS \"auth_target_type\",\n             user_id,\n             workspace_id,\n             icon,\n             folder_id\n      FROM tool\n      ${tool_query_set}\n       UNION\n     SELECT \"id\",\n             \"name\",\n             'MODEL' AS \"auth_target_type\",\n             user_id,\n             workspace_id,\n             provider as icon,\n             'default' as folder_id\n      FROM model\n      ${model_query_set}\n   ) app_or_knowledge\n         LEFT JOIN (SELECT *\n                    FROM workspace_user_resource_permission\n                     ${workspace_user_resource_permission_query_set}) workspace_user_resource_permission\n                   ON workspace_user_resource_permission.target = app_or_knowledge.\"id\";\n"
  },
  {
    "path": "apps/system_manage/sql/list_resource_mapping.sql",
    "content": "WITH source_data_cte AS (SELECT 'APPLICATION' as source_type,\n                                id,\n                                \"name\",\n                                \"desc\",\n                                \"user_id\",\n                                \"workspace_id\",\n                                \"icon\",\n                                \"type\",\n                                \"folder_id\"\n                         FROM application\n                         UNION ALL\n                         SELECT 'KNOWLEDGE' as source_type,\n                                id,\n                                \"name\",\n                                \"desc\",\n                                \"user_id\",\n                                \"workspace_id\",\n                                \"type\"::text as \"icon\" , \"type\"::text as \"type\",\n                                        \"folder_id\"\n                         FROM knowledge)\nSELECT rm.*,\n       sdc.*,\n       u.username as username\nFROM resource_mapping rm\n         LEFT JOIN source_data_cte sdc\n                   ON rm.source_type = sdc.source_type\n                       AND rm.source_id::uuid = sdc.id\n         LEFT JOIN \"public\".\"user\" u\non u.id = sdc.user_id"
  },
  {
    "path": "apps/system_manage/sql/list_resource_mapping_ee.sql",
    "content": "WITH source_data_cte AS (SELECT 'APPLICATION' as source_type,\n                                id,\n                                \"name\",\n                                \"desc\",\n                                \"user_id\",\n                                \"workspace_id\",\n                                \"icon\",\n                                \"type\",\n                                \"folder_id\"\n                         FROM application\n                         UNION ALL\n                         SELECT 'KNOWLEDGE' as source_type,\n                                id,\n                                \"name\",\n                                \"desc\",\n                                \"user_id\",\n                                \"workspace_id\",\n                                \"type\"::text as \"icon\" , \"type\"::text as \"type\", \"folder_id\"\n                         FROM knowledge)\nSELECT rm.*,\n       sdc.*,\n       u.username as username,\n       w.name     as workspace_name\nFROM resource_mapping rm\n         LEFT JOIN source_data_cte sdc\n                   ON rm.source_type = sdc.source_type\n                       AND rm.source_id::uuid = sdc.id\n         LEFT JOIN \"public\".\"user\" u\non u.id = sdc.user_id\n    LEFT JOIN \"public\".\"workspace\" w on w.id = sdc.workspace_id"
  },
  {
    "path": "apps/system_manage/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/system_manage/urls.py",
    "content": "from django.urls import path\n\nfrom . import views\n\napp_name = \"system_manage\"\n# @formatter:off\nurlpatterns = [\n    path('workspace/<str:workspace_id>/user_resource_permission/user/<str:user_id>/resource/<str:resource>', views.WorkSpaceUserResourcePermissionView.as_view()),\n    path('workspace/<str:workspace_id>/user_resource_permission/user/<str:user_id>/resource/<str:resource>/<int:current_page>/<int:page_size>', views.WorkSpaceUserResourcePermissionView.Page.as_view()),\n    path('workspace/<str:workspace_id>/resource_user_permission/resource/<str:target>/resource/<str:resource>', views.WorkspaceResourceUserPermissionView.as_view()),\n    path('workspace/<str:workspace_id>/resource_user_permission/resource/<str:target>/resource/<str:resource>/<int:current_page>/<int:page_size>', views.WorkspaceResourceUserPermissionView.Page.as_view()),\n    path('workspace/<str:workspace_id>/resource_mapping/<str:resource>/<str:resource_id>/<int:current_page>/<int:page_size>', views.ResourceMappingView.as_view()),\n    path('email_setting', views.SystemSetting.Email.as_view()),\n    path('profile', views.SystemProfile.as_view()),\n    path('valid/<str:valid_type>/<int:valid_count>', views.Valid.as_view())\n]\n"
  },
  {
    "path": "apps/system_manage/views/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/4/16 19:07\n    @desc:\n\"\"\"\nfrom .user_resource_permission import *\nfrom .email_setting import *\nfrom .system_profile import *\nfrom .valid import *\nfrom .resource_mapping import *\n"
  },
  {
    "path": "apps/system_manage/views/email_setting.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： system_setting.py\n    @date：2024/3/19 16:01\n    @desc:\n\"\"\"\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants\n\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.log.log import log\nfrom common.result import result\nfrom common.utils.common import encryption\nfrom models_provider.api.model import DefaultModelResponse\nfrom system_manage.api.email_setting import EmailSettingAPI\nfrom system_manage.serializers.email_setting import EmailSettingSerializer\n\n\ndef encryption_str(_value):\n    if isinstance(_value, str):\n        return encryption(_value)\n    return _value\n\n\ndef get_email_details(request):\n    path = request.path\n    body = request.data\n    query = request.query_params\n    email_host_password = body.get('email_host_password', '')\n    return {\n        'path': path,\n        'body': {**body, 'email_host_password': encryption_str(email_host_password)},\n        'query': query\n    }\n\n\nclass SystemSetting(APIView):\n    class Email(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['PUT'],\n                       summary=_('Create or update email settings'),\n                       description=_('Create or update email settings'),\n                       operation_id=_('Create or update email settings'),  # type: ignore\n                       request=EmailSettingAPI.get_request(),\n                       responses=EmailSettingAPI.get_response(),\n                       tags=[_('Email Settings')])  # type: ignore\n        @log(menu='Email settings', operate='Create or update email settings',\n             get_details=get_email_details)\n        @has_permissions(PermissionConstants.EMAIL_SETTING_EDIT, RoleConstants.ADMIN)\n        def put(self, request: Request):\n            return result.success(\n                EmailSettingSerializer.Create(\n                    data=request.data).update_or_save())\n\n        @extend_schema(\n            methods=['POST'],\n            summary=_('Test email settings'),\n            operation_id=_('Test email settings'),  # type: ignore\n            request=EmailSettingAPI.get_request(),\n            responses=DefaultModelResponse.get_response(),\n            tags=[_('Email Settings')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.EMAIL_SETTING_EDIT, RoleConstants.ADMIN)\n        @log(menu='Email settings', operate='Test email settings',\n             get_details=get_email_details\n             )\n        def post(self, request: Request):\n            return result.success(\n                EmailSettingSerializer.Create(\n                    data=request.data).is_valid())\n\n        @extend_schema(methods=['GET'],\n                       summary=_('Get email settings'),\n                       description=_('Get email settings'),\n                       operation_id=_('Get email settings'),  # type: ignore\n                       responses=DefaultModelResponse.get_response(),\n                       tags=[_('Email Settings')])  # type: ignore\n        @has_permissions(PermissionConstants.EMAIL_SETTING_READ, RoleConstants.ADMIN)\n        def get(self, request: Request):\n            return result.success(\n                EmailSettingSerializer.one())\n"
  },
  {
    "path": "apps/system_manage/views/log_management.py",
    "content": ""
  },
  {
    "path": "apps/system_manage/views/resource_mapping.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： resource_mapping.py\n    @date：2025/12/25 15:28\n    @desc:\n\"\"\"\n\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import Permission, Group, Operate, RoleConstants, ViewPermission, \\\n    CompareConstants\nfrom system_manage.api.resource_mapping import ResourceMappingAPI\nfrom system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer\n\n\nclass ResourceMappingView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Retrieve the pagination list of resource relationships'),\n        operation_id=_('Retrieve the pagination list of resource relationships'),  # type: ignore\n        responses=ResourceMappingAPI.get_response(),\n        parameters=ResourceMappingAPI.get_parameters(),\n        tags=[_('Resources mapping')]  # type: ignore\n    )\n    @has_permissions(\n        lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),\n                                     operate=Operate.RELATE_VIEW,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"),\n        lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),\n                                     operate=Operate.RELATE_VIEW,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource')}/{kwargs.get('resource_id')}\"),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),\n                                                     operate=Operate.SELF,\n                                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource')}/{kwargs.get('resource_id')}\")],\n                       CompareConstants.AND),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str, resource: str, resource_id: str, current_page, page_size):\n        return result.success(ResourceMappingSerializer({\n            'resource': resource,\n            'resource_id': resource_id,\n            'resource_name': request.query_params.get('resource_name'),\n            'user_name': request.query_params.get('user_name'),\n            'source_type': request.query_params.getlist('source_type[]'),\n        }).page(current_page, page_size))\n"
  },
  {
    "path": "apps/system_manage/views/system_profile.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： system_profile.py\n    @date：2025/6/4 15:59\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common import result\nfrom system_manage.api.system import SystemProfileAPI\nfrom system_manage.serializers.system import SystemProfileSerializer\n\n\nclass SystemProfile(APIView):\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get MaxKB related information'),\n        operation_id=_('Get MaxKB related information'),  # type: ignore\n        responses=SystemProfileAPI.get_response(),\n        tags=[_('System parameters')]  # type: ignore\n    )\n    def get(self, request: Request):\n        return result.success(SystemProfileSerializer.profile())\n"
  },
  {
    "path": "apps/system_manage/views/user_resource_permission.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： workspace_user_resource_permission.py\n    @date：2025/4/28 16:38\n    @desc:\n\"\"\"\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import RoleConstants, Permission, Group, Operate, ViewPermission, \\\n    CompareConstants\nfrom common.log.log import log\nfrom system_manage.api.user_resource_permission import UserResourcePermissionAPI, EditUserResourcePermissionAPI, \\\n    ResourceUserPermissionAPI, ResourceUserPermissionPageAPI, ResourceUserPermissionEditAPI, \\\n    UserResourcePermissionPageAPI\nfrom system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer, \\\n    ResourceUserPermissionSerializer\nfrom users.models import User\n\n\ndef get_user_operation_object(user_id):\n    user_model = QuerySet(model=User).filter(id=user_id).first()\n    if user_model is not None:\n        return {\n            \"name\": user_model.username\n        }\n    return {}\n\n\nclass WorkSpaceUserResourcePermissionView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Obtain resource authorization list'),\n        operation_id=_('Obtain resource authorization list'),  # type: ignore\n        parameters=UserResourcePermissionAPI.get_parameters(),\n        responses=UserResourcePermissionAPI.get_response(),\n        tags=[_('Resources authorization')]  # type: ignore\n    )\n    @has_permissions(\n        lambda r, kwargs: Permission(group=Group(kwargs.get('resource') + '_WORKSPACE_USER_RESOURCE_PERMISSION'),\n                                     operate=Operate.READ),\n        RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str, user_id: str, resource: str):\n        return result.success(UserResourcePermissionSerializer(\n            data={'workspace_id': workspace_id, 'user_id': user_id, 'auth_target_type': resource}\n        ).list({'name': request.query_params.get('name'),\n                'permission': request.query_params.getlist('permission[]')}, request.user))\n\n    @extend_schema(\n        methods=['PUT'],\n        description=_('Modify the resource authorization list'),\n        operation_id=_('Modify the resource authorization list'),  # type: ignore\n        parameters=EditUserResourcePermissionAPI.get_parameters(),\n        request=EditUserResourcePermissionAPI.get_request(),\n        responses=EditUserResourcePermissionAPI.get_response(),\n        tags=[_('Resources authorization')]  # type: ignore\n    )\n    @log(menu='System', operate='Modify the resource authorization list',\n         get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id'))\n         )\n    @has_permissions(\n        lambda r, kwargs: Permission(group=Group(kwargs.get('resource') + '_WORKSPACE_USER_RESOURCE_PERMISSION'),\n                                     operate=Operate.EDIT),\n        RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def put(self, request: Request, workspace_id: str, user_id: str, resource: str):\n        return result.success(UserResourcePermissionSerializer(\n            data={'workspace_id': workspace_id, 'user_id': user_id, 'auth_target_type': resource}\n        ).edit(request.data, request.user))\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Obtain resource authorization list by page'),\n            summary=_('Obtain resource authorization list by page'),\n            operation_id=_('Obtain resource authorization list by page'),  # type: ignore\n            request=None,\n            parameters=UserResourcePermissionPageAPI.get_parameters(),\n            responses=UserResourcePermissionPageAPI.get_response(),\n            tags=[_('Resources authorization')]  # type: ignore\n        )\n        @has_permissions(\n            lambda r, kwargs: Permission(group=Group(kwargs.get('resource') + '_WORKSPACE_USER_RESOURCE_PERMISSION'),\n                                         operate=Operate.READ),\n            RoleConstants.ADMIN, RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, user_id: str, resource: str, current_page: str,\n                page_size: str):\n            return result.success(UserResourcePermissionSerializer(\n                data={'workspace_id': workspace_id, 'user_id': user_id, 'auth_target_type': resource}\n                ).page({'name': request.query_params.get('name'),\n                        'permission': request.query_params.getlist('permission[]')}, current_page, page_size, request.user))\n\n\nclass WorkspaceResourceUserPermissionView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get user authorization status of resource'),\n        summary=_('Get user authorization status of resource'),\n        operation_id=_('Get user authorization status of resource'),  # type: ignore\n        parameters=ResourceUserPermissionAPI.get_parameters(),\n        responses=ResourceUserPermissionAPI.get_response(),\n        tags=[_('Resources authorization')]  # type: ignore\n    )\n    @has_permissions(\n        lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),\n                                     operate=Operate.AUTH,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"),\n        lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),\n                                     operate=Operate.AUTH,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}\"),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [lambda r, kwargs: Permission(group=Group(kwargs.get('resource').replace('_FOLDER','')),\n                                                     operate=Operate.SELF,\n                                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}\")],\n                       CompareConstants.AND),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str, target: str, resource: str):\n        return result.success(ResourceUserPermissionSerializer(\n            data={'workspace_id': workspace_id, \"target\": target, 'auth_target_type': resource.replace('_FOLDER',''),\n                  }).list(\n            {'username': request.query_params.get(\"username\"), 'nick_name': request.query_params.get(\"nick_name\"),\n             'permission': request.query_params.getlist(\"permission[]\")\n             }))\n\n    @extend_schema(\n        methods=['PUT'],\n        description=_('Edit user authorization status of resource'),\n        summary=_('Edit user authorization status of resource'),\n        operation_id=_('Edit user authorization status of resource'),  # type: ignore\n        parameters=ResourceUserPermissionEditAPI.get_parameters(),\n        request=ResourceUserPermissionEditAPI.get_request(),\n        responses=ResourceUserPermissionEditAPI.get_response(),\n        tags=[_('Resources authorization')]  # type: ignore\n    )\n    @log(menu='System', operate='Edit user authorization status of resource',\n         get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id'))\n         )\n    @has_permissions(\n        lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),\n                                     operate=Operate.AUTH,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"),\n        lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),\n                                     operate=Operate.AUTH,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}\"),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [lambda r, kwargs: Permission(group=Group(kwargs.get('resource').replace('_FOLDER','')),\n                                                     operate=Operate.SELF,\n                                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}\")],\n                       CompareConstants.AND),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def put(self, request: Request, workspace_id: str, target: str, resource: str):\n        return result.success(ResourceUserPermissionSerializer(\n            data={'workspace_id': workspace_id, \"target\": target, 'auth_target_type': resource.replace('_FOLDER',''), })\n                              .edit(instance=request.data, current_user_id=request.user.id))\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get user authorization status of resource by page'),\n            summary=_('Get user authorization status of resource by page'),\n            operation_id=_('Get user authorization status of resource by page'),  # type: ignore\n            parameters=ResourceUserPermissionPageAPI.get_parameters(),\n            responses=ResourceUserPermissionPageAPI.get_response(),\n            tags=[_('Resources authorization')]  # type: ignore\n        )\n        @has_permissions(\n            lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),\n                                         operate=Operate.AUTH,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"),\n        lambda r, kwargs: Permission(group=Group(kwargs.get('resource')),\n                                     operate=Operate.AUTH,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}\"),\n             ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [lambda r, kwargs: Permission(group=Group(kwargs.get('resource').replace('_FOLDER','')),\n                                     operate=Operate.SELF,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('resource').replace('_FOLDER','')}/{kwargs.get('target')}\")],\n                           CompareConstants.AND),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, target: str, resource: str, current_page: int,\n                page_size: int):\n            return result.success(ResourceUserPermissionSerializer(\n                data={'workspace_id': workspace_id, \"target\": target, 'auth_target_type': resource.replace('_FOLDER',''), }\n            ).page({'username': request.query_params.get(\"username\"),\n                    'role': request.query_params.get(\"role\"),\n                    'nick_name': request.query_params.get(\"nick_name\"),\n                    'permission': request.query_params.getlist(\"permission[]\")}, current_page, page_size,\n                   ))\n"
  },
  {
    "path": "apps/system_manage/views/valid.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎\n    @file： valid.py\n    @date：2024/7/8 17:50\n    @desc:\n\"\"\"\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common.auth import TokenAuth\nfrom django.utils.translation import gettext_lazy as _\n\nfrom common.result import result\nfrom system_manage.serializers.valid_serializers import ValidSerializer\n\n\nclass Valid(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get verification results'),\n        summary=_('Get verification results'),\n        operation_id=_('Get verification results'),  # type: ignore\n        tags=[_('Validation')]  # type: ignore\n    )\n    def get(self, request: Request, valid_type: str, valid_count: int):\n        return result.success(ValidSerializer(data={'valid_type': valid_type, 'valid_count': valid_count}).valid())\n"
  },
  {
    "path": "apps/tools/__init__.py",
    "content": ""
  },
  {
    "path": "apps/tools/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "apps/tools/api/__init__.py",
    "content": "# coding=utf-8\n"
  },
  {
    "path": "apps/tools/api/tool.py",
    "content": "# coding=utf-8\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, DefaultResultSerializer\nfrom tools.serializers.tool import ToolModelSerializer, ToolCreateRequest, ToolDebugRequest, ToolEditRequest, \\\n    PylintInstance, AddInternalToolRequest\n\n\nclass ToolCreateResponse(ResultSerializer):\n    def get_data(self):\n        return ToolModelSerializer()\n\n\nclass ToolCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_request():\n        return ToolCreateRequest\n\n    @staticmethod\n    def get_response():\n        return ToolCreateResponse\n\n\nclass ToolReadAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"tool_id\",\n                description=\"工具id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_response():\n        return ToolCreateResponse\n\n\nclass ToolEditAPI(ToolReadAPI):\n\n    @staticmethod\n    def get_request():\n        return ToolEditRequest\n\n\nclass ToolDeleteAPI(ToolReadAPI):\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ToolTreeReadAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"folder_id\",\n                description=\"文件夹id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            )\n        ]\n\n\nclass ToolDebugApi(APIMixin):\n    @staticmethod\n    def get_request():\n        return ToolDebugRequest\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ToolExportAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"tool_id\",\n                description=\"工具id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ToolImportAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n\n        ]\n\n    @staticmethod\n    def get_request():\n        return {\n            'multipart/form-data': {\n                'type': 'object',\n                'properties': {\n                    'file': {\n                        'type': 'string',\n                        'format': 'binary'  # Tells Swagger it's a file\n                    }\n                }\n            }\n        }\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ToolPageAPI(ToolReadAPI):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"current_page\",\n                description=\"当前页码\",\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"page_size\",\n                description=\"每页大小\",\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"folder_id\",\n                description=\"文件夹id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"user_id\",\n                description=\"创建者id\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n            OpenApiParameter(\n                name=\"scope\",\n                description=\"工具类型\",\n                type=OpenApiTypes.STR,\n                enum=[\"SHARED\", \"WORKSPACE\"],\n                location='query',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"name\",\n                description=\"工具名称\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            ),\n        ]\n\n\nclass PylintAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n\n        ]\n\n    @staticmethod\n    def get_request():\n        return PylintInstance\n\n\nclass EditIconAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"tool_id\",\n                description=\"工具id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return {\n            'multipart/form-data': {\n                'type': 'object',\n                'properties': {\n                    'icon': {\n                        'type': 'string',\n                        'format': 'binary'  # Tells Swagger it's a file\n                    }\n                }\n            }\n        }\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass GetInternalToolAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"name\",\n                description=\"工具名称\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=False,\n            )\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer()\n\n\nclass AddInternalToolAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return AddInternalToolRequest\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"tool_id\",\n                description=\"工具id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n"
  },
  {
    "path": "apps/tools/api/tool_workflow.py",
    "content": "# coding=utf-8\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import DefaultResultSerializer\nfrom tools.serializers.tool_workflow import ToolWorkflowImportRequest\n\n\nclass ToolWorkflowApi(APIMixin):\n    pass\n\n\nclass ToolWorkflowVersionApi(APIMixin):\n    pass\n\n\nclass ToolWorkflowExportApi(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"tool_id\",\n                description=\"工具id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass ToolWorkflowImportApi(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return ToolWorkflowExportApi.get_parameters()\n\n    @staticmethod\n    def get_request():\n        return ToolWorkflowImportRequest\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n"
  },
  {
    "path": "apps/tools/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass ToolConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'tools'\n"
  },
  {
    "path": "apps/tools/migrations/0001_initial.py",
    "content": "# Generated by Django 5.2.3 on 2025-06-23 02:14\nimport os\n\nimport django.db.models.deletion\nimport mptt.fields\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\nfrom common.utils.common import get_file_content\nfrom maxkb.const import PROJECT_DIR\nfrom tools.models import ToolFolder\n\n\ndef insert_default_data(apps, schema_editor):\n    # 创建一个根模块（没有父节点）\n    ToolFolder.objects.create(id='default', name='根目录', user_id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', workspace_id='default')\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('users', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ToolFolder',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.CharField(editable=False, max_length=64, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('name', models.CharField(db_index=True, max_length=64, verbose_name='文件夹名称')),\n                ('desc', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('lft', models.PositiveIntegerField(editable=False)),\n                ('rght', models.PositiveIntegerField(editable=False)),\n                ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),\n                ('level', models.PositiveIntegerField(editable=False)),\n                ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='children', to='tools.toolfolder')),\n                ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),\n            ],\n            options={\n                'db_table': 'tool_folder',\n            },\n        ),\n        migrations.RunPython(insert_default_data),\n        migrations.CreateModel(\n            name='Tool',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('name', models.CharField(db_index=True, max_length=64, verbose_name='工具名称')),\n                ('desc', models.CharField(max_length=128, verbose_name='描述')),\n                ('code', models.CharField(max_length=102400, verbose_name='python代码')),\n                ('input_field_list', models.JSONField(default=list, verbose_name='输入字段列表')),\n                ('init_field_list', models.JSONField(default=list, verbose_name='启动字段列表')),\n                ('icon', models.CharField(default='', max_length=256, verbose_name='工具库icon')),\n                ('is_active', models.BooleanField(db_index=True, default=True)),\n                ('scope', models.CharField(choices=[('SHARED', '共享'), ('WORKSPACE', '工作空间可用'), ('INTERNAL', '内置')], db_index=True, default='WORKSPACE', max_length=20, verbose_name='可用范围')),\n                ('tool_type', models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型')),\n                ('template_id', models.UUIDField(db_index=True, default=None, null=True, verbose_name='模版id')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('init_params', models.CharField(max_length=102400, null=True, verbose_name='初始化参数')),\n                ('label', models.CharField(db_index=True, max_length=128, null=True, verbose_name='标签')),\n                ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),\n                ('folder', models.ForeignKey(default='default', on_delete=django.db.models.deletion.DO_NOTHING, to='tools.toolfolder', verbose_name='文件夹id')),\n            ],\n            options={\n                'db_table': 'tool',\n            },\n        ),\n        migrations.RunSQL(get_file_content(os.path.join(PROJECT_DIR, \"apps\", \"tools\", 'migrations', 'internal_tool.sql')))\n    ]\n"
  },
  {
    "path": "apps/tools/migrations/0002_alter_tool_tool_type.py",
    "content": "# Generated by Django 5.2.4 on 2025-08-11 09:37\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('tools', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='tool',\n            name='tool_type',\n            field=models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义'), ('MCP', 'MCP工具')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/tools/migrations/0003_alter_tool_template_id.py",
    "content": "# Generated by Django 5.2.4 on 2025-09-09 04:07\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('tools', '0002_alter_tool_tool_type'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='tool',\n            name='template_id',\n            field=models.CharField(db_index=True, default=None, max_length=128, null=True, verbose_name='模版id'),\n        ),\n        migrations.AddField(\n            model_name='tool',\n            name='version',\n            field=models.CharField(default=None, max_length=64, null=True, verbose_name='版本号'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/tools/migrations/0004_alter_tool_tool_type.py",
    "content": "# Generated by Django 5.2.8 on 2025-11-17 07:07\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('tools', '0003_alter_tool_template_id'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='tool',\n            name='tool_type',\n            field=models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义'), ('MCP', 'MCP工具'), ('DATA_SOURCE', '数据源')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/tools/migrations/0005_taskrecord.py",
    "content": "# Generated by Django 5.2.9 on 2026-01-29 02:58\n\nimport common.encoder.encoder\nimport django.db.models.deletion\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\nold = [\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/md2docx/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/mcp_output/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/feishubot/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/mongo/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/OFD_Parse/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/html_to_pdf/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/feishu_datasource/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/quotation_generation_agent/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/html2pdf/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/sqlbot_ai/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/metaso/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/registry/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/smtp_email/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/wecomrobot/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/paperx/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/case_inquire/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/data_analysis_assistant/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/crm_intelligent_search/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/case_collection/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/knowledge_self_assessment/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/minerU_util/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/invoice_recognition/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/fragment/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/contract_review/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/echart_to_svg/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/extract/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/big_order/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/crm_intelligent_recording/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/dingrobot/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/timestamp/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/knowledge_workflow/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/household_registration_policy_qa_assistant/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/httputils/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/html_compression/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/crm_customer_entry_ai_agent/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/anspire/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/application_template/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/baidu-translate/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/website_translation/logo.png\",\n]\nnew = [\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_md2docx/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_mcp_output/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_feishubot/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_mongo/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_OFD_Parse/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_html_to_pdf/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/db_feishu_datasource/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_quotation_generation_agent/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_html2pdf/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_sqlbot_ai/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_metaso/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_registry/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_smtp_email/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_wecomrobot/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_paperx/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_case_inquire/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_data_analysis_assistant/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_crm_intelligent_search/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_case_collection/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_knowledge_self_assessment/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_minerU_util/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_invoice_recognition/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_fragment/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_contract_review/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_echart_to_svg/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_extract/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_big_order/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_crm_intelligent_recording/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_dingrobot/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_timestamp/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/kbwf_knowledge_workflow/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_household_registration_policy_qa_assistant/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_httputils/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_html_compression/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_crm_customer_entry_ai_agent/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_anspire/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_application_template/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/tool_baidu-translate/logo.png\",\n    \"https://apps-assets.fit2cloud.com/stable/maxkb/app_website_translation/logo.png\",\n]\n\ndef _replace_tool_icons(apps, schema_editor):\n    if len(old) != len(new):\n        raise ValueError(\"`old` 与 `new` 长度不一致，无法一一替换\")\n\n    Tool = apps.get_model(\"tools\", \"Tool\")\n    mapping = dict(zip(old, new))\n\n    # 逐个 update，避免依赖数据库对 CASE/WHEN 的兼容差异\n    for old_icon, new_icon in mapping.items():\n        Tool.objects.filter(icon=old_icon).update(icon=new_icon)\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('tools', '0004_alter_tool_tool_type'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='ToolRecord',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('source_type', models.CharField(choices=[('APPLICATION', 'Application'), ('KNOWLEDGE', 'Knowledge'), ('TRIGGER', 'Trigger')], default='APPLICATION', max_length=256, verbose_name='触发器任务类型')),\n                ('source_id', models.UUIDField(verbose_name='资源id')),\n                ('meta', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder)),\n                ('state', models.CharField(choices=[('PENDING', 'Pending'), ('STARTED', 'Started'), ('SUCCESS', 'Success'), ('FAILURE', 'Failure'), ('REVOKE', 'Revoke'), ('REVOKED', 'Revoked')], default='STARTED', max_length=20, verbose_name='状态')),\n                ('run_time', models.FloatField(default=0, verbose_name='运行时长')),\n                ('tool', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='tools.tool')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n            ],\n            options={\n                'db_table': 'tool_record',\n            },\n        ),\n        migrations.RunPython(_replace_tool_icons, reverse_code=migrations.RunPython.noop),\n    ]\n"
  },
  {
    "path": "apps/tools/migrations/0006_alter_tool_tool_type.py",
    "content": "# Generated by Django 5.2.11 on 2026-02-27 02:38\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('tools', '0005_taskrecord'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='tool',\n            name='tool_type',\n            field=models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义'), ('SKILL', '技能'), ('MCP', 'MCP工具'), ('DATA_SOURCE', '数据源')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/tools/migrations/0007_alter_tool_tool_type_toolworkflow_and_more.py",
    "content": "# Generated by Django 5.2.11 on 2026-03-10 10:46\n\nimport django.db.models.deletion\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('tools', '0006_alter_tool_tool_type'),\n    ]\n\n    operations = [\n        migrations.AlterField(\n            model_name='tool',\n            name='tool_type',\n            field=models.CharField(choices=[('INTERNAL', '内置'), ('CUSTOM', '自定义'), ('SKILL', '技能'), ('MCP', 'MCP工具'), ('DATA_SOURCE', '数据源'), ('WORKFLOW', 'Workflow')], db_index=True, default='CUSTOM', max_length=20, verbose_name='工具类型'),\n        ),\n        migrations.CreateModel(\n            name='ToolWorkflow',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),\n                ('is_publish', models.BooleanField(db_index=True, default=False, verbose_name='是否发布')),\n                ('publish_time', models.DateTimeField(blank=True, null=True, verbose_name='发布时间')),\n                ('tool', models.OneToOneField(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, related_name='workflow', to='tools.tool', verbose_name='工具')),\n            ],\n            options={\n                'db_table': 'tool_workflow',\n            },\n        ),\n        migrations.CreateModel(\n            name='ToolWorkflowVersion',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('name', models.CharField(default='', max_length=128, verbose_name='版本名称')),\n                ('work_flow', models.JSONField(default=dict, verbose_name='工作流数据')),\n                ('publish_user_id', models.UUIDField(default=None, null=True, verbose_name='发布者id')),\n                ('publish_user_name', models.CharField(default='', max_length=128, verbose_name='发布者名称')),\n                ('tool', models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='tools.tool', verbose_name='工具')),\n            ],\n            options={\n                'db_table': 'tool_workflow_version',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "apps/tools/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "apps/tools/migrations/internal_tool.sql",
    "content": "INSERT INTO tool (create_time, update_time, id, name, label, \"desc\", code, input_field_list, init_field_list, icon, is_active, scope, tool_type, template_id, workspace_id, init_params, user_id, folder_id) VALUES ('2025-03-10 06:20:35.945414 +00:00', '2025-03-10 09:19:23.608026 +00:00', 'c75cb48e-fd77-11ef-84d2-5618c4394482', '博查搜索', 'web_search', '从博查搜索任何信息和网页URL', e'def bocha_search(query, apikey):\n    import requests\n    import json\n    url = \"https://api.bochaai.com/v1/web-search\"\n    payload = json.dumps({\n        \"query\": query,\n        \"summary\": True,\n        \"count\": 8\n    })\n\n    headers = {\n        \"Authorization\": \"Bearer \" + apikey, #鉴权参数，示例：Bearer xxxxxx，API KEY请先前往博查AI开放平台（https://open.bochaai.com）> API KEY 管理中获取。\n        \"Content-Type\": \"application/json\"\n    }\n\n    response = requests.request(\"POST\", url, headers=headers, data=payload)\n    if response.status_code == 200:\n        return response.json()\n    else:\n        raise Exception(f\"API请求失败: {response.status_code}, 错误信息: {response.text}\")\n        return (response.text)', '[{\"name\": \"query\", \"type\": \"string\", \"source\": \"reference\", \"is_required\": true}]', '[{\"attrs\": {\"type\": \"password\", \"maxlength\": 200, \"minlength\": 1, \"show-password\": true, \"show-word-limit\": true}, \"field\": \"apikey\", \"label\": \"API Key\", \"required\": true, \"input_type\": \"PasswordInput\", \"props_info\": {\"rules\": [{\"message\": \"API Key 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"API Key 长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"x\", \"show_default_value\": false}]', './tool/bochaai/icon.png', true, 'INTERNAL', 'INTERNAL', null, 'None', '', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', 'default');\nINSERT INTO tool (create_time, update_time, id, name, label, \"desc\", code, input_field_list, init_field_list, icon, is_active, scope, tool_type, template_id, workspace_id, init_params, user_id, folder_id) VALUES ('2025-02-26 03:36:48.187286 +00:00', '2025-03-11 07:23:46.123972 +00:00', 'e89ad2ae-f3f2-11ef-ad09-0242ac110002', 'Google Search','web_search', 'Google Web Search', e'def google_search(query, apikey, cx):\n    import requests\n    import json\n    url = \"https://customsearch.googleapis.com/customsearch/v1\"\n    params = {\n        \"q\": query,\n        \"key\": apikey,\n        \"cx\": cx,\n        \"num\": 10,  # 每次最多返回10条\n    }\n\n    response = requests.get(url, params=params)\n    if response.status_code == 200:\n        return response.json()\n    else:\n        raise Exception(f\"API请求失败: {response.status_code}, 错误信息: {response.text}\")\n        return (response.text)', '[{\"name\": \"query\", \"type\": \"string\", \"source\": \"reference\", \"is_required\": true}]', '[{\"attrs\": {\"type\": \"password\", \"maxlength\": 200, \"minlength\": 1, \"show-password\": true, \"show-word-limit\": true}, \"field\": \"apikey\", \"label\": \"API Key\", \"required\": true, \"input_type\": \"PasswordInput\", \"props_info\": {\"rules\": [{\"message\": \"API Key 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"API Key 长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"x\", \"show_default_value\": false}, {\"attrs\": {\"maxlength\": 200, \"minlength\": 1, \"show-word-limit\": true}, \"field\": \"cx\", \"label\": \"cx\", \"required\": true, \"input_type\": \"TextInput\", \"props_info\": {\"rules\": [{\"message\": \"cx 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"cx长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"x\", \"show_default_value\": false}]', './tool/google_search/icon.png', true, 'INTERNAL', 'INTERNAL', null, 'None', '', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', 'default');\nINSERT INTO tool (create_time, update_time, id, name, label, \"desc\", code, input_field_list, init_field_list, icon, is_active, scope, tool_type, template_id, workspace_id, init_params, user_id, folder_id) VALUES ('2025-02-25 07:44:40.141515 +00:00', '2025-03-11 06:33:53.248495 +00:00', '5e912f00-f34c-11ef-8a9c-5618c4394482', 'LangSearch','web_search', e'A Web Search tool supporting natural language search\n', e'\ndef langsearch(query, apikey):\n    import json\n    import requests\n\n    url = \"https://api.langsearch.com/v1/web-search\"\n    payload = json.dumps({\n        \"query\": query,\n        \"summary\": True,\n        \"freshness\": \"noLimit\",\n        \"livecrawl\": True,\n        \"count\": 20\n    })\n    headers = {\n        \"Authorization\": apikey,\n        \"Content-Type\": \"application/json\"\n    }\n    # key从官网申请 https://langsearch.com/\n    response = requests.request(\"POST\", url, headers=headers, data=payload)\n    if response.status_code == 200:\n        return response.json()\n    else:\n        raise Exception(f\"API请求失败: {response.status_code}, 错误信息: {response.text}\")\n        return (response.text)', '[{\"name\": \"query\", \"type\": \"string\", \"source\": \"reference\", \"is_required\": true}]', '[{\"attrs\": {\"type\": \"password\", \"maxlength\": 200, \"minlength\": 1, \"show-password\": true, \"show-word-limit\": true}, \"field\": \"apikey\", \"label\": \"API Key\", \"required\": true, \"input_type\": \"PasswordInput\", \"props_info\": {\"rules\": [{\"message\": \"API Key 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"API Key 长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"x\", \"show_default_value\": false}]', './tool/langsearch/icon.png', true, 'INTERNAL', 'INTERNAL', null, 'None', '', 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', 'default');\nINSERT INTO tool (create_time, update_time, id, name, label, \"desc\", code, input_field_list, init_field_list, icon, is_active, scope, tool_type, template_id, workspace_id, init_params, user_id, folder_id) VALUES ('2025-03-17 08:16:32.626245 +00:00', '2025-03-17 08:16:32.626308 +00:00', '22c21b76-0308-11f0-9694-5618c4394482', 'MySQL 查询','database_search', '一个连接MySQL数据库执行SQL查询的工具', e'\ndef query_mysql(host,port, user, password, database, sql):\n    import pymysql\n    import json\n    from pymysql.cursors import DictCursor\n    from datetime import datetime, date\n\n    def default_serializer(obj):\n        from decimal import Decimal\n        if isinstance(obj, (datetime, date)):\n            return obj.isoformat()  # 将 datetime/date 转换为 ISO 格式字符串\n        elif isinstance(obj, Decimal):\n            return float(obj)  # 将 Decimal 转换为 float\n        raise TypeError(f\"Type {type(obj)} not serializable\")\n\n    try:\n        # 创建连接\n        db = pymysql.connect(\n            host=host,\n            port=int(port),\n            user=user,\n            password=password,\n            database=database,\n            cursorclass=DictCursor  # 使用字典游标\n        )\n\n        # 使用 cursor() 方法创建一个游标对象 cursor\n        cursor = db.cursor()\n\n        # 使用 execute() 方法执行 SQL 查询\n        cursor.execute(sql)\n\n        # 使用 fetchall() 方法获取所有数据\n        data = cursor.fetchall()\n\n        # 处理 bytes 类型的数据\n        for row in data:\n            for key, value in row.items():\n                if isinstance(value, bytes):\n                    row[key] = value.decode(\"utf-8\")  # 转换为字符串\n\n        # 将数据序列化为 JSON\n        json_data = json.dumps(data, default=default_serializer, ensure_ascii=False)\n        return json_data\n\n        # 关闭数据库连接\n        db.close()\n\n    except Exception as e:\n        print(f\"Error while connecting to MySQL: {e}\")\n        raise e', '[{\"name\": \"sql\", \"type\": \"string\", \"source\": \"reference\", \"is_required\": true}]', '[{\"attrs\": {\"maxlength\": 200, \"minlength\": 1, \"show-word-limit\": true}, \"field\": \"host\", \"label\": \"host\", \"required\": true, \"input_type\": \"TextInput\", \"props_info\": {\"rules\": [{\"message\": \"host 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"host长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"x\", \"show_default_value\": false}, {\"attrs\": {\"maxlength\": 20, \"minlength\": 1, \"show-word-limit\": true}, \"field\": \"port\", \"label\": \"port\", \"required\": true, \"input_type\": \"TextInput\", \"props_info\": {\"rules\": [{\"message\": \"port 为必填属性\", \"required\": true}, {\"max\": 20, \"min\": 1, \"message\": \"port长度在 1 到 20 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"3306\", \"show_default_value\": false}, {\"attrs\": {\"maxlength\": 200, \"minlength\": 1, \"show-word-limit\": true}, \"field\": \"user\", \"label\": \"user\", \"required\": true, \"input_type\": \"TextInput\", \"props_info\": {\"rules\": [{\"message\": \"user 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"user长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"root\", \"show_default_value\": false}, {\"attrs\": {\"type\": \"password\", \"maxlength\": 200, \"minlength\": 1, \"show-password\": true, \"show-word-limit\": true}, \"field\": \"password\", \"label\": \"password\", \"required\": true, \"input_type\": \"PasswordInput\", \"props_info\": {\"rules\": [{\"message\": \"password 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"password长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"x\", \"show_default_value\": false}, {\"attrs\": {\"maxlength\": 200, \"minlength\": 1, \"show-word-limit\": true}, \"field\": \"database\", \"label\": \"database\", \"required\": true, \"input_type\": \"TextInput\", \"props_info\": {\"rules\": [{\"message\": \"database 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"database长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"x\", \"show_default_value\": false}]', './tool/mysql/icon.png', true, 'INTERNAL', 'INTERNAL', null, 'None', null, 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', 'default');\nINSERT INTO tool (create_time, update_time, id, name, label, \"desc\", code, input_field_list, init_field_list, icon, is_active, scope, tool_type, template_id, workspace_id, init_params, user_id, folder_id) VALUES ('2025-03-17 07:37:54.620836 +00:00', '2025-03-17 07:37:54.620887 +00:00', 'bd1e8b88-0302-11f0-87bb-5618c4394482', 'PostgreSQL 查询','database_search', '一个连接PostgreSQL数据库执行SQL查询的工具', e'\ndef queryPgSQL(database, user, password, host, port, query):\n    import psycopg2\n    import json\n    from datetime import datetime\n\n    # 自定义 JSON 序列化函数\n    def default_serializer(obj):\n        from decimal import Decimal\n        if isinstance(obj, datetime):\n            return obj.isoformat()  # 将 datetime 转换为 ISO 格式字符串\n        elif isinstance(obj, Decimal):\n            return float(obj)  # 将 Decimal 转换为 float\n        raise TypeError(f\"Type {type(obj)} not serializable\")\n\n    # 数据库连接信息\n    conn_params = {\n        \"dbname\": database,\n        \"user\": user,\n        \"password\": password,\n        \"host\": host,\n        \"port\": port\n    }\n    try:\n        # 建立连接\n        conn = psycopg2.connect(**conn_params)\n        print(\"连接成功！\")\n        # 创建游标对象\n        cursor = conn.cursor()\n        # 执行查询语句\n        cursor.execute(query)\n        # 获取查询结果\n        rows = cursor.fetchall()\n        # 处理 bytes 类型的数据\n        columns = [desc[0] for desc in cursor.description]\n        result = [dict(zip(columns, row)) for row in rows]\n        # 转换为 JSON 格式\n        json_result = json.dumps(result, default=default_serializer, ensure_ascii=False)\n        return json_result\n    except Exception as e:\n        print(f\"发生错误：{e}\")\n        raise e\n    finally:\n        # 关闭游标和连接\n        if cursor:\n            cursor.close()\n        if conn:\n            conn.close()', '[{\"name\": \"query\", \"type\": \"string\", \"source\": \"reference\", \"is_required\": true}]', '[{\"attrs\": {\"maxlength\": 200, \"minlength\": 1, \"show-word-limit\": true}, \"field\": \"host\", \"label\": \"host\", \"required\": true, \"input_type\": \"TextInput\", \"props_info\": {\"rules\": [{\"message\": \"host 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"host长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"x\", \"show_default_value\": false}, {\"attrs\": {\"maxlength\": 20, \"minlength\": 1, \"show-word-limit\": true}, \"field\": \"port\", \"label\": \"port\", \"required\": true, \"input_type\": \"TextInput\", \"props_info\": {\"rules\": [{\"message\": \"port 为必填属性\", \"required\": true}, {\"max\": 20, \"min\": 1, \"message\": \"port长度在 1 到 20 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"5432\", \"show_default_value\": false}, {\"attrs\": {\"maxlength\": 200, \"minlength\": 1, \"show-word-limit\": true}, \"field\": \"user\", \"label\": \"user\", \"required\": true, \"input_type\": \"TextInput\", \"props_info\": {\"rules\": [{\"message\": \"user 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"user长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"root\", \"show_default_value\": false}, {\"attrs\": {\"type\": \"password\", \"maxlength\": 200, \"minlength\": 1, \"show-password\": true, \"show-word-limit\": true}, \"field\": \"password\", \"label\": \"password\", \"required\": true, \"input_type\": \"PasswordInput\", \"props_info\": {\"rules\": [{\"message\": \"password 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"password长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"x\", \"show_default_value\": false}, {\"attrs\": {\"maxlength\": 200, \"minlength\": 1, \"show-word-limit\": true}, \"field\": \"database\", \"label\": \"database\", \"required\": true, \"input_type\": \"TextInput\", \"props_info\": {\"rules\": [{\"message\": \"database 为必填属性\", \"required\": true}, {\"max\": 200, \"min\": 1, \"message\": \"database长度在 1 到 200 个字符\", \"trigger\": \"blur\"}]}, \"default_value\": \"x\", \"show_default_value\": false}]', './tool/postgresql/icon.png', true, 'INTERNAL', 'INTERNAL', null, 'None', null, 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', 'default');\n"
  },
  {
    "path": "apps/tools/models/__init__.py",
    "content": "# -*- coding: utf-8 -*-\n\nfrom .tool import *\nfrom .tool_workflow import *"
  },
  {
    "path": "apps/tools/models/tool.py",
    "content": "import uuid_utils.compat as uuid\nfrom django.db import models\nfrom mptt.fields import TreeForeignKey\nfrom mptt.models import MPTTModel\n\nfrom common.encoder.encoder import SystemEncoder\nfrom common.mixins.app_model_mixin import AppModelMixin\nfrom knowledge.models.knowledge_action import State\nfrom users.models import User\n\n\nclass ToolFolder(MPTTModel, AppModelMixin):\n    id = models.CharField(primary_key=True, max_length=64, editable=False, verbose_name=\"主键id\")\n    name = models.CharField(max_length=64, verbose_name=\"文件夹名称\", db_index=True)\n    desc = models.CharField(max_length=200, null=True, blank=True, verbose_name=\"描述\")\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    parent = TreeForeignKey('self', on_delete=models.DO_NOTHING, null=True, blank=True, related_name='children')\n\n    class Meta:\n        db_table = \"tool_folder\"\n\n    class MPTTMeta:\n        order_insertion_by = ['name']\n\n\nclass ToolScope(models.TextChoices):\n    SHARED = \"SHARED\", '共享'\n    WORKSPACE = \"WORKSPACE\", \"工作空间可用\"\n    INTERNAL = \"INTERNAL\", '内置'\n\n\nclass ToolType(models.TextChoices):\n    INTERNAL = \"INTERNAL\", '内置'\n    CUSTOM = \"CUSTOM\", \"自定义\"\n    SKILL = \"SKILL\", \"技能\"\n    MCP = \"MCP\", \"MCP工具\"\n    DATA_SOURCE = \"DATA_SOURCE\", \"数据源\"\n    WORKFLOW = \"WORKFLOW\"\n\n\nclass ToolTaskTypeChoices(models.TextChoices):\n    APPLICATION = 'APPLICATION'\n    KNOWLEDGE = 'KNOWLEDGE'\n    TOOL = 'TOOL'\n    TRIGGER = 'TRIGGER'\n\n\nclass Tool(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n    name = models.CharField(max_length=64, verbose_name=\"工具名称\", db_index=True)\n    desc = models.CharField(max_length=128, verbose_name=\"描述\")\n    code = models.CharField(max_length=102400, verbose_name=\"python代码\")\n    input_field_list = models.JSONField(verbose_name=\"输入字段列表\", default=list)\n    init_field_list = models.JSONField(verbose_name=\"启动字段列表\", default=list)\n    icon = models.CharField(max_length=256, verbose_name=\"工具库icon\", default=\"\")\n    is_active = models.BooleanField(default=True, db_index=True)\n    scope = models.CharField(max_length=20, verbose_name='可用范围', choices=ToolScope.choices,\n                             default=ToolScope.WORKSPACE, db_index=True)\n    tool_type = models.CharField(max_length=20, verbose_name='工具类型', choices=ToolType.choices,\n                                 default=ToolType.CUSTOM, db_index=True)\n    template_id = models.CharField(max_length=128, verbose_name=\"模版id\", null=True, default=None, db_index=True)\n    folder = models.ForeignKey(ToolFolder, on_delete=models.DO_NOTHING, verbose_name=\"文件夹id\", default='default')\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    init_params = models.CharField(max_length=102400, verbose_name=\"初始化参数\", null=True)\n    label = models.CharField(max_length=128, verbose_name=\"标签\", null=True, db_index=True)\n    version = models.CharField(max_length=64, verbose_name=\"版本号\", null=True, default=None)\n\n    class Meta:\n        db_table = \"tool\"\n\n\nclass ToolRecord(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    tool = models.ForeignKey(Tool, on_delete=models.SET_NULL, null=True)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    source_type = models.CharField(verbose_name=\"触发器任务类型\", choices=ToolTaskTypeChoices.choices,\n                                   default=ToolTaskTypeChoices.APPLICATION, max_length=256)\n    source_id = models.UUIDField(verbose_name=\"资源id\")\n    meta = models.JSONField(default=dict, encoder=SystemEncoder)\n    state = models.CharField(verbose_name='状态', max_length=20, choices=State.choices, default=State.STARTED)\n    run_time = models.FloatField(verbose_name=\"运行时长\", default=0)\n\n    class Meta:\n        db_table = \"tool_record\"\n"
  },
  {
    "path": "apps/tools/models/tool_workflow.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： tool_workflow.py\n    @date：2026/3/3 13:59\n    @desc:\n\"\"\"\nfrom django.db import models\n\nfrom common.mixins.app_model_mixin import AppModelMixin\nimport uuid_utils.compat as uuid\n\nfrom tools.models import Tool\n\n\nclass ToolWorkflow(AppModelMixin):\n    \"\"\"\n    知识库工作流表\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    tool = models.OneToOneField(Tool, on_delete=models.CASCADE, verbose_name=\"工具\",\n                                db_constraint=False, related_name='workflow')\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    work_flow = models.JSONField(verbose_name=\"工作流数据\", default=dict)\n    is_publish = models.BooleanField(verbose_name=\"是否发布\", default=False, db_index=True)\n    publish_time = models.DateTimeField(verbose_name=\"发布时间\", null=True, blank=True)\n\n    class Meta:\n        db_table = \"tool_workflow\"\n\n\nclass ToolWorkflowVersion(AppModelMixin):\n    \"\"\"\n    知识库工作流版本表 - 记录工作流历史版本\n    \"\"\"\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    tool = models.ForeignKey(Tool, on_delete=models.CASCADE, verbose_name=\"工具\", db_constraint=False)\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    name = models.CharField(verbose_name=\"版本名称\", max_length=128, default=\"\")\n    work_flow = models.JSONField(verbose_name=\"工作流数据\", default=dict)\n    publish_user_id = models.UUIDField(verbose_name=\"发布者id\", max_length=128, default=None, null=True)\n    publish_user_name = models.CharField(verbose_name=\"发布者名称\", max_length=128, default=\"\")\n\n    class Meta:\n        db_table = \"tool_workflow_version\"\n"
  },
  {
    "path": "apps/tools/serializers/__init__.py",
    "content": "# coding=utf-8\n"
  },
  {
    "path": "apps/tools/serializers/tool.py",
    "content": "# -*- coding: utf-8 -*-\nimport asyncio\nimport base64\nimport io\nimport json\nimport os\nimport pickle\nimport re\nimport tempfile\nimport zipfile\nfrom typing import Dict\nfrom django.core.cache import cache\nimport requests\nimport uuid_utils.compat as uuid\nfrom django.core import validators\nfrom django.db import transaction\nfrom django.db.models import QuerySet, Q, Subquery, OuterRef, CharField, Value, When, Case\nfrom django.http import HttpResponse\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\nfrom langchain_mcp_adapters.client import MultiServerMCPClient\nfrom pylint.lint import Run\nfrom pylint.reporters import JSON2Reporter\nfrom rest_framework import serializers, status\n\nfrom application.models import Application\nfrom common.constants.cache_version import Cache_Version\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.db.search import page_search, native_page_search, native_search\nfrom common.exception.app_exception import AppApiException\nfrom common.field.common import UploadedImageField\nfrom common.result import result\nfrom common.utils.common import get_file_content\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.rsa_util import rsa_long_decrypt, rsa_long_encrypt\nfrom common.utils.tool_code import ToolExecutor\nfrom knowledge.models import File, FileSourceType, Knowledge\nfrom maxkb.const import PROJECT_DIR\nfrom system_manage.models import AuthTargetType, WorkspaceUserResourcePermission\nfrom system_manage.models.resource_mapping import ResourceMapping\nfrom system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer\nfrom system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer\nfrom tools.models import Tool, ToolScope, ToolFolder, ToolType, ToolRecord\nfrom tools.models.tool_workflow import ToolWorkflow\nfrom trigger.models import TriggerTask, Trigger\nfrom users.serializers.user import is_workspace_manage\n\ntool_executor = ToolExecutor()\n\n\nclass ToolInstance:\n    def __init__(self, tool: dict, version: str):\n        self.tool = tool\n        self.version = version\n\n\nALLOWED_CLASSES = {\n    (\"builtins\", \"dict\"),\n    ('uuid', 'UUID'),\n    (\"tools.serializers.tool\", \"ToolInstance\")\n}\n\n\ndef to_dict(message, file_name):\n    return {\n        'line': message.line,\n        'column': message.column,\n        'endLine': message.end_line,\n        'endColumn': message.end_column,\n        'message': (message.msg or \"\").replace(file_name, 'code'),\n        'type': message.category\n    }\n\n\ndef get_file_name():\n    file_name = f\"{uuid.uuid7()}\"\n    pylint_dir = os.path.join(PROJECT_DIR, 'data', 'pylint')\n    if not os.path.exists(pylint_dir):\n        os.makedirs(pylint_dir, 0o700, exist_ok=True)\n        os.chmod(os.path.dirname(pylint_dir), 0o700)\n    return os.path.join(pylint_dir, file_name)\n\n\nclass RestrictedUnpickler(pickle.Unpickler):\n\n    def find_class(self, folder, name):\n        if (folder, name) in ALLOWED_CLASSES:\n            return super().find_class(folder, name)\n        raise pickle.UnpicklingError(\"global '%s.%s' is forbidden\" %\n                                     (folder, name))\n\n\ndef encryption(message: str):\n    \"\"\"\n        加密敏感字段数据  加密方式是 如果密码是 1234567890  那么给前端则是 123******890\n    :param message:\n    :return:\n    \"\"\"\n    if type(message) != str:\n        return message\n    if message == \"\":\n        return \"\"\n    max_pre_len = 8\n    max_post_len = 4\n    message_len = len(message)\n    pre_len = int(message_len / 5 * 2)\n    post_len = int(message_len / 5 * 1)\n    pre_str = \"\".join([message[index] for index in\n                       range(0,\n                             max_pre_len if pre_len > max_pre_len else 1 if pre_len <= 0 else int(\n                                 pre_len))])\n    end_str = \"\".join(\n        [message[index] for index in\n         range(message_len - (int(post_len) if pre_len < max_post_len else max_post_len),\n               message_len)])\n    content = \"***************\"\n    return pre_str + content + end_str\n\n\ndef validate_mcp_config(servers: Dict):\n    async def validate():\n        client = MultiServerMCPClient(servers)\n        await client.get_tools()\n\n    try:\n        asyncio.run(validate())\n    except Exception as e:\n        maxkb_logger.error(f\"validate mcp config error: {e}, servers: {servers}\")\n        raise serializers.ValidationError(_('MCP configuration is invalid'))\n\n\nclass ToolModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Tool\n        fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list', 'init_field_list', 'init_params',\n                  'scope', 'is_active', 'user_id', 'template_id', 'workspace_id', 'folder_id', 'tool_type', 'label',\n                  'version', 'create_time', 'update_time']\n\n\nclass ToolRecordModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = ToolRecord\n        fields = ['id', 'workspace_id', 'tool_id', 'source_type', 'source_id', 'meta', 'state', 'run_time',\n                  'create_time', 'update_time']\n\n\nclass ToolExportModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Tool\n        fields = ['id', 'name', 'icon', 'desc', 'code', 'input_field_list', 'init_field_list',\n                  'scope', 'is_active', 'user_id', 'template_id', 'workspace_id', 'folder_id', 'tool_type', 'label',\n                  'create_time', 'update_time']\n\n\nclass UploadedFileField(serializers.FileField):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n\n    def to_representation(self, value):\n        return value\n\n\nclass ToolInputField(serializers.Serializer):\n    name = serializers.CharField(required=True, label=_('variable name'))\n    is_required = serializers.BooleanField(required=True, label=_('required'))\n    type = serializers.CharField(required=True, label=_('type'), validators=[\n        validators.RegexValidator(regex=re.compile(\"^string|int|dict|array|float$\"),\n                                  message=_('fields only support string|int|dict|array|float'), code=500)\n    ])\n    source = serializers.CharField(required=True, label=_('source'), validators=[\n        validators.RegexValidator(regex=re.compile(\"^custom|reference$\"),\n                                  message=_('The field only supports custom|reference'), code=500)\n    ])\n\n\nclass InitField(serializers.Serializer):\n    field = serializers.CharField(required=True, label=_('field name'))\n    label = serializers.CharField(required=True, label=_('field label'))\n    required = serializers.BooleanField(required=True, label=_('required'))\n    input_type = serializers.CharField(required=True, label=_('input type'))\n    default_value = serializers.CharField(required=False, allow_null=True, allow_blank=True)\n    show_default_value = serializers.BooleanField(required=False, default=False)\n    props_info = serializers.DictField(required=False, default=dict)\n    attrs = serializers.DictField(required=False, default=dict)\n\n\nclass ToolCreateRequest(serializers.Serializer):\n    name = serializers.CharField(required=True, label=_('tool name'))\n\n    desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('tool description'))\n\n    code = serializers.CharField(required=True, label=_('tool content'))\n\n    input_field_list = serializers.ListField(required=False, default=list, label=_('input field list'))\n\n    init_field_list = serializers.ListField(required=False, default=list, label=_('init field list'))\n\n    is_active = serializers.BooleanField(required=False, label=_('Is active'))\n\n    folder_id = serializers.CharField(required=False, allow_null=True)\n\n\nclass ToolEditRequest(serializers.Serializer):\n    name = serializers.CharField(required=False, label=_('tool name'), allow_null=True)\n    desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('tool description'))\n    code = serializers.CharField(required=False, label=_('tool content'), allow_null=True, )\n    input_field_list = serializers.ListField(required=False, default=list, allow_null=True, label=_('input field list'))\n    init_field_list = serializers.ListField(required=False, default=list, allow_null=True, label=_('init field list'))\n    init_params = serializers.DictField(required=False, default=dict, allow_null=True, label=_('init params'))\n    is_active = serializers.BooleanField(required=False, label=_('Is active'), allow_null=True, )\n    folder_id = serializers.CharField(required=False, allow_null=True)\n\n\nclass AddInternalToolRequest(serializers.Serializer):\n    name = serializers.CharField(required=False, label=_(\"tool name\"), allow_null=True, allow_blank=True)\n    folder_id = serializers.CharField(required=False, allow_null=True, label=_(\"folder id\"))\n\n\nclass DebugField(serializers.Serializer):\n    name = serializers.CharField(required=True, label=_('variable name'))\n    value = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('variable value'))\n\n\nclass ToolDebugRequest(serializers.Serializer):\n    code = serializers.CharField(required=True, label=_('tool content'))\n    input_field_list = serializers.ListField(required=False, default=list, label=_('input field list'))\n    init_field_list = serializers.ListField(required=False, default=list, label=_('init field list'))\n    init_params = serializers.DictField(required=False, default=dict, label=_('init params'))\n    debug_field_list = DebugField(required=True, many=True)\n\n\nclass PylintInstance(serializers.Serializer):\n    code = serializers.CharField(required=True, allow_null=True, allow_blank=True, label=_('function content'))\n\n\nclass ToolSerializer(serializers.Serializer):\n    class Query(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        folder_id = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('folder id'))\n        name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('tool name'))\n        user_id = serializers.UUIDField(required=False, allow_null=True, label=_('user id'))\n        scope = serializers.CharField(required=True, label=_('scope'))\n        tool_type = serializers.CharField(required=False, label=_('tool type'), allow_null=True, allow_blank=True)\n        create_user = serializers.UUIDField(required=False, label=_('create user'), allow_null=True)\n\n        def get_query_set(self, workspace_manage, is_x_pack_ee):\n            tool_query_set = QuerySet(Tool).filter(workspace_id=self.data.get('workspace_id'))\n            folder_query_set = QuerySet(ToolFolder)\n            default_query_set = QuerySet(Tool)\n\n            workspace_id = self.data.get('workspace_id')\n            user_id = self.data.get('user_id')\n            scope = self.data.get('scope')\n            tool_type = self.data.get('tool_type')\n            desc = self.data.get('desc')\n            name = self.data.get('name')\n            folder_id = self.data.get('folder_id')\n            create_user = self.data.get('create_user')\n\n            if workspace_id is not None:\n                folder_query_set = folder_query_set.filter(workspace_id=workspace_id)\n                default_query_set = default_query_set.filter(workspace_id=workspace_id)\n            if folder_id is not None and folder_id != workspace_id:\n                folder_query_set = folder_query_set.filter(parent=folder_id)\n                default_query_set = default_query_set.filter(folder_id=folder_id)\n            if name is not None:\n                folder_query_set = folder_query_set.filter(name__icontains=name)\n                default_query_set = default_query_set.filter(name__icontains=name)\n            if desc is not None:\n                folder_query_set = folder_query_set.filter(desc__icontains=desc)\n                default_query_set = default_query_set.filter(desc__icontains=desc)\n            if create_user is not None:\n                tool_query_set = tool_query_set.filter(user_id=create_user)\n                folder_query_set = folder_query_set.filter(user_id=create_user)\n\n            default_query_set = default_query_set.order_by(\"-create_time\")\n\n            if scope is not None:\n                tool_query_set = tool_query_set.filter(scope=scope)\n            if tool_type:\n                tool_query_set = tool_query_set.filter(tool_type=tool_type)\n\n            query_set_dict = {\n                'tool_query_set': tool_query_set,\n                'default_query_set': default_query_set,\n            }\n            if not workspace_manage:\n                query_set_dict['workspace_user_resource_permission_query_set'] = QuerySet(\n                    WorkspaceUserResourcePermission).filter(\n                    auth_target_type=\"TOOL\",\n                    workspace_id=workspace_id,\n                    user_id=user_id\n                )\n            return query_set_dict\n\n        def get_authorized_query_set(self):\n            default_query_set = QuerySet(Tool)\n            tool_type = self.data.get('tool_type')\n            desc = self.data.get('desc')\n            name = self.data.get('name')\n            create_user = self.data.get('create_user')\n\n            default_query_set = default_query_set.filter(workspace_id='None')\n            default_query_set = default_query_set.filter(scope=ToolScope.SHARED)\n            if name is not None:\n                default_query_set = default_query_set.filter(name__icontains=name)\n            if desc is not None:\n                default_query_set = default_query_set.filter(desc__icontains=desc)\n            if create_user is not None:\n                default_query_set = default_query_set.filter(user_id=create_user)\n            if tool_type:\n                default_query_set = default_query_set.filter(tool_type=tool_type)\n\n            default_query_set = default_query_set.order_by(\"-create_time\")\n\n            return default_query_set\n\n        @staticmethod\n        def is_x_pack_ee():\n            workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n            role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n            return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None\n\n        def get_tools(self):\n            self.is_valid(raise_exception=True)\n\n            workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id'))\n            is_x_pack_ee = self.is_x_pack_ee()\n            results = native_search(\n                self.get_query_set(workspace_manage, is_x_pack_ee),\n                get_file_content(\n                    os.path.join(\n                        PROJECT_DIR,\n                        \"apps\", \"tools\", 'sql',\n                        'list_tool.sql' if workspace_manage else (\n                            'list_tool_user_ee.sql' if is_x_pack_ee else 'list_tool_user.sql'\n                        )\n                    )\n                ),\n            )\n\n            get_authorized_tool = DatabaseModelManage.get_model(\"get_authorized_tool\")\n            shared_queryset = QuerySet(Tool).none()\n            if get_authorized_tool is not None:\n                shared_queryset = self.get_authorized_query_set()\n                shared_queryset = get_authorized_tool(shared_queryset, self.data.get('workspace_id'))\n\n            return {\n                'shared_tools': [\n                    ToolModelSerializer(data).data for data in shared_queryset\n                ],\n                'tools': [\n                    {\n                        **tool,\n                        'input_field_list': json.loads(tool.get('input_field_list', '[]')),\n                        'init_field_list': json.loads(tool.get('init_field_list', '[]')),\n                    } for tool in results if tool['resource_type'] == 'tool'\n                ],\n            }\n\n    class Create(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n\n        @transaction.atomic\n        def insert(self, instance, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                ToolCreateRequest(data=instance).is_valid(raise_exception=True)\n                # 校验代码是否包括禁止的关键字\n                if instance.get('tool_type') == ToolType.MCP:\n                    ToolExecutor().validate_mcp_transport(instance.get('code', ''))\n\n            tool_id = uuid.uuid7()\n            Tool(\n                id=tool_id,\n                name=instance.get('name'),\n                desc=instance.get('desc'),\n                code=instance.get('code'),\n                user_id=self.data.get('user_id'),\n                workspace_id=self.data.get('workspace_id'),\n                input_field_list=instance.get('input_field_list', []),\n                init_field_list=instance.get('init_field_list', []),\n                scope=instance.get('scope', ToolScope.WORKSPACE),\n                tool_type=instance.get('tool_type', ToolType.CUSTOM),\n                folder_id=instance.get('folder_id', self.data.get('workspace_id')),\n                is_active=False\n            ).save()\n\n            # 自动授权给创建者\n            UserResourcePermissionSerializer(data={\n                'workspace_id': self.data.get('workspace_id'),\n                'user_id': self.data.get('user_id'),\n                'auth_target_type': AuthTargetType.TOOL.value\n            }).auth_resource(str(tool_id))\n            if instance.get('tool_type') == ToolType.WORKFLOW:\n                ToolWorkflow(id=uuid.uuid7(), tool_id=tool_id, work_flow=instance.get('work_flow', {})).save()\n            # 如果是SKILL类型的工具，修改file表中对应的记录\n            if instance.get('tool_type') == ToolType.SKILL:\n                file_id = instance.get('code')\n                old_file = QuerySet(File).filter(id=file_id).first()\n                if old_file:\n                    # 创建新的文件副本,不复制实际文件内容\n                    new_file_id = uuid.uuid7()\n                    new_file = File(\n                        id=new_file_id,\n                        file_name=old_file.file_name,\n                        file_size=old_file.file_size,\n                        sha256_hash=old_file.sha256_hash,\n                        source_type=FileSourceType.TOOL,\n                        source_id=tool_id,\n                        meta=old_file.meta,\n                    )\n                    new_file.save(old_file.get_bytes())\n                    # 更新工具的code为新的文件id\n                    QuerySet(Tool).filter(id=tool_id).update(code=str(new_file_id))\n            return ToolSerializer.Operate(data={\n                'id': tool_id, 'workspace_id': self.data.get('workspace_id')\n            }).one()\n\n    class TestConnection(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        code = serializers.CharField(required=True, label=_('tool content'))\n\n        def test_connection(self):\n            self.is_valid(raise_exception=True)\n            # 校验代码是否包括禁止的关键字\n            ToolExecutor().validate_mcp_transport(self.data.get('code', ''))\n\n            # 校验mcp json\n            validate_mcp_config(json.loads(self.data.get('code')))\n            return True\n\n    class Debug(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n\n        def debug(self, debug_instance):\n            self.is_valid(raise_exception=True)\n            ToolDebugRequest(data=debug_instance).is_valid(raise_exception=True)\n            input_field_list = debug_instance.get('input_field_list')\n            code = debug_instance.get('code')\n            debug_field_list = debug_instance.get('debug_field_list')\n            init_params = debug_instance.get('init_params')\n            params = {field.get('name'): self.convert_value(field.get('name'), field.get('value'), field.get('type'),\n                                                            field.get('is_required'))\n                      for field in\n                      [{'value': self.get_field_value(debug_field_list, field.get('name'), field.get('is_required')),\n                        **field} for field in\n                       input_field_list]}\n            # 合并初始化参数\n            if init_params is not None:\n                all_params = init_params | params\n            else:\n                all_params = params\n            return tool_executor.exec_code(code, all_params)\n\n        @staticmethod\n        def get_field_value(debug_field_list, name, is_required):\n            result = [field for field in debug_field_list if field.get('name') == name]\n            if len(result) > 0:\n                return result[-1].get('value')\n            if is_required:\n                raise AppApiException(500, f\"{name}\" + _('field has no value set'))\n            return None\n\n        @staticmethod\n        def convert_value(name: str, value: str, _type: str, is_required: bool):\n            if not is_required and (value is None or (isinstance(value, str) and len(value.strip()) == 0)):\n                return None\n            try:\n                if _type == 'int':\n                    return int(value)\n                if _type == 'boolean':\n                    value = 0 if ['0', '[]'].__contains__(value) else value\n                    return bool(value)\n                if _type == 'float':\n                    return float(value)\n                if _type == 'dict':\n                    v = json.loads(value)\n                    if isinstance(v, dict):\n                        return v\n                    raise Exception(_('type error'))\n                if _type == 'array':\n                    v = json.loads(value)\n                    if isinstance(v, list):\n                        return v\n                    raise Exception(_('type error'))\n                return value\n            except Exception as e:\n                raise AppApiException(500, _('Field: {name} Type: {_type} Value: {value} Type conversion error').format(\n                    name=name, type=_type, value=value\n                ))\n\n    class Operate(serializers.Serializer):\n        id = serializers.UUIDField(required=True, label=_('tool id'))\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n\n        def is_one_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Tool).filter(id=self.data.get('id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                get_authorized_tool = DatabaseModelManage.get_model('get_authorized_tool')\n                if get_authorized_tool:\n                    if not get_authorized_tool(QuerySet(Tool).filter(id=self.data.get('id')), workspace_id).exists():\n                        raise AppApiException(500, _('Tool id does not exist'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Tool).filter(id=self.data.get('id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Tool id does not exist'))\n\n        @transaction.atomic\n        def edit(self, instance, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                ToolEditRequest(data=instance).is_valid(raise_exception=True)\n                # 校验代码是否包括禁止的关键字\n                if instance.get('tool_type') == ToolType.MCP:\n                    ToolExecutor().validate_mcp_transport(instance.get('code', ''))\n\n            if not QuerySet(Tool).filter(id=self.data.get('id')).exists():\n                raise serializers.ValidationError(_('Tool not found'))\n\n            edit_field_list = ['name', 'desc', 'code', 'icon', 'input_field_list', 'init_field_list', 'init_params',\n                               'is_active', 'folder_id']\n            edit_dict = {field: instance.get(field) for field in edit_field_list if (\n                    field in instance and instance.get(field) is not None)}\n\n            tool = QuerySet(Tool).filter(id=self.data.get('id')).first()\n            if 'init_params' in edit_dict:\n                if edit_dict['init_field_list'] is not None:\n                    rm_key = []\n                    for key in edit_dict['init_params']:\n                        if key not in [field['field'] for field in edit_dict['init_field_list']]:\n                            rm_key.append(key)\n                    for key in rm_key:\n                        edit_dict['init_params'].pop(key)\n                if tool.init_params:\n                    old_init_params = json.loads(rsa_long_decrypt(tool.init_params))\n                    for key in edit_dict['init_params']:\n                        if key in old_init_params and edit_dict['init_params'][key] == encryption(old_init_params[key]):\n                            edit_dict['init_params'][key] = old_init_params[key]\n                edit_dict['init_params'] = rsa_long_encrypt(json.dumps(edit_dict['init_params']))\n\n            edit_dict['update_time'] = timezone.now()\n            QuerySet(Tool).filter(id=self.data.get('id')).update(**edit_dict)\n            if 'is_active' in instance:\n                QuerySet(TriggerTask).filter(source_type=\"TOOL\", source_id=self.data.get('id')).update(\n                    is_active=instance.get('is_active'))\n\n            # 如果是SKILL类型的工具，修改file表中对应的记录\n            if instance.get('tool_type') == ToolType.SKILL:\n                old_file_id = tool.code\n                file_id = instance.get('code')\n                if old_file_id != file_id:\n                    QuerySet(File).filter(id=old_file_id).delete()\n                    QuerySet(File).filter(id=file_id).update(source_id=tool.id, source_type=FileSourceType.TOOL)\n\n            return self.one()\n\n        @transaction.atomic\n        def delete(self):\n            from trigger.handler.simple_tools import deploy\n            from trigger.serializers.trigger import TriggerModelSerializer\n\n            self.is_valid(raise_exception=True)\n            tool = QuerySet(Tool).filter(id=self.data.get('id')).first()\n            if tool.template_id is None and tool.icon != '':\n                QuerySet(File).filter(id=tool.icon.split('/')[-1]).delete()\n            if tool.tool_type == ToolType.SKILL:\n                QuerySet(File).filter(id=tool.code).delete()\n            QuerySet(WorkspaceUserResourcePermission).filter(target=tool.id).delete()\n            QuerySet(Tool).filter(id=self.data.get('id')).delete()\n            ResourceMapping.objects.filter(target_id=self.data.get('id')).delete()\n            QuerySet(ToolRecord).filter(tool_id=self.data.get('id')).delete()\n            trigger_ids = list(\n                QuerySet(TriggerTask).filter(\n                    source_type=\"TOOL\", source_id=self.data.get('id')\n                ).values('trigger_id').distinct()\n            )\n            QuerySet(TriggerTask).filter(source_type=\"TOOL\", source_id=self.data.get('id')).delete()\n            for trigger_id in trigger_ids:\n                trigger = Trigger.objects.filter(id=trigger_id['trigger_id']).first()\n                if trigger and trigger.is_active:\n                    deploy(TriggerModelSerializer(trigger).data, **{})\n\n        def one(self):\n            self.is_one_valid(raise_exception=True)\n            tool = QuerySet(Tool).filter(id=self.data.get('id')).select_related('user').first()\n            nick_name = tool.user.nick_name if tool and tool.user else None\n            if tool.init_params:\n                tool.init_params = json.loads(rsa_long_decrypt(tool.init_params))\n            if tool.init_field_list:\n                password_fields = [i[\"field\"] for i in tool.init_field_list if\n                                   i.get(\"input_type\") == \"PasswordInput\"]\n                if tool.init_params:\n                    for k in tool.init_params:\n                        if k in password_fields and tool.init_params[k]:\n                            tool.init_params[k] = encryption(tool.init_params[k])\n            if tool.tool_type == 'SKILL':\n                skill_file = QuerySet(File).filter(id=tool.code).first()\n                skill_file_dict = {\n                    'id': str(skill_file.id),\n                    'name': skill_file.file_name,\n                    'size': skill_file.file_size,\n                } if skill_file else None\n            work_flow = {}\n            if tool.tool_type == 'WORKFLOW':\n                tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=tool.id).first()\n                if tool_workflow:\n                    work_flow = tool_workflow.work_flow\n\n            return {\n                **ToolModelSerializer(tool).data,\n                'init_params': tool.init_params if tool.init_params else {},\n                'nick_name': nick_name,\n                'fileList': [skill_file_dict] if tool.tool_type == 'SKILL' else [],\n                'work_flow': work_flow\n            }\n\n        def export(self):\n            try:\n                self.is_valid()\n                id = self.data.get('id')\n                tool = QuerySet(Tool).filter(id=id).first()\n                tool_dict = ToolExportModelSerializer(tool).data\n                # 如果是SKILL类型的工具，校验文件是否存在\n                if tool.tool_type == ToolType.SKILL:\n                    skill_file = QuerySet(File).filter(id=tool.code).first()\n                    if skill_file:\n                        tool_dict['code'] = base64.b64encode(skill_file.get_bytes()).decode('utf-8')\n                mk_instance = ToolInstance(tool_dict, 'v2')\n                tool_pickle = pickle.dumps(mk_instance)\n                response = HttpResponse(content_type='text/plain', content=tool_pickle)\n                response['Content-Disposition'] = f'attachment; filename=\"{tool.name}.tool\"'\n                return response\n            except Exception as e:\n                return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR)\n\n    class Pylint(serializers.Serializer):\n\n        def run(self, instance, is_valid=True):\n            if is_valid:\n                self.is_valid(raise_exception=True)\n                PylintInstance(data=instance).is_valid(raise_exception=True)\n            code = instance.get('code')\n            file_name = get_file_name()\n            with open(file_name, 'w') as file:\n                file.write(code)\n            reporter = JSON2Reporter(output=io.StringIO())\n            Run([file_name,\n                 \"--disable=line-too-long\",\n                 '--module-rgx=[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'],\n                reporter=reporter, exit=False)\n            os.remove(file_name)\n            return [to_dict(m, os.path.basename(file_name)) for m in reporter.messages]\n\n    class Import(serializers.Serializer):\n        file = UploadedFileField(required=True, label=_(\"file\"))\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n        workspace_id = serializers.CharField(required=True, label=_(\"workspace id\"))\n        folder_id = serializers.CharField(required=False, allow_null=True, label=_(\"folder id\"))\n\n        #\n        @transaction.atomic\n        def import_(self, scope=ToolScope.WORKSPACE):\n            self.is_valid()\n\n            user_id = self.data.get('user_id')\n            tool_instance_bytes = self.data.get('file').read()\n            try:\n                tool_instance = RestrictedUnpickler(io.BytesIO(tool_instance_bytes)).load()\n            except Exception as e:\n                raise AppApiException(1001, _(\"Unsupported file format\"))\n            if self.data.get('folder_id') is None:\n                folder_id = self.data.get('workspace_id')\n            else:\n                folder_id = self.data.get('folder_id')\n            tool = tool_instance.tool\n            tool_id = uuid.uuid7()\n            code = tool.get('code')\n            if tool.get('tool_type') == ToolType.SKILL:\n                skill_file_id = uuid.uuid7()\n                skill_file = File(\n                    id=skill_file_id,\n                    file_name=f\"{tool.get('name')}.zip\",\n                    source_type=FileSourceType.TOOL,\n                    source_id=tool_id,\n                    meta={}\n                )\n                skill_file.save(base64.b64decode(code))\n                code = skill_file_id\n            tool_model = Tool(\n                id=tool_id,\n                name=tool.get('name'),\n                desc=tool.get('desc'),\n                code=code,\n                user_id=user_id,\n                workspace_id=self.data.get('workspace_id'),\n                input_field_list=tool.get('input_field_list'),\n                init_field_list=tool.get('init_field_list', []),\n                tool_type=tool.get('tool_type'),\n                folder_id=folder_id,\n                scope=scope,\n                is_active=False\n            )\n            tool_model.save()\n\n            # 自动授权给创建者\n            UserResourcePermissionSerializer(data={\n                'workspace_id': self.data.get('workspace_id'),\n                'user_id': self.data.get('user_id'),\n                'auth_target_type': AuthTargetType.TOOL.value\n            }).auth_resource(str(tool_id))\n\n            return True\n\n    class IconOperate(serializers.Serializer):\n        id = serializers.UUIDField(required=True, label=_(\"function ID\"))\n        workspace_id = serializers.CharField(required=True, label=_(\"workspace id\"))\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n        image = UploadedImageField(required=True, label=_(\"picture\"))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Tool).filter(id=self.data.get('id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Tool id does not exist'))\n\n        def edit(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            tool = QuerySet(Tool).filter(id=self.data.get('id')).first()\n            if tool is None:\n                raise AppApiException(500, _('Function does not exist'))\n            # 删除旧的图片\n            if tool.icon != '':\n                QuerySet(File).filter(id=tool.icon.split('/')[-1]).delete()\n            if self.data.get('image') is None:\n                tool.icon = ''\n            else:\n                meta = {\n                    'debug': False\n                }\n                file_id = uuid.uuid7()\n                file = File(\n                    id=file_id,\n                    file_name=self.data.get('image').name,\n                    source_type=FileSourceType.TOOL,\n                    source_id=tool.id,\n                    meta=meta\n                )\n                file.save(self.data.get('image').read())\n\n                tool.icon = f'./oss/file/{file_id}'\n            tool.save()\n\n            return tool.icon\n\n    class InternalTool(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n        name = serializers.CharField(required=False, label=_(\"tool name\"), allow_null=True, allow_blank=True)\n\n        def get_internal_tools(self):\n            self.is_valid(raise_exception=True)\n            query_set = QuerySet(Tool)\n\n            if self.data.get('name', '') != '':\n                query_set = query_set.filter(\n                    Q(name__icontains=self.data.get('name')) |\n                    Q(desc__icontains=self.data.get('name'))\n                )\n\n            query_set = query_set.filter(\n                Q(scope=ToolScope.INTERNAL) &\n                Q(is_active=True)\n            )\n            return ToolModelSerializer(query_set, many=True).data\n\n    class AddInternalTool(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n        workspace_id = serializers.CharField(required=True, label=_(\"workspace id\"))\n        tool_id = serializers.UUIDField(required=True, label=_(\"tool id\"))\n\n        def add(self, instance, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                AddInternalToolRequest(data=instance).is_valid(raise_exception=True)\n\n            internal_tool = QuerySet(Tool).filter(id=self.data.get('tool_id')).first()\n            if internal_tool is None:\n                raise AppApiException(500, _('Tool does not exist'))\n\n            tool_id = uuid.uuid7()\n            tool = Tool(\n                id=tool_id,\n                name=instance.get('name', internal_tool.name),\n                desc=internal_tool.desc,\n                code=internal_tool.code,\n                user_id=self.data.get('user_id'),\n                icon=internal_tool.icon,\n                workspace_id=self.data.get('workspace_id'),\n                input_field_list=internal_tool.input_field_list,\n                init_field_list=internal_tool.init_field_list,\n                scope=ToolScope.WORKSPACE,\n                tool_type=ToolType.CUSTOM,\n                folder_id=instance.get('folder_id', self.data.get('workspace_id')),\n                template_id=internal_tool.id,\n                label=internal_tool.label,\n                is_active=False\n            )\n            tool.save()\n\n            # 自动授权给创建者\n            UserResourcePermissionSerializer(data={\n                'workspace_id': self.data.get('workspace_id'),\n                'user_id': self.data.get('user_id'),\n                'auth_target_type': AuthTargetType.TOOL.value\n            }).auth_resource(str(tool_id))\n\n            return ToolModelSerializer(tool).data\n\n    class StoreTool(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n        name = serializers.CharField(required=False, label=_(\"tool name\"), allow_null=True, allow_blank=True)\n\n        def get_appstore_tools(self):\n            self.is_valid(raise_exception=True)\n            # 下载zip文件\n            try:\n                res = requests.get('https://apps-assets.fit2cloud.com/stable/maxkb.json.zip', timeout=5)\n                res.raise_for_status()\n                # 创建临时文件保存zip\n                with tempfile.NamedTemporaryFile(delete=False, suffix='.zip') as temp_zip:\n                    temp_zip.write(res.content)\n                    temp_zip_path = temp_zip.name\n\n                try:\n                    # 解压zip文件\n                    with zipfile.ZipFile(temp_zip_path, 'r') as zip_ref:\n                        # 获取zip中的第一个文件（假设只有一个json文件）\n                        json_filename = zip_ref.namelist()[0]\n                        json_content = zip_ref.read(json_filename)\n\n                    # 将json转换为字典\n                    tool_store = json.loads(json_content.decode('utf-8'))\n                    tag_dict = {tag['name']: tag['key'] for tag in tool_store['additionalProperties']['tags']}\n                    filter_apps = []\n                    for tool in tool_store['apps']:\n                        if self.data.get('name', '') != '':\n                            if self.data.get('name').lower() not in tool.get('name', '').lower():\n                                continue\n                        if not tool['downloadUrl'].endswith('.tool'):\n                            continue\n                        versions = tool.get('versions', [])\n                        tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else ''\n                        tool['version'] = next(\n                            (version.get('name') for version in versions if\n                             version.get('downloadUrl') == tool['downloadUrl']),\n                        )\n                        filter_apps.append(tool)\n\n                    tool_store['apps'] = filter_apps\n                    return tool_store\n                finally:\n                    # 清理临时文件\n                    os.unlink(temp_zip_path)\n            except Exception as e:\n                maxkb_logger.error(f\"fetch appstore tools error: {e}\")\n                return {'apps': [], 'additionalProperties': {'tags': []}}\n\n    class AddStoreTool(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n        workspace_id = serializers.CharField(required=True, label=_(\"workspace id\"))\n        tool_id = serializers.CharField(required=True, label=_(\"tool id\"))\n\n        def add(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                AddInternalToolRequest(data=instance).is_valid(raise_exception=True)\n\n            versions = instance.get('versions', [])\n            download_url = instance.get('download_url')\n            # 查找匹配的版本名称\n            version_name = next(\n                (version.get('name') for version in versions if version.get('downloadUrl') == download_url),\n            )\n            res = requests.get(download_url, timeout=5)\n            tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool\n            tool_id = uuid.uuid7()\n            # 如果是SKILL类型的工具，保存文件内容到file表，并将code替换为file_id\n            if tool_data.get('tool_type') == ToolType.SKILL:\n                skill_file_id = uuid.uuid7()\n                skill_file = File(\n                    id=skill_file_id,\n                    file_name=f\"{tool_data.get('name')}.zip\",\n                    source_type=FileSourceType.TOOL,\n                    source_id=tool_id,\n                    meta={}\n                )\n                skill_file.save(base64.b64decode(tool_data.get('code')))\n                tool_data['code'] = skill_file_id\n            tool = Tool(\n                id=tool_id,\n                name=instance.get('name'),\n                desc=tool_data.get('desc'),\n                code=tool_data.get('code'),\n                user_id=self.data.get('user_id'),\n                icon=instance.get('icon', ''),\n                workspace_id=self.data.get('workspace_id'),\n                input_field_list=tool_data.get('input_field_list', []),\n                init_field_list=tool_data.get('init_field_list', []),\n                scope=ToolScope.WORKSPACE,\n                tool_type=tool_data.get('tool_type', ToolType.CUSTOM),\n                folder_id=instance.get('folder_id', self.data.get('workspace_id')),\n                template_id=self.data.get('tool_id'),\n                label=instance.get('label'),\n                version=version_name,\n                is_active=False\n            )\n            tool.save()\n\n            # 自动授权给创建者\n            UserResourcePermissionSerializer(data={\n                'workspace_id': self.data.get('workspace_id'),\n                'user_id': self.data.get('user_id'),\n                'auth_target_type': AuthTargetType.TOOL.value\n            }).auth_resource(str(tool_id))\n            try:\n                requests.get(instance.get('download_callback_url'), timeout=5)\n            except Exception as e:\n                maxkb_logger.error(f\"callback appstore tool download error: {e}\")\n            return ToolModelSerializer(tool).data\n\n    class UpdateStoreTool(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n        workspace_id = serializers.CharField(required=True, label=_(\"workspace id\"))\n        tool_id = serializers.UUIDField(required=True, label=_(\"tool id\"))\n        download_url = serializers.CharField(required=True, label=_(\"download url\"))\n        download_callback_url = serializers.CharField(required=True, label=_(\"download callback url\"))\n        icon = serializers.CharField(required=True, label=_(\"icon\"), allow_null=True, allow_blank=True)\n        versions = serializers.ListField(required=True, label=_(\"versions\"), child=serializers.DictField())\n\n        def update_tool(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            tool = QuerySet(Tool).filter(id=self.data.get('tool_id')).first()\n            if tool is None:\n                raise AppApiException(500, _('Tool does not exist'))\n            # 查找匹配的版本名称\n            version_name = next(\n                (version.get('name') for version in self.data.get('versions') if\n                 version.get('downloadUrl') == self.data.get('download_url')),\n            )\n            res = requests.get(self.data.get('download_url'), timeout=5)\n            tool_data = RestrictedUnpickler(io.BytesIO(res.content)).load().tool\n            # 如果是SKILL类型的工具，保存文件内容到file表，并将code替换为file_id\n            if tool_data.get('tool_type') == ToolType.SKILL:\n                skill_file_id = uuid.uuid7()\n                skill_file = File(\n                    id=skill_file_id,\n                    file_name=f\"{tool_data.get('name')}.zip\",\n                    source_type=FileSourceType.TOOL,\n                    source_id=tool.id,\n                    meta={}\n                )\n                skill_file.save(base64.b64decode(tool_data.get('code')))\n                tool_data['code'] = skill_file_id\n            tool.desc = tool_data.get('desc')\n            tool.code = tool_data.get('code')\n            tool.input_field_list = tool_data.get('input_field_list', [])\n            tool.init_field_list = tool_data.get('init_field_list', [])\n            tool.icon = self.data.get('icon', tool.icon)\n            tool.version = version_name\n            # tool.is_active = False\n            tool.save()\n            try:\n                requests.get(self.data.get('download_callback_url'), timeout=5)\n            except Exception as e:\n                maxkb_logger.error(f\"callback appstore tool download error: {e}\")\n            return ToolModelSerializer(tool).data\n\n    class ToolRecord(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, allow_null=True, label=_('workspace id'))\n        tool_id = serializers.UUIDField(required=True, label=_('tool id'))\n        record_id = serializers.UUIDField(required=False, allow_null=True, label=_('record id'))\n        source_name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('source name'))\n        source_type = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('source type'))\n        state = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('state'))\n\n        class Operate(serializers.Serializer):\n            id = serializers.UUIDField(required=False, allow_null=True, label=_('record id'))\n            tool_id = serializers.UUIDField(required=True, label=_('tool id'))\n            workspace_id = serializers.CharField(required=False, allow_null=True, label=_('workspace id'))\n\n            def one(self):\n                self.is_valid(raise_exception=True)\n                tool_record = cache.get(Cache_Version.TOOL_WORKFLOW_EXECUTE.get_key(key=self.data.get('id')),\n                                        version=Cache_Version.TOOL_WORKFLOW_EXECUTE.get_version())\n                if tool_record:\n                    return tool_record\n                tool_record = QuerySet(ToolRecord).filter(id=self.data.get('id'), tool_id=self.data.get('tool_id'),\n                                                          workspace_id=self.data.get('workspace_id')).first()\n                if tool_record:\n                    return {'id': tool_record.id,\n                            'tool_id': tool_record.tool_id,\n                            'workspace_id': tool_record.workspace_id,\n                            'source_type': tool_record.source_type,\n                            'source_id': tool_record.source_id,\n                            'meta': tool_record.meta,\n                            'state': tool_record.state,\n                            'run_time': tool_record.run_time}\n                raise AppApiException(500, _('Tool record does not exist'))\n\n        def one(self):\n            self.is_valid(raise_exception=True)\n            if self.data.get('record_id'):\n                page = self.get_tool_records(1, 1)\n                return page.get('records')[0]\n\n            return None\n\n        def get_tool_records(self, current_page: int, page_size: int):\n            self.is_valid(raise_exception=True)\n            application_subquery = Application.objects.filter(id=OuterRef('source_id')).values('name')[:1]\n            knowledge_subquery = Knowledge.objects.filter(id=OuterRef('source_id')).values('name')[:1]\n            trigger_subquery = Trigger.objects.filter(id=OuterRef('source_id')).values('name')[:1]\n            trigger_type_subquery = Trigger.objects.filter(id=OuterRef('source_id')).values('trigger_type')[:1]\n\n            query_set = QuerySet(ToolRecord)\n            query_set = query_set.filter(\n                tool_id=self.data.get('tool_id')\n            ).annotate(\n                source_name=Case(\n                    When(source_type='APPLICATION', then=Subquery(application_subquery)),\n                    When(source_type='KNOWLEDGE', then=Subquery(knowledge_subquery)),\n                    When(source_type='TRIGGER', then=Subquery(trigger_subquery)),\n                    default=Value(''),\n                    output_field=CharField()\n                )\n            ).annotate(\n                trigger_type=Case(\n                    When(source_type='TRIGGER', then=Subquery(trigger_type_subquery)),\n                    default=Value(''),\n                    output_field=CharField()\n                )\n            ).annotate(\n                tool_name=Subquery(\n                    Tool.objects.filter(id=OuterRef('tool_id')).values('name')[:1]\n                )\n            ).annotate(\n                tool_icon=Subquery(\n                    Tool.objects.filter(id=OuterRef('tool_id')).values('icon')[:1]\n                )\n            )\n            if self.data.get('source_type'):\n                query_set = query_set.filter(Q(source_type=self.data.get('source_type', '')))\n            if self.data.get('state'):\n                query_set = query_set.filter(Q(state=self.data.get('state', '')))\n            if self.data.get('source_name'):\n                query_set = query_set.filter(Q(source_name__icontains=self.data.get('source_name', '')))\n            if self.data.get('record_id'):\n                query_set = query_set.filter(Q(id=self.data.get('record_id')))\n            if self.data.get('workspace_id'):\n                query_set = query_set.filter(Q(workspace_id=self.data.get('workspace_id')))\n            query_set = query_set.order_by('-create_time')\n\n            return page_search(\n                current_page, page_size, query_set,\n                lambda record: {\n                    **ToolRecordModelSerializer(record).data,\n                    'source_name': record.source_name,\n                    'tool_name': record.tool_name,\n                    'tool_icon': record.tool_icon,\n                    'trigger_type': record.trigger_type,\n                }\n            )\n\n    class UploadSkillFile(serializers.Serializer):\n        file = UploadedFileField(required=True, label=_(\"file\"))\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n        workspace_id = serializers.CharField(required=True, label=_(\"workspace id\"))\n\n        def upload(self):\n            self.is_valid()\n            file = self.data.get('file')\n            if not file.name.endswith('.zip'):\n                raise AppApiException(1001, _(\"Unsupported file format\"))\n            file_id = uuid.uuid7()\n            file = File(\n                id=file_id,\n                file_name=self.data.get('file').name,\n                meta={}\n            )\n            file.save(self.data.get('file').read())\n            return file_id\n\n\nclass ToolTreeSerializer(serializers.Serializer):\n    class Query(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        folder_id = serializers.CharField(required=True, label=_('folder id'))\n        name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('tool name'))\n        user_id = serializers.UUIDField(required=False, allow_null=True, label=_('user id'))\n        scope = serializers.CharField(required=True, label=_('scope'))\n        tool_type = serializers.CharField(required=False, label=_('tool type'), allow_null=True, allow_blank=True)\n        tool_type_list = serializers.ListField(child=serializers.CharField(), required=False, label=_('tool type list'),\n                                               allow_null=True, allow_empty=True)\n        create_user = serializers.UUIDField(required=False, label=_('create user'), allow_null=True)\n\n        def page_tool(self, current_page: int, page_size: int):\n            self.is_valid(raise_exception=True)\n\n            folder_id = self.data.get('folder_id', self.data.get('workspace_id'))\n            root = ToolFolder.objects.filter(id=folder_id).first()\n            if not root:\n                raise serializers.ValidationError(_('Folder not found'))\n            # 使用MPTT的get_descendants()方法获取所有相关节点\n            all_folders = root.get_descendants(include_self=True)\n\n            if self.data.get('name'):\n                tools = QuerySet(Tool).filter(\n                    Q(workspace_id=self.data.get('workspace_id')) &\n                    Q(folder_id__in=all_folders) &\n                    Q(user_id=self.data.get('user_id')) &\n                    Q(name__contains=self.data.get('name'))\n                )\n            else:\n                tools = QuerySet(Tool).filter(\n                    Q(workspace_id=self.data.get('workspace_id')) &\n                    Q(folder_id__in=all_folders) &\n                    Q(user_id=self.data.get('user_id'))\n                )\n            return page_search(current_page, page_size, tools, lambda record: ToolModelSerializer(record).data)\n\n        def get_query_set(self, workspace_manage, is_x_pack_ee):\n            tool_query_set = QuerySet(Tool).filter(workspace_id=self.data.get('workspace_id'))\n            folder_query_set = QuerySet(ToolFolder)\n            default_query_set = QuerySet(Tool)\n\n            workspace_id = self.data.get('workspace_id')\n            user_id = self.data.get('user_id')\n            scope = self.data.get('scope')\n            tool_type = self.data.get('tool_type')\n            desc = self.data.get('desc')\n            name = self.data.get('name')\n            folder_id = self.data.get('folder_id')\n            create_user = self.data.get('create_user')\n\n            if workspace_id is not None:\n                folder_query_set = folder_query_set.filter(workspace_id=workspace_id)\n                default_query_set = default_query_set.filter(workspace_id=workspace_id)\n            if folder_id is not None and folder_id != workspace_id:\n                folder_query_set = folder_query_set.filter(parent=folder_id)\n                default_query_set = default_query_set.filter(folder_id=folder_id)\n            if name is not None:\n                folder_query_set = folder_query_set.filter(name__icontains=name)\n                default_query_set = default_query_set.filter(name__icontains=name)\n            if desc is not None:\n                folder_query_set = folder_query_set.filter(desc__icontains=desc)\n                default_query_set = default_query_set.filter(desc__icontains=desc)\n            if create_user is not None:\n                tool_query_set = tool_query_set.filter(user_id=create_user)\n                folder_query_set = folder_query_set.filter(user_id=create_user)\n\n            default_query_set = default_query_set.order_by(\"-create_time\")\n\n            if scope is not None:\n                tool_query_set = tool_query_set.filter(scope=scope)\n\n            tool_type_list = self.data.get('tool_type_list')\n            if tool_type_list:\n                tool_query_set = tool_query_set.filter(tool_type__in=tool_type_list)\n            elif tool_type:\n                tool_query_set = tool_query_set.filter(tool_type=tool_type)\n\n            query_set_dict = {\n                'tool_query_set': tool_query_set,\n                'default_query_set': default_query_set,\n            }\n            if not workspace_manage:\n                query_set_dict['workspace_user_resource_permission_query_set'] = QuerySet(\n                    WorkspaceUserResourcePermission).filter(\n                    auth_target_type=\"TOOL\",\n                    workspace_id=workspace_id,\n                    user_id=user_id\n                )\n            return query_set_dict\n\n        @staticmethod\n        def is_x_pack_ee():\n            workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n            role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n            return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None\n\n        def page_tool_with_folders(self, current_page: int, page_size: int):\n            self.is_valid(raise_exception=True)\n\n            workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id'))\n            is_x_pack_ee = self.is_x_pack_ee()\n            result = native_page_search(\n                current_page, page_size, self.get_query_set(workspace_manage, is_x_pack_ee),\n                get_file_content(\n                    os.path.join(\n                        PROJECT_DIR,\n                        \"apps\", \"tools\", 'sql',\n                        'list_tool.sql' if workspace_manage else (\n                            'list_tool_user_ee.sql' if is_x_pack_ee else 'list_tool_user.sql'\n                        )\n                    )\n                ),\n                post_records_handler=lambda record: {\n                    **record,\n                    'input_field_list': json.loads(record.get('input_field_list', '[]')),\n                    'init_field_list': json.loads(record.get('init_field_list', '[]')),\n                },\n            )\n            return ResourceMappingSerializer().get_resource_count(result)\n\n        def get_tools(self):\n            self.is_valid(raise_exception=True)\n\n            workspace_manage = is_workspace_manage(self.data.get('user_id'), self.data.get('workspace_id'))\n            is_x_pack_ee = self.is_x_pack_ee()\n            results = native_search(\n                self.get_query_set(workspace_manage, is_x_pack_ee),\n                get_file_content(\n                    os.path.join(\n                        PROJECT_DIR,\n                        \"apps\", \"tools\", 'sql',\n                        'list_tool.sql' if workspace_manage else (\n                            'list_tool_user_ee.sql' if is_x_pack_ee else 'list_tool_user.sql'\n                        )\n                    )\n                ),\n\n            )\n\n            # 返回包含文件夹和工具的结构\n            return {\n                'folders': [\n                    folder for folder in results if folder['resource_type'] == 'folder'\n                ],\n                'tools': [\n                    {\n                        **tool,\n                        'input_field_list': json.loads(tool.get('input_field_list', '[]')),\n                        'init_field_list': json.loads(tool.get('init_field_list', '[]')),\n                    } for tool in results if tool['resource_type'] == 'tool'\n                ],\n            }\n"
  },
  {
    "path": "apps/tools/serializers/tool_folder.py",
    "content": "# -*- coding: utf-8 -*-\n\nfrom rest_framework import serializers\n\nfrom tools.models import ToolFolder\n\n\nclass ToolFolderTreeSerializer(serializers.ModelSerializer):\n    children = serializers.SerializerMethodField()\n\n    class Meta:\n        model = ToolFolder\n        fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id', 'children','create_time','update_time']\n\n    def get_children(self, obj):\n        return ToolFolderTreeSerializer(obj.get_children(), many=True).data\n\n\nclass ToolFolderFlatSerializer(serializers.ModelSerializer):\n    \"\"\"只序列化当前层的文件夹，不包含子节点\"\"\"\n\n    class Meta:\n        model = ToolFolder\n        fields = ['id', 'name', 'desc', 'user_id', 'workspace_id', 'parent_id']\n"
  },
  {
    "path": "apps/tools/serializers/tool_version.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： KnowledgeVersionSerializer.py\n    @date：2025/11/28 18:00\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom common.db.search import page_search\nfrom common.exception.app_exception import AppApiException\nfrom tools.models import ToolWorkflowVersion, Tool\n\n\nclass ToolWorkflowVersionEditSerializer(serializers.Serializer):\n    name = serializers.CharField(required=False, max_length=128, allow_null=True, allow_blank=True,\n                                 label=_(\"Version Name\"))\n\n\nclass ToolVersionModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = ToolWorkflowVersion\n        fields = ['id', 'name', 'workspace_id', 'tool_id', 'work_flow', 'publish_user_id', 'publish_user_name',\n                  'create_time',\n                  'update_time']\n\n\nclass ToolWorkflowVersionQuerySerializer(serializers.Serializer):\n    tool_id = serializers.UUIDField(required=True, label=_(\"Tool ID\"))\n    name = serializers.CharField(required=False, allow_null=True, allow_blank=True,\n                                 label=_(\"summary\"))\n\n\nclass ToolWorkflowVersionSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=False, label=_(\"Workspace ID\"))\n\n    class Query(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n\n        def get_query_set(self, query):\n            query_set = QuerySet(ToolWorkflowVersion).filter(tool_id=query.get('tool_id'))\n            if 'name' in query and query.get('name') is not None:\n                query_set = query_set.filter(name__contains=query.get('name'))\n            if 'workspace_id' in self.data and self.data.get('workspace_id') is not None:\n                query_set = query_set.filter(workspace_id=self.data.get('workspace_id'))\n            return query_set.order_by(\"-create_time\")\n\n        def list(self, query, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                ToolWorkflowVersionQuerySerializer(data=query).is_valid(raise_exception=True)\n            query_set = self.get_query_set(query)\n            return [ToolVersionModelSerializer(v).data for v in query_set]\n\n        def page(self, query, current_page, page_size, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            return page_search(current_page, page_size,\n                               self.get_query_set(query),\n                               post_records_handler=lambda v: ToolVersionModelSerializer(v).data)\n\n    class Operate(serializers.Serializer):\n        workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n        tool_id = serializers.UUIDField(required=True, label=_(\"Tool ID\"))\n        tool_version_id = serializers.UUIDField(required=True,\n                                                label=_(\"Tool version ID\"))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            workspace_id = self.data.get('workspace_id')\n            query_set = QuerySet(Tool).filter(id=self.data.get('tool_id'))\n            if workspace_id:\n                query_set = query_set.filter(workspace_id=workspace_id)\n            if not query_set.exists():\n                raise AppApiException(500, _('Tool id does not exist'))\n\n        def one(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            tool_version = QuerySet(ToolWorkflowVersion).filter(tool_id=self.data.get('tool_id'),\n                                                                id=self.data.get(\n                                                                    'tool_version_id')).first()\n            if tool_version is not None:\n                return ToolVersionModelSerializer(tool_version).data\n            else:\n                raise AppApiException(500, _('Workflow version does not exist'))\n\n        def edit(self, instance: Dict, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                ToolWorkflowVersionEditSerializer(data=instance).is_valid(raise_exception=True)\n            tool_version = QuerySet(ToolWorkflowVersion).filter(tool_id=self.data.get('tool_id'),\n                                                                id=self.data.get(\n                                                                    'knowledge_version_id')).first()\n            if tool_version is not None:\n                name = instance.get('name', None)\n                if name is not None and len(name) > 0:\n                    tool_version.name = name\n                tool_version.save()\n                return ToolVersionModelSerializer(tool_version).data\n            else:\n                raise AppApiException(500, _('Workflow version does not exist'))\n"
  },
  {
    "path": "apps/tools/serializers/tool_workflow.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： tool_workflow.py\n    @date：2026/3/6 13:59\n    @desc:\n\"\"\"\n# coding=utf-8\nimport pickle\nfrom functools import reduce\nfrom typing import Dict, List\n\nimport requests\nimport uuid_utils.compat as uuid\nfrom django.db import transaction\nfrom django.db.models import QuerySet\nfrom django.http import HttpResponse\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers, status\n\nfrom application.flow.common import Workflow, WorkflowMode\nfrom application.flow.i_step_node import ToolWorkflowPostHandler\nfrom application.flow.tool_workflow_manage import ToolWorkflowManage\nfrom application.models import ChatRecord\nfrom application.serializers.common import ToolExecute\nfrom common.exception.app_exception import AppApiException\nfrom common.field.common import UploadedFileField\nfrom common.result import result\nfrom common.utils.common import bytes_to_uploaded_file\nfrom common.utils.common import restricted_loads, generate_uuid\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.tool_code import ToolExecutor\nfrom knowledge.models import KnowledgeWorkflow\nfrom system_manage.models import AuthTargetType\nfrom system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer\nfrom tools.models import Tool, ToolScope, ToolWorkflow, ToolWorkflowVersion\nfrom tools.serializers.tool import ToolExportModelSerializer\nfrom users.models import User\n\ntool_executor = ToolExecutor()\n\n\ndef hand_node(node, update_tool_map):\n    if node.get('type') == 'tool-lib-node':\n        tool_lib_id = (node.get('properties', {}).get('node_data', {}).get('tool_lib_id') or '')\n        node.get('properties', {}).get('node_data', {})['tool_lib_id'] = update_tool_map.get(tool_lib_id, tool_lib_id)\n\n    if node.get('type') == 'search-knowledge-node':\n        node.get('properties', {}).get('node_data', {})['knowledge_id_list'] = []\n    if node.get('type') == 'ai-chat-node':\n        node_data = node.get('properties', {}).get('node_data', {})\n        mcp_tool_ids = node_data.get('mcp_tool_ids') or []\n        node_data['mcp_tool_ids'] = [update_tool_map.get(tool_id,\n                                                         tool_id) for tool_id in mcp_tool_ids]\n        tool_ids = node_data.get('tool_ids') or []\n        node_data['tool_ids'] = [update_tool_map.get(tool_id,\n                                                     tool_id) for tool_id in tool_ids]\n    if node.get('type') == 'mcp-node':\n        mcp_tool_id = (node.get('properties', {}).get('node_data', {}).get('mcp_tool_id') or '')\n        node.get('properties', {}).get('node_data', {})['mcp_tool_id'] = update_tool_map.get(mcp_tool_id,\n                                                                                             mcp_tool_id)\n\n\nclass ToolWorkflowModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = ToolWorkflow\n        fields = '__all__'\n\n\nclass ToolWorkflowImportRequest(serializers.Serializer):\n    file = UploadedFileField(required=True, label=_(\"file\"))\n\n\nclass ToolWorkflowActionListQuerySerializer(serializers.Serializer):\n    user_name = serializers.CharField(required=False, label=_('Name'), allow_blank=True, allow_null=True)\n    state = serializers.CharField(required=False, label=_(\"State\"), allow_blank=True, allow_null=True)\n\n\nclass ToolWorkflowInstance:\n\n    def __init__(self, knowledge_workflow: dict, version: str, tool_list: List[dict]):\n        self.knowledge_workflow = knowledge_workflow\n        self.version = version\n        self.tool_list = tool_list\n\n    def get_tool_list(self):\n        return self.tool_list or []\n\n\nclass ToolWorkflowSerializer(serializers.Serializer):\n    class Import(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'))\n        knowledge_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        @transaction.atomic\n        def import_(self, instance: dict, is_import_tool, with_valid=True):\n            if with_valid:\n                self.is_valid()\n                ToolWorkflowSerializer(data=instance).is_valid(raise_exception=True)\n            user_id = self.data.get('user_id')\n            workspace_id = self.data.get('workspace_id')\n            tool_id = self.data.get('tool_id')\n            tool_instance_bytes = instance.get('file').read()\n            try:\n                tool_instance = restricted_loads(tool_instance_bytes)\n            except Exception as e:\n                raise AppApiException(1001, _(\"Unsupported file format\"))\n            tool_workflow = tool_instance.work_flow\n            tool_list = tool_instance.get_tool_list()\n            update_tool_map = {}\n            if len(tool_list) > 0:\n                tool_id_list = reduce(lambda x, y: [*x, *y],\n                                      [[tool.get('id'), generate_uuid((tool.get('id') + workspace_id or ''))]\n                                       for tool\n                                       in\n                                       tool_list], [])\n                # 存在的工具列表\n                exits_tool_id_list = [str(tool.id) for tool in\n                                      QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=workspace_id)]\n                # 需要更新的工具集合\n                update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + workspace_id or '')) for tool\n                                   in\n                                   tool_list if\n                                   not exits_tool_id_list.__contains__(\n                                       tool.get('id'))}\n\n                tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if\n                             not exits_tool_id_list.__contains__(\n                                 tool.get('id')) and not exits_tool_id_list.__contains__(\n                                 generate_uuid((tool.get('id') + workspace_id or '')))]\n\n            work_flow = self.to_tool_workflow(\n                tool_workflow,\n                update_tool_map,\n            )\n            tool_model_list = [self.to_tool(tool, workspace_id, user_id) for tool in tool_list]\n            QuerySet(ToolWorkflow).filter(workspace_id=workspace_id, tool_id=tool_id).update_or_create(\n                tool_id=tool_id,\n                workspace_id=workspace_id,\n                defaults={'work_flow': work_flow}\n            )\n\n            if is_import_tool:\n                if len(tool_model_list) > 0:\n                    QuerySet(Tool).bulk_create(tool_model_list)\n                    UserResourcePermissionSerializer(data={\n                        'workspace_id': self.data.get('workspace_id'),\n                        'user_id': self.data.get('user_id'),\n                        'auth_target_type': AuthTargetType.TOOL.value\n                    }).auth_resource_batch([t.id for t in tool_model_list])\n\n        @staticmethod\n        def to_tool_workflow(knowledge_workflow, update_tool_map):\n            work_flow = knowledge_workflow.get(\"work_flow\")\n            for node in work_flow.get('nodes', []):\n                hand_node(node, update_tool_map)\n                if node.get('type') == 'loop_node':\n                    for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []):\n                        hand_node(n, update_tool_map)\n            return work_flow\n\n        @staticmethod\n        def to_tool(tool, workspace_id, user_id):\n            return Tool(id=tool.get('id'),\n                        user_id=user_id,\n                        name=tool.get('name'),\n                        code=tool.get('code'),\n                        template_id=tool.get('template_id'),\n                        input_field_list=tool.get('input_field_list'),\n                        init_field_list=tool.get('init_field_list'),\n                        is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'),\n                        tool_type=tool.get('tool_type', 'CUSTOM') or 'CUSTOM',\n                        scope=ToolScope.SHARED if workspace_id == 'None' else ToolScope.WORKSPACE,\n                        folder_id='default' if workspace_id == 'None' else workspace_id,\n                        workspace_id=workspace_id)\n\n    class Export(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=False, label=_('workspace id'))\n        tool_id = serializers.UUIDField(required=True, label=_('knowledge id'))\n\n        def export(self, with_valid=True):\n            try:\n                if with_valid:\n                    self.is_valid()\n                tool_id = self.data.get('tool_id')\n                tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=tool_id).first()\n                tool = QuerySet(Tool).filter(id=tool_id).first()\n                from application.flow.tools import get_tool_id_list\n                tool_id_list = get_tool_id_list(tool_workflow.work_flow)\n                tool_list = []\n                if len(tool_id_list) > 0:\n                    tool_list = QuerySet(Tool).filter(id__in=tool_id_list).exclude(scope=ToolScope.SHARED)\n                tool_workflow_dict = {'id': tool.id,\n                                      'work_flow': tool_workflow.work_flow,\n                                      'workspace_id': tool.workspace_id,\n                                      'name': tool.name,\n                                      'desc': tool.desc,\n                                      'tool_type': tool.tool_type}\n\n                tool_workflow_instance = ToolWorkflowInstance(\n                    tool_workflow_dict,\n                    'v2',\n                    [ToolExportModelSerializer(tool).data for tool in tool_list]\n                )\n                tool_workflow_pickle = pickle.dumps(tool_workflow_instance)\n                response = HttpResponse(content_type='text/plain', content=tool_workflow_pickle)\n                response['Content-Disposition'] = f'attachment; filename=\"{tool.name}.tool\"'\n                return response\n            except Exception as e:\n                return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR)\n\n    class Operate(serializers.Serializer):\n        user_id = serializers.UUIDField(required=True, label=_('user id'))\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        tool_id = serializers.UUIDField(required=True, label=_('tool id'))\n\n        def debug(self, instance: Dict, user, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=self.data.get(\"tool_id\")).first()\n            tool_record_id = instance.get('chat_record_id') or str(uuid.uuid7())\n            took_execute = ToolExecute(self.data.get(\"tool_id\"), tool_record_id,\n                                       self.data.get(\"workspace_id\"),\n                                       None,\n                                       None,\n                                       True)\n            record = took_execute.get_record()\n            work_flow_manage = ToolWorkflowManage(\n                Workflow.new_instance(tool_workflow.work_flow, WorkflowMode.TOOL),\n                {\n                    'chat_record_id': tool_record_id,\n                    'tool_id': self.data.get(\"tool_id\"),\n                    'stream': True,\n                    'workspace_id': self.data.get(\"workspace_id\"),\n                    **instance},\n\n                ToolWorkflowPostHandler(took_execute, self.data.get(\"tool_id\")),\n                is_the_task_interrupted=lambda: False,\n                child_node=instance.get('child_node'),\n                start_node_id=instance.get('runtime_node_id'),\n                start_node_data=instance.get('node_data'),\n                chat_record=self.to_chat_record(record)\n            )\n\n            r = work_flow_manage.run()\n            return r\n\n        @staticmethod\n        def to_chat_record(record):\n            if record is None:\n                return None\n            return ChatRecord(\n                answer_text_list=record.meta.get('answer_text_list'),\n                details=record.meta.get('details'),\n                answer_text='',\n            )\n\n        def publish(self, with_valid=True):\n            if with_valid:\n                self.is_valid()\n            user_id = self.data.get('user_id')\n            workspace_id = self.data.get(\"workspace_id\")\n            user = QuerySet(User).filter(id=user_id).first()\n            tool_workflow = QuerySet(ToolWorkflow).filter(tool_id=self.data.get(\"tool_id\"),\n                                                          workspace_id=workspace_id).first()\n            work_flow_version = ToolWorkflowVersion(work_flow=tool_workflow.work_flow,\n                                                    tool_id=self.data.get(\"tool_id\"),\n                                                    name=timezone.localtime(timezone.now()).strftime(\n                                                        '%Y-%m-%d %H:%M:%S'),\n                                                    publish_user_id=user_id,\n                                                    publish_user_name=user.username,\n                                                    workspace_id=workspace_id)\n            work_flow_version.save()\n            QuerySet(ToolWorkflow).filter(\n                tool_id=self.data.get(\"tool_id\")\n            ).update(is_publish=True, publish_time=timezone.now())\n            return True\n\n        def edit(self, instance: Dict):\n            self.is_valid(raise_exception=True)\n            if instance.get(\"work_flow\"):\n                QuerySet(ToolWorkflow).update_or_create(tool_id=self.data.get(\"tool_id\"),\n                                                        create_defaults={'id': uuid.uuid7(),\n                                                                         'tool_id': self.data.get(\n                                                                             \"tool_id\"),\n                                                                         \"workspace_id\": self.data.get(\n                                                                             'workspace_id'),\n                                                                         'work_flow': instance.get('work_flow',\n                                                                                                   {}), },\n                                                        defaults={\n                                                            'work_flow': instance.get('work_flow')\n                                                        })\n                return self.one()\n            if instance.get(\"work_flow_template\"):\n                template_instance = instance.get('work_flow_template')\n                download_url = template_instance.get('downloadUrl')\n                # 查找匹配的版本名称\n                res = requests.get(download_url, timeout=5)\n                ToolWorkflowSerializer.Import(data={\n                    'user_id': self.data.get('user_id'),\n                    'workspace_id': self.data.get('workspace_id'),\n                    'tool_id': str(self.data.get('tool_id')),\n                }).import_({'file': bytes_to_uploaded_file(res.content, 'file.tool')}, is_import_tool=False)\n\n                try:\n                    requests.get(template_instance.get('downloadCallbackUrl'), timeout=5)\n                except Exception as e:\n                    maxkb_logger.error(f\"callback appstore tool download error: {e}\")\n\n                return self.one()\n\n        def one(self):\n            self.is_valid(raise_exception=True)\n            workflow = QuerySet(KnowledgeWorkflow).filter(knowledge_id=self.data.get('knowledge_id')).first()\n            return {**ToolWorkflowModelSerializer(workflow).data}\n"
  },
  {
    "path": "apps/tools/sql/list_tool.sql",
    "content": "select *\nfrom (select tool.\"id\"::text,\n             tool.\"name\",\n             tool.\"desc\",\n             tool.\"tool_type\",\n             tool.\"scope\",\n             'tool'           as \"resource_type\",\n             tool.\"workspace_id\",\n             tool.\"folder_id\",\n             tool.\"user_id\",\n             \"user\".nick_name as \"nick_name\",\n             tool.\"icon\",\n             tool.label,\n             tool.\"template_id\"::text,\n             tool.\"create_time\",\n             tool.\"update_time\",\n             tool.init_field_list,\n             tool.input_field_list,\n             tool.version,\n             tool.\"is_active\"\n      from tool\n               left join \"user\" on \"user\".id = user_id ${tool_query_set}\n      ) temp\n     ${default_query_set}"
  },
  {
    "path": "apps/tools/sql/list_tool_user.sql",
    "content": "SELECT *\nFROM (SELECT tool.\"id\"::text,\n             tool.\"name\",\n             tool.\"desc\",\n             tool.\"tool_type\",\n             tool.\"scope\",\n             'tool'           AS \"resource_type\",\n             tool.\"workspace_id\",\n             tool.\"folder_id\",\n             tool.\"user_id\",\n             \"user\".nick_name AS \"nick_name\",\n             tool.\"icon\",\n             tool.label,\n             tool.\"template_id\"::text,\n             tool.\"create_time\",\n             tool.\"update_time\",\n             tool.init_field_list,\n             tool.input_field_list,\n             tool.version,\n             tool.\"is_active\"\n      FROM (SELECT tool.*\n            FROM tool tool ${tool_query_set}\n             AND tool.id::text IN (SELECT target\n                          FROM workspace_user_resource_permission\n                          ${workspace_user_resource_permission_query_set}\n                            AND 'VIEW' = ANY (permission_list))) AS tool\n               LEFT JOIN \"user\" ON \"user\".id = user_id\n) temp\n      ${default_query_set}"
  },
  {
    "path": "apps/tools/sql/list_tool_user_ee.sql",
    "content": "SELECT *\nFROM (SELECT tool.\"id\"::text,\n             tool.\"name\",\n             tool.\"desc\",\n             tool.\"tool_type\",\n             tool.\"scope\",\n             'tool'           AS \"resource_type\",\n             tool.\"workspace_id\",\n             tool.\"folder_id\",\n             tool.\"user_id\",\n             \"user\".nick_name AS \"nick_name\",\n             tool.\"icon\",\n             tool.label,\n             tool.\"template_id\"::text,\n             tool.\"create_time\",\n             tool.\"update_time\",\n             tool.init_field_list,\n             tool.input_field_list,\n             tool.version,\n             tool.\"is_active\"\n      FROM (SELECT tool.*\n            FROM tool tool ${tool_query_set}\n             AND tool.id::text IN (SELECT target\n                   FROM workspace_user_resource_permission ${workspace_user_resource_permission_query_set}\n                AND CASE\n                WHEN auth_type = 'ROLE' THEN\n                'ROLE' = ANY (permission_list)\n                AND\n                'TOOL:READ' IN (SELECT (CASE WHEN user_role_relation.role_id = ANY (ARRAY ['USER']) THEN 'TOOL:READ' ELSE role_permission.permission_id END)\n                FROM role_permission role_permission\n                RIGHT JOIN user_role_relation user_role_relation ON user_role_relation.role_id=role_permission.role_id\n                WHERE user_role_relation.user_id=workspace_user_resource_permission.user_id\n                AND user_role_relation.workspace_id=workspace_user_resource_permission.workspace_id)\n                ELSE\n                'VIEW' = ANY (permission_list)\n                END\n                )) AS tool\n               LEFT JOIN \"user\" ON \"user\".id = user_id\n      ) temp\n       ${default_query_set}"
  },
  {
    "path": "apps/tools/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/tools/urls.py",
    "content": "from django.urls import path\n\nfrom . import views\n\napp_name = \"tool\"\n# @formatter:off\nurlpatterns = [\n    path('workspace/internal/tool', views.ToolView.InternalTool.as_view()),\n    path('workspace/store/tool', views.ToolView.StoreTool.as_view()),\n    path('workspace/<str:workspace_id>/tool', views.ToolView.as_view()),\n    path('workspace/<str:workspace_id>/tool/workflow', views.ToolWorkflowView.as_view()),\n    path('workspace/<str:workspace_id>/tool/import', views.ToolView.Import.as_view()),\n    path('workspace/<str:workspace_id>/tool/pylint', views.ToolView.Pylint.as_view()),\n    path('workspace/<str:workspace_id>/tool/debug', views.ToolView.Debug.as_view()),\n    path('workspace/<str:workspace_id>/tool/tool_list', views.ToolView.Query.as_view()),\n    path('workspace/<str:workspace_id>/tool/test_connection', views.ToolView.TestConnection.as_view()),\n    path('workspace/<str:workspace_id>/tool/upload_skill_file', views.ToolView.UploadSkillFile.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>', views.ToolView.Operate.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/publish', views.ToolWorkflowView.Publish.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/debug', views.ToolWorkflowDebugView.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/workflow', views.ToolWorkflowView.Operate.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/workflow/export', views.ToolWorkflowView.Export.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/edit_icon', views.ToolView.EditIcon.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/export', views.ToolView.Export.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/add_internal_tool', views.ToolView.AddInternalTool.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/add_store_tool', views.ToolView.AddStoreTool.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/update_store_tool', views.ToolView.UpdateStoreTool.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_record/<str:record_id>', views.ToolView.ToolRecord.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_record/<int:current_page>/<int:page_size>', views.ToolView.PageToolRecord.as_view()),\n    path('workspace/<str:workspace_id>/tool/<int:current_page>/<int:page_size>', views.ToolView.Page.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_version', views.ToolWorkflowVersionView.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_version/<int:current_page>/<int:page_size>', views.ToolWorkflowVersionView.Page.as_view()),\n    path('workspace/<str:workspace_id>/tool/<str:tool_id>/tool_version/<str:tool_version_id>', views.ToolWorkflowVersionView.Operate.as_view()),\n\n]\n"
  },
  {
    "path": "apps/tools/views/__init__.py",
    "content": "from .tool import *\nfrom .tool_workflow import *\nfrom .tool_workflow_version import *\n"
  },
  {
    "path": "apps/tools/views/tool.py",
    "content": "from django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.parsers import MultiPartParser\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result\nfrom tools.api.tool import ToolCreateAPI, ToolEditAPI, ToolReadAPI, ToolDeleteAPI, ToolTreeReadAPI, ToolDebugApi, \\\n    ToolExportAPI, ToolImportAPI, ToolPageAPI, PylintAPI, EditIconAPI, GetInternalToolAPI, AddInternalToolAPI\nfrom tools.models import ToolScope, Tool\nfrom tools.serializers.tool import ToolSerializer, ToolTreeSerializer\n\n\ndef get_tool_operation_object(tool_id):\n    tool_model = QuerySet(model=Tool).filter(id=tool_id).first()\n    if tool_model is not None:\n        return {\n            \"name\": tool_model.name\n        }\n    return {}\n\n\nclass ToolView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create tool'),\n        summary=_('Create tool'),\n        operation_id=_('Create tool'),  # type: ignore\n        parameters=ToolCreateAPI.get_parameters(),\n        request=ToolCreateAPI.get_request(),\n        responses=ToolCreateAPI.get_response(),\n        tags=[_('Tool')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.TOOL_CREATE.get_workspace_permission(),\n        PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n    )\n    @log(\n        menu=\"Tool\", operate=\"Create tool\",\n        get_operation_object=lambda r, k: r.data.get('name'),\n    )\n    def post(self, request: Request, workspace_id: str):\n        return result.success(ToolSerializer.Create(\n            data={'user_id': request.user.id, 'workspace_id': workspace_id}\n        ).insert({**request.data, 'scope': ToolScope.WORKSPACE}))\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get tool by folder'),\n        summary=_('Get tool by folder'),\n        operation_id=_('Get tool by folder'),  # type: ignore\n        parameters=ToolTreeReadAPI.get_parameters(),\n        responses=ToolTreeReadAPI.get_response(),\n        tags=[_('Tool')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.TOOL_READ.get_workspace_permission(),\n        PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n    )\n    def get(self, request: Request, workspace_id: str):\n        return result.success(ToolTreeSerializer.Query(\n            data={\n                'workspace_id': workspace_id,\n                'folder_id': request.query_params.get('folder_id'),\n                'name': request.query_params.get('name'),\n                'scope': request.query_params.get('scope', ToolScope.WORKSPACE),\n                'tool_type': request.query_params.get('tool_type'),\n                'tool_type_list': request.query_params.getlist('tool_type_list[]'),\n                'user_id': request.user.id,\n                'create_user': request.query_params.get('create_user'),\n            }\n        ).get_tools())\n\n    class Debug(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_('Debug Tool'),\n            summary=_('Debug Tool'),\n            operation_id=_('Debug Tool'),  # type: ignore\n            request=ToolDebugApi.get_request(),\n            responses=ToolDebugApi.get_response(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_EDIT.get_workspace_permission(),\n            PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n        )\n        def post(self, request: Request, workspace_id: str):\n            return result.success(ToolSerializer.Debug(\n                data={'workspace_id': workspace_id, 'user_id': request.user.id}\n            ).debug(request.data))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Update tool'),\n            summary=_('Update tool'),\n            operation_id=_('Update tool'),  # type: ignore\n            parameters=ToolEditAPI.get_parameters(),\n            request=ToolEditAPI.get_request(),\n            responses=ToolEditAPI.get_response(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.TOOL.get_workspace_tool_permission()],\n                           CompareConstants.AND),\n        )\n        @log(\n            menu='Tool', operate='Update tool',\n            get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),\n\n        )\n        def put(self, request: Request, workspace_id: str, tool_id: str):\n            return result.success(ToolSerializer.Operate(\n                data={'id': tool_id, 'workspace_id': workspace_id}\n            ).edit(request.data))\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get tool'),\n            summary=_('Get tool'),\n            operation_id=_('Get tool'),  # type: ignore\n            parameters=ToolReadAPI.get_parameters(),\n            responses=ToolReadAPI.get_response(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_READ.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            PermissionConstants.APPLICATION_READ.get_workspace_permission(),\n            PermissionConstants.APPLICATION_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.USER.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.TOOL.get_workspace_tool_permission()],\n                           CompareConstants.AND),\n        )\n        @log(menu='Tool', operate='Get tool')\n        def get(self, request: Request, workspace_id: str, tool_id: str):\n            return result.success(ToolSerializer.Operate(\n                data={'id': tool_id, 'workspace_id': workspace_id}\n            ).one())\n\n        @extend_schema(\n            methods=['DELETE'],\n            description=_('Delete tool'),\n            summary=_('Delete tool'),\n            operation_id=_('Delete tool'),  # type: ignore\n            parameters=ToolDeleteAPI.get_parameters(),\n            responses=ToolDeleteAPI.get_response(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_DELETE.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_DELETE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.TOOL.get_workspace_tool_permission()],\n                           CompareConstants.AND),\n        )\n        @log(\n            menu='Tool', operate=\"Delete tool\",\n            get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),\n\n        )\n        def delete(self, request: Request, workspace_id: str, tool_id: str):\n            return result.success(ToolSerializer.Operate(\n                data={'id': tool_id, 'workspace_id': workspace_id}\n            ).delete())\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get tool list by pagination'),\n            summary=_('Get tool list by pagination'),\n            operation_id=_('Get tool list by pagination'),  # type: ignore\n            parameters=ToolPageAPI.get_parameters(),\n            responses=ToolPageAPI.get_response(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_READ.get_workspace_permission(),\n            PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n        )\n        @log(menu='Tool', operate='Get tool list')\n        def get(self, request: Request, workspace_id: str, current_page: int, page_size: int):\n            return result.success(ToolTreeSerializer.Query(\n                data={\n                    'workspace_id': workspace_id,\n                    'folder_id': request.query_params.get('folder_id'),\n                    'name': request.query_params.get('name'),\n                    'scope': request.query_params.get('scope'),\n                    'tool_type': request.query_params.get('tool_type'),\n                    'user_id': request.user.id,\n                    'create_user': request.query_params.get('create_user'),\n                }\n            ).page_tool_with_folders(current_page, page_size))\n\n    class Query(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get tool list '),\n            summary=_('Get tool list'),\n            operation_id=_('Get tool list'),  # type: ignore\n            parameters=ToolReadAPI.get_parameters(),\n            responses=ToolReadAPI.get_response(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_READ.get_workspace_permission(),\n            PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n        )\n        @log(menu='Tool', operate='Get tool list')\n        def get(self, request: Request, workspace_id: str):\n            return result.success(ToolSerializer.Query(\n                data={\n                    'workspace_id': workspace_id,\n                    'folder_id': request.query_params.get('folder_id'),\n                    'name': request.query_params.get('name'),\n                    'scope': request.query_params.get('scope'),\n                    'tool_type': request.query_params.get('tool_type'),\n                    'user_id': request.user.id,\n                    'create_user': request.query_params.get('create_user'),\n                }\n            ).get_tools())\n\n    class Import(APIView):\n        authentication_classes = [TokenAuth]\n        parser_classes = [MultiPartParser]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_(\"Import tool\"),\n            summary=_(\"Import tool\"),\n            operation_id=_(\"Import tool\"),  # type: ignore\n            parameters=ToolImportAPI.get_parameters(),\n            request=ToolImportAPI.get_request(),\n            responses=ToolImportAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_IMPORT.get_workspace_permission(),\n            PermissionConstants.TOOL_IMPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n        )\n        @log(menu='Tool', operate='Import tool', )\n        def post(self, request: Request, workspace_id: str):\n            return result.success(ToolSerializer.Import(\n                data={\n                    'workspace_id': workspace_id,\n                    'file': request.FILES.get('file'),\n                    'user_id': request.user.id,\n                    'folder_id': request.data.get('folder_id')\n                }\n            ).import_(ToolScope.WORKSPACE))\n\n    class Export(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Export tool\"),\n            summary=_(\"Export tool\"),\n            operation_id=_(\"Export tool\"),  # type: ignore\n            parameters=ToolExportAPI.get_parameters(),\n            responses=ToolExportAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_EXPORT.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_EXPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.TOOL.get_workspace_tool_permission()],\n                           CompareConstants.AND),\n        )\n        @log(\n            menu='Tool', operate=\"Export tool\",\n            get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),\n        )\n        def get(self, request: Request, tool_id: str, workspace_id: str):\n            return ToolSerializer.Operate(\n                data={'id': tool_id, 'workspace_id': workspace_id}\n            ).export()\n\n    class Pylint(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            summary=_('Check code'),\n            operation_id=_('Check code'),  # type: ignore\n            description=_('Check code'),\n            request=PylintAPI.get_request(),\n            responses=PylintAPI.get_response(),\n            parameters=PylintAPI.get_parameters(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_READ.get_workspace_permission(),\n            PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            RoleConstants.USER.get_workspace_role()\n        )\n        def post(self, request: Request, workspace_id: str):\n            return result.success(ToolSerializer.Pylint(\n                data={'workspace_id': workspace_id}\n            ).run(request.data))\n\n    class EditIcon(APIView):\n        authentication_classes = [TokenAuth]\n        parser_classes = [MultiPartParser]\n\n        @extend_schema(\n            methods=['PUT'],\n            summary=_('Edit tool icon'),\n            operation_id=_('Edit tool icon'),  # type: ignore\n            description=_('Edit tool icon'),\n            request=EditIconAPI.get_request(),\n            responses=EditIconAPI.get_response(),\n            parameters=EditIconAPI.get_parameters(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.TOOL.get_workspace_tool_permission()],\n                           CompareConstants.AND),\n        )\n        def put(self, request: Request, tool_id: str, workspace_id: str):\n            return result.success(ToolSerializer.IconOperate(data={\n                'id': tool_id,\n                'workspace_id': workspace_id,\n                'user_id': request.user.id,\n                'image': request.FILES.get('file')\n            }).edit(request.data))\n\n    class TestConnection(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_(\"Test tool connection\"),\n            summary=_(\"Test tool connection\"),\n            operation_id=_(\"Test tool connection\"),  # type: ignore\n            request=ToolReadAPI.get_request(),\n            responses=ToolReadAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_CREATE.get_workspace_permission(),\n            PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),\n            PermissionConstants.TOOL_EDIT.get_workspace_permission(),\n            PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n        )\n        def post(self, request: Request, workspace_id: str):\n            return result.success(ToolSerializer.TestConnection(data={\n                'workspace_id': workspace_id,\n                'code': request.data.get('code'),\n            }).test_connection())\n\n    class InternalTool(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get internal tool\"),\n            summary=_(\"Get internal tool\"),\n            operation_id=_(\"Get internal tool\"),  # type: ignore\n            parameters=GetInternalToolAPI.get_parameters(),\n            responses=GetInternalToolAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        def get(self, request: Request):\n            return result.success(ToolSerializer.InternalTool(data={\n                'user_id': request.user.id,\n                'name': request.query_params.get('name', ''),\n            }).get_internal_tools())\n\n    class AddInternalTool(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_(\"Add internal tool\"),\n            summary=_(\"Add internal tool\"),\n            operation_id=_(\"Add internal tool\"),  # type: ignore\n            parameters=AddInternalToolAPI.get_parameters(),\n            request=AddInternalToolAPI.get_request(),\n            responses=AddInternalToolAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_CREATE.get_workspace_permission(),\n            PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            RoleConstants.USER.get_workspace_role(),\n        )\n        @log(\n            menu='Tool', operate=\"Add internal tool\",\n            get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),\n        )\n        def post(self, request: Request, tool_id: str, workspace_id: str):\n            return result.success(ToolSerializer.AddInternalTool(data={\n                'tool_id': tool_id,\n                'user_id': request.user.id,\n                'workspace_id': workspace_id\n            }).add(request.data))\n\n    class StoreTool(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get Appstore tools\"),\n            summary=_(\"Get Appstore tools\"),\n            operation_id=_(\"Get Appstore tools\"),  # type: ignore\n            responses=GetInternalToolAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        def get(self, request: Request):\n            return result.success(ToolSerializer.StoreTool(data={\n                'user_id': request.user.id,\n                'name': request.query_params.get('name', ''),\n            }).get_appstore_tools())\n\n    class AddStoreTool(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_(\"Add Appstore tool\"),\n            summary=_(\"Add Appstore tool\"),\n            operation_id=_(\"Add Appstore tool\"),  # type: ignore\n            parameters=AddInternalToolAPI.get_parameters(),\n            request=AddInternalToolAPI.get_request(),\n            responses=AddInternalToolAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_CREATE.get_workspace_permission(),\n            PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            RoleConstants.USER.get_workspace_role(),\n        )\n        @log(\n            menu='Tool', operate=\"Add Appstore tool\",\n            get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),\n        )\n        def post(self, request: Request, tool_id: str, workspace_id: str):\n            return result.success(ToolSerializer.AddStoreTool(data={\n                'tool_id': tool_id,\n                'user_id': request.user.id,\n                'workspace_id': workspace_id,\n            }).add(request.data))\n\n    class UpdateStoreTool(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_(\"Update Appstore tool\"),\n            summary=_(\"Update Appstore tool\"),\n            operation_id=_(\"Update Appstore tool\"),  # type: ignore\n            parameters=AddInternalToolAPI.get_parameters(),\n            request=AddInternalToolAPI.get_request(),\n            responses=AddInternalToolAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_CREATE.get_workspace_permission(),\n            PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            RoleConstants.USER.get_workspace_role(),\n        )\n        @log(\n            menu='Tool', operate=\"Update Appstore tool\",\n            get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),\n        )\n        def post(self, request: Request, tool_id: str, workspace_id: str):\n            return result.success(ToolSerializer.UpdateStoreTool(data={\n                'tool_id': tool_id,\n                'user_id': request.user.id,\n                'workspace_id': workspace_id,\n                'download_url': request.data.get('download_url'),\n                'download_callback_url': request.data.get('download_callback_url'),\n                'icon': request.data.get('icon'),\n                'versions': request.data.get('versions'),\n            }).update_tool(request.data))\n\n    class PageToolRecord(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get tool records\"),\n            summary=_(\"Get tool records\"),\n            operation_id=_(\"Get tool records\"),  # type: ignore\n            parameters=AddInternalToolAPI.get_parameters(),\n            responses=AddInternalToolAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_EXECUTE_RECORD.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_EXECUTE_RECORD.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.TOOL.get_workspace_tool_permission()],\n                           CompareConstants.AND),\n        )\n        def get(self, request: Request, tool_id: str, workspace_id: str, current_page: int, page_size: int):\n            return result.success(ToolSerializer.ToolRecord(data={\n                'tool_id': tool_id,\n                'workspace_id': workspace_id,\n                'source_name': request.query_params.get('source_name'),\n                'source_type': request.query_params.get('source_type'),\n                'state': request.query_params.get('state'),\n            }).get_tool_records(current_page, page_size))\n\n    class ToolRecord(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get tool record\"),\n            summary=_(\"Get tool record\"),\n            operation_id=_(\"Get tool record\"),  # type: ignore\n            parameters=AddInternalToolAPI.get_parameters(),\n            responses=AddInternalToolAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_EXECUTE_RECORD.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_EXECUTE_RECORD.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [PermissionConstants.TOOL.get_workspace_tool_permission()],\n                           CompareConstants.AND),\n        )\n        def get(self, request: Request, tool_id: str, workspace_id: str, record_id: str):\n            return result.success(ToolSerializer.ToolRecord.Operate(data={\n                'tool_id': tool_id,\n                'workspace_id': workspace_id,\n                'id': record_id,\n            }).one())\n\n    class UploadSkillFile(APIView):\n        authentication_classes = [TokenAuth]\n        parser_classes = [MultiPartParser]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_(\"Upload skill file\"),\n            summary=_(\"Upload skill file\"),\n            operation_id=_(\"Upload skill file\"),  # type: ignore\n            parameters=AddInternalToolAPI.get_parameters(),\n            request=AddInternalToolAPI.get_request(),\n            responses=AddInternalToolAPI.get_response(),\n            tags=[_(\"Tool\")]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_CREATE.get_workspace_permission(),\n            PermissionConstants.TOOL_CREATE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n        )\n        def put(self, request: Request, workspace_id: str):\n            return result.success(ToolSerializer.UploadSkillFile(data={\n                'workspace_id': workspace_id,\n                'user_id': request.user.id,\n                'file': request.FILES.get('file'),\n            }).upload())\n"
  },
  {
    "path": "apps/tools/views/tool_workflow.py",
    "content": "# coding=utf-8\n\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions, get_is_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom common.result import result, DefaultResultSerializer\nfrom knowledge.api.knowledge_workflow import KnowledgeWorkflowApi\nfrom knowledge.serializers.knowledge_workflow import KnowledgeWorkflowSerializer\nfrom tools.api.tool_workflow import ToolWorkflowApi, ToolWorkflowExportApi, ToolWorkflowImportApi\nfrom tools.serializers.tool_workflow import ToolWorkflowSerializer\nfrom tools.views import get_tool_operation_object\n\n\nclass ToolWorkflowView(APIView):\n    authentication_classes = [TokenAuth]\n\n    class Publish(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_(\"Publishing an tool\"),\n            summary=_(\"Publishing an tool\"),\n            operation_id=_(\"Publishing an tool\"),  # type: ignore\n            parameters=ToolWorkflowApi.get_parameters(),\n            request=None,\n            responses=DefaultResultSerializer,\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(),\n                         PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.TOOL.get_workspace_knowledge_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Tool', operate='Publishing an tool',\n             get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')))\n        def put(self, request: Request, workspace_id: str, tool_id: str):\n            return result.success(\n                ToolWorkflowSerializer.Operate(\n                    data={'tool_id': tool_id, 'user_id': request.user.id,\n                          'workspace_id': workspace_id, }).publish())\n\n    class Export(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Export tool workflow'),\n            summary=_('Export tool workflow'),\n            operation_id=_('Export tool workflow'),  # type: ignore\n            parameters=ToolWorkflowExportApi.get_parameters(),\n            request=None,\n            responses=ToolWorkflowExportApi.get_response(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_EXPORT.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_EXPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.KNOWLEDGE.get_workspace_tool_permission()],\n                CompareConstants.AND\n            )\n        )\n        @log(menu='Tool', operate=\"Export tool workflow\",\n             get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),\n             )\n        def get(self, request: Request, workspace_id: str, tool_id: str):\n            return ToolWorkflowSerializer.Export(\n                data={'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id}\n            ).export()\n\n    class Import(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['POST'],\n            description=_('Import tool workflow'),\n            summary=_('Import tool workflow'),\n            operation_id=_('Import tool workflow'),  # type: ignore\n            parameters=ToolWorkflowImportApi.get_parameters(),\n            request=ToolWorkflowImportApi.get_request(),\n            responses=ToolWorkflowImportApi.get_response(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_EXPORT.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_EXPORT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.KNOWLEDGE.get_workspace_tool_permission()],\n                CompareConstants.AND\n            )\n        )\n        @log(menu='Tool', operate=\"Import tool workflow\",\n             get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool')),\n             )\n        def post(self, request: Request, workspace_id: str, tool_id: str):\n            is_import_tool = get_is_permissions(request, workspace_id=workspace_id)(\n                PermissionConstants.TOOL_IMPORT.get_workspace_permission(),\n                PermissionConstants.TOOL_IMPORT.get_workspace_permission_workspace_manage_role(),\n                RoleConstants.WORKSPACE_MANAGE.get_workspace_role(), RoleConstants.USER.get_workspace_role()\n            )\n            return result.success(ToolWorkflowSerializer.Import(data={\n                'tool_id': tool_id, 'user_id': request.user.id, 'workspace_id': workspace_id\n            }).import_({'file': request.FILES.get('file')}, is_import_tool))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Edit tool workflow'),\n            summary=_('Edit tool workflow'),\n            operation_id=_('Edit tool workflow'),  # type: ignore\n            parameters=ToolWorkflowApi.get_parameters(),\n            request=ToolWorkflowApi.get_request(),\n            responses=ToolWorkflowApi.get_response(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.TOOL.get_workspace_tool_permission()],\n                CompareConstants.AND\n            )\n        )\n        @log(\n            menu='Tool', operate=\"Modify tool workflow\",\n            get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),\n        )\n        def put(self, request: Request, workspace_id: str, tool_id: str):\n            return result.success(ToolWorkflowSerializer.Operate(\n                data={'user_id': request.user.id, 'workspace_id': workspace_id, 'tool_id': tool_id}\n            ).edit(request.data))\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get tool workflow'),\n            summary=_('Get tool workflow'),\n            operation_id=_('Get tool workflow'),  # type: ignore\n            parameters=KnowledgeWorkflowApi.get_parameters(),\n            responses=KnowledgeWorkflowApi.get_response(),\n            tags=[_('Tool')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TOOL_READ.get_workspace_tool_permission(),\n            PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n            ViewPermission(\n                [RoleConstants.USER.get_workspace_role()],\n                [PermissionConstants.TOOL.get_workspace_tool_permission()],\n                CompareConstants.AND\n            ),\n        )\n        def get(self, request: Request, workspace_id: str, tool_id: str):\n            return result.success(ToolWorkflowSerializer.Operate(\n                data={'user_id': request.user.id, 'workspace_id': workspace_id, 'tool_id': tool_id}\n            ).one())\n\n\nclass KnowledgeWorkflowVersionView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get tool workflow version list'),\n        summary=_('Get tool workflow version list'),\n        operation_id=_('Get tool workflow version list'),  # type: ignore\n        parameters=ToolWorkflowApi.get_parameters(),\n        responses=ToolWorkflowApi.get_response(),\n        tags=[_('Tool')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.TOOL_READ.get_workspace_tool_permission(),\n        PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission(\n            [RoleConstants.USER.get_workspace_role()],\n            [PermissionConstants.TOOL.get_workspace_tool_permission()],\n            CompareConstants.AND\n        ),\n    )\n    def get(self, request: Request, workspace_id: str, tool_id: str):\n        return result.success(KnowledgeWorkflowSerializer.Operate(\n            data={'user_id': request.user.id, 'workspace_id': workspace_id, 'tool_id': tool_id}\n        ).one())\n\n\nclass ToolWorkflowDebugView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('tool workflow debug'),\n        summary=_('tool workflow debug'),\n        operation_id=_('tool workflow debug'),  # type: ignore\n        parameters=ToolWorkflowApi.get_parameters(),\n        responses=ToolWorkflowApi.get_response(),\n        tags=[_('Tool')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.TOOL_EDIT.get_workspace_tool_permission(),\n        PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        ViewPermission(\n            [RoleConstants.USER.get_workspace_role()],\n            [PermissionConstants.TOOL.get_workspace_tool_permission()],\n            CompareConstants.AND\n        ),\n    )\n    def post(self, request: Request, workspace_id: str, tool_id: str):\n        return ToolWorkflowSerializer.Operate(\n            data={'workspace_id': workspace_id, 'tool_id': tool_id, 'user_id': request.user.id}).debug(\n            request.data,\n            request.user,\n            True)\n"
  },
  {
    "path": "apps/tools/views/tool_workflow_version.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_version.py.py\n    @date：2025/6/3 15:46\n    @desc:\n\"\"\"\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants\nfrom common.log.log import log\nfrom knowledge.api.knowledge_version import KnowledgeVersionListAPI, KnowledgeVersionPageAPI, \\\n    KnowledgeVersionOperateAPI\nfrom knowledge.models import Knowledge\nfrom tools.serializers.tool_version import ToolWorkflowVersionSerializer\nfrom tools.views import get_tool_operation_object\n\n\ndef get_knowledge_operation_object(knowledge_id):\n    knowledge_model = QuerySet(model=Knowledge).filter(id=knowledge_id).first()\n    if knowledge_model is not None:\n        return {\n            'name': knowledge_model.name\n        }\n    return {}\n\n\nclass ToolWorkflowVersionView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_(\"Get the tool version list\"),\n        summary=_(\"Get the tool version list\"),\n        operation_id=_(\"Get the tool version list\"),  # type: ignore\n        parameters=KnowledgeVersionListAPI.get_parameters(),\n        responses=KnowledgeVersionListAPI.get_response(),\n        tags=[_('Tool/Version')]  # type: ignore\n    )\n    @has_permissions(PermissionConstants.TOOL_READ.get_workspace_knowledge_permission(),\n                     PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),\n                     ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                    [PermissionConstants.TOOL.get_workspace_knowledge_permission()],\n                                    CompareConstants.AND),\n                     RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id, tool_id: str):\n        return result.success(\n            ToolWorkflowVersionSerializer.Query(\n                data={'workspace_id': workspace_id}).list(\n                {'name': request.query_params.get(\"name\"), 'tool_id': tool_id}))\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get the list of tool versions by page\"),\n            summary=_(\"Get the list of tool versions by page\"),\n            operation_id=_(\"Get the list of tool versions by page\"),  # type: ignore\n            parameters=KnowledgeVersionPageAPI.get_parameters(),\n            responses=KnowledgeVersionPageAPI.get_response(),\n            tags=[_('Tool/Version')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.TOOL_READ.get_workspace_knowledge_permission(),\n                         PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.TOOL.get_workspace_knowledge_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, tool_id: str, current_page: int, page_size: int):\n            return result.success(\n                ToolWorkflowVersionSerializer.Query(\n                    data={'workspace_id': workspace_id}).page(\n                    {'name': request.query_params.get(\"name\"), 'tool_id': tool_id},\n                    current_page, page_size))\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_(\"Get tool version details\"),\n            summary=_(\"Get tool version details\"),\n            operation_id=_(\"Get tool version details\"),  # type: ignore\n            parameters=KnowledgeVersionOperateAPI.get_parameters(),\n            responses=KnowledgeVersionOperateAPI.get_response(),\n            tags=[_('Tool/Version')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.TOOL_READ.get_workspace_knowledge_permission(),\n                         PermissionConstants.TOOL_READ.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.TOOL.get_workspace_knowledge_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, tool_id: str, tool_version_id: str):\n            return result.success(\n                ToolWorkflowVersionSerializer.Operate(\n                    data={'user_id': request.user, 'workspace_id': workspace_id,\n                          'tool_id': tool_id, 'tool_version_id': tool_version_id}).one())\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_(\"Modify tool version information\"),\n            summary=_(\"Modify tool version information\"),\n            operation_id=_(\"Modify tool version information\"),  # type: ignore\n            parameters=KnowledgeVersionOperateAPI.get_parameters(),\n            request=None,\n            responses=KnowledgeVersionOperateAPI.get_response(),\n            tags=[_('Tool/Version')]  # type: ignore\n        )\n        @has_permissions(PermissionConstants.TOOL_EDIT.get_workspace_knowledge_permission(),\n                         PermissionConstants.TOOL_EDIT.get_workspace_permission_workspace_manage_role(),\n                         ViewPermission([RoleConstants.USER.get_workspace_role()],\n                                        [PermissionConstants.TOOL.get_workspace_knowledge_permission()],\n                                        CompareConstants.AND),\n                         RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(menu='Tool', operate=\"Modify tool version information\",\n             get_operation_object=lambda r, k: get_tool_operation_object(k.get('tool_id')),\n             )\n        def put(self, request: Request, workspace_id: str, tool_id: str, tool_version_id: str):\n            return result.success(\n                ToolWorkflowVersionSerializer.Operate(\n                    data={'tool_id': tool_id, 'workspace_id': workspace_id,\n                          'tool_version_id': tool_version_id,\n                          'user_id': request.user.id}).edit(\n                    request.data))\n"
  },
  {
    "path": "apps/trigger/__init__.py",
    "content": ""
  },
  {
    "path": "apps/trigger/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "apps/trigger/api/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2026/1/14 15:48\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/trigger/api/trigger.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： trigger.py\n    @date：2026/1/14 15:49\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\nfrom rest_framework import serializers\nfrom django.utils.translation import gettext_lazy as _\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer\nfrom knowledge.serializers.common import BatchSerializer\nfrom trigger.serializers.task_source_trigger import TaskSourceTriggerEditRequest\nfrom trigger.serializers.trigger import TriggerCreateRequest, TriggerResponse, BatchActiveSerializer\n\n\nclass TriggerQueryResponseSerializer(serializers.Serializer):\n    id = serializers.UUIDField(required=True, help_text=\"触发器id\", label='触发器id')\n    workspace_id = serializers.CharField(required=True, help_text=\"触发器工作空间\", label='触发器工作空间')\n    name = serializers.CharField(required=True, help_text=\"触发器名称\", label='触发器名称')\n    desc = serializers.CharField(required=True, help_text=\"触发器描述\", label=\"触发器描述\")\n    trigger_type = serializers.CharField(required=True, help_text=\"触发器类型\", label=\"触发器类型\")\n    type = serializers.CharField(required=True, help_text=\"资源类型\", label=\"资源类型\")\n    is_active = serializers.BooleanField(required=True, help_text=\"是否激活\", label=\"是否激活\")\n    source_name = serializers.CharField(required=True, help_text=\"资源类型\", label=\"资源类型\")\n    source_icon = serializers.CharField(required=True, help_text=\"资源图标\", label=\"资源图标\")\n    create_time = serializers.CharField(required=True, help_text=\"创建时间\", label=\"创建时间\")\n    update_time = serializers.CharField(required=True, help_text=\"修改时间\", label=\"修改时间\")\n\n\nclass TriggerTaskRecordResponse(ResultSerializer):\n    def get_data(self):\n        return TriggerQueryResponseSerializer(many=True)\n\n\nclass TriggerQueryAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"name\",\n                description=\"触发器名称\",\n                type=OpenApiTypes.STR,\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"type\",\n                description=\"触发器类型\",\n                type=OpenApiTypes.STR,\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"task\",\n                description=\"任务名称\",\n                type=OpenApiTypes.STR,\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"is_active\",\n                description=\"启用状态\",\n                type=OpenApiTypes.STR,\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"create_user\",\n                description=\"创建者\",\n                type=OpenApiTypes.STR,\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return TriggerTaskRecordResponse\n\n\nclass TriggerQueryPageAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [TriggerQueryAPI.get_parameters(),\n                OpenApiParameter(\n                    name=\"current_page\",\n                    description=_(\"Current page\"),\n                    type=OpenApiTypes.INT,\n                    location='path',\n                    required=True,\n                ),\n                OpenApiParameter(\n                    name=\"page_size\",\n                    description=_(\"Page size\"),\n                    type=OpenApiTypes.INT,\n                    location='path',\n                    required=True,\n                )]\n\n    @staticmethod\n    def get_response():\n        return TriggerQueryAPI.get_response()\n\n\nclass TriggerCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return TriggerCreateRequest\n\n    @staticmethod\n    def get_response():\n        return TriggerResponse\n\n\nclass TaskSourceTriggerCreateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source_id\",\n                description=\"资源id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source_type\",\n                description=\"资源类型\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return TriggerCreateRequest\n\n    @staticmethod\n    def get_response():\n        return TriggerResponse\n\n\nclass TriggerBatchDeleteAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            )\n        ]\n\n    @staticmethod\n    def get_request():\n        return BatchSerializer\n\n\nclass TriggerBatchActiveAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return BatchActiveSerializer\n\n\nclass TriggerOperateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"trigger_id\",\n                description=\"触发器id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return TriggerCreateRequest\n\n    @staticmethod\n    def get_response():\n        return TriggerResponse\n\n\nclass RequestSE(serializers.Serializer):\n    pass\n\n\nclass TriggerEditAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return TriggerCreateRequest\n\n\nclass TaskSourceTriggerAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source_id\",\n                description=\"资源id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source_type\",\n                description=\"资源类型\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return TriggerResponse\n\n\nclass TaskSourceTriggerOperateAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source_id\",\n                description=\"资源id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"source_type\",\n                description=\"资源类型\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"trigger_id\",\n                description=\"触发器id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_request():\n        return TaskSourceTriggerEditRequest\n"
  },
  {
    "path": "apps/trigger/api/trigger_task.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： trigger_task.py\n    @date：2026/1/28 16:37\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer\nfrom trigger.serializers.trigger_task import ChatRecordSerializerModel, TriggerTaskResponse\n\n\nclass TriggerTaskRecordResultSerializer(serializers.Serializer):\n    id = serializers.UUIDField(required=True, help_text=\"任务记录id\", label='任务记录id')\n    state = serializers.CharField(required=True, help_text=\"任务记录状态\", label='任务记录状态')\n    source_type = serializers.CharField(required=True, help_text=\"资源类型\", label='资源类型')\n    source_name = serializers.CharField(required=True, help_text=\"资源名称\", label=\"资源名称\")\n    source_id = serializers.CharField(required=True, help_text=\"资源id\", label=\"资源id\")\n    task_record_id = serializers.CharField(required=True, help_text=\"资源任务记录id\", label=\"资源任务记录id\")\n    trigger_id = serializers.CharField(required=True, help_text=\"触发器id\", label=\"触发器id\")\n    type = serializers.CharField(required=True, help_text=\"资源类型\", label=\"资源类型\")\n    create_time = serializers.CharField(required=True, help_text=\"创建时间\", label=\"创建时间\")\n    update_time = serializers.CharField(required=True, help_text=\"修改时间\", label=\"修改时间\")\n\n\nclass TriggerTaskRecordResponse(ResultSerializer):\n    def get_data(self):\n        return TriggerTaskRecordResultSerializer(many=True)\n\n\nclass TriggerTaskRecordExecutionDetailsResponse(ResultSerializer):\n    def get_data(self):\n        return ChatRecordSerializerModel()\n\n\nclass TriggerTaskResultSerializer(ResultSerializer):\n    def get_data(self):\n        return TriggerTaskResponse(many=True)\n\n\nclass TriggerTaskAPI(APIMixin):\n    @staticmethod\n    def get_system_parameters():\n        return [parameter for parameter in TriggerTaskRecordExecutionDetailsAPI.get_parameters() if\n                not parameter.name == 'workspace_id']\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"trigger_id\",\n                description=\"触发器id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return TriggerTaskResultSerializer\n\n\nclass TriggerTaskRecordPageAPI(APIMixin):\n    @staticmethod\n    def get_system_parameters():\n        return [parameter for parameter in TriggerTaskRecordExecutionDetailsAPI.get_parameters() if\n                not parameter.name == 'workspace_id']\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"trigger_id\",\n                description=\"触发器id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"current_page\",\n                description=_(\"Current page\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"page_size\",\n                description=_(\"Page size\"),\n                type=OpenApiTypes.INT,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"name\",\n                description=\"任务名称\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"state\",\n                description=\"状态\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"order\",\n                description=\"排序字段\",\n                type=OpenApiTypes.STR,\n                location='query',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return TriggerTaskRecordResponse\n\n\nclass TriggerTaskRecordExecutionDetailsAPI(APIMixin):\n    @staticmethod\n    def get_system_parameters():\n        return [parameter for parameter in TriggerTaskRecordExecutionDetailsAPI.get_parameters() if\n                not parameter.name == 'workspace_id']\n\n    @staticmethod\n    def get_parameters():\n        return [\n            OpenApiParameter(\n                name=\"workspace_id\",\n                description=\"工作空间id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"trigger_id\",\n                description=\"触发器id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n            OpenApiParameter(\n                name=\"trigger_task_id\",\n                description=\"触发器任务id\",\n                type=OpenApiTypes.STR,\n                location='path',\n                required=True,\n            ),\n        ]\n\n    @staticmethod\n    def get_response():\n        return TriggerTaskRecordExecutionDetailsResponse\n"
  },
  {
    "path": "apps/trigger/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass TriggerConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'trigger'\n"
  },
  {
    "path": "apps/trigger/handler/base_task.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： base_task.py\n    @date：2026/1/14 19:03\n    @desc:\n\"\"\"\nfrom abc import ABC, abstractmethod\n\n\nclass BaseTriggerTask(ABC):\n    \"\"\"\n    任务执行器抽象\n    \"\"\"\n\n    @abstractmethod\n    def support(self, trigger_task, **kwargs):\n        pass\n\n    @abstractmethod\n    def execute(self, trigger_task, **kwargs):\n        pass\n"
  },
  {
    "path": "apps/trigger/handler/base_trigger.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： Trigger.py\n    @date：2026/1/14 18:45\n    @desc:\n\"\"\"\n\nfrom abc import ABC, abstractmethod\n\n\nclass BaseTrigger(ABC):\n    \"\"\"\n    触发器抽象\n    \"\"\"\n\n    @abstractmethod\n    def support(self, trigger, **kwargs):\n        pass\n\n    @abstractmethod\n    def deploy(self, trigger, **kwargs):\n        pass\n\n    @abstractmethod\n    def undeploy(self, trigger, **kwargs):\n        pass\n\n    @staticmethod\n    @abstractmethod\n    def execute(trigger, **kwargs):\n        pass\n"
  },
  {
    "path": "apps/trigger/handler/impl/task/application_task.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_task.py\n    @date：2026/1/14 19:14\n    @desc:\n\"\"\"\nimport json\nimport time\nimport traceback\n\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\n\nfrom application.models import ChatUserType, Chat, ChatRecord, ChatSourceChoices, Application\nfrom chat.serializers.chat import ChatSerializers\nfrom common.utils.logger import maxkb_logger\nfrom knowledge.models.knowledge_action import State\nfrom trigger.handler.base_task import BaseTriggerTask\nfrom trigger.models import TaskRecord, TriggerTask\n\n\ndef get_reference(fields, obj):\n    for field in fields:\n        value = obj.get(field)\n        if value is None:\n            return None\n        else:\n            obj = value\n    return obj\n\n\ndef conversion_custom_value(value, _type):\n    if ['array', 'dict', 'float', 'int', 'boolean', 'any'].__contains__(_type):\n        try:\n            return json.loads(value)\n        except Exception as e:\n            pass\n    return value\n\n\ndef valid_value_type(value, _type):\n    if _type == 'array':\n        return isinstance(value, list)\n    if _type == 'dict':\n        return isinstance(value, dict)\n    if _type == 'float':\n        return isinstance(value, float)\n    if _type == 'int':\n        return isinstance(value, int)\n    if _type == 'boolean':\n        return isinstance(value, bool)\n    return isinstance(value, str)\n\n\ndef get_field_value(value, kwargs, _type, required, default_value, field):\n    source = value.get('source')\n    if source == 'custom':\n        _value = value.get('value')\n        if _value:\n            _value = conversion_custom_value(_value, _type)\n        else:\n            if default_value:\n                return default_value\n            if required:\n                raise Exception(f'{field} is required')\n            else:\n                return None\n    else:\n        _value = get_reference(value.get('value'), kwargs)\n    valid = valid_value_type(_value, _type)\n    if not valid:\n        raise Exception(f'{field} type error')\n    return _value\n\n\ndef get_application_execute_parameters(parameter_setting, application_parameters_setting, kwargs):\n    many_field = ['api_input_field_list', 'user_input_field_list']\n    parameters = {'form_data': {}}\n    for key, value in application_parameters_setting.items():\n        setting = parameter_setting.get(key)\n        if setting:\n            if many_field.__contains__(key):\n                for ck, cv in value.items():\n                    _setting = setting.get(ck)\n                    if _setting:\n                        _value = get_field_value(_setting, kwargs, cv.get('type'), cv.get('required'),\n                                                 cv.get('default_value'), ck)\n                        parameters['form_data'][ck] = _value\n                    else:\n                        if cv.get('default_value'):\n                            parameters['form_data'][ck] = cv.get('default_value')\n                        else:\n                            if cv.get('required'):\n                                raise Exception(f'{ck} is required')\n            else:\n                value = get_field_value(setting, kwargs, value.get('type'), value.get('required'),\n                                        value.get('default_value'), key)\n                parameters['message' if key == 'question' else key] = value\n        else:\n            if value.get('default_value'):\n                parameters['message' if key == 'question' else key] = value.get('default_value')\n            else:\n                if value.get('required'):\n                    raise Exception(f'{\"message\" if key == \"question\" else key} is required')\n\n    return parameters\n\n\ndef get_loop_workflow_node(node_list):\n    result = []\n    for item in node_list:\n        if item.get('type') == 'loop-node':\n            for loop_item in item.get('loop_node_data') or []:\n                for inner_item in loop_item.values():\n                    result.append(inner_item)\n    return result\n\n\ndef get_workflow_state(details):\n    node_list = details.values()\n    all_node = [*node_list, *get_loop_workflow_node(node_list)]\n    err = any([True for value in all_node if value.get('status') == 500 and not value.get('enableException')])\n    if err:\n        return State.FAILURE\n    return State.SUCCESS\n\n\ndef get_user_field_component_input_type(input_type):\n    if input_type == \"MultiRow\":\n        return 'array'\n    if input_type == \"SwitchInput\":\n        return 'boolean'\n    return 'string'\n\n\ndef get_application_parameters_setting(application):\n    application_parameter_setting = {'question': {\n        'required': True,\n        'type': 'string'\n    }}\n    if application.type == 'SIMPLE':\n        return application_parameter_setting\n    else:\n        base_node_list = [n for n in application.work_flow.get('nodes') if n.get('type') == \"base-node\"]\n        if len(base_node_list) == 0:\n            raise Exception('Incorrect application workflow information')\n        base_node = base_node_list[0]\n        api_input_field_list = base_node.get('properties').get('api_input_field_list') or []\n        api_input_field_list = {user_field.get('variable'): {\n            'required': user_field.get('is_required'),\n            'default_value': user_field.get('default_value'),\n            'type': 'string'\n        } for user_field in api_input_field_list}\n        user_input_field_list = base_node.get('properties').get('user_input_field_list') or []\n        user_input_field_list = {user_field.get('field'): {\n            'required': user_field.get('required'),\n            'default_value': user_field.get('default_value'),\n            'type': get_user_field_component_input_type(user_field.get('input_type'))\n        } for user_field in user_input_field_list}\n        application_parameter_setting['api_input_field_list'] = api_input_field_list\n        application_parameter_setting['user_input_field_list'] = user_input_field_list\n        node_data = base_node.get('properties').get('node_data') or {}\n        file_upload_enable = node_data.get('file_upload_enable')\n        if file_upload_enable:\n            file_upload_setting = node_data.get('file_upload_setting') or {}\n            for field in ['audio', 'document', 'image', 'other', 'video']:\n                v = file_upload_setting.get(field)\n                if v:\n                    application_parameter_setting[field + '_list'] = {'required': False, 'default_value': [],\n                                                                     'type': 'array'}\n        return application_parameter_setting\n\n\nclass ApplicationTask(BaseTriggerTask):\n    def support(self, trigger_task, **kwargs):\n        return trigger_task.get('source_type') == 'APPLICATION'\n\n    def execute(self, trigger_task, **kwargs):\n        parameter_setting = trigger_task.get('parameter')\n        task_record_id = uuid.uuid7()\n        start_time = time.time()\n        try:\n            application = QuerySet(Application).filter(id=trigger_task.get('source_id')).only('type',\n                                                                                              'work_flow').first()\n            if application is None:\n                QuerySet(TriggerTask).filter(id=trigger_task.get('id')).delete()\n                return\n            application_id = trigger_task.get('source_id')\n            chat_id = uuid.uuid7()\n            chat_user_id = str(uuid.uuid7())\n            chat_record_id = str(uuid.uuid7())\n            TaskRecord(id=task_record_id, trigger_id=trigger_task.get('trigger'),\n                       trigger_task_id=trigger_task.get('id'),\n                       source_type=\"APPLICATION\",\n                       source_id=application_id,\n                       task_record_id=chat_record_id,\n                       meta={'chat_id': chat_id},\n                       state=State.STARTED).save()\n            application_parameters_setting = get_application_parameters_setting(application)\n            parameters = get_application_execute_parameters(parameter_setting, application_parameters_setting, kwargs)\n            parameters['re_chat'] = False\n            parameters['stream'] = True\n            parameters['chat_record_id'] = chat_record_id\n            message = parameters.get('message')\n            ip_address = '-'\n            if kwargs.get('body') is not None:\n                ip_address = kwargs.get('body').get('ip_address')\n            Chat.objects.get_or_create(id=chat_id, defaults={\n                'application_id': application_id,\n                'abstract': message,\n                'chat_user_id': chat_user_id,\n                'chat_user_type': ChatUserType.ANONYMOUS_USER.value,\n                'asker': {'username': \"游客\"},\n                'ip_address': ip_address,\n                'source': {\n                    'type': ChatSourceChoices.TRIGGER.value\n                },\n            })\n\n            list(ChatSerializers(data={\n                \"chat_id\": chat_id,\n                \"chat_user_id\": chat_user_id,\n                'chat_user_type': ChatUserType.ANONYMOUS_USER.value,\n                'application_id': application_id,\n                'ip_address': ip_address,\n                'source': {\n                    'type': ChatSourceChoices.TRIGGER.value\n                },\n                'debug': False\n            }).chat(instance=parameters))\n            chat_record = QuerySet(ChatRecord).filter(id=chat_record_id).first()\n            if chat_record:\n                state = get_workflow_state(chat_record.details)\n                QuerySet(TaskRecord).filter(id=task_record_id).update(state=state, run_time=chat_record.run_time,\n                                                                      meta={'parameter_setting': parameter_setting,\n                                                                            'input': parameters, 'output': None})\n            else:\n                QuerySet(TaskRecord).filter(id=task_record_id).update(state=State.FAILURE,\n                                                                      run_time=time.time() - start_time,\n                                                                      meta={'parameter_setting': parameter_setting,\n                                                                            'input': parameters, 'output': None,\n                                                                            'err_message': 'Error: An unknown error occurred during the execution of the conversation'})\n        except Exception as e:\n            maxkb_logger.error(f\"Application execution error: {traceback.format_exc()}\")\n            QuerySet(TaskRecord).filter(id=task_record_id).update(\n                state=State.FAILURE,\n                run_time=time.time() - start_time,\n                meta={'input': {'parameter_setting': parameter_setting, **kwargs}, 'output': None,\n                      'err_message': 'Error: ' + str(e)}\n            )\n"
  },
  {
    "path": "apps/trigger/handler/impl/task/tool_task.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： application_task.py\n    @date：2026/1/14 19:14\n    @desc:\n\"\"\"\nimport json\nimport time\nimport traceback\n\nimport uuid_utils.compat as uuid\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext as _\n\nfrom common.utils.logger import maxkb_logger\nfrom common.utils.rsa_util import rsa_long_decrypt\nfrom common.utils.tool_code import ToolExecutor\nfrom knowledge.models.knowledge_action import State\nfrom tools.models import Tool, ToolRecord, ToolTaskTypeChoices\nfrom trigger.handler.base_task import BaseTriggerTask\nfrom trigger.models import TaskRecord\n\nexecutor = ToolExecutor()\n\n\ndef get_reference(fields, obj):\n    for field in fields:\n        value = obj.get(field)\n        if value is None:\n            return None\n        else:\n            obj = value\n    return obj\n\n\ndef get_field_value(value, kwargs):\n    source = value.get('source')\n    if source == 'custom':\n        return value.get('value')\n    else:\n        return get_reference(value.get('value'), kwargs)\n\n\ndef _convert_value(_type, value):\n    if value is None:\n        return None\n\n    if _type == 'int':\n        return int(value)\n    if _type == 'boolean':\n        value = 0 if ['0', '[]'].__contains__(value) else value\n        return bool(value)\n    if _type == 'float':\n        return float(value)\n    if _type == 'dict':\n        v = json.loads(value)\n        if isinstance(v, dict):\n            return v\n        raise Exception(_('type error'))\n    if _type == 'array':\n        v = json.loads(value)\n        if isinstance(v, list):\n            return v\n        raise Exception(_('type error'))\n    return value\n\n\ndef get_tool_execute_parameters(input_field_list, parameter_setting, kwargs):\n    type_map = {f.get(\"name\"): f.get(\"type\") for f in (input_field_list or []) if f.get(\"name\")}\n\n    parameters = {}\n    for key, value in parameter_setting.items():\n        raw = get_field_value(value, kwargs)\n        parameters[key] = _convert_value(type_map.get(key), raw)\n    return parameters\n\n\ndef get_loop_workflow_node(node_list):\n    result = []\n    for item in node_list:\n        if item.get('type') == 'loop-node':\n            for loop_item in item.get('loop_node_data') or []:\n                for inner_item in loop_item.values():\n                    result.append(inner_item)\n    return result\n\n\ndef get_workflow_state(details):\n    node_list = details.values()\n    all_node = [*node_list, *get_loop_workflow_node(node_list)]\n    err = any([True for value in all_node if value.get('status') == 500 and not value.get('enableException')])\n    if err:\n        return State.FAILURE\n    return State.SUCCESS\n\n\ndef _get_result_detail(result):\n    if isinstance(result, dict):\n        result_dict = {k: (str(v)[:500] if len(str(v)) > 500 else v) for k, v in result.items()}\n    elif isinstance(result, list):\n        result_dict = [str(item)[:500] if len(str(item)) > 500 else item for item in result]\n    elif isinstance(result, str):\n        result_dict = result[:500] if len(result) > 500 else result\n    else:\n        result_dict = result\n    return result_dict\n\n\nclass ToolTask(BaseTriggerTask):\n    def support(self, trigger_task, **kwargs):\n        return trigger_task.get('source_type') == 'TOOL'\n\n    def execute(self, trigger_task, **kwargs):\n        parameter_setting = trigger_task.get('parameter')\n        tool_id = trigger_task.get('source_id')\n        task_record_id = uuid.uuid7()\n        start_time = time.time()\n        try:\n            tool = QuerySet(Tool).filter(id=tool_id, is_active=True).first()\n            if not tool:\n                maxkb_logger.info(f\"Tool with id {tool_id} not found or inactive.\")\n                return\n\n            TaskRecord(\n                id=task_record_id,\n                trigger_id=trigger_task.get('trigger'),\n                trigger_task_id=trigger_task.get('id'),\n                source_type=\"TOOL\",\n                source_id=tool_id,\n                task_record_id=task_record_id,\n                meta={'input': parameter_setting, 'output': {}},\n                state=State.STARTED\n            ).save()\n            ToolRecord(\n                id=task_record_id,\n                workspace_id=tool.workspace_id,\n                tool_id=tool.id,\n                source_type=ToolTaskTypeChoices.TRIGGER,\n                source_id=trigger_task.get('trigger'),\n                meta={'input': parameter_setting, 'output': {}},\n                state=State.STARTED\n            ).save()\n\n            parameters = get_tool_execute_parameters(tool.input_field_list, parameter_setting, kwargs)\n            init_params_default_value = {i[\"field\"]: i.get('default_value') for i in tool.init_field_list}\n\n            if tool.init_params is not None:\n                all_params = init_params_default_value | json.loads(rsa_long_decrypt(tool.init_params)) | parameters\n            else:\n                all_params = init_params_default_value | parameters\n\n            result = executor.exec_code(tool.code, all_params)\n\n            result_dict = _get_result_detail(result)\n\n            maxkb_logger.debug(f\"Tool execution result: {result}\")\n\n            QuerySet(TaskRecord).filter(id=task_record_id).update(\n                state=State.SUCCESS,\n                run_time=time.time() - start_time,\n                meta={'input': parameter_setting, 'output': result_dict}\n            )\n            QuerySet(ToolRecord).filter(id=task_record_id).update(\n                state=State.SUCCESS,\n                run_time=time.time() - start_time,\n                meta={'input': parameters, 'output': result_dict}\n            )\n        except Exception as e:\n            maxkb_logger.error(f\"Tool execution error: {traceback.format_exc()}\")\n            QuerySet(TaskRecord).filter(id=task_record_id).update(\n                state=State.FAILURE,\n                run_time=time.time() - start_time,\n                meta={'input': parameter_setting, 'output': 'Error: ' + str(e), 'err_message': 'Error: ' + str(e)}\n            )\n            QuerySet(ToolRecord).filter(id=task_record_id).update(\n                state=State.FAILURE,\n                run_time=time.time() - start_time,\n                meta={'input': parameter_setting, 'output': 'Error: ' + str(e), 'err_message': 'Error: ' + str(e)}\n            )\n"
  },
  {
    "path": "apps/trigger/handler/impl/trigger/event_trigger.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： event_trigger.py\n    @date：2026/1/15 11:08\n    @desc:\n\"\"\"\n\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext as _, gettext_lazy\nfrom drf_spectacular.utils import extend_schema, OpenApiExample\nfrom rest_framework import serializers\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common import result\nfrom common.auth import WebhookAuth\nfrom common.exception.app_exception import AppApiException, AppAuthenticationFailed\nfrom common.log.log import _get_ip_address\nfrom common.result import Result\nfrom trigger.handler.base_trigger import BaseTrigger\nfrom trigger.models import TriggerTask, Trigger\nfrom trigger.serializers.trigger import TriggerResponse\nfrom trigger.serializers.trigger_task import TriggerTaskResponse\n\n\ndef valid_parameter_type(value, _type, desc):\n    try:\n        if _type == 'int':\n            instance_type = int | float\n        elif _type == 'boolean':\n            instance_type = bool\n        elif _type == 'float':\n            instance_type = float | int\n        elif _type == 'dict':\n            instance_type = dict\n        elif _type == 'array':\n            instance_type = list\n        elif _type == 'string':\n            instance_type = str\n        else:\n            raise Exception(_(\n                'Field: {name} Type: {_type} Value: {value} Unsupported types'\n            ).format(name=desc, _type=_type))\n    except:\n        return value\n    if not isinstance(value, instance_type):\n        raise Exception(_(\n            'Field: {name} Type: {_type} Value: {value} Type error'\n        ).format(name=desc, _type=_type, value=value))\n    return value\n\n\ndef get_parameters(body_setting, request: Request):\n    parameters = {}\n    for body in body_setting:\n        value = request.data.get(body.get('field'))\n        required = body.get('required')\n        if value is None and required:\n            raise AppApiException(500, f'{body.get(\"desc\")} is required')\n        if value is None and not required:\n            parameters[body.get('field')] = None\n            continue\n        _type = body.get('type')\n        valid_parameter_type(value, _type, body.get(\"desc\"))\n        parameters[body.get('field')] = value\n    ip_address = _get_ip_address(request)\n    parameters['ip_address'] = ip_address or '-'\n    return parameters\n\n\nclass EventTriggerRequest(serializers.Serializer):\n    pass\n\n\nclass EventTriggerView(APIView):\n    authentication_classes = [WebhookAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=gettext_lazy('Event Trigger WebHook'),\n        summary=gettext_lazy('Event Trigger WebHook'),\n        operation_id=gettext_lazy('Event Trigger WebHook'),  # type: ignore\n        request={\n            'application/json': {\n                'schema': {\n                    'type': 'object',\n                    'example': {}\n                }\n            }\n        },\n        tags=[gettext_lazy('Trigger')],  # type: ignore\n        examples=[\n            OpenApiExample(\n                'Example Request',\n                description='Send an empty JSON object as request body',\n                value={},\n                request_only=True,  # 仅用于请求示例\n                response_only=False,\n            )\n        ]\n\n    )\n    def post(self, request: Request, trigger_id: str):\n        trigger = QuerySet(Trigger).filter(id=trigger_id).first()\n        if trigger:\n            return EventTrigger.execute(TriggerResponse(trigger).data, request)\n        return Result(code=404, message=\"404\")\n\n\nclass EventTrigger(BaseTrigger):\n    \"\"\"\n    事件触发器\n    \"\"\"\n\n    @staticmethod\n    def execute(trigger, request=None, **kwargs):\n        trigger_setting = trigger.get('trigger_setting')\n        if trigger_setting.get('token'):\n            token = request.META.get('HTTP_AUTHORIZATION')\n            if trigger_setting.get('token') != token.replace('Bearer ', ''):\n                raise AppAuthenticationFailed(1002, _('Authentication information is incorrect'))\n        is_active = trigger.get('is_active')\n        if not is_active:\n            return Result(code=404, message=\"404\", response_status=404)\n        body = trigger_setting.get('body')\n        parameters = get_parameters(body, request)\n        trigger_task_list = [TriggerTaskResponse(trigger_task).data for trigger_task in\n                             QuerySet(TriggerTask).filter(trigger__id=trigger.get('id'), is_active=True)]\n        from trigger.handler.simple_tools import execute\n        for trigger_task in trigger_task_list:\n            execute(trigger_task, body=parameters)\n        return result.success(True)\n\n    def support(self, trigger, **kwargs):\n        return trigger.get('trigger_type') == 'EVENT'\n\n    def deploy(self, trigger, **kwargs):\n        return True\n\n    def undeploy(self, trigger, **kwargs):\n        return True\n"
  },
  {
    "path": "apps/trigger/handler/impl/trigger/scheduled_trigger.py",
    "content": "# coding=utf-8\nfrom django.db.models import QuerySet\n\nfrom common.utils.logger import maxkb_logger\nfrom ops import celery_app\nfrom trigger.handler.base_trigger import BaseTrigger\nfrom trigger.models import TriggerTask\n\n\ndef _parse_hhmm(value: str) -> tuple[int, int]:\n    hour_str, minute_str = (value or \"\").split(\":\")\n    hour = int(hour_str)\n    minute = int(minute_str)\n    if not (0 <= hour <= 23 and 0 <= minute <= 59):\n        raise ValueError(\"hour/minute out of range\")\n    return hour, minute\n\n\ndef _weekday_to_cron(d: int | str) -> str:\n    mapping = {1: \"mon\", 2: \"tue\", 3: \"wed\", 4: \"thu\", 5: \"fri\", 6: \"sat\", 7: \"sun\", 0: \"sun\"}\n    di = int(d)\n    if di not in mapping:\n        raise ValueError(\"invalid weekday\")\n    return mapping[di]\n\n\ndef _get_active_trigger_tasks(trigger_id: str) -> list[dict]:\n    return list(\n        QuerySet(TriggerTask)\n        .filter(trigger_id=trigger_id, is_active=True)\n        .values(\"id\", \"source_type\", \"source_id\", \"parameter\", \"trigger\")\n    )\n\n\ndef _deploy_daily(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None:\n    from common.job import scheduler\n\n    times = setting.get(\"time\") or []\n    for t in times:\n        try:\n            hour, minute = _parse_hhmm(t)\n        except Exception:\n            maxkb_logger.warning(f\"invalid time={t}, trigger_id={trigger_id}\")\n            continue\n\n        for task in trigger_tasks:\n            job_id = f\"trigger:{trigger_id}:task:{task['id']}:daily:{hour:02d}{minute:02d}\"\n            scheduler.add_job(\n                ScheduledTrigger.execute,\n                trigger=\"cron\",\n                hour=str(hour),\n                minute=str(minute),\n                id=job_id,\n                kwargs={\"trigger\": trigger, \"trigger_task\": task},\n                replace_existing=True,\n            )\n\n\ndef _deploy_weekly(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None:\n    from common.job import scheduler\n\n    times = setting.get(\"time\") or []\n    days = setting.get(\"days\") or []\n    if not times or not days:\n        maxkb_logger.warning(f\"empty weekly setting, trigger_id={trigger_id}\")\n        return\n\n    for d in days:\n        try:\n            dow = _weekday_to_cron(d)\n        except Exception:\n            maxkb_logger.warning(f\"invalid weekday={d}, trigger_id={trigger_id}\")\n            continue\n\n        for t in times:\n            try:\n                hour, minute = _parse_hhmm(t)\n            except Exception:\n                maxkb_logger.warning(f\"invalid time={t}, trigger_id={trigger_id}\")\n                continue\n\n            for task in trigger_tasks:\n                job_id = f\"trigger:{trigger_id}:task:{task['id']}:weekly:{dow}:{hour:02d}{minute:02d}\"\n                scheduler.add_job(\n                    ScheduledTrigger.execute,\n                    trigger=\"cron\",\n                    day_of_week=dow,\n                    hour=str(hour),\n                    minute=str(minute),\n                    id=job_id,\n                    kwargs={\"trigger\": trigger, \"trigger_task\": task},\n                    replace_existing=True,\n                )\n\n\ndef _deploy_monthly(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None:\n    from common.job import scheduler\n\n    times = setting.get(\"time\") or []\n    days = setting.get(\"days\") or []\n    if not times or not days:\n        maxkb_logger.warning(f\"empty monthly setting, trigger_id={trigger_id}\")\n        return\n\n    for d in days:\n        try:\n            dom = int(d)\n            if not (1 <= dom <= 31):\n                raise ValueError(\"invalid day of month\")\n        except Exception:\n            maxkb_logger.warning(f\"invalid day={d}, trigger_id={trigger_id}\")\n            continue\n\n        for t in times:\n            try:\n                hour, minute = _parse_hhmm(t)\n            except Exception:\n                maxkb_logger.warning(f\"invalid time={t}, trigger_id={trigger_id}\")\n                continue\n\n            for task in trigger_tasks:\n                job_id = f\"trigger:{trigger_id}:task:{task['id']}:monthly:{dom:02d}:{hour:02d}{minute:02d}\"\n                scheduler.add_job(\n                    ScheduledTrigger.execute,\n                    trigger=\"cron\",\n                    day=str(dom),\n                    hour=str(hour),\n                    minute=str(minute),\n                    id=job_id,\n                    kwargs={\"trigger\": trigger, \"trigger_task\": task},\n                    replace_existing=True,\n                )\n\ndef _deploy_cron(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None:\n    from common.job import scheduler\n    from apscheduler.triggers.cron import CronTrigger\n\n    cron_expression = setting.get('cron_expression')\n    if not cron_expression:\n        maxkb_logger.warning(f\"empty cron_expression, trigger_id={trigger_id}\")\n        return\n\n    try:\n        cron_trigger = CronTrigger.from_crontab(cron_expression.strip())\n    except ValueError:\n        maxkb_logger.warning(f\"invalid cron_expression={cron_expression}, trigger_id={trigger_id}\")\n        return\n\n    for task in trigger_tasks:\n        job_id = f\"trigger:{trigger_id}:task:{task['id']}:cron:{cron_expression.strip()}\"\n        scheduler.add_job(\n            ScheduledTrigger.execute,\n            trigger=cron_trigger,\n            id=job_id,\n            kwargs={\"trigger\": trigger, \"trigger_task\": task},\n            replace_existing=True,\n        )\n\ndef _deploy_interval(trigger: dict, trigger_tasks: list[dict], setting: dict, trigger_id: str) -> None:\n    from common.job import scheduler\n\n    unit = (setting.get(\"interval_unit\") or \"\").strip()\n    value = setting.get(\"interval_value\")\n\n    try:\n        value_i = int(value)\n        if value_i <= 0:\n            raise ValueError(\"interval_value must be positive\")\n    except Exception:\n        maxkb_logger.warning(f\"invalid interval_value={value}, trigger_id={trigger_id}\")\n        return\n\n    if unit not in {\"seconds\", \"minutes\", \"hours\", \"days\"}:\n        maxkb_logger.warning(f\"invalid interval_unit={unit}, trigger_id={trigger_id}\")\n        return\n\n    for task in trigger_tasks:\n        job_id = f\"trigger:{trigger_id}:task:{task['id']}:interval:{unit}:{value_i}\"\n        scheduler.add_job(\n            ScheduledTrigger.execute,\n            trigger=\"interval\",\n            id=job_id,\n            kwargs={\"trigger\": trigger, \"trigger_task\": task},\n            replace_existing=True,\n            **{unit: value_i},\n        )\n\n@celery_app.task(name=\"celery:undeploy_scheduled_trigger\")\ndef _remove_trigger_jobs(trigger_id: str) -> None:\n    from common.job import scheduler\n\n    prefix = f\"trigger:{trigger_id}:\"\n    for job in scheduler.get_jobs():\n        if getattr(job, \"id\", \"\").startswith(prefix):\n            try:\n                job.remove()\n            except Exception as e:\n                maxkb_logger.warning(f\"remove job failed, job_id={job.id}, err={e}\")\n\n\n@celery_app.task(name=\"celery:deploy_scheduled_trigger\")\ndef deploy_scheduled_trigger(trigger: dict, trigger_tasks: list[dict], setting: dict, schedule_type: str) -> None:\n    _remove_trigger_jobs(trigger[\"id\"])\n\n    deployers = {\n        \"daily\": _deploy_daily,\n        \"weekly\": _deploy_weekly,\n        \"monthly\": _deploy_monthly,\n        \"interval\": _deploy_interval,\n        'cron': _deploy_cron\n    }\n    fn = deployers.get(schedule_type)\n    if not fn:\n        maxkb_logger.warning(f\"unsupported schedule_type={schedule_type}, trigger_id={trigger['id']}\")\n        return\n\n    fn(trigger, trigger_tasks, setting, trigger[\"id\"])\n\n\nclass ScheduledTrigger(BaseTrigger):\n    \"\"\"\n    定时任务触发器\n    \"\"\"\n\n    @staticmethod\n    def execute(trigger, **kwargs):\n        trigger_task = kwargs.get(\"trigger_task\")\n        if not trigger_task:\n            maxkb_logger.warning(f\"unsupported task={trigger_task}\")\n            return\n        source_type = trigger_task[\"source_type\"]\n\n        if source_type == \"APPLICATION\":\n            from trigger.handler.impl.task.application_task import ApplicationTask\n\n            ApplicationTask.execute(trigger_task, **kwargs)\n        elif source_type == \"TOOL\":\n            from trigger.handler.impl.task.tool_task import ToolTask\n\n            ToolTask.execute(trigger_task, **kwargs)\n        else:\n            maxkb_logger.warning(f\"unsupported source_type={source_type}, task_id={trigger_task['id']}\")\n\n    def support(self, trigger, **kwargs):\n        return trigger.get(\"trigger_type\") == \"SCHEDULED\"\n\n    def deploy(self, trigger, **kwargs):\n        trigger_id = str(trigger[\"id\"])\n        setting = trigger.get(\"trigger_setting\") or {}\n        schedule_type = setting.get(\"schedule_type\")\n\n        if not trigger.get(\"is_active\", True):\n            self.undeploy(trigger, **kwargs)\n            return\n\n        if trigger.get(\"trigger_type\") != \"SCHEDULED\":\n            self.undeploy(trigger, **kwargs)\n            return\n\n        trigger_tasks = _get_active_trigger_tasks(trigger[\"id\"])\n        if not trigger_tasks:\n            maxkb_logger.warning(f\"no active trigger_tasks, trigger_id={trigger_id}\")\n            self.undeploy(trigger, **kwargs)\n            return\n\n        deploy_scheduled_trigger.delay(trigger, trigger_tasks, setting, schedule_type)\n\n    def undeploy(self, trigger, **kwargs):\n        trigger_id = str(trigger[\"id\"])\n\n        _remove_trigger_jobs.delay(trigger_id)\n"
  },
  {
    "path": "apps/trigger/handler/simple_tools.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： simple_task.py\n    @date：2026/1/14 19:18\n    @desc:\n\"\"\"\nfrom threading import Thread\n\nfrom trigger.handler.impl.task.application_task import ApplicationTask\nfrom trigger.handler.impl.task.tool_task import ToolTask\nfrom trigger.handler.impl.trigger.event_trigger import EventTrigger\nfrom trigger.handler.impl.trigger.scheduled_trigger import ScheduledTrigger\n\nsimple_task_handlers = [ApplicationTask(), ToolTask()]\n\nsimple_trigger_handlers = [ScheduledTrigger(), EventTrigger()]\n\n\ndef execute(trigger_task, **kwargs):\n    \"\"\"\n    执行触发器任务\n    @param trigger_task:  触发器任务数据\n    @param kwargs:        额外数据\n    @return:\n    \"\"\"\n    for simple_task_handler in simple_task_handlers:\n        if simple_task_handler.support(trigger_task, **kwargs):\n            Thread(target=simple_task_handler.execute, args=(trigger_task,), kwargs=kwargs).start()\n            return\n    raise Exception(\"不支持的处理器类型\")\n\n\ndef deploy(trigger, **kwargs):\n    \"\"\"\n    部署触发器\n    @param trigger: 触发器字典数据\n    @param kwargs:  额外数据\n    @return:\n    \"\"\"\n    for simple_trigger_handler in simple_trigger_handlers:\n        if simple_trigger_handler.support(trigger, **kwargs):\n            return simple_trigger_handler.deploy(trigger, **kwargs)\n    raise Exception(\"不支持的触发器类型\")\n\n\ndef undeploy(trigger, **kwargs):\n    \"\"\"\n    取消部署触发器\n    @param trigger: 触发器字典数据\n    @param kwargs:  额外数据\n    @return:\n    \"\"\"\n    for simple_trigger_handler in simple_trigger_handlers:\n        return simple_trigger_handler.undeploy(trigger, **kwargs)\n    raise Exception(\"不支持的触发器类型\")\n"
  },
  {
    "path": "apps/trigger/migrations/0001_initial.py",
    "content": "# Generated by Django 5.2.9 on 2026-01-23 09:47\n\nimport common.encoder.encoder\nimport django.db.models.deletion\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n        ('users', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Trigger',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('workspace_id', models.CharField(db_index=True, default='default', max_length=64, verbose_name='工作空间id')),\n                ('name', models.CharField(db_index=True, max_length=128, verbose_name='触发器名称')),\n                ('desc', models.CharField(default='', max_length=512, verbose_name='引用描述')),\n                ('trigger_type', models.CharField(choices=[('SCHEDULED', 'Scheduled'), ('EVENT', 'Event')], default='SCHEDULED', max_length=256, verbose_name='触发器类型')),\n                ('trigger_setting', models.JSONField(default=dict)),\n                ('meta', models.JSONField(default=dict)),\n                ('is_active', models.BooleanField(db_index=True, default=True)),\n                ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.user')),\n            ],\n            options={\n                'db_table': 'event_trigger',\n            },\n        ),\n        migrations.CreateModel(\n            name='TriggerTask',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('source_type', models.CharField(choices=[('APPLICATION', 'Application'), ('TOOL', 'Tool')], default='APPLICATION', max_length=256, verbose_name='触发器任务类型')),\n                ('source_id', models.UUIDField(verbose_name='资源id')),\n                ('is_active', models.BooleanField(db_index=True, default=True)),\n                ('parameter', models.JSONField(default=list)),\n                ('meta', models.JSONField(default=dict)),\n                ('trigger', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='trigger.trigger')),\n            ],\n            options={\n                'db_table': 'event_trigger_task',\n                'unique_together': {('trigger', 'source_id', 'source_type')},\n            },\n        ),\n        migrations.CreateModel(\n            name='TaskRecord',\n            fields=[\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, verbose_name='修改时间')),\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('source_type', models.CharField(choices=[('APPLICATION', 'Application'), ('TOOL', 'Tool')], default='APPLICATION', max_length=256, verbose_name='触发器任务类型')),\n                ('source_id', models.UUIDField(verbose_name='资源id')),\n                ('task_record_id', models.UUIDField(verbose_name='任务记录id')),\n                ('meta', models.JSONField(default=dict, encoder=common.encoder.encoder.SystemEncoder)),\n                ('state', models.CharField(choices=[('PENDING', 'Pending'), ('STARTED', 'Started'), ('SUCCESS', 'Success'), ('FAILURE', 'Failure'), ('REVOKE', 'Revoke'), ('REVOKED', 'Revoked')], default='STARTED', max_length=20, verbose_name='状态')),\n                ('run_time', models.FloatField(default=0, verbose_name='运行时长')),\n                ('trigger', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='trigger.trigger')),\n                ('trigger_task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='trigger.triggertask')),\n            ],\n            options={\n                'db_table': 'event_trigger_task_record',\n            },\n        ),\n    ]\n"
  },
  {
    "path": "apps/trigger/migrations/0002_remove_taskrecord_trigger_task_and_more.py",
    "content": "# Generated by Django 5.2.9 on 2026-01-26 03:27\n\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Migration):\n\n    dependencies = [\n        ('trigger', '0001_initial'),\n    ]\n\n    operations = [\n        migrations.RemoveField(\n            model_name='taskrecord',\n            name='trigger_task',\n        ),\n        migrations.AddField(\n            model_name='taskrecord',\n            name='trigger_task_id',\n            field=models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, verbose_name='触发器任务id'),\n        ),\n    ]\n"
  },
  {
    "path": "apps/trigger/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "apps/trigger/models/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2026/1/9 16:13\n    @desc:\n\"\"\"\nfrom .trigger import *"
  },
  {
    "path": "apps/trigger/models/trigger.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： trigger.py.py\n    @date：2026/1/9 15:33\n    @desc:\n\"\"\"\nimport uuid_utils.compat as uuid\n\nfrom django.db import models\n\nfrom common.encoder.encoder import SystemEncoder\nfrom common.mixins.app_model_mixin import AppModelMixin\nfrom knowledge.models.knowledge_action import State\nfrom users.models import User\n\n\nclass TriggerTypeChoices(models.TextChoices):\n    SCHEDULED = 'SCHEDULED'\n    EVENT = 'EVENT'\n\n\nclass TriggerTaskTypeChoices(models.TextChoices):\n    APPLICATION = 'APPLICATION'\n    TOOL = 'TOOL'\n\n\nclass Trigger(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    workspace_id = models.CharField(max_length=64, verbose_name=\"工作空间id\", default=\"default\", db_index=True)\n    name = models.CharField(max_length=128, verbose_name=\"触发器名称\", db_index=True)\n    desc = models.CharField(max_length=512, verbose_name=\"引用描述\", default=\"\")\n    trigger_type = models.CharField(verbose_name=\"触发器类型\", choices=TriggerTypeChoices.choices,\n                                    default=TriggerTypeChoices.SCHEDULED, max_length=256)\n    trigger_setting = models.JSONField(default=dict)\n    meta = models.JSONField(default=dict)\n    is_active = models.BooleanField(default=True, db_index=True)\n    user = models.ForeignKey(User, on_delete=models.SET_NULL, db_constraint=False, blank=True, null=True)\n\n    class Meta:\n        db_table = \"event_trigger\"\n\n\nclass TriggerTask(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    trigger = models.ForeignKey(Trigger, on_delete=models.CASCADE)\n    source_type = models.CharField(verbose_name=\"触发器任务类型\", choices=TriggerTaskTypeChoices.choices,\n                                   default=TriggerTaskTypeChoices.APPLICATION, max_length=256\n                                   )\n    source_id = models.UUIDField(verbose_name=\"资源id\")\n    is_active = models.BooleanField(default=True, db_index=True)\n    parameter = models.JSONField(default=list)\n    meta = models.JSONField(default=dict)\n\n    class Meta:\n        unique_together = [('trigger', 'source_id', 'source_type')]\n        db_table = \"event_trigger_task\"\n\n\nclass TaskRecord(AppModelMixin):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n\n    trigger = models.ForeignKey(Trigger, on_delete=models.CASCADE)\n\n    trigger_task_id = models.UUIDField(max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"触发器任务id\")\n\n    source_type = models.CharField(verbose_name=\"触发器任务类型\", choices=TriggerTaskTypeChoices.choices,\n                                   default=TriggerTaskTypeChoices.APPLICATION, max_length=256)\n    source_id = models.UUIDField(verbose_name=\"资源id\")\n    task_record_id = models.UUIDField(verbose_name=\"任务记录id\")\n    meta = models.JSONField(default=dict, encoder=SystemEncoder)\n    state = models.CharField(verbose_name='状态', max_length=20,\n                             choices=State.choices,\n                             default=State.STARTED)\n    run_time = models.FloatField(verbose_name=\"运行时长\", default=0)\n\n    class Meta:\n        db_table = \"event_trigger_task_record\"\n"
  },
  {
    "path": "apps/trigger/serializers/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2026/1/9 16:16\n    @desc:\n\"\"\"\n"
  },
  {
    "path": "apps/trigger/serializers/task_source_trigger.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： task_source_trigger.py\n    @date：2026/1/22 16:18\n    @desc:\n\"\"\"\nfrom typing import Dict\n\nfrom django.db import transaction\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import Application\nfrom common.exception.app_exception import AppApiException\nfrom tools.models import Tool\nfrom trigger.models import TriggerTypeChoices, Trigger, TriggerTaskTypeChoices, TriggerTask\nfrom trigger.serializers.trigger import TriggerModelSerializer, TriggerSerializer, ApplicationTriggerTaskSerializer, \\\n    ToolTriggerTaskSerializer, TriggerTaskModelSerializer\n\n\nclass TaskSourceTriggerTaskEditRequest(serializers.Serializer):\n    meta = serializers.DictField(default=dict, required=False)\n    parameter = serializers.DictField(default=dict, required=False)\n\n\nclass TaskSourceTriggerEditRequest(serializers.Serializer):\n    name = serializers.CharField(required=False, label=_('trigger name'))\n    desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('trigger description'))\n    trigger_type = serializers.ChoiceField(required=False, choices=TriggerTypeChoices)\n    trigger_setting = serializers.DictField(required=False, label=_(\"trigger setting\"))\n    meta = serializers.DictField(default=dict, required=False)\n    trigger_task = TaskSourceTriggerTaskEditRequest(many=True, required=False)\n\n\nclass TaskSourceTriggerSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n    user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n\n    def insert(self, instance, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        if not len(instance.get(\"trigger_task\")) == 1:\n            raise AppApiException(500, _('Trigger task number must be one'))\n        source_id = instance.get('source_id')\n        source_type = instance.get('source_type')\n        source_trigger_task = instance.get('trigger_task')[0]\n        if not (instance.get('source_id') == source_id and source_trigger_task.get('source_type') == source_type):\n            raise AppApiException(500, _('Incorrect trigger task'))\n\n        return TriggerSerializer(data={\n            'workspace_id': self.data.get('workspace_id'),\n            'user_id': self.data.get('user_id')\n        }).insert(instance, with_valid=True)\n\n\nclass TaskSourceTriggerOperateSerializer(serializers.Serializer):\n    trigger_id = serializers.UUIDField(required=True, label=_('trigger id'))\n    workspace_id = serializers.CharField(required=False, label=_('workspace id'))\n    source_type = serializers.CharField(required=True, label=_('source type'))\n    source_id = serializers.CharField(required=True, label=_('source id'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Trigger id does not exist'))\n\n    def one(self, with_valid=True):\n        if with_valid:\n            self.is_valid()\n        trigger_id = self.data.get('trigger_id')\n        workspace_id = self.data.get('workspace_id')\n        source_id = self.data.get('source_id')\n        source_type = self.data.get('source_type')\n\n        trigger = QuerySet(Trigger).filter(workspace_id=workspace_id, id=trigger_id).first()\n        trigger_task = TriggerTaskModelSerializer(TriggerTask.objects.filter(\n            trigger_id=trigger_id, source_id=source_id, source_type=source_type).first()).data\n\n        if source_type == TriggerTaskTypeChoices.APPLICATION:\n            application_task = ApplicationTriggerTaskSerializer(\n                Application.objects.filter(workspace_id=workspace_id, id=source_id).first()).data\n            return {\n                **TriggerModelSerializer(trigger).data,\n                'trigger_task': trigger_task,\n                'application_task': application_task,\n            }\n        if source_type == TriggerTaskTypeChoices.TOOL:\n            tool_task = ToolTriggerTaskSerializer(\n                Tool.objects.filter(workspace_id=workspace_id, id=source_id).first()).data\n            return {\n                **TriggerModelSerializer(trigger).data,\n                'trigger_task': trigger_task,\n                'tool_task': tool_task,\n            }\n\n    @transaction.atomic\n    def edit(self, instance: Dict, with_valid=True):\n        from trigger.handler.simple_tools import deploy, undeploy\n\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        serializer = TaskSourceTriggerEditRequest(data=instance)\n        serializer.is_valid(raise_exception=True)\n        valid_data = serializer.validated_data\n        trigger_id = self.data.get('trigger_id')\n        workspace_id = self.data.get('workspace_id')\n        source_id = self.data.get('source_id')\n        source_type = self.data.get('source_type')\n\n        trigger = Trigger.objects.filter(workspace_id=workspace_id, id=trigger_id).first()\n        if not trigger:\n            raise serializers.ValidationError(_('Trigger not found'))\n        task_source_trigger_edit_field_list = ['name', 'desc', 'trigger_type', 'trigger_setting', 'meta']\n        trigger_deploy_edit_field_list = ['trigger_type', 'trigger_setting']\n\n        need_redeploy = any(field in instance for field in trigger_deploy_edit_field_list)\n\n        for field in task_source_trigger_edit_field_list:\n            if field in valid_data:\n                setattr(trigger, field, valid_data.get(field))\n        trigger.save()\n\n        trigger_task = valid_data.get('trigger_task')\n        if trigger_task is not None:\n            # 检查是否为空列表\n            if not trigger_task:\n                raise serializers.ValidationError(_('Trigger must have at least one task'))\n\n            TriggerTask.objects.filter(\n                source_id=source_id,\n                source_type=source_type,\n                trigger_id=trigger_id\n            ).update(parameter=trigger_task[0].get(\"parameter\"), meta=trigger_task[0].get(\"meta\"))\n        else:\n            # 用户没提交 trigger_task 字段，确保数据库中有 task\n            if not TriggerTask.objects.filter(trigger_id=trigger_id).exists():\n                raise serializers.ValidationError(_('Trigger must have at least one task'))\n\n        if need_redeploy:\n            if trigger.is_active and trigger.trigger_type == 'SCHEDULED':\n                deploy(TriggerModelSerializer(trigger).data, **{})\n            else:\n                undeploy(TriggerModelSerializer(trigger).data, **{})\n\n        return self.one()\n\n    # 删除的是当前trigger_id+source_id+source_type对应的task\n    @transaction.atomic\n    def delete(self):\n        from trigger.handler.simple_tools import undeploy\n\n        self.is_valid(raise_exception=True)\n        trigger_id = self.data.get('trigger_id')\n        workspace_id = self.data.get('workspace_id')\n        source_id = self.data.get('source_id')\n        source_type = self.data.get('source_type')\n\n        trigger = Trigger.objects.filter(workspace_id=workspace_id, id=trigger_id).first()\n        if not trigger:\n            raise AppApiException(404, _('Trigger not found'))\n        delete_count = TriggerTask.objects.filter(trigger_id=trigger_id, source_id=source_id,\n                                                  source_type=source_type).delete()[0]\n        if delete_count == 0:\n            raise AppApiException(404, _('Task not found'))\n        has_other_tasks = TriggerTask.objects.filter(trigger_id=trigger_id).exists()\n\n        undeploy(TriggerModelSerializer(trigger).data, **{})\n\n        if not has_other_tasks:\n            trigger.delete()\n        return True\n\n\nclass TaskSourceTriggerListSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n    source_type = serializers.CharField(required=True, label=_('source type'))\n    source_id = serializers.CharField(required=True, label=_('source id'))\n\n    def list(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n\n        triggers = Trigger.objects.filter(workspace_id=self.data.get(\"workspace_id\"),\n                                          triggertask__source_id=self.data.get(\"source_id\"),\n                                          triggertask__source_type=self.data.get(\"source_type\"),\n                                          is_active=True\n                                          ).distinct()\n\n        return [TriggerModelSerializer(trigger).data for trigger in triggers]\n"
  },
  {
    "path": "apps/trigger/serializers/trigger.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： trigger.py\n    @date：2026/1/14 11:48\n    @desc:\n\"\"\"\nimport os.path\nimport re\nfrom typing import Dict\n\nimport uuid_utils.compat as uuid\nfrom django.core import validators\nfrom django.db import models, transaction\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import Application\nfrom common.db.search import get_dynamics_model, native_page_search, native_search\nfrom common.exception.app_exception import AppApiException\nfrom common.field.common import ObjectField\nfrom common.utils.common import get_file_content\nfrom knowledge.serializers.common import BatchSerializer\nfrom maxkb.conf import PROJECT_DIR\nfrom tools.models import Tool\nfrom trigger.models import TriggerTypeChoices, Trigger, TriggerTaskTypeChoices, TriggerTask, TaskRecord\n\n\nclass BatchActiveSerializer(serializers.Serializer):\n    id_list = serializers.ListField(required=True, child=serializers.UUIDField(required=True), label=_('id list'))\n    is_active = serializers.BooleanField(required=True, label=_(\"is_active\"))\n\n    def is_valid(self, *, model=None, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        if model is not None:\n            id_list = self.data.get('id_list')\n            model_list = QuerySet(model).filter(id__in=id_list)\n            if len(model_list) != len(id_list):\n                model_id_list = [str(m.id) for m in model_list]\n                error_id_list = list(filter(lambda row_id: not model_id_list.__contains__(row_id), id_list))\n                raise AppApiException(500, _('The following id does not exist: %s') % ','.join(map(str, error_id_list)))\n\n\nclass InputField(serializers.Serializer):\n    source = serializers.CharField(required=True, label=_(\"source\"), validators=[\n        validators.RegexValidator(regex=re.compile(\"^custom|reference$\"),\n                                  message=_(\"The field only supports custom|reference\"), code=500)\n    ])\n    value = ObjectField(required=True, label=_(\"Variable Value\"), model_type_list=[str, list])\n\n\nclass ApplicationTaskParameterSerializer(serializers.Serializer):\n    question = InputField(required=True)\n    api_input_field_list = serializers.JSONField(required=False)\n    user_input_field_list = serializers.JSONField(required=False)\n    image_list = InputField(required=False)\n    document_list = InputField(required=False)\n    audio_list = InputField(required=False)\n    video_list = InputField(required=False)\n    other_list = InputField(required=False)\n\n    @staticmethod\n    def _validate_input_dict(value, field_name):\n        if not value:\n            return value\n        if not isinstance(value, dict):\n            raise serializers.ValidationError(_(\"%s must be a dict\") % field_name)\n\n        for key, val in value.items():\n            serializer = InputField(data=val)\n            if not serializer.is_valid():\n                raise serializers.ValidationError({f\"{field_name}.{key}\": serializer.errors})\n        return value\n\n    def validate_api_input_field_list(self, value):\n        return self._validate_input_dict(value, 'api_input_field_list')\n\n    def validate_user_input_field_list(self, value):\n        return self._validate_input_dict(value, 'user_input_field_list')\n\n\nclass ToolTaskParameterSerializer(serializers.Serializer):\n\n    def to_internal_value(self, data):\n        if not isinstance(data, dict):\n            raise serializers.ValidationError(\"must be a dict\")\n\n        validated = {}\n        for key, val in data.items():\n            serializer = InputField(data=val)\n            if not serializer.is_valid():\n                raise serializers.ValidationError({key: serializer.errors})\n            validated[key] = serializer.validated_data\n\n        return validated\n\nclass TriggerValidationMixin:\n\n    def validate(self, attrs):\n        # trigger_setting 校验\n        trigger_type = attrs.get('trigger_type')\n        trigger_setting = attrs.get('trigger_setting')\n\n        if trigger_type and trigger_setting:\n            if trigger_type == TriggerTypeChoices.SCHEDULED:\n                self._validate_scheduled_setting(trigger_setting)\n            elif trigger_type == TriggerTypeChoices.EVENT:\n                self._validate_event_setting(trigger_setting)\n            else:\n                raise AppApiException(500, _('Error trigger type'))\n\n        return attrs\n\n    @staticmethod\n    def _validate_required_field(setting, field_name, trigger_type):\n        if field_name not in setting:\n            raise serializers.ValidationError({\n                'trigger_setting': _('%s type requires %s field') % (trigger_type, field_name)\n            })\n\n    @staticmethod\n    def _validate_non_empty_array(value, field_name):\n        if not isinstance(value, list):\n            raise serializers.ValidationError({\n                'trigger_setting': _('%s must be an array') % field_name})\n        if len(value) == 0:\n            raise serializers.ValidationError({\n                'trigger_setting': _('%s must not be empty') % field_name})\n\n    @staticmethod\n    def _validate_number_range(values, field_name, min_val, max_val):\n        for val in values:\n            try:\n                num = int(str(val))\n                if num < min_val or num > max_val:\n                    raise ValueError\n            except (ValueError, TypeError):\n                raise serializers.ValidationError({\n                    'trigger_setting': _('%s values must be between %s and %s') % (field_name, min_val, max_val)\n                })\n\n    def _validate_time_array(self, time_list):\n        self._validate_non_empty_array(time_list, 'time')\n\n        for time_str in time_list:\n            self._validate_time_format(time_str)\n\n    @staticmethod\n    def _validate_time_format(time_str):\n        import re\n\n        pattern = r'^([01]\\d|2[0-3]):([0-5]\\d)$'\n        if not re.match(pattern, str(time_str)):\n            raise serializers.ValidationError({\n                'trigger_setting': _('Invalid time format: %s, must be HH:MM (e.g., 09:00)') % time_str\n            })\n\n    def _validate_scheduled_setting(self, setting):\n        schedule_type = setting.get('schedule_type')\n\n        valid_types = ['daily', 'weekly', 'monthly', 'interval','cron']\n        if schedule_type not in valid_types:\n            raise serializers.ValidationError(\n                {'trigger_setting': _('schedule_type must be one of %s') % ', '.join(valid_types)\n                 })\n        if schedule_type == 'daily':\n            self._validate_daily(setting)\n        elif schedule_type == 'weekly':\n            self._validate_weekly(setting)\n        elif schedule_type == 'monthly':\n            self._validate_monthly(setting)\n        elif schedule_type == 'interval':\n            self._validate_interval(setting)\n        elif schedule_type == 'cron':\n            self._validate_cron(setting)\n    def _validate_daily(self, setting):\n        self._validate_required_field(setting, 'time', 'daily')\n        self._validate_time_array(setting['time'])\n\n    def _validate_weekly(self, setting):\n        self._validate_required_field(setting, 'days', 'weekly')\n        self._validate_required_field(setting, 'time', 'weekly')\n        days = setting['days']\n        self._validate_non_empty_array(days, 'days')\n        self._validate_number_range(days, 'days', 1, 7)\n        self._validate_time_array(setting['time'])\n\n    def _validate_monthly(self, setting):\n        self._validate_required_field(setting, 'days', 'monthly')\n        self._validate_required_field(setting, 'time', 'monthly')\n        days = setting['days']\n        self._validate_non_empty_array(days, 'days')\n        self._validate_number_range(days, 'days', 1, 31)\n        self._validate_time_array(setting['time'])\n\n    def _validate_interval(self, setting):\n        self._validate_required_field(setting, 'interval_value', 'interval')\n        self._validate_required_field(setting, 'interval_unit', 'interval')\n        interval_value = setting['interval_value']\n        interval_unit = setting['interval_unit']\n        try:\n            value_int = int(interval_value)\n            if value_int < 1:\n                raise ValueError\n        except (ValueError, TypeError):\n            raise serializers.ValidationError({\n                'trigger_setting': _('interval_value must be an integer greater than or equal to 1')\n            })\n        valid_units = ['minutes', 'hours']\n        if interval_unit not in valid_units:\n            raise serializers.ValidationError({\n                'trigger_setting': _('interval_unit must be one of %s') % ', '.join(valid_units)\n            })\n    @staticmethod\n    def _validate_cron(setting):\n        from apscheduler.triggers.cron import CronTrigger\n\n        cron_expression: str = setting.get('cron_expression')\n        if not cron_expression:\n            raise serializers.ValidationError({\n                'trigger_setting': _('cron type requires cron_expression field')\n            })\n        try:\n            CronTrigger.from_crontab(cron_expression.strip())\n        except ValueError:\n            raise serializers.ValidationError({\n                'trigger_setting': _('Invalid cron expression: %s') % cron_expression\n            })\n\n    @staticmethod\n    def _validate_event_setting(setting):\n        body = setting.get('body')\n        if body is not None and not isinstance(body, list):\n            raise serializers.ValidationError({\n                'trigger_setting': _('body must be an array')\n            })\n\n\nclass TriggerTaskCreateRequest(serializers.Serializer):\n    source_type = serializers.ChoiceField(required=True, choices=TriggerTaskTypeChoices)\n    source_id = serializers.CharField(required=True, label=_('source_id'))\n    is_active = serializers.BooleanField(required=False, label=_('Is active'))\n    meta = serializers.DictField(default=dict, required=False)\n    parameter = serializers.DictField(default=dict, required=False)\n\n    def validate(self, attrs):\n        source_type = attrs.get('source_type')\n        parameter = attrs.get('parameter')\n        if source_type == TriggerTaskTypeChoices.APPLICATION:\n            serializer = ApplicationTaskParameterSerializer(data=parameter)\n            serializer.is_valid(raise_exception=True)\n            attrs['parameter'] = serializer.validated_data\n        if source_type == TriggerTaskTypeChoices.TOOL:\n            serializer = ToolTaskParameterSerializer(data=parameter)\n            serializer.is_valid(raise_exception=True)\n            attrs['parameter'] = serializer.validated_data\n\n        return attrs\n\n\nclass TriggerTaskEditRequest(serializers.Serializer):\n    source_type = serializers.ChoiceField(required=False, choices=TriggerTaskTypeChoices)\n    source_id = serializers.CharField(required=False, label=_('source_id'))\n    is_active = serializers.BooleanField(required=False, label=_('Is active'))\n    meta = serializers.DictField(default=dict, required=False)\n    parameter = serializers.DictField(default=dict, required=False)\n\n    def validate(self, attrs):\n        source_type = attrs.get('source_type')\n        parameter = attrs.get('parameter')\n        if source_type == TriggerTaskTypeChoices.APPLICATION:\n            serializer = ApplicationTaskParameterSerializer(data=parameter)\n            serializer.is_valid(raise_exception=True)\n            attrs['parameter'] = serializer.validated_data\n        if source_type == TriggerTaskTypeChoices.TOOL:\n            serializer = ToolTaskParameterSerializer(data=parameter)\n            serializer.is_valid(raise_exception=True)\n            attrs['parameter'] = serializer.validated_data\n\n        return attrs\n\n\nclass TriggerEditRequest(TriggerValidationMixin, serializers.Serializer):\n    name = serializers.CharField(required=False, label=_('trigger name'))\n    desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('trigger description'))\n    trigger_type = serializers.ChoiceField(required=False, choices=TriggerTypeChoices)\n    trigger_setting = serializers.DictField(required=False, label=_(\"trigger setting\"))\n    meta = serializers.DictField(default=dict, required=False)\n    trigger_task = TriggerTaskEditRequest(many=True, required=False)\n\n\nclass TriggerCreateRequest(TriggerValidationMixin, serializers.Serializer):\n    id = serializers.UUIDField(required=True, label=_(\"Trigger ID\"))\n    name = serializers.CharField(required=True, label=_('trigger name'))\n    desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('trigger description'))\n    trigger_type = serializers.ChoiceField(required=True, choices=TriggerTypeChoices)\n    trigger_setting = serializers.DictField(required=True, label=_(\"trigger setting\"))\n    meta = serializers.DictField(default=dict, required=False)\n    is_active = serializers.BooleanField(required=False, label=_('Is active'))\n    trigger_task = TriggerTaskCreateRequest(many=True)\n\n\nclass TriggerModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Trigger\n        fields = \"__all__\"\n\n\nclass TriggerTaskModelSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = TriggerTask\n        fields = \"__all__\"\n\n\nclass ApplicationTriggerTaskSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Application\n        fields = ['id', 'name', 'work_flow', 'icon', 'type']\n\n\nclass ToolTriggerTaskSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Tool\n        fields = ['id', 'name', 'input_field_list', 'icon']\n\n\nclass TriggerResponse(serializers.ModelSerializer):\n    class Meta:\n        model = Trigger\n        fields = \"__all__\"\n\n\nclass TriggerSerializer(serializers.Serializer):\n    workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n    user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n\n    @transaction.atomic\n    def insert(self, instance, with_valid=True):\n        from trigger.handler.simple_tools import deploy\n\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        serializer = TriggerCreateRequest(data=instance)\n        serializer.is_valid(raise_exception=True)\n        valid_data = serializer.validated_data\n\n        trigger_id = valid_data.get('id') if valid_data.get('id') else uuid.uuid7()\n\n        trigger_model = Trigger(\n            id=trigger_id,\n            name=valid_data.get('name'),\n            workspace_id=self.data.get('workspace_id'),\n            desc=valid_data.get('desc') or '',\n            trigger_type=valid_data.get('trigger_type'),\n            trigger_setting=valid_data.get('trigger_setting'),\n            meta=valid_data.get('meta', {}),\n            is_active=valid_data.get('is_active') or False,\n            user_id=self.data.get('user_id'),\n        )\n        trigger_model.save()\n\n        trigger_tasks = valid_data.get('trigger_task')\n        if trigger_tasks:\n\n            is_active_map = self.batch_get_source_active_status(trigger_tasks)\n\n            trigger_task_models = [\n                TriggerTask(\n                    id=uuid.uuid7(),\n                    trigger_id=trigger_id,\n                    source_type=task_data.get('source_type'),\n                    source_id=task_data.get('source_id'),\n                    is_active=is_active_map.get((task_data.get('source_type'), task_data.get('source_id'))) or False,\n                    parameter=task_data.get('parameter', {}),\n                    meta=task_data.get('meta', {})\n                )\n                for task_data in trigger_tasks\n            ]\n\n            TriggerTask.objects.bulk_create(trigger_task_models)\n        else:\n            raise AppApiException(500, _('Trigger task can not be empty'))\n\n        if trigger_model.is_active:\n            deploy(TriggerModelSerializer(trigger_model).data, **{})\n        return TriggerResponse(trigger_model).data\n\n    @staticmethod\n    def batch_get_source_active_status(trigger_tasks: list) -> Dict[tuple, bool]:\n        \"\"\"\n        批量查询所有 source 的 is_active 状态\n        返回: {(source_type, source_id): is_active}\n        \"\"\"\n        config = {\n            TriggerTaskTypeChoices.APPLICATION: (Application, 'is_publish'),\n            TriggerTaskTypeChoices.TOOL: (Tool, 'is_active'),\n        }\n        source_ids_by_type = {}\n\n        for task_data in trigger_tasks:\n            source_type = task_data.get('source_type')\n            source_id = task_data.get('source_id')\n\n            if source_type not in config:\n                raise AppApiException(500, _('Error source type'))\n\n            if source_type not in source_ids_by_type:\n                source_ids_by_type[source_type] = []\n            source_ids_by_type[source_type].append(source_id)\n\n        is_active_map = {}\n        for source_type, source_ids in source_ids_by_type.items():\n            source_model, field = config[source_type]\n            source_query_set = QuerySet(source_model).filter(id__in=source_ids).values('id', field)\n\n            for source in source_query_set:\n                is_active_map[(source_type, str(source['id']))] = source[field]\n\n        return is_active_map\n\n    @staticmethod\n    def is_active_source(source_type: str, source_id: str):\n\n        config = {\n            TriggerTaskTypeChoices.APPLICATION: (Application, 'is_publish'),\n            TriggerTaskTypeChoices.TOOL: (Tool, 'is_active'),\n        }\n        if source_type not in config:\n            raise AppApiException(500, _('Error source type'))\n        source_model, field = config.get(TriggerTaskTypeChoices(source_type))\n        source = QuerySet(source_model).filter(id=source_id).first()\n        if not source:\n            raise AppApiException(500, _('%s id does not exist') % source_type)\n\n        return getattr(source, field)\n\n    class Batch(serializers.Serializer):\n        workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n        user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n\n        @transaction.atomic\n        def batch_delete(self, instance: Dict, with_valid=True):\n            from trigger.handler.simple_tools import deploy, undeploy\n\n            if with_valid:\n                BatchSerializer(data=instance).is_valid(model=Trigger, raise_exception=True)\n                self.is_valid(raise_exception=True)\n            workspace_id = self.data.get(\"workspace_id\")\n            trigger_id_list = instance.get(\"id_list\")\n            for trigger_id in trigger_id_list:\n                trigger = QuerySet(Trigger).filter(id=trigger_id).first()\n                undeploy(TriggerModelSerializer(trigger).data, **{})\n\n            TaskRecord.objects.filter(trigger_id__in=trigger_id_list).delete()\n            TriggerTask.objects.filter(trigger_id__in=trigger_id_list).delete()\n            Trigger.objects.filter(workspace_id=workspace_id, id__in=trigger_id_list).delete()\n\n            return True\n\n        @transaction.atomic\n        def batch_switch(self, instance: Dict, with_valid=True):\n            from trigger.handler.simple_tools import deploy, undeploy\n\n            if with_valid:\n                BatchActiveSerializer(data=instance).is_valid(model=Trigger, raise_exception=True)\n                self.is_valid(raise_exception=True)\n            workspace_id = self.data.get(\"workspace_id\")\n            trigger_id_list = instance.get(\"id_list\")\n            is_active = instance.get(\"is_active\")\n            Trigger.objects.filter(workspace_id=workspace_id, id__in=trigger_id_list, is_active=not is_active).update(\n                is_active=is_active)\n            if is_active:\n                for trigger_id in trigger_id_list:\n                    trigger = QuerySet(Trigger).filter(id=trigger_id).first()\n                    deploy(TriggerModelSerializer(trigger).data, **{})\n            else:\n                for trigger_id in trigger_id_list:\n                    trigger = QuerySet(Trigger).filter(id=trigger_id).first()\n                    undeploy(TriggerModelSerializer(trigger).data, **{})\n\n            return True\n\n\nclass TriggerOperateSerializer(serializers.Serializer):\n    trigger_id = serializers.UUIDField(required=True, label=_('trigger id'))\n    user_id = serializers.UUIDField(required=True, label=_(\"User ID\"))\n    workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Trigger id does not exist'))\n\n    @transaction.atomic\n    def edit(self, instance: Dict, with_valid=True):\n        from trigger.handler.simple_tools import deploy, undeploy\n\n        if with_valid:\n            self.is_valid()\n            TriggerEditRequest(data=instance).is_valid(raise_exception=True)\n        trigger_id = self.data.get('trigger_id')\n        workspace_id = self.data.get('workspace_id')\n        trigger = Trigger.objects.filter(workspace_id=workspace_id, id=trigger_id).first()\n        if not trigger:\n            raise serializers.ValidationError(_('Trigger not found'))\n\n        trigger_direct_edit_field_list = ['name', 'desc', 'trigger_type', 'trigger_setting', 'meta', 'is_active']\n        trigger_deploy_edit_field_list = ['trigger_type', 'trigger_setting', 'is_active']\n        # is need to redeploy\n        need_redeploy = any(field in instance for field in trigger_deploy_edit_field_list)\n\n        for field in trigger_direct_edit_field_list:\n            if field in instance:\n                trigger.__setattr__(field, instance.get(field))\n        trigger.save()\n        # 处理trigger task\n        trigger_tasks = instance.get('trigger_task')\n\n        if trigger_tasks is not None:\n            # 检查是否为空列表\n            if not trigger_tasks:\n                raise serializers.ValidationError(_('Trigger must have at least one task'))\n\n            is_active_map = TriggerSerializer.batch_get_source_active_status(trigger_tasks)\n\n            trigger_task_model_list = [TriggerTask(\n                id=task_data.get('id') or uuid.uuid7(),\n                trigger_id=trigger_id,\n                source_type=task_data.get('source_type'),\n                source_id=task_data.get('source_id'),\n                is_active=is_active_map.get((task_data.get('source_type'), task_data.get('source_id'))) or False,\n                parameter=task_data.get('parameter', []),\n                meta=task_data.get('meta', {})\n            ) for task_data in trigger_tasks]\n\n            TriggerTask.objects.filter(trigger_id=trigger_id).delete()\n\n            TriggerTask.objects.bulk_create(trigger_task_model_list)\n        else:\n            # 用户没提交 trigger_task 字段，确保数据库中有 task\n            if not TriggerTask.objects.filter(trigger_id=trigger_id).exists():\n                raise serializers.ValidationError(_('Trigger must have at least one task'))\n\n        # 重新部署触发器任务\n        if need_redeploy:\n            if trigger.is_active and trigger.trigger_type == 'SCHEDULED':\n                deploy(TriggerModelSerializer(trigger).data, **{})\n            else:\n                undeploy(TriggerModelSerializer(trigger).data, **{})\n\n        return self.one(with_valid=False)\n\n    def delete(self):\n        from trigger.handler.simple_tools import deploy, undeploy\n\n        self.is_valid(raise_exception=True)\n        trigger_id = self.data.get('trigger_id')\n        trigger = QuerySet(Trigger).filter(workspace_id=self.data.get('workspace_id'), id=trigger_id).first()\n        if trigger:\n            undeploy(TriggerModelSerializer(trigger).data, **{})\n        TaskRecord.objects.filter(trigger_id=trigger_id).delete()\n        TriggerTask.objects.filter(trigger_id=trigger_id).delete()\n        Trigger.objects.filter(id=trigger_id).delete()\n        return True\n\n    def one(self, with_valid=True):\n        if with_valid:\n            self.is_valid()\n        trigger_id = self.data.get('trigger_id')\n        workspace_id = self.data.get('workspace_id')\n        trigger = QuerySet(Trigger).filter(workspace_id=workspace_id, id=trigger_id).first()\n\n        trigger_tasks = list(QuerySet(TriggerTask).filter(trigger_id=trigger_id))\n\n        application_ids = []\n        tool_ids = []\n        for task in trigger_tasks:\n            if task.source_type == TriggerTaskTypeChoices.APPLICATION:\n                application_ids.append(task.source_id)\n            elif task.source_type == TriggerTaskTypeChoices.TOOL:\n                tool_ids.append(task.source_id)\n\n        trigger_task_list = TriggerTaskModelSerializer(trigger_tasks, many=True).data\n\n        application_task_list = []\n        if application_ids:\n            applications = Application.objects.filter(workspace_id=workspace_id, id__in=application_ids)\n            application_task_list = ApplicationTriggerTaskSerializer(applications, many=True).data\n        tool_task_list = []\n        if tool_ids:\n            tools = Tool.objects.filter(workspace_id=workspace_id, id__in=tool_ids)\n            tool_task_list = ToolTriggerTaskSerializer(tools, many=True).data\n\n        return {\n            **TriggerModelSerializer(trigger).data,\n            'trigger_task': trigger_task_list,\n            'application_task_list': application_task_list,\n            'tool_task_list': tool_task_list,\n        }\n\n\nclass TriggerQuerySerializer(serializers.Serializer):\n    name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('Trigger name'))\n    type = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Trigger type'))\n    is_active = serializers.BooleanField(required=False, allow_null=True, label=_('Is active'))\n    task = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Trigger task'))\n    create_user = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Create user'))\n    workspace_id = serializers.CharField(required=True, label=_('workspace id'))\n\n    def get_query_set(self):\n        trigger_query_set = QuerySet(\n            model=get_dynamics_model({\n                't.name': models.CharField(),\n                'trigger_type': models.CharField(),\n                't.workspace_id': models.CharField(),\n                't.is_active': models.BooleanField(),\n                't.user_id': models.CharField(),\n            }))\n        task_query_set = QuerySet(model=get_dynamics_model({\n            'trigger_task_str': models.CharField(),\n        }))\n        trigger_query_set = trigger_query_set.filter(**{'t.workspace_id': self.data.get(\"workspace_id\")})\n        if self.data.get(\"name\"):\n            trigger_query_set = trigger_query_set.filter(**{'t.name__icontains': self.data.get(\"name\")})\n        if self.data.get(\"type\"):\n            trigger_query_set = trigger_query_set.filter(trigger_type=self.data.get(\"type\"))\n        if self.data.get(\"is_active\") is not None:\n            trigger_query_set = trigger_query_set.filter(**{\"t.is_active\": self.data.get(\"is_active\")})\n        if self.data.get(\"task\"):\n            task_query_set = task_query_set.filter(trigger_task_str__icontains=self.data.get(\"task\"))\n        if self.data.get(\"create_user\"):\n            trigger_query_set = trigger_query_set.filter(**{\"t.user_id\": self.data.get(\"create_user\")})\n\n        return {\"trigger_query_set\": trigger_query_set, \"task_query_set\": task_query_set}\n\n    def page(self, current_page: int, page_size: int, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        return native_page_search(current_page, page_size, self.get_query_set(), get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", \"trigger\", \"sql\", \"get_trigger_page_list.sql\")\n        ))\n\n    def list(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        return native_search(self.get_query_set(), select_string=get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", \"trigger\", \"sql\", \"get_trigger_page_list.sql\")))\n"
  },
  {
    "path": "apps/trigger/serializers/trigger_task.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： trigger_task.py\n    @date：2026/1/14 16:34\n    @desc:\n\"\"\"\nimport os\n\nfrom django.db import models\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import ChatRecord\nfrom common.db.search import native_page_search, get_dynamics_model\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import get_file_content\nfrom knowledge.models.knowledge_action import State\nfrom maxkb.conf import PROJECT_DIR\nfrom tools.models import ToolRecord\nfrom trigger.models import TriggerTask, TaskRecord, Trigger\n\n\nclass ChatRecordSerializerModel(serializers.ModelSerializer):\n    class Meta:\n        model = ChatRecord\n        fields = ['id', 'chat_id', 'vote_status', 'vote_reason', 'vote_other_content', 'problem_text', 'answer_text',\n                  'message_tokens', 'answer_tokens', 'const', 'improve_paragraph_id_list', 'run_time', 'index',\n                  'answer_text_list', 'details',\n                  'create_time', 'update_time']\n\n\nclass TriggerTaskResponse(serializers.ModelSerializer):\n    class Meta:\n        model = TriggerTask\n        fields = \"__all__\"\n\n\nclass TriggerTaskQuerySerializer(serializers.Serializer):\n    trigger_id = serializers.CharField(required=True, label=_(\"Trigger ID\"))\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Trigger id does not exist'))\n\n    def get_query_set(self):\n        query_set = QuerySet(TriggerTask).filter(workspace_id=self.data.get(\"workspace_id\")).filter(\n            trigger_id=self.data.get(\"trigger_id\"))\n        return query_set\n\n    def list(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        return [TriggerTaskResponse(row).data for row in self.get_query_set()]\n\n\nclass TriggerTaskRecordOperateSerializer(serializers.Serializer):\n    trigger_id = serializers.CharField(required=True, label=_(\"Trigger ID\"))\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n    trigger_task_id = serializers.CharField(required=True, label=_(\"Trigger task ID\"))\n    trigger_task_record_id = serializers.CharField(required=True, label=_(\"Trigger task record ID\"))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Trigger id does not exist'))\n\n    def get_execution_details(self, is_valid=True):\n        if is_valid:\n            self.is_valid(raise_exception=True)\n        task_record = QuerySet(TaskRecord).filter(trigger_id=self.data.get(\"trigger_id\"),\n                                                  trigger_task_id=self.data.get(\"trigger_task_id\"),\n                                                  id=self.data.get('trigger_task_record_id')).first()\n        if not task_record:\n            raise AppApiException(500, _('Trigger task record id does not exist'))\n        if task_record.source_type == 'APPLICATION':\n            chat_record = QuerySet(ChatRecord).filter(id=task_record.task_record_id).first()\n            if chat_record:\n                return ChatRecordSerializerModel(chat_record).data\n            return {\n                'state': 'TRIGGER_ERROR',\n                'meta': task_record.meta\n            }\n        if task_record.source_type == 'TOOL':\n            tool_record = QuerySet(ToolRecord).filter(id=task_record.task_record_id).first()\n            if tool_record:\n                return {\n                    'id': tool_record.id,\n                    'tool_id': tool_record.tool_id,\n                    'workspace_id': tool_record.workspace_id,\n                    'source_type': tool_record.source_type,\n                    'source_id': tool_record.source_id,\n                    'meta': tool_record.meta,\n                    'state': tool_record.state,\n                    'run_time': tool_record.run_time,\n                    'details': {\n                        'tool_call': {\n                            'index': 1,\n                            'result': tool_record.meta.get('output'),\n                            'params': tool_record.meta.get('input'),\n                            'status': 500 if tool_record.state == State.FAILURE else 200 if tool_record.state == State.SUCCESS else 201,\n                            'type': 'tool-node',\n                            'err_message': tool_record.meta.get('err_message')\n                        }\n                    }\n                }\n            return {\n                'state': 'TRIGGER_ERROR',\n                'meta': task_record.meta\n            }\n\n\nclass TriggerTaskRecordQuerySerializer(serializers.Serializer):\n    trigger_id = serializers.CharField(required=True, label=_(\"Trigger ID\"))\n    workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_(\"Workspace ID\"))\n    state = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Trigger state'))\n    name = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Trigger name'))\n    source_type = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_('Source type'))\n    order = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('Order field'))\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        workspace_id = self.data.get('workspace_id')\n        query_set = QuerySet(Trigger).filter(id=self.data.get('trigger_id'))\n        if workspace_id:\n            query_set = query_set.filter(workspace_id=workspace_id)\n        if not query_set.exists():\n            raise AppApiException(500, _('Trigger id does not exist'))\n\n    def get_query_set(self):\n        trigger_query_set = QuerySet(\n            model=get_dynamics_model({\n                'ett.create_time': models.DateTimeField(),\n                'ett.state': models.CharField(),\n                'sdc.name': models.CharField(),\n                'ett.workspace_id': models.CharField(),\n                'ett.trigger_id': models.UUIDField(),\n                'sdc.source_type': models.CharField()\n            }))\n        trigger_query_set = trigger_query_set.filter(\n            **{'ett.trigger_id': self.data.get(\"trigger_id\")})\n        if self.data.get(\"order\"):\n            trigger_query_set = trigger_query_set.order_by(self.data.get(\"order\"))\n        else:\n            trigger_query_set = trigger_query_set.order_by(\"-ett.create_time\")\n        if self.data.get('state'):\n            trigger_query_set = trigger_query_set.filter(**{'ett.state': self.data.get('state')})\n        if self.data.get(\"name\"):\n            trigger_query_set = trigger_query_set.filter(**{'sdc.name__contains': self.data.get('name')})\n        if self.data.get('source_type'):\n            trigger_query_set = trigger_query_set.filter(**{'sdc.source_type': self.data.get('source_type')})\n        return trigger_query_set\n\n    def list(self, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        return [TriggerTaskResponse(row).data for row in self.get_query_set()]\n\n    def page(self, current_page, page_size, with_valid=True):\n        if with_valid:\n            self.is_valid(raise_exception=True)\n        return native_page_search(current_page, page_size, self.get_query_set(), get_file_content(\n            os.path.join(PROJECT_DIR, \"apps\", \"trigger\", \"sql\", 'get_trigger_task_record_page_list.sql')\n        ))\n"
  },
  {
    "path": "apps/trigger/sql/get_trigger_page_list.sql",
    "content": "WITH scheduler AS (SELECT SPLIT_PART(id, ':', 2) as trigger_id,\n                          id,\n                          next_run_time\n                   FROM django_apscheduler_djangojob\n                   WHERE id LIKE 'trigger:%%')\nSELECT *\nFROM (SELECT t.id,\n             t.workspace_id,\n             t.name,\n             t.\"desc\",\n             t.trigger_type,\n             t.trigger_setting,\n             t.meta::JSON,\n             t.is_active,\n             t.create_time,\n             t.update_time,\n             t.user_id,\n             (SELECT nick_name FROM \"user\" WHERE id = t.user_id) AS create_user,\n             COALESCE(\n                     (ARRAY_AGG(sj.next_run_time ORDER BY sj.next_run_time))[1],\n                     NULL\n             )  as next_run_time,\n             COALESCE(\n                     JSON_AGG(\n                             JSON_BUILD_OBJECT(\n                                     'type', tt.source_type,\n                                     'name', COALESCE(app.name, tool.name),\n                                     'icon', COALESCE(app.icon, tool.icon)\n                             )\n                     ), '[]'::JSON\n             )                                              AS trigger_task,\n             STRING_AGG(COALESCE(app.name, tool.name), ' ') AS trigger_task_str\n      FROM event_trigger t\n               LEFT JOIN scheduler sj ON sj.trigger_id=t.id::text\n               LEFT JOIN event_trigger_task tt ON t.id = tt.trigger_id\n               LEFT JOIN application app ON tt.source_type = 'APPLICATION' AND tt.source_id = app.id\n               LEFT JOIN tool ON tt.source_type = 'TOOL' AND tt.source_id = tool.id\n          ${trigger_query_set}\n      GROUP BY t.id, t.workspace_id, t.name, t.desc, t.trigger_type, t.trigger_setting, t.meta, t.is_active,\n               t.create_time,\n               t.update_time, t.user_id) AS sub\n    ${task_query_set}\nORDER BY sub.create_time DESC"
  },
  {
    "path": "apps/trigger/sql/get_trigger_task_record_page_list.sql",
    "content": "WITH source_data_cte AS (SELECT 'APPLICATION' as source_type,\n                                id,\n                                \"name\",\n                                \"desc\",\n                                \"user_id\",\n                                \"workspace_id\",\n                                \"icon\",\n                                \"type\",\n                                \"folder_id\"\n                         FROM application\n                         UNION ALL\n                         SELECT 'TOOL'            as source_type,\n                                id,\n                                \"name\",\n                                \"desc\",\n                                \"user_id\",\n                                \"workspace_id\",\n                                \"icon\",\n                                \"tool_type\"::text as \"type\",\n                                \"folder_id\"\n                         FROM tool)\nselect ett.*,\n       ett.meta::json as meta,\n       sdc.name as source_name,\n       sdc.icon as source_icon,\n       sdc.type as type\nfrom event_trigger_task_record ett\n         left join source_data_cte sdc\n                   on ett.source_id = sdc.id and ett.source_type = sdc.source_type\n"
  },
  {
    "path": "apps/trigger/tasks.py",
    "content": "# apps/trigger/tasks.py\n# coding=utf-8\nfrom __future__ import annotations\n\n# 作为 Celery autodiscover 的入口，确保任务模块被导入从而完成注册\nfrom trigger.handler.impl.trigger.scheduled_trigger import deploy_scheduled_trigger  # noqa: F401\n"
  },
  {
    "path": "apps/trigger/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/trigger/urls.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： urls.py\n    @date：2026/1/9 16:15\n    @desc:\n\"\"\"\nfrom django.urls import path\n\nfrom . import views\nfrom .handler.impl.trigger.event_trigger import EventTriggerView\n\napp_name = \"trigger\"\n\n# @formatter:off\nurlpatterns = [\n    path('workspace/<str:workspace_id>/trigger', views.TriggerView.as_view(), name='trigger'),\n    path('workspace/<str:workspace_id>/trigger/batch_delete', views.TriggerView.BatchDelete.as_view(), name='delete batch'),\n    path('workspace/<str:workspace_id>/trigger/batch_activate', views.TriggerView.BatchActivate.as_view(), name='activate batch'),\n    path('workspace/<str:workspace_id>/trigger/<str:trigger_id>', views.TriggerView.Operate.as_view(), name='trigger operate'),\n    path('workspace/<str:workspace_id>/trigger/<int:current_page>/<int:page_size>', views.TriggerView.Page.as_view(), name='trigger_page'),\n    path('workspace/<str:workspace_id>/<str:source_type>/<str:source_id>/trigger/<str:trigger_id>', views.TaskSourceTriggerView.Operate.as_view(), name='task source trigger operate'),\n    path('workspace/<str:workspace_id>/<str:source_type>/<str:source_id>/trigger', views.TaskSourceTriggerView.as_view(), name='task source trigger'),\n    path('workspace/<str:workspace_id>/trigger/<str:trigger_id>/task_record/<int:current_page>/<int:page_size>', views.TriggerTaskRecordPageView.as_view(), name='trigger_task_record'),\n    path('workspace/<str:workspace_id>/trigger/<str:trigger_id>/task', views.TriggerTaskView.as_view(), name='task'),\n    path('trigger/v1/webhook/<str:trigger_id>', EventTriggerView.as_view(), name='trigger_webhook'),\n    path('workspace/<str:workspace_id>/trigger/<str:trigger_id>/trigger_task/<str:trigger_task_id>/trigger_task_record/<str:trigger_task_record_id>', views.TriggerTaskRecordExecutionDetailsView.as_view(), name='task source trigger'),\n]\n"
  },
  {
    "path": "apps/trigger/views/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： __init__.py.py\n    @date：2026/1/9 16:15\n    @desc:\n\"\"\"\nfrom .trigger import *\nfrom .trigger_task import *\n"
  },
  {
    "path": "apps/trigger/views/trigger.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：niu\n    @file： trigger.py\n    @date：2026/1/14 11:44\n    @desc:\n\"\"\"\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom application.api.application_api import ApplicationCreateAPI\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants, ViewPermission, CompareConstants, \\\n    Permission, Group, Operate\nfrom common.log.log import log\nfrom common.result import DefaultResultSerializer\nfrom trigger.models import Trigger\nfrom trigger.serializers.task_source_trigger import TaskSourceTriggerListSerializer, TaskSourceTriggerOperateSerializer, \\\n    TaskSourceTriggerSerializer\nfrom trigger.serializers.trigger import TriggerQuerySerializer, TriggerOperateSerializer\n\nfrom trigger.api.trigger import TriggerCreateAPI, TriggerOperateAPI, TriggerEditAPI, TriggerBatchDeleteAPI, \\\n    TriggerBatchActiveAPI, TaskSourceTriggerOperateAPI, TaskSourceTriggerAPI, TaskSourceTriggerCreateAPI, \\\n    TriggerQueryAPI, TriggerQueryPageAPI\nfrom trigger.serializers.trigger import TriggerSerializer\n\n\ndef get_trigger_operation_object(trigger_id):\n    trigger_model = QuerySet(model=Trigger).filter(id=trigger_id).first()\n    if trigger_model is not None:\n        return {\n            \"name\": trigger_model.name\n        }\n\n\ndef get_trigger_operation_object_batch(trigger_id_list):\n    trigger_model_list = QuerySet(model=Trigger).filter(id__in=trigger_id_list)\n    if trigger_model_list is not None:\n        return {\n            \"name\": f'[{\",\".join([trigger_model.name for trigger_model in trigger_model_list])}]',\n            \"trigger_list\": [{'name': trigger_model.name, 'type': trigger_model.type} for trigger_model in\n                             trigger_model_list]\n        }\n\n\nclass TriggerView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create trigger'),\n        summary=_('Create trigger'),\n        operation_id=_('Create trigger'),  # type: ignore\n        parameters=TriggerCreateAPI.get_parameters(),\n        request=TriggerCreateAPI.get_request(),\n        responses=TriggerCreateAPI.get_response(),\n        tags=[_('Trigger')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.TRIGGER_CREATE.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n    )\n    @log(\n        menu=\"Trigger\", operate=\"Create trigger\",\n        get_operation_object=lambda r, k: r.data.get('name'),\n    )\n    def post(self, request: Request, workspace_id: str):\n        return result.success(TriggerSerializer(\n            data={'workspace_id': workspace_id, 'user_id': request.user.id}).insert(request.data))\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get the trigger list'),\n        summary=_('Get the trigger list'),\n        operation_id=_('Get the trigger list'),  # type: ignore\n        parameters=TriggerQueryAPI.get_parameters(),\n        responses=TriggerQueryAPI.get_response(),\n        tags=[_('Trigger')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n    )\n    def get(self, request: Request, workspace_id: str):\n        return result.success(TriggerQuerySerializer(data={\n            'workspace_id': workspace_id,\n            'name': request.query_params.get('name'),\n            'type': request.query_params.get('type'),\n            'task': request.query_params.get('task'),\n            'is_active': request.query_params.get('is_active'),\n            'create_user': request.query_params.get('create_user'),\n        }).list())\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get trigger details'),\n            summary=_('Get trigger details'),\n            operation_id=_('Get trigger details'),  # type: ignore\n            parameters=TriggerOperateAPI.get_parameters(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Trigger')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        )\n        @log(\n            menu=\"Trigger\", operate=\"Get trigger details\",\n            get_operation_object=lambda r, k: get_trigger_operation_object(k.get('trigger_id')),\n        )\n        def get(self, request: Request, workspace_id: str, trigger_id: str):\n            return result.success(TriggerOperateSerializer(\n                data={'trigger_id': trigger_id, 'workspace_id': workspace_id, 'user_id': request.user.id}\n            ).one())\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Modify the trigger'),\n            summary=_('Modify the trigger'),\n            operation_id=_('Modify the trigger'),  # type: ignore\n            parameters=TriggerOperateAPI.get_parameters(),\n            request=TriggerEditAPI.get_request(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Trigger')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TRIGGER_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        )\n        @log(\n            menu=\"Trigger\", operate=\"Modify the trigger\",\n            get_operation_object=lambda r, k: get_trigger_operation_object(k.get('trigger_id')),\n        )\n        def put(self, request: Request, workspace_id: str, trigger_id: str):\n            return result.success(TriggerOperateSerializer(\n                data={'trigger_id': trigger_id, 'workspace_id': workspace_id, 'user_id': request.user.id}\n            ).edit(request.data))\n\n        @extend_schema(\n            methods=['DELETE'],\n            description=_('Delete the trigger'),\n            summary=_('Delete the trigger'),\n            operation_id=_('Delete the trigger'),  # type: ignore\n            parameters=TriggerOperateAPI.get_parameters(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Trigger')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TRIGGER_DELETE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        )\n        @log(\n            menu=\"Trigger\", operate=\"Delete the trigger\",\n            get_operation_object=lambda r, k: get_trigger_operation_object(k.get('trigger_id')),\n        )\n        def delete(self, request: Request, workspace_id: str, trigger_id: str):\n            return result.success(TriggerOperateSerializer(\n                data={'trigger_id': trigger_id, 'workspace_id': workspace_id, 'user_id': request.user.id}\n            ).delete())\n\n    class BatchDelete(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Delete trigger in batches'),\n            summary=_('Delete trigger in batches'),\n            operation_id=_('Delete trigger in batches'),  # type: ignore\n            parameters=TriggerBatchDeleteAPI.get_parameters(),\n            request=TriggerBatchDeleteAPI.get_request(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Trigger')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TRIGGER_DELETE.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        )\n        @log(\n            menu=\"Trigger\", operate=\"Delete trigger in batches\",\n            get_operation_object=lambda r, k: get_trigger_operation_object_batch(r.data.get('id_list')),\n        )\n        def put(self, request: Request, workspace_id: str):\n            return result.success(TriggerSerializer.Batch(\n                data={'workspace_id': workspace_id, 'user_id': request.user.id}\n            ).batch_delete(request.data))\n\n    class BatchActivate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Activate trigger in batches'),\n            summary=_('Activate trigger in batches'),\n            operation_id=_('Activate trigger in batches'),  # type: ignore\n            parameters=TriggerBatchDeleteAPI.get_parameters(),\n            request=TriggerBatchActiveAPI.get_request(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Trigger')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TRIGGER_EDIT.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        )\n        @log(\n            menu=\"Trigger\", operate=\"Activate trigger in batches\",\n            get_operation_object=lambda r, k: get_trigger_operation_object_batch(r.data.get('id_list')),\n        )\n        def put(self, request: Request, workspace_id: str):\n            return result.success(TriggerSerializer.Batch(\n                data={'workspace_id': workspace_id, 'user_id': request.user.id}\n            ).batch_switch(request.data))\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get the trigger list by page'),\n            summary=_('Get the trigger list by page'),\n            operation_id=_('Get the trigger list by page'),  # type: ignore\n            parameters=TriggerQueryPageAPI.get_parameters(),\n            responses=TriggerQueryPageAPI.get_response(),\n            tags=[_('Trigger')]  # type: ignore\n        )\n        @has_permissions(\n            PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n        )\n        def get(self, request: Request, workspace_id: str, current_page: int, page_size: int):\n            return result.success(TriggerQuerySerializer(data={\n                'workspace_id': workspace_id,\n                'name': request.query_params.get('name'),\n                'task': request.query_params.get('task'),\n                'type': request.query_params.get('type'),\n                'is_active': request.query_params.get('is_active'),\n                'create_user': request.query_params.get('create_user'),\n            }).page(current_page, page_size))\n\n\nclass TaskSourceTriggerView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['POST'],\n        description=_('Create trigger in source'),\n        summary=_('Create trigger in source'),\n        operation_id=_('Create trigger in source'),  # type: ignore\n        parameters=TaskSourceTriggerCreateAPI.get_parameters(),\n        request=TaskSourceTriggerCreateAPI.get_request(),\n        responses=TaskSourceTriggerCreateAPI.get_response(),\n        tags=[_('Trigger')]  # type: ignore\n    )\n    @has_permissions(\n        lambda r, kwargs: Permission(group=Group(kwargs.get(\"source_type\")), operate=Operate.TRIGGER_CREATE,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"\n                                     ),\n        lambda r, kwargs: Permission(group=Group(kwargs.get(\"source_type\")), operate=Operate.TRIGGER_CREATE,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}\"\n                                     ),\n        ViewPermission([RoleConstants.USER.get_workspace_role()],\n                       [lambda r, kwargs: Permission(group=Group(kwargs.get('source_type')),\n                                                     operate=Operate.SELF,\n                                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}\")],\n                       CompareConstants.AND),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    @log(\n        menu=\"Trigger\", operate=\"Create trigger in source\",\n        get_operation_object=lambda r, k: r.data.get('name'),\n    )\n    def post(self, request: Request, workspace_id: str, source_type: str, source_id: str):\n        return result.success(TaskSourceTriggerSerializer(data={\n            'workspace_id': workspace_id,\n            'user_id': request.user.id\n        }).insert({**request.data, 'source_id': source_id,\n                   'workspace_id': workspace_id,\n                   'is_active': True,\n                   'source_type': source_type}))\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get the trigger list of source'),\n        summary=_('Get the trigger list of source'),\n        operation_id=_('Get the trigger list of source'),  # type: ignore\n        parameters=TaskSourceTriggerAPI.get_parameters(),\n        responses=DefaultResultSerializer,\n        tags=[_('Trigger')]  # type: ignore\n    )\n    @has_permissions(\n        lambda r, kwargs: Permission(group=Group(kwargs.get(\"source_type\")), operate=Operate.TRIGGER_READ,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"\n                                     ),\n        lambda r, kwargs: Permission(group=Group(kwargs.get(\"source_type\")), operate=Operate.TRIGGER_READ,\n                                     resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}\"\n                                     ),\n        RoleConstants.USER.get_workspace_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n    def get(self, request: Request, workspace_id: str, source_type: str, source_id: str):\n        return result.success(TaskSourceTriggerListSerializer(data={\n            'workspace_id': workspace_id,\n            'source_id': source_id,\n            'source_type': source_type,\n        }).list())\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(\n            methods=['GET'],\n            description=_('Get Task source trigger details'),\n            summary=_('Get Task source trigger details'),\n            operation_id=_('Get Task source trigger details'),  # type: ignore\n            parameters=TaskSourceTriggerOperateAPI.get_parameters(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Trigger')]  # type: ignore\n        )\n        @has_permissions(\n            lambda r, kwargs: Permission(group=Group(kwargs.get(\"source_type\")), operate=Operate.TRIGGER_READ,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"\n                                         ),\n            lambda r, kwargs: Permission(group=Group(kwargs.get(\"source_type\")), operate=Operate.TRIGGER_READ,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}\"\n                                         ),\n            RoleConstants.USER.get_workspace_role(),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        def get(self, request: Request, workspace_id: str, source_type: str, source_id: str, trigger_id: str):\n            return result.success(TaskSourceTriggerOperateSerializer(\n                data={'trigger_id': trigger_id, 'workspace_id': workspace_id,\n                      'source_id': source_id, 'source_type': source_type}\n            ).one())\n\n        @extend_schema(\n            methods=['PUT'],\n            description=_('Modify the task source trigger'),\n            summary=_('Modify the task source trigger'),\n            operation_id=_('Modify the task source trigger'),  # type: ignore\n            parameters=TaskSourceTriggerOperateAPI.get_parameters(),\n            request=TaskSourceTriggerOperateAPI.get_request(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Trigger')]  # type: ignore\n        )\n        @has_permissions(\n            lambda r, kwargs: Permission(group=Group(kwargs.get(\"source_type\")), operate=Operate.TRIGGER_EDIT,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"\n                                         ),\n            lambda r, kwargs: Permission(group=Group(kwargs.get(\"source_type\")), operate=Operate.TRIGGER_EDIT,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}\"\n                                         ),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [lambda r, kwargs: Permission(group=Group(kwargs.get('source_type')),\n                                                         operate=Operate.SELF,\n                                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}\")],\n                           CompareConstants.AND),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(\n            menu=\"Trigger\", operate=\"Modify the source point trigger\",\n            get_operation_object=lambda r, k: get_trigger_operation_object(k.get('trigger_id')),\n        )\n        def put(self, request: Request, workspace_id: str, source_type: str, source_id: str, trigger_id: str):\n            return result.success(TaskSourceTriggerOperateSerializer(\n                data={'trigger_id': trigger_id, 'workspace_id': workspace_id,\n                      'source_id': source_id, 'source_type': source_type}\n            ).edit(request.data))\n\n        @extend_schema(\n            methods=['DELETE'],\n            description=_('Delete the task source trigger'),\n            summary=_('Delete the task source trigger'),\n            operation_id=_('Delete the task source trigger'),  # type: ignore\n            parameters=TaskSourceTriggerOperateAPI.get_parameters(),\n            responses=result.DefaultResultSerializer,\n            tags=[_('Trigger')]  # type: ignore\n        )\n        @has_permissions(\n            lambda r, kwargs: Permission(group=Group(kwargs.get(\"source_type\")), operate=Operate.TRIGGER_DELETE,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}:ROLE/WORKSPACE_MANAGE\"\n                                         ),\n            lambda r, kwargs: Permission(group=Group(kwargs.get(\"source_type\")), operate=Operate.TRIGGER_DELETE,\n                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}\"\n                                         ),\n            ViewPermission([RoleConstants.USER.get_workspace_role()],\n                           [lambda r, kwargs: Permission(group=Group(kwargs.get('source_type')),\n                                                         operate=Operate.SELF,\n                                                         resource_path=f\"/WORKSPACE/{kwargs.get('workspace_id')}/{kwargs.get('source_type')}/{kwargs.get('source_id')}\")],\n                           CompareConstants.AND),\n            RoleConstants.WORKSPACE_MANAGE.get_workspace_role())\n        @log(\n            menu=\"Trigger\", operate=\"Delete the source point trigger\",\n            get_operation_object=lambda r, k: get_trigger_operation_object(k.get('trigger_id')),\n        )\n        def delete(self, request: Request, workspace_id: str, source_type: str, source_id: str, trigger_id: str):\n            return result.success(TaskSourceTriggerOperateSerializer(\n                data={'trigger_id': trigger_id, 'workspace_id': workspace_id,\n                      'source_id': source_id, 'source_type': source_type}\n            ).delete())\n"
  },
  {
    "path": "apps/trigger/views/trigger_task.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： trigger_task.py\n    @date：2026/1/14 16:01\n    @desc:\n\"\"\"\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common.auth import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common import result\nfrom trigger.api.trigger_task import TriggerTaskRecordExecutionDetailsAPI, TriggerTaskRecordPageAPI, TriggerTaskAPI\nfrom trigger.serializers.trigger_task import TriggerTaskQuerySerializer, TriggerTaskRecordQuerySerializer, \\\n    TriggerTaskRecordOperateSerializer\nfrom common.constants.permission_constants import PermissionConstants, RoleConstants\n\n\nclass TriggerTaskView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get the task list of triggers'),\n        summary=_('Get the task list of triggers'),\n        operation_id=_('Get the task list of triggers'),  # type: ignore\n        parameters=TriggerTaskAPI.get_parameters(),\n        responses=TriggerTaskAPI.get_response(),\n        tags=[_('Trigger')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n    )\n    def get(self, request: Request, workspace_id: str, trigger_id: str):\n        return result.success(\n            TriggerTaskQuerySerializer(data={'workspace_id': workspace_id, 'trigger_id': trigger_id}).list())\n\n\nclass TriggerTaskRecordView(APIView):\n    pass\n\n\nclass TriggerTaskRecordExecutionDetailsView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Retrieve detailed records of tasks executed by the trigger.'),\n        summary=_('Retrieve detailed records of tasks executed by the trigger.'),\n        operation_id=_('Retrieve detailed records of tasks executed by the trigger.'),  # type: ignore\n        parameters=TriggerTaskRecordExecutionDetailsAPI.get_parameters(),\n        responses=TriggerTaskRecordExecutionDetailsAPI.get_response(),\n        tags=[_('Trigger')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n    )\n    def get(self, request: Request, workspace_id: str, trigger_id: str, trigger_task_id: str,\n            trigger_task_record_id: str):\n        return result.success(\n            TriggerTaskRecordOperateSerializer(\n                data={'workspace_id': workspace_id, 'trigger_id': trigger_id, 'trigger_task_id': trigger_task_id,\n                      'trigger_task_record_id': trigger_task_record_id})\n            .get_execution_details())\n\n\nclass TriggerTaskRecordPageView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(\n        methods=['GET'],\n        description=_('Get a paginated list of execution records for trigger tasks.'),\n        summary=_('Get a paginated list of execution records for trigger tasks.'),\n        operation_id=_('Get a paginated list of execution records for trigger tasks.'),  # type: ignore\n        parameters=TriggerTaskRecordPageAPI.get_parameters(),\n        responses=TriggerTaskRecordPageAPI.get_response(),\n        tags=[_('Trigger')]  # type: ignore\n    )\n    @has_permissions(\n        PermissionConstants.TRIGGER_READ.get_workspace_permission_workspace_manage_role(),\n        RoleConstants.WORKSPACE_MANAGE.get_workspace_role(),\n    )\n    def get(self, request: Request, workspace_id: str, trigger_id: str, current_page: int, page_size: int):\n        return result.success(\n            TriggerTaskRecordQuerySerializer(\n                data={'workspace_id': workspace_id, 'trigger_id': trigger_id,\n                      'source_type': request.query_params.get('source_type'),\n                      'state': request.query_params.get('state'),\n                      'name': request.query_params.get('name')})\n            .page(current_page, page_size))\n"
  },
  {
    "path": "apps/users/__init__.py",
    "content": ""
  },
  {
    "path": "apps/users/admin.py",
    "content": "from django.contrib import admin\n\n# Register your models here.\n"
  },
  {
    "path": "apps/users/api/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/4/14 10:28\n    @desc:\n\"\"\"\nfrom .login import *\n"
  },
  {
    "path": "apps/users/api/login.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： login.py\n    @date：2025/4/14 10:30\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer\nfrom users.serializers.login import LoginResponse, LoginRequest, CaptchaResponse\n\n\nclass ApiLoginResponse(ResultSerializer):\n    def get_data(self):\n        return LoginResponse()\n\n\n\"\"\"\nRequest 和Response 都可以使用此方法\n使用serializers.Serializer\nclass LoginRequest(serializers.Serializer):\n    username = serializers.CharField(required=True, max_length=64, help_text=_(\"Username\"), label=_(\"Username\"))\n    password = serializers.CharField(required=True, max_length=128, label=_(\"Password\"))\n使用serializers.ModelSerializer Request不要使用serializers.ModelSerializer的方式\nclass LoginRequest(serializers.ModelSerializer):\n    class Meta:\n        model = User\n        fields = ['username', 'password']\n\n\"\"\"\n\n\nclass LoginAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return LoginRequest\n\n    @staticmethod\n    def get_response():\n        return ApiLoginResponse\n\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"code\",\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            required=True,\n        )]\n\n\nclass ApiCaptchaResponse(ResultSerializer):\n    def get_data(self):\n        return CaptchaResponse()\n\n\nclass CaptchaAPI(APIMixin):\n    @staticmethod\n    def get_response():\n        return ApiCaptchaResponse\n"
  },
  {
    "path": "apps/users/api/user.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： user.py\n    @date：2025/4/14 19:23\n    @desc:\n\"\"\"\nfrom drf_spectacular.types import OpenApiTypes\nfrom drf_spectacular.utils import OpenApiParameter\n\nfrom common.mixins.api_mixin import APIMixin\nfrom common.result import ResultSerializer, DefaultResultSerializer\nfrom users.serializers.user import UserProfileResponse, CreateUserSerializer, UserManageSerializer, \\\n    UserInstanceSerializer, RePasswordSerializer, CheckCodeSerializer, SendEmailSerializer\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\n\nclass ApiUserProfileResponse(ResultSerializer):\n    def get_data(self):\n        return UserProfileResponse()\n\n\nclass RoleSettingRequestSerializer(serializers.Serializer):\n    role_id = serializers.CharField(required=True, label=_('Role ID'))\n    workspace_ids = serializers.ListField(\n        child=serializers.CharField(required=False),\n        required=False,\n        label=_('Workspace IDs')\n    )\n\n\nclass CreateUserRequestSerializer(CreateUserSerializer):\n    role_setting = RoleSettingRequestSerializer(required=False, label=_('Role Setting'), allow_null=True, many=True)\n\n\nclass UserProfileAPI(APIMixin):\n\n    @staticmethod\n    def get_response():\n        return ApiUserProfileResponse\n\n    @staticmethod\n    def get_request():\n        return CreateUserRequestSerializer\n\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"user_id\",\n            description=_('User ID'),\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            required=True,\n        )]\n\n\nclass WorkspaceUserAPI(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"workspace_id\",\n            description=_('Workspace ID'),\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            required=True,\n        )]\n\n    @staticmethod\n    def get_response():\n        return WorkspaceUserListResponse\n\n\nclass WorkspaceUser(serializers.Serializer):\n    id = serializers.CharField(required=True, label=_('id'))\n    username = serializers.CharField(required=True, label=_('Username'))\n\n\nclass WorkspaceUserListResponse(ResultSerializer):\n    def get_data(self):\n        return WorkspaceUser(many=True)\n\n\nclass UserPasswordResponse(APIMixin):\n\n    @staticmethod\n    def get_response():\n        return PasswordResponse\n\n\nclass Password(serializers.Serializer):\n    password = serializers.CharField(required=True, label=_('Password'))\n\n\nclass PasswordResponse(ResultSerializer):\n    def get_data(self):\n        return Password()\n\n\nclass EditUserRequestSerializer(UserManageSerializer.UserEditInstance):\n    role_setting = RoleSettingRequestSerializer(required=False, label=_('Role Setting'), allow_null=True, many=True)\n\n\nclass EditUserApi(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"user_id\",\n            description=_('User ID'),\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            required=True,\n        )]\n\n    @staticmethod\n    def get_request():\n        return EditUserRequestSerializer\n\n\nclass DeleteUserApi(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"user_id\",\n            description=_('User ID'),\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            required=True,\n        )]\n\n    @staticmethod\n    def get_request():\n        return serializers.ListSerializer(child=serializers.CharField(required=True), required=True,\n                                          label=_('User IDs'))\n\n\nclass ChangeUserPasswordApi(APIMixin):\n    @staticmethod\n    def get_request():\n        return UserManageSerializer.RePasswordInstance\n\n\nclass UserListResponse(ResultSerializer):\n    def get_data(self):\n        return UserInstanceSerializer(many=True)\n\n\nclass UserPageApi(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"email_or_username\",\n            description=_('Email or Username'),\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.QUERY,\n            required=False,\n        )]\n\n    @staticmethod\n    def get_response():\n        return UserListResponse\n\n\nclass UserListApi(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            name=\"workspace_id\",\n            description=_('Workspace ID'),\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            required=False,\n        )]\n\n    @staticmethod\n    def get_response():\n        return UserListResponse\n\n\nclass TestWorkspacePermissionUserApi(APIMixin):\n    @staticmethod\n    def get_parameters():\n        return [OpenApiParameter(\n            # 参数的名称是done\n            name=\"workspace_id\",\n            # 对参数的备注\n            description=\"工作空间id\",\n            # 指定参数的类型\n            type=OpenApiTypes.STR,\n            location=OpenApiParameter.PATH,\n            # 指定必须给\n            required=True,\n        )]\n\n\nclass ResetPasswordAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return RePasswordSerializer\n\n\nclass CheckCodeAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return CheckCodeSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass SendEmailAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return SendEmailSerializer\n\n    @staticmethod\n    def get_response():\n        return DefaultResultSerializer\n\n\nclass LanguageSerializer(serializers.Serializer):\n    language = serializers.CharField(required=True, label=_('Language'))\n\n\nclass SwitchUserLanguageAPI(APIMixin):\n    @staticmethod\n    def get_request():\n        return LanguageSerializer\n"
  },
  {
    "path": "apps/users/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass UsersConfig(AppConfig):\n    default_auto_field = 'django.db.models.BigAutoField'\n    name = 'users'\n\n    def ready(self):\n        from ops.celery import signal_handler  # noqa"
  },
  {
    "path": "apps/users/migrations/0001_initial.py",
    "content": "# Generated by Django 5.2.4 on 2025-07-14 03:50\n\nimport uuid_utils.compat\nfrom django.db import migrations, models\n\nfrom common.constants.permission_constants import RoleConstants\nfrom common.utils.common import password_encrypt\nfrom maxkb.const import CONFIG\n\ndefault_password = CONFIG.get('DEFAULT_PASSWORD', 'MaxKB@123..')\n\n\ndef insert_default_data(apps, schema_editor):\n    UserModel = apps.get_model('users', 'User')\n    UserModel.objects.create(id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab', email='', username='admin',\n                             nick_name=\"系统管理员\",\n                             password=password_encrypt(default_password),\n                             role=RoleConstants.ADMIN.name,\n                             is_active=True)\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='User',\n            fields=[\n                ('id', models.UUIDField(default=uuid_utils.compat.uuid7, editable=False, primary_key=True, serialize=False, verbose_name='主键id')),\n                ('email', models.EmailField(blank=True, db_index=True, max_length=254, null=True, unique=True, verbose_name='邮箱')),\n                ('phone', models.CharField(db_index=True, default='', max_length=20, verbose_name='电话')),\n                ('nick_name', models.CharField(db_index=True, max_length=150, unique=True, verbose_name='昵称')),\n                ('username', models.CharField(db_index=True, max_length=150, unique=True, verbose_name='用户名')),\n                ('password', models.CharField(max_length=150, verbose_name='密码')),\n                ('role', models.CharField(max_length=150, verbose_name='角色')),\n                ('source', models.CharField(db_index=True, default='LOCAL', max_length=10, verbose_name='来源')),\n                ('is_active', models.BooleanField(db_index=True, default=True)),\n                ('language', models.CharField(default=None, max_length=10, null=True, verbose_name='语言')),\n                ('create_time', models.DateTimeField(auto_now_add=True, db_index=True, null=True, verbose_name='创建时间')),\n                ('update_time', models.DateTimeField(auto_now=True, db_index=True, null=True, verbose_name='修改时间')),\n            ],\n            options={\n                'db_table': 'user',\n            },\n        ),\n        migrations.RunPython(insert_default_data)\n    ]\n"
  },
  {
    "path": "apps/users/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "apps/users/models/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/4/14 10:20\n    @desc:\n\"\"\"\nfrom .user import *\n"
  },
  {
    "path": "apps/users/models/user.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： user.py\n    @date：2025/4/14 10:20\n    @desc:\n\"\"\"\nimport uuid_utils.compat as uuid\n\nfrom django.db import models\n\nfrom common.utils.common import password_encrypt\n\n\nclass User(models.Model):\n    id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7, editable=False, verbose_name=\"主键id\")\n    email = models.EmailField(unique=True, null=True, blank=True, verbose_name=\"邮箱\", db_index=True)\n    phone = models.CharField(max_length=20, verbose_name=\"电话\", default=\"\", db_index=True)\n    nick_name = models.CharField(max_length=150, verbose_name=\"昵称\", unique=True, db_index=True)\n    username = models.CharField(max_length=150, unique=True, verbose_name=\"用户名\", db_index=True)\n    password = models.CharField(max_length=150, verbose_name=\"密码\")\n    role = models.CharField(max_length=150, verbose_name=\"角色\")\n    source = models.CharField(max_length=10, verbose_name=\"来源\", default=\"LOCAL\", db_index=True)\n    is_active = models.BooleanField(default=True, db_index=True)\n    language = models.CharField(max_length=10, verbose_name=\"语言\", null=True, default=None)\n    create_time = models.DateTimeField(verbose_name=\"创建时间\", auto_now_add=True, null=True, db_index=True)\n    update_time = models.DateTimeField(verbose_name=\"修改时间\", auto_now=True, null=True, db_index=True)\n\n    USERNAME_FIELD = 'username'\n    REQUIRED_FIELDS = []\n\n    class Meta:\n        db_table = \"user\"\n\n    def set_password(self, row_password):\n        self.password = password_encrypt(row_password)\n        self._password = row_password\n"
  },
  {
    "path": "apps/users/serializers/__init__.py",
    "content": ""
  },
  {
    "path": "apps/users/serializers/login.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： login.py\n    @date：2025/4/14 11:08\n    @desc:\n\"\"\"\nimport base64\nimport datetime\nimport json\n\nfrom captcha.image import ImageCaptcha\nfrom django.core import signing\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom rest_framework import serializers\n\nfrom application.models import ApplicationAccessToken\nfrom common.constants.authentication_type import AuthenticationType\nfrom common.constants.cache_version import Cache_Version\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import password_encrypt, get_random_chars\nfrom common.utils.rsa_util import encrypt, decrypt\nfrom maxkb.const import CONFIG\nfrom users.models import User\n\n\nclass LoginRequest(serializers.Serializer):\n    username = serializers.CharField(required=True, max_length=64, help_text=_(\"Username\"), label=_(\"Username\"))\n    password = serializers.CharField(required=True, max_length=128, label=_(\"Password\"))\n    captcha = serializers.CharField(required=False, max_length=64, label=_('captcha'), allow_null=True,\n                                    allow_blank=True)\n    encryptedData = serializers.CharField(required=False, label=_('encryptedData'), allow_null=True,\n                                          allow_blank=True)\n\n\nsystem_version, system_get_key = Cache_Version.SYSTEM.value\n\n\nclass LoginResponse(serializers.Serializer):\n    \"\"\"\n    登录响应对象\n    \"\"\"\n    token = serializers.CharField(required=True, label=_(\"token\"))\n\n\ndef record_login_fail(username: str, expire: int = 600):\n    \"\"\"记录登录失败次数\"\"\"\n    if not username:\n        return\n    fail_key = system_get_key(f'system_{username}')\n    fail_count = cache.get(fail_key, version=system_version)\n    if fail_count is None:\n        cache.set(fail_key, 1, timeout=expire, version=system_version)\n    else:\n        cache.incr(fail_key, 1, version=system_version)\n\n\ndef record_login_fail_lock(username: str, expire: int = 10):\n    if not username:\n        return\n    fail_key = system_get_key(f'system_{username}_lock_count')\n    fail_count = cache.get(fail_key, version=system_version)\n    if fail_count is None:\n        cache.set(fail_key, 1, timeout=expire * 60, version=system_version)\n    else:\n        cache.incr(fail_key, 1, version=system_version)\n\n\nclass LoginSerializer(serializers.Serializer):\n\n    @staticmethod\n    def get_auth_setting():\n        \"\"\"获取认证设置\"\"\"\n        auth_setting_model = DatabaseModelManage.get_model('auth_setting')\n        auth_setting = {}\n        if auth_setting_model:\n            setting_obj = auth_setting_model.objects.filter(param_key='auth_setting').first()\n            if setting_obj:\n                try:\n                    auth_setting = json.loads(setting_obj.param_value) or {}\n                except Exception:\n                    auth_setting = {}\n        return auth_setting\n\n    @staticmethod\n    def login(instance):\n        # 解密数据\n        username = instance.get(\"username\", \"\")\n        encrypted_data = instance.get(\"encryptedData\", \"\")\n\n        if encrypted_data:\n            decrypted_data = json.loads(decrypt(encrypted_data))\n            instance.update(decrypted_data)\n        try:\n            LoginRequest(data=instance).is_valid(raise_exception=True)\n        except serializers.ValidationError:\n            raise\n        except Exception as e:\n            raise AppApiException(500, str(e))\n\n        password = instance.get(\"password\")\n        captcha = instance.get(\"captcha\", \"\")\n\n        # 获取认证配置\n        auth_setting = LoginSerializer.get_auth_setting()\n        max_attempts = auth_setting.get(\"max_attempts\", 1)\n        failed_attempts = auth_setting.get(\"failed_attempts\", 5)\n        lock_time = auth_setting.get(\"lock_time\", 10)\n\n        # 检查许可证有效性\n        license_validator = DatabaseModelManage.get_model('license_is_valid') or (lambda: False)\n        is_license_valid = license_validator() if license_validator() is not None else False\n\n        if is_license_valid:\n            # 检查账户是否被锁定\n            if LoginSerializer._is_account_locked(username, failed_attempts):\n                raise AppApiException(\n                    1005,\n                    _(\"This account has been locked for %s minutes, please try again later\") % lock_time\n                )\n\n            # 验证验证码\n            if LoginSerializer._need_captcha(username, max_attempts):\n                LoginSerializer._validate_captcha(username, captcha)\n\n        # 验证用户凭据\n        user = QuerySet(User).filter(\n            username=username,\n            password=password_encrypt(password)\n        ).first()\n\n        if not user:\n            LoginSerializer._handle_failed_login(username, is_license_valid, failed_attempts, lock_time)\n            raise AppApiException(500, _('The username or password is incorrect'))\n\n        if not user.is_active:\n            raise AppApiException(1005, _(\"The user has been disabled, please contact the administrator!\"))\n\n        # 清除失败计数并生成令牌\n        cache.delete(system_get_key(f'system_{username}'), version=system_version)\n        cache.delete(system_get_key(f'system_{username}_lock'), version=system_version)\n        token = signing.dumps({\n            'username': user.username,\n            'id': str(user.id),\n            'email': user.email,\n            'type': AuthenticationType.SYSTEM_USER.value\n        })\n\n        version, get_key = Cache_Version.TOKEN.value\n        timeout = CONFIG.get_session_timeout()\n        cache.set(get_key(token), user, timeout=timeout, version=version)\n\n        return {'token': token}\n\n    @staticmethod\n    def _is_account_locked(username: str, failed_attempts: int) -> bool:\n        \"\"\"检查账户是否被锁定\"\"\"\n        if failed_attempts == -1:\n            return False\n        lock_cache = cache.get(system_get_key(f'system_{username}_lock'), version=system_version)\n        return bool(lock_cache)\n\n    @staticmethod\n    def _need_captcha(username: str, max_attempts: int) -> bool:\n        \"\"\"判断是否需要验证码\"\"\"\n        if max_attempts == -1:\n            return False\n        elif max_attempts > 0:\n            fail_count = cache.get(system_get_key(f'system_{username}'), version=system_version) or 0\n            return fail_count >= max_attempts\n        return True\n\n    @staticmethod\n    def _validate_captcha(username: str, captcha: str) -> None:\n        \"\"\"验证验证码\"\"\"\n        if not captcha:\n            raise AppApiException(1005, _(\"Captcha is required\"))\n\n        captcha_cache = cache.get(\n            Cache_Version.CAPTCHA.get_key(captcha=f\"system_{username}\"),\n            version=Cache_Version.CAPTCHA.get_version()\n        )\n\n        if captcha_cache is None or captcha.lower() != captcha_cache:\n            raise AppApiException(1005, _(\"Captcha code error or expiration\"))\n\n    @staticmethod\n    def _handle_failed_login(username: str, is_license_valid: bool, failed_attempts: int, lock_time: int) -> None:\n        \"\"\"处理登录失败\"\"\"\n        record_login_fail(username)\n        record_login_fail_lock(username, lock_time)\n\n        if not is_license_valid or failed_attempts <= 0:\n            return\n\n        fail_count = cache.get(system_get_key(f'system_{username}_lock_count'), version=system_version) or 0\n        remain_attempts = failed_attempts - fail_count\n\n        if remain_attempts > 0:\n            raise AppApiException(\n                1005,\n                _(\"Login failed %s times, account will be locked, you have %s more chances !\") % (\n                    failed_attempts, remain_attempts\n                )\n            )\n        elif remain_attempts == 0:\n            cache.set(\n                system_get_key(f'system_{username}_lock'),\n                1,\n                timeout=lock_time * 60,\n                version=system_version\n            )\n            raise AppApiException(\n                1005,\n                _(\"This account has been locked for %s minutes, please try again later\") % lock_time\n            )\n\n\nclass CaptchaResponse(serializers.Serializer):\n    \"\"\"\n       登录响应对象\n       \"\"\"\n    captcha = serializers.CharField(required=True, label=_(\"captcha\"))\n\n\nclass CaptchaSerializer(serializers.Serializer):\n    @staticmethod\n    def generate(username: str, type: str = 'system'):\n        auth_setting = LoginSerializer.get_auth_setting()\n        max_attempts = auth_setting.get(\"max_attempts\", 1)\n\n        need_captcha = True\n        if max_attempts == -1:\n            need_captcha = False\n        elif max_attempts > 0:\n            fail_count = cache.get(system_get_key(f'system_{username}'), version=system_version) or 0\n            need_captcha = fail_count >= max_attempts\n\n        return CaptchaSerializer._generate_captcha_if_needed(username, type, need_captcha)\n\n    @staticmethod\n    def chat_generate(username: str, type: str = 'chat', access_token: str = ''):\n        application_access_token = ApplicationAccessToken.objects.filter(\n            access_token=access_token\n        ).first()\n\n        if not application_access_token:\n            raise AppApiException(1005, _('Invalid access token'))\n\n        auth_setting = application_access_token.authentication_value\n        max_attempts = auth_setting.get(\"max_attempts\", 1)\n\n        need_captcha = True\n        if max_attempts == -1:\n            need_captcha = False\n        elif max_attempts > 0:\n            fail_count = cache.get(system_get_key(f'{type}_{username}'), version=system_version) or 0\n            need_captcha = fail_count >= max_attempts\n\n        return CaptchaSerializer._generate_captcha_if_needed(username, type, need_captcha)\n\n    @staticmethod\n    def _generate_captcha_if_needed(username: str, type: str, need_captcha: bool):\n        \"\"\"\n        提取的公共验证码生成方法\n        \"\"\"\n        if need_captcha:\n            chars = get_random_chars()\n            image = ImageCaptcha()\n            data = image.generate(chars)\n            captcha = base64.b64encode(data.getbuffer())\n            cache.set(Cache_Version.CAPTCHA.get_key(captcha=f'{type}_{username}'), chars.lower(),\n                      timeout=300, version=Cache_Version.CAPTCHA.get_version())\n            return {'captcha': 'data:image/png;base64,' + captcha.decode()}\n        return {'captcha': ''}\n"
  },
  {
    "path": "apps/users/serializers/user.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： user.py\n    @date：2025/4/14 19:18\n    @desc:\n\"\"\"\nimport datetime\nimport os\nimport random\nimport re\nfrom collections import defaultdict\n\nfrom django.core.cache import cache\nfrom django.core.mail.backends.smtp import EmailBackend\nfrom django.db import transaction\nfrom django.db.models import Q, QuerySet\nfrom rest_framework import serializers\nimport uuid_utils.compat as uuid\n\nfrom common.constants.cache_version import Cache_Version\nfrom common.constants.exception_code_constants import ExceptionCodeConstants\nfrom common.constants.permission_constants import RoleConstants, Auth, ResourceAuthType, ResourcePermissionRole, \\\n    ResourcePermission\nfrom common.database_model_manage.database_model_manage import DatabaseModelManage\nfrom common.db.search import page_search\nfrom common.exception.app_exception import AppApiException\nfrom common.utils.common import valid_license, password_encrypt, get_random_chars\nfrom common.utils.rsa_util import decrypt\nfrom maxkb import settings\nfrom maxkb.conf import PROJECT_DIR\nfrom system_manage.models import SystemSetting, SettingType, AuthTargetType, WorkspaceUserResourcePermission\nfrom users.models import User\nfrom django.utils.translation import gettext_lazy as _, to_locale\nfrom django.core import validators\nfrom django.core.mail import send_mail\nfrom django.utils.translation import get_language\n\nPASSWORD_REGEX = re.compile(\n    r\"^\"  # 开始\n    r\"(?=.*[a-z])\"  # 至少一个小写字母\n    r\"(?=.*[-_!@#$%^&*`~.()+=])\"  # 至少一个指定的特殊字符\n    r\"(?:(?=.*[A-Z])|(?=.*\\d))\"  # 至少一个大写字母 或 数字\n    r\"[a-zA-Z0-9-_!@#$%^&*`~.()+=]{6,20}\"  # 总长度6~20个合法字符\n    r\"$\"  # 结束\n)\n\nversion, get_key = Cache_Version.SYSTEM.value\n\n\nclass UserProfileResponse(serializers.ModelSerializer):\n    is_edit_password = serializers.BooleanField(required=True, label=_('Is Edit Password'))\n    permissions = serializers.ListField(required=True, label=_('permissions'))\n\n    class Meta:\n        model = User\n        fields = ['id', 'username', 'nick_name', 'email', 'role', 'permissions', 'language', 'is_edit_password']\n\n\nclass CreateUserSerializer(serializers.Serializer):\n    username = serializers.CharField(required=True, label=_('Username'))\n    password = serializers.CharField(required=True, label=_('Password'))\n    email = serializers.EmailField(required=True, label=_('Email'))\n    nick_name = serializers.CharField(required=False, label=_('Nick name'))\n    phone = serializers.CharField(required=False, label=_('Phone'))\n    source = serializers.CharField(required=False, label=_('Source'), default='LOCAL')\n    defaultPermission = serializers.CharField(required=False, label=_('defaultPermission'))\n\n\ndef is_workspace_manage(user_id: str, workspace_id: str):\n    workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n    role_permission_mapping_model = DatabaseModelManage.get_model(\"role_permission_mapping_model\")\n    is_x_pack_ee = workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None\n    if is_x_pack_ee:\n        return QuerySet(workspace_user_role_mapping_model).select_related('role', 'user').filter(\n            workspace_id=workspace_id, user_id=user_id,\n            role__type=RoleConstants.WORKSPACE_MANAGE.value.__str__()).exists()\n    return QuerySet(User).filter(id=user_id, role=RoleConstants.ADMIN.value.__str__()).exists()\n\n\ndef get_workspace_list_by_user(user_id):\n    get_workspace_list = DatabaseModelManage.get_model('get_workspace_list_by_user')\n    license_is_valid = DatabaseModelManage.get_model('license_is_valid') or (lambda: False)\n    if get_workspace_list is not None and license_is_valid():\n        return get_workspace_list(user_id)\n    return [{'id': 'default', 'name': 'default'}]\n\n\nclass UserProfileSerializer(serializers.Serializer):\n    @staticmethod\n    def profile(user: User, auth: Auth):\n        \"\"\"\n          获取用户详情\n        @param user: 用户对象\n        @param auth: 认证对象\n        @return:\n        \"\"\"\n        workspace_list = get_workspace_list_by_user(user.id)\n        user_role_relation_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n        role_name = [user.role]\n        if user_role_relation_model:\n            user_role_relations = (\n                user_role_relation_model.objects\n                .filter(user_id=user.id)\n                .select_related('role')\n                .distinct('role_id')\n            )\n            role_name = [relation.role.role_name for relation in user_role_relations]\n\n        return {\n            'id': user.id,\n            'username': user.username,\n            'nick_name': user.nick_name,\n            'email': user.email,\n            'source': user.source,\n            'role': auth.role_list,\n            'permissions': auth.permission_list,\n            'is_edit_password': user.password == 'd880e722c47a34d8e9fce789fc62389d' if user.source == 'LOCAL' else False,\n            'language': user.language,\n            'workspace_list': workspace_list,\n            'role_name': role_name\n        }\n\n\nclass UserInstanceSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = User\n        fields = ['id', 'username', 'email', 'phone', 'is_active', 'role', 'nick_name', 'create_time', 'update_time',\n                  'source']\n\n\nclass UserManageSerializer(serializers.Serializer):\n    class UserInstance(serializers.Serializer):\n        email = serializers.EmailField(\n            required=True,\n            label=_(\"Email\"),\n            validators=[validators.EmailValidator(\n                message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message,\n                code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code\n            )]\n        )\n        username = serializers.CharField(\n            required=True,\n            label=_(\"Username\"),\n            max_length=64,\n            min_length=4,\n            validators=[\n                validators.RegexValidator(\n                    regex=re.compile(\"^.{4,64}$\"),\n                    message=_('Username must be 4-64 characters long')\n                )\n            ]\n        )\n        password = serializers.CharField(\n            required=True,\n            label=_(\"Password\"),\n            max_length=20,\n            min_length=6,\n            validators=[\n                validators.RegexValidator(\n                    regex=PASSWORD_REGEX,\n                    message=_(\n                        \"The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters.\"\n                    )\n                )\n            ]\n        )\n        nick_name = serializers.CharField(\n            required=True,\n            label=_(\"Nick name\"),\n            max_length=64,\n        )\n        phone = serializers.CharField(\n            required=False,\n            label=_(\"Phone\"),\n            max_length=20,\n            allow_null=True,\n            allow_blank=True\n        )\n        source = serializers.CharField(\n            required=False,\n            label=_(\"Source\"),\n            max_length=20,\n            default=\"LOCAL\"\n        )\n\n        def is_valid(self, *, raise_exception=True):\n            super().is_valid(raise_exception=True)\n            self._check_unique_username_and_email()\n\n        def _check_unique_username_and_email(self):\n            username = self.data.get('username')\n            email = self.data.get('email')\n            nick_name = self.data.get('nick_name')\n            user = User.objects.filter(Q(username=username) | Q(email=email) | Q(nick_name=nick_name)).first()\n            if user:\n                if user.email == email:\n                    raise ExceptionCodeConstants.EMAIL_IS_EXIST.value.to_app_api_exception()\n                if user.username == username:\n                    raise ExceptionCodeConstants.USERNAME_IS_EXIST.value.to_app_api_exception()\n                if user.nick_name == nick_name:\n                    raise ExceptionCodeConstants.NICKNAME_IS_EXIST.value.to_app_api_exception()\n\n    class Query(serializers.Serializer):\n        username = serializers.CharField(\n            required=False,\n            label=_(\"Username\"),\n            max_length=64,\n            allow_blank=True\n        )\n        nick_name = serializers.CharField(\n            required=False,\n            label=_(\"Nick Name\"),\n            max_length=64,\n            allow_blank=True\n        )\n        email = serializers.CharField(\n            required=False,\n            label=_(\"Email\"),\n            allow_blank=True,\n        )\n        is_active = serializers.BooleanField(\n            required=False,\n            label=_(\"Is active\"),\n        )\n        source = serializers.CharField(\n            required=False,\n            label=_(\"Source\"),\n            allow_blank=True,\n        )\n\n        def get_query_set(self):\n            username = self.data.get('username')\n            nick_name = self.data.get('nick_name')\n            email = self.data.get('email')\n            is_active = self.data.get('is_active', None)\n            source = self.data.get('source', None)\n            query_set = QuerySet(User)\n            if username is not None:\n                query_set = query_set.filter(username__contains=username)\n            if nick_name is not None:\n                query_set = query_set.filter(nick_name__contains=nick_name)\n            if email is not None:\n                query_set = query_set.filter(email__contains=email)\n            if is_active is not None:\n                query_set = query_set.filter(is_active=is_active)\n            if source is not None:\n                query_set = query_set.filter(source=source)\n            query_set = query_set.order_by(\"-create_time\")\n            return query_set\n\n        def list(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            return [{'id': user_model.id, 'username': user_model.username, 'email': user_model.email} for user_model in\n                    self.get_query_set()]\n\n        def page(self, current_page: int, page_size: int, user_id: str, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            result = page_search(current_page, page_size,\n                                 self.get_query_set(),\n                                 post_records_handler=lambda u: UserInstanceSerializer(u).data)\n            role_model = DatabaseModelManage.get_model(\"role_model\")\n            user_role_relation_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n\n            def _get_user_roles(user_ids, is_admin=True):\n                workspace_model = DatabaseModelManage.get_model(\"workspace_model\")\n                if not (role_model and user_role_relation_model and workspace_model):\n                    return {}\n\n                workspace_mapping = {str(workspace_model.id): workspace_model.name for workspace_model in\n                                     workspace_model.objects.all()}\n\n                # 获取所有相关角色关系，并预加载角色信息\n                user_role_relations = (\n                    user_role_relation_model.objects\n                    .filter(user_id__in=user_ids)\n                    .select_related('role')\n                    .distinct('user_id', 'role_id', 'workspace_id')  # 确保组合唯一性\n                )\n\n                # 构建用户ID到角色名称列表的映射\n                user_role_mapping = defaultdict(set)  # 使用 set 去重\n                # 构建用户ID到角色ID与工作空间ID映射\n                user_role_setting_mapping = defaultdict(lambda: defaultdict(list))\n                user_role_workspace_mapping = defaultdict(lambda: defaultdict(list))\n\n                for relation in user_role_relations:\n                    user_id = str(relation.user_id)\n                    role_id = relation.role_id\n                    workspace_id = relation.workspace_id\n                    if not is_admin and relation.role.type == RoleConstants.ADMIN.name:\n                        continue\n                    user_role_mapping[user_id].add(relation.role.role_name)\n                    user_role_setting_mapping[user_id][role_id].append(workspace_id)\n                    user_role_workspace_mapping[user_id][relation.role.role_name].append(\n                        workspace_mapping.get(workspace_id, workspace_id))\n\n                    # 将 set 转换为 list 以符合返回格式\n                user_role_mapping = {uid: list(roles) for uid, roles in user_role_mapping.items()}\n\n                # 转换为所需的结构\n                result_user_role_setting_mapping = {\n                    user_id: [{\"role_id\": role_id, \"workspace_ids\": workspace_ids}\n                              for role_id, workspace_ids in roles.items()]\n                    for user_id, roles in user_role_setting_mapping.items()\n                }\n                result_user_role_workspace_mapping = {\n                    user_id: {role_name: workspace_names\n                              for role_name, workspace_names in roles.items()}\n                    for user_id, roles in user_role_workspace_mapping.items()\n                }\n\n                return user_role_mapping, result_user_role_setting_mapping, result_user_role_workspace_mapping\n\n            if role_model and user_role_relation_model:\n                # 获取当前用户的所有角色 判断是不是内置的系统管理员\n                is_admin = user_role_relation_model.objects.filter(user_id=user_id,\n                                                                   role_id=RoleConstants.ADMIN.name).exists()\n                user_ids = [user['id'] for user in result['records']]\n                user_role_mapping, user_role_setting_mapping, user_role_workspace_mapping = _get_user_roles(user_ids,\n                                                                                                            is_admin)\n\n                # 将角色信息添加回用户数据中\n                for user in result['records']:\n                    user_id = str(user['id'])\n                    user['role_name'] = user_role_mapping.get(user_id, [])\n                    user['role_setting'] = user_role_setting_mapping.get(user_id, [])\n                    user['role_workspace'] = user_role_workspace_mapping.get(user_id, [])\n            return result\n\n    @transaction.atomic\n    def save(self, instance, user_id, with_valid=True):\n        if with_valid:\n            if instance.get('encrypted'):\n                instance['password'] = decrypt(instance.get('password'))\n            self.UserInstance(data=instance).is_valid(raise_exception=True)\n\n        user = User(\n            id=uuid.uuid7(),\n            email=instance.get('email'),\n            phone=instance.get('phone', ''),\n            nick_name=instance.get('nick_name', ''),\n            username=instance.get('username'),\n            password=password_encrypt(instance.get('password')),\n            role=RoleConstants.USER.name,\n            source=instance.get('source', 'LOCAL'),\n            is_active=True\n        )\n        update_user_role(instance, user, user_id)\n        set_default_permission(user.id, instance)\n        user.save()\n        return UserInstanceSerializer(user).data\n\n    class UserEditInstance(serializers.Serializer):\n        email = serializers.EmailField(\n            required=False,\n            label=_(\"Email\"),\n            validators=[validators.EmailValidator(\n                message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message,\n                code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code\n            )]\n        )\n        nick_name = serializers.CharField(\n            required=False,\n            label=_(\"Name\"),\n            max_length=64,\n        )\n        phone = serializers.CharField(\n            required=False,\n            label=_(\"Phone\"),\n            max_length=20,\n            allow_null=True,\n            allow_blank=True\n        )\n        is_active = serializers.BooleanField(\n            required=False,\n            label=_(\"Is Active\")\n        )\n\n        def is_valid(self, *, user_id=None, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            self._check_unique_email(user_id)\n            self._check_unique_nick_name(user_id)\n\n        def _check_unique_nick_name(self, user_id):\n            nick_name = self.data.get('nick_name')\n            if nick_name and User.objects.filter(nick_name=nick_name).exclude(id=user_id).exists():\n                raise AppApiException(1008, _('Nickname is already in use'))\n\n        def _check_unique_email(self, user_id):\n            email = self.data.get('email')\n            if email and User.objects.filter(email=email).exclude(id=user_id).exists():\n                raise AppApiException(1004, _('Email is already in use'))\n\n    class RePasswordInstance(serializers.Serializer):\n        password = serializers.CharField(\n            required=True,\n            label=_(\"Password\"),\n            max_length=20,\n            min_length=6,\n            validators=[\n                validators.RegexValidator(\n                    regex=PASSWORD_REGEX,\n                    message=_(\n                        \"The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters.\"\n                    )\n                )\n            ]\n        )\n        re_password = serializers.CharField(\n            required=True,\n            label=_(\"Re Password\"),\n            validators=[\n                validators.RegexValidator(\n                    regex=PASSWORD_REGEX,\n                    message=_(\n                        \"The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters.\"\n                    )\n                )\n            ]\n        )\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            self._check_passwords_match()\n\n        def _check_passwords_match(self):\n            if self.data.get('password') != self.data.get('re_password'):\n                raise ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.to_app_api_exception()\n\n    class Operate(serializers.Serializer):\n        id = serializers.UUIDField(required=True, label=_('User ID'))\n\n        def is_valid(self, *, raise_exception=False):\n            super().is_valid(raise_exception=True)\n            self._check_user_exists()\n\n        def _check_user_exists(self):\n            if not User.objects.filter(id=self.data.get('id')).exists():\n                raise AppApiException(1004, _('User does not exist'))\n\n        @transaction.atomic\n        def delete(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                self._check_not_admin()\n            user_id = self.data.get('id')\n            # TODO  需要删除授权关系\n            User.objects.filter(id=user_id).delete()\n            return True\n\n        def _check_not_admin(self):\n            user = User.objects.filter(id=self.data.get('id')).first()\n            if user.role == RoleConstants.ADMIN.name or str(user.id) == 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab':\n                raise AppApiException(1004, _('Unable to delete administrator'))\n\n        def edit(self, instance, user_id, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                UserManageSerializer.UserEditInstance(data=instance).is_valid(user_id=self.data.get('id'),\n                                                                              raise_exception=True)\n            user = User.objects.filter(id=self.data.get('id')).first()\n            self._check_admin_modification(user, instance)\n            self._update_user_fields(user, instance)\n            update_user_role(instance, user, user_id)\n            user.save()\n            return UserInstanceSerializer(user).data\n\n        @staticmethod\n        def _check_admin_modification(user, instance):\n            if user.role == RoleConstants.ADMIN.name and 'is_active' in instance and instance.get(\n                    'is_active') is not None:\n                raise AppApiException(1004, _('Cannot modify administrator status'))\n\n        @staticmethod\n        def _update_user_fields(user, instance):\n            update_keys = ['email', 'nick_name', 'phone', 'is_active']\n            for key in update_keys:\n                if key in instance and instance.get(key) is not None:\n                    setattr(user, key, instance.get(key))\n\n        def one(self, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n            user = User.objects.filter(id=self.data.get('id')).first()\n            workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n            if workspace_user_role_mapping_model:\n                role_setting = {}\n                workspace_user_role_mapping_list = QuerySet(workspace_user_role_mapping_model).filter(\n                    user_id=user.id)\n                for workspace_user_role_mapping in workspace_user_role_mapping_list:\n                    role_id = workspace_user_role_mapping.role_id\n                    workspace_id = workspace_user_role_mapping.workspace_id\n                    if role_id not in role_setting:\n                        role_setting[role_id] = []\n                    role_setting[role_id].append(workspace_id)\n                return {\n                    'id': user.id,\n                    'username': user.username,\n                    'email': user.email,\n                    'phone': user.phone,\n                    'nick_name': user.nick_name,\n                    'is_active': user.is_active,\n                    'role_setting': role_setting\n                }\n            return UserInstanceSerializer(user).data\n\n        def re_password(self, instance, with_valid=True):\n            if with_valid:\n                self.is_valid(raise_exception=True)\n                UserManageSerializer.RePasswordInstance(data=instance).is_valid(raise_exception=True)\n            user = User.objects.filter(id=self.data.get('id')).first()\n            user.password = password_encrypt(instance.get('password'))\n            user.save()\n            return True\n\n    def get_user_list(self, workspace_id):\n        \"\"\"\n        获取用户列表\n        :param workspace_id: 工作空间ID\n        :return: 用户列表\n        \"\"\"\n        workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n        if workspace_user_role_mapping_model:\n            user_ids = (\n                workspace_user_role_mapping_model.objects\n                .filter(workspace_id=workspace_id)\n                .values_list('user_id', flat=True)\n                .distinct()\n            )\n        else:\n            user_ids = User.objects.values_list('id', flat=True)\n\n        users = User.objects.filter(id__in=user_ids).values('id', 'nick_name')\n        return list(users)\n\n    def get_user_members(self, workspace_id):\n        \"\"\"\n        获取工作空间成员列表\n        :param workspace_id: 工作空间ID\n        :return: 成员列表\n        \"\"\"\n        role_model = DatabaseModelManage.get_model(\"role_model\")\n        user_role_relation_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n\n        if user_role_relation_model and role_model:\n            user_role_relations = (\n                user_role_relation_model.objects\n                .filter(workspace_id=workspace_id, role__type='USER')\n                .select_related('role', 'user')\n            )\n            user_dict = {}\n            for relation in user_role_relations:\n                user_id = relation.user.id\n                if user_id not in user_dict:\n                    user_dict[user_id] = {\n                        'id': user_id,\n                        'nick_name': relation.user.nick_name,\n                        'email': relation.user.email,\n                        'roles': [relation.role.role_name]\n                    }\n                else:\n                    user_dict[user_id]['roles'].append(relation.role.role_name)\n\n            # 将字典值转换为列表形式\n            return list(user_dict.values())\n        user_list = User.objects.exclude(role=RoleConstants.ADMIN.name)\n        return [\n            {\n                'id': user.id,\n                'nick_name': user.nick_name,\n                'email': user.email,\n                'roles': [RoleConstants.USER.name]\n            } for user in user_list\n        ]\n\n    class BatchDelete(serializers.Serializer):\n        ids = serializers.ListField(required=True, label=_('User IDs'))\n\n        def batch_delete(self, with_valid=True):\n            user_ids = self.data.get('ids')\n            if not user_ids:\n                raise AppApiException(1004, _('User IDs cannot be empty'))\n            User.objects.filter(id__in=user_ids).exclude(id='f0dd8f71-e4ee-11ee-8c84-a8a1595801ab').delete()\n            return True\n\n    def get_all_user_list(self):\n        users = User.objects.all().values('id', 'nick_name', 'username')\n        return list(users)\n\n\ndef update_user_role(instance, user, user_id=None):\n    workspace_user_role_mapping_model = DatabaseModelManage.get_model(\"workspace_user_role_mapping\")\n    if workspace_user_role_mapping_model:\n        role_setting = instance.get('role_setting')\n        license_is_valid = DatabaseModelManage.get_model('license_is_valid') or (lambda: False)\n        license_is_valid = license_is_valid() if license_is_valid() is not None else False\n        if not role_setting or (len(role_setting) == 1\n                                and role_setting[0].get('role_id') == ''\n                                and len(role_setting[0].get('workspace_ids', [])) == 0):\n            if not license_is_valid:\n                workspace_user_role_mapping_model.objects.create(\n                    id=uuid.uuid7(),\n                    user_id=user.id,\n                    role_id=RoleConstants.USER.name,\n                    workspace_id='default'\n                )\n            return\n\n        is_admin = workspace_user_role_mapping_model.objects.filter(user_id=user_id,\n                                                                    role_id=RoleConstants.ADMIN.name).exists()\n\n        if str(user.id) == 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab':\n            # 需要判断当前角色的权限 不能删除系统管理员 空间管理员 普通管理员等角色\n            # role_setting是一个数组 结构式 [{role_id:1,workspace_ids:[1,2]}]\n            # 如果role_id不包含ADMIN 就直接报错   如果WORKSPACE_MANAGE 或者USER 必须判断workspace_ids是否包含默认工作空间 不包含就报错\n            admin_role_id = RoleConstants.ADMIN.name\n            workspace_manage_role_id = RoleConstants.WORKSPACE_MANAGE.name\n            # 判断内置的三个角色是不是不在\n            current_role_ids = {item['role_id'] for item in role_setting}\n            initial_role = [admin_role_id, workspace_manage_role_id, RoleConstants.USER.name]\n            if not set(initial_role).issubset(current_role_ids):\n                raise AppApiException(1004, _(\"Cannot delete built-in role\"))\n\n            if not any(item['role_id'] == str(admin_role_id) for item in role_setting):\n                raise AppApiException(1004, _(\"Cannot delete built-in role\"))\n\n            # 验证 WORKSPACE_MANAGE 或 USER 是否包含默认工作空间\n            default_workspace_id = 'default'\n\n            for item in role_setting:\n                role_id = item['role_id']\n                workspace_ids = item.get('workspace_ids', [])\n\n                if role_id == str(workspace_manage_role_id) or role_id == str(RoleConstants.USER.value):\n                    if default_workspace_id not in workspace_ids:\n                        raise AppApiException(1004, _(\"Cannot delete built-in role\"))\n        if is_admin:\n            workspace_user_role_mapping_model.objects.filter(user_id=user.id).delete()\n        else:\n            workspace_user_role_mapping_model.objects.filter(user_id=user.id).exclude(\n                role__type=RoleConstants.ADMIN.name).delete()\n\n        relations = set()\n        for item in role_setting:\n            role_id = item['role_id']\n            workspace_ids = item['workspace_ids'] if item['workspace_ids'] else ['None']\n            for workspace_id in workspace_ids:\n                relations.add((role_id, workspace_id))\n        for role_id, workspace_id in relations:\n            workspace_user_role_mapping_model.objects.create(\n                id=uuid.uuid7(),\n                role_id=role_id,\n                workspace_id=workspace_id,\n                user_id=user.id\n            )\n        permission_version, permission_get_key = Cache_Version.PERMISSION_LIST.value\n        cache.delete(permission_get_key(str(user.id)), version=permission_version)\n\n\ndef set_default_permission(user_id, instance):\n    \"\"\"\n    为用户设置默认权限\n    \"\"\"\n    default_permission = instance.get('defaultPermission', 'NOT_AUTH')\n\n    # 获取工作空间ID列表\n    workspace_ids = _get_workspace_ids(instance, default_permission)\n    if not workspace_ids:\n        return\n\n    # 根据权限类型确定认证类型\n    auth_type = (ResourceAuthType.ROLE\n                 if default_permission == ResourceAuthType.ROLE\n                 else ResourceAuthType.RESOURCE_PERMISSION_GROUP)\n\n    # 设置根目录权限\n    _set_root_permissions(user_id, workspace_ids)\n\n    # 如果是无权限设置，直接返回\n    if default_permission == 'NOT_AUTH':\n        return\n\n    # 设置具体资源权限\n    _set_resource_permissions(user_id, workspace_ids, default_permission, auth_type)\n\n\ndef _get_workspace_ids(instance, default_permission):\n    \"\"\"\n    获取工作空间ID列表\n    \"\"\"\n    role_setting_model = DatabaseModelManage.get_model(\"role_model\")\n\n    if not role_setting_model:\n        return ['default']\n\n    # 检查许可证有效性\n    license_is_valid = DatabaseModelManage.get_model('license_is_valid') or (lambda: False)\n    if default_permission == ResourceAuthType.ROLE and not license_is_valid():\n        return []\n\n    role_setting = instance.get('role_setting')\n    if not role_setting:\n        return ['default']\n\n    # 获取用户角色的工作空间ID\n    all_role_ids = [item['role_id'] for item in role_setting]\n    user_role_ids = set(role_setting_model.objects.filter(\n        id__in=all_role_ids,\n        type=RoleConstants.USER.name\n    ).values_list('id', flat=True))\n\n    workspace_ids = set()\n    for item in role_setting:\n        role_id = item['role_id']\n        if role_id in user_role_ids:\n            workspace_ids.update(item.get('workspace_ids', []))\n\n    return list(workspace_ids) if workspace_ids else []\n\n\ndef _set_root_permissions(user_id, workspace_ids):\n    \"\"\"\n    设置根目录权限（默认为查看权限）\n    \"\"\"\n    root_permissions = []\n    for ws in workspace_ids:\n        root_permissions.extend([\n            WorkspaceUserResourcePermission(\n                target=ws,\n                auth_target_type=auth_target_type,\n                permission_list=[ResourcePermission.VIEW],\n                workspace_id=ws,\n                user_id=user_id,\n                auth_type=ResourceAuthType.RESOURCE_PERMISSION_GROUP\n            )\n            for auth_target_type in [\n                AuthTargetType.APPLICATION.value,\n                AuthTargetType.KNOWLEDGE.value,\n                AuthTargetType.TOOL.value\n            ]\n        ])\n\n    _batch_create_permissions(root_permissions)\n\n\ndef _set_resource_permissions(user_id, workspace_ids, default_permission, auth_type):\n    \"\"\"\n    设置具体资源权限\n    \"\"\"\n    # 批量查询资源并按工作空间分组\n    resource_maps = _get_resource_maps(workspace_ids)\n\n    # 构造权限实例\n    instances = []\n    for ws in workspace_ids:\n        instances.extend(_create_resource_permission_instances(\n            ws, resource_maps, user_id, default_permission, auth_type))\n\n    # 批量创建权限\n    _batch_create_permissions(instances)\n\n\ndef _get_resource_maps(workspace_ids):\n    \"\"\"\n    获取各类型资源按工作空间的映射\n    \"\"\"\n    from application.models import Application, ApplicationFolder\n    from knowledge.models import Knowledge, KnowledgeFolder\n    from tools.models import Tool, ToolFolder\n    from models_provider.models import Model\n    from collections import defaultdict\n\n    resource_maps = {\n        'apps': defaultdict(list),\n        'app_folders': defaultdict(list),\n        'knowledge': defaultdict(list),\n        'knowledge_folders': defaultdict(list),\n        'tools': defaultdict(list),\n        'tool_folders': defaultdict(list),\n        'models': defaultdict(list)\n    }\n\n    # 查询应用资源\n    for ws, rid in Application.objects.filter(workspace_id__in=workspace_ids).values_list('workspace_id', 'id'):\n        resource_maps['apps'][ws].append(rid)\n\n    for ws, fid in ApplicationFolder.objects.filter(workspace_id__in=workspace_ids).exclude(\n            id__in=workspace_ids).values_list('workspace_id', 'id'):\n        resource_maps['app_folders'][ws].append(fid)\n\n    # 查询知识库资源\n    for ws, kid in Knowledge.objects.filter(workspace_id__in=workspace_ids).values_list('workspace_id', 'id'):\n        resource_maps['knowledge'][ws].append(kid)\n\n    for ws, kfid in KnowledgeFolder.objects.filter(workspace_id__in=workspace_ids).exclude(\n            id__in=workspace_ids).values_list('workspace_id', 'id'):\n        resource_maps['knowledge_folders'][ws].append(kfid)\n\n    # 查询工具资源\n    for ws, tid in Tool.objects.filter(workspace_id__in=workspace_ids).values_list('workspace_id', 'id'):\n        resource_maps['tools'][ws].append(tid)\n\n    for ws, tfid in ToolFolder.objects.filter(workspace_id__in=workspace_ids).exclude(\n            id__in=workspace_ids).values_list('workspace_id', 'id'):\n        resource_maps['tool_folders'][ws].append(tfid)\n\n    # 查询模型资源\n    for ws, mid in Model.objects.filter(workspace_id__in=workspace_ids).values_list('workspace_id', 'id'):\n        resource_maps['models'][ws].append(mid)\n\n    return resource_maps\n\n\ndef _create_resource_permission_instances(workspace_id, resource_maps, user_id, permission, auth_type):\n    \"\"\"\n    创建资源权限实例列表\n    \"\"\"\n    instances = []\n    if permission == ResourcePermission.MANAGE:\n        permission = [ResourcePermission.VIEW, ResourcePermission.MANAGE]\n    else:\n        permission = [permission]\n\n    # 应用权限\n    for rid in resource_maps['apps'].get(workspace_id, []):\n        instances.append(WorkspaceUserResourcePermission(\n            target=rid,\n            auth_target_type=AuthTargetType.APPLICATION.value,\n            permission_list=permission,\n            workspace_id=workspace_id,\n            user_id=user_id,\n            auth_type=auth_type\n        ))\n\n    # 应用文件夹权限\n    for fid in resource_maps['app_folders'].get(workspace_id, []):\n        instances.append(WorkspaceUserResourcePermission(\n            target=fid,\n            auth_target_type=AuthTargetType.APPLICATION.value,\n            permission_list=permission,\n            workspace_id=workspace_id,\n            user_id=user_id,\n            auth_type=auth_type\n        ))\n\n    # 知识库权限\n    for kid in resource_maps['knowledge'].get(workspace_id, []):\n        instances.append(WorkspaceUserResourcePermission(\n            target=kid,\n            auth_target_type=AuthTargetType.KNOWLEDGE.value,\n            permission_list=permission,\n            workspace_id=workspace_id,\n            user_id=user_id,\n            auth_type=auth_type\n        ))\n\n    # 知识库文件夹权限\n    for kf in resource_maps['knowledge_folders'].get(workspace_id, []):\n        instances.append(WorkspaceUserResourcePermission(\n            target=kf,\n            auth_target_type=AuthTargetType.KNOWLEDGE.value,\n            permission_list=permission,\n            workspace_id=workspace_id,\n            user_id=user_id,\n            auth_type=auth_type\n        ))\n\n    # 工具权限\n    for tid in resource_maps['tools'].get(workspace_id, []):\n        instances.append(WorkspaceUserResourcePermission(\n            target=tid,\n            auth_target_type=AuthTargetType.TOOL.value,\n            permission_list=permission,\n            workspace_id=workspace_id,\n            user_id=user_id,\n            auth_type=auth_type\n        ))\n\n    # 工具文件夹权限\n    for tf in resource_maps['tool_folders'].get(workspace_id, []):\n        instances.append(WorkspaceUserResourcePermission(\n            target=tf,\n            auth_target_type=AuthTargetType.TOOL.value,\n            permission_list=permission,\n            workspace_id=workspace_id,\n            user_id=user_id,\n            auth_type=auth_type\n        ))\n\n    # 模型权限\n    for mid in resource_maps['models'].get(workspace_id, []):\n        instances.append(WorkspaceUserResourcePermission(\n            target=mid,\n            auth_target_type=AuthTargetType.MODEL.value,\n            permission_list=permission,\n            workspace_id=workspace_id,\n            user_id=user_id,\n            auth_type=auth_type\n        ))\n\n    return instances\n\n\ndef _batch_create_permissions(instances, batch_size=500):\n    \"\"\"\n    批量创建权限实例\n    \"\"\"\n    if not instances:\n        return\n\n    objs = WorkspaceUserResourcePermission.objects\n    for i in range(0, len(instances), batch_size):\n        objs.bulk_create(instances[i:i + batch_size])\n\n\nclass RePasswordSerializer(serializers.Serializer):\n    email = serializers.EmailField(\n        required=True,\n        label=_(\"Email\"),\n        validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message,\n                                              code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)])\n\n    code = serializers.CharField(required=True, label=_(\"Code\"))\n    password = serializers.CharField(\n        required=True,\n        label=_(\"Password\"),\n        max_length=20,\n        min_length=6,\n        validators=[\n            validators.RegexValidator(\n                regex=PASSWORD_REGEX,\n                message=_(\n                    \"The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters.\"\n                )\n            )\n        ]\n    )\n    re_password = serializers.CharField(\n        required=True,\n        label=_(\"Re Password\"),\n        validators=[\n            validators.RegexValidator(\n                regex=PASSWORD_REGEX,\n                message=_(\n                    \"The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters.\"\n                )\n            )\n        ]\n    )\n\n    class Meta:\n        model = User\n        fields = '__all__'\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        email = self.data.get(\"email\")\n        cache_code = cache.get(get_key(email + ':reset_password'), version=version)\n        if self.data.get('password') != self.data.get('re_password'):\n            raise AppApiException(ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.code,\n                                  ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.message)\n        if cache_code != self.data.get('code'):\n            raise AppApiException(ExceptionCodeConstants.CODE_ERROR.value.code,\n                                  ExceptionCodeConstants.CODE_ERROR.value.message)\n        return True\n\n    def reset_password(self):\n        \"\"\"\n        修改密码\n        :return: 是否成功\n        \"\"\"\n        if self.is_valid():\n            email = self.data.get(\"email\")\n            QuerySet(User).filter(email=email).update(\n                password=password_encrypt(self.data.get('password')))\n            code_cache_key = email + \":reset_password\"\n            cache.delete(get_key(code_cache_key), version=version)\n            return True\n\n\nclass ResetCurrentUserPassword(serializers.Serializer):\n    password = serializers.CharField(\n        required=True,\n        label=_(\"Password\"),\n        max_length=20,\n        min_length=6,\n        validators=[\n            validators.RegexValidator(\n                regex=PASSWORD_REGEX,\n                message=_(\n                    \"The password must be 6-20 characters long and must be a combination of letters, numbers, and special characters.\"\n                )\n            )\n        ]\n    )\n    re_password = serializers.CharField(\n        required=True,\n        label=_(\"Re Password\"),\n        validators=[\n            validators.RegexValidator(\n                regex=PASSWORD_REGEX,\n                message=_(\n                    \"The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters.\"\n                )\n            )\n        ]\n    )\n\n    class Meta:\n        model = User\n        fields = '__all__'\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=True)\n        if self.data.get('password') != self.data.get('re_password'):\n            raise AppApiException(ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.code,\n                                  ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.message)\n        return True\n\n    def reset_password(self, user_id: str):\n        \"\"\"\n        修改密码\n        :return: 是否成功\n        \"\"\"\n        if self.is_valid():\n            QuerySet(User).filter(id=user_id).update(\n                password=password_encrypt(self.data.get('password')))\n            return True\n\n\nclass SendEmailSerializer(serializers.Serializer):\n    email = serializers.EmailField(\n        required=True\n        , label=_(\"Email\"),\n        validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message,\n                                              code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)])\n\n    type = serializers.CharField(required=True, label=_(\"Type\"), validators=[\n        validators.RegexValidator(regex=re.compile(\"^register|reset_password$\"),\n                                  message=_(\"The type only supports register|reset_password\"), code=500)\n    ])\n\n    class Meta:\n        model = User\n        fields = '__all__'\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid(raise_exception=raise_exception)\n        code_cache_key = self.data.get('email') + \":\" + self.data.get(\"type\")\n        code_cache_key_lock = code_cache_key + \"_lock\"\n        ttl = cache.ttl(code_cache_key_lock, version=version)\n        if ttl is not None and ttl > 0:\n            raise AppApiException(500, _(\"Do not send emails again within {seconds} seconds\").format(\n                seconds=int(ttl.total_seconds())))\n        return True\n\n    def send(self):\n        \"\"\"\n        发送邮件\n        :return:   是否发送成功\n        :exception 发送失败异常\n        \"\"\"\n        email = self.data.get(\"email\")\n        state = self.data.get(\"type\")\n        # 生成随机验证码\n        code = \"\".join(list(map(lambda i: random.choice(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'\n                                                         ]), range(6))))\n        # 获取邮件模板\n        language = get_language()\n        file = open(\n            os.path.join(PROJECT_DIR, \"apps\", \"common\", 'template', f'email_template_{to_locale(language)}.html'), \"r\",\n            encoding='utf-8')\n        content = file.read()\n        file.close()\n        code_cache_key = email + \":\" + state\n        code_cache_key_lock = code_cache_key + \"_lock\"\n        # 设置缓存\n        cache.set(get_key(code_cache_key_lock), code, timeout=60, version=version)\n        system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first()\n        if system_setting is None:\n            cache.delete(get_key(code_cache_key_lock), version=version)\n            raise AppApiException(1004,\n                                  _(\"The email service has not been set up. Please contact the administrator to set up the email service in [Email Settings].\"))\n        try:\n            connection = EmailBackend(system_setting.meta.get(\"email_host\"),\n                                      system_setting.meta.get('email_port'),\n                                      system_setting.meta.get('email_host_user'),\n                                      system_setting.meta.get('email_host_password'),\n                                      system_setting.meta.get('email_use_tls'),\n                                      False,\n                                      system_setting.meta.get('email_use_ssl')\n                                      )\n            # 发送邮件\n            send_mail(_('【Intelligent knowledge base question and answer system-{action}】').format(\n                action=_('User registration') if state == 'register' else _('Change password')),\n                '',\n                html_message=f'{content.replace(\"${code}\", code)}',\n                from_email=system_setting.meta.get('from_email'),\n                recipient_list=[email], fail_silently=False, connection=connection)\n        except Exception as e:\n            cache.delete(get_key(code_cache_key_lock))\n            return True\n        cache.set(get_key(code_cache_key), code, timeout=60 * 30, version=version)\n        return True\n\n\nclass CheckCodeSerializer(serializers.Serializer):\n    \"\"\"\n     校验验证码\n    \"\"\"\n    email = serializers.EmailField(\n        required=True,\n        label=_(\"Email\"),\n        validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message,\n                                              code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)])\n    code = serializers.CharField(required=True, label=_(\"Verification code\"))\n\n    type = serializers.CharField(required=True,\n                                 label=_(\"Type\"),\n                                 validators=[\n                                     validators.RegexValidator(regex=re.compile(\"^register|reset_password$\"),\n                                                               message=_(\n                                                                   \"The type only supports register|reset_password\"),\n                                                               code=500)\n                                 ])\n\n    def is_valid(self, *, raise_exception=False):\n        super().is_valid()\n        value = cache.get(get_key(self.data.get(\"email\") + \":\" + self.data.get(\"type\")), version=version)\n        if value is None or value != self.data.get(\"code\"):\n            raise ExceptionCodeConstants.CODE_ERROR.value.to_app_api_exception()\n        return True\n\n\nclass SwitchLanguageSerializer(serializers.Serializer):\n    user_id = serializers.UUIDField(required=True, label=_('user id'))\n    language = serializers.CharField(required=True, label=_('language'))\n\n    def switch(self):\n        self.is_valid(raise_exception=True)\n        language = self.data.get('language')\n        support_language_list = ['zh-CN', 'zh-Hant', 'en-US']\n        if not support_language_list.__contains__(language):\n            raise AppApiException(500, _('language only support:') + ','.join(support_language_list))\n        QuerySet(User).filter(id=self.data.get('user_id')).update(language=language)\n"
  },
  {
    "path": "apps/users/tests.py",
    "content": "from django.test import TestCase\n\n# Create your tests here.\n"
  },
  {
    "path": "apps/users/urls.py",
    "content": "from django.urls import path\n\nfrom . import views\n\napp_name = \"user\"\n# @formatter:off\nurlpatterns = [\n    path('user/login', views.LoginView.as_view(), name='login'),\n    path('user/profile', views.UserProfileView.as_view(), name=\"user_profile\"),\n    path('user/captcha', views.CaptchaView.as_view(), name='captcha'),\n    path('user/test', views.TestPermissionsUserView.as_view(), name=\"test\"),\n    path('user/logout', views.Logout.as_view(), name='logout'),\n    path('user/language', views.SwitchUserLanguageView.as_view(), name='language'),\n    path(\"user/send_email\", views.SendEmail.as_view(), name='send_email'),\n    path(\"user/check_code\", views.CheckCode.as_view(), name='check_code'),\n    path(\"user/re_password\", views.RePasswordView.as_view(), name='re_password'),\n    path(\"user/current/send_email\", views.SendEmailToCurrentUserView.as_view(), name=\"send_email_current\"),\n    path(\"user/current/reset_password\", views.ResetCurrentUserPasswordView.as_view(), name=\"reset_password_current\"),\n    path(\"user/list\", views.UserList.as_view(), name=\"current_user_profile\"),\n    path('workspace/<str:workspace_id>/user_list', views.WorkspaceUserListView.as_view(), name=\"test_workspace_id_permission\"),\n    path('workspace/<str:workspace_id>/user_member',views.WorkspaceUserMemberView.as_view(), name=\"test_workspace_id_permission\"),\n    path('workspace/<str:workspace_id>/user/profile', views.TestWorkspacePermissionUserView.as_view(), name=\"test_workspace_id_permission\"),\n    path(\"user_manage\", views.UserManage.as_view(), name=\"user_manage\"),\n    path(\"user_manage/batch_delete\", views.UserManage.BatchDelete.as_view()),\n    path(\"user_manage/password\", views.UserManage.Password.as_view()),\n    path(\"user_manage/<str:user_id>\", views.UserManage.Operate.as_view(), name=\"user_manage_operate\"),\n    path(\"user_manage/<str:user_id>/re_password\", views.UserManage.RePassword.as_view(), name=\"user_manage_re_password\"),\n    path(\"user_manage/<int:current_page>/<int:page_size>\", views.UserManage.Page.as_view(), name=\"user_manage_page\"),\n]\n"
  },
  {
    "path": "apps/users/views/__init__.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： __init__.py.py\n    @date：2025/4/14 10:20\n    @desc:\n\"\"\"\nfrom .login import *\nfrom .user import *\n"
  },
  {
    "path": "apps/users/views/login.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： user.py\n    @date：2025/4/14 10:22\n    @desc:\n\"\"\"\nfrom django.core.cache import cache\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common import result\nfrom common.auth import TokenAuth\nfrom common.constants.cache_version import Cache_Version\nfrom common.log.log import log\nfrom common.utils.common import encryption\nfrom models_provider.api.model import DefaultModelResponse\nfrom users.api.login import LoginAPI, CaptchaAPI\nfrom users.serializers.login import LoginSerializer, CaptchaSerializer\n\n\ndef _get_details(request):\n    path = request.path\n    body = request.data\n    query = request.query_params\n    return {\n        'path': path,\n        'body': {**body, 'password': encryption(body.get('password', ''))},\n        'query': query\n    }\n\n\nclass LoginView(APIView):\n    @extend_schema(methods=['POST'],\n                   description=_(\"Log in\"),\n                   summary=_(\"Log in\"),\n                   operation_id=_(\"Log in\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   request=LoginAPI.get_request(),\n                   responses=LoginAPI.get_response())\n    @log(menu='User management', operate='Log in', get_user=lambda r: {'username': r.data.get('username', None)},\n         get_details=_get_details,\n         get_operation_object=lambda r, k: {'name': r.data.get('username')})\n    def post(self, request: Request):\n        return result.success(LoginSerializer().login(request.data))\n\n\nclass Logout(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['POST'],\n                   summary=_(\"Sign out\"),\n                   description=_(\"Sign out\"),\n                   operation_id=_(\"Sign out\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   responses=DefaultModelResponse.get_response())\n    @log(menu='User management', operate='Sign out',\n         get_operation_object=lambda r, k: {'name': r.user.username})\n    def post(self, request: Request):\n        version, get_key = Cache_Version.TOKEN.value\n        cache.delete(get_key(token=request.META.get('HTTP_AUTHORIZATION')[7:]), version=version)\n        return result.success(True)\n\n\nclass CaptchaView(APIView):\n    @extend_schema(methods=['GET'],\n                   summary=_(\"Get captcha\"),\n                   description=_(\"Get captcha\"),\n                   operation_id=_(\"Get captcha\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   responses=CaptchaAPI.get_response())\n    def get(self, request: Request):\n        username = request.query_params.get('username', None)\n        return result.success(CaptchaSerializer().generate(username))\n"
  },
  {
    "path": "apps/users/views/user.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: MaxKB\n    @Author：虎虎\n    @file： user.py\n    @date：2025/4/14 19:25\n    @desc:\n\"\"\"\nfrom django.core.cache import cache\nfrom django.db.models import QuerySet\nfrom django.utils.translation import gettext_lazy as _\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework.request import Request\nfrom rest_framework.views import APIView\n\nfrom common.auth.authenticate import TokenAuth\nfrom common.auth.authentication import has_permissions\nfrom common.constants.cache_version import Cache_Version\nfrom common.constants.permission_constants import PermissionConstants, Permission, Group, Operate, RoleConstants\nfrom common.log.log import log\nfrom common.result import result\nfrom common.utils.common import query_params_to_single_dict\nfrom maxkb.const import CONFIG\nfrom models_provider.api.model import DefaultModelResponse\nfrom tools.serializers.tool import encryption\nfrom users.api.user import UserProfileAPI, TestWorkspacePermissionUserApi, DeleteUserApi, EditUserApi, \\\n    ChangeUserPasswordApi, UserPageApi, UserListApi, UserPasswordResponse, WorkspaceUserAPI, ResetPasswordAPI, \\\n    SendEmailAPI, CheckCodeAPI, SwitchUserLanguageAPI\nfrom users.models import User\nfrom users.serializers.user import UserProfileSerializer, UserManageSerializer, CheckCodeSerializer, \\\n    SendEmailSerializer, RePasswordSerializer, SwitchLanguageSerializer, ResetCurrentUserPassword\n\ndefault_password = CONFIG.get('DEFAULT_PASSWORD', 'MaxKB@123..')\n\n\ndef get_user_operation_object(user_id):\n    user_model = QuerySet(model=User).filter(id=user_id).first()\n    if user_model is not None:\n        return {\n            \"name\": user_model.name\n        }\n    return {}\n\n\n\ndef get_re_password_details(request):\n    path = request.path\n    body = request.data\n    query = request.query_params\n    return {\n        \"path\": path,\n        \"body\": {**body, 'password': encryption(body.get('password', '')),\n                 're_password': encryption(body.get('re_password', ''))},\n        \"query\": query\n    }\n\n\nclass UserProfileView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['GET'],\n                   summary=_(\"Get current user information\"),\n                   description=_(\"Get current user information\"),\n                   operation_id=_(\"Get current user information\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n\n                   responses=UserProfileAPI.get_response())\n    def get(self, request: Request):\n        return result.success(UserProfileSerializer().profile(request.user, request.auth))\n\n\nclass TestPermissionsUserView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['GET'],\n                   summary=_(\"Get current user information\"),\n                   description=_(\"Get current user information\"),\n                   operation_id=\"测试\",\n                   tags=[_(\"User Management\")],  # type: ignore\n                   responses=UserProfileAPI.get_response())\n    @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN)\n    def get(self, request: Request):\n        return result.success(UserProfileSerializer().profile(request.user, request.auth))\n\n\nclass SwitchUserLanguageView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['POST'],\n                   summary=_(\"Switch Language\"),\n                   description=_(\"Switch Language\"),\n                   operation_id=_(\"Switch Language\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   request=SwitchUserLanguageAPI.get_request(),\n                   )\n    @log(menu='User management', operate='Switch Language',\n         get_operation_object=lambda r, k: {'name': r.user.username})\n    @has_permissions(PermissionConstants.SWITCH_LANGUAGE, RoleConstants.ADMIN, RoleConstants.USER,\n                     RoleConstants.WORKSPACE_MANAGE)\n    def post(self, request: Request):\n        data = {**request.data, 'user_id': request.user.id}\n        return result.success(SwitchLanguageSerializer(data=data).switch())\n\n\nclass TestWorkspacePermissionUserView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['GET'],\n                   summary=\"针对工作空间下权限校验\",\n                   description=\"针对工作空间下权限校验\",\n                   operation_id=\"针对工作空间下权限校验\",\n                   tags=[_(\"User Management\")],  # type: ignore\n                   responses=UserProfileAPI.get_response(),\n                   parameters=TestWorkspacePermissionUserApi.get_parameters())\n    @has_permissions(PermissionConstants.USER_EDIT.get_workspace_permission(), RoleConstants.ADMIN)\n    def get(self, request: Request, workspace_id):\n        return result.success(UserProfileSerializer().profile(request.user, request.auth))\n\n\nclass UserList(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['GET'],\n                   summary=_(\"Get all user\"),\n                   description=_(\"Get all user\"),\n                   operation_id=_(\"Get all user\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   responses=UserListApi.get_response())\n    @has_permissions(RoleConstants.WORKSPACE_MANAGE, RoleConstants.ADMIN, RoleConstants.EXTENDS_ADMIN,\n                     RoleConstants.EXTENDS_WORKSPACE_MANAGE)\n    def get(self, request: Request):\n        return result.success(UserManageSerializer().get_all_user_list())\n\n\nclass WorkspaceUserListView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['GET'],\n                   summary=_(\"Get user list under workspace\"),\n                   description=_(\"Get user list under workspace\"),\n                   operation_id=_(\"Get user list under workspace\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   parameters=WorkspaceUserAPI.get_parameters(),\n                   responses=WorkspaceUserAPI.get_response())\n    def get(self, request: Request, workspace_id):\n        return result.success(UserManageSerializer().get_user_list(workspace_id))\n\n\nclass WorkspaceUserMemberView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['GET'],\n                   summary=_(\"Get user member under workspace\"),\n                   description=_(\"Get user member under workspace\"),\n                   operation_id=_(\"Get user member under workspace\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   parameters=WorkspaceUserAPI.get_parameters(),\n                   responses=WorkspaceUserAPI.get_response())\n    def get(self, request: Request, workspace_id):\n        return result.success(UserManageSerializer().get_user_members(workspace_id))\n\n\nclass UserManage(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['POST'],\n                   summary=_(\"Create user\"),\n                   description=_(\"Create user\"),\n                   operation_id=_(\"Create user\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   request=UserProfileAPI.get_request(),\n                   responses=UserProfileAPI.get_response())\n    @has_permissions(PermissionConstants.USER_CREATE, RoleConstants.ADMIN)\n    @log(menu='User management', operate='Add user',\n         get_operation_object=lambda r, k: {'name': r.data.get('username', None)})\n    def post(self, request: Request):\n        return result.success(UserManageSerializer().save(request.data, str(request.user.id)))\n\n    class Password(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['Get'],\n                       summary=_(\"Get default password\"),\n                       description=_(\"Get default password\"),\n                       operation_id=_(\"Get default password\"),  # type: ignore\n                       tags=[_(\"User Management\")],  # type: ignore\n                       responses=UserPasswordResponse.get_response())\n        @has_permissions(PermissionConstants.USER_CREATE, PermissionConstants.CHAT_USER_CREATE,\n                         PermissionConstants.WORKSPACE_CHAT_USER_CREATE, RoleConstants.ADMIN,\n                         RoleConstants.WORKSPACE_MANAGE)\n        def get(self, request: Request):\n            return result.success(data={'password': default_password})\n\n    class Operate(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['DELETE'],\n                       description=_(\"Delete user\"),\n                       summary=_(\"Delete user\"),\n                       operation_id=_(\"Delete user\"),  # type: ignore\n                       tags=[_(\"User Management\")],  # type: ignore\n                       parameters=DeleteUserApi.get_parameters(),\n                       responses=DefaultModelResponse.get_response())\n        @has_permissions(PermissionConstants.USER_DELETE, RoleConstants.ADMIN)\n        @log(menu='User management', operate='Delete user',\n             get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id')))\n        def delete(self, request: Request, user_id):\n            return result.success(UserManageSerializer.Operate(data={'id': user_id}).delete(with_valid=True))\n\n        @extend_schema(methods=['GET'],\n                       summary=_(\"Get user information\"),\n                       description=_(\"Get user information\"),\n                       operation_id=_(\"Get user information\"),  # type: ignore\n                       tags=[_(\"User Management\")],  # type: ignore\n                       request=DeleteUserApi.get_parameters(),\n                       responses=UserProfileAPI.get_response())\n        @has_permissions(PermissionConstants.USER_READ, RoleConstants.ADMIN)\n        def get(self, request: Request, user_id):\n            return result.success(UserManageSerializer.Operate(data={'id': user_id}).one(with_valid=True))\n\n        @extend_schema(methods=['PUT'],\n                       summary=_(\"Update user information\"),\n                       description=_(\"Update user information\"),\n                       operation_id=_(\"Update user information\"),  # type: ignore\n                       tags=[_(\"User Management\")],  # type: ignore\n                       parameters=DeleteUserApi.get_parameters(),\n                       request=EditUserApi.get_request(),\n                       responses=UserProfileAPI.get_response())\n        @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN)\n        @log(menu='User management', operate='Update user information',\n             get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id')))\n        def put(self, request: Request, user_id):\n            return result.success(\n                UserManageSerializer.Operate(data={'id': user_id}).edit(request.data, str(request.user.id),\n                                                                        with_valid=True))\n\n    class BatchDelete(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['POST'],\n                       description=_(\"Batch delete user\"),\n                       summary=_(\"Batch delete user\"),\n                       operation_id=_(\"Batch delete user\"),  # type: ignore\n                       tags=[_(\"User Management\")],  # type: ignore\n                       request=DeleteUserApi.get_request(),\n                       responses=DefaultModelResponse.get_response())\n        @has_permissions(PermissionConstants.USER_DELETE, RoleConstants.ADMIN)\n        @log(menu='User management', operate='Batch delete user',\n             get_operation_object=lambda r, k: get_user_operation_object(r.data.get('ids', [])))\n        def post(self, request: Request):\n            return result.success(UserManageSerializer.BatchDelete({'ids': request.data}).batch_delete(with_valid=True))\n\n    class RePassword(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['PUT'],\n                       summary=_(\"Change password\"),\n                       description=_(\"Change password\"),\n                       operation_id=_(\"Change password\"),  # type: ignore\n                       tags=[_(\"User Management\")],  # type: ignore\n                       parameters=DeleteUserApi.get_parameters(),\n                       request=ChangeUserPasswordApi.get_request(),\n                       responses=DefaultModelResponse.get_response())\n        @log(menu='User management', operate='Change password',\n             get_operation_object=lambda r, k: get_user_operation_object(k.get('user_id')),\n             get_details=get_re_password_details)\n        @has_permissions(PermissionConstants.USER_EDIT, RoleConstants.ADMIN)\n        def put(self, request: Request, user_id):\n            return result.success(\n                UserManageSerializer.Operate(data={'id': user_id}).re_password(request.data, with_valid=True))\n\n    class Page(APIView):\n        authentication_classes = [TokenAuth]\n\n        @extend_schema(methods=['GET'],\n                       summary=_(\"Get user paginated list\"),\n                       description=_(\"Get user paginated list\"),\n                       operation_id=_(\"Get user paginated list\"),  # type: ignore\n                       tags=[_(\"User Management\")],  # type: ignore\n                       parameters=UserPageApi.get_parameters(),\n                       responses=UserPageApi.get_response())\n        @has_permissions(PermissionConstants.USER_READ, RoleConstants.ADMIN)\n        def get(self, request: Request, current_page, page_size):\n            d = UserManageSerializer.Query(\n                data={**query_params_to_single_dict(request.query_params)})\n            return result.success(d.page(current_page, page_size, str(request.user.id)))\n\n\nclass RePasswordView(APIView):\n\n    @extend_schema(methods=['POST'],\n                   summary=_(\"Change password\"),\n                   description=_(\"Change password\"),\n                   operation_id=_(\"Change password\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   request=ResetPasswordAPI.get_request(),\n                   responses=DefaultModelResponse.get_response())\n    @log(menu='User management', operate='Change password',\n         get_operation_object=lambda r, k: {'name': r.user.username},\n         get_details=get_re_password_details)\n    def post(self, request: Request):\n        serializer_obj = RePasswordSerializer(data=request.data)\n        return result.success(serializer_obj.reset_password())\n\n\nclass SendEmail(APIView):\n\n    @extend_schema(methods=['POST'],\n                   summary=_(\"Send email\"),\n                   description=_(\"Send email\"),\n                   operation_id=_(\"Send email\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   request=SendEmailAPI.get_request(),\n                   responses=SendEmailAPI.get_response())\n    @log(menu='User management', operate='Send email',\n         get_operation_object=lambda r, k: {'name': r.data.get('email', None)},\n         get_user=lambda r: {'user_name': None, 'email': r.data.get('email', None)})\n    def post(self, request: Request):\n        serializer_obj = SendEmailSerializer(data=request.data)\n        if serializer_obj.is_valid(raise_exception=True):\n            return result.success(serializer_obj.send())\n\n\nclass CheckCode(APIView):\n\n    @extend_schema(methods=['POST'],\n                   summary=_(\"Check whether the verification code is correct\"),\n                   description=_(\"Check whether the verification code is correct\"),\n                   operation_id=_(\"Check whether the verification code is correct\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   request=CheckCodeAPI.get_request(),\n                   responses=CheckCodeAPI.get_response())\n    @log(menu='User management', operate='Check whether the verification code is correct',\n         get_operation_object=lambda r, k: {'name': r.data.get('email', None)},\n         get_user=lambda r: {'user_name': None, 'email': r.data.get('email', None)})\n    def post(self, request: Request):\n        return result.success(CheckCodeSerializer(data=request.data).is_valid(raise_exception=True))\n\n\nclass SendEmailToCurrentUserView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['POST'],\n                   summary=_(\"Send email to current user\"),\n                   description=_(\"Send email to current user\"),\n                   operation_id=_(\"Send email to current user\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   request=SendEmailAPI.get_request(),\n                   responses=SendEmailAPI.get_response())\n    @log(menu='User management', operate='Send email to current user',\n         get_operation_object=lambda r, k: {'name': r.user.username})\n    def post(self, request: Request):\n        serializer_obj = SendEmailSerializer(data={'email': request.user.email, 'type': \"reset_password\"})\n        if serializer_obj.is_valid(raise_exception=True):\n            return result.success(serializer_obj.send())\n\n\nclass ResetCurrentUserPasswordView(APIView):\n    authentication_classes = [TokenAuth]\n\n    @extend_schema(methods=['POST'],\n                   summary=_(\"Modify current user password\"),\n                   description=_(\"Modify current user password\"),\n                   operation_id=_(\"Modify current user password\"),  # type: ignore\n                   tags=[_(\"User Management\")],  # type: ignore\n                   request=ResetPasswordAPI.get_request(),\n                   responses=DefaultModelResponse.get_response())\n    @log(menu='User management', operate='Modify current user password',\n         get_operation_object=lambda r, k: {'name': r.user.username},\n         get_details=get_re_password_details)\n    @has_permissions(PermissionConstants.CHANGE_PASSWORD, RoleConstants.ADMIN, RoleConstants.USER,\n                     RoleConstants.WORKSPACE_MANAGE)\n    def post(self, request: Request):\n        serializer_obj = ResetCurrentUserPassword(data=request.data)\n        if serializer_obj.reset_password(request.user.id):\n            version, get_key = Cache_Version.TOKEN.value\n            cache.delete(get_key(token=request.auth), version=version)\n            return result.success(True)\n        return result.error(_(\"Failed to change password\"))\n"
  },
  {
    "path": "installer/Dockerfile",
    "content": "FROM node:24-alpine AS web-build\nCOPY ui ui\nRUN cd ui && ls -la && if [ -d \"dist\" ]; then exit 0; fi && \\\n    npm install --prefer-offline --no-audit && \\\n    npm install -D concurrently && \\\n    NODE_OPTIONS=\"--max-old-space-size=4096\" npx concurrently \"npm run build\" \"npm run build-chat\" && \\\n    find . -maxdepth 1 ! -name '.' ! -name 'dist' ! -name 'public' -exec rm -rf {} +\n\nFROM ghcr.io/1panel-dev/maxkb-base:python3.11-pg17.7-20260212 AS stage-build\nCOPY --chmod=700 . /opt/maxkb-app\nRUN apt-get update && \\\n    apt-get install -y --no-install-recommends gcc g++ gettext libexpat1-dev libffi-dev && \\\n    apt-get clean all  && \\\n    rm -rf /var/lib/apt/lists/*\nWORKDIR /opt/maxkb-app\nRUN gcc -shared -fPIC -o ${MAXKB_SANDBOX_HOME}/lib/sandbox.so /opt/maxkb-app/installer/sandbox.c -ldl && \\\n    rm -rf /opt/maxkb-app/ui && \\\n    pip install uv --break-system-packages && \\\n    python -m uv pip install -r pyproject.toml && \\\n    find /opt/maxkb-app  -depth \\( -name \".git*\" -o -name \".docker*\" -o -name \".idea*\" -o -name \".editorconfig*\" -o -name \".prettierrc*\" -o -name \"README.md\" -o -name \"poetry.lock\" -o -name \"pyproject.toml\"  \\) -exec rm -rf {} + && \\\n    python /opt/maxkb-app/apps/manage.py compilemessages && \\\n    export PIP_TARGET=/opt/maxkb-app/sandbox/python-packages && \\\n    python -m uv pip install --target=$PIP_TARGET requests pymysql psycopg2-binary && \\\n    rm -rf /opt/maxkb-app/installer\nCOPY --from=web-build --chmod=700 ui /opt/maxkb-app/ui\n\nFROM ghcr.io/1panel-dev/maxkb-base:python3.11-pg17.7-20260212\nARG DOCKER_IMAGE_TAG=dev \\\n    BUILD_AT \\\n    GITHUB_COMMIT\n\nENV MAXKB_VERSION=\"${DOCKER_IMAGE_TAG} (build at ${BUILD_AT}, commit: ${GITHUB_COMMIT})\" \\\n    MAXKB_DB_NAME=maxkb \\\n    MAXKB_DB_HOST=127.0.0.1 \\\n    MAXKB_DB_PORT=5432  \\\n    MAXKB_DB_USER=${POSTGRES_USER} \\\n    MAXKB_DB_PASSWORD=${POSTGRES_PASSWORD} \\\n    MAXKB_DB_MAX_OVERFLOW=80 \\\n    MAXKB_REDIS_HOST=127.0.0.1 \\\n    MAXKB_REDIS_PORT=6379 \\\n    MAXKB_REDIS_DB=0 \\\n    MAXKB_REDIS_PASSWORD=${REDIS_PASSWORD} \\\n    MAXKB_EMBEDDING_MODEL_PATH=/opt/maxkb-app/model/embedding \\\n    MAXKB_EMBEDDING_MODEL_NAME=/opt/maxkb-app/model/embedding/shibing624_text2vec-base-chinese \\\n    MAXKB_LOCAL_MODEL_HOST=127.0.0.1 \\\n    MAXKB_LOCAL_MODEL_PORT=11636 \\\n    MAXKB_LOCAL_MODEL_PROTOCOL=http \\\n    PIP_TARGET=/opt/maxkb/python-packages\n\nWORKDIR /opt/maxkb-app\nCOPY --from=stage-build /opt/maxkb-app /opt/maxkb-app\nCOPY --from=stage-build /opt/py3 /opt/py3\n\nEXPOSE 8080\nVOLUME /opt/maxkb\nENTRYPOINT [\"bash\", \"-c\"]\nCMD [ \"/usr/bin/start-all.sh\" ]"
  },
  {
    "path": "installer/Dockerfile-base",
    "content": "FROM python:3.11-slim-trixie AS python-stage\nRUN python3 -m venv /opt/py3\n\nFROM ghcr.io/1panel-dev/maxkb-vector-model:v2.0.3 AS vector-model\n\nFROM postgres:17.7-trixie\nCOPY --from=python-stage /usr/local /usr/local\nCOPY --from=python-stage /opt/py3 /opt/py3\nCOPY --chmod=500 installer/*.sh /usr/bin/\nCOPY installer/init.sql /docker-entrypoint-initdb.d/\n\nARG DEPENDENCIES=\"                    \\\n        curl                          \\\n        ca-certificates               \\\n        vim                           \\\n        wait-for-it                   \\\n        redis-server                  \\\n        postgresql-17-pgvector        \\\n        postgresql-17-age\"\n\nRUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \\\n    echo \"Asia/Shanghai\" > /etc/timezone && \\\n    echo \"deb http://deb.debian.org/debian testing main\" >> /etc/apt/sources.list && \\\n    printf \"Package: redis-server\\nPin: release a=testing\\nPin-Priority: 501\\n\" > /etc/apt/preferences.d/redis && \\\n    apt-get update && apt-get install -y --no-install-recommends $DEPENDENCIES && \\\n    find /etc/ -type f ! -path '/etc/resolv.conf' ! -path '/etc/hosts' | xargs chmod g-rx && \\\n    curl -L --connect-timeout 120 -m 1800 https://resource.fit2cloud.com/maxkb/ffmpeg/get-ffmpeg-linux | sh && \\\n    mkdir -p /opt/maxkb-app/sandbox/lib && chmod -R 550 /opt/maxkb-app/sandbox && \\\n    useradd --no-create-home --home /opt/maxkb-app/sandbox -s /usr/sbin/nologin sandbox -g root && \\\n    chmod g-rwx /usr/local/bin/* /usr/bin/* /bin/* /usr/sbin/* /sbin/* /usr/lib/postgresql/17/bin/* && \\\n    chmod g+xr /usr/bin/ld.so /usr/local/bin/python* `which env` && \\\n    chmod -R g-rwx /tmp /var/tmp /var/lock && \\\n    chmod g+rx /tmp && \\\n    apt-get clean all && \\\n    rm -rf /var/lib/postgresql /var/lib/apt/lists/* /usr/share/doc/* /usr/share/man/* /usr/share/info/* /usr/share/locale/* /usr/share/lintian/* /usr/share/linda/* /var/cache/* /var/log/* /var/tmp/* /tmp/*\nCOPY --from=vector-model --chmod=700 /opt/maxkb-app/model /opt/maxkb-app/model\n\nENV PATH=/opt/py3/bin:$PATH \\\n    PGDATA=/opt/maxkb/data/postgresql/pgdata \\\n    POSTGRES_USER=root \\\n    POSTGRES_PASSWORD=Password123@postgres \\\n    POSTGRES_MAX_CONNECTIONS=1000 \\\n    REDIS_PASSWORD=Password123@redis \\\n    LANG=en_US.UTF-8 \\\n    PYTHONUNBUFFERED=1 \\\n    MAXKB_CONFIG_TYPE=ENV \\\n    MAXKB_LOG_LEVEL=INFO \\\n    MAXKB_SANDBOX=1 \\\n    MAXKB_SANDBOX_HOME=/opt/maxkb-app/sandbox \\\n    MAXKB_SANDBOX_PYTHON_PACKAGE_PATHS=\"/opt/py3/lib/python3.11/site-packages,/opt/maxkb-app/sandbox/python-packages,/opt/maxkb/python-packages\" \\\n    MAXKB_SANDBOX_PYTHON_BANNED_HOSTS=\"127.0.0.0/8,localhost,host.docker.internal,172.17.0.0/16,maxkb,pgsql,redis,172.31.250.192/26,0.0.0.0/32,::/0\" \\\n    MAXKB_ADMIN_PATH=/admin\n\nEXPOSE 6379"
  },
  {
    "path": "installer/Dockerfile-vector-model",
    "content": "#FROM python:3.11-slim-bookworm AS vector-model\n#COPY installer/install_model.py install_model.py\n#RUN pip3 install --upgrade pip setuptools && \\\n#    pip install pycrawlers && \\\n#    pip install transformers && \\\n#    python3 install_model.py && \\\n#    cp -r model/base/hub model/tokenizer\n#FROM scratch\n#COPY --from=vector-model model /opt/maxkb-app/model\n\n# 不知道为什么用上面的脚本重新拉一遍向量模型比之前的大很多，所以还是用下面的脚本复用原来已经构建好的向量模型\n\nFROM python:3.11-slim-bookworm AS tmp-stage1\nCOPY installer/install_model_bert_base_cased.py install_model_bert_base_cased.py\nRUN pip3 install --upgrade pip setuptools && \\\n    pip install pycrawlers && \\\n    pip install transformers && \\\n    python3 install_model_bert_base_cased.py && \\\n    cp -r model/base/hub model/tokenizer\n\nFROM ghcr.io/1panel-dev/maxkb-vector-model:v1.0.1 AS vector-model\n\nFROM alpine AS tmp-stage2\nCOPY --from=vector-model /opt/maxkb/app/model /opt/maxkb-app/model\nCOPY --from=vector-model /opt/maxkb/app/model/base/hub /opt/maxkb-app/model/tokenizer\nCOPY --from=tmp-stage1 model/tokenizer /opt/maxkb-app/model/tokenizer\nRUN rm -rf /opt/maxkb-app/model/embedding/shibing624_text2vec-base-chinese/onnx\nRUN apk add --update --no-cache curl && \\\n    mkdir -p openai-tiktoken-cl100k-base && \\\n    curl -Lf  https://openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken > openai-tiktoken-cl100k-base/cl100k_base.tiktoken && \\\n    mv -f openai-tiktoken-cl100k-base /opt/maxkb-app/model/tokenizer/\n\n\nFROM scratch\nCOPY --from=tmp-stage2 /opt/maxkb-app/model /opt/maxkb-app/model"
  },
  {
    "path": "installer/init.sql",
    "content": "CREATE DATABASE \"maxkb\";\n\n\\c \"maxkb\";\n\nCREATE EXTENSION \"vector\";"
  },
  {
    "path": "installer/install_model.py",
    "content": "# coding=utf-8\n\"\"\"\n    @project: maxkb\n    @Author：虎\n    @file： install_model.py\n    @date：2023/12/18 14:02\n    @desc:\n\"\"\"\nimport json\nimport os.path\nfrom pycrawlers import huggingface\nfrom transformers import GPT2TokenizerFast\nhg = huggingface()\nprefix_dir = \"./model\"\nmodel_config = [\n    {\n        'download_params': {\n            'cache_dir': os.path.join(prefix_dir, 'base/hub'),\n            'pretrained_model_name_or_path': 'gpt2'\n        },\n        'download_function': GPT2TokenizerFast.from_pretrained\n    },\n    {\n        'download_params': {\n            'cache_dir': os.path.join(prefix_dir, 'base/hub'),\n            'pretrained_model_name_or_path': 'gpt2-medium'\n        },\n        'download_function': GPT2TokenizerFast.from_pretrained\n    },\n    {\n        'download_params': {\n            'cache_dir': os.path.join(prefix_dir, 'base/hub'),\n            'pretrained_model_name_or_path': 'gpt2-large'\n        },\n        'download_function': GPT2TokenizerFast.from_pretrained\n    },\n    {\n        'download_params': {\n            'cache_dir': os.path.join(prefix_dir, 'base/hub'),\n            'pretrained_model_name_or_path': 'gpt2-xl'\n        },\n        'download_function': GPT2TokenizerFast.from_pretrained\n    },\n    {\n        'download_params': {\n            'cache_dir': os.path.join(prefix_dir, 'base/hub'),\n            'pretrained_model_name_or_path': 'distilgpt2'\n        },\n        'download_function': GPT2TokenizerFast.from_pretrained\n    },\n    {\n        'download_params': {\n            'urls': [\"https://huggingface.co/shibing624/text2vec-base-chinese/tree/main\"],\n            'file_save_paths': [os.path.join(prefix_dir, 'embedding',\"shibing624_text2vec-base-chinese\")]\n        },\n        'download_function': hg.get_batch_data\n    }\n\n]\n\n\ndef install():\n    for model in model_config:\n        print(json.dumps(model.get('download_params')))\n        model.get('download_function')(**model.get('download_params'))\n\n\nif __name__ == '__main__':\n    install()"
  },
  {
    "path": "installer/install_model_bert_base_cased.py",
    "content": "# coding=utf-8\nimport json\nimport os.path\nfrom transformers import BertTokenizer\n\nprefix_dir = \"./model\"\nmodel_config = [\n    {\n        'download_params': {\n            'cache_dir': os.path.join(prefix_dir, 'base/hub'),\n            'pretrained_model_name_or_path': 'bert-base-cased'\n        },\n        'download_function': BertTokenizer.from_pretrained\n    },\n]\n\n\ndef install():\n    for model in model_config:\n        print(json.dumps(model.get('download_params')))\n        model.get('download_function')(**model.get('download_params'))\n\n\nif __name__ == '__main__':\n    install()\n"
  },
  {
    "path": "installer/sandbox.c",
    "content": "#define _GNU_SOURCE\n#include <dlfcn.h>\n#include <netdb.h>\n#include <arpa/inet.h>\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <regex.h>\n#include <unistd.h>\n#include <sys/socket.h>\n#include <sys/mman.h>\n#include <sys/un.h>\n#include <errno.h>\n#include <limits.h>\n#include <libgen.h>\n#include <pwd.h>\n#include <stdarg.h>\n#include <spawn.h>\n#include <sys/syscall.h>\n#include <sys/types.h>\n#include <linux/sched.h>\n#include <pty.h>\n#include <stdint.h>\n#include <stdbool.h>\n\n#define CONFIG_FILE \".sandbox.conf\"\n#define KEY_BANNED_HOSTS \"SANDBOX_PYTHON_BANNED_HOSTS\"\n#define KEY_ALLOW_DL_PATHS \"SANDBOX_PYTHON_ALLOW_DL_PATHS\"\n#define KEY_ALLOW_SUBPROCESS \"SANDBOX_PYTHON_ALLOW_SUBPROCESS\"\n#define KEY_ALLOW_SYSCALL \"SANDBOX_PYTHON_ALLOW_SYSCALL\"\n\nstatic char *banned_hosts = NULL;\nstatic char *allow_dl_paths = NULL;\nstatic int allow_subprocess = 0; // 默认禁止\nstatic int allow_syscall = 0;\n\nstatic void load_sandbox_config() {\n    Dl_info info;\n    if (dladdr((void *)load_sandbox_config, &info) == 0 || !info.dli_fname) {\n        banned_hosts = strdup(\"\");\n        allow_dl_paths = strdup(\"\");\n        allow_subprocess = 0;\n        allow_syscall = 0;\n        return;\n    }\n    char so_path[PATH_MAX];\n    strncpy(so_path, info.dli_fname, sizeof(so_path));\n    so_path[sizeof(so_path) - 1] = '\\0';\n    char *dir = dirname(so_path);\n    char config_path[PATH_MAX];\n    snprintf(config_path, sizeof(config_path), \"%s/%s\", dir, CONFIG_FILE);\n    FILE *fp = fopen(config_path, \"r\");\n    if (!fp) {\n        banned_hosts = strdup(\"\");\n        allow_dl_paths = strdup(\"\");\n        allow_subprocess = 0;\n        allow_syscall = 0;\n        return;\n    }\n    char line[512];\n    if (banned_hosts) { free(banned_hosts); banned_hosts = NULL; }\n    if (allow_dl_paths) { free(allow_dl_paths); allow_dl_paths = NULL; }\n    banned_hosts = strdup(\"\");\n    allow_dl_paths = strdup(\"\");\n    allow_subprocess = 0;\n    allow_syscall = 0;\n    while (fgets(line, sizeof(line), fp)) {\n        char *key = strtok(line, \"=\");\n        char *value = strtok(NULL, \"\\n\");\n        if (!key || !value) continue;\n        while (*key == ' ' || *key == '\\t') key++;\n        char *keyend = key + strlen(key) - 1;\n        while (keyend > key && (*keyend == ' ' || *keyend == '\\t')) *keyend-- = '\\0';\n        while (*value == ' ' || *value == '\\t') value++;\n        char *vend = value + strlen(value) - 1;\n        while (vend > value && (*vend == ' ' || *vend == '\\t')) *vend-- = '\\0';\n        if (strcmp(key, KEY_BANNED_HOSTS) == 0) {\n            free(banned_hosts);\n            banned_hosts = strdup(value);\n        } else if (strcmp(key, KEY_ALLOW_DL_PATHS) == 0) {\n            free(allow_dl_paths);\n            allow_dl_paths = strdup(value);  // 逗号分隔字符串\n        } else if (strcmp(key, KEY_ALLOW_SUBPROCESS) == 0) {\n            allow_subprocess = atoi(value);\n        } else if (strcmp(key, KEY_ALLOW_SYSCALL) == 0) {\n            allow_syscall = atoi(value);\n        }\n    }\n    fclose(fp);\n}\nstatic void ensure_config_loaded() {\n    if (!banned_hosts) load_sandbox_config();\n}\nstatic int is_sandbox_user() {\n    uid_t uid = getuid();\n    struct passwd *pw = getpwuid(uid);\n    if (!pw || !pw->pw_name) {\n        return 1;  // 无法识别用户 → 认为是 sandbox\n    }\n    if (strcmp(pw->pw_name, \"sandbox\") == 0) {\n        return 1;\n    }\n    return 0;\n}\nstatic int throw_permission_denied_err(bool whether_to_exit,const char *fmt, ...) {\n    va_list ap;\n    fputs(\"Permission denied to \", stderr);\n    va_start(ap, fmt);\n    vfprintf(stderr, fmt, ap);\n    va_end(ap);\n    fputs(\".\\n\", stderr);\n    errno = EACCES;\n    if (whether_to_exit) _exit(126);\n    return -1;\n}\n#define RESOLVE_REAL(func)                      \\\n    static typeof(func) *real_##func = NULL;    \\\n    if (!real_##func) {                         \\\n        real_##func = dlsym(RTLD_NEXT, #func);  \\\n    }\n/**\n * 限制网络访问\n */\n// ------------------ 匹配 域名 黑名单 ------------------\nstatic int match_banned_domain(const char *target, const char *rules) {\n    if (!target || !rules || !*rules) return 0;\n    char *list = strdup(rules);\n    char *token = strtok(list, \",\");\n    int matched = 0;\n    while (token) {\n        while (*token == ' ' || *token == '\\t') token++;\n        if (*token) {\n            regex_t re;\n            char buf[512];\n            snprintf(buf, sizeof(buf), \"^%s$\", token);\n            if (regcomp(&re, buf, REG_EXTENDED | REG_NOSUB | REG_ICASE) == 0) {\n                if (regexec(&re, target, 0, NULL, 0) == 0)\n                    matched = 1;\n                regfree(&re);\n            }\n        }\n        if (matched) break;\n        token = strtok(NULL, \",\");\n    }\n    free(list);\n    return matched;\n}\n// ------------------ 匹配 IP/CIDR 黑名单 ------------------\nstatic int match_banned_ip(const char *ip_str, const char *rules) {\n    if (!ip_str || !rules || !*rules) return 0;\n    struct in_addr  ip4;\n    struct in6_addr ip6;\n    int is_v4 = inet_pton(AF_INET, ip_str, &ip4) == 1;\n    int is_v6 = inet_pton(AF_INET6, ip_str, &ip6) == 1;\n    if (!is_v4 && !is_v6) return 0;\n    char *list = strdup(rules);\n    char *token = strtok(list, \",\");\n    int blocked = 0;\n    while (token) {\n        while (*token == ' ' || *token == '\\t') token++;\n        if (!*token) goto next;\n        char *slash = strchr(token, '/');\n        int prefix = -1;\n        if (slash) {\n            *slash++ = '\\0';\n            prefix = atoi(slash);\n        }\n        /* ---------- IPv4 ---------- */\n        if (is_v4) {\n            struct in_addr net4;\n            if (inet_pton(AF_INET, token, &net4) == 1) {\n                if (prefix < 0) {\n                    /* 单 IP */\n                    if (ip4.s_addr == net4.s_addr) {\n                        blocked = 1;\n                        break;\n                    }\n                } else if (prefix >= 0 && prefix <= 32) {\n                    uint32_t mask = prefix == 0\n                        ? 0\n                        : htonl(0xFFFFFFFFu << (32 - prefix));\n                    if ((ip4.s_addr & mask) == (net4.s_addr & mask)) {\n                        blocked = 1;\n                        break;\n                    }\n                }\n            }\n        }\n        /* ---------- IPv6 ---------- */\n        if (is_v6) {\n            struct in6_addr net6;\n            if (inet_pton(AF_INET6, token, &net6) == 1) {\n                if (prefix < 0) {\n                    /* 单 IP */\n                    if (memcmp(&ip6, &net6, sizeof(ip6)) == 0) {\n                        blocked = 1;\n                        break;\n                    }\n                } else if (prefix >= 0 && prefix <= 128) {\n                    int full = prefix / 8;\n                    int rem  = prefix % 8;\n                    if (full &&\n                        memcmp(ip6.s6_addr, net6.s6_addr, full) != 0)\n                        goto next;\n                    if (rem) {\n                        uint8_t mask = (uint8_t)(0xFF << (8 - rem));\n                        if ((ip6.s6_addr[full] & mask) !=\n                            (net6.s6_addr[full] & mask))\n                            goto next;\n                    }\n                    blocked = 1;\n                    break;\n                }\n            }\n        }\n    next:\n        token = strtok(NULL, \",\");\n    }\n    free(list);\n    return blocked;\n}\n\n// ------------------ 网络拦截 ------------------\nint connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {\n    RESOLVE_REAL(connect);\n    ensure_config_loaded();\n    if (is_sandbox_user() && addr->sa_family == AF_UNIX) {\n        struct sockaddr_un *un = (struct sockaddr_un *)addr;\n        throw_permission_denied_err(false, \"access unix socket: %s\", un->sun_path[0] ? un->sun_path : \"(abstract)\");\n        return -1;\n    }\n    char ip[INET6_ADDRSTRLEN] = {0};\n    if (addr->sa_family == AF_INET) {\n        inet_ntop(AF_INET,\n                  &((struct sockaddr_in *)addr)->sin_addr,\n                  ip, sizeof(ip));\n    } else if (addr->sa_family == AF_INET6) {\n        struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;\n        if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {\n            struct in_addr v4;\n            memcpy(&v4, &sin6->sin6_addr.s6_addr[12], sizeof(v4));\n            inet_ntop(AF_INET, &v4, ip, sizeof(ip));\n        } else {\n            inet_ntop(AF_INET6, &sin6->sin6_addr, ip, sizeof(ip));\n        }\n    }\n    if (is_sandbox_user() && match_banned_ip(ip, banned_hosts)) {\n        throw_permission_denied_err(false, \"access %s\", ip);\n        return -1;\n    }\n    return real_connect(sockfd, addr, addrlen);\n}\nint getaddrinfo(const char *node, const char *service,\n                const struct addrinfo *hints,\n                struct addrinfo **res) {\n    RESOLVE_REAL(getaddrinfo);\n    ensure_config_loaded();\n    if (node && is_sandbox_user()) {\n        struct in_addr ip4;\n        struct in6_addr ip6;\n        int is_ip = inet_pton(AF_INET, node, &ip4) == 1 ||\n                    inet_pton(AF_INET6, node, &ip6) == 1;\n        if (!is_ip && match_banned_domain(node, banned_hosts)) {\n            throw_permission_denied_err(false, \"access %s\", node);\n            return EAI_SYSTEM;\n        }\n    }\n    return real_getaddrinfo(node, service, hints, res);\n}\n/**\n * 限制创建子进程\n */\nstatic int allow_create_subprocess() {\n    ensure_config_loaded();\n    return allow_subprocess || !is_sandbox_user();\n}\nstatic int not_supported(const char *function_name) {\n    fprintf(stderr, \"Not supported function: %s\\n\", function_name);\n    _exit(126);\n    return -1;\n}\nint execv(const char *path, char *const argv[]) {\n    RESOLVE_REAL(execv);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_execv(path, argv);\n}\nint __execv(const char *path, char *const argv[]) {\n    return execv(path, argv);\n}\nint execve(const char *filename, char *const argv[], char *const envp[]) {\n    RESOLVE_REAL(execve);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_execve(filename, argv, envp);\n}\nint __execve(const char *filename, char *const argv[], char *const envp[]) {\n    return execve(filename, argv, envp);\n}\nint execveat(int dirfd, const char *pathname,\n             char *const argv[], char *const envp[], int flags) {\n    RESOLVE_REAL(execveat);\n    if (!allow_create_subprocess())  return throw_permission_denied_err(true, \"create subprocess\");\n    return real_execveat(dirfd, pathname, argv, envp, flags);\n}\nint execvpe(const char *file, char *const argv[], char *const envp[]) {\n    RESOLVE_REAL(execvpe);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_execvpe(file, argv, envp);\n}\nint __execvpe(const char *file, char *const argv[], char *const envp[]) {\n    return execvpe(file, argv, envp);\n}\nint execvp(const char *file, char *const argv[]) {\n    RESOLVE_REAL(execvp);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_execvp(file, argv);\n}\nint __execvp(const char *file, char *const argv[]) {\n    return execvp(file, argv);\n}\nint execl(const char *path, const char *arg, ...) {\n    return not_supported(\"execl\");\n}\nint __execl(const char *path, const char *arg, ...) {\n    return not_supported(\"__execl\");\n}\nint execlp(const char *file, const char *arg, ...) {\n    return not_supported(\"execlp\");\n}\nint __execlp(const char *file, const char *arg, ...) {\n    return not_supported(\"__execlp\");\n}\nint execle(const char *path, const char *arg, ...) {\n    return not_supported(\"execle\");\n}\npid_t fork(void) {\n    RESOLVE_REAL(fork);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_fork();\n}\npid_t __fork(void) {\n    return fork();\n}\npid_t vfork(void) {\n    RESOLVE_REAL(vfork);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_vfork();\n}\npid_t __vfork(void) {\n    return vfork();\n}\nint clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...) {\n    RESOLVE_REAL(clone);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    va_list ap;\n    va_start(ap, arg);\n    long a4 = va_arg(ap, long);\n    long a5 = va_arg(ap, long);\n    va_end(ap);\n    return real_clone(fn, child_stack, flags, arg, (void *)a4, (void *)a5);\n}\nint clone3(struct clone_args *cl_args, size_t size) {\n    RESOLVE_REAL(clone3);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_clone3(cl_args, size);\n}\nint posix_spawn(pid_t *pid, const char *path,\n                const posix_spawn_file_actions_t *file_actions,\n                const posix_spawnattr_t *attrp,\n                char *const argv[], char *const envp[]) {\n    RESOLVE_REAL(posix_spawn);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_posix_spawn(pid, path, file_actions, attrp, argv, envp);\n}\nint __posix_spawn(pid_t *pid, const char *path,\n                  const posix_spawn_file_actions_t *file_actions,\n                  const posix_spawnattr_t *attrp,\n                  char *const argv[], char *const envp[]) {\n    return posix_spawn(pid, path, file_actions, attrp, argv, envp);\n}\nint posix_spawnp(pid_t *pid, const char *file,\n                 const posix_spawn_file_actions_t *file_actions,\n                 const posix_spawnattr_t *attrp,\n                 char *const argv[], char *const envp[]) {\n    RESOLVE_REAL(posix_spawnp);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_posix_spawnp(pid, file, file_actions, attrp, argv, envp);\n}\nint __posix_spawnp(pid_t *pid, const char *file,\n                   const posix_spawn_file_actions_t *file_actions,\n                   const posix_spawnattr_t *attrp,\n                   char *const argv[], char *const envp[]) {\n    return posix_spawnp(pid, file, file_actions, attrp, argv, envp);\n}\nFILE *popen(const char *command, const char *type) {\n    RESOLVE_REAL(popen);\n    if (!allow_create_subprocess()) {\n        fprintf(stderr, \"Permission denied to create subprocess.\\n\");\n        errno = EACCES;\n        return NULL;\n    }\n    return real_popen(command, type);\n}\nFILE *__popen(const char *command, const char *type) {\n    return popen(command, type);\n}\nint system(const char *command) {\n    RESOLVE_REAL(system);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_system(command);\n}\nint __libc_system(const char *command) {\n    RESOLVE_REAL(__libc_system);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real___libc_system(command);\n}\npid_t __libc_clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...) {\n    RESOLVE_REAL(__libc_clone);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    va_list ap;\n    va_start(ap, arg);\n    long a4 = va_arg(ap, long);\n    long a5 = va_arg(ap, long);\n    va_end(ap);\n    return real___libc_clone(fn, child_stack, flags, arg, (void *)a4, (void *)a5);\n}\n\npid_t forkpty(int *amaster, char *name, const struct termios *termp, const struct winsize *winp) {\n    RESOLVE_REAL(forkpty);\n    if (!allow_create_subprocess()) return throw_permission_denied_err(true, \"create subprocess\");\n    return real_forkpty(amaster, name, termp, winp);\n}\npid_t __forkpty(int *amaster, char *name, const struct termios *termp, const struct winsize *winp) {\n    return forkpty(amaster, name, termp, winp);\n}\n/**\n * 限制调用syscall\n */\nstatic int allow_access_syscall() {\n    ensure_config_loaded();\n    return allow_syscall || !is_sandbox_user();\n}\nlong (*real_syscall)(long, ...) = NULL;\nlong syscall(long number, ...) {\n    RESOLVE_REAL(syscall);\n    va_list ap;\n    va_start(ap, number);\n    long a1 = va_arg(ap, long);\n    long a2 = va_arg(ap, long);\n    long a3 = va_arg(ap, long);\n    long a4 = va_arg(ap, long);\n    long a5 = va_arg(ap, long);\n    long a6 = va_arg(ap, long);\n    va_end(ap);\n    switch (number) {\n        case SYS_execve:\n        case SYS_execveat:\n#ifdef SYS_fork\n        case SYS_fork:\n#endif\n#ifdef SYS_vfork\n        case SYS_vfork:\n#endif\n        case SYS_clone:\n        case SYS_clone3:\n#ifdef SYS_posix_spawn\n        case SYS_posix_spawn:\n#endif\n#ifdef SYS_posix_spawnp\n        case SYS_posix_spawnp:\n#endif\n        case SYS_socket:\n        case SYS_connect:\n        case SYS_bind:\n        case SYS_listen:\n        case SYS_accept:\n        case SYS_accept4:\n        case SYS_sendto:\n        case SYS_recvmsg:\n        case SYS_getsockopt:\n        case SYS_setsockopt:\n        case SYS_ptrace:\n        case SYS_setuid:\n        case SYS_setgid:\n        case SYS_reboot:\n        case SYS_mount:\n#ifdef SYS_chown\n        case SYS_chown:\n#endif\n#ifdef SYS_chmod\n        case SYS_chmod:\n#endif\n        case SYS_fchmodat:\n        case SYS_mprotect:\n#ifdef SYS_open\n        case SYS_open:\n#endif\n        case SYS_openat:\n        case SYS_swapon:\n        case SYS_swapoff:\n        case SYS_kill:\n        case SYS_mmap:\n        case SYS_munmap:\n        case SYS_memfd_create:\n        case SYS_shmat:\n        case SYS_shmget:\n        case SYS_shmctl:\n        case SYS_prctl:\n            if (!allow_access_syscall()) {\n                throw_permission_denied_err(true, \"access syscall %ld\", number);\n             }\n    }\n    return real_syscall(number, a1, a2, a3, a4, a5, a6);\n}\n\n/**\n * 限制加载动态链接库\n */\nstatic int is_in_allow_dl_paths(const char *filename) {\n    if (!filename || !*filename) return 1;\n    ensure_config_loaded();\n    if (!allow_dl_paths || !*allow_dl_paths) return 0;\n    char real_file[PATH_MAX];\n    if (!realpath(filename, real_file)) return 0;\n    char *rules = strdup(allow_dl_paths);\n    if (!rules) return 0;\n    int allowed = 0;\n    char *saveptr = NULL;\n    for (char *token = strtok_r(rules, \",\", &saveptr); token; token = strtok_r(NULL, \",\", &saveptr)) {\n        while (*token == ' ' || *token == '\\t') token++;\n        if (!*token) continue;\n        char real_rule[PATH_MAX];\n        if (!realpath(token, real_rule)) continue;\n        size_t len = strlen(real_rule);\n        if (strncmp(real_file, real_rule, len) == 0 &&\n            (real_file[len] == '\\0' || real_file[len] == '/')) {\n            allowed = 1;\n            break;\n        }\n    }\n    free(rules);\n    return allowed;\n}\nvoid *dlopen(const char *filename, int flag) {\n    RESOLVE_REAL(dlopen);\n    if (is_sandbox_user() && !is_in_allow_dl_paths(filename)) {\n        throw_permission_denied_err(true, \"access file %s\", filename);\n    }\n    return real_dlopen(filename, flag);\n}\nvoid *__dlopen(const char *filename, int flag) {\n    return dlopen(filename, flag);\n}\nvoid *dlmopen(Lmid_t lmid, const char *filename, int flags) {\n    RESOLVE_REAL(dlmopen);\n    if (is_sandbox_user() && !is_in_allow_dl_paths(filename)) {\n        throw_permission_denied_err(true, \"access file %s\", filename);\n    }\n    return real_dlmopen(lmid, filename, flags);\n}\nvoid *__dlmopen(Lmid_t lmid, const char *filename, int flags) {\n    return dlmopen(lmid, filename, flags);\n}\nvoid* mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off) {\n    RESOLVE_REAL(mmap);\n    if (is_sandbox_user() && (prot & PROT_EXEC)) {\n        if ((flags & MAP_ANONYMOUS) || fd < 0) { //匿名映射：直接拒绝\n            throw_permission_denied_err(true, \"mmap(anonymous)\");\n        }\n        char link[64];\n        snprintf(link, sizeof(link), \"/proc/self/fd/%d\", fd);\n        char real_path[PATH_MAX];\n        ssize_t n = readlink(link, real_path, sizeof(real_path) - 1);\n        if (n < 0) {\n            throw_permission_denied_err(true, \"mmap(readlink failed)\");\n        }\n        real_path[n] = '\\0';\n        if (!is_in_allow_dl_paths(real_path)) {\n            throw_permission_denied_err(true, \"mmap %s\", real_path);\n        }\n    }\n    return real_mmap(addr, len, prot, flags, fd, off);\n}\nvoid* mmap64(void *addr, size_t len, int prot, int flags, int fd, off_t off) {\n    return mmap(addr, len, prot, flags, fd, off);\n}\nint mprotect(void *addr, size_t len, int prot) {\n    RESOLVE_REAL(mprotect);\n    if (is_sandbox_user() && (prot & PROT_EXEC)) {\n        throw_permission_denied_err(true, \"mprotect\");\n    }\n    return real_mprotect(addr, len, prot);\n}"
  },
  {
    "path": "installer/start-all.sh",
    "content": "#!/bin/bash\n\nset -e\n\nif [ -f \"/opt/maxkb/PG_VERSION\" ] || [ -f \"/var/lib/postgresql/data/PG_VERSION\" ]; then\n  # 如果是v1版本一键安装的的目录则退出\n  echo -e \"\\033[1;31mFATAL ERROR: Upgrade from v1 to v2 is not supported.\\033[0m\"\n  echo -e \"\\033[1;31mThe process will exit.\\033[0m\"\n  exit 1\nfi\n\nif [ \"$MAXKB_DB_HOST\" = \"127.0.0.1\" ]; then\n  echo -e \"\\033[1;32mPostgreSQL starting...\\033[0m\"\n  /usr/bin/start-postgres.sh &\n  postgres_pid=$!\n  sleep 5\n  wait-for-it 127.0.0.1:5432 --timeout=120 --strict -- echo -e \"\\033[1;32mPostgreSQL started.\\033[0m\"\nfi\n\nif [ \"$MAXKB_REDIS_HOST\" = \"127.0.0.1\" ]; then\n  echo -e \"\\033[1;32mRedis starting...\\033[0m\"\n  /usr/bin/start-redis.sh &\n  redis_pid=$!\n  sleep 5\n  wait-for-it 127.0.0.1:6379 --timeout=60 --strict -- echo -e \"\\033[1;32mRedis started.\\033[0m\"\nfi\n\necho -e \"\\033[1;32mMaxKB starting...\\033[0m\"\n/usr/bin/start-maxkb.sh &\nmaxkb_pid=$!\nsleep 10\nwait-for-it 127.0.0.1:8080 --timeout=180 --strict -- echo -e \"\\033[1;32mMaxKB started.\\033[0m\"\n\nwait -n\necho -e \"\\033[1;31mSystem is shutting down.\\033[0m\"\nkill $postgres_pid $redis_pid $maxkb_pid 2>/dev/null\nwait"
  },
  {
    "path": "installer/start-maxkb.sh",
    "content": "#!/bin/bash\n\nif [ ! -d /opt/maxkb/logs ]; then\n    mkdir -p /opt/maxkb/logs\nfi\nchmod -R 700 /opt/maxkb/logs\nif [ ! -d /opt/maxkb/local ]; then\n    mkdir -p /opt/maxkb/local\n    chmod 700 /opt/maxkb/local\nfi\nmkdir -p /opt/maxkb/python-packages\n\nrm -f /opt/maxkb-app/tmp/*\npython /opt/maxkb-app/main.py start"
  },
  {
    "path": "installer/start-postgres.sh",
    "content": "#!/bin/bash\n\nmkdir -p /opt/maxkb/data/postgresql\ndocker-entrypoint.sh postgres -c max_connections=${POSTGRES_MAX_CONNECTIONS}\n"
  },
  {
    "path": "installer/start-redis.sh",
    "content": "#!/bin/bash\n\nif [ ! -d /opt/maxkb/data/redis ]; then\n    mkdir -p /opt/maxkb/data/redis\n    chmod 700 /opt/maxkb/data/redis\nfi\nif [ ! -d /opt/maxkb/logs ]; then\n    mkdir -p /opt/maxkb/logs\n    chmod 700 /opt/maxkb/logs\nfi\nif [ ! -f /opt/maxkb/conf/redis.conf ]; then\n  mkdir -p /opt/maxkb/conf\n  touch /opt/maxkb/conf/redis.conf\n  chmod 700 /opt/maxkb/conf/redis.conf\n  cat <<EOF > /opt/maxkb/conf/redis.conf\nbind 0.0.0.0\nport 6379\ndatabases 16\nmaxmemory 1G\naof-use-rdb-preamble yes\nsave 30 1\nsave 10 10\nsave 5 20\ndbfilename dump.rdb\nrdbcompression yes\nappendonly yes\nappendfilename \"appendonly.aof\"\nappendfsync everysec\nauto-aof-rewrite-percentage 100\nauto-aof-rewrite-min-size 64mb\nmaxmemory-policy allkeys-lru\nloglevel warning\nlogfile /opt/maxkb/logs/redis.log\ndir /opt/maxkb/data/redis\nrequirepass ${REDIS_PASSWORD}\nEOF\nfi\n\nredis-server /opt/maxkb/conf/redis.conf"
  },
  {
    "path": "main.py",
    "content": "import argparse\nimport logging\nimport os\nimport sys\nimport time\n\nimport django\nfrom django.core import management\n\nBASE_DIR = os.path.dirname(os.path.abspath(__file__))\nAPP_DIR = os.path.join(BASE_DIR, 'apps')\n\nos.chdir(BASE_DIR)\nsys.path.insert(0, APP_DIR)\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"maxkb.settings\")\n\n\ndef collect_static():\n    \"\"\"\n     收集静态文件到指定目录\n     本项目主要是将前端vue/dist的前端项目放到静态目录下面\n    :return:\n    \"\"\"\n    logging.info(\"Collect static files\")\n    try:\n        management.call_command('collectstatic', '--no-input', '-c', verbosity=0, interactive=False)\n        logging.info(\"Collect static files done\")\n    except:\n        pass\n\n\ndef perform_db_migrate():\n    \"\"\"\n    初始化数据库表\n    \"\"\"\n    logging.info(\"Check database structure change ...\")\n    logging.info(\"Migrate model change to database ...\")\n    try:\n        management.call_command('migrate')\n    except Exception as e:\n        logging.error('Perform migrate failed, exit', exc_info=True)\n        sys.exit(11)\n\n\ndef start_services():\n    services = args.services if isinstance(args.services, list) else [args.services]\n    start_args = []\n    if args.daemon:\n        start_args.append('--daemon')\n    if args.force:\n        start_args.append('--force')\n    if args.worker:\n        start_args.extend(['--worker', str(args.worker)])\n    else:\n        worker = os.environ.get('MAXKB_CORE_WORKER')\n        if isinstance(worker, str) and worker.isdigit():\n            start_args.extend(['--worker', worker])\n\n    try:\n        management.call_command(action, *services, *start_args)\n    except KeyboardInterrupt:\n        logging.info('Cancel ...')\n        time.sleep(2)\n    except Exception as exc:\n        logging.error(\"Start service error {}: {}\".format(services, exc))\n        time.sleep(2)\n\n\ndef dev():\n    services = args.services if isinstance(args.services, list) else args.services\n    if services.__contains__('web'):\n        management.call_command('runserver', \"0.0.0.0:8080\")\n    elif services.__contains__('celery'):\n        management.call_command('celery', 'celery')\n    elif services.__contains__('local_model'):\n        from maxkb.const import CONFIG\n        bind = f'{CONFIG.get(\"LOCAL_MODEL_HOST\")}:{CONFIG.get(\"LOCAL_MODEL_PORT\")}'\n        management.call_command('runserver', bind)\n\n\nif __name__ == '__main__':\n    os.environ['HF_HOME'] = '/opt/maxkb-app/model/base'\n    os.environ['TMPDIR'] = '/opt/maxkb-app/tmp'\n    parser = argparse.ArgumentParser(\n        description=\"\"\"\n           qabot service control tools;\n\n           Example: \\r\\n\n\n           %(prog)s start all -d;\n           \"\"\"\n    )\n    parser.add_argument(\n        'action', type=str,\n        choices=(\"start\", \"dev\", \"upgrade_db\", \"collect_static\"),\n        help=\"Action to run\"\n    )\n    args, e = parser.parse_known_args()\n    parser.add_argument(\n        \"services\", type=str, default='all' if args.action == 'start' else 'web', nargs=\"*\",\n        choices=(\"all\", \"web\", \"task\") if args.action == 'start' else (\"web\", \"celery\", 'local_model'),\n        help=\"The service to start\",\n    )\n\n    parser.add_argument('-d', '--daemon', nargs=\"?\", const=True)\n    parser.add_argument('-w', '--worker', type=int, nargs=\"?\")\n    parser.add_argument('-f', '--force', nargs=\"?\", const=True)\n    args = parser.parse_args()\n    action = args.action\n    services = args.services if isinstance(args.services, list) else args.services\n    if services.__contains__('web'):\n        os.environ.setdefault('SERVER_NAME', 'web')\n    elif services.__contains__('local_model'):\n        os.environ.setdefault('SERVER_NAME', 'local_model')\n    django.setup()\n    if action == \"upgrade_db\":\n        perform_db_migrate()\n    elif action == \"collect_static\":\n        collect_static()\n    elif action == 'dev':\n        collect_static()\n        perform_db_migrate()\n        dev()\n    else:\n        collect_static()\n        perform_db_migrate()\n        start_services()\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"maxkb\"\nversion = \"2.0.0\"\ndescription = \"强大易用的开源企业级智能体平台\"\nauthors = [{ name = \"shaohuzhang1\", email = \"shaohu.zhang@fit2cloud.com\" }]\nrequires-python = \"~=3.11.0\"\nreadme = \"README.md\"\ndependencies = [\n    \"django==5.2.12\",\n    \"drf-spectacular[sidecar]==0.28.0\",\n    \"django-redis==6.0.0\",\n    \"django-db-connection-pool==1.2.6\",\n    \"django-mptt==0.17.0\",\n    \"psycopg[binary]==3.2.9\",\n    \"python-dotenv==1.1.1\",\n    \"uuid-utils==0.14.0\",\n    \"captcha==0.7.1\",\n    \"pytz==2025.2\",\n    \"psutil==7.0.0\",\n    \"cffi==1.17.1\",\n    \"beautifulsoup4==4.13.4\",\n    \"jieba==0.42.1\",\n    \"langchain==1.2.12\",\n    \"langchain-openai==1.1.11\",\n    \"langchain-anthropic==1.3.4\",\n    \"langchain-community==0.4.1\",\n    \"langchain-deepseek==1.0.1\",\n    \"langchain-google-genai==4.2.1\",\n    \"langchain-mcp-adapters==0.2.1\",\n    \"langchain-huggingface==1.2.1\",\n    \"langchain-ollama==1.0.1\",\n    \"langchain-aws==1.4.0\",\n    \"langgraph==1.1.1\",\n    \"deepagents==0.4.10\",\n    \"torch==2.8.0\",\n    \"numpy==1.26.4\",\n    \"sentence-transformers==5.0.0\",\n    \"qianfan==0.4.12.3\",\n    \"zhipuai==2.1.5.20250708\",\n    \"volcengine-python-sdk[ark]==4.0.5\",\n    \"boto3==1.42.46\",\n    \"tencentcloud-sdk-python==3.0.1420\",\n    \"xinference-client==1.7.1.post1\",\n    \"anthropic==0.79.0\",\n    \"dashscope==1.25.7\",\n    \"celery[sqlalchemy]==5.5.3\",\n    \"django-celery-beat==2.8.1\",\n    \"celery-once==3.0.1\",\n    \"django-apscheduler==0.7.0\",\n    \"html2text==2025.4.15\",\n    \"openpyxl==3.1.5\",\n    \"python-docx==1.2.0\",\n    \"xlrd==2.0.2\",\n    \"xlwt==1.3.0\",\n    \"pymupdf==1.26.3\",\n    \"pypdf==6.9.1\",\n    \"pydub==0.25.1\",\n    \"pysilk==0.0.1\",\n    \"gunicorn==23.0.0\",\n    \"python-daemon==3.1.2\",\n    \"websockets==15.0.1\",\n    \"pylint==3.3.7\",\n    \"cohere==5.17.0\",\n    \"jsonpath-ng==1.7.0\",\n]\n\n[tool.uv]\npackage = false\n\n[[tool.uv.index]]\nname = \"pytorch\"\nurl = \"https://download.pytorch.org/whl/cpu\"\nexplicit = true\n\n[[tool.uv.index]]\nname = \"macpytorch\"\nurl = \"https://download.pytorch.org/whl\"\nexplicit = true\n\n[tool.uv.sources]\ntorch = [\n    { index = \"pytorch\", marker = \"sys_platform == 'linux'\" },\n    { index = \"pytorch\", marker = \"sys_platform == 'win'\" },\n    { index = \"macpytorch\", marker = \"sys_platform == 'darwin'\" },\n]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\""
  },
  {
    "path": "ui/.editorconfig",
    "content": "[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]\ncharset = utf-8\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\nend_of_line = lf\nmax_line_length = 100\n"
  },
  {
    "path": "ui/.gitattributes",
    "content": "* text=auto eol=lf\n"
  },
  {
    "path": "ui/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n.DS_Store\ndist\ndist-ssr\ncoverage\n*.local\n\n/cypress/videos/\n/cypress/screenshots/\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n*.tsbuildinfo\n"
  },
  {
    "path": "ui/.prettierrc.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/prettierrc\",\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"printWidth\": 100\n}\n"
  },
  {
    "path": "ui/README.md",
    "content": "# ui\n\nThis template should help get you started developing with Vue 3 in Vite.\n\n## Recommended IDE Setup\n\n[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).\n\n## Type Support for `.vue` Imports in TS\n\nTypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.\n\n## Customize configuration\n\nSee [Vite Configuration Reference](https://vite.dev/config/).\n\n## Project Setup\n\n```sh\nnpm install\n```\n\n### Compile and Hot-Reload for Development\n\n```sh\nnpm run dev\n```\n\n### Type-Check, Compile and Minify for Production\n\n```sh\nnpm run build\n```\n\n### Lint with [ESLint](https://eslint.org/)\n\n```sh\nnpm run lint\n```\n"
  },
  {
    "path": "ui/admin.html",
    "content": "<!doctype html>\n<html lang=\"\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"./favicon.ico\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1.0,  maximum-scale=1.0, minimum-scale=1.0, user-scalable=no,\n  viewport-fit=cover\"\n    />\n    <base target=\"_blank\" />\n    <title>%VITE_APP_TITLE%</title>\n    <script>\n      ;((prefix) => {\n        window.MaxKB = {\n          prefix: '/admin',\n          chatPrefix: '/chat',\n        }\n      })()\n    </script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "ui/chat.html",
    "content": "<!doctype html>\n<html lang=\"\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"./favicon.ico\" />\n    <meta\n      name=\"viewport\"\n      content=\"width=device-width, initial-scale=1.0,  maximum-scale=1.0, minimum-scale=1.0, user-scalable=no,\n  viewport-fit=cover\"\n    />\n    <base target=\"_blank\" />\n    <title>%VITE_APP_TITLE%</title>\n    <script>\n      ;((prefix) => {\n        window.MaxKB = {\n          prefix: '/chat',\n        }\n      })()\n    </script>\n  </head>\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"/src/chat.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "ui/env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\ndeclare module 'katex'\ninterface Window {\n  sendMessage: ?((message: string, other_params_data: any) => void)\n  chatUserProfile: ?(() => any)\n  MaxKB: {\n    prefix: string\n    chatPrefix: string\n  }\n}\n"
  },
  {
    "path": "ui/eslint.config.ts",
    "content": "import { globalIgnores } from 'eslint/config'\nimport { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'\nimport pluginVue from 'eslint-plugin-vue'\nimport skipFormatting from '@vue/eslint-config-prettier/skip-formatting'\n\n// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:\n// import { configureVueProject } from '@vue/eslint-config-typescript'\n// configureVueProject({ scriptLangs: ['ts', 'tsx'] })\n// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup\n\nexport default defineConfigWithVueTs(\n  {\n    name: 'app/files-to-lint',\n    files: ['**/*.{ts,mts,tsx,vue}'],\n  },\n\n  globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),\n\n  pluginVue.configs['flat/essential'],\n  vueTsConfigs.recommended,\n  skipFormatting,\n  {\n    rules: {\n      'vue/multi-word-component-names': 'off',\n      '@typescript-eslint/no-explicit-any': 'off',\n      '@typescript-eslint/no-unused-vars': ['off'],\n    }\n  }\n)\n"
  },
  {
    "path": "ui/package.json",
    "content": "{\n  \"name\": \"ui\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"chat\": \"vite --mode chat\",\n    \"build\": \"run-p type-check \\\"build-only {@}\\\" --\",\n    \"build-chat\": \"run-p type-check \\\"build-only-chat {@}\\\" --\",\n    \"preview\": \"vite preview\",\n    \"build-only\": \"vite build\",\n    \"build-only-chat\": \"vite build --mode chat\",\n    \"type-check\": \"vue-tsc --build\",\n    \"lint\": \"eslint . --fix\",\n    \"format\": \"prettier --write src/\"\n  },\n  \"dependencies\": {\n    \"@antv/layout\": \"^0.3.1\",\n    \"@codemirror/lang-json\": \"^6.0.1\",\n    \"@codemirror/lang-python\": \"^6.2.1\",\n    \"@codemirror/theme-one-dark\": \"^6.1.2\",\n    \"@logicflow/core\": \"^1.2.27\",\n    \"@logicflow/extension\": \"^1.2.27\",\n    \"@vavt/cm-extension\": \"^1.9.1\",\n    \"@vueuse/core\": \"^13.3.0\",\n    \"axios\": \"^1.8.4\",\n    \"cron-validator\": \"^1.4.0\",\n    \"cropperjs\": \"^1.6.2\",\n    \"dingtalk-jsapi\": \"^3.1.0\",\n    \"echarts\": \"^5.6.0\",\n    \"el-table-infinite-scroll\": \"^3.0.8\",\n    \"element-plus\": \"^2.13.5\",\n    \"file-saver\": \"^2.0.5\",\n    \"highlight.js\": \"^11.11.1\",\n    \"html-to-image\": \"^1.11.13\",\n    \"html2canvas\": \"^1.4.1\",\n    \"jspdf\": \"^4.1.0\",\n    \"katex\": \"^0.16.10\",\n    \"marked\": \"^12.0.2\",\n    \"md-editor-v3\": \"^5.8.2\",\n    \"mermaid\": \"^11.12.0\",\n    \"moment\": \"^2.30.1\",\n    \"nanoid\": \"^5.1.5\",\n    \"node-forge\": \"^1.3.1\",\n    \"nprogress\": \"^0.2.0\",\n    \"pinia\": \"^3.0.1\",\n    \"recorder-core\": \"^1.3.25011100\",\n    \"sanitize-html\": \"^2.17.0\",\n    \"screenfull\": \"^6.0.2\",\n    \"sortablejs\": \"^1.15.6\",\n    \"svg2pdf.js\": \"^2.5.0\",\n    \"use-element-plus-theme\": \"^0.0.5\",\n    \"vite-plugin-html\": \"^3.2.2\",\n    \"vue\": \"^3.5.13\",\n    \"vue-clipboard3\": \"^2.0.0\",\n    \"vue-codemirror\": \"^6.1.1\",\n    \"vue-demi\": \"^0.14.10\",\n    \"vue-draggable-plus\": \"^0.6.0\",\n    \"vue-i18n\": \"^11.1.3\",\n    \"vue-router\": \"^4.5.0\",\n    \"vue3-menus\": \"^1.1.2\"\n  },\n  \"devDependencies\": {\n    \"@tsconfig/node22\": \"^22.0.1\",\n    \"@types/crypto-js\": \"^4.2.2\",\n    \"@types/file-saver\": \"^2.0.7\",\n    \"@types/node\": \"^22.14.0\",\n    \"@types/node-forge\": \"^1.3.14\",\n    \"@types/nprogress\": \"^0.2.3\",\n    \"@vitejs/plugin-vue\": \"^5.2.3\",\n    \"@vitejs/plugin-vue-jsx\": \"^4.1.2\",\n    \"@vue/eslint-config-prettier\": \"^10.2.0\",\n    \"@vue/eslint-config-typescript\": \"^14.5.0\",\n    \"@vue/tsconfig\": \"^0.7.0\",\n    \"eslint\": \"^9.22.0\",\n    \"eslint-plugin-vue\": \"~10.0.0\",\n    \"jiti\": \"^2.4.2\",\n    \"npm-run-all2\": \"^7.0.2\",\n    \"prettier\": \"3.5.3\",\n    \"sass\": \"^1.86.3\",\n    \"sass-loader\": \"^16.0.5\",\n    \"typescript\": \"~5.8.0\",\n    \"unplugin-vue-define-options\": \"^3.0.0-beta.8\",\n    \"vite\": \"^6.2.4\",\n    \"vite-plugin-vue-devtools\": \"^7.7.2\",\n    \"vue-tsc\": \"^2.2.8\"\n  }\n}\n"
  },
  {
    "path": "ui/public/tool/bochaai/detail.md",
    "content": "## 概述\n\n博查工具是一个支持自然语言搜索的 Web Search API，从近百亿网页和生态内容源中搜索高质量世界知识，包括新闻、图片、视频、百科、机酒、学术等。\n\n\n## 配置\n\n1. 获取API Key \n在[博查开放平台](https://open.bochaai.com/overview) 上申请 API 密钥。\n![API Key](/admin/tool/img/bocha_APIKey.jpg)\n2. 在函数库中配置\n在函数库的博查函数面板中，点击 … > 启动参数，填写 API 密钥，并启用该函数。   \n![启动参数](/admin/tool/img/bocha_setting.jpg)\n3. 在应用中使用\n在高级编排应用中，点击添加组件->函数库->博查，设置使用参数。\n![应用中使用](/admin/tool/img/bocha_app_used.jpg)\n"
  },
  {
    "path": "ui/public/tool/google_search/detail.md",
    "content": "## 概述\n\nGoogle 搜索工具是一个实时 API，可提取搜索引擎结果，提供来自 Google 的结构化数据。它支持各种搜索类型，包括 Web、图像、新闻和地图。\n\n## 配置\n\n1. 创建 Google Custom Search Engine\n在[Programmable Search Engine](https://programmablesearchengine.google.com/)中 添加 Search Engine\n![google 创建引擎](/admin/tool/img/google_AddSearchEngine.jpg)\n2. 获取cx参数\n进入添加的引擎详情中，在【基本】菜单中获取搜索引擎的ID，即cx。\n![google cx ](/admin/tool/img/google_cx.jpg)\n3. 获取 API Key\n打开 https://developers.google.com/custom-search/v1/overview?hl=zh-cn 获取API Key。\n![google API Key](/admin/tool/img/google_APIKey.jpg)\n4. 配置启动参数\n在Google 搜索函数的启动参数中填写配置以上参数，并启用该函数。\n![启动参数](/admin/tool/img/google_setting.jpg)\n5. 在应用中使用\n在高级编排应用中，点击添加组件->函数库->Google搜索，设置使用参数。\n![应用中使用](/admin/tool/img/google_app_used.jpg)\n"
  },
  {
    "path": "ui/public/tool/langsearch/detail.md",
    "content": "## 概述\n\nLangSearch 是一个提供免费Web Search API和Rerank API的服务，支持新闻、图像、视频等内容。它结合了关键词和向量进行混合搜索，以提高准确性。\n\n\n## 配置\n\n1. 获取API Key \n在[LangSearch](https://langsearch.com/overview) 上申请 API 密钥。\n![API Key](/admin/tool/img/langsearch_APIKey.jpg)\n2. 在函数库中配置\n在函数库的LangSearch函数面板中，点击 … > 启动参数，填写 API 密钥，并启用该函数。   \n![启动参数](/admin/tool/img/langsearch_setting.jpg)\n3. 在应用中使用\n在高级编排应用中，点击添加组件->函数库->LangSearch，设置使用参数。\n![应用中使用](/admin/tool/img/langsearch_app_used.jpg)\n "
  },
  {
    "path": "ui/public/tool/mysql/detail.md",
    "content": "## 概述\n\nMySQL查询是一个连接MySQL数据库执行SQL查询的工具。\n\n\n## 配置\n \n1. 在函数库中配置启动参数\n在函数库的MySQL函数面板中，点击 … > 启动参数，填写数据库连接参数，并启用该函数。   \n![启动参数](/admin/tool/img/MySQL_setting.jpg)\n2. 在应用中使用\n在高级编排应用中，点击添加组件->函数库->MySQL查询，设置查询内容。\n![应用中使用](/admin/tool/img/MySQL_app_used.jpg)\n "
  },
  {
    "path": "ui/public/tool/postgresql/detail.md",
    "content": "## 概述\n\nPostgreSQL查询是一个连接PostgreSQL数据库执行SQL查询的工具。\n\n\n## 配置\n \n1. 在函数库中配置启动参数\n在函数库的PostgreSQL函数面板中，点击 … > 启动参数，填写数据库连接参数，并启用该函数。   \n![启动参数](/admin/tool/img/PostgreSQL_setting.jpg)\n2. 在应用中使用\n在高级编排应用中，点击添加组件->函数库->PostgreSQL查询，设置查询内容。\n![应用中使用](/admin/tool/img/PostgreSQL_app_used.jpg)\n "
  },
  {
    "path": "ui/src/App.vue",
    "content": "<script setup lang=\"ts\"></script>\n\n<template>\n  <RouterView />\n</template>\n"
  },
  {
    "path": "ui/src/api/application/application-key.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, del, put} from '@/request/index'\nimport useStore from '@/stores'\nimport {type Ref} from 'vue'\n\nconst prefix: any = {_value: '/workspace/'}\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const {user} = useStore()\n    return this._value + user.getWorkspaceId() + '/application'\n  },\n})\n/**\n * API_KEY列表\n * @param 参数 application_id\n */\nconst getAPIKey: (application_id: string, current_page: number, page_size: number, params: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  application_id,\n  current_page,\n  page_size,\n  params,\n  loading,\n) => {\n  return get(`${prefix.value}/${application_id}/application_key/${current_page}/${page_size}`, params, loading)\n}\n\n/**\n * 新增API_KEY\n * @param 参数 application_id\n */\nconst postAPIKey: (application_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  application_id,\n  loading,\n) => {\n  return post(`${prefix.value}/${application_id}/application_key`, {}, undefined, loading)\n}\n\n/**\n * 删除API_KEY\n * @param 参数 application_id api_key_id\n */\nconst delAPIKey: (\n  application_id: string,\n  api_key_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (application_id, api_key_id, loading) => {\n  return del(\n    `${prefix.value}/${application_id}/application_key/${api_key_id}`,\n    undefined,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 修改API_KEY\n * @param 参数 application_id,api_key_id\n * data {\n *   is_active: boolean\n * }\n */\nconst putAPIKey: (\n  application_id: string,\n  api_key_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, api_key_id, data, loading) => {\n  return put(\n    `${prefix.value}/${application_id}/application_key/${api_key_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\nexport default {\n  getAPIKey,\n  postAPIKey,\n  delAPIKey,\n  putAPIKey,\n}\n"
  },
  {
    "path": "ui/src/api/application/application.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, postStream, del, put, request, download, exportFile } from '@/request/index'\nimport type { pageRequest } from '@/api/type/common'\nimport type { ApplicationFormType } from '@/api/type/application'\nimport { type Ref } from 'vue'\nimport useStore from '@/stores'\n\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId() + '/application'\n  },\n})\n/**\n * 获取全部应用\n * @param param\n * @param loading\n */\nconst getAllApplication: (param?: any, loading?: Ref<boolean>) => Promise<Result<any[]>> = (\n  param,\n  loading,\n) => {\n  return get(`${prefix.value}`, param, loading)\n}\n\n/**\n * 获取分页应用\n * param {\n \"name\": \"string\",\n }\n */\nconst getApplication: (\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (page, param, loading) => {\n  return get(`${prefix.value}/${page.current_page}/${page.page_size}`, param, loading)\n}\n\n/**\n * 创建应用\n * @param data\n * @param loading\n */\nconst postApplication: (\n  data: ApplicationFormType,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (data, loading) => {\n  return post(`${prefix.value}`, data, undefined, loading)\n}\n\n/**\n * 修改应用\n * @param application_id\n * @param data\n * @param loading\n */\nconst putApplication: (\n  application_id: string,\n  data: ApplicationFormType,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return put(`${prefix.value}/${application_id}`, data, undefined, loading)\n}\n/**\n * 移动应用\n * @param application_id\n * @param folder_id\n * @param loading\n * @returns\n */\nconst moveApplication: (\n  application_id: string,\n  folder_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, folder_id, loading) => {\n  return put(`${prefix.value}/${application_id}/move/${folder_id}`, {}, undefined, loading)\n}\n\n/**\n * 删除应用\n * @param application_id\n * @param loading\n */\nconst delApplication: (\n  application_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (application_id, loading) => {\n  return del(`${prefix.value}/${application_id}`, undefined, {}, loading)\n}\n\n/**\n * 应用详情\n * @param application_id\n * @param loading\n */\nconst getApplicationDetail: (\n  application_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, loading) => {\n  return get(`${prefix.value}/${application_id}`, undefined, loading)\n}\n\n/**\n * 获取AccessToken\n * @param application_id\n * @param loading\n */\nconst getAccessToken: (application_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  application_id,\n  loading,\n) => {\n  return get(`${prefix.value}/${application_id}/access_token`, undefined, loading)\n}\n/**\n * 获取应用设置\n * @param application_id 应用id\n * @param loading 加载器\n * @returns\n */\nconst getApplicationSetting: (\n  application_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, loading) => {\n  return get(`${prefix.value}/${application_id}/setting`, undefined, loading)\n}\n\n/**\n * 修改AccessToken\n * data {\n *  \"is_active\": true\n * }\n * @param application_id\n * @param data\n * @param loading\n */\nconst putAccessToken: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return put(`${prefix.value}/${application_id}/access_token`, data, undefined, loading)\n}\n\n/**\n * 替换社区版-修改AccessToken\n * data {\n *  \"show_source\": boolean,\n *  \"show_history\": boolean,\n *  \"draggable\": boolean,\n *  \"show_guide\": boolean,\n *  \"avatar\": file,\n *  \"float_icon\": file,\n * }\n * @param application_id\n * @param data\n * @param loading\n */\nconst putXpackAccessToken: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return put(`${prefix.value}/${application_id}/setting`, data, undefined, loading)\n}\n\n/**\n * 导出应用\n */\n\nconst exportApplication = (\n  application_id: string,\n  application_name: string,\n  loading?: Ref<boolean>,\n) => {\n  return exportFile(\n    application_name + '.mk',\n    `${prefix.value}/${application_id}/export`,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 导入应用\n */\nconst importApplication: (\n  folder_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (folder_id, data, loading) => {\n  return post(`${prefix.value}/folder/${folder_id}/import`, data, undefined, loading)\n}\n\n/**\n * 统计\n * @param application_id\n * @param data\n * @param loading\n */\nconst getStatistics: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return get(`${prefix.value}/${application_id}/application_stats`, data, loading)\n}\n/**\n * 统计token消耗\n */\nconst getTokenUsage: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return get(`${prefix.value}/${application_id}/application_token_usage`, data, loading)\n}\n/**\n * 统计提问次数\n */\nconst topQuestions: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return get(`${prefix.value}/${application_id}/top_questions`, data, loading)\n}\n/**\n * 打开调试对话id\n * @param application_id 应用id\n * @param loading 加载器\n * @returns\n */\nconst open: (application_id: string, loading?: Ref<boolean>) => Promise<Result<string>> = (\n  application_id,\n  loading,\n) => {\n  return get(`${prefix.value}/${application_id}/open`, {}, loading)\n}\n\n/**\n * 生成提示词\n * @param workspace_id\n * @param model_id\n * @param application_id\n * @param data\n * @returns\n */\nconst generate_prompt: (\n  workspace_id: string,\n  model_id: string,\n  application_id: string,\n  data: any,\n) => Promise<any> = (workspace_id, model_id, application_id, data) => {\n  const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api'\n  return postStream(\n    `${prefix}/workspace/${workspace_id}/application/${application_id}/model/${model_id}/prompt_generate`,\n    data,\n  )\n}\n\n/**\n * 对话\n * chat_id: string\n * data\n * @param chat_id\n * @param data\n */\nconst chat: (chat_id: string, data: any) => Promise<any> = (chat_id, data) => {\n  const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api'\n  return postStream(`${prefix}/chat_message/${chat_id}`, data)\n}\n/**\n * 获取对话用户认证类型\n * @param loading 加载器\n * @returns\n */\nconst getChatUserAuthType: (loading?: Ref<boolean>) => Promise<any> = (loading) => {\n  return get(`/chat_user/auth/types`, {}, loading)\n}\n\n/**\n * 获取平台状态\n */\nconst getPlatformStatus: (application_id: string) => Promise<Result<any>> = (application_id) => {\n  return get(`${prefix.value}/${application_id}/platform/status`)\n}\n/**\n * 更新平台状态\n */\nconst updatePlatformStatus: (application_id: string, data: any) => Promise<Result<any>> = (\n  application_id,\n  data,\n) => {\n  return post(`${prefix.value}/${application_id}/platform/status`, data)\n}\n/**\n * 获取平台配置\n */\nconst getPlatformConfig: (application_id: string, type: string) => Promise<Result<any>> = (\n  application_id,\n  type,\n) => {\n  return get(`${prefix.value}/${application_id}/platform/${type}`)\n}\n/**\n * 更新平台配置\n */\nconst updatePlatformConfig: (\n  application_id: string,\n  type: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, type, data, loading) => {\n  return post(`${prefix.value}/${application_id}/platform/${type}`, data, undefined, loading)\n}\n/**\n * 应用发布\n * @param application_id\n * @param data\n * @param loading\n * @returns\n */\nconst publish: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return put(`${prefix.value}/${application_id}/publish`, data, {}, loading)\n}\n\n/**\n *\n * @param application_id\n * @param data\n * @param loading\n * @returns\n */\nconst playDemoText: (application_id: string, data: any, loading?: Ref<boolean>) => Promise<any> = (\n  application_id,\n  data,\n  loading,\n) => {\n  return download(\n    `${prefix.value}/${application_id}/play_demo_text`,\n    'post',\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 文本转语音\n */\nconst postTextToSpeech: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return download(\n    `${prefix.value}/${application_id}/text_to_speech`,\n    'post',\n    data,\n    undefined,\n    loading,\n  )\n}\n/**\n * 语音转文本\n */\nconst speechToText: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return post(`${prefix.value}/${application_id}/speech_to_text`, data, undefined, loading)\n}\n\n/**\n * mcp 节点\n */\nconst getMcpTools: (\n  application_id: string,\n  mcp_servers: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, mcp_servers, loading) => {\n  return post(`${prefix.value}/${application_id}/mcp_tools`, { mcp_servers }, {}, loading)\n}\n\n/**\n * 上传文件\n * @param file\n * @param sourceId\n * @param resourceType\n * @param loading\n */\nconst postUploadFile: (\n  file: any,\n  sourceId: string,\n  resourceType:\n    | 'KNOWLEDGE'\n    | 'APPLICATION'\n    | 'TOOL'\n    | 'DOCUMENT'\n    | 'CHAT'\n    | 'TEMPORARY_30_MINUTE'\n    | 'TEMPORARY_120_MINUTE'\n    | 'TEMPORARY_1_DAY',\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (file, sourceId, resourceType, loading) => {\n  const fd = new FormData()\n  fd.append('file', file)\n  fd.append('source_id', sourceId)\n  fd.append('source_type', resourceType)\n  return post(`/oss/file`, fd, undefined, loading)\n}\n\nconst getFile: (application_id: string, params: any) => Promise<Result<any>> = (\n  application_id,\n  params,\n) => {\n  return get(`/oss/get_url/${application_id}`, params)\n}\nexport default {\n  getAllApplication,\n  getApplication,\n  postApplication,\n  putApplication,\n  delApplication,\n  getApplicationDetail,\n  getAccessToken,\n  putAccessToken,\n  putXpackAccessToken,\n  exportApplication,\n  importApplication,\n  getStatistics,\n  open,\n  chat,\n  getChatUserAuthType,\n  getApplicationSetting,\n  getPlatformStatus,\n  updatePlatformStatus,\n  getPlatformConfig,\n  publish,\n  updatePlatformConfig,\n  playDemoText,\n  postTextToSpeech,\n  speechToText,\n  getMcpTools,\n  postUploadFile,\n  generate_prompt,\n  getTokenUsage,\n  topQuestions,\n  getFile,\n  moveApplication,\n}\n"
  },
  {
    "path": "ui/src/api/application/chat-log.ts",
    "content": "import {Result} from '@/request/Result'\nimport {\n  get,\n  post,\n  exportExcelPost,\n  del,\n  put,\n} from '@/request/index'\nimport type {pageRequest} from '@/api/type/common'\nimport {type Ref} from 'vue'\nimport useStore from '@/stores'\n\nconst prefix: any = {_value: '/workspace/'}\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const {user} = useStore()\n    return this._value + user.getWorkspaceId() + '/application'\n  },\n})\n/**\n * 对话记录提交至知识库\n * @param data\n * @param loading\n * @param application_id\n * @param knowledge_id\n */\n\nconst postChatLogAddKnowledge: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return post(`${prefix.value}/${application_id}/add_knowledge`, data, undefined, loading)\n}\n\n/**\n * 对话日志\n * @param 参数\n * application_id\n * param  {\n \"start_time\": \"string\",\n \"end_time\": \"string\",\n }\n */\nconst getChatLog: (\n  application_id: String,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, page, param, loading) => {\n  return get(\n    `${prefix.value}/${application_id}/chat/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 获得对话日志记录\n * @param 参数\n * application_id, chart_id,order_asc\n */\nconst getChatRecordLog: (\n  application_id: String,\n  chart_id: String,\n  page: pageRequest,\n  loading?: Ref<boolean>,\n  order_asc?: boolean,\n) => Promise<Result<any>> = (application_id, chart_id, page, loading, order_asc) => {\n  return get(\n    `${prefix.value}/${application_id}/chat/${chart_id}/chat_record/${page.current_page}/${page.page_size}`,\n    {order_asc: order_asc !== undefined ? order_asc : true},\n    loading,\n  )\n}\n\n/**\n * 获取标注段落列表信息\n * @param 参数\n * application_id, chart_id,  chart_record_id\n */\nconst getMarkChatRecord: (\n  application_id: string,\n  chart_id: string,\n  chart_record_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  application_id,\n  chart_id,\n  chart_record_id,\n  loading,\n) => {\n  return get(\n    `${prefix.value}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/improve`,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 修改日志记录内容\n * @param 参数\n * application_id, chart_id,  chart_record_id, knowledge_id, document_id\n * data {\n \"title\": \"string\",\n \"content\": \"string\",\n \"problem_text\": \"string\"\n }\n */\nconst putChatRecordLog: (\n  application_id: String,\n  chart_id: String,\n  chart_record_id: String,\n  knowledge_id: String,\n  document_id: String,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  application_id,\n  chart_id,\n  chart_record_id,\n  knowledge_id,\n  document_id,\n  data,\n  loading,\n) => {\n  return put(\n    `${prefix.value}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/knowledge/${knowledge_id}/document/${document_id}/improve`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 删除标注\n * @param 参数\n * application_id, chart_id,  chart_record_id, knowledge_id, document_id,paragraph_id\n */\nconst delMarkChatRecord: (\n  application_id: String,\n  chart_id: String,\n  chart_record_id: String,\n  knowledge_id: String,\n  document_id: String,\n  paragraph_id: String,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  application_id,\n  chart_id,\n  chart_record_id,\n  knowledge_id,\n  document_id,\n  paragraph_id,\n  loading,\n) => {\n  return del(\n    `${prefix.value}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/knowledge/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/improve`,\n    undefined,\n    {},\n    loading,\n  )\n}\n\n/**\n * 导出对话日志\n * @param 参数\n * application_id\n * param  {\n \"start_time\": \"string\",\n \"end_time\": \"string\",\n }\n */\nconst postExportChatLog: (\n  application_id: string,\n  application_name: string,\n  param: any,\n  data: any,\n  loading?: Ref<boolean>,\n) => void = (application_id, application_name, param, data, loading) => {\n  exportExcelPost(\n    application_name + '.xlsx',\n    `${prefix.value}/${application_id}/chat/export`,\n    param,\n    data,\n    loading,\n  )\n}\nconst getChatRecordDetails: (\n  application_id: string,\n  chat_id: string,\n  chat_record_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (application_id, chat_id, chat_record_id, loading) => {\n  return get(\n    `${prefix.value}/${application_id}/chat/${chat_id}/chat_record/${chat_record_id}`,\n    {},\n    loading,\n  )\n}\nexport default {\n  postChatLogAddKnowledge,\n  getChatLog,\n  getChatRecordLog,\n  getMarkChatRecord,\n  putChatRecordLog,\n  delMarkChatRecord,\n  postExportChatLog,\n  getChatRecordDetails,\n}\n"
  },
  {
    "path": "ui/src/api/application/workflow-version.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, put } from '@/request/index'\nimport { type Ref } from 'vue'\n\nimport useStore from '@/stores'\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId() + '/application'\n  },\n})\n\n/**\n * workflow历史版本\n */\nconst getWorkFlowVersion: (\n  application_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, loading) => {\n  return get(`${prefix.value}/${application_id}/application_version`, undefined, loading)\n}\n\n/**\n * workflow历史版本详情\n */\nconst getWorkFlowVersionDetail: (\n  application_id: string,\n  application_version_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, application_version_id, loading) => {\n  return get(\n    `${prefix.value}/${application_id}/application_version/${application_version_id}`,\n    undefined,\n    loading,\n  )\n}\n/**\n * 修改workflow历史版本\n */\nconst putWorkFlowVersion: (\n  application_id: string,\n  application_version_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, application_version_id, data, loading) => {\n  return put(\n    `${prefix.value}/${application_id}/application_version/${application_version_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\nexport default {\n  getWorkFlowVersion,\n  getWorkFlowVersionDetail,\n  putWorkFlowVersion,\n}\n"
  },
  {
    "path": "ui/src/api/chat/chat.ts",
    "content": "import { Result } from '@/request/Result'\nimport {\n  get,\n  post,\n  postStream,\n  del,\n  put,\n  request,\n  download,\n  exportFile,\n} from '@/request/chat/index'\nimport { type ChatProfile } from '@/api/type/chat'\nimport { type Ref } from 'vue'\nimport type { ResetPasswordRequest } from '@/api/type/user.ts'\n\nimport useStore from '@/stores'\nimport type { LoginRequest } from '@/api/type/user'\n\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId() + '/application'\n  },\n})\n\n/**\n * 打开调试对话id\n * @param application_id 应用id\n * @param loading 加载器\n * @returns\n */\nconst open: (loading?: Ref<boolean>) => Promise<Result<string>> = (loading) => {\n  return get('/open', {}, loading)\n}\n/**\n * 对话\n * @param 参数\n * chat_id: string\n * data\n */\nconst chat: (chat_id: string, data: any) => Promise<any> = (chat_id, data) => {\n  const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/chat') + '/api'\n  return postStream(`${prefix}/chat_message/${chat_id}`, data)\n}\n\n/**\n * 应用认证信息\n */\nconst chatProfile: (assessToken: string, loading?: Ref<boolean>) => Promise<Result<ChatProfile>> = (\n  assessToken,\n  loading,\n) => {\n  return get('/profile', { access_token: assessToken }, loading)\n}\n/**\n * 匿名认证\n * @param assessToken\n * @param loading\n * @returns\n */\nconst anonymousAuthentication: (\n  assessToken: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (assessToken, loading) => {\n  return post('/auth/anonymous', { access_token: assessToken }, {}, loading)\n}\n/**\n * 密码认证\n * @param assessToken\n * @param password\n * @param loading\n * @returns\n */\nconst passwordAuthentication: (\n  assessToken: string,\n  password: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (assessToken, password, loading) => {\n  return post('auth/password', { access_token: assessToken, password: password }, {}, loading)\n}\n/**\n * 获取应用相关信息\n * @param loading\n * @returns\n */\nconst applicationProfile: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get('/application/profile', {}, loading)\n}\n\n/**\n * 登录\n * @param request 登录接口请求表单\n * @param loading 接口加载器\n * @returns 认证数据\n */\nconst login: (\n  accessToken: string,\n  request: LoginRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (accessToken: string, request, loading) => {\n  return post('/auth/login/' + accessToken, request, undefined, loading)\n}\n\nconst ldapLogin: (\n  accessToken: string,\n  request: LoginRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (accessToken: string, request, loading) => {\n  return post('/auth/ldap/login/' + accessToken, request, undefined, loading)\n}\n\n/**\n * 获取验证码\n * @param username\n * @param loading 接口加载器\n */\nconst getCaptcha: (\n  username?: string,\n  accessToken?: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (username, accessToken, loading) => {\n  return get('/captcha', { username: username, accessToken: accessToken }, loading)\n}\n\n/**\n * 获取二维码类型\n */\nconst getQrType: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get('auth/qr_type', undefined, loading)\n}\n\nconst getQrSource: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get('auth/qr_type/source', undefined, loading)\n}\n\nconst getDingCallback: (\n  code: string,\n  accessToken: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (code, accessToken, loading) => {\n  return get('auth/dingtalk', { code, accessToken: accessToken }, loading)\n}\n\nconst getDingOauth2Callback: (\n  code: string,\n  accessToken: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (code, accessToken, loading) => {\n  return get('auth/dingtalk/oauth2', { code, accessToken: accessToken }, loading)\n}\n\nconst getWecomCallback: (\n  code: string,\n  accessToken: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (code, accessToken, loading) => {\n  return get('auth/wecom', { code, accessToken: accessToken }, loading)\n}\nconst getLarkCallback: (\n  code: string,\n  accessToken: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (code, accessToken, loading) => {\n  return get('auth/lark/oauth2', { code, accessToken: accessToken }, loading)\n}\n\n/**\n * 获取认证设置\n */\nconst getAuthSetting: (auth_type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  auth_type,\n  loading,\n) => {\n  return get(`/chat_user/${auth_type}/detail`, undefined, loading)\n}\n/**\n * 点赞点踩\n * @param chat_id         对话id\n * @param chat_record_id  对话记录id\n * @param vote_status     点赞状态\n * @param loading         加载器\n * @returns\n */\nconst vote: (\n  chat_id: string,\n  chat_record_id: string,\n  vote_status: string,\n  vote_reason?: string,\n  vote_other_content?: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (\n  chat_id,\n  chat_record_id,\n  vote_status,\n  vote_reason,\n  vote_other_content,\n  loading,\n) => {\n  const data = {\n    vote_status,\n    ...(vote_reason !== undefined && { vote_reason }),\n    ...(vote_other_content !== undefined && { vote_other_content }),\n  }\n  return put(`/vote/chat/${chat_id}/chat_record/${chat_record_id}`, data, undefined, loading)\n}\nconst pageChat: (\n  current_page: number,\n  page_size: number,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (current_page, page_size, loading) => {\n  return get(`/historical_conversation/${current_page}/${page_size}`, undefined, loading)\n}\nconst pageChatRecord: (\n  chat_id: string,\n  current_page: number,\n  page_size: number,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (chat_id, current_page, page_size, loading) => {\n  return get(\n    `/historical_conversation_record/${chat_id}/${current_page}/${page_size}`,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 登出\n */\nconst logout: (loading?: Ref<boolean>) => Promise<Result<boolean>> = (loading) => {\n  return post('/auth/logout', undefined, undefined, loading)\n}\n\n/**\n * 重置密码\n */\nconst resetCurrentPassword: (\n  request: ResetPasswordRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (request, loading) => {\n  return post('/chat_user/current/reset_password', request, undefined, loading)\n}\n\n/**\n * 获取当前用户信息\n */\nconst getChatUserProfile: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get('/chat_user/profile', {}, loading)\n}\n/**\n * 获取对话详情\n * @param chat_id         对话id\n * @param chat_record_id  对话记录id\n * @param loading         加载器\n * @returns\n */\nconst getChatRecord: (\n  chat_id: string,\n  chat_record_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (chat_id, chat_record_id, loading) => {\n  return get(`historical_conversation/${chat_id}/record/${chat_record_id}`, {}, loading)\n}\n/**\n * 文本转语音\n */\nconst textToSpeech: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return download(`text_to_speech`, 'post', data, undefined, loading)\n}\n\n/**\n * 语音转文本\n */\nconst speechToText: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`speech_to_text`, data, undefined, loading)\n}\n/**\n *\n * @param chat_id  对话ID\n * @param loading\n * @returns\n */\nconst deleteChat: (chat_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  chat_id,\n  loading,\n) => {\n  return del(`historical_conversation/${chat_id}`, undefined, undefined, loading)\n}\n/**\n *\n * @param loading\n * @returns\n */\nconst clearChat: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return del(`historical_conversation/clear`, undefined, undefined, loading)\n}\n/**\n *\n * @param chat_id 对话id\n * @param data    对话简介\n * @param loading\n * @returns\n */\nconst modifyChat: (chat_id: string, data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  chat_id,\n  data,\n  loading,\n) => {\n  return put(`historical_conversation/${chat_id}`, data, undefined, loading)\n}\n/**\n * 上传文件\n * @param file      文件\n * @param sourceId  资源id\n * @param resourceType  资源类型\n * @returns\n */\nconst postUploadFile: (\n  file: any,\n  sourceId: string,\n  resourceType:\n    | 'KNOWLEDGE'\n    | 'APPLICATION'\n    | 'TOOL'\n    | 'DOCUMENT'\n    | 'CHAT'\n    | 'TEMPORARY_30_MINUTE'\n    | 'TEMPORARY_120_MINUTE'\n    | 'TEMPORARY_1_DAY',\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (file, sourceId, sourceType, loading) => {\n  const fd = new FormData()\n  fd.append('file', file)\n  fd.append('source_id', sourceId)\n  fd.append('source_type', sourceType)\n  return post(`/oss/file`, fd, undefined, loading)\n}\n\nconst getFile: (application_id: string, params: any) => Promise<Result<any>> = (\n  application_id,\n  params,\n) => {\n  return get(`/oss/get_url/${application_id}`, params)\n}\n\n/**\n * 生成分享链接\n * @param 参数\n * chat_id: string\n * data\n */\nconst postShareChat: (\n  application_id: string,\n  chat_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<any> = (application_id, chat_id, data, loading) => {\n  return post(`/${application_id}/chat/${chat_id}/share_chat`, data, undefined, loading)\n}\n\nconst getShareLink: (link: string) => Promise<Result<any>> = (link) => {\n  return get(`/share/${link}`, undefined)\n}\n\nexport default {\n  open,\n  chat,\n  chatProfile,\n  anonymousAuthentication,\n  applicationProfile,\n  login,\n  getCaptcha,\n  getDingCallback,\n  getQrType,\n  getWecomCallback,\n  getDingOauth2Callback,\n  getLarkCallback,\n  getQrSource,\n  ldapLogin,\n  getAuthSetting,\n  passwordAuthentication,\n  vote,\n  pageChat,\n  pageChatRecord,\n  logout,\n  resetCurrentPassword,\n  getChatUserProfile,\n  getChatRecord,\n  textToSpeech,\n  speechToText,\n  deleteChat,\n  clearChat,\n  modifyChat,\n  postUploadFile,\n  getFile,\n  postShareChat,\n  getShareLink,\n}\n"
  },
  {
    "path": "ui/src/api/chat-user/auth-setting.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, put} from '@/request/index'\nimport {type Ref} from 'vue'\n\nconst prefix = '/chat_user/auth'\n/**\n * 获取认证设置\n */\nconst getAuthSetting: (auth_type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (auth_type, loading) => {\n  return get(`${prefix}/${auth_type}/detail`, undefined, loading)\n}\n\n/**\n * ldap连接测试\n */\nconst postAuthSetting: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return post(`${prefix}/connection`, data, undefined, loading)\n}\n\n/**\n * 修改邮箱设置\n */\nconst putAuthSetting: (auth_type: string, data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  auth_type,\n  data,\n  loading\n) => {\n  return put(`${prefix}/${auth_type}/info`, data, undefined, loading)\n}\n\nconst platformPrefix = '/chat_user/auth/platform'\nconst getPlatformInfo: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get(`${platformPrefix}/source`, undefined, loading)\n}\n\nconst updateConfig: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return post(`${platformPrefix}/source`, data, undefined, loading)\n}\n\nconst validateConnection: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return put(`${platformPrefix}/source`, data, undefined, loading)\n}\n\nexport default {\n  getAuthSetting,\n  postAuthSetting,\n  putAuthSetting,\n  getPlatformInfo,\n  updateConfig,\n  validateConnection\n}\n"
  },
  {
    "path": "ui/src/api/chat-user/chat-user.ts",
    "content": "import type { Ref } from 'vue'\nimport { Result } from '@/request/Result'\nimport { get, put } from '@/request/index'\nimport type { ChatUserGroupItem, ChatUserGroupUserItem, putUserGroupUserParams } from '@/api/type/workspaceChatUser'\nimport type { pageRequest, PageList } from '@/api/type/common'\n\nimport useStore from '@/stores'\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId()\n  },\n})\n/**\n * 获取用户组列表\n */\nconst getUserGroupList: (resource: any, loading?: Ref<boolean>) => Promise<Result<ChatUserGroupItem[]>> = (resource, loading) => {\n  return get(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group`, undefined, loading)\n}\n\n/**\n * 修改用户组列表授权\n */\nconst editUserGroupList: (resource: any, data: { user_group_id: string, is_auth: boolean }[], loading?: Ref<boolean>) => Promise<Result<any>> = (resource, data, loading) => {\n  return put(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group`, data, undefined, loading)\n}\n\n/**\n * 获取用户组的用户列表\n */\nconst getUserGroupUserList: (\n  resource: any,\n  user_group_id: string,\n  page: pageRequest,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<PageList<ChatUserGroupUserItem[]>>> = (resource, user_group_id, page, params, loading) => {\n  return get(\n    `${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}/${page.current_page}/${page.page_size}`,\n    params,\n    loading,\n  )\n}\n\n/**\n * 更新用户组的用户列表\n */\nconst putUserGroupUser: (\n  resource: any,\n  user_group_id: string,\n  data: putUserGroupUserParams[],\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (resource, user_group_id, data, loading) => {\n  return put(`${prefix.value}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}`, data, undefined, loading)\n}\n\nexport default {\n  getUserGroupList,\n  editUserGroupList,\n  getUserGroupUserList,\n  putUserGroupUser\n}\n"
  },
  {
    "path": "ui/src/api/image.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, del, put} from '@/request/index'\n\nconst prefix = '/oss/file'\n/**\n * 上传图片\n * @param 参数  file:file\n */\nconst postImage: (data: any) => Promise<Result<any>> = (data) => {\n  return post(`${prefix}`, data)\n}\n\nexport default {\n  postImage\n}\n"
  },
  {
    "path": "ui/src/api/knowledge/document.ts",
    "content": "import { Result } from '@/request/Result'\nimport {\n  del,\n  exportExcel,\n  exportExcelPost,\n  exportFile,\n  exportFilePost,\n  get,\n  post,\n  put\n} from '@/request/index'\nimport type { Ref } from 'vue'\nimport type { KeyValue, pageRequest } from '@/api/type/common'\n\nimport useStore from '@/stores'\n\nconst prefix: any = {_value: '/workspace/'}\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const {user} = useStore()\n    return this._value + user.getWorkspaceId() + '/knowledge'\n  },\n})\n\n/**\n * 文档列表（无分页）\n * @param 参数  knowledge_id,\n * param {\n \"   name\": \"string\",\n }\n */\n\nconst getDocumentList: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id,\n  loading,\n) => {\n  return get(`${prefix.value}/${knowledge_id}/document`, undefined, loading)\n}\n\n/**\n * 文档分页列表\n * @param 参数  knowledge_id,\n * param {\n \"name\": \"string\",\n folder_id: \"string\",\n }\n */\n\nconst getDocumentPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, page, param, loading) => {\n  return get(\n    `${prefix.value}/${knowledge_id}/document/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 文档详情\n * @param 参数 knowledge_id\n */\nconst getDocumentDetail: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, loading) => {\n  return get(`${prefix.value}/${knowledge_id}/document/${document_id}`,\n    {},\n    loading,)\n}\n\n/**\n * 修改文档\n * @param 参数\n * knowledge_id, document_id,\n * {\n \"name\": \"string\",\n \"is_active\": true,\n \"meta\": {}\n }\n */\nconst putDocument: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data: any, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/document/${document_id}`, data, undefined, loading)\n}\n\n/**\n * 删除文档\n * @param 参数 knowledge_id, document_id,\n */\nconst delDocument: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, loading) => {\n  return del(`${prefix.value}/${knowledge_id}/document/${document_id}`, loading)\n}\n\n/**\n * 批量取消文档任务\n * @param 参数 knowledge_id,\n *{\n \"id_list\": [\n \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n ],\n \"type\": 0\n }\n */\n\nconst putBatchCancelTask: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/document/batch_cancel_task`, data, undefined, loading)\n}\n\n/**\n * 取消文档任务\n * @param 参数 knowledge_id, document_id,\n */\nconst putCancelTask: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/cancel_task`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 下载原文档\n * @param 参数 knowledge_id\n */\nconst getDownloadSourceFile: (knowledge_id: string, document_id: string, document_name: string) => Promise<Result<any>> = (\n  knowledge_id,\n  document_id,\n  document_name,\n) => {\n  return exportFile(document_name, `${prefix.value}/${knowledge_id}/document/${document_id}/download_source_file`, {}, undefined)\n}\n\nconst postReplaceSourceFile: (knowledge_id: string, document_id: string, data: any) => Promise<Result<any>> = (\n  knowledge_id,\n  document_id,\n  data,\n) => {\n  return post(`${prefix.value}/${knowledge_id}/document/${document_id}/replace_source_file`, data, {}, undefined)\n}\n\n/**\n * 导出文档\n * @param document_name 文档名称\n * @param knowledge_id    数据集id\n * @param document_id   文档id\n * @param loading       加载器\n * @returns\n */\nconst exportDocument: (\n  document_name: string,\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_id, loading) => {\n  return exportExcel(\n    document_name.trim() + '.xlsx',\n    `${prefix.value}/${knowledge_id}/document/${document_id}/export`,\n    {},\n    loading,\n  )\n}\n\nconst exportMulDocument: (\n  document_name: string,\n  knowledge_id: string,\n  document_ids: string[],\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_ids, loading) => {\n  return exportExcelPost(\n    document_name.trim() + '.xlsx',\n    `${prefix.value}/${knowledge_id}/document/batch_export`,\n    {},\n    document_ids,\n    loading,\n  )\n}\n/**\n * 导出文档\n * @param document_name 文档名称\n * @param knowledge_id    数据集id\n * @param document_id   文档id\n * @param loading       加载器\n * @returns\n */\nconst exportDocumentZip: (\n  document_name: string,\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_id, loading) => {\n  return exportFile(\n    document_name.trim() + '.zip',\n    `${prefix.value}/${knowledge_id}/document/${document_id}/export_zip`,\n    {},\n    loading,\n  )\n}\n\nconst exportMulDocumentZip: (\n  document_name: string,\n  knowledge_id: string,\n  document_ids: string[],\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_ids, loading) => {\n  return exportFilePost(\n    document_name.trim() + '.zip',\n    `${prefix.value}/${knowledge_id}/document/batch_export_zip`,\n    {},\n    document_ids,\n    loading,\n  )\n}\n\n/**\n * 刷新文档向量库\n * @param 参数\n * knowledge_id, document_id,\n * {\n \"state_list\": [\n \"string\"\n ]\n }\n */\nconst putDocumentRefresh: (\n  knowledge_id: string,\n  document_id: string,\n  state_list: Array<string>,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, state_list, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/refresh`,\n    {state_list},\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 同步web站点类型\n * @param 参数\n * knowledge_id, document_id,\n */\nconst putDocumentSync: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/sync`,\n    undefined,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 创建批量文档\n * @param 参数\n {\n \"name\": \"string\",\n \"paragraphs\": [\n {\n \"content\": \"string\",\n \"title\": \"string\",\n \"problem_list\": [\n {\n \"id\": \"string\",\n \"content\": \"string\"\n }\n ],\n \"is_active\": true\n }\n ],\n \"source_file_id\": string\n }\n */\nconst putMulDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/batch_create`,\n    data,\n    {},\n    loading,\n    1000 * 60 * 5,\n  )\n}\n\n/**\n * 批量删除文档\n * @param 参数 knowledge_id,\n * {\n \"id_list\": [String]\n }\n */\nconst delMulDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/batch_delete`,\n    {id_list: data},\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量关联\n * @param 参数 knowledge_id,\n {\n \"document_id_list\": [\n \"string\"\n ],\n \"model_id\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n \"prompt\": \"string\",\n \"state_list\": [\n \"string\"\n ]\n }\n */\nconst putBatchGenerateRelated: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/batch_generate_related`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量修改命中方式\n * @param knowledge_id 知识库id\n * @param data\n * {id_list:[],hit_handling_method:'directly_return|optimization',directly_return_similarity}\n * @param loading\n * @returns\n */\nconst putBatchEditHitHandling: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/batch_hit_handling`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量刷新文档向量库\n * @param knowledge_id 知识库id\n * @param data\n {\n \"id_list\": [\n \"string\"\n ],\n \"state_list\": [\n \"string\"\n ]\n }\n * @param loading\n * @returns\n */\nconst putBatchRefresh: (\n  knowledge_id: string,\n  data: any,\n  stateList: Array<string>,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, stateList, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/batch_refresh`,\n    {id_list: data, state_list: stateList},\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量同步文档\n * @param 参数 knowledge_id,\n */\nconst putMulSyncDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/batch_sync`,\n    {id_list: data},\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量迁移文档\n * @param 参数 knowledge_id,target_knowledge_id,\n\n */\nconst putMigrateMulDocument: (\n  knowledge_id: string,\n  target_knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, target_knowledge_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/migrate/${target_knowledge_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 导入QA文档\n * @param 参数\n * file\n }\n */\nconst postQADocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/document/qa`, data, undefined, loading)\n}\n\n/**\n * 分段预览（上传文档）\n * @param 参数  file:file,limit:number,patterns:array,with_filter:boolean\n */\nconst postSplitDocument: (knowledge_id: string, data: any) => Promise<Result<any>> = (\n  knowledge_id,\n  data,\n) => {\n  return post(\n    `${prefix.value}/${knowledge_id}/document/split`,\n    data,\n    undefined,\n    undefined,\n    1000 * 60 * 60,\n  )\n}\n\n/**\n * 分段标识列表\n * @param loading 加载器\n * @returns 分段标识列表\n */\nconst listSplitPattern: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<KeyValue<string, string>>>> = (knowledge_id, loading) => {\n  return get(`${prefix.value}/${knowledge_id}/document/split_pattern`, {}, loading)\n}\n\n/**\n * 导入表格\n * @param 参数\n * file\n */\nconst postTableDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/document/table`, data, undefined, loading)\n}\n\n/**\n * 获得QA模板\n * @param 参数 fileName,type,\n */\nconst exportQATemplate: (fileName: string, type: string, loading?: Ref<boolean>) => void = (\n  fileName,\n  type,\n  loading,\n) => {\n  return exportExcel(fileName, `/workspace/knowledge/document/template/export`, {type}, loading)\n}\n\n/**\n * 获得table模板\n * @param 参数 fileName,type,\n */\nconst exportTableTemplate: (fileName: string, type: string, loading?: Ref<boolean>) => void = (\n  fileName,\n  type,\n  loading,\n) => {\n  return exportExcel(\n    fileName,\n    `/workspace/knowledge/document/table_template/export`,\n    {type},\n    loading,\n  )\n}\n\n/**\n * 创建Web站点文档\n * @param 参数\n * {\n \"source_url_list\": [\n \"string\"\n ],\n \"selector\": \"string\"\n }\n }\n */\nconst postWebDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/document/web`, data, undefined, loading)\n}\n\n/**\n * 飞书导入获得相关文档\n * @param 参数\n * {\n \"source_url_list\": [\n \"string\"\n ],\n \"selector\": \"string\"\n }\n }\n */\nconst getLarkDocumentList: (\n  knowledge_id: string,\n  folder_token: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, folder_token, data, loading) => {\n  return post(\n    `${prefix.value}/lark/${knowledge_id}/${folder_token}/doc_list`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 同步飞书文档\n */\nconst putLarkDocumentSync: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, loading) => {\n  return put(\n    `${prefix.value}/lark/${knowledge_id}/document/${document_id}/sync`,\n    undefined,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量同步飞书文档\n */\nconst putMulLarkSyncDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix.value}/lark/${knowledge_id}/_batch`, {id_list: data}, undefined, loading)\n}\n\n/**\n * 导入飞书文档\n */\nconst importLarkDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, data, loading) => {\n  return post(`${prefix.value}/lark/${knowledge_id}/import`, data, null, loading)\n}\n\nconst getDocumentTags: (\n  knowledge_id: string,\n  document_id: string,\n  params: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<string>>> = (knowledge_id, document_id, params, loading) => {\n  return get(`${prefix.value}/${knowledge_id}/document/${document_id}/tags`, params, loading)\n}\n\nconst postDocumentTags: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/document/${document_id}/tags`, data, null, loading)\n}\n\nconst postMulDocumentTags: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/document/batch_add_tag`, data, null, loading)\n}\n\nconst delMulDocumentTag: (\n  knowledge_id: string,\n  document_id: string,\n  tags: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, tags, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/document/${document_id}/tags/batch_delete`, tags, null, loading)\n}\n\nconst delDocsTag: (\n  knowledge_id: string,\n  tag_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, tag_id, data, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/tag/${tag_id}/docs_delete`, {id_list: data}, null, loading)\n}\n\n\nexport default {\n  getDocumentList,\n  getDocumentPage,\n  getDocumentDetail,\n  putDocument,\n  delDocument,\n  putBatchCancelTask,\n  putCancelTask,\n  getDownloadSourceFile,\n  postReplaceSourceFile,\n  exportDocument,\n  exportDocumentZip,\n  exportMulDocument,\n  exportMulDocumentZip,\n  putDocumentRefresh,\n  putDocumentSync,\n  putMulDocument,\n  delMulDocument,\n  putBatchGenerateRelated,\n  putBatchEditHitHandling,\n  putBatchRefresh,\n  putMulSyncDocument,\n  putMigrateMulDocument,\n  postQADocument,\n  postSplitDocument,\n  listSplitPattern,\n  postTableDocument,\n  exportQATemplate,\n  exportTableTemplate,\n  postWebDocument,\n  getLarkDocumentList,\n  putLarkDocumentSync,\n  putMulLarkSyncDocument,\n  importLarkDocument,\n  getDocumentTags,\n  postDocumentTags,\n  postMulDocumentTags,\n  delMulDocumentTag,\n  delDocsTag\n}\n"
  },
  {
    "path": "ui/src/api/knowledge/knowledge.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put, exportFile, exportExcel } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type { Dict, pageRequest } from '@/api/type/common'\nimport type { knowledgeData } from '@/api/type/knowledge'\n\nimport useStore from '@/stores'\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId() + '/knowledge'\n  },\n})\n\n/**\n * 知识库列表（无分页）\n * @param 参数\n * param  {\n    folder_id: \"string\",\n    name: \"string\",\n    tool_type: \"string\",\n    desc: string,\n  }\n */\nconst getKnowledgeList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  param,\n  loading,\n) => {\n  return get(`${prefix.value}`, param, loading)\n}\n\n/**\n * 知识库分页列表\n * @param 参数\n * param  {\n \"folder_id\": \"string\",\n \"name\": \"string\",\n \"tool_type\": \"string\",\n desc: string,\n }\n */\nconst getKnowledgeListPage: (\n  page: pageRequest,\n  param?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (page, param, loading) => {\n  return get(`${prefix.value}/${page.current_page}/${page.page_size}`, param, loading)\n}\n\n/**\n * 知识库详情\n * @param 参数 knowledge_id\n */\nconst getKnowledgeDetail: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id,\n  loading,\n) => {\n  return get(`${prefix.value}/${knowledge_id}`, undefined, loading)\n}\n\n/**\n * 修改知识库信息\n * @param 参数\n * knowledge_id\n * {\n \"name\": \"string\",\n \"desc\": true\n }\n */\nconst putKnowledge: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix.value}/${knowledge_id}`, data, undefined, loading)\n}\n\n/**\n * 删除知识库\n * @param 参数 knowledge_id\n */\nconst delKnowledge: (knowledge_id: String, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  knowledge_id,\n  loading,\n) => {\n  return del(`${prefix.value}/${knowledge_id}`, undefined, {}, loading)\n}\n\n/**\n * 向量化知识库\n * @param 参数 knowledge_id\n */\nconst putReEmbeddingKnowledge: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/embedding`, undefined, undefined, loading)\n}\n\n/**\n * 导出知识库\n * @param knowledge_name 知识库名称\n * @param knowledge_id   知识库id\n * @returns\n */\nconst exportKnowledge: (\n  knowledge_name: string,\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (knowledge_name, knowledge_id, loading) => {\n  return exportExcel(\n    knowledge_name + '.xlsx',\n    `${prefix.value}/${knowledge_id}/export`,\n    undefined,\n    loading,\n  )\n}\n/**\n *导出Zip知识库\n * @param knowledge_name 知识库名称\n * @param knowledge_id   知识库id\n * @param loading      加载器\n * @returns\n */\nconst exportZipKnowledge: (\n  knowledge_name: string,\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (knowledge_name, knowledge_id, loading) => {\n  return exportFile(\n    knowledge_name + '.zip',\n    `${prefix.value}/${knowledge_id}/export_zip`,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 生成关联问题\n * @param knowledge_id 知识库id\n * @param data\n * @param loading\n * @returns\n */\nconst putGenerateRelated: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, data, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/generate_related`, data, null, loading)\n}\n\n/**\n * 命中测试列表\n * @param knowledge_id\n * @param loading\n * @query  { query_text: string, top_number: number, similarity: number }\n * @returns\n */\nconst putKnowledgeHitTest: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, data, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/hit_test`, data, undefined, loading)\n}\n\n/**\n * 同步知识库\n * @param 参数 knowledge_id\n * @query 参数 sync_type // 同步类型->replace:替换同步,complete:完整同步\n */\nconst putSyncWebKnowledge: (\n  knowledge_id: string,\n  sync_type: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, sync_type, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/sync`, undefined, { sync_type }, loading)\n}\n\n/**\n * 创建知识库\n * @param 参数\n * {\n \"name\": \"string\",\n \"folder_id\": \"string\",\n \"desc\": \"string\",\n \"embedding\": \"string\"\n }\n */\nconst postKnowledge: (data: knowledgeData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix.value}/base`, data, undefined, loading, 1000 * 60 * 5)\n}\n\n/**\n * 创建工作流知识库\n * @param data\n * @param loading\n * @returns\n */\nconst createWorkflowKnowledge: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix.value}/workflow`, data, undefined, loading)\n}\n/**\n * 获取当前用户可使用的向量化模型列表 (没用到)\n * @param application_id\n * @param loading\n * @query  { query_text: string, top_number: number, similarity: number }\n * @returns\n */\nconst getKnowledgeEmdeddingModel: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, loading) => {\n  return get(`${prefix.value}/${knowledge_id}/emdedding_model`, loading)\n}\n\n/**\n * 获取当前用户可使用的模型列表\n * @param\n * @param loading\n * @returns\n */\nconst getKnowledgeModel: (loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (loading) => {\n  return get(`${prefix.value}/model`, loading)\n}\n\n/**\n * 创建Web知识库\n * @param 参数\n * {\n \"name\": \"string\",\n \"folder_id\": \"string\",\n \"desc\": \"string\",\n \"embedding\": \"string\",\n \"source_url\": \"string\",\n \"selector\": \"string\"\n }\n */\nconst postWebKnowledge: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix.value}/web`, data, undefined, loading)\n}\n\n// 创建飞书知识库\nconst postLarkKnowledge: (data: any, loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix.value}/lark/save`, data, null, loading)\n}\n\nconst putLarkKnowledge: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix.value}/lark/${knowledge_id}`, data, undefined, loading)\n}\n\nconst getAllTags: (params: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  params,\n  loading,\n) => {\n  return get(`${prefix.value}/tags`, params, loading)\n}\n\nconst getTags: (\n  knowledge_id: string,\n  params: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, params, loading) => {\n  return get(`${prefix.value}/${knowledge_id}/tags`, params, loading)\n}\n\nconst postTags: (\n  knowledge_id: string,\n  tags: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tags, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/tags`, tags, null, loading)\n}\n\nconst putTag: (\n  knowledge_id: string,\n  tag_id: string,\n  tag: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tag_id, tag, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/tags/${tag_id}`, tag, null, loading)\n}\n\nconst delTag: (\n  knowledge_id: string,\n  tag_id: string,\n  type: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tag_id, type, loading) => {\n  return del(`${prefix.value}/${knowledge_id}/tags/${tag_id}/${type}`, null, loading)\n}\n\nconst delMulTag: (\n  knowledge_id: string,\n  tags: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tags, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/tags/batch_delete`, tags, null, loading)\n}\nconst getKnowledgeWorkflowFormList: (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  node: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  node,\n  loading,\n) => {\n  return post(\n    `${prefix.value}/${knowledge_id}/datasource/${type}/${id}/form_list`,\n    { node },\n    {},\n    loading,\n  )\n}\nconst getKnowledgeWorkflowDatasourceDetails: (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  params: any,\n  function_name: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  params,\n  function_name,\n  loading,\n) => {\n  return post(\n    `${prefix.value}/${knowledge_id}/datasource/${type}/${id}/${function_name}`,\n    params,\n    {},\n    loading,\n  )\n}\nconst workflowAction: (\n  knowledge_id: string,\n  instance: Dict<any>,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, instance, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/debug`, instance, {}, loading)\n}\n\nconst workflowUpload: (\n  knowledge_id: string,\n  instance: Dict<any>,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, instance, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/upload_document`, instance, {}, loading)\n}\n\nconst publish: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id: string,\n  loading,\n) => {\n  return put(`${prefix.value}/${knowledge_id}/publish`, {}, {}, loading)\n}\n\n/**\n * 保存知识库工作流\n * @param knowledge_id\n * @param data\n * @param loading\n * @returns\n */\nconst putKnowledgeWorkflow: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/workflow`, data, undefined, loading)\n}\n\n/**\n * 导出知识库工作流\n * @param knowledge_id\n * @param knowledge_name\n * @param loading\n * @returns\n */\nconst exportKnowledgeWorkflow = (\n  knowledge_id: string,\n  knowledge_name: string,\n  loading?: Ref<boolean>,\n) => {\n  return exportFile(\n    knowledge_name + '.kbwf',\n    `${prefix.value}/${knowledge_id}/workflow/export`,\n    undefined,\n    loading,\n  )\n}\n/**\n * 导入知识库工作流\n */\nconst importKnowledgeWorkflow: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/workflow/import`, data, undefined, loading)\n}\n\nconst listKnowledgeVersion: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, loading) => {\n  return get(`${prefix.value}/${knowledge_id}/knowledge_version`, {}, loading)\n}\nconst updateKnowledgeVersion: (\n  knowledge_id: string,\n  knowledge_version_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, knowledge_version_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/knowledge_version/${knowledge_version_id}`,\n    data,\n    {},\n    loading,\n  )\n}\nconst getWorkflowActionPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  query: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, page, query, loading) => {\n  return get(\n    `${prefix.value}/${knowledge_id}/action/${page.current_page}/${page.page_size}`,\n    query,\n    loading,\n  )\n}\nconst getWorkflowAction: (\n  knowledge_id: string,\n  knowledge_action_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, knowledge_action_id, loading) => {\n  return get(`${prefix.value}/${knowledge_id}/action/${knowledge_action_id}`, {}, loading)\n}\nconst cancelWorkflowAction: (\n  knowledge_id: string,\n  knowledge_action_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, knowledge_action_id, loading) => {\n  return post(\n    `${prefix.value}/${knowledge_id}/action/${knowledge_action_id}/cancel`,\n    {},\n    undefined,\n    loading,\n  )\n}\n/**\n * mcp 节点\n */\nconst getMcpTools: (\n  knowledge_id: string,\n  mcp_servers: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, mcp_servers, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/mcp_tools`, { mcp_servers }, {}, loading)\n}\n\nconst postTransformWorkflow: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/transform_workflow`, data, undefined, loading)\n}\n\n\nexport default {\n  getKnowledgeList,\n  getKnowledgeListPage,\n  getKnowledgeDetail,\n  putKnowledge,\n  delKnowledge,\n  putReEmbeddingKnowledge,\n  exportKnowledge,\n  exportZipKnowledge,\n  putGenerateRelated,\n  putKnowledgeHitTest,\n  putSyncWebKnowledge,\n  postKnowledge,\n  getKnowledgeModel,\n  postWebKnowledge,\n  postLarkKnowledge,\n  putLarkKnowledge,\n  getAllTags,\n  getTags,\n  postTags,\n  putTag,\n  delTag,\n  delMulTag,\n  createWorkflowKnowledge,\n  getKnowledgeWorkflowFormList,\n  workflowAction,\n  getWorkflowAction,\n  getKnowledgeWorkflowDatasourceDetails,\n  getMcpTools,\n  listKnowledgeVersion,\n  updateKnowledgeVersion,\n  publish,\n  putKnowledgeWorkflow,\n  workflowUpload,\n  getWorkflowActionPage,\n  cancelWorkflowAction,\n  exportKnowledgeWorkflow,\n  importKnowledgeWorkflow,\n  postTransformWorkflow,\n}\n"
  },
  {
    "path": "ui/src/api/knowledge/paragraph.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport type { pageRequest } from '@/api/type/common'\nimport type { Ref } from 'vue'\nimport useStore from '@/stores'\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId() + '/knowledge'\n  },\n})\n\n/**\n * 创建段落\n * @param 参数\n * knowledge_id, document_id\n * {\n      \"content\": \"string\",\n      \"title\": \"string\",\n      \"is_active\": true,\n      \"problem_list\": [\n        {\n          \"content\": \"string\"\n        }\n      ]\n    }\n */\nconst postParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data, loading) => {\n  return post(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 段落分页列表\n * @param 参数 knowledge_id document_id\n * param {\n          \"title\": \"string\",\n          \"content\": \"string\",\n        }\n */\nconst getParagraphPage: (\n  knowledge_id: string,\n  document_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, page, param, loading) => {\n  return get(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 修改段落\n * @param 参数\n * knowledge_id, document_id, paragraph_id\n * {\n    \"content\": \"string\",\n    \"title\": \"string\",\n    \"is_active\": true,\n      \"problem_list\": [\n        {\n          \"content\": \"string\"\n        }\n      ]\n  }\n */\nconst putParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, paragraph_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 删除段落\n * @param 参数 knowledge_id, document_id, paragraph_id\n */\nconst delParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, paragraph_id, loading) => {\n  return del(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`,\n    undefined,\n    {},\n    loading,\n  )\n}\n\n/**\n * 某段落问题列表\n * @param 参数 knowledge_id，document_id，paragraph_id\n */\nconst getParagraphProblem: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n) => Promise<Result<any>> = (knowledge_id, document_id, paragraph_id: string) => {\n  return get(`${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`)\n}\n\n/**\n * 给某段落创建问题\n * @param 参数\n * knowledge_id, document_id, paragraph_id\n * {\n      content\": \"string\"\n    }\n */\nconst postParagraphProblem: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, paragraph_id, data: any, loading) => {\n  return post(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`,\n    data,\n    {},\n    loading,\n  )\n}\n\n\n/**\n * 段落调整顺序\n * @param knowledge_id 数据集id\n * @param document_id 文档id\n * @param loading 加载器\n * @query data {\n *              paragraph_id 段落id  new_position 新顺序\n *             }\n */\nconst putAdjustPosition: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/adjust_position`,\n    {},\n    data,\n    loading,\n  )\n}\n\n/**\n * 添加某段落关联问题\n * @param knowledge_id 数据集id\n * @param document_id 文档id\n * @param loading 加载器\n * @query data {\n *              paragraph_id 段落id  problem_id 问题id\n *             }\n */\nconst putAssociationProblem: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/association`,\n    {},\n    data,\n    loading,\n  )\n}\n\n/**\n * 批量删除段落\n * @param 参数 knowledge_id, document_id\n */\nconst putMulParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/batch_delete`,\n    { id_list: data },\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量关联问题\n * @param 参数 knowledge_id, document_id\n * {\n      \"paragraph_id_list\": [\n        \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n      ],\n      \"model_id\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n      \"prompt\": \"string\",\n      \"document_id\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n    }\n */\nconst putBatchGenerateRelated: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/batch_generate_related`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量迁移段落\n * @param 参数 knowledge_id,target_knowledge_id,\n * {\n      \"id_list\": [\n        \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n      ]\n    }\n */\nconst putMigrateMulParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  target_knowledge_id: string,\n  target_document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (\n  knowledge_id,\n  document_id,\n  target_knowledge_id,\n  target_document_id,\n  data,\n  loading,\n) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/migrate/knowledge/${target_knowledge_id}/document/${target_document_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 解除某段落关联问题\n * @param 参数 knowledge_id, document_id,\n * @query data {\n *            paragraph_id 段落id  problem_id 问题id\n *         }\n */\nconst putDisassociationProblem: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix.value}/${knowledge_id}/document/${document_id}/paragraph/unassociation`,\n    {},\n    data,\n    loading,\n  )\n}\n\nexport default {\n  postParagraph,\n  getParagraphPage,\n  putParagraph,\n  delParagraph,\n  getParagraphProblem,\n  postParagraphProblem,\n  putAssociationProblem,\n  putMulParagraph,\n  putBatchGenerateRelated,\n  putMigrateMulParagraph,\n  putDisassociationProblem,\n  putAdjustPosition\n}\n"
  },
  {
    "path": "ui/src/api/knowledge/problem.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport type { Ref } from 'vue'\nimport type { pageRequest } from '@/api/type/common'\n\nimport useStore from '@/stores'\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId() + '/knowledge'\n  },\n})\n\n/**\n * 创建问题\n * @param 参数 knowledge_id\n * data: array[string]\n */\nconst postProblems: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix.value}/${knowledge_id}/problem`, data, undefined, loading)\n}\n\n/**\n * 问题分页列表\n * @param 参数  knowledge_id,\n * query {\n     \"content\": \"string\",\n   }\n */\n\nconst getProblemsPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, page, param, loading) => {\n  return get(\n    `${prefix.value}/${knowledge_id}/problem/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 修改问题\n * @param 参数\n * knowledge_id, problem_id,\n * {\n \"content\": \"string\",\n }\n */\nconst putProblems: (\n  knowledge_id: string,\n  problem_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, problem_id, data: any, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/problem/${problem_id}`, data, undefined, loading)\n}\n\n/**\n * 删除问题\n * @param 参数 knowledge_id, problem_id,\n */\nconst delProblems: (\n  knowledge_id: string,\n  problem_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, problem_id, loading) => {\n  return del(`${prefix.value}/${knowledge_id}/problem/${problem_id}`, loading)\n}\n\n/**\n * 问题详情\n * @param 参数\n * knowledge_id, problem_id,\n */\nconst getDetailProblems: (\n  knowledge_id: string,\n  problem_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, problem_id, loading) => {\n  return get(`${prefix.value}/${knowledge_id}/problem/${problem_id}/paragraph`, undefined, loading)\n}\n\n/**\n * 批量关联段落\n * @param 参数 knowledge_id,\n * {\n      \"problem_id_list\": \"Array\",\n      \"paragraph_list\": \"Array\",\n    }\n */\nconst putMulAssociationProblem: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/problem/batch_association`, data, undefined, loading)\n}\n\n/**\n * 批量删除问题\n * @param 参数 knowledge_id,\n * data: array[string]\n */\nconst putMulProblem: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix.value}/${knowledge_id}/problem/batch_delete`, data, undefined, loading)\n}\n\nexport default {\n  postProblems,\n  getProblemsPage,\n  putProblems,\n  delProblems,\n  getDetailProblems,\n  putMulAssociationProblem,\n  putMulProblem,\n}\n"
  },
  {
    "path": "ui/src/api/model/model.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type {\n  ListModelRequest,\n  Model,\n  CreateModelRequest,\n  EditModelRequest,\n} from '@/api/type/model'\nimport type { FormField } from '@/components/dynamics-form/type'\n\nimport useStore from '@/stores'\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId()\n  },\n})\n\n/**\n * 获得模型列表\n * @params 参数 name, model_type, model_name\n */\nconst getModelList: (\n  data?: ListModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<Model>>> = (data, loading) => {\n  return get(`${prefix.value}/model`, data, loading)\n}\n\n/**\n * 获得下拉选择框模型列表\n * @params 参数 name, model_type, model_name\n */\nconst getSelectModelList: (\n  data?: ListModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<Model>>> = (data, loading) => {\n  return get(`${prefix.value}/model_list`, data, loading).then((ok) => {\n    return {\n      ...ok,\n      data: [\n        ...ok.data.shared_model.map((m: any) => {\n          return { ...m, type: 'share' }\n        }),\n        ...ok.data.model.map((m: any) => {\n          return { ...m, type: 'workspace' }\n        }),\n      ],\n    }\n  })\n}\n\n/**\n * 获取模型参数表单\n * @param model_id 模型id\n * @param loading\n * @returns\n */\nconst getModelParamsForm: (\n  model_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<FormField>>> = (model_id, loading) => {\n  return get(`${prefix.value}/model/${model_id}/model_params_form`, {}, loading)\n}\n\n/**\n * 创建模型\n * @param request 请求对象\n * @param loading 加载器\n * @returns\n */\nconst createModel: (\n  request: CreateModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Model>> = (request, loading) => {\n  return post(`${prefix.value}/model`, request, {}, loading)\n}\n\n/**\n * 修改模型\n * @param request 請求對象\n * @param loading 加載器\n * @returns\n */\nconst updateModel: (\n  model_id: string,\n  request: EditModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Model>> = (model_id, request, loading) => {\n  return put(`${prefix.value}/model/${model_id}`, request, {}, loading)\n}\n\n/**\n * 修改模型参数配置\n * @param request 請求對象\n * @param loading 加載器\n * @returns\n */\nconst updateModelParamsForm: (\n  model_id: string,\n  request: any[],\n  loading?: Ref<boolean>,\n) => Promise<Result<Model>> = (model_id, request, loading) => {\n  return put(`${prefix.value}/model/${model_id}/model_params_form`, request, {}, loading)\n}\n\n/**\n * 获取模型详情根据模型id 包括认证信息\n * @param model_id 模型id\n * @param loading  加载器\n * @returns\n */\nconst getModelById: (model_id: string, loading?: Ref<boolean>) => Promise<Result<Model>> = (\n  model_id,\n  loading,\n) => {\n  return get(`${prefix.value}/model/${model_id}`, {}, loading)\n}\n/**\n * 获取模型信息不包括认证信息根据模型id\n * @param model_id 模型id\n * @param loading  加载器\n * @returns\n */\nconst getModelMetaById: (model_id: string, loading?: Ref<boolean>) => Promise<Result<Model>> = (\n  model_id,\n  loading,\n) => {\n  return get(`${prefix.value}/model/${model_id}/meta`, {}, loading)\n}\n/**\n * 暂停下载\n * @param model_id 模型id\n * @param loading 加载器\n * @returns\n */\nconst pauseDownload: (model_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  model_id,\n  loading,\n) => {\n  return put(`${prefix.value}/model/${model_id}/pause_download`, undefined, {}, loading)\n}\nconst deleteModel: (model_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  model_id,\n  loading,\n) => {\n  return del(`${prefix.value}/model/${model_id}`, undefined, {}, loading)\n}\nexport default {\n  getModelList,\n  createModel,\n  updateModel,\n  deleteModel,\n  getModelById,\n  getModelMetaById,\n  pauseDownload,\n  getModelParamsForm,\n  updateModelParamsForm,\n  getSelectModelList,\n}\n"
  },
  {
    "path": "ui/src/api/model/provider.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post} from '@/request/index'\nimport type {Ref} from 'vue'\nimport type {Provider, BaseModel} from '@/api/type/model'\nimport type {FormField} from '@/components/dynamics-form/type'\nimport type {KeyValue} from '../type/common'\n\nconst prefix_provider = '/provider'\n/**\n * 获得供应商列表\n */\nconst getProvider: (loading?: Ref<boolean>) => Promise<Result<Array<Provider>>> = (loading) => {\n  return get(`${prefix_provider}`, {}, loading)\n}\n\n/**\n * 获得供应商列表\n */\nconst getProviderByModelType: (\n  model_type: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<Provider>>> = (model_type, loading) => {\n  return get(`${prefix_provider}`, {model_type}, loading)\n}\n\n/**\n * 获取模型创建表单\n * @param provider\n * @param model_type\n * @param model_name\n * @param loading\n * @returns\n */\nconst getModelCreateForm: (\n  provider: string,\n  model_type: string,\n  model_name: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<FormField>>> = (provider, model_type, model_name, loading) => {\n  return get(`${prefix_provider}/model_form`, {provider, model_type, model_name}, loading)\n}\n\n/**\n * 获取模型类型列表\n * @param provider 供应商\n * @param loading  加载器\n * @returns 模型类型列表\n */\nconst listModelType: (\n  provider: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<KeyValue<string, string>>>> = (provider, loading?: Ref<boolean>) => {\n  return get(`${prefix_provider}/model_type_list`, {provider}, loading)\n}\n\n/**\n * 获取基础模型列表\n * @param provider\n * @param model_type\n * @param loading\n * @returns\n */\nconst listBaseModel: (\n  provider: string,\n  model_type: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<BaseModel>>> = (provider, model_type, loading) => {\n  return get(`${prefix_provider}/model_list`, {provider, model_type}, loading)\n}\n\nconst listBaseModelParamsForm: (\n  provider: string,\n  model_type: string,\n  model_name: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<BaseModel>>> = (provider, model_type, model_name, loading) => {\n  return get(`${prefix_provider}/model_params_form`, {provider, model_type, model_name}, loading)\n}\nexport default {\n  getProvider,\n  getModelCreateForm,\n  getProviderByModelType,\n  listModelType,\n  listBaseModel,\n  listBaseModelParamsForm,\n}\n"
  },
  {
    "path": "ui/src/api/shared-workspace.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, del, put, exportFile, exportExcel} from '@/request/index'\nimport {type Ref} from 'vue'\nimport type {PageList, pageRequest} from '@/api/type/common'\nimport type {knowledgeData} from '@/api/type/knowledge'\n\nimport useStore from '@/stores'\nimport type {ChatUserGroupItem} from './type/workspaceChatUser'\n\nconst prefix = '/system/shared'\nconst prefix_workspace: any = {_value: 'workspace/'}\nObject.defineProperty(prefix_workspace, 'value', {\n  get: function () {\n    const {user} = useStore()\n    return this._value + user.getWorkspaceId()\n  },\n})\n\nconst getKnowledgeList: (loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (loading) => {\n  return get(`${prefix}/${prefix_workspace.value}/knowledge`, {}, loading)\n}\n\nconst getKnowledgeListPage: (\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (page, param, loading) => {\n  return get(\n    `${prefix}/${prefix_workspace.value}/knowledge/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 知识库详情\n * @param 参数 knowledge_id\n */\nconst getKnowledgeDetail: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id,\n  loading,\n) => {\n  return get(`${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}`, undefined, loading)\n}\n\n/**\n * 文档分页列表\n * @param 参数  knowledge_id,\n * param {\n \"name\": \"string\",\n folder_id: \"string\",\n }\n */\n\nconst getDocumentPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, page, param, loading) => {\n  return get(\n    `${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}/document/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 文档详情\n * @param 参数 knowledge_id\n */\nconst getDocumentDetail: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, loading) => {\n  return get(\n    `${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}/document/${document_id}`,\n    {},\n    loading,\n  )\n}\n\n/**\n * 问题分页列表\n * @param 参数  knowledge_id,\n * query {\n \"content\": \"string\",\n }\n */\n\nconst getProblemsPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, page, param, loading) => {\n  return get(\n    `${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}/problem/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 获取工作空间下共享知识库用户组的用户列表\n */\nconst getUserGroupUserList: (\n  resource: any,\n  user_group_id: string,\n  page: pageRequest,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<PageList<ChatUserGroupItem[]>>> = (resource, user_group_id, page, params, loading) => {\n  return get(\n    `${prefix}/${prefix_workspace.value}/KNOWLEDGE/${resource.resource_id}/user_group_id/${user_group_id}/${page.current_page}/${page.page_size}`,\n    params, loading,\n  )\n}\n\n/**\n * 获取工作空间下共享知识库的用户组\n */\nconst getUserGroupList: (resource: any, loading?: Ref<boolean>) => Promise<Result<ChatUserGroupItem[]>> = (resource, loading) => {\n  return get(`${prefix}/${prefix_workspace.value}/KNOWLEDGE/${resource.resource_id}/user_group`, undefined, loading)\n}\n\n/**\n * 段落分页列表\n * @param 参数 knowledge_id document_id\n * param {\n \"title\": \"string\",\n \"content\": \"string\",\n }\n */\nconst getParagraphPage: (\n  knowledge_id: string,\n  document_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, page, param, loading) => {\n  return get(\n    `${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}/document/${document_id}/paragraph/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\nconst getModelList: (param: any, loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (\n  param: any,\n  loading,\n) => {\n  return get(`${prefix}/${prefix_workspace.value}/model`, param, loading)\n}\n\nconst getToolList: (param: any, loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (param, loading) => {\n  return get(`${prefix}/${prefix_workspace.value}/tool`, param, loading)\n}\n\nconst getToolListPage: (\n  page: pageRequest,\n  param?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (page, param, loading) => {\n  return get(\n    `${prefix}/${prefix_workspace.value}/tool/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 获取全部用户\n */\nconst getAllMemberList: (arg: string, loading?: Ref<boolean>) => Promise<Result<Record<string, any>[]>> = (\n  arg,\n  loading,\n) => {\n  return get('/user/list', undefined, loading)\n}\n\nconst getTags: (knowledge_id: string, params: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id,\n  params,\n  loading,\n) => {\n  return get(`${prefix}/${prefix_workspace.value}/knowledge/${knowledge_id}/tags`, params, loading)\n}\n\nexport default {\n  getKnowledgeList,\n  getKnowledgeListPage,\n  getKnowledgeDetail,\n  getProblemsPage,\n  getDocumentPage,\n  getDocumentDetail,\n  getParagraphPage,\n  getModelList,\n  getToolList,\n  getToolListPage,\n  getUserGroupList,\n  getUserGroupUserList,\n  getAllMemberList,\n  getTags\n}\n"
  },
  {
    "path": "ui/src/api/system/api-key.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, del, put} from '@/request/index'\n\nimport {type Ref} from 'vue'\n\nconst prefix = '/system/api_key'\n\n/**\n * API_KEY列表\n */\nconst getAPIKey: (currentPage: number, pageSize: number, params: any, loading?: Ref<boolean>) => Promise<Result<any>> = (currentPage: number, pageSize: number, params, loading?: Ref<boolean>) => {\n  return get(`${prefix}/${currentPage}/${pageSize}`, params)\n}\n\n/**\n * 新增API_KEY\n */\nconst postAPIKey: (loading?: Ref<boolean>) => Promise<Result<any>> = (\n  loading\n) => {\n  return post(`${prefix}`, {}, undefined, loading)\n}\n\n/**\n * 删除API_KEY\n * @param 参数 application_id api_key_id\n */\nconst delAPIKey: (\n  api_key_id: string,\n  loading?: Ref<boolean>\n) => Promise<Result<boolean>> = (api_key_id, loading) => {\n  return del(`${prefix}/${api_key_id}`, undefined, undefined, loading)\n}\n\n/**\n * 修改API_KEY\n * data {\n *   is_active: boolean\n * }\n * @param api_key_id\n * @param data\n * @param loading\n */\nconst putAPIKey: (\n  api_key_id: string,\n  data: any,\n  loading?: Ref<boolean>\n) => Promise<Result<any>> = (api_key_id, data, loading) => {\n  return put(`${prefix}/${api_key_id}`, data, undefined, loading)\n}\n\n\nexport default {\n  getAPIKey,\n  postAPIKey,\n  delAPIKey,\n  putAPIKey\n}\n"
  },
  {
    "path": "ui/src/api/system/auth.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, put} from '@/request/index'\nimport {type Ref} from 'vue'\n\nconst prefix = '/system/auth'\n/**\n * 获取认证设置\n */\nconst getAuthSetting: (auth_type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (auth_type, loading) => {\n  return get(`${prefix}/${auth_type}/detail`, undefined, loading)\n}\n\n/**\n * ldap连接测试\n */\nconst postAuthSetting: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return post(`${prefix}/connection`, data, undefined, loading)\n}\n\n/**\n * 修改邮箱设置\n */\nconst putAuthSetting: (auth_type: string, data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  auth_type,\n  data,\n  loading\n) => {\n  return put(`${prefix}/${auth_type}/info`, data, undefined, loading)\n}\n\nexport default {\n  getAuthSetting,\n  postAuthSetting,\n  putAuthSetting\n}\n"
  },
  {
    "path": "ui/src/api/system/chat-user.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, put, post, del} from '@/request/index'\nimport type {pageRequest, PageList} from '@/api/type/common'\nimport type {ChatUserItem} from '@/api/type/systemChatUser'\nimport type {Ref} from 'vue'\n\nconst prefix = '/system/chat_user'\n\n\n/**\n * 用户列表\n */\nconst getChatUserList: (loading?: Ref<boolean>) => Promise<Result<ChatUserItem[]>> = (loading) => {\n  return get(`${prefix}/list`, undefined, loading)\n}\n\n/**\n * 用户分页列表\n * @query 参数\n username_or_nickname: string\n */\nconst getUserManage: (\n  page: pageRequest,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<PageList<ChatUserItem[]>>> = (page, params, loading) => {\n  return get(\n    `${prefix}/user_manage/${page.current_page}/${page.page_size}`,\n    params ? params : undefined,\n    loading,\n  )\n}\n\n/**\n * 删除用户\n * @param 参数 user_id,\n */\nconst delUserManage: (user_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  user_id,\n  loading,\n) => {\n  return del(`${prefix}/${user_id}`, undefined, {}, loading)\n}\n\n/**\n * 创建用户\n */\nconst postUserManage: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}`, data, undefined, loading)\n}\n\n/**\n * 编辑用户\n */\nconst putUserManage: (\n  user_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (user_id, data, loading) => {\n  return put(`${prefix}/${user_id}`, data, undefined, loading)\n}\n\n/**\n * 修改用户密码\n */\nconst putUserManagePassword: (\n  user_id: string,\n  data: any,\n  loading?: Ref<boolean>\n) => Promise<Result<any>> = (user_id, data, loading) => {\n  return put(`${prefix}/${user_id}/re_password`, data, undefined, loading)\n}\n\n/**\n * 设置用户组\n */\nconst batchAddGroup: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/batch_add_group`, data, undefined, loading)\n}\n\n/**\n * 批量删除\n */\nconst batchDelete: (data: string[], loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/batch_delete`, data, undefined, loading)\n}\n\n/**\n * 同步用户\n */\nconst batchSync: (sync_type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  sync_type,\n  loading,\n) => {\n  return post(`${prefix}/sync/${sync_type}`, undefined, undefined, loading)\n}\n\n/**\n * 获取同步类型\n */\nconst getSyncType: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get(`${prefix}/sync_types`, undefined, loading)\n}\n\nexport default {\n  getUserManage,\n  putUserManage,\n  delUserManage,\n  postUserManage,\n  putUserManagePassword,\n  getChatUserList,\n  batchAddGroup,\n  batchDelete,\n  batchSync,\n  getSyncType\n}\n"
  },
  {
    "path": "ui/src/api/system/license.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport { type Ref } from 'vue'\n\nconst prefix = '/license'\n\n/**\n * 获得license信息\n */\nconst getLicense: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get(`${prefix}/profile`, undefined, loading)\n}\n/**\n * 更新license信息\n * @param 参数  license_file:file\n */\nconst putLicense: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (data, loading) => {\n  return put(`${prefix}/profile`, data, undefined, loading)\n}\n\nexport default {\n  getLicense,\n  putLicense\n}\n"
  },
  {
    "path": "ui/src/api/system/operate-log.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, exportExcelPost, post} from '@/request/index'\nimport type {pageRequest} from '@/api/type/common'\nimport {type Ref} from 'vue'\n\nconst prefix = '/operate_log'\n/**\n * 日志分页列表\n * @param 参数\n * page  {\n \"current_page\": \"string\",\n \"page_size\": \"string\",\n }\n * @query 参数\n param: any\n */\nconst getOperateLog: (\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>\n) => Promise<Result<any>> = (page, param, loading) => {\n  return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading)\n}\n\nconst getMenuList: () => Promise<Result<any>> = () => {\n  return get(`${prefix}/menu_operation_option/`, undefined, undefined)\n}\n\nconst exportOperateLog: (\n  param: any,\n  loading?: Ref<boolean>\n) => void = (param, loading) => {\n  exportExcelPost(\n    'log.xlsx',\n    `${prefix}/export/`,\n    param,\n    undefined,\n    loading\n  )\n}\n\nconst saveCleanTime: (\n  data: any,\n  loading?: Ref<boolean>\n) => Promise<Result<any>> = (data, loading) => {\n  return post(`${prefix}/save`, data, undefined, loading)\n}\nconst getCleanTime: () => Promise<Result<any>> = () => {\n  return get(`${prefix}/get_clean_time`, undefined, undefined)\n}\n\nexport default {\n  getOperateLog,\n  getMenuList,\n  exportOperateLog,\n  saveCleanTime,\n  getCleanTime\n}\n"
  },
  {
    "path": "ui/src/api/system/platform-source.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport { type Ref } from 'vue'\n\nconst prefix = '/platform'\nconst getPlatformInfo: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get(`${prefix}/source`, undefined, loading)\n}\n\nconst updateConfig: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return post(`${prefix}/source`, data, undefined, loading)\n}\n\nconst validateConnection: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return put(`${prefix}/source`, data, undefined, loading)\n}\nexport default {\n  getPlatformInfo,\n  updateConfig,\n  validateConnection\n}\n"
  },
  {
    "path": "ui/src/api/system/resource-authorization.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, put, post, del } from '@/request/index'\nimport type { Ref } from 'vue'\nimport type { pageRequest } from '@/api/type/common'\nimport { t } from '@/locales/index'\nconst prefix = '/workspace'\n\n/**\n * 系统资源授权获取资源权限\n * @query 参数\n */\nconst getResourceAuthorization: (\n  workspace_id: string,\n  user_id: string,\n  resource: string,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, user_id, resource, params, loading) => {\n  return get(\n    `${prefix}/${workspace_id}/user_resource_permission/user/${user_id}/resource/${resource}`,\n    params,\n    loading,\n  )\n}\n\n/**\n * 系统资源授权修改成员权限\n * @param 参数 member_id\n * @param 参数 {\n     [\n      {\n        \"target_id\": \"string\",\n        \"permission\": \"NOT_AUTH\"\n      }\n    ]\n        }\n */\nconst putResourceAuthorization: (\n  workspace_id: string,\n  user_id: string,\n  resource: string,\n  body: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, user_id, resource, body, loading) => {\n  return put(\n    `${prefix}/${workspace_id}/user_resource_permission/user/${user_id}/resource/${resource}`,\n    body,\n    {},\n    loading,\n  )\n}\n\n/**\n * 获取成员列表\n * @query 参数\n */\nconst getUserList: (workspace_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  workspace_id,\n  loading,\n) => {\n  return get(`${prefix}/${workspace_id}/user_list`, undefined, loading)\n}\n\nconst getUserMember: (workspace_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  workspace_id,\n  loading,\n) => {\n  return get(`${prefix}/${workspace_id}/user_member`, undefined, loading)\n}\n\n/**\n * 获得系统文件夹列表\n * @params 参数\n *  source : APPLICATION, KNOWLEDGE, TOOL\n *  data : {name: string}\n */\nconst getSystemFolder: (\n  workspace_id: string,\n  source: string,\n  data?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (workspace_id, source, data, loading) => {\n  if (source == 'MODEL') {\n    return Promise.resolve(\n      Result.success([\n        {\n          id: 'default',\n          name: t('layout.about.root'),\n          desc: null,\n          parent_id: null,\n          children: [],\n        },\n      ]),\n    )\n  }\n  return get(`${prefix}/${workspace_id}/${source}/folder`, data, loading)\n}\n\nexport default {\n  getResourceAuthorization,\n  putResourceAuthorization,\n  getUserList,\n  getUserMember,\n  getSystemFolder,\n}\n"
  },
  {
    "path": "ui/src/api/system/role.ts",
    "content": "import { get, post, del } from '@/request/index'\nimport type { Ref } from 'vue'\nimport { Result } from '@/request/Result'\nimport type { RoleItem, RolePermissionItem, CreateOrUpdateParams, RoleMemberItem, CreateMemberParamsItem } from '@/api/type/role'\nimport { RoleTypeEnum } from '@/enums/system'\nimport type { pageRequest, PageList } from '@/api/type/common'\n\nconst prefix = '/system/role'\n/**\n * 获取角色列表\n */\nconst getRoleList: (loading?: Ref<boolean>) => Promise<Result<{ internal_role: RoleItem[], custom_role: RoleItem[] }>> = (loading) => {\n  return get(`${prefix}`, undefined, loading)\n}\n\n/**\n * 根据类型获取角色权限模板列表\n */\nconst getRoleTemplate: (role_type: RoleTypeEnum, loading?: Ref<boolean>) => Promise<Result<RolePermissionItem[]>> = (role_type, loading) => {\n  return get(`${prefix}/template/${role_type}`, undefined, loading)\n}\n\n/**\n * 获取角色权限选中\n */\nconst getRolePermissionList: (role_id: string, loading?: Ref<boolean>) => Promise<Result<RolePermissionItem[]>> = (role_id, loading) => {\n  return get(`${prefix}/${role_id}/permission`, undefined, loading)\n}\n\n/**\n * 新建或更新角色\n */\nconst CreateOrUpdateRole: (\n  data: CreateOrUpdateParams,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (data, loading) => {\n  return post(`${prefix}`, data, undefined, loading)\n}\n\n/**\n * 删除角色\n */\nconst deleteRole: (role_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  role_id,\n  loading,\n) => {\n  return del(`${prefix}/${role_id}`, undefined, {}, loading)\n}\n\n/**\n * 保存角色权限\n */\nconst saveRolePermission: (\n  role_id: string,\n  data: { id: string, enable: boolean }[],\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (role_id, data, loading) => {\n  return post(`${prefix}/${role_id}/permission`, data, undefined, loading)\n}\n\n/**\n * 获取角色成员列表\n */\nconst getRoleMemberList: (\n  role_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<PageList<RoleMemberItem[]>>> = (role_id, page, param, loading) => {\n  return get(\n    `${prefix}/${role_id}/user_list/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 新建角色成员\n */\nconst CreateMember: (\n  role_id: string,\n  data: { members: CreateMemberParamsItem[] },\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (role_id, data, loading) => {\n  return post(`${prefix}/${role_id}/add_member`, data, undefined, loading)\n}\n\n/**\n * 删除角色成员\n */\nconst deleteRoleMember: (role_id: string, user_relation_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  role_id,\n  user_relation_id,\n  loading,\n) => {\n  return del(`${prefix}/${role_id}/remove_member/${user_relation_id}`, undefined, {}, loading)\n}\n\nexport default {\n  getRoleList,\n  getRolePermissionList,\n  getRoleTemplate,\n  CreateOrUpdateRole,\n  deleteRole,\n  saveRolePermission,\n  getRoleMemberList,\n  CreateMember,\n  deleteRoleMember\n}\n"
  },
  {
    "path": "ui/src/api/system/user-group.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, del} from '@/request/index'\nimport type {Ref} from 'vue'\nimport type {ChatUserGroupUserItem,} from '@/api/type/systemChatUser'\nimport type {pageRequest, PageList, ListItem} from '@/api/type/common'\n\nconst prefix = '/system/group'\n\n/**\n * 获取用户组列表\n */\nconst getUserGroup: (loading?: Ref<boolean>) => Promise<Result<ListItem[]>> = () => {\n  return get(`${prefix}`)\n}\n\n/**\n * 创建用户组\n * @param 参数\n * {\n \"id\": \"string\",\n \"name\": \"string\"\n }\n */\nconst postUserGroup: (data: ListItem, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}`, data, undefined, loading)\n}\n\n/**\n * 删除用户组\n * @param 参数 user_group_id\n */\nconst delUserGroup: (user_group_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  user_group_id,\n  loading,\n) => {\n  return del(`${prefix}/${user_group_id}`, undefined, {}, loading)\n}\n\n/**\n * 给用户组添加用户\n */\nconst postAddMember: (\n  user_group_id: string,\n  body: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (user_group_id, body, loading) => {\n  return post(`${prefix}/${user_group_id}/add_member`, body, {}, loading)\n}\n\n/**\n * 从用户组删除用户\n */\nconst postRemoveMember: (\n  user_group_id: string,\n  body: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (user_group_id, body, loading) => {\n  return post(`${prefix}/${user_group_id}/remove_member`, body, {}, loading)\n}\n\n/**\n * 获取用户组的成员列表\n */\nconst getUserListByGroup: (\n  user_group_id: string,\n  page: pageRequest,\n  params ?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<PageList<ChatUserGroupUserItem[]>>> = (user_group_id, page, params, loading) => {\n  return get(\n    `${prefix}/${user_group_id}/user_list/${page.current_page}/${page.page_size}`,\n    params ? params : undefined,\n    loading,\n  )\n}\nexport default {\n  getUserGroup,\n  postUserGroup,\n  delUserGroup,\n  postAddMember,\n  postRemoveMember,\n  getUserListByGroup\n}\n"
  },
  {
    "path": "ui/src/api/system/user-manage.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, put, post, del} from '@/request/index'\nimport type {pageRequest} from '@/api/type/common'\nimport type {Ref} from 'vue'\n\n\nconst prefix = '/user_manage'\n/**\n * 用户分页列表\n * @query 参数\n email_or_username: string\n */\nconst getUserManage: (\n  page: pageRequest,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (page, params, loading) => {\n  return get(\n    `${prefix}/${page.current_page}/${page.page_size}`,\n    params ? params : undefined,\n    loading,\n  )\n}\n\n/**\n * 删除用户\n * @param 参数 user_id,\n */\nconst delUserManage: (user_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  user_id,\n  loading,\n) => {\n  return del(`${prefix}/${user_id}`, undefined, {}, loading)\n}\n\n/**\n * 创建用户\n */\nconst postUserManage: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}`, data, undefined, loading)\n}\n\n/**\n * 编辑用户\n */\nconst putUserManage: (\n  user_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (user_id, data, loading) => {\n  return put(`${prefix}/${user_id}`, data, undefined, loading)\n}\n\n/**\n * 修改用户密码\n */\nconst putUserManagePassword: (\n  user_id: string,\n  data: any,\n  loading?: Ref<boolean>\n) => Promise<Result<any>> = (user_id, data, loading) => {\n  return put(`${prefix}/${user_id}/re_password`, data, undefined, loading)\n}\n\n\n/**\n * 获取系统默认密码\n */\nconst getSystemDefaultPassword: (\n  loading?: Ref<boolean>\n) => Promise<Result<string>> = (loading) => {\n  return get('/user_manage/password', undefined, loading)\n}\n\n\n/**\n * 获取校验\n * @param valid_type 校验类型: application|knowledge|user\n * @param valid_count 校验数量: 5 | 50 | 2\n */\nconst getValid: (\n  valid_type: string,\n  valid_count: number,\n  loading?: Ref<boolean>\n) => Promise<Result<any>> = (valid_type, valid_count, loading) => {\n  return get(`/valid/${valid_type}/${valid_count}`, undefined, loading)\n}\n\nconst batchDelete: (\n  ids: string[],\n  loading?: Ref<boolean>\n) => Promise<Result<any>> = (ids, loading) => {\n  return post(`/user_manage/batch_delete`, ids, {}, loading)\n}\n\nconst batchSetRolePE: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`/user_manage/batch/add_role`, data, undefined, loading)\n}\nconst batchSetRoleEE: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`/user_manage/batch/add_role_ee`, data, undefined, loading)\n}\n\nexport default {\n  getUserManage,\n  putUserManage,\n  delUserManage,\n  postUserManage,\n  putUserManagePassword,\n  getSystemDefaultPassword,\n  getValid,\n  batchDelete,\n  batchSetRolePE,\n  batchSetRoleEE\n}\n"
  },
  {
    "path": "ui/src/api/system/workspace.ts",
    "content": "import { Result } from '@/request/Result'\nimport type { Ref } from 'vue'\nimport { get, post, del } from '@/request/index'\nimport type { WorkspaceItem, CreateWorkspaceMemberParamsItem, WorkspaceMemberItem } from '@/api/type/workspace'\nimport type { pageRequest, PageList } from '@/api/type/common'\n\nconst prefix = '/system/workspace'\n\n/**\n * 获取首页的工作空间下拉列表\n */\nconst getWorkspaceListByUser: (loading?: Ref<boolean>) => Promise<Result<WorkspaceItem[]>> = (loading) => {\n  return get('/workspace/by_user', undefined, loading)\n}\n\n/**\n * 获取添加成员时的工作空间下拉列表\n */\nconst getWorkspaceList: (loading?: Ref<boolean>) => Promise<Result<Record<string, any>[]>> = (loading) => {\n  return get('/workspace/current_user', undefined, loading)\n}\n\n/**\n * 获取工作空间列表\n */\nconst getSystemWorkspaceList: (loading?: Ref<boolean>) => Promise<Result<WorkspaceItem[]>> = (loading) => {\n  return get(`${prefix}`, undefined, loading)\n}\n\n/**\n * 新建或更新工作空间\n */\nconst CreateOrUpdateWorkspace: (\n  data: WorkspaceItem,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (data, loading) => {\n  return post(`${prefix}`, data, undefined, loading)\n}\n\n/**\n * 删除工作空间前的校验\n */\nconst deleteWorkspaceCheck: (workspace_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  workspace_id,\n  loading,\n) => {\n  return get(`${prefix}/${workspace_id}/check`, undefined, loading)\n}\n\n/**\n * 删除工作空间\n */\nconst deleteWorkspace: (workspace_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  workspace_id,\n  loading,\n) => {\n  return del(`${prefix}/${workspace_id}`, undefined, {}, loading)\n}\n\n/**\n * 获取工作空间成员列表\n */\nconst getWorkspaceMemberList: (\n  workspace_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<PageList<WorkspaceMemberItem[]>>> = (workspace_id, page, param, loading) => {\n  return get(\n    `${prefix}/${workspace_id}/user_list/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 新建工作空间成员\n */\nconst CreateWorkspaceMember: (\n  workspace_id: string,\n  data: CreateWorkspaceMemberParamsItem[],\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, data, loading) => {\n  return post(`${prefix}/${workspace_id}/add_member`, data, undefined, loading)\n}\n\n/**\n * 删除工作空间成员\n */\nconst deleteWorkspaceMember: (workspace_id: string, user_relation_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  workspace_id,\n  user_relation_id,\n  loading,\n) => {\n  return post(`${prefix}/${workspace_id}/remove_member/${user_relation_id}`, undefined, {}, loading)\n}\n\n/**\n * 获取添加成员时的角色下拉列表\n */\nconst getWorkspaceRoleList: (loading?: Ref<boolean>) => Promise<Result<Record<string, any>[]>> = (loading) => {\n  return get('/role_list/current_user', undefined, loading)\n}\n\nexport default {\n  getWorkspaceList,\n  getSystemWorkspaceList,\n  CreateOrUpdateWorkspace,\n  deleteWorkspace,\n  getWorkspaceMemberList,\n  CreateWorkspaceMember,\n  deleteWorkspaceMember,\n  getWorkspaceRoleList,\n  getWorkspaceListByUser,\n  deleteWorkspaceCheck\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/application-key.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, del, put} from '@/request/index'\nimport {type Ref} from 'vue'\n\nconst prefix = '/system/resource/application'\n/**\n * API_KEY列表\n * @param 参数 application_id\n */\nconst getAPIKey: (application_id: string, current_page: number, page_size: number, params?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  application_id,\n  current_page,\n  page_size,\n  params,\n  loading,\n) => {\n  return get(`${prefix}/${application_id}/application_key/${current_page}/${page_size}`, params, loading)\n}\n\n/**\n * 新增API_KEY\n * @param 参数 application_id\n */\nconst postAPIKey: (application_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  application_id,\n  loading,\n) => {\n  return post(`${prefix}/${application_id}/application_key`, {}, undefined, loading)\n}\n\n/**\n * 删除API_KEY\n * @param 参数 application_id api_key_id\n */\nconst delAPIKey: (\n  application_id: string,\n  api_key_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (application_id, api_key_id, loading) => {\n  return del(\n    `${prefix}/${application_id}/application_key/${api_key_id}`,\n    undefined,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 修改API_KEY\n * @param 参数 application_id,api_key_id\n * data {\n *   is_active: boolean\n * }\n */\nconst putAPIKey: (\n  application_id: string,\n  api_key_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, api_key_id, data, loading) => {\n  return put(`${prefix}/${application_id}/application_key/${api_key_id}`, data, undefined, loading)\n}\n\nexport default {\n  getAPIKey,\n  postAPIKey,\n  delAPIKey,\n  putAPIKey,\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/application.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, postStream, del, put, request, download, exportFile } from '@/request/index'\nimport type { pageRequest } from '@/api/type/common'\nimport type { ApplicationFormType } from '@/api/type/application'\nimport { type Ref } from 'vue'\n\nconst prefix = '/system/resource/application'\n\n/**\n * 获取全部应用\n * @param param\n * @param loading\n */\nconst getAllApplication: (param?: any, loading?: Ref<boolean>) => Promise<Result<any[]>> = (\n  param,\n  loading,\n) => {\n  return get(`${prefix}`, param, loading)\n}\n/**\n * 获取分页应用\n * param {\n \"name\": \"string\",\n }\n */\nconst getApplication: (\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (page, param, loading) => {\n  return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading)\n}\n\n/**\n * 修改应用\n * @param 参数\n */\nconst putApplication: (\n  application_id: string,\n  data: ApplicationFormType,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return put(`${prefix}/${application_id}`, data, undefined, loading)\n}\n\n/**\n * 删除应用\n * @param 参数 application_id\n */\nconst delApplication: (\n  application_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (application_id, loading) => {\n  return del(`${prefix}/${application_id}`, undefined, {}, loading)\n}\n\n/**\n * 应用详情\n * @param 参数 application_id\n */\nconst getApplicationDetail: (\n  application_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, loading) => {\n  return get(`${prefix}/${application_id}`, undefined, loading)\n}\n\n/**\n * 获取AccessToken\n * @param 参数 application_id\n */\nconst getAccessToken: (application_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  application_id,\n  loading,\n) => {\n  return get(`${prefix}/${application_id}/access_token`, undefined, loading)\n}\n/**\n * 修改AccessToken\n * @param 参数 application_id\n * data {\n *  \"is_active\": true\n * }\n */\nconst putAccessToken: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return put(`${prefix}/${application_id}/access_token`, data, undefined, loading)\n}\n\n/**\n * 替换社区版-修改AccessToken\n * @param 参数 application_id\n * data {\n *  \"show_source\": boolean,\n *  \"show_history\": boolean,\n *  \"draggable\": boolean,\n *  \"show_guide\": boolean,\n *  \"avatar\": file,\n *  \"float_icon\": file,\n * }\n */\nconst putXpackAccessToken: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return put(`${prefix}/${application_id}/setting`, data, undefined, loading)\n}\n\n/**\n * 统计\n * @param 参数 application_id, data\n */\nconst getStatistics: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return get(`${prefix}/${application_id}/application_stats`, data, loading)\n}\n/**\n * 统计token消耗\n */\nconst getTokenUsage: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return get(`${prefix}/${application_id}/application_token_usage`, data, loading)\n}\nconst topQuestions: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return get(`${prefix}/${application_id}/top_questions`, data, loading)\n}\n/**\n * 打开调试对话id\n * @param application_id 应用id\n * @param loading 加载器\n * @returns\n */\nconst open: (application_id: string, loading?: Ref<boolean>) => Promise<Result<string>> = (\n  application_id,\n  loading,\n) => {\n  return get(`${prefix}/${application_id}/open`, {}, loading)\n}\n\n/**\n * 生成提示词\n * @param application_id\n * @param model_id\n * @param data\n * @returns\n */\nconst generate_prompt: (application_id:string, model_id:string, data: any) => Promise<any> = (\n  application_id,\n  model_id,\n  data\n) => {\n  const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api'\n  return postStream(`${prefix}/system/resource/application/${application_id}/model/${model_id}/prompt_generate`, data)\n}\n\n\n/**\n * 应用发布\n * @param application_id\n * @param loading\n * @returns\n */\nconst publish: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return put(`${prefix}/${application_id}/publish`, data, {}, loading)\n}\n\n/**\n *\n * @param application_id\n * @param data\n * @param loading\n * @returns\n */\nconst playDemoText: (application_id: string, data: any, loading?: Ref<boolean>) => Promise<any> = (\n  application_id,\n  data,\n  loading,\n) => {\n  return download(`${prefix}/${application_id}/play_demo_text`, 'post', data, undefined, loading)\n}\n\n/**\n * 文本转语音\n */\nconst postTextToSpeech: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return download(`${prefix}/${application_id}/text_to_speech`, 'post', data, undefined, loading)\n}\n/**\n * 语音转文本\n */\nconst speechToText: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return post(`${prefix}/${application_id}/speech_to_text`, data, undefined, loading)\n}\n\n/**\n * 获取应用设置\n * @param application_id 应用id\n * @param loading 加载器\n * @returns\n */\nconst getApplicationSetting: (\n  application_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, loading) => {\n  return get(`${prefix}/${application_id}/setting`, undefined, loading)\n}\n\n/**\n * 导出应用\n */\n\nconst exportApplication = (\n  application_id: string,\n  application_name: string,\n  loading?: Ref<boolean>,\n) => {\n  return exportFile(\n    application_name + '.mk',\n    `${prefix}/${application_id}/export`,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 导入应用\n */\nconst importApplication: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/import`, data, undefined, loading)\n}\n\n/**\n * 对话\n * @param 参数\n * chat_id: string\n * data\n */\nconst chat: (chat_id: string, data: any) => Promise<any> = (chat_id, data) => {\n  const prefix = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api'\n  return postStream(`${prefix}/chat_message/${chat_id}`, data)\n}\n/**\n * 获取对话用户认证类型\n * @param loading 加载器\n * @returns\n */\nconst getChatUserAuthType: (loading?: Ref<boolean>) => Promise<any> = (loading) => {\n  return get(`/chat_user/auth/types`, {}, loading)\n}\n\n/**\n * 获取平台状态\n */\nconst getPlatformStatus: (application_id: string) => Promise<Result<any>> = (application_id) => {\n  return get(`${prefix}/${application_id}/platform/status`)\n}\n/**\n * 更新平台状态\n */\nconst updatePlatformStatus: (application_id: string, data: any) => Promise<Result<any>> = (\n  application_id,\n  data,\n) => {\n  return post(`${prefix}/${application_id}/platform/status`, data)\n}\n/**\n * 获取平台配置\n */\nconst getPlatformConfig: (application_id: string, type: string) => Promise<Result<any>> = (\n  application_id,\n  type,\n) => {\n  return get(`${prefix}/${application_id}/platform/${type}`)\n}\n/**\n * 更新平台配置\n */\nconst updatePlatformConfig: (\n  application_id: string,\n  type: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, type, data, loading) => {\n  return post(`${prefix}/${application_id}/platform/${type}`, data, undefined, loading)\n}\n\n/**\n * mcp 节点\n */\nconst getMcpTools: (application_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  application_id,\n  loading,\n) => {\n  return get(`${prefix}/${application_id}/mcp_tools`, undefined, loading)\n}\n\nexport default {\n  getAllApplication,\n  getApplication,\n  putApplication,\n  delApplication,\n  getApplicationDetail,\n  getAccessToken,\n  putAccessToken,\n  exportApplication,\n  importApplication,\n  getStatistics,\n  open,\n  chat,\n  getChatUserAuthType,\n  getApplicationSetting,\n  getPlatformStatus,\n  updatePlatformStatus,\n  getPlatformConfig,\n  publish,\n  updatePlatformConfig,\n  playDemoText,\n  postTextToSpeech,\n  speechToText,\n  getMcpTools,\n  putXpackAccessToken,\n  generate_prompt,\n  getTokenUsage,\n  topQuestions\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/chat-log.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, exportExcelPost, del, put } from '@/request/index'\nimport type { pageRequest } from '@/api/type/common'\nimport { type Ref } from 'vue'\n\nconst prefix = '/system/resource/application'\n/**\n * 对话记录提交至知识库\n * @param data\n * @param loading\n * @param application_id\n * @param knowledge_id\n */\n\nconst postChatLogAddKnowledge: (\n  application_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, data, loading) => {\n  return post(`${prefix}/${application_id}/add_knowledge`, data, undefined, loading)\n}\n\n/**\n * 对话日志\n * @param 参数\n * application_id\n * param  {\n \"start_time\": \"string\",\n \"end_time\": \"string\",\n }\n */\nconst getChatLog: (\n  application_id: String,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, page, param, loading) => {\n  return get(\n    `${prefix}/${application_id}/chat/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 获得对话日志记录\n * @param 参数\n * application_id, chart_id,order_asc\n */\nconst getChatRecordLog: (\n  application_id: String,\n  chart_id: String,\n  page: pageRequest,\n  loading?: Ref<boolean>,\n  order_asc?: boolean,\n) => Promise<Result<any>> = (application_id, chart_id, page, loading, order_asc) => {\n  return get(\n    `${prefix}/${application_id}/chat/${chart_id}/chat_record/${page.current_page}/${page.page_size}`,\n    { order_asc: order_asc !== undefined ? order_asc : true },\n    loading,\n  )\n}\n\n/**\n * 获取标注段落列表信息\n * @param 参数\n * application_id, chart_id,  chart_record_id\n */\nconst getMarkChatRecord: (\n  application_id: string,\n  chart_id: string,\n  chart_record_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, chart_id, chart_record_id, loading) => {\n  return get(\n    `${prefix}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/improve`,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 修改日志记录内容\n * @param 参数\n * application_id, chart_id,  chart_record_id, knowledge_id, document_id\n * data {\n \"title\": \"string\",\n \"content\": \"string\",\n \"problem_text\": \"string\"\n }\n */\nconst putChatRecordLog: (\n  application_id: String,\n  chart_id: String,\n  chart_record_id: String,\n  knowledge_id: String,\n  document_id: String,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  application_id,\n  chart_id,\n  chart_record_id,\n  knowledge_id,\n  document_id,\n  data,\n  loading,\n) => {\n  return put(\n    `${prefix}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/knowledge/${knowledge_id}/document/${document_id}/improve`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 删除标注\n * @param 参数\n * application_id, chart_id,  chart_record_id, knowledge_id, document_id,paragraph_id\n */\nconst delMarkChatRecord: (\n  application_id: String,\n  chart_id: String,\n  chart_record_id: String,\n  knowledge_id: String,\n  document_id: String,\n  paragraph_id: String,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  application_id,\n  chart_id,\n  chart_record_id,\n  knowledge_id,\n  document_id,\n  paragraph_id,\n  loading,\n) => {\n  return del(\n    `${prefix}/${application_id}/chat/${chart_id}/chat_record/${chart_record_id}/knowledge/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/improve`,\n    undefined,\n    {},\n    loading,\n  )\n}\n\n/**\n * 导出对话日志\n * @param 参数\n * application_id\n * param  {\n \"start_time\": \"string\",\n \"end_time\": \"string\",\n }\n */\nconst postExportChatLog: (\n  application_id: string,\n  application_name: string,\n  param: any,\n  data: any,\n  loading?: Ref<boolean>,\n) => void = (application_id, application_name, param, data, loading) => {\n  exportExcelPost(\n    application_name + '.xlsx',\n    `${prefix}/${application_id}/chat/export`,\n    param,\n    data,\n    loading,\n  )\n}\nconst getChatRecordDetails: (\n  application_id: string,\n  chat_id: string,\n  chat_record_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (application_id, chat_id, chat_record_id, loading) => {\n  return get(\n    `${prefix}/${application_id}/chat/${chat_id}/chat_record/${chat_record_id}`,\n    {},\n    loading,\n  )\n}\nexport default {\n  postChatLogAddKnowledge,\n  getChatLog,\n  getChatRecordLog,\n  getMarkChatRecord,\n  putChatRecordLog,\n  delMarkChatRecord,\n  postExportChatLog,\n  getChatRecordDetails,\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/chat-user.ts",
    "content": "import type {Ref} from 'vue'\nimport {Result} from '@/request/Result'\nimport {get, put } from '@/request/index'\nimport type { ChatUserGroupItem, ChatUserGroupUserItem, putUserGroupUserParams } from '@/api/type/workspaceChatUser'\nimport type { pageRequest, PageList } from '@/api/type/common'\n\n\nconst prefix = '/system/resource/knowledge'\n/**\n * 获取共享知识库用户组列表\n */\nconst getUserGroupList: (resource: any, loading?: Ref<boolean>) =>\n    Promise<Result<ChatUserGroupItem[]>> = (resource, loading) => {\n        return get(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group`, undefined, loading)\n    }\n\n/*\n * 修改共享知识库用户组列表授权\n */\nconst editUserGroupList: (resource: any, data: { user_group_id: string, is_auth: boolean }[], loading?: Ref<boolean>) =>\n    Promise<Result<any>> = (resource, data, loading) => {\n        return put(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group`, data, undefined, loading)\n    }\n\n/**\n * 获取共享知识库用户组的用户列表\n */\nconst getUserGroupUserList: (\n    resource: any,\n    user_group_id: string,\n    page: pageRequest,\n    param?: any,\n    loading?: Ref<boolean>,\n) => Promise<Result<PageList<ChatUserGroupUserItem[]>>> = (resource, user_group_id, page, param, loading) => {\n    return get(\n        `${prefix}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}/${page.current_page}/${page.page_size}`,\n        param,\n        loading,\n    )\n}\n\n/**\n * 更新共享知识库用户组的用户列表\n */\nconst putUserGroupUser: (\n    resource: any,\n    user_group_id:string,\n    data: putUserGroupUserParams[],\n    loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (resource, user_group_id, data, loading) => {\n    return put(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}`, data, undefined, loading)\n}\n\nexport default {\n    getUserGroupList,\n    editUserGroupList,\n    getUserGroupUserList,\n    putUserGroupUser\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/document.ts",
    "content": "import { Result } from '@/request/Result'\nimport {\n  get,\n  post,\n  del,\n  put,\n  exportExcel,\n  exportFile,\n  exportFilePost,\n  exportExcelPost\n} from '@/request/index'\nimport type { Ref } from 'vue'\nimport type { KeyValue } from '@/api/type/common'\nimport type { pageRequest } from '@/api/type/common'\n\nconst prefix = '/system/resource/knowledge'\n\n/**\n * 文档列表（无分页）\n * @param 参数  knowledge_id,\n * param {\n \"   name\": \"string\",\n  }\n */\n\nconst getDocumentList: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id,\n  loading,\n) => {\n  return get(`${prefix}/${knowledge_id}/document`, undefined, loading)\n}\n\n\n/**\n * 文档分页列表\n * @param 参数  knowledge_id,\n * param {\n \"   name\": \"string\",\n  }\n */\n\nconst getDocumentPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, page, param, loading) => {\n  return get(\n    `${prefix}/${knowledge_id}/document/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n/**\n * 文档详情\n * @param 参数 knowledge_id\n */\nconst getDocumentDetail: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, loading) => {\n  return get(`${prefix}/${knowledge_id}/document/${document_id}`, {}, loading)\n}\n\n/**\n * 修改文档\n * @param 参数\n * knowledge_id, document_id,\n * {\n    \"name\": \"string\",\n    \"is_active\": true,\n    \"meta\": {}\n }\n */\nconst putDocument: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data: any, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/${document_id}`, data, undefined, loading)\n}\n\n/**\n * 删除文档\n * @param 参数 knowledge_id, document_id,\n */\nconst delDocument: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, loading) => {\n  return del(`${prefix}/${knowledge_id}/document/${document_id}`, loading)\n}\n\n/**\n * 批量取消文档任务\n * @param 参数 knowledge_id,\n *{\n  \"id_list\": [\n    \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n  ],\n  \"type\": 0\n}\n */\n\nconst putBatchCancelTask: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/batch_cancel_task`, data, undefined, loading)\n}\n\n/**\n * 取消文档任务\n * @param 参数 knowledge_id, document_id,\n */\nconst putCancelTask: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/cancel_task`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 下载原文档\n * @param 参数 knowledge_id\n */\nconst getDownloadSourceFile: (knowledge_id: string, document_id: string, document_name: string) => Promise<Result<any>> = (\n  knowledge_id,\n  document_id,\n  document_name\n) => {\n  return exportFile(document_name, `${prefix}/${knowledge_id}/document/${document_id}/download_source_file`, {}, undefined)\n}\n\nconst postReplaceSourceFile: (knowledge_id: string, document_id: string, data: any) => Promise<Result<any>> = (\n  knowledge_id,\n  document_id,\n  data,\n) => {\n  return post(`${prefix}/${knowledge_id}/document/${document_id}/replace_source_file`, data, {}, undefined)\n}\n\n\n\n/**\n * 导出文档\n * @param document_name 文档名称\n * @param knowledge_id    数据集id\n * @param document_id   文档id\n * @param loading       加载器\n * @returns\n */\nconst exportDocument: (\n  document_name: string,\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_id, loading) => {\n  return exportExcel(\n    document_name.trim() + '.xlsx',\n    `${prefix}/${knowledge_id}/document/${document_id}/export`,\n    {},\n    loading,\n  )\n}\n\nconst exportMulDocument: (\n  document_name: string,\n  knowledge_id: string,\n  document_ids: string[],\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_ids, loading) => {\n  return exportExcelPost(\n    document_name.trim() + '.xlsx',\n    `${prefix}/${knowledge_id}/document/batch_export`,\n    {},\n    document_ids,\n    loading,\n  )\n}\n/**\n * 导出文档\n * @param document_name 文档名称\n * @param knowledge_id    数据集id\n * @param document_id   文档id\n * @param loading       加载器\n * @returns\n */\nconst exportDocumentZip: (\n  document_name: string,\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_id, loading) => {\n  return exportFile(\n    document_name.trim() + '.zip',\n    `${prefix}/${knowledge_id}/document/${document_id}/export_zip`,\n    {},\n    loading,\n  )\n}\n\nconst exportMulDocumentZip: (\n  document_name: string,\n  knowledge_id: string,\n  document_ids: string[],\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_ids, loading) => {\n  return exportFilePost(\n    document_name.trim() + '.zip',\n    `${prefix}/${knowledge_id}/document/batch_export_zip`,\n    {},\n    document_ids,\n    loading,\n  )\n}\n/**\n * 刷新文档向量库\n * @param 参数\n * knowledge_id, document_id,\n * {\n  \"state_list\": [\n    \"string\"\n  ]\n}\n */\nconst putDocumentRefresh: (\n  knowledge_id: string,\n  document_id: string,\n  state_list: Array<string>,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, state_list, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/refresh`,\n    { state_list },\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 同步web站点类型\n * @param 参数\n * knowledge_id, document_id,\n */\nconst putDocumentSync: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/sync`,\n    undefined,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 创建批量文档\n * @param 参数\n{\n  \"name\": \"string\",\n  \"paragraphs\": [\n    {\n      \"content\": \"string\",\n      \"title\": \"string\",\n      \"problem_list\": [\n        {\n          \"id\": \"string\",\n          \"content\": \"string\"\n        }\n      ],\n      \"is_active\": true\n    }\n  ],\n  \"source_file_id\": string\n}\n */\nconst putMulDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/batch_create`, data, {}, loading, 1000 * 60 * 5)\n}\n\n/**\n * 批量删除文档\n * @param 参数 knowledge_id,\n * {\n  \"id_list\": [String]\n}\n */\nconst delMulDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/batch_delete`,\n    { id_list: data },\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量关联\n * @param 参数 knowledge_id,\n{\n  \"document_id_list\": [\n    \"string\"\n  ],\n  \"model_id\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n  \"prompt\": \"string\",\n  \"state_list\": [\n    \"string\"\n  ]\n}\n */\nconst putBatchGenerateRelated: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/batch_generate_related`, data, undefined, loading)\n}\n\n/**\n * 批量修改命中方式\n * @param knowledge_id 知识库id\n * @param data\n * {id_list:[],hit_handling_method:'directly_return|optimization',directly_return_similarity}\n * @param loading\n * @returns\n */\nconst putBatchEditHitHandling: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/batch_hit_handling`, data, undefined, loading)\n}\n\n/**\n * 批量刷新文档向量库\n * @param knowledge_id 知识库id\n * @param data\n{\n  \"id_list\": [\n    \"string\"\n  ],\n  \"state_list\": [\n    \"string\"\n  ]\n}\n * @param loading\n * @returns\n */\nconst putBatchRefresh: (\n  knowledge_id: string,\n  data: any,\n  stateList: Array<string>,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, stateList, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/batch_refresh`,\n    { id_list: data, state_list: stateList },\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量同步文档\n * @param 参数 knowledge_id,\n */\nconst putMulSyncDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/batch_sync`, { id_list: data }, undefined, loading)\n}\n\n/**\n * 批量迁移文档\n * @param 参数 knowledge_id,target_knowledge_id,\n\n */\nconst putMigrateMulDocument: (\n  knowledge_id: string,\n  target_knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, target_knowledge_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/migrate/${target_knowledge_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 导入QA文档\n * @param 参数\n * file\n }\n */\nconst postQADocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/document/qa`, data, undefined, loading)\n}\n\n/**\n * 分段预览（上传文档）\n * @param 参数  file:file,limit:number,patterns:array,with_filter:boolean\n */\nconst postSplitDocument: (knowledge_id: string, data: any) => Promise<Result<any>> = (\n  knowledge_id,\n  data,\n) => {\n  return post(\n    `${prefix}/${knowledge_id}/document/split`,\n    data,\n    undefined,\n    undefined,\n    1000 * 60 * 60,\n  )\n}\n\n/**\n * 分段标识列表\n * @param loading 加载器\n * @returns 分段标识列表\n */\nconst listSplitPattern: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<KeyValue<string, string>>>> = (knowledge_id, loading) => {\n  return get(`${prefix}/${knowledge_id}/document/split_pattern`, {}, loading)\n}\n\n/**\n * 导入表格\n * @param 参数\n * file\n */\nconst postTableDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/document/table`, data, undefined, loading)\n}\n\n/**\n * 获得QA模板\n * @param 参数 fileName,type,\n */\nconst exportQATemplate: (fileName: string, type: string, loading?: Ref<boolean>) => void = (\n  fileName,\n  type,\n  loading,\n) => {\n  return exportExcel(fileName, `${prefix}/document/template/export`, { type }, loading)\n}\n\n/**\n * 获得table模板\n * @param 参数 fileName,type,\n */\nconst exportTableTemplate: (fileName: string, type: string, loading?: Ref<boolean>) => void = (\n  fileName,\n  type,\n  loading,\n) => {\n  return exportExcel(fileName, `${prefix}/document/table_template/export`, { type }, loading)\n}\n\n/**\n * 创建Web站点文档\n * @param 参数\n * {\n \"source_url_list\": [\n \"string\"\n ],\n \"selector\": \"string\"\n }\n }\n */\nconst postWebDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/document/web`, data, undefined, loading)\n}\n\n/**\n * 飞书导入获得相关文档\n * @param 参数\n * {\n \"source_url_list\": [\n \"string\"\n ],\n \"selector\": \"string\"\n }\n }\n */\nconst getLarkDocumentList: (\n  knowledge_id: string,\n  folder_token: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, folder_token, data, loading) => {\n  return post(`${prefix}/lark/${knowledge_id}/${folder_token}/doc_list`, data, undefined, loading)\n}\n\n/**\n * 同步飞书文档\n */\nconst putLarkDocumentSync: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, loading) => {\n  return put(\n    `${prefix}/lark/${knowledge_id}/document/${document_id}/sync`,\n    undefined,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量同步飞书文档\n */\nconst putMulLarkSyncDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/lark/${knowledge_id}/_batch`, { id_list: data }, undefined, loading)\n}\n\n/**\n * 导入飞书文档\n */\nconst importLarkDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/lark/${knowledge_id}/import`, data, null, loading)\n}\n\nconst getDocumentTags: (\n  knowledge_id: string,\n  document_id: string,\n  params: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<string>>> = (knowledge_id, document_id, params, loading) => {\n  return get(`${prefix}/${knowledge_id}/document/${document_id}/tags`, params, loading)\n}\n\nconst postDocumentTags: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/document/${document_id}/tags`, data, null, loading)\n}\n\nconst postMulDocumentTags: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/document/batch_add_tag`, data, null, loading)\n}\n\nconst delMulDocumentTag: (\n  knowledge_id: string,\n  document_id: string,\n  tags: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, tags, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/${document_id}/tags/batch_delete`, tags, null, loading)\n}\n\nconst delDocsTag: (\n  knowledge_id: string,\n  tag_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, tag_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/tag/${tag_id}/docs_delete`, {id_list: data}, null, loading)\n}\n\nexport default {\n  getDocumentList,\n  getDocumentPage,\n  getDocumentDetail,\n  putDocument,\n  delDocument,\n  putBatchCancelTask,\n  putCancelTask,\n  getDownloadSourceFile,\n  postReplaceSourceFile,\n  exportDocument,\n  exportDocumentZip,\n  exportMulDocument,\n  exportMulDocumentZip,\n  putDocumentRefresh,\n  putDocumentSync,\n  putMulDocument,\n  delMulDocument,\n  putBatchGenerateRelated,\n  putBatchEditHitHandling,\n  putBatchRefresh,\n  putMulSyncDocument,\n  putMigrateMulDocument,\n  postQADocument,\n  postSplitDocument,\n  listSplitPattern,\n  postTableDocument,\n  postWebDocument,\n  exportQATemplate,\n  exportTableTemplate,\n  getLarkDocumentList,\n  putLarkDocumentSync,\n  putMulLarkSyncDocument,\n  importLarkDocument,\n  getDocumentTags,\n  postDocumentTags,\n  postMulDocumentTags,\n  delMulDocumentTag,\n  delDocsTag\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/folder.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport { type Ref } from 'vue'\n\nconst prefix = '/system/resource'\n\n\n/**\n * 获得文件夹列表\n * @params 参数\n *  source : APPLICATION, KNOWLEDGE, TOOL\n *  data : {name: string}\n */\nconst getFolder: (\n  source: string,\n  data?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (source, data, loading) => {\n  return get(`${prefix}/${source}/folder`, data, loading)\n}\n\n\n\nexport default {\n  getFolder,\n\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/knowledge.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put, exportFile, exportExcel } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type { Dict, pageRequest } from '@/api/type/common'\n\nconst prefix = '/system/resource/knowledge'\n\n/**\n * 知识库列表（无分页）\n * @param 参数\n * param  {\n \"folder_id\": \"string\",\n \"name\": \"string\",\n \"tool_type\": \"string\",\n desc: string,\n }\n */\nconst getKnowledgeList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  param,\n  loading,\n) => {\n  return get(`${prefix}`, param, loading)\n}\n\n/**\n * 知识库分页列表\n * @param 参数\n * param  {\n \"folder_id\": \"string\",\n \"name\": \"string\",\n \"tool_type\": \"string\",\n desc: string,\n }\n */\nconst getKnowledgeListPage: (\n  page: pageRequest,\n  param?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (page, param, loading) => {\n  return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading)\n}\n\n/**\n * 知识库详情\n * @param 参数 knowledge_id\n */\nconst getKnowledgeDetail: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id,\n  loading,\n) => {\n  return get(`${prefix}/${knowledge_id}`, undefined, loading)\n}\n\n/**\n * 修改知识库信息\n * @param 参数\n * knowledge_id\n * {\n \"name\": \"string\",\n \"desc\": true\n }\n */\nconst putKnowledge: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}`, data, undefined, loading)\n}\n\n/**\n * 删除知识库\n * @param 参数 knowledge_id\n */\nconst delKnowledge: (knowledge_id: String, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  knowledge_id,\n  loading,\n) => {\n  return del(`${prefix}/${knowledge_id}`, undefined, {}, loading)\n}\n\n/**\n * 向量化知识库\n * @param 参数 knowledge_id\n */\nconst putReEmbeddingKnowledge: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, loading) => {\n  return put(`${prefix}/${knowledge_id}/embedding`, undefined, undefined, loading)\n}\n\n/**\n * 导出知识库\n * @param knowledge_name 知识库名称\n * @param knowledge_id   知识库id\n * @returns\n */\nconst exportKnowledge: (\n  knowledge_name: string,\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (knowledge_name, knowledge_id, loading) => {\n  return exportExcel(\n    knowledge_name + '.xlsx',\n    `${prefix}/${knowledge_id}/export`,\n    undefined,\n    loading,\n  )\n}\n/**\n *导出Zip知识库\n * @param knowledge_name 知识库名称\n * @param knowledge_id   知识库id\n * @param loading      加载器\n * @returns\n */\nconst exportZipKnowledge: (\n  knowledge_name: string,\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (knowledge_name, knowledge_id, loading) => {\n  return exportFile(\n    knowledge_name + '.zip',\n    `${prefix}/${knowledge_id}/export_zip`,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 生成关联问题\n * @param knowledge_id 知识库id\n * @param data\n * @param loading\n * @returns\n */\nconst putGenerateRelated: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/generate_related`, data, null, loading)\n}\n/**\n * 命中测试列表\n * @param knowledge_id\n * @param loading\n * @query  { query_text: string, top_number: number, similarity: number }\n * @returns\n */\nconst putKnowledgeHitTest: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/hit_test`, data, undefined, loading)\n}\n\n/**\n * 同步知识库\n * @param 参数 knowledge_id\n * @query 参数 sync_type // 同步类型->replace:替换同步,complete:完整同步\n */\nconst putSyncWebKnowledge: (\n  knowledge_id: string,\n  sync_type: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, sync_type, loading) => {\n  return put(`${prefix}/${knowledge_id}/sync`, undefined, { sync_type }, loading)\n}\n\n/**\n * 获取当前用户可使用的向量化模型列表（没用到）\n * @param application_id\n * @param loading\n * @query  { query_text: string, top_number: number, similarity: number }\n * @returns\n */\nconst getKnowledgeEmdeddingModel: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, loading) => {\n  return get(`${prefix}/${knowledge_id}/emdedding_model`, loading)\n}\n\n/**\n * 获取当前用户可使用的模型列表\n * @param application_id\n * @param loading\n * @query  { query_text: string, top_number: number, similarity: number }\n * @returns\n */\nconst getKnowledgeModel: (loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (loading) => {\n  return get(`${prefix}/model`, loading)\n}\n\nconst putLarkKnowledge: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/lark/${knowledge_id}`, data, undefined, loading)\n}\n\nconst getAllTags: (params: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  params,\n  loading,\n) => {\n  return get(`${prefix}/tags`, params, loading)\n}\n\nconst getTags: (\n  knowledge_id: string,\n  params: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, params, loading) => {\n  return get(`${prefix}/${knowledge_id}/tags`, params, loading)\n}\n\nconst postTags: (\n  knowledge_id: string,\n  tags: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tags, loading) => {\n  return post(`${prefix}/${knowledge_id}/tags`, tags, null, loading)\n}\n\nconst putTag: (\n  knowledge_id: string,\n  tag_id: string,\n  tag: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tag_id, tag, loading) => {\n  return put(`${prefix}/${knowledge_id}/tags/${tag_id}`, tag, null, loading)\n}\n\nconst delTag: (\n  knowledge_id: string,\n  tag_id: string,\n  type: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tag_id, type, loading) => {\n  return del(`${prefix}/${knowledge_id}/tags/${tag_id}/${type}`, null, loading)\n}\n\nconst delMulTag: (\n  knowledge_id: string,\n  tags: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tags, loading) => {\n  return put(`${prefix}/${knowledge_id}/tags/batch_delete`, tags, null, loading)\n}\nconst getKnowledgeWorkflowFormList: (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  node: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  node,\n  loading,\n) => {\n  return post(`${prefix}/${knowledge_id}/datasource/${type}/${id}/form_list`, { node }, {}, loading)\n}\nconst getKnowledgeWorkflowDatasourceDetails: (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  params: any,\n  function_name: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  params,\n  function_name,\n  loading,\n) => {\n  return post(\n    `${prefix}/${knowledge_id}/datasource/${type}/${id}/${function_name}`,\n    params,\n    {},\n    loading,\n  )\n}\nconst workflowAction: (\n  knowledge_id: string,\n  instance: Dict<any>,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, instance, loading) => {\n  return post(`${prefix}/${knowledge_id}/action`, instance, {}, loading)\n}\nconst getWorkflowActionPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  query: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, page, query, loading) => {\n  return get(\n    `${prefix}/${knowledge_id}/action/${page.current_page}/${page.page_size}`,\n    query,\n    loading,\n  )\n}\nconst getWorkflowAction: (\n  knowledge_id: string,\n  knowledge_action_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, knowledge_action_id, loading) => {\n  return get(`${prefix}/${knowledge_id}/action/${knowledge_action_id}`, {}, loading)\n}\n\n/**\n * 保存知识库工作流\n * @param knowledge_id\n * @param data\n * @param loading\n * @returns\n */\nconst putKnowledgeWorkflow: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/workflow`, data, undefined, loading)\n}\n\n/**\n * 导出知识库工作流\n * @param knowledge_id\n * @param knowledge_name\n * @param loading\n * @returns\n */\nconst exportKnowledgeWorkflow = (\n  knowledge_id: string,\n  knowledge_name: string,\n  loading?: Ref<boolean>,\n) => {\n  return exportFile(\n    knowledge_name + '.kbwf',\n    `${prefix}/${knowledge_id}/workflow/export`,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 导入知识库工作流\n * @param knowledge_id\n * @param data\n * @param loading\n * @returns\n */\nconst importKnowledgeWorkflow: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/workflow/import`, data, undefined, loading)\n}\n\nconst workflowUpload: (\n  knowledge_id: string,\n  instance: Dict<any>,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, instance, loading) => {\n  return post(`${prefix}/${knowledge_id}/upload_document`, instance, {}, loading)\n}\n\nconst publish: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id: string,\n  loading,\n) => {\n  return put(`${prefix}/${knowledge_id}/publish`, {}, {}, loading)\n}\n\nconst listKnowledgeVersion: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, loading) => {\n  return get(`${prefix}/${knowledge_id}/knowledge_version`, {}, loading)\n}\n\nconst postTransformWorkflow: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/transform_workflow`, data, undefined, loading)\n}\n\n\nexport default {\n  getKnowledgeList,\n  getKnowledgeListPage,\n  getKnowledgeDetail,\n  putKnowledge,\n  delKnowledge,\n  putReEmbeddingKnowledge,\n  exportKnowledge,\n  exportZipKnowledge,\n  putGenerateRelated,\n  putKnowledgeHitTest,\n  putSyncWebKnowledge,\n  getKnowledgeModel,\n  putLarkKnowledge,\n  getAllTags,\n  getTags,\n  postTags,\n  putTag,\n  delTag,\n  delMulTag,\n  getKnowledgeWorkflowFormList,\n  getKnowledgeWorkflowDatasourceDetails,\n  workflowAction,\n  getWorkflowAction,\n  publish,\n  putKnowledgeWorkflow,\n  listKnowledgeVersion,\n  workflowUpload,\n  getWorkflowActionPage,\n  exportKnowledgeWorkflow,\n  importKnowledgeWorkflow,\n  postTransformWorkflow,\n} as {\n  [key: string]: any\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/model.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type {\n  ListModelRequest,\n  Model,\n  CreateModelRequest,\n  EditModelRequest,\n} from '@/api/type/model'\nimport type { pageRequest } from '@/api/type/common'\nimport type { FormField } from '@/components/dynamics-form/type'\n\nconst prefix = '/system/resource'\n\n/**\n * 获得模型列表\n * @params 参数 name, model_type, model_name\n */\nconst getModelListPage: (\n  page: pageRequest,\n  data?: ListModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<Model>>> = (page, data, loading) => {\n  return get(`${prefix}/model/${page.current_page}/${page.page_size}`, data, loading)\n}\n\n/**\n * 获得下拉选择框模型列表\n * @params 参数 name, model_type, model_name\n */\nconst getSelectModelList: (\n  data?: ListModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<Model>>> = (data, loading) => {\n  return get(`${prefix}/model/model_list`, data, loading).then((ok) => {\n    return {\n      ...ok,\n      data: [\n        ...ok.data.shared_model.map((m: any) => {\n          return { ...m, type: 'share' }\n        }),\n        ...ok.data.model.map((m: any) => {\n          return { ...m, type: 'workspace' }\n        }),\n      ],\n    }\n  })\n}\n\n/**\n * 获取模型参数表单\n * @param model_id 模型id\n * @param loading\n * @returns\n */\nconst getModelParamsForm: (\n  model_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<FormField>>> = (model_id, loading) => {\n  return get(`${prefix}/model/${model_id}/model_params_form`, {}, loading)\n}\n\n/**\n * 创建模型\n * @param request 请求对象\n * @param loading 加载器\n * @returns\n */\nconst createModel: (\n  request: CreateModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Model>> = (request, loading) => {\n  return post(`${prefix}/model`, request, {}, loading)\n}\n\n/**\n * 修改模型\n * @param request 請求對象\n * @param loading 加載器\n * @returns\n */\nconst updateModel: (\n  model_id: string,\n  request: EditModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Model>> = (model_id, request, loading) => {\n  return put(`${prefix}/model/${model_id}`, request, {}, loading)\n}\n\n/**\n * 修改模型参数配置\n * @param request 請求對象\n * @param loading 加載器\n * @returns\n */\nconst updateModelParamsForm: (\n  model_id: string,\n  request: any[],\n  loading?: Ref<boolean>,\n) => Promise<Result<Model>> = (model_id, request, loading) => {\n  return put(`${prefix}/model/${model_id}/model_params_form`, request, {}, loading)\n}\n\n/**\n * 获取模型详情根据模型id 包括认证信息\n * @param model_id 模型id\n * @param loading  加载器\n * @returns\n */\nconst getModelById: (model_id: string, loading?: Ref<boolean>) => Promise<Result<Model>> = (\n  model_id,\n  loading,\n) => {\n  return get(`${prefix}/model/${model_id}`, {}, loading)\n}\n/**\n * 获取模型信息不包括认证信息根据模型id\n * @param model_id 模型id\n * @param loading  加载器\n * @returns\n */\nconst getModelMetaById: (model_id: string, loading?: Ref<boolean>) => Promise<Result<Model>> = (\n  model_id,\n  loading,\n) => {\n  return get(`${prefix}/model/${model_id}/meta`, {}, loading)\n}\n/**\n * 暂停下载\n * @param model_id 模型id\n * @param loading 加载器\n * @returns\n */\nconst pauseDownload: (model_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  model_id,\n  loading,\n) => {\n  return put(`${prefix}/model/${model_id}/pause_download`, undefined, {}, loading)\n}\nconst deleteModel: (model_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  model_id,\n  loading,\n) => {\n  return del(`${prefix}/model/${model_id}`, undefined, {}, loading)\n}\nexport default {\n  getModelListPage,\n  createModel,\n  updateModel,\n  deleteModel,\n  getModelById,\n  getModelMetaById,\n  pauseDownload,\n  getModelParamsForm,\n  updateModelParamsForm,\n  getSelectModelList,\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/paragraph.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport type { pageRequest } from '@/api/type/common'\nimport type { Ref } from 'vue'\nconst prefix = '/system/resource/knowledge'\n\n/**\n * 创建段落\n * @param 参数\n * knowledge_id, document_id\n * {\n      \"content\": \"string\",\n      \"title\": \"string\",\n      \"is_active\": true,\n      \"problem_list\": [\n        {\n          \"content\": \"string\"\n        }\n      ]\n    }\n */\nconst postParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data, loading) => {\n  return post(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 段落列表\n * @param 参数 knowledge_id document_id\n * param {\n          \"title\": \"string\",\n          \"content\": \"string\",\n        }\n */\nconst getParagraphPage: (\n  knowledge_id: string,\n  document_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, page, param, loading) => {\n  return get(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 修改段落\n * @param 参数\n * knowledge_id, document_id, paragraph_id\n * {\n    \"content\": \"string\",\n    \"title\": \"string\",\n    \"is_active\": true,\n      \"problem_list\": [\n        {\n          \"content\": \"string\"\n        }\n      ]\n  }\n */\nconst putParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, paragraph_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 删除段落\n * @param 参数 knowledge_id, document_id, paragraph_id\n */\nconst delParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, paragraph_id, loading) => {\n  return del(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`,\n    undefined,\n    {},\n    loading,\n  )\n}\n\n/**\n * 某段落问题列表\n * @param 参数 knowledge_id，document_id，paragraph_id\n */\nconst getParagraphProblem: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n) => Promise<Result<any>> = (knowledge_id, document_id, paragraph_id: string) => {\n  return get(`${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`)\n}\n\n/**\n * 给某段落创建问题\n * @param 参数\n * knowledge_id, document_id, paragraph_id\n * {\n      content\": \"string\"\n    }\n */\nconst postParagraphProblem: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, paragraph_id, data: any, loading) => {\n  return post(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`,\n    data,\n    {},\n    loading,\n  )\n}\n\n/**\n * 段落调整顺序\n * @param knowledge_id 数据集id\n * @param document_id 文档id\n * @param loading 加载器\n * @query data {\n *              paragraph_id 段落id  new_position 新顺序\n *             }\n */\nconst putAdjustPosition: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/adjust_position`,\n    {},\n    data,\n    loading,\n  )\n}\n\n/**\n * 添加某段落关联问题\n * @param knowledge_id 数据集id\n * @param document_id 文档id\n * @param loading 加载器\n * @query data {\n *              paragraph_id 段落id  problem_id 问题id\n *             }\n */\nconst putAssociationProblem: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/association`,\n    {},\n    data,\n    loading,\n  )\n}\n\n/**\n * 批量删除段落\n * @param 参数 knowledge_id, document_id\n */\nconst putMulParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/batch_delete`,\n    { id_list: data },\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量关联问题\n * @param 参数 knowledge_id, document_id\n * {\n      \"paragraph_id_list\": [\n        \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n      ],\n      \"model_id\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n      \"prompt\": \"string\",\n      \"document_id\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n    }\n */\nconst putBatchGenerateRelated: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/batch_generate_related`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量迁移段落\n * @param 参数 knowledge_id,target_knowledge_id,\n */\nconst putMigrateMulParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  target_knowledge_id: string,\n  target_document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (\n  knowledge_id,\n  document_id,\n  target_knowledge_id,\n  target_document_id,\n  data,\n  loading,\n) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/migrate/knowledge/${target_knowledge_id}/document/${target_document_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 解除某段落关联问题\n * @param 参数 knowledge_id, document_id,\n * @query data {\n *            paragraph_id 段落id  problem_id 问题id\n *         }\n */\nconst putDisassociationProblem: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/unassociation`,\n    {},\n    data,\n    loading,\n  )\n}\n\nexport default {\n  postParagraph,\n  getParagraphPage,\n  putParagraph,\n  delParagraph,\n  getParagraphProblem,\n  postParagraphProblem,\n  putAssociationProblem,\n  putMulParagraph,\n  putBatchGenerateRelated,\n  putMigrateMulParagraph,\n  putDisassociationProblem,\n  putAdjustPosition,\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/problem.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport type { Ref } from 'vue'\nimport type { pageRequest } from '@/api/type/common'\n\nconst prefix = '/system/resource/knowledge'\n\n/**\n * 创建问题\n * @param 参数 knowledge_id\n * data: array[string]\n */\nconst postProblems: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/problem`, data, undefined, loading)\n}\n\n/**\n * 问题分页列表\n * @param 参数  knowledge_id,\n * query {\n     \"content\": \"string\",\n   }\n */\n\nconst getProblemsPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, page, param, loading) => {\n  return get(\n    `${prefix}/${knowledge_id}/problem/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 修改问题\n * @param 参数\n * knowledge_id, problem_id,\n * {\n \"content\": \"string\",\n }\n */\nconst putProblems: (\n  knowledge_id: string,\n  problem_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, problem_id, data: any, loading) => {\n  return put(`${prefix}/${knowledge_id}/problem/${problem_id}`, data, undefined, loading)\n}\n\n/**\n * 删除问题\n * @param 参数 knowledge_id, problem_id,\n */\nconst delProblems: (\n  knowledge_id: string,\n  problem_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, problem_id, loading) => {\n  return del(`${prefix}/${knowledge_id}/problem/${problem_id}`, loading)\n}\n\n/**\n * 问题详情\n * @param 参数\n * knowledge_id, problem_id,\n */\nconst getDetailProblems: (\n  knowledge_id: string,\n  problem_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, problem_id, loading) => {\n  return get(`${prefix}/${knowledge_id}/problem/${problem_id}/paragraph`, undefined, loading)\n}\n\n/**\n * 批量关联段落\n * @param 参数 knowledge_id,\n * {\n      \"problem_id_list\": \"Array\",\n      \"paragraph_list\": \"Array\",\n    }\n */\nconst putMulAssociationProblem: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/problem/batch_association`, data, undefined, loading)\n}\n\n/**\n * 批量删除问题\n * @param 参数 knowledge_id,\n * data: array[string]\n */\nconst putMulProblem: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/problem/batch_delete`, data, undefined, loading)\n}\n\nexport default {\n  postProblems,\n  getProblemsPage,\n  putProblems,\n  delProblems,\n  getDetailProblems,\n  putMulAssociationProblem,\n  putMulProblem,\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/resource-authorization.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, put, post, del } from '@/request/index'\nimport type { Ref } from 'vue'\nimport type { pageRequest } from '@/api/type/common'\nconst prefix = 'system/workspace'\n\n/**\n * 系统资源授权获取资源权限\n * @query 参数\n */\nconst getResourceAuthorization: (\n  workspace_id: string,\n  target: string,\n  resource: string,\n  page: pageRequest,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, target, resource, page, params, loading) => {\n  return get(\n    `${prefix}/${workspace_id}/resource_management/resource/${target}/resource/${resource}/${page.current_page}/${page.page_size}`,\n    params,\n    loading,\n  )\n}\n/**\n * 系统资源授权修改成员权限\n * @param 参数 member_id\n * @param 参数 {\n     [\n      {\n        \"target_id\": \"string\",\n        \"permission\": \"NOT_AUTH\"\n      }\n    ]\n        }\n */\nconst putResourceAuthorization: (\n  workspace_id: string,\n  target: string,\n  resource: string,\n  body: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, target, resource, body, loading) => {\n  return put(\n    `${prefix}/${workspace_id}/resource_management/resource/${target}/resource/${resource}`,\n    body,\n    {},\n    loading,\n  )\n}\n\nexport default {\n  getResourceAuthorization,\n  putResourceAuthorization,\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/resource-mapping.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, put, post, del} from '@/request/index'\nimport type {Ref} from 'vue'\nimport type {pageRequest} from '@/api/type/common'\n\nconst prefix = '/system/resource'\n\nconst getResourceMapping: (\n  workspace_id: string,\n  resource: string,\n  resource_id: string,\n  page: pageRequest,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, resource, resource_id, page, params, loading) => {\n  return get(\n    `${prefix}/resource_mapping/${resource}/${resource_id}/${page.current_page}/${page.page_size}`,\n    params,\n    loading,\n  )\n}\n\nexport default {\n  getResourceMapping,\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/tool.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put, exportFile } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type { pageRequest } from '@/api/type/common'\nimport type { toolData } from '@/api/type/tool'\n\nconst prefix = '/system/resource/tool'\n\n/**\n * 工具列表带分页（无分页）\n * @params 参数\n *  param  {\n        \"name\": \"string\",\n        \"tool_type\": \"string\",\n    }\n */\nconst getToolList: (data?: any, loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (\n  data,\n  loading,\n) => {\n  return get(`${prefix}`, data, loading)\n}\n\n/**\n * 工具列表带分页（无分页）\n * @params 参数\n *  param  {\n        \"name\": \"string\",\n        \"tool_type\": \"string\",\n    }\n */\nconst getAllToolList: (data?: any, loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (\n  data,\n  loading,\n) => {\n  return get(`${prefix}/tool_list`, data, loading)\n}\n\n/**\n * 工具列表带分页\n * @param 参数\n * param  {\n \"name\": \"string\",\n \"tool_type\": \"string\",\n }\n */\nconst getToolListPage: (\n  page: pageRequest,\n  param?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (page, param, loading) => {\n  return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading)\n}\n\n/**\n * 获取工具详情\n * @param tool_id 工具id\n * @param loading 加载器\n * @returns 工具详情\n */\nconst getToolById: (tool_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  tool_id,\n  loading,\n) => {\n  return get(`${prefix}/${tool_id}`, undefined, loading)\n}\n\n\n/**\n * 修改工具\n * @param 参数\n\n */\nconst putTool: (tool_id: string, data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  tool_id,\n  data,\n  loading,\n) => {\n  return put(`${prefix}/${tool_id}`, data, undefined, loading)\n}\n\nconst postToolTestConnection: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/test_connection`, data, undefined, loading)\n}\n\n\n/**\n * 删除工具\n * @param 参数 tool_id\n */\nconst delTool: (tool_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  tool_id,\n  loading,\n) => {\n  return del(`${prefix}/${tool_id}`, undefined, {}, loading)\n}\n\nconst putToolIcon: (id: string, data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  id,\n  data,\n  loading,\n) => {\n  return put(`${prefix}/${id}/edit_icon`, data, undefined, loading)\n}\n\nconst exportTool = (id: string, name: string, loading?: Ref<boolean>) => {\n  return exportFile(name + '.tool', `${prefix}/${id}/export`, undefined, loading)\n}\n\n/**\n * 调试工具\n * @param 参数\n\n */\nconst postToolDebug: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data: any,\n  loading,\n) => {\n  return post(`${prefix}/debug`, data, undefined, loading)\n}\n\nconst postPylint: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  code,\n  loading,\n) => {\n  return post(`${prefix}/pylint`, { code }, {}, loading)\n}\n\nconst pageToolRecord = (\n  tool_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => {\n  return get(\n    `${prefix}/${tool_id}/tool_record/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\nconst getToolRecordDetail = (\n tool_id: string,\n record_id: string\n) => {\n  return get(`${prefix}/${tool_id}/tool_record/${record_id}`)\n}\n\nconst uploadSkillFile: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return put(`${prefix}/upload_skill_file`, data, undefined, loading)\n}\n\n\n\nexport default {\n  getToolListPage,\n  getToolList,\n  getAllToolList,\n  putTool,\n  getToolById,\n  postToolDebug,\n  postPylint,\n  exportTool,\n  putToolIcon,\n  delTool,\n  postToolTestConnection,\n  pageToolRecord,\n  getToolRecordDetail,\n  uploadSkillFile,\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/trigger.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put, exportFile } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type { TriggerData } from '../type/trigger'\n\n\n\nconst prefix = 'system/resource'\n\n\n/**\n * 资源端创建触发器\n * @param source_type  资源类型\n * @param source_id    资源id\n * @param data         数据\n * @param loading      加载器\n * @returns\n */\nconst postResourceTrigger: (\n  source_type: string,\n  source_id: string,\n  data: TriggerData,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (source_type, source_id, data, loading) => {\n  return post(\n    `${prefix}/${source_type}/${source_id}/trigger`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 资源端触发器列表\n * @param source_type\n * @param source_id\n * @param loading\n * @returns\n */\nconst getResourceTriggerList: (\n  source_type: string,\n  source_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (source_type, source_id, loading) => {\n  return get(\n    `${prefix}/${source_type}/${source_id}/trigger`,\n    undefined,\n    loading\n  )\n}\n\n/**\n * 资源端触发器详情\n * @param source_type\n * @param source_id\n * @param trigger_id\n * @param loading\n * @returns\n */\nconst getResourceTriggerDetail: (\n    source_type: string,\n  source_id: string,\n  trigger_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (source_type, source_id, trigger_id, loading) => {\n  return get(\n    `${prefix}/${source_type}/${source_id}/trigger/${trigger_id}`,\n    undefined,\n    loading\n  )\n}\n\n/**\n * 资源端删除触发器\n * @param source_type\n * @param source_id\n * @param trigger_id\n * @param loading\n * @returns\n */\nconst deleteResourceTrigger: (\n    source_type: string,\n  source_id: string,\n  trigger_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (source_type, source_id, trigger_id, loading) => {\n  return del(\n    `${prefix}/${source_type}/${source_id}/trigger/${trigger_id}`,\n    undefined,\n    {},\n    loading\n  )\n}\n\n/**\n * 资源端修改触发器\n * @param source_type 资源类型\n * @param source_id   资源id\n * @param trigger_id  触发器id\n * @param data        触发器数据\n * @param loading     加载器\n * @returns\n */\nconst putResourceTrigger: (\n  source_type: string,\n  source_id: string,\n  trigger_id: string,\n  data: TriggerData,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (source_type, source_id, trigger_id, data, loading) => {\n  return put(\n    `${prefix}/${source_type}/${source_id}/trigger/${trigger_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\nexport default {\n  postResourceTrigger,\n  getResourceTriggerList,\n  getResourceTriggerDetail,\n  deleteResourceTrigger,\n  putResourceTrigger\n}\n"
  },
  {
    "path": "ui/src/api/system-resource-management/workflow-version.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, put } from '@/request/index'\nimport { type Ref } from 'vue'\n\nconst prefix = '/system/resource/application'\n\n/**\n * workflow历史版本\n */\nconst getWorkFlowVersion: (\n  application_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, loading) => {\n  return get(`${prefix}/${application_id}/application_version`, undefined, loading)\n}\n\n/**\n * workflow历史版本详情\n */\nconst getWorkFlowVersionDetail: (\n  application_id: string,\n  application_version_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, application_version_id, loading) => {\n  return get(\n    `${prefix}/${application_id}/application_version/${application_version_id}`,\n    undefined,\n    loading,\n  )\n}\n/**\n * 修改workflow历史版本\n */\nconst putWorkFlowVersion: (\n  application_id: string,\n  application_version_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (application_id, application_version_id, data, loading) => {\n  return put(\n    `${prefix}/${application_id}/application_version/${application_version_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\nexport default {\n  getWorkFlowVersion,\n  getWorkFlowVersionDetail,\n  putWorkFlowVersion,\n}\n"
  },
  {
    "path": "ui/src/api/system-settings/auth-setting.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, put} from '@/request/index'\nimport {type Ref} from 'vue'\n\nconst prefix = '/auth'\n/**\n * 获取认证设置\n */\nconst getAuthSetting: (auth_type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (auth_type, loading) => {\n  return get(`${prefix}/${auth_type}/detail`, undefined, loading)\n}\n\n/**\n * ldap连接测试\n */\nconst postAuthSetting: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return post(`${prefix}/connection`, data, undefined, loading)\n}\n\n/**\n * 修改邮箱设置\n */\nconst putAuthSetting: (auth_type: string, data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  auth_type,\n  data,\n  loading\n) => {\n  return put(`${prefix}/${auth_type}/info`, data, undefined, loading)\n}\n/**\n * 登录设置\n */\nconst putLoginSetting: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return put(`${prefix}/setting`, data, undefined, loading)\n}\n/**\n * 获取登录设置\n */\nconst getLoginSetting: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get(`${prefix}/setting`, undefined, loading)\n}\n\nconst getLoginAuthSetting: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get(`login/auth/setting`, undefined, loading)\n}\n\n/**\n * 获取认证设置\n */\nconst getLoginViewAuthSetting: (auth_type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (auth_type, loading) => {\n  return get(`login${prefix}/${auth_type}/detail`, undefined, loading)\n}\n\nexport default {\n  getAuthSetting,\n  postAuthSetting,\n  putAuthSetting,\n  putLoginSetting,\n  getLoginSetting,\n  getLoginAuthSetting,\n  getLoginViewAuthSetting\n}\n"
  },
  {
    "path": "ui/src/api/system-settings/email-setting.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport type { pageRequest } from '@/api/type/common'\nimport { type Ref } from 'vue'\n\nconst prefix = '/email_setting'\n/**\n * 获取邮箱设置\n */\nconst getEmailSetting: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get(`${prefix}`, undefined, loading)\n}\n\n/**\n * 邮箱测试\n */\nconst postTestEmail: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return post(`${prefix}`, data, undefined, loading)\n}\n\n/**\n * 修改邮箱设置\n */\nconst putEmailSetting: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return put(`${prefix}`, data, undefined, loading)\n}\n\nexport default {\n  getEmailSetting,\n  postTestEmail,\n  putEmailSetting\n}\n"
  },
  {
    "path": "ui/src/api/system-settings/platform-source.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport { type Ref } from 'vue'\n\nconst prefix = '/platform'\nconst getPlatformInfo: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get(`${prefix}/source`, undefined, loading)\n}\n\nconst updateConfig: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return post(`${prefix}/source`, data, undefined, loading)\n}\n\nconst validateConnection: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading\n) => {\n  return put(`${prefix}/source`, data, undefined, loading)\n}\nexport default {\n  getPlatformInfo,\n  updateConfig,\n  validateConnection\n}\n"
  },
  {
    "path": "ui/src/api/system-settings/theme.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, del, put} from '@/request/index'\nimport type {Ref} from 'vue'\n\nconst prefix = '/display'\n\n/**\n * 查看外观设置\n */\nconst getThemeInfo: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get(`${prefix}/info`, undefined, loading)\n}\n\n/**\n * 更新外观设置\n * @param 参数\n * * formData {\n *   theme\n *   icon\n *   loginLogo\n *   loginImage\n *   title\n *   slogan\n * }\n */\nconst postThemeInfo: (data: any, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  data,\n  loading\n) => {\n  return put(`${prefix}/update`, data, undefined, loading)\n}\n\nexport default {\n  getThemeInfo,\n  postThemeInfo\n}\n"
  },
  {
    "path": "ui/src/api/system-shared/authorization.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put, exportFile, exportExcel } from '@/request/index'\nimport { type Ref } from 'vue'\n\nconst prefix = '/system/shared'\n\nconst getSharedAuthorizationKnowledge: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, loading) => {\n  return get(`${prefix}/knowledge/${knowledge_id}/authorization`, {}, loading)\n}\n\nconst postSharedAuthorizationKnowledge: (\n  knowledge_id: string,\n  param?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, param, loading) => {\n  return post(`${prefix}/knowledge/${knowledge_id}/authorization`, param, loading)\n}\n\nconst getSharedAuthorizationTool: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, loading) => {\n  return get(`${prefix}/tool/${knowledge_id}/authorization`, {}, loading)\n}\n\nconst postSharedAuthorizationTool: (\n  knowledge_id: string,\n  param?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, param, loading) => {\n  return post(`${prefix}/tool/${knowledge_id}/authorization`, param, loading)\n}\n\nconst getSharedAuthorizationModel: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, loading) => {\n  return get(`${prefix}/model/${knowledge_id}/authorization`, {}, loading)\n}\n\nconst postSharedAuthorizationModel: (\n  knowledge_id: string,\n  param?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, param, loading) => {\n  return post(`${prefix}/model/${knowledge_id}/authorization`, param, loading)\n}\n\nexport default {\n  getSharedAuthorizationKnowledge,\n  postSharedAuthorizationKnowledge,\n  getSharedAuthorizationTool,\n  postSharedAuthorizationTool,\n  getSharedAuthorizationModel,\n  postSharedAuthorizationModel,\n} as {\n  [key: string]: any\n}\n"
  },
  {
    "path": "ui/src/api/system-shared/chat-user.ts",
    "content": "import type {Ref} from 'vue'\nimport {Result} from '@/request/Result'\nimport {get, put } from '@/request/index'\nimport type { ChatUserGroupItem, ChatUserGroupUserItem, putUserGroupUserParams } from '@/api/type/workspaceChatUser'\nimport type { pageRequest, PageList } from '@/api/type/common'\n\n\nconst prefix = '/system/shared/knowledge'\n/**\n * 获取共享知识库用户组列表\n */\nconst getUserGroupList: (resource: any, loading?: Ref<boolean>) =>\n    Promise<Result<ChatUserGroupItem[]>> = (resource, loading) => {\n        return get(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group`, undefined, loading)\n    }\n\n/*\n * 修改共享知识库用户组列表授权\n */\nconst editUserGroupList: (resource: any, data: { user_group_id: string, is_auth: boolean }[], loading?: Ref<boolean>) =>\n    Promise<Result<any>> = (resource, data, loading) => {\n        return put(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group`, data, undefined, loading)\n    }\n\n/**\n * 获取共享知识库用户组的用户列表\n */\nconst getUserGroupUserList: (\n    resource: any,\n    user_group_id: string,\n    page: pageRequest,\n    params?: any,\n    loading?: Ref<boolean>,\n) => Promise<Result<PageList<ChatUserGroupUserItem[]>>> = (resource, user_group_id, page, params, loading) => {\n    return get(\n        `${prefix}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}/${page.current_page}/${page.page_size}`,\n        params,\n        loading,\n    )\n}\n\n/**\n * 更新共享知识库用户组的用户列表\n */\nconst putUserGroupUser: (\n    resource: any,\n    user_group_id:string,\n    data: putUserGroupUserParams[],\n    loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (resource, user_group_id, data, loading) => {\n    return put(`${prefix}/${resource.resource_type}/${resource.resource_id}/user_group_id/${user_group_id}`, data, undefined, loading)\n}\n\nexport default {\n    getUserGroupList,\n    editUserGroupList,\n    getUserGroupUserList,\n    putUserGroupUser\n}\n"
  },
  {
    "path": "ui/src/api/system-shared/document.ts",
    "content": "import { Result } from '@/request/Result'\nimport {\n  get,\n  post,\n  del,\n  put,\n  exportExcel,\n  exportFile,\n  exportExcelPost,\n  exportFilePost\n} from '@/request/index'\nimport type { Ref } from 'vue'\nimport type { KeyValue } from '@/api/type/common'\nimport type { pageRequest } from '@/api/type/common'\n\nconst prefix = '/system/shared/knowledge'\n\n/**\n * 文档列表（无分页）\n * @param 参数  knowledge_id,\n * param {\n \"   name\": \"string\",\n  }\n */\n\nconst getDocumentList: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id,\n  loading,\n) => {\n  return get(`${prefix}/${knowledge_id}/document`, undefined, loading)\n}\n\n\n/**\n * 文档分页列表\n * @param 参数  knowledge_id,\n * param {\n \"   name\": \"string\",\n  }\n */\n\nconst getDocumentPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, page, param, loading) => {\n  return get(\n    `${prefix}/${knowledge_id}/document/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n/**\n * 文档详情\n * @param 参数 knowledge_id\n */\nconst getDocumentDetail: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, loading) => {\n  return get(`${prefix}/${knowledge_id}/document/${document_id}`, {}, loading)\n}\n\n/**\n * 修改文档\n * @param 参数\n * knowledge_id, document_id,\n * {\n    \"name\": \"string\",\n    \"is_active\": true,\n    \"meta\": {}\n }\n */\nconst putDocument: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data: any, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/${document_id}`, data, undefined, loading)\n}\n\n/**\n * 删除文档\n * @param 参数 knowledge_id, document_id,\n */\nconst delDocument: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, loading) => {\n  return del(`${prefix}/${knowledge_id}/document/${document_id}`, loading)\n}\n\n/**\n * 批量取消文档任务\n * @param 参数 knowledge_id,\n *{\n  \"id_list\": [\n    \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n  ],\n  \"type\": 0\n}\n */\n\nconst putBatchCancelTask: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/batch_cancel_task`, data, undefined, loading)\n}\n\n/**\n * 取消文档任务\n * @param 参数 knowledge_id, document_id,\n */\nconst putCancelTask: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/cancel_task`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 下载原文档\n * @param 参数 knowledge_id\n */\nconst getDownloadSourceFile: (knowledge_id: string, document_id: string, document_name: string) => Promise<Result<any>> = (\n  knowledge_id,\n  document_id,\n  document_name\n) => {\n  return exportFile(document_name, `${prefix}/${knowledge_id}/document/${document_id}/download_source_file`, {}, undefined)\n}\n\nconst postReplaceSourceFile: (knowledge_id: string, document_id: string, data: any) => Promise<Result<any>> = (\n  knowledge_id,\n  document_id,\n  data,\n) => {\n  return post(`${prefix}/${knowledge_id}/document/${document_id}/replace_source_file`, data, {}, undefined)\n}\n\n\n\n/**\n * 导出文档\n * @param document_name 文档名称\n * @param knowledge_id    数据集id\n * @param document_id   文档id\n * @param loading       加载器\n * @returns\n */\nconst exportDocument: (\n  document_name: string,\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_id, loading) => {\n  return exportExcel(\n    document_name.trim() + '.xlsx',\n    `${prefix}/${knowledge_id}/document/${document_id}/export`,\n    {},\n    loading,\n  )\n}\n\nconst exportMulDocument: (\n  document_name: string,\n  knowledge_id: string,\n  document_ids: string[],\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_ids, loading) => {\n  return exportExcelPost(\n    document_name.trim() + '.xlsx',\n    `${prefix}/${knowledge_id}/document/batch_export`,\n    {},\n    document_ids,\n    loading,\n  )\n}\n/**\n * 导出文档\n * @param document_name 文档名称\n * @param knowledge_id    数据集id\n * @param document_id   文档id\n * @param loading       加载器\n * @returns\n */\nconst exportDocumentZip: (\n  document_name: string,\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_id, loading) => {\n  return exportFile(\n    document_name.trim() + '.zip',\n    `${prefix}/${knowledge_id}/document/${document_id}/export_zip`,\n    {},\n    loading,\n  )\n}\n\nconst exportMulDocumentZip: (\n  document_name: string,\n  knowledge_id: string,\n  document_ids: string[],\n  loading?: Ref<boolean>,\n) => Promise<any> = (document_name, knowledge_id, document_ids, loading) => {\n  return exportFilePost(\n    document_name.trim() + '.zip',\n    `${prefix}/${knowledge_id}/document/batch_export_zip`,\n    {},\n    document_ids,\n    loading,\n  )\n}\n/**\n * 刷新文档向量库\n * @param 参数\n * knowledge_id, document_id,\n * {\n  \"state_list\": [\n    \"string\"\n  ]\n}\n */\nconst putDocumentRefresh: (\n  knowledge_id: string,\n  document_id: string,\n  state_list: Array<string>,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, state_list, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/refresh`,\n    { state_list },\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 同步web站点类型\n * @param 参数\n * knowledge_id, document_id,\n */\nconst putDocumentSync: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/sync`,\n    undefined,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 创建批量文档\n * @param 参数\n{\n  \"name\": \"string\",\n  \"paragraphs\": [\n    {\n      \"content\": \"string\",\n      \"title\": \"string\",\n      \"problem_list\": [\n        {\n          \"id\": \"string\",\n          \"content\": \"string\"\n        }\n      ],\n      \"is_active\": true\n    }\n  ],\n  \"source_file_id\": string\n}\n */\nconst putMulDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/batch_create`, data, {}, loading, 1000 * 60 * 5)\n}\n\n/**\n * 批量删除文档\n * @param 参数 knowledge_id,\n * {\n  \"id_list\": [String]\n}\n */\nconst delMulDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/batch_delete`,\n    { id_list: data },\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量关联\n * @param 参数 knowledge_id,\n{\n  \"document_id_list\": [\n    \"string\"\n  ],\n  \"model_id\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n  \"prompt\": \"string\",\n  \"state_list\": [\n    \"string\"\n  ]\n}\n */\nconst putBatchGenerateRelated: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/batch_generate_related`, data, undefined, loading)\n}\n\n/**\n * 批量修改命中方式\n * @param knowledge_id 知识库id\n * @param data\n * {id_list:[],hit_handling_method:'directly_return|optimization',directly_return_similarity}\n * @param loading\n * @returns\n */\nconst putBatchEditHitHandling: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/batch_hit_handling`, data, undefined, loading)\n}\n\n/**\n * 批量刷新文档向量库\n * @param knowledge_id 知识库id\n * @param data\n{\n  \"id_list\": [\n    \"string\"\n  ],\n  \"state_list\": [\n    \"string\"\n  ]\n}\n * @param loading\n * @returns\n */\nconst putBatchRefresh: (\n  knowledge_id: string,\n  data: any,\n  stateList: Array<string>,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, stateList, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/batch_refresh`,\n    { id_list: data, state_list: stateList },\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量同步文档\n * @param 参数 knowledge_id,\n */\nconst putMulSyncDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/batch_sync`, { id_list: data }, undefined, loading)\n}\n\n/**\n * 批量迁移文档\n * @param 参数 knowledge_id,target_knowledge_id,\n\n */\nconst putMigrateMulDocument: (\n  knowledge_id: string,\n  target_knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, target_knowledge_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/migrate/${target_knowledge_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 导入QA文档\n * @param 参数\n * file\n }\n */\nconst postQADocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/document/qa`, data, undefined, loading)\n}\n\n/**\n * 分段预览（上传文档）\n * @param 参数  file:file,limit:number,patterns:array,with_filter:boolean\n */\nconst postSplitDocument: (knowledge_id: string, data: any) => Promise<Result<any>> = (\n  knowledge_id,\n  data,\n) => {\n  return post(\n    `${prefix}/${knowledge_id}/document/split`,\n    data,\n    undefined,\n    undefined,\n    1000 * 60 * 60,\n  )\n}\n\n/**\n * 分段标识列表\n * @param loading 加载器\n * @returns 分段标识列表\n */\nconst listSplitPattern: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<KeyValue<string, string>>>> = (knowledge_id, loading) => {\n  return get(`${prefix}/${knowledge_id}/document/split_pattern`, {}, loading)\n}\n\n/**\n * 导入表格\n * @param 参数\n * file\n */\nconst postTableDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/document/table`, data, undefined, loading)\n}\n\n/**\n * 获得QA模板\n * @param 参数 fileName,type,\n */\nconst exportQATemplate: (fileName: string, type: string, loading?: Ref<boolean>) => void = (\n  fileName,\n  type,\n  loading,\n) => {\n  return exportExcel(fileName, `${prefix}/document/template/export`, { type }, loading)\n}\n\n/**\n * 获得table模板\n * @param 参数 fileName,type,\n */\nconst exportTableTemplate: (fileName: string, type: string, loading?: Ref<boolean>) => void = (\n  fileName,\n  type,\n  loading,\n) => {\n  return exportExcel(fileName, `${prefix}/document/table_template/export`, { type }, loading)\n}\n\n/**\n * 创建Web站点文档\n * @param 参数\n * {\n \"source_url_list\": [\n \"string\"\n ],\n \"selector\": \"string\"\n }\n }\n */\nconst postWebDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/document/web`, data, undefined, loading)\n}\n\n/**\n * 飞书导入获得相关文档\n * @param 参数\n * {\n \"source_url_list\": [\n \"string\"\n ],\n \"selector\": \"string\"\n }\n }\n */\nconst getLarkDocumentList: (\n  knowledge_id: string,\n  folder_token: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, folder_token, data, loading) => {\n  return post(`${prefix}/lark/${knowledge_id}/${folder_token}/doc_list`, data, undefined, loading)\n}\n\n/**\n * 同步飞书文档\n */\nconst putLarkDocumentSync: (\n  knowledge_id: string,\n  document_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, loading) => {\n  return put(\n    `${prefix}/lark/${knowledge_id}/document/${document_id}/sync`,\n    undefined,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量同步飞书文档\n */\nconst putMulLarkSyncDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/lark/${knowledge_id}/_batch`, { id_list: data }, undefined, loading)\n}\n\n/**\n * 导入飞书文档\n */\nconst importLarkDocument: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/lark/${knowledge_id}/import`, data, null, loading)\n}\n\n\nconst getDocumentTags: (\n  knowledge_id: string,\n  document_id: string,\n  params: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<string>>> = (knowledge_id, document_id, params, loading) => {\n  return get(`${prefix}/${knowledge_id}/document/${document_id}/tags`, params, loading)\n}\n\nconst postDocumentTags: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/document/${document_id}/tags`, data, null, loading)\n}\n\nconst postMulDocumentTags: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/document/batch_add_tag`, data, null, loading)\n}\n\nconst delMulDocumentTag: (\n  knowledge_id: string,\n  document_id: string,\n  tags: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, tags, loading) => {\n  return put(`${prefix}/${knowledge_id}/document/${document_id}/tags/batch_delete`, tags, null, loading)\n}\n\nconst delDocsTag: (\n  knowledge_id: string,\n  tag_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, tag_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/tag/${tag_id}/docs_delete`, {id_list: data}, null, loading)\n}\n\n\nexport default {\n  getDocumentList,\n  getDocumentPage,\n  getDocumentDetail,\n  putDocument,\n  delDocument,\n  putBatchCancelTask,\n  putCancelTask,\n  getDownloadSourceFile,\n  postReplaceSourceFile,\n  exportDocument,\n  exportDocumentZip,\n  exportMulDocument,\n  exportMulDocumentZip,\n  putDocumentRefresh,\n  putDocumentSync,\n  putMulDocument,\n  delMulDocument,\n  putBatchGenerateRelated,\n  putBatchEditHitHandling,\n  putBatchRefresh,\n  putMulSyncDocument,\n  putMigrateMulDocument,\n  postQADocument,\n  postSplitDocument,\n  listSplitPattern,\n  postTableDocument,\n  postWebDocument,\n  exportQATemplate,\n  exportTableTemplate,\n  getLarkDocumentList,\n  putLarkDocumentSync,\n  putMulLarkSyncDocument,\n  importLarkDocument,\n  getDocumentTags,\n  postDocumentTags,\n  postMulDocumentTags,\n  delMulDocumentTag,\n  delDocsTag\n}\n"
  },
  {
    "path": "ui/src/api/system-shared/knowledge.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put, exportFile, exportExcel } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type { Dict, pageRequest } from '@/api/type/common'\nimport type { knowledgeData } from '@/api/type/knowledge'\n\nconst prefix = '/system/shared/knowledge'\n\n/**\n * 知识库列表（无分页）\n * @param 参数\n * param  {\n \"folder_id\": \"string\",\n \"name\": \"string\",\n \"tool_type\": \"string\",\n desc: string,\n }\n */\nconst getKnowledgeList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  param,\n  loading,\n) => {\n  return get(`${prefix}`, param, loading)\n}\n\n/**\n * 知识库分页列表\n * @param 参数\n * param  {\n \"folder_id\": \"string\",\n \"name\": \"string\",\n \"tool_type\": \"string\",\n desc: string,\n }\n */\nconst getKnowledgeListPage: (\n  page: pageRequest,\n  param?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (page, param, loading) => {\n  return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading)\n}\n\n/**\n * 知识库详情\n * @param 参数 knowledge_id\n */\nconst getKnowledgeDetail: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id,\n  loading,\n) => {\n  return get(`${prefix}/${knowledge_id}`, undefined, loading)\n}\n\n/**\n * 修改知识库信息\n * @param 参数\n * knowledge_id\n * {\n \"name\": \"string\",\n \"desc\": true\n }\n */\nconst putKnowledge: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}`, data, undefined, loading)\n}\n\n/**\n * 删除知识库\n * @param 参数 knowledge_id\n */\nconst delKnowledge: (knowledge_id: String, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  knowledge_id,\n  loading,\n) => {\n  return del(`${prefix}/${knowledge_id}`, undefined, {}, loading)\n}\n\n/**\n * 向量化知识库\n * @param 参数 knowledge_id\n */\nconst putReEmbeddingKnowledge: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, loading) => {\n  return put(`${prefix}/${knowledge_id}/embedding`, undefined, undefined, loading)\n}\n\n/**\n * 导出知识库\n * @param knowledge_name 知识库名称\n * @param knowledge_id   知识库id\n * @returns\n */\nconst exportKnowledge: (\n  knowledge_name: string,\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (knowledge_name, knowledge_id, loading) => {\n  return exportExcel(\n    knowledge_name + '.xlsx',\n    `${prefix}/${knowledge_id}/export`,\n    undefined,\n    loading,\n  )\n}\n/**\n *导出Zip知识库\n * @param knowledge_name 知识库名称\n * @param knowledge_id   知识库id\n * @param loading      加载器\n * @returns\n */\nconst exportZipKnowledge: (\n  knowledge_name: string,\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<any> = (knowledge_name, knowledge_id, loading) => {\n  return exportFile(\n    knowledge_name + '.zip',\n    `${prefix}/${knowledge_id}/export_zip`,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 生成关联问题\n * @param knowledge_id 知识库id\n * @param data\n * @param loading\n * @returns\n */\nconst putGenerateRelated: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/generate_related`, data, null, loading)\n}\n/**\n * 命中测试列表\n * @param knowledge_id\n * @param loading\n * @query  { query_text: string, top_number: number, similarity: number }\n * @returns\n */\nconst putKnowledgeHitTest: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/hit_test`, data, undefined, loading)\n}\n\n/**\n * 同步知识库\n * @param 参数 knowledge_id\n * @query 参数 sync_type // 同步类型->replace:替换同步,complete:完整同步\n */\nconst putSyncWebKnowledge: (\n  knowledge_id: string,\n  sync_type: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, sync_type, loading) => {\n  return put(`${prefix}/${knowledge_id}/sync`, undefined, { sync_type }, loading)\n}\n\n/**\n * 创建知识库\n * @param 参数\n * {\n \"name\": \"string\",\n \"folder_id\": \"string\",\n \"desc\": \"string\",\n \"embedding\": \"string\"\n }\n */\nconst postKnowledge: (data: knowledgeData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/base`, data, undefined, loading, 1000 * 60 * 5)\n}\n\n/**\n * 创建工作流知识库\n * @param data\n * @param loading\n * @returns\n */\nconst createWorkflowKnowledge: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/workflow`, data, undefined, loading)\n}\n\n/**\n * 获取当前用户可使用的向量化模型列表（没用到）\n * @param application_id\n * @param loading\n * @query  { query_text: string, top_number: number, similarity: number }\n * @returns\n */\nconst getKnowledgeEmdeddingModel: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (knowledge_id, loading) => {\n  return get(`${prefix}/${knowledge_id}/emdedding_model`, loading)\n}\n\n/**\n * 获取当前用户可使用的模型列表\n * @param application_id\n * @param loading\n * @query  { query_text: string, top_number: number, similarity: number }\n * @returns\n */\nconst getKnowledgeModel: (loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (loading) => {\n  return get(`${prefix}/model`, loading)\n}\n\n/**\n * 创建Web知识库\n * @param 参数\n * {\n \"name\": \"string\",\n \"folder_id\": \"string\",\n \"desc\": \"string\",\n \"embedding\": \"string\",\n \"source_url\": \"string\",\n \"selector\": \"string\"\n }\n */\nconst postWebKnowledge: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/web`, data, undefined, loading)\n}\n\n// 创建飞书知识库\nconst postLarkKnowledge: (data: any, loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/lark/save`, data, null, loading)\n}\n\nconst putLarkKnowledge: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/lark/${knowledge_id}`, data, undefined, loading)\n}\n\nconst getAllTags: (params: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  params,\n  loading,\n) => {\n  return get(`${prefix}/tags`, params, loading)\n}\n\nconst getTags: (\n  knowledge_id: string,\n  params: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, params, loading) => {\n  return get(`${prefix}/${knowledge_id}/tags`, params, loading)\n}\n\nconst postTags: (\n  knowledge_id: string,\n  tags: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tags, loading) => {\n  return post(`${prefix}/${knowledge_id}/tags`, tags, null, loading)\n}\n\nconst putTag: (\n  knowledge_id: string,\n  tag_id: string,\n  tag: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tag_id, tag, loading) => {\n  return put(`${prefix}/${knowledge_id}/tags/${tag_id}`, tag, null, loading)\n}\n\nconst delTag: (\n  knowledge_id: string,\n  tag_id: string,\n  type: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tag_id, type, loading) => {\n  return del(`${prefix}/${knowledge_id}/tags/${tag_id}/${type}`, null, loading)\n}\n\nconst delMulTag: (\n  knowledge_id: string,\n  tags: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, tags, loading) => {\n  return put(`${prefix}/${knowledge_id}/tags/batch_delete`, tags, null, loading)\n}\nconst getKnowledgeWorkflowFormList: (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  node: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  node,\n  loading,\n) => {\n  return post(`${prefix}/${knowledge_id}/datasource/${type}/${id}/form_list`, { node }, {}, loading)\n}\nconst getKnowledgeWorkflowDatasourceDetails: (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  params: any,\n  function_name: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (\n  knowledge_id: string,\n  type: 'local' | 'tool',\n  id: string,\n  params,\n  function_name,\n  loading,\n) => {\n  return post(\n    `${prefix}/${knowledge_id}/datasource/${type}/${id}/${function_name}`,\n    params,\n    {},\n    loading,\n  )\n}\nconst workflowAction: (\n  knowledge_id: string,\n  instance: Dict<any>,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, instance, loading) => {\n  return post(`${prefix}/${knowledge_id}/action`, instance, {}, loading)\n}\nconst getWorkflowActionPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  query: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, page, query, loading) => {\n  return get(\n    `${prefix}/${knowledge_id}/action/${page.current_page}/${page.page_size}`,\n    query,\n    loading,\n  )\n}\nconst getWorkflowAction: (\n  knowledge_id: string,\n  knowledge_action_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, knowledge_action_id, loading) => {\n  return get(`${prefix}/${knowledge_id}/action/${knowledge_action_id}`, {}, loading)\n}\n\n/**\n * 保存知识库工作流\n * @param knowledge_id\n * @param data\n * @param loading\n * @returns\n */\nconst putKnowledgeWorkflow: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/workflow`, data, undefined, loading)\n}\n\n/** * 导出知识库工作流\n * @param knowledge_id\n * @param knowledge_name\n * @param loading\n * @returns\n */\nconst exportKnowledgeWorkflow = (\n  knowledge_id: string,\n  knowledge_name: string,\n  loading?: Ref<boolean>,\n) => {\n  return exportFile(\n    knowledge_name + '.kbwf',\n    `${prefix}/${knowledge_id}/workflow/export`,\n    undefined,\n    loading,\n  )\n}\n\n/** * 导入知识库工作流\n * @param knowledge_id\n * @param data\n * @param loading\n * @returns\n */\nconst importKnowledgeWorkflow: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/workflow/import`, data, undefined, loading)\n}\n\nconst workflowUpload: (\n  knowledge_id: string,\n  instance: Dict<any>,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, instance, loading) => {\n  return post(`${prefix}/${knowledge_id}/upload_document`, instance, {}, loading)\n}\n\nconst publish: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  knowledge_id: string,\n  loading,\n) => {\n  return put(`${prefix}/${knowledge_id}/publish`, {}, {}, loading)\n}\n\nconst listKnowledgeVersion: (\n  knowledge_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id: string, loading) => {\n  return get(`${prefix}/${knowledge_id}/knowledge_version`, {}, loading)\n}\n\n\nconst getMcpTools: (\n  knowledge_id: string,\n  mcp_servers: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, mcp_servers, loading) => {\n  return post(`${prefix}/${knowledge_id}/mcp_tools`, { mcp_servers }, {}, loading)\n}\n\nconst postTransformWorkflow: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/transform_workflow`, data, undefined, loading)\n}\n\n\nexport default {\n  getKnowledgeList,\n  getKnowledgeListPage,\n  getKnowledgeDetail,\n  putKnowledge,\n  delKnowledge,\n  putReEmbeddingKnowledge,\n  exportKnowledge,\n  exportZipKnowledge,\n  putGenerateRelated,\n  putKnowledgeHitTest,\n  putSyncWebKnowledge,\n  postKnowledge,\n  getKnowledgeModel,\n  postWebKnowledge,\n  createWorkflowKnowledge,\n  postLarkKnowledge,\n  putLarkKnowledge,\n  getAllTags,\n  getTags,\n  postTags,\n  putTag,\n  delTag,\n  delMulTag,\n  getWorkflowAction,\n  getKnowledgeWorkflowFormList,\n  getKnowledgeWorkflowDatasourceDetails,\n  workflowAction,\n  publish,\n  putKnowledgeWorkflow,\n  listKnowledgeVersion,\n  workflowUpload,\n  getWorkflowActionPage,\n  exportKnowledgeWorkflow,\n  importKnowledgeWorkflow,\n  getMcpTools,\n  postTransformWorkflow,\n} as {\n  [key: string]: any\n}\n"
  },
  {
    "path": "ui/src/api/system-shared/model.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type {\n  ListModelRequest,\n  Model,\n  CreateModelRequest,\n  EditModelRequest,\n} from '@/api/type/model'\nimport type { FormField } from '@/components/dynamics-form/type'\n\nconst prefix = '/system/shared/model'\n\n/**\n * 获得模型列表\n * @params 参数 name, model_type, model_name\n */\nconst getModelList: (\n  request?: ListModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<Model>>> = (data, loading) => {\n  return get(`${prefix}`, data, loading)\n}\n\n/**\n * 获得下拉选择框模型列表\n * @params 参数 name, model_type, model_name\n */\nconst getSelectModelList: (\n  data?: ListModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<Model>>> = (data, loading) => {\n  return get(`${prefix}`, data, loading)\n}\n\n/**\n * 获取模型参数表单\n * @param model_id 模型id\n * @param loading\n * @returns\n */\nconst getModelParamsForm: (\n  model_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<FormField>>> = (model_id, loading) => {\n  return get(`${prefix}/${model_id}/model_params_form`, {}, loading)\n}\n\n/**\n * 创建模型\n * @param request 请求对象\n * @param loading 加载器\n * @returns\n */\nconst createModel: (\n  request: CreateModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Model>> = (request, loading) => {\n  return post(`${prefix}`, request, {}, loading)\n}\n\n/**\n * 修改模型\n * @param request 請求對象\n * @param loading 加載器\n * @returns\n */\nconst updateModel: (\n  model_id: string,\n  request: EditModelRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<Model>> = (model_id, request, loading) => {\n  return put(`${prefix}/${model_id}`, request, {}, loading)\n}\n\n/**\n * 修改模型参数配置\n * @param request 請求對象\n * @param loading 加載器\n * @returns\n */\nconst updateModelParamsForm: (\n  model_id: string,\n  request: any[],\n  loading?: Ref<boolean>,\n) => Promise<Result<Model>> = (model_id, request, loading) => {\n  return put(`${prefix}/${model_id}/model_params_form`, request, {}, loading)\n}\n\n/**\n * 获取模型详情根据模型id 包括认证信息\n * @param model_id 模型id\n * @param loading  加载器\n * @returns\n */\nconst getModelById: (model_id: string, loading?: Ref<boolean>) => Promise<Result<Model>> = (\n  model_id,\n  loading,\n) => {\n  return get(`${prefix}/${model_id}`, {}, loading)\n}\n/**\n * 获取模型信息不包括认证信息根据模型id\n * @param model_id 模型id\n * @param loading  加载器\n * @returns\n */\nconst getModelMetaById: (model_id: string, loading?: Ref<boolean>) => Promise<Result<Model>> = (\n  model_id,\n  loading,\n) => {\n  return get(`${prefix}/${model_id}/meta`, {}, loading)\n}\n/**\n * 暂停下载\n * @param model_id 模型id\n * @param loading 加载器\n * @returns\n */\nconst pauseDownload: (model_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  model_id,\n  loading,\n) => {\n  return put(`${prefix}/${model_id}/pause_download`, undefined, {}, loading)\n}\nconst deleteModel: (model_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  model_id,\n  loading,\n) => {\n  return del(`${prefix}/${model_id}`, undefined, {}, loading)\n}\n\nexport default {\n  getModelList,\n  createModel,\n  updateModel,\n  deleteModel,\n  getModelById,\n  getModelMetaById,\n  pauseDownload,\n  getModelParamsForm,\n  updateModelParamsForm,\n  getSelectModelList,\n}\n"
  },
  {
    "path": "ui/src/api/system-shared/paragraph.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport type { pageRequest } from '@/api/type/common'\nimport type { Ref } from 'vue'\nconst prefix = '/system/shared/knowledge'\n\n/**\n * 创建段落\n * @param 参数\n * knowledge_id, document_id\n * {\n      \"content\": \"string\",\n      \"title\": \"string\",\n      \"is_active\": true,\n      \"problem_list\": [\n        {\n          \"content\": \"string\"\n        }\n      ]\n    }\n */\nconst postParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data, loading) => {\n  return post(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 段落列表\n * @param 参数 knowledge_id document_id\n * param {\n          \"title\": \"string\",\n          \"content\": \"string\",\n        }\n */\nconst getParagraphPage: (\n  knowledge_id: string,\n  document_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, page, param, loading) => {\n  return get(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 修改段落\n * @param 参数\n * knowledge_id, document_id, paragraph_id\n * {\n    \"content\": \"string\",\n    \"title\": \"string\",\n    \"is_active\": true,\n      \"problem_list\": [\n        {\n          \"content\": \"string\"\n        }\n      ]\n  }\n */\nconst putParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, paragraph_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 删除段落\n * @param 参数 knowledge_id, document_id, paragraph_id\n */\nconst delParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, paragraph_id, loading) => {\n  return del(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}`,\n    undefined,\n    {},\n    loading,\n  )\n}\n\n/**\n * 某段落问题列表\n * @param 参数 knowledge_id，document_id，paragraph_id\n */\nconst getParagraphProblem: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n) => Promise<Result<any>> = (knowledge_id, document_id, paragraph_id: string) => {\n  return get(`${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`)\n}\n\n/**\n * 给某段落创建问题\n * @param 参数\n * knowledge_id, document_id, paragraph_id\n * {\n      content\": \"string\"\n    }\n */\nconst postParagraphProblem: (\n  knowledge_id: string,\n  document_id: string,\n  paragraph_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, paragraph_id, data: any, loading) => {\n  return post(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/${paragraph_id}/problem`,\n    data,\n    {},\n    loading,\n  )\n}\n\n/**\n * 段落调整顺序\n * @param knowledge_id 数据集id\n * @param document_id 文档id\n * @param loading 加载器\n * @query data {\n *              paragraph_id 段落id  new_position 新顺序\n *             }\n */\nconst putAdjustPosition: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/adjust_position`,\n    {},\n    data,\n    loading,\n  )\n}\n\n/**\n * 添加某段落关联问题\n * @param knowledge_id 数据集id\n * @param document_id 文档id\n * @param loading 加载器\n * @query data {\n *              paragraph_id 段落id  problem_id 问题id\n *             }\n */\nconst putAssociationProblem: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/association`,\n    {},\n    data,\n    loading,\n  )\n}\n\n/**\n * 批量删除段落\n * @param 参数 knowledge_id, document_id\n */\nconst putMulParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/batch_delete`,\n    { id_list: data },\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量关联问题\n * @param 参数 knowledge_id, document_id\n * {\n      \"paragraph_id_list\": [\n        \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n      ],\n      \"model_id\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\",\n      \"prompt\": \"string\",\n      \"document_id\": \"3fa85f64-5717-4562-b3fc-2c963f66afa6\"\n    }\n */\nconst putBatchGenerateRelated: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/batch_generate_related`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 批量迁移段落\n * @param 参数 knowledge_id,target_knowledge_id,\n */\nconst putMigrateMulParagraph: (\n  knowledge_id: string,\n  document_id: string,\n  target_knowledge_id: string,\n  target_document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (\n  knowledge_id,\n  document_id,\n  target_knowledge_id,\n  target_document_id,\n  data,\n  loading,\n) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/migrate/knowledge/${target_knowledge_id}/document/${target_document_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 解除某段落关联问题\n * @param 参数 knowledge_id, document_id,\n * @query data {\n *            paragraph_id 段落id  problem_id 问题id\n *         }\n */\nconst putDisassociationProblem: (\n  knowledge_id: string,\n  document_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, document_id, data, loading) => {\n  return put(\n    `${prefix}/${knowledge_id}/document/${document_id}/paragraph/unassociation`,\n    {},\n    data,\n    loading,\n  )\n}\n\nexport default {\n  postParagraph,\n  getParagraphPage,\n  putParagraph,\n  delParagraph,\n  getParagraphProblem,\n  postParagraphProblem,\n  putAssociationProblem,\n  putMulParagraph,\n  putBatchGenerateRelated,\n  putMigrateMulParagraph,\n  putDisassociationProblem,\n  putAdjustPosition,\n}\n"
  },
  {
    "path": "ui/src/api/system-shared/problem.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport type { Ref } from 'vue'\nimport type { pageRequest } from '@/api/type/common'\n\nconst prefix = '/system/shared/knowledge'\n\n/**\n * 创建问题\n * @param 参数 knowledge_id\n * data: array[string]\n */\nconst postProblems: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, data, loading) => {\n  return post(`${prefix}/${knowledge_id}/problem`, data, undefined, loading)\n}\n\n/**\n * 问题分页列表\n * @param 参数  knowledge_id,\n * query {\n     \"content\": \"string\",\n   }\n */\n\nconst getProblemsPage: (\n  knowledge_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, page, param, loading) => {\n  return get(\n    `${prefix}/${knowledge_id}/problem/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 修改问题\n * @param 参数\n * knowledge_id, problem_id,\n * {\n \"content\": \"string\",\n }\n */\nconst putProblems: (\n  knowledge_id: string,\n  problem_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, problem_id, data: any, loading) => {\n  return put(`${prefix}/${knowledge_id}/problem/${problem_id}`, data, undefined, loading)\n}\n\n/**\n * 删除问题\n * @param 参数 knowledge_id, problem_id,\n */\nconst delProblems: (\n  knowledge_id: string,\n  problem_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, problem_id, loading) => {\n  return del(`${prefix}/${knowledge_id}/problem/${problem_id}`, loading)\n}\n\n/**\n * 问题详情\n * @param 参数\n * knowledge_id, problem_id,\n */\nconst getDetailProblems: (\n  knowledge_id: string,\n  problem_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (knowledge_id, problem_id, loading) => {\n  return get(`${prefix}/${knowledge_id}/problem/${problem_id}/paragraph`, undefined, loading)\n}\n\n/**\n * 批量关联段落\n * @param 参数 knowledge_id,\n * {\n      \"problem_id_list\": \"Array\",\n      \"paragraph_list\": \"Array\",\n    }\n */\nconst putMulAssociationProblem: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/problem/batch_association`, data, undefined, loading)\n}\n\n/**\n * 批量删除问题\n * @param 参数 knowledge_id,\n * data: array[string]\n */\nconst putMulProblem: (\n  knowledge_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (knowledge_id, data, loading) => {\n  return put(`${prefix}/${knowledge_id}/problem/batch_delete`, data, undefined, loading)\n}\n\nexport default {\n  postProblems,\n  getProblemsPage,\n  putProblems,\n  delProblems,\n  getDetailProblems,\n  putMulAssociationProblem,\n  putMulProblem,\n}\n"
  },
  {
    "path": "ui/src/api/system-shared/resource-mapping.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, put, post, del} from '@/request/index'\nimport type {Ref} from 'vue'\nimport type {pageRequest} from '@/api/type/common'\n\nconst prefix = '/system/shared'\n\nconst getResourceMapping: (\n  workspace_id: string,\n  resource: string,\n  resource_id: string,\n  page: pageRequest,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, resource, resource_id, page, params, loading) => {\n  return get(\n    `${prefix}/resource_mapping/${resource}/${resource_id}/${page.current_page}/${page.page_size}`,\n    params,\n    loading,\n  )\n}\n\nexport default {\n  getResourceMapping,\n}\n"
  },
  {
    "path": "ui/src/api/system-shared/tool.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put, exportFile } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type { pageRequest } from '@/api/type/common'\nimport type { toolData, AddInternalToolParam } from '@/api/type/tool'\n\nconst prefix = '/system/shared/tool'\n\n/**\n * 工具列表带分页（无分页）\n * @params 参数 {folder_id: string}\n */\nconst getToolList: (data?: any, loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (\n  data,\n  loading,\n) => {\n  return get(`${prefix}`, data, loading)\n}\n\n/**\n * 工具列表带分页（无分页）\n */\nconst getAllToolList: (data?: any, loading?: Ref<boolean>) => Promise<Result<Array<any>>> = (\n  data,\n  loading,\n) => {\n  return get(`${prefix}/tool_list`, data, loading)\n}\n\n/**\n * 工具列表带分页\n * @param 参数\n * param  {\n \"folder_id\": \"string\",\n \"name\": \"string\",\n \"tool_type\": \"string\",\n }\n */\nconst getToolListPage: (\n  page: pageRequest,\n  param?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (page, param, loading) => {\n  return get(`${prefix}/${page.current_page}/${page.page_size}`, param, loading)\n}\n\n/**\n * 创建工具\n * @param 参数\n */\nconst postTool: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}`, data, undefined, loading)\n}\n\n/**\n * 修改工具\n * @param 参数\n\n */\nconst putTool: (tool_id: string, data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  tool_id,\n  data,\n  loading,\n) => {\n  return put(`${prefix}/${tool_id}`, data, undefined, loading)\n}\n\n/**\n * @param 参数\n */\nconst postToolTestConnection: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/test_connection`, data, undefined, loading)\n}\n\n\n/**\n * 获取工具详情\n * @param tool_id 工具id\n * @param loading 加载器\n * @returns 工具详情\n */\nconst getToolById: (tool_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  tool_id,\n  loading,\n) => {\n  return get(`${prefix}/${tool_id}`, undefined, loading)\n}\n\n/**\n * 删除工具\n * @param 参数 tool_id\n */\nconst delTool: (tool_id: String, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  tool_id,\n  loading,\n) => {\n  return del(`${prefix}/${tool_id}`, undefined, {}, loading)\n}\n\nconst putToolIcon: (id: string, data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  id,\n  data,\n  loading,\n) => {\n  return put(`${prefix}/${id}/edit_icon`, data, undefined, loading)\n}\n\nconst exportTool = (id: string, name: string, loading?: Ref<boolean>) => {\n  return exportFile(name + '.fx', `${prefix}/${id}/export`, undefined, loading)\n}\n\n/**\n * 调试工具\n * @param 参数\n\n */\nconst postToolDebug: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data: any,\n  loading,\n) => {\n  return post(`${prefix}/debug`, data, undefined, loading)\n}\n\nconst postImportTool: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/import`, data, undefined, loading)\n}\n\nconst postPylint: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  code,\n  loading,\n) => {\n  return post(`${prefix}/pylint`, { code }, {}, loading)\n}\n\n\n/**\n * 工具商店-添加系统内置\n */\nconst addInternalTool: (\n  tool_id: string,\n  param: AddInternalToolParam,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id, param, loading) => {\n  return post(`${prefix}/${tool_id}/add_internal_tool`, param, undefined, loading)\n}\n\n/**\n * 工具商店\n */\nconst addStoreTool: (\n  tool_id: string,\n  param: AddInternalToolParam,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id, param, loading) => {\n  return post(`${prefix}/${tool_id}/add_store_tool`, param, undefined, loading)\n}\n\nconst updateStoreTool: (\n  tool_id: string,\n  param: AddInternalToolParam,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id, param, loading) => {\n  return post(`${prefix}/${tool_id}/update_store_tool`, param, undefined, loading)\n}\n\n\nconst pageToolRecord = (\n  tool_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => {\n  return get(\n    `${prefix}/${tool_id}/tool_record/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\nconst getToolRecordDetail = (\n tool_id: string,\n record_id: string\n) => {\n  return get(`${prefix}/${tool_id}/tool_record/${record_id}`)\n}\n\nconst uploadSkillFile: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return put(`${prefix}/upload_skill_file`, data, undefined, loading)\n}\n\n\n\nexport default {\n  getToolList,\n  getAllToolList,\n  getToolListPage,\n  putTool,\n  getToolById,\n  postTool,\n  postToolDebug,\n  postImportTool,\n  postPylint,\n  exportTool,\n  putToolIcon,\n  delTool,\n  addInternalTool,\n  addStoreTool,\n  updateStoreTool,\n  postToolTestConnection,\n  pageToolRecord,\n  getToolRecordDetail,\n  uploadSkillFile,\n}\n"
  },
  {
    "path": "ui/src/api/tool/store.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put, exportFile } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type { AddInternalToolParam } from '@/api/type/tool'\n\nimport useStore from '@/stores'\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId() + '/tool'\n  },\n})\n\n/**\n * 工具商店-系统内置列表\n */\nconst getInternalToolList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  param,\n  loading,\n) => {\n  return get('/workspace/internal/tool', param, loading)\n}\n\n/**\n * 工具商店列表\n */\nconst getStoreToolList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  param,\n  loading,\n) => {\n  return get('/workspace/store/tool', param, loading)\n}\n\nconst getStoreKBList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  param,\n  loading,\n) => {\n  return get('/workspace/store/knowledge_template', param, loading)\n}\n\nconst getStoreAppList: (param?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  param,\n  loading,\n) => {\n  return get('/workspace/store/application_template', param, loading)\n}\n\n/**\n * 工具商店-添加系统内置\n */\nconst addInternalTool: (\n  tool_id: string,\n  param: AddInternalToolParam,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id, param, loading) => {\n  return post(`${prefix.value}/${tool_id}/add_internal_tool`, param, undefined, loading)\n}\n\n/**\n * 工具商店-添加\n */\nconst addStoreTool: (\n  tool_id: string,\n  param: AddInternalToolParam,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id, param, loading) => {\n  return post(`${prefix.value}/${tool_id}/add_store_tool`, param, undefined, loading)\n}\n\nexport default {\n  getInternalToolList,\n  getStoreToolList,\n  getStoreKBList,\n  getStoreAppList,\n  addInternalTool,\n  addStoreTool\n}\n"
  },
  {
    "path": "ui/src/api/tool/tool.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put, exportFile, postStream } from '@/request/index'\nimport { type Ref } from 'vue'\nimport type { pageRequest } from '@/api/type/common'\nimport type { AddInternalToolParam, toolData } from '@/api/type/tool'\n\nimport useStore from '@/stores'\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId() + '/tool'\n  },\n})\n\n/**\n * 工具列表带分页（无分页）\n * @params 参数 {folder_id: string}\n */\nconst getToolList: (\n  data?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<{ tools: any[]; folders: any[] }>> = (data, loading) => {\n  return get(`${prefix.value}`, data, loading)\n}\n\n/**\n * 工具列表带分页（无分页）\n */\nconst getAllToolList: (\n  data?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<{ tools: any[]; folders: any[] }>> = (data, loading) => {\n  return get(`${prefix.value}/tool_list`, data, loading)\n}\n\n/**\n * 工具列表带分页\n * @param 参数\n * param  {\n \"folder_id\": \"string\",\n \"name\": \"string\",\n \"tool_type\": \"string\",\n }\n */\nconst getToolListPage: (\n  page: pageRequest,\n  param?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (page, param, loading) => {\n  return get(`${prefix.value}/${page.current_page}/${page.page_size}`, param, loading)\n}\n\n/**\n * 创建工具\n * @param 参数\n */\nconst postTool: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix.value}`, data, undefined, loading)\n}\n\n/**\n * 修改工具\n * @param 参数\n\n */\nconst putTool: (tool_id: string, data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  tool_id,\n  data,\n  loading,\n) => {\n  return put(`${prefix.value}/${tool_id}`, data, undefined, loading)\n}\n\n/**\n * @param 参数\n */\nconst postToolTestConnection: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix.value}/test_connection`, data, undefined, loading)\n}\n\n/**\n * 获取工具详情\n * @param tool_id 工具id\n * @param loading 加载器\n * @returns 函数详情\n */\nconst getToolById: (tool_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  tool_id,\n  loading,\n) => {\n  return get(`${prefix.value}/${tool_id}`, undefined, loading)\n}\n\n/**\n * 删除工具\n * @param 参数 tool_id\n */\nconst delTool: (tool_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  tool_id,\n  loading,\n) => {\n  return del(`${prefix.value}/${tool_id}`, undefined, {}, loading)\n}\n\nconst putToolIcon: (id: string, data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  id,\n  data,\n  loading,\n) => {\n  return put(`${prefix.value}/${id}/edit_icon`, data, undefined, loading)\n}\n\nconst exportTool = (id: string, name: string, loading?: Ref<boolean>) => {\n  return exportFile(name + '.tool', `${prefix.value}/${id}/export`, undefined, loading)\n}\n\n/**\n * 调试工具\n * @param 参数\n\n */\nconst postToolDebug: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data: any,\n  loading,\n) => {\n  return post(`${prefix.value}/debug`, data, undefined, loading)\n}\n\nconst postImportTool: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix.value}/import`, data, undefined, loading)\n}\n\nconst postPylint: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  code,\n  loading,\n) => {\n  return post(`${prefix.value}/pylint`, { code }, {}, loading)\n}\n\n/**\n * 工具商店-添加系统内置\n */\nconst addInternalTool: (\n  tool_id: string,\n  param: AddInternalToolParam,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id, param, loading) => {\n  return post(`${prefix.value}/${tool_id}/add_internal_tool`, param, undefined, loading)\n}\n\n/**\n * 工具商店-添加\n */\nconst addStoreTool: (\n  tool_id: string,\n  param: AddInternalToolParam,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id, param, loading) => {\n  return post(`${prefix.value}/${tool_id}/add_store_tool`, param, undefined, loading)\n}\n\nconst updateStoreTool: (\n  tool_id: string,\n  param: AddInternalToolParam,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id, param, loading) => {\n  return post(`${prefix.value}/${tool_id}/update_store_tool`, param, undefined, loading)\n}\n\nconst pageToolRecord = (tool_id: string, page: pageRequest, param: any, loading?: Ref<boolean>) => {\n  return get(\n    `${prefix.value}/${tool_id}/tool_record/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\nconst getToolRecordDetail = (tool_id: string, record_id: string) => {\n  return get(`${prefix.value}/${tool_id}/tool_record/${record_id}`)\n}\n\nconst uploadSkillFile: (data: toolData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return put(`${prefix.value}/upload_skill_file`, data, undefined, loading)\n}\n/**\n * 保存工具工作流\n * @param tool_id\n * @param data\n * @param loading\n * @returns\n */\nconst putToolWorkflow: (\n  tool_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id, data, loading) => {\n  return put(`${prefix.value}/${tool_id}/workflow`, data, undefined, loading)\n}\n\n/**\n * 导出知识库工作流\n * @param knowledge_id\n * @param knowledge_name\n * @param loading\n * @returns\n */\nconst exportKnowledgeWorkflow = (\n  knowledge_id: string,\n  knowledge_name: string,\n  loading?: Ref<boolean>,\n) => {\n  return exportFile(\n    knowledge_name + '.kbwf',\n    `${prefix.value}/${knowledge_id}/workflow/export`,\n    undefined,\n    loading,\n  )\n}\n/**\n * 导出知识库工作流\n * @param knowledge_id\n * @param knowledge_name\n * @param loading\n * @returns\n */\nconst exportToolWorkflow = (tool_id: string, tool_name: string, loading?: Ref<boolean>) => {\n  return exportFile(\n    tool_name + '.tool',\n    `${prefix.value}/${tool_id}/workflow/export`,\n    undefined,\n    loading,\n  )\n}\n/**\n * 导入工具工作流\n */\nconst importToolWorkflow: (\n  tool_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id, data, loading) => {\n  return post(`${prefix.value}/${tool_id}/workflow/import`, data, undefined, loading)\n}\n/**\n * 获取工具工作流版本列表\n * @param tool_id\n * @param loading\n * @returns\n */\nconst listToolWorkflowVersion: (tool_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  tool_id: string,\n  loading,\n) => {\n  return get(`${prefix.value}/${tool_id}/tool_version`, {}, loading)\n}\n/**\n *\n * @param tool_id 工具id\n * @param tool_version_id 工具版本id\n * @param data 数据\n * @param loading\n * @returns\n */\nconst updateToolWorkflowVersion: (\n  tool_id: string,\n  tool_version_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (tool_id: string, tool_version_id, data, loading) => {\n  return put(`${prefix.value}/${tool_id}/tool_version/${tool_version_id}`, data, {}, loading)\n}\nconst publish: (tool_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  tool_id: string,\n  loading,\n) => {\n  return put(`${prefix.value}/${tool_id}/publish`, {}, {}, loading)\n}\n\n/**\n * 调试工作流\n * @param 参数\n * chat_id: string\n * data\n */\nconst debugToolWorkflow: (tool_id: string, data: any) => Promise<any> = (tool_id, data) => {\n  const p = (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api'\n  return postStream(`${p}${prefix.value}/${tool_id}/debug`, data)\n}\nexport default {\n  getToolList,\n  getAllToolList,\n  getToolListPage,\n  putTool,\n  getToolById,\n  postTool,\n  postToolDebug,\n  postImportTool,\n  postPylint,\n  exportTool,\n  putToolIcon,\n  delTool,\n  addInternalTool,\n  addStoreTool,\n  updateStoreTool,\n  postToolTestConnection,\n  pageToolRecord,\n  getToolRecordDetail,\n  uploadSkillFile,\n  putToolWorkflow,\n  importToolWorkflow,\n  listToolWorkflowVersion,\n  updateToolWorkflowVersion,\n  publish,\n  exportToolWorkflow,\n  debugToolWorkflow,\n}\n"
  },
  {
    "path": "ui/src/api/trigger/trigger.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport type { User, ResetPasswordRequest, CheckCodeRequest } from '@/api/type/user'\nimport type { Ref } from 'vue'\nimport type { KeyValue, pageRequest } from '@/api/type/common'\nimport useStore from '@/stores'\nimport type { TriggerData } from '../type/trigger'\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId() + '/trigger'\n  },\n})\n\nconst prefixWorkspace: any = { _value: '/workspace/' }\nObject.defineProperty(prefixWorkspace, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId()\n  },\n})\n\n/**\n * 触发器列表\n * @param data\n * @param loading\n * @returns\n */\nconst getTriggerList: (data?: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return get(`${prefix.value}`, data, loading)\n}\n\n/**\n * 触发器详情\n * @param trigger_id\n * @param loading\n * @returns\n */\nconst getTriggerDetail: (trigger_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  trigger_id,\n  loading,\n) => {\n  return get(`${prefix.value}/${trigger_id}`, {}, loading)\n}\n\n/**\n * 创建触发器\n * @param data\n * @param loading\n * @returns\n */\nconst postTrigger: (data: TriggerData, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix.value}`, data, undefined, loading)\n}\n\n/**\n * 修改触发器\n * @param trigger_id\n * @param data\n * @param loading\n * @returns\n */\nconst putTrigger: (\n  trigger_id: string,\n  data: TriggerData,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (trigger_id, data, loading) => {\n  return put(`${prefix.value}/${trigger_id}`, data, undefined, loading)\n}\n\n/**\n * 删除触发器\n * @param trigger_id\n * @param loading\n * @returns\n */\nconst deleteTrigger: (trigger_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  trigger_id,\n  loading,\n) => {\n  return del(`${prefix.value}/${trigger_id}`, undefined, {}, loading)\n}\n\n/**\n * 批量删除触发器\n * @param data\n * @param loading\n * @returns\n */\nconst delMulTrigger: (data: any, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  data: any,\n  loading,\n) => {\n  return put(`${prefix.value}/batch_delete`, { id_list: data }, undefined, loading)\n}\n\n/**\n * 批量激活/禁用触发器\n * @param data\n * @param loading\n * @returns\n */\nconst activateMulTrigger: (data: any, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  data: any,\n  loading,\n) => {\n  return put(\n    `${prefix.value}/batch_activate`,\n    { id_list: data.id_list, is_active: data.is_active },\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 分页查询触发器\n * @param page    分页参数\n * @param param   查询参数\n * @param loading 加载器\n * @returns\n */\nconst pageTrigger = (page: pageRequest, param: any, loading?: Ref<boolean>) => {\n  return get(`${prefix.value}/${page.current_page}/${page.page_size}`, param, loading)\n}\n/**\n * 分页查询触发器执行任务\n * @param trigger_id 触发器id\n * @param page       分页参数\n * @param param      查询参数\n * @param loading    记载器\n * @returns\n */\nconst pageTriggerTaskRecord = (\n  trigger_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => {\n  return get(\n    `${prefix.value}/${trigger_id}/task_record/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\nconst getTriggerTaskRecordDetails = (\n  trigger_id: string,\n  trigger_task_id: string,\n  trigger_task_record_id: string,\n  loading?: Ref<boolean>,\n) => {\n  return get(\n    `${prefix.value}/${trigger_id}/trigger_task/${trigger_task_id}/trigger_task_record/${trigger_task_record_id}`,\n    {},\n    loading,\n  )\n}\n\n/**\n * 资源端创建触发器\n * @param source_type  资源类型\n * @param source_id    资源id\n * @param data         数据\n * @param loading      加载器\n * @returns\n */\nconst postResourceTrigger: (\n  source_type: string,\n  source_id: string,\n  data: TriggerData,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (source_type, source_id, data, loading) => {\n  return post(\n    `${prefixWorkspace.value}/${source_type}/${source_id}/trigger`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\n/**\n * 资源端触发器列表\n * @param source_type\n * @param source_id\n * @param loading\n * @returns\n */\nconst getResourceTriggerList: (\n  source_type: string,\n  source_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (source_type, source_id, loading) => {\n  return get(\n    `${prefixWorkspace.value}/${source_type}/${source_id}/trigger`,\n    undefined,\n    loading\n  )\n}\n\n/**\n * 资源端触发器详情\n * @param source_type\n * @param source_id\n * @param trigger_id\n * @param loading\n * @returns\n */\nconst getResourceTriggerDetail: (\n    source_type: string,\n  source_id: string,\n  trigger_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (source_type, source_id, trigger_id, loading) => {\n  return get(\n    `${prefixWorkspace.value}/${source_type}/${source_id}/trigger/${trigger_id}`,\n    undefined,\n    loading\n  )\n}\n\n/**\n * 资源端删除触发器\n * @param source_type\n * @param source_id\n * @param trigger_id\n * @param loading\n * @returns\n */\nconst deleteResourceTrigger: (\n    source_type: string,\n  source_id: string,\n  trigger_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (source_type, source_id, trigger_id, loading) => {\n  return del(\n    `${prefixWorkspace.value}/${source_type}/${source_id}/trigger/${trigger_id}`,\n    undefined,\n    {},\n    loading\n  )\n}\n\n/**\n * 资源端修改触发器\n * @param source_type 资源类型\n * @param source_id   资源id\n * @param trigger_id  触发器id\n * @param data        触发器数据\n * @param loading     加载器\n * @returns\n */\nconst putResourceTrigger: (\n  source_type: string,\n  source_id: string,\n  trigger_id: string,\n  data: TriggerData,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (source_type, source_id, trigger_id, data, loading) => {\n  return put(\n    `${prefixWorkspace.value}/${source_type}/${source_id}/trigger/${trigger_id}`,\n    data,\n    undefined,\n    loading,\n  )\n}\n\nexport default {\n  pageTrigger,\n  getTriggerList,\n  postTrigger,\n  getTriggerDetail,\n  putTrigger,\n  deleteTrigger,\n  delMulTrigger,\n  activateMulTrigger,\n  pageTriggerTaskRecord,\n  getTriggerTaskRecordDetails,\n  postResourceTrigger,\n  putResourceTrigger,\n  getResourceTriggerList,\n  getResourceTriggerDetail,\n  deleteResourceTrigger\n}\n"
  },
  {
    "path": "ui/src/api/type/application.ts",
    "content": "import { type Dict } from '@/api/type/common'\nimport { type Ref } from 'vue'\nimport bus from '@/bus'\n\ninterface ApplicationFormType {\n  name?: string\n  desc?: string\n  model_id?: string\n  dialogue_number?: number\n  prologue?: string\n  knowledge_id_list?: string[]\n  knowledge_setting?: any\n  model_setting?: any\n  problem_optimization?: boolean\n  problem_optimization_prompt?: string\n  icon?: string | undefined\n  type?: string\n  work_flow?: any\n  model_params_setting?: any\n  tts_model_params_setting?: any\n  stt_model_params_setting?: any\n  stt_model_id?: string\n  tts_model_id?: string\n  stt_model_enable?: boolean\n  tts_model_enable?: boolean\n  tts_type?: string\n  tts_autoplay?: boolean\n  stt_autosend?: boolean\n  folder_id?: string\n  workspace_id?: string\n  mcp_enable?: boolean\n  mcp_servers?: string\n  mcp_tool_ids?: string[]\n  mcp_source?: string\n  tool_enable?: boolean\n  tool_ids?: string[]\n  application_enable?: boolean\n  application_ids?: string[]\n  skill_tool_ids?: string[]\n  mcp_output_enable?: boolean\n  work_flow_template?: any\n}\n\ninterface Chunk {\n  real_node_id: string\n  chat_id: string\n  chat_record_id: string\n  content: string\n  reasoning_content: string\n  node_id: string\n  up_node_id: string\n  is_end: boolean\n  node_is_end: boolean\n  node_type: string\n  view_type: string\n  runtime_node_id: string\n  child_node: any\n}\n\ninterface chatType {\n  id: string\n  problem_text: string\n  answer_text: string\n  buffer: Array<string>\n  answer_text_list: Array<\n    Array<{\n      content: string\n      reasoning_content: string\n      chat_record_id?: string\n      runtime_node_id?: string\n      child_node?: any\n      real_node_id?: string\n    }>\n  >\n  /**\n   * 是否写入结束\n   */\n  write_ed?: boolean\n  /**\n   * 是否暂停\n   */\n  is_stop?: boolean\n  record_id: string\n  chat_id: string\n  vote_status: string\n  status?: number\n  execution_details: any[]\n  upload_meta?: {\n    document_list: Array<any>\n    image_list: Array<any>\n    audio_list: Array<any>\n    video_list: Array<any>\n    other_list: Array<any>\n  }\n}\n\ninterface Node {\n  buffer: Array<string>\n  node_id: string\n  up_node_id: string\n  node_type: string\n  view_type: string\n  index: number\n  is_end: boolean\n}\n\ninterface WriteNodeInfo {\n  current_node: any\n  answer_text_list_index: number\n  current_up_node?: any\n  divider_content?: Array<string>\n  divider_reasoning_content?: Array<string>\n}\n\nexport class ChatRecordManage {\n  id?: any\n  ms: number\n  chat: chatType\n  is_close?: boolean\n  write_ed?: boolean\n  is_stop?: boolean\n  loading?: Ref<boolean>\n  node_list: Array<any>\n  write_node_info?: WriteNodeInfo\n\n  constructor(chat: chatType, ms?: number, loading?: Ref<boolean>) {\n    this.ms = ms ? ms : 10\n    this.chat = chat\n    this.loading = loading\n    this.is_stop = false\n    this.is_close = false\n    this.write_ed = false\n    this.node_list = []\n  }\n\n  append_answer(\n    chunk_answer: string,\n    reasoning_content: string,\n    index?: number,\n    chat_record_id?: string,\n    runtime_node_id?: string,\n    child_node?: any,\n    real_node_id?: string,\n  ) {\n    if (chunk_answer || reasoning_content) {\n      const set_index = index != undefined ? index : this.chat.answer_text_list.length - 1\n      let card_list = this.chat.answer_text_list[set_index]\n      if (!card_list) {\n        card_list = []\n        this.chat.answer_text_list[set_index] = card_list\n      }\n      const answer_value = card_list.find((item) => item.real_node_id == real_node_id)\n      const content = answer_value ? answer_value.content + chunk_answer : chunk_answer\n      const _reasoning_content = answer_value\n        ? answer_value.reasoning_content + reasoning_content\n        : reasoning_content\n      if (answer_value) {\n        answer_value.content = content\n        answer_value.reasoning_content = _reasoning_content\n      } else {\n        card_list.push({\n          content: content,\n          reasoning_content: _reasoning_content,\n          chat_record_id,\n          runtime_node_id,\n          child_node,\n          real_node_id,\n        })\n      }\n    }\n    this.chat.answer_text = this.chat.answer_text + chunk_answer\n    bus.emit('change:answer', { record_id: this.chat.record_id, is_end: false })\n  }\n\n  get_current_up_node(run_node: any) {\n    const index = this.node_list.findIndex((item) => item == run_node)\n    if (index > 0) {\n      const n = this.node_list[index - 1]\n      return n\n    }\n    return undefined\n  }\n\n  get_run_node() {\n    if (\n      this.write_node_info &&\n      (this.write_node_info.current_node.reasoning_content_buffer.length > 0 ||\n        this.write_node_info.current_node.buffer.length > 0 ||\n        !this.write_node_info.current_node.is_end)\n    ) {\n      return this.write_node_info\n    }\n    const run_node = this.node_list.filter(\n      (item) => item.reasoning_content_buffer.length > 0 || item.buffer.length > 0 || !item.is_end,\n    )[0]\n\n    if (run_node) {\n      const index = this.node_list.indexOf(run_node)\n      let current_up_node = undefined\n      if (index > 0) {\n        current_up_node = this.get_current_up_node(run_node)\n      }\n      let answer_text_list_index = 0\n      if (\n        current_up_node == undefined ||\n        run_node.view_type == 'single_view' ||\n        current_up_node.view_type == 'single_view'\n      ) {\n        const none_index = this.findIndex(\n          this.chat.answer_text_list,\n          (item) => (item.length == 1 && item[0].content == '') || item.length == 0,\n          'index',\n        )\n        if (none_index > -1) {\n          answer_text_list_index = none_index\n        } else {\n          answer_text_list_index = this.chat.answer_text_list.length\n        }\n      } else {\n        const none_index = this.findIndex(\n          this.chat.answer_text_list,\n          (item) => (item.length == 1 && item[0].content == '') || item.length == 0,\n          'index',\n        )\n        if (none_index > -1) {\n          answer_text_list_index = none_index\n        } else {\n          answer_text_list_index = this.chat.answer_text_list.length - 1\n        }\n      }\n\n      this.write_node_info = {\n        current_node: run_node,\n        current_up_node: current_up_node,\n        answer_text_list_index: answer_text_list_index,\n      }\n\n      return this.write_node_info\n    }\n    return undefined\n  }\n\n  findIndex<T>(array: Array<T>, find: (item: T) => boolean, type: 'last' | 'index') {\n    let set_index = -1\n    for (let index = 0; index < array.length; index++) {\n      const element = array[index]\n      if (find(element)) {\n        set_index = index\n        if (type == 'index') {\n          break\n        }\n      }\n    }\n    return set_index\n  }\n\n  closeInterval() {\n    this.chat.write_ed = true\n    this.write_ed = true\n    if (this.loading) {\n      this.loading.value = false\n    }\n    bus.emit('change:answer', { record_id: this.chat.record_id, is_end: true })\n    if (this.id) {\n      clearInterval(this.id)\n    }\n    const last_index = this.findIndex(\n      this.chat.answer_text_list,\n      (item) => (item.length == 1 && item[0].content == '') || item.length == 0,\n      'last',\n    )\n    if (last_index > 0) {\n      this.chat.answer_text_list.splice(last_index, 1)\n    }\n  }\n\n  write() {\n    this.chat.is_stop = false\n    this.is_stop = false\n    if (!this.is_close) {\n      this.is_close = false\n    }\n\n    this.write_ed = false\n    this.chat.write_ed = false\n    if (this.loading) {\n      this.loading.value = true\n    }\n    this.id = setInterval(() => {\n      const node_info = this.get_run_node()\n      if (node_info == undefined) {\n        if (this.is_close) {\n          this.closeInterval()\n        }\n        return\n      }\n      const { current_node, answer_text_list_index } = node_info\n\n      if (current_node.buffer.length > 20) {\n        const context = current_node.is_end\n          ? current_node.buffer.splice(0)\n          : current_node.buffer.splice(\n              0,\n              current_node.is_end ? undefined : current_node.buffer.length - 20,\n            )\n        const reasoning_content = current_node.is_end\n          ? current_node.reasoning_content_buffer.splice(0)\n          : current_node.reasoning_content_buffer.splice(\n              0,\n              current_node.is_end ? undefined : current_node.reasoning_content_buffer.length - 20,\n            )\n        this.append_answer(\n          context.join(''),\n          reasoning_content.join(''),\n          answer_text_list_index,\n          current_node.chat_record_id,\n          current_node.runtime_node_id,\n          current_node.child_node,\n          current_node.real_node_id,\n        )\n      } else if (this.is_close) {\n        while (true) {\n          const node_info = this.get_run_node()\n\n          if (node_info == undefined) {\n            break\n          }\n          this.append_answer(\n            node_info.current_node.buffer.splice(0).join(''),\n            node_info.current_node.reasoning_content_buffer.splice(0).join(''),\n            node_info.answer_text_list_index,\n            node_info.current_node.chat_record_id,\n            node_info.current_node.runtime_node_id,\n            node_info.current_node.child_node,\n            node_info.current_node.real_node_id,\n          )\n\n          if (\n            node_info.current_node.buffer.length == 0 &&\n            node_info.current_node.reasoning_content_buffer.length == 0\n          ) {\n            node_info.current_node.is_end = true\n          }\n        }\n        this.closeInterval()\n      } else {\n        const s = current_node.buffer.shift()\n        const reasoning_content = current_node.reasoning_content_buffer.shift()\n        if (s !== undefined) {\n          this.append_answer(\n            s,\n            '',\n            answer_text_list_index,\n            current_node.chat_record_id,\n            current_node.runtime_node_id,\n            current_node.child_node,\n            current_node.real_node_id,\n          )\n        }\n        if (reasoning_content !== undefined) {\n          this.append_answer(\n            '',\n            reasoning_content,\n            answer_text_list_index,\n            current_node.chat_record_id,\n            current_node.runtime_node_id,\n            current_node.child_node,\n            current_node.real_node_id,\n          )\n        }\n      }\n    }, this.ms)\n  }\n\n  stop() {\n    clearInterval(this.id)\n    this.is_stop = true\n    this.chat.is_stop = true\n    if (this.loading) {\n      this.loading.value = false\n    }\n  }\n\n  close() {\n    this.is_close = true\n  }\n\n  open() {\n    this.is_close = false\n    this.is_stop = false\n  }\n\n  appendChunk(chunk: Chunk) {\n    let n = this.node_list.find((item) => item.real_node_id == chunk.real_node_id)\n    if (n) {\n      for (const ch of chunk.content) {\n        n.buffer.push(ch)\n      }\n      // n.buffer.push(...chunk.content)\n      n.content += chunk.content\n      if (chunk.reasoning_content) {\n        for (const ch of chunk.reasoning_content) {\n          n.reasoning_content_buffer.push(ch)\n        }\n        // n.reasoning_content_buffer.push(...chunk.reasoning_content)\n        n.reasoning_content += chunk.reasoning_content\n      }\n    } else {\n      n = {\n        buffer: [...chunk.content],\n        reasoning_content_buffer: chunk.reasoning_content ? [...chunk.reasoning_content] : [],\n        reasoning_content: chunk.reasoning_content ? chunk.reasoning_content : '',\n        content: chunk.content,\n        real_node_id: chunk.real_node_id,\n        node_id: chunk.node_id,\n        chat_record_id: chunk.chat_record_id,\n        up_node_id: chunk.up_node_id,\n        runtime_node_id: chunk.runtime_node_id,\n        child_node: chunk.child_node,\n        node_type: chunk.node_type,\n        index: this.node_list.length,\n        view_type: chunk.view_type,\n        is_end: false,\n      }\n      this.node_list.push(n)\n    }\n    if (chunk.node_is_end) {\n      n['is_end'] = true\n    }\n  }\n\n  append(answer_text_block: string, reasoning_content?: string) {\n    let set_index = this.findIndex(\n      this.chat.answer_text_list,\n      (item) => item.length == 1 && item[0].content == '',\n      'index',\n    )\n    if (set_index <= -1) {\n      set_index = 0\n    }\n    this.chat.answer_text_list[set_index] = [\n      {\n        content: answer_text_block,\n        reasoning_content: reasoning_content ? reasoning_content : '',\n      },\n    ]\n  }\n}\n\nexport class ChatManagement {\n  static chatMessageContainer: Dict<ChatRecordManage> = {}\n\n  static addChatRecord(chat: chatType, ms: number, loading?: Ref<boolean>) {\n    this.chatMessageContainer[chat.id] = new ChatRecordManage(chat, ms, loading)\n  }\n\n  static appendChunk(chatRecordId: string, chunk: Chunk) {\n    const chatRecord = this.chatMessageContainer[chatRecordId]\n    if (chatRecord) {\n      chatRecord.appendChunk(chunk)\n    }\n  }\n\n  static append(chatRecordId: string, content: string, reasoning_content?: string) {\n    const chatRecord = this.chatMessageContainer[chatRecordId]\n    if (chatRecord) {\n      chatRecord.append(content, reasoning_content)\n    }\n  }\n\n  static updateStatus(chatRecordId: string, code: number) {\n    const chatRecord = this.chatMessageContainer[chatRecordId]\n    if (chatRecord) {\n      chatRecord.chat.status = code\n    }\n  }\n\n  /**\n   * 持续从缓存区 写出数据\n   * @param chatRecordId 对话记录id\n   */\n  static write(chatRecordId: string) {\n    const chatRecord = this.chatMessageContainer[chatRecordId]\n    if (chatRecord) {\n      chatRecord.write()\n    }\n  }\n\n  static open(chatRecordId: string) {\n    const chatRecord = this.chatMessageContainer[chatRecordId]\n    if (chatRecord) {\n      chatRecord.open()\n    }\n  }\n\n  /**\n   * 等待所有数据输出完毕后 才会关闭流\n   * @param chatRecordId 对话记录id\n   * @returns boolean\n   */\n  static close(chatRecordId: string) {\n    const chatRecord = this.chatMessageContainer[chatRecordId]\n    if (chatRecord) {\n      chatRecord.close()\n    }\n  }\n\n  /**\n   * 停止输出 立即关闭定时任务输出\n   * @param chatRecordId 对话记录id\n   * @returns boolean\n   */\n  static stop(chatRecordId: string) {\n    const chatRecord = this.chatMessageContainer[chatRecordId]\n    if (chatRecord) {\n      chatRecord.stop()\n    }\n  }\n\n  /**\n   * 判断是否输出完成\n   * @param chatRecordId 对话记录id\n   * @returns boolean\n   */\n  static isClose(chatRecordId: string) {\n    const chatRecord = this.chatMessageContainer[chatRecordId]\n    return chatRecord ? chatRecord.is_close && chatRecord.write_ed : false\n  }\n\n  /**\n   * 判断是否停止输出\n   * @param chatRecordId 对话记录id\n   * @returns\n   */\n  static isStop(chatRecordId: string) {\n    const chatRecord = this.chatMessageContainer[chatRecordId]\n    return chatRecord ? chatRecord.is_stop : false\n  }\n\n  /**\n   * 清除无用数据 也就是被close掉的和stop的数据\n   */\n  static clean() {\n    for (const key in Object.keys(this.chatMessageContainer)) {\n      if (this.chatMessageContainer[key].is_close) {\n        delete this.chatMessageContainer[key]\n      }\n    }\n  }\n}\n\nexport type { ApplicationFormType, chatType }\n"
  },
  {
    "path": "ui/src/api/type/chat.ts",
    "content": "interface ChatProfile {\n  // 是否开启认证\n  authentication: boolean\n  // icon\n  icon?: string\n  // 应用名称\n  application_name?: string\n  // 背景图\n  bg_icon?: string\n  // 认证类型\n  authentication_type?: 'password' | 'login'\n  // 登录类型\n  login_value?: Array<string>\n  max_attempts?: number\n  rasKey?: string\n}\n\ninterface ChatUserProfile {\n  email: string\n  id: string\n  nick_name: string\n  username: string\n  source: string\n}\nexport { type ChatProfile, type ChatUserProfile }\n"
  },
  {
    "path": "ui/src/api/type/common.ts",
    "content": "interface KeyValue<K, V> {\n  key: K\n  value: V\n}\ninterface Dict<V> {\n  [propName: string]: V\n}\n\ninterface pageRequest {\n  current_page: number\n  page_size: number\n}\n\ninterface PageList<T> {\n  current: number,\n  size: number,\n  total: number,\n  records: T\n}\n\ninterface ListItem {\n  name: string,\n  id?: string,\n}\nexport type { KeyValue, Dict, pageRequest, PageList, ListItem }\n"
  },
  {
    "path": "ui/src/api/type/knowledge.ts",
    "content": "interface knowledgeData {\n  name: string\n  folder_id?: string\n  desc: string\n  embedding_model_id?: string\n  documents?: Array<any>\n}\n\nexport type { knowledgeData }\n"
  },
  {
    "path": "ui/src/api/type/login.ts",
    "content": "interface LoginRequest {\n  /**\n   * 用户名\n   */\n  username: string\n  /**\n   * 密码\n   */\n  password: string\n  /**\n   * 验证码\n   */\n  captcha: string\n  /**\n   * 加密数据\n   */\n  encryptedData?: string\n}\nexport type { LoginRequest }\n"
  },
  {
    "path": "ui/src/api/type/model.ts",
    "content": "import type { Dict } from './common'\ninterface modelRequest {\n  name: string\n  model_type: string\n  model_name: string\n}\n\ninterface Provider {\n  /**\n   * 供应商代号\n   */\n  provider: string\n  /**\n   * 供应商名称\n   */\n  name: string\n  /**\n   * 供应商icon\n   */\n  icon: string\n}\n\ninterface ListModelRequest {\n  /**\n   * 模型名称\n   */\n  name?: string\n  /**\n   * 模型类型\n   */\n  model_type?: string\n  /**\n   * 基础模型名称\n   */\n  model_name?: string\n  /**\n   * 供应商\n   */\n  provider?: string\n\n  workspace_id?: string\n}\n\ninterface Model {\n  /**\n   * 主键id\n   */\n  id: string\n  /**\n   * 模型名\n   */\n  name: string\n  /**\n   * 模型类型\n   */\n  model_type: string\n  user_id: string\n  username: string\n  nick_name: string\n  /**\n   * 基础模型\n   */\n  model_name: string\n  /**\n   * 认证信息\n   */\n  credential: any\n  /**\n   * 供应商\n   */\n  provider: string\n  /**\n   * 状态\n   */\n  status: 'SUCCESS' | 'DOWNLOAD' | 'ERROR' | 'PAUSE_DOWNLOAD'\n  /**\n   * 元数据\n   */\n  meta: Dict<any>\n  /**\n   * 模型参数配置\n   */\n  model_params_form: Dict<any>[]\n  resource_count: number\n  create_time?: any\n}\ninterface CreateModelRequest {\n  /**\n   * 模型名\n   */\n  name: string\n  /**\n   * 模型类型\n   */\n  model_type: string\n  /**\n   * 基础模型\n   */\n  model_name: string\n  /**\n   * 认证信息\n   */\n  credential: any\n  /**\n   * 供应商\n   */\n  provider: string\n}\n\ninterface EditModelRequest {\n  /**\n   * 模型名\n   */\n  name: string\n  /**\n   * 模型类型\n   */\n  model_type: string\n  /**\n   * 基础模型\n   */\n  model_name: string\n  /**\n   * 认证信息\n   */\n  credential: any\n}\n\ninterface BaseModel {\n  /**\n   * 基础模型名称\n   */\n  name: string\n  /**\n   * 基础模型描述\n   */\n  desc: string\n  /**\n   * 基础模型类型\n   */\n  model_type: string\n}\nexport type {\n  modelRequest,\n  Provider,\n  ListModelRequest,\n  Model,\n  BaseModel,\n  CreateModelRequest,\n  EditModelRequest\n}\n"
  },
  {
    "path": "ui/src/api/type/role.ts",
    "content": "import { RoleTypeEnum } from '@/enums/system'\nimport type { FormItemRule } from 'element-plus'\ninterface RoleItem {\n  id: string,\n  role_name: string,\n  type: RoleTypeEnum,\n  create_user: string,\n  internal: boolean,\n  user_count?: number,\n}\n\ninterface ChildrenPermissionItem {\n  id: string\n  name: string\n  enable: boolean\n}\n\ninterface RolePermissionItem {\n  id: string,\n  name: string,\n  children: {\n    id: string,\n    name: string,\n    permission: ChildrenPermissionItem[],\n    enable: boolean,\n  }[]\n}\n\ninterface RoleTableDataItem {\n  module: string\n  name: string\n  permission: ChildrenPermissionItem[]\n  enable: boolean\n  perChecked: string[]\n  indeterminate: boolean\n}\n\ninterface CreateOrUpdateParams {\n  role_id?: string,\n  role_name: string,\n  role_type?: RoleTypeEnum,\n}\n\ninterface RoleMemberItem {\n  user_relation_id: string,\n  user_id: string,\n  username: string,\n  nick_name: string,\n  workspace_id: string,\n  workspace_name: string,\n}\n\ninterface CreateMemberParamsItem {\n  user_ids: string[],\n  workspace_ids?: string[]\n}\n\ntype Arrayable<T> = T | T[]\ninterface FormItemModel {\n  path: string\n  label?: string\n  rules?: Arrayable<FormItemRule>,\n  hidden?: (e: any) => boolean,\n  selectProps?: {\n    options?: { label: string, value: string, disabledFunction?: (e: any) => boolean }[]\n    placeholder?: string\n    multiple?: boolean\n    clearableFunction?: (e: any) => boolean\n  }\n}\n\nexport type { RoleItem, FormItemModel, RolePermissionItem, RoleTableDataItem, CreateOrUpdateParams, ChildrenPermissionItem, RoleMemberItem, CreateMemberParamsItem }\n"
  },
  {
    "path": "ui/src/api/type/systemChatUser.ts",
    "content": "interface ChatUserItem {\n  create_time: string,\n  email: string,\n  id: string,\n  nick_name: string,\n  phone: string,\n  source: string,\n  update_time: string,\n  username: string,\n  is_active: boolean,\n  user_group_ids?: string[],\n  user_group_names?: string[],\n}\n\ninterface ChatUserGroupUserItem {\n  id: string,\n  email: string,\n  phone: string,\n  nick_name: string,\n  username: string,\n  source: string,\n  is_active: boolean,\n  create_time: string,\n  update_time: string,\n  user_group_relation_id: string,\n}\nexport type { ChatUserGroupUserItem, ChatUserItem }\n"
  },
  {
    "path": "ui/src/api/type/tool.ts",
    "content": "interface toolData {\n  id?: string\n  name?: string\n  icon?: string\n  desc?: string\n  code?: string\n  input_field_list?: Array<any>\n  init_field_list?: Array<any>\n  is_active?: boolean\n  folder_id?: string\n  tool_type?: string\n  fileList?: Array<any>\n}\n\ninterface AddInternalToolParam {\n  name: string,\n  folder_id: string\n}\n\nexport type { toolData, AddInternalToolParam }\n"
  },
  {
    "path": "ui/src/api/type/trigger.ts",
    "content": "interface TriggerData {\n  id?: string\n  name?: string\n  desc?: string\n  trigger_type?: string\n  trigger_setting?: Record<string,any>\n  meta?: Record<string,any>\n  is_active?: boolean\n}\n\nexport type { TriggerData }\n"
  },
  {
    "path": "ui/src/api/type/user.ts",
    "content": "interface User {\n  /**\n   * 用户id\n   */\n  id: string\n  /**\n   * 用户名\n   */\n  username: string\n  nick_name: string\n  /**\n   * 邮箱\n   */\n  email: string\n  /**\n   * 用户角色\n   */\n  role: Array<string>\n  /**\n   * 用户权限\n   */\n  permissions: Array<string>\n  /**\n   * 是否需要修改密码\n   */\n  is_edit_password?: boolean\n  IS_XPACK?: boolean\n  XPACK_LICENSE_IS_VALID?: boolean\n  language?: string\n  workspace_list?: Array<any>\n  role_name?: Array<any>\n  source?: string\n}\n\ninterface LoginRequest {\n  /**\n   * 用户名\n   */\n  username: string\n  /**\n   * 密码\n   */\n  password: string\n}\n\ninterface RegisterRequest {\n  /**\n   * 用户名\n   */\n  username: string\n  /**\n   * 密码\n   */\n  password: string\n  /**\n   * 确定密码\n   */\n  re_password: string\n  /**\n   * 邮箱\n   */\n  email: string\n  /**\n   * 验证码\n   */\n  code: string\n}\n\ninterface CheckCodeRequest {\n  /**\n   * 邮箱\n   */\n  email: string\n  /**\n   *验证码\n   */\n  code: string\n  /**\n   * 类型\n   */\n  type: 'register' | 'reset_password'\n}\n\ninterface ResetCurrentUserPasswordRequest {\n  /**\n   * 验证码\n   */\n  code?: string\n  /**\n   *密码\n   */\n  password: string\n  /**\n   * 确认密码\n   */\n  re_password: string\n}\n\ninterface ResetPasswordRequest {\n  /**\n   * 邮箱\n   */\n  email?: string\n  /**\n   * 验证码\n   */\n  code?: string\n  /**\n   * 密码\n   */\n  password: string\n  /**\n   * 确认密码\n   */\n  re_password: string\n}\n\nexport type {\n  LoginRequest,\n  RegisterRequest,\n  CheckCodeRequest,\n  ResetPasswordRequest,\n  User,\n  ResetCurrentUserPasswordRequest,\n}\n"
  },
  {
    "path": "ui/src/api/type/workspace.ts",
    "content": "interface WorkspaceItem {\n  name: string,\n  id?: string,\n  user_count?: number,\n}\n\ninterface CreateWorkspaceMemberParamsItem {\n  user_ids: string[],\n  role_ids: string[]\n}\n\ninterface WorkspaceMemberItem {\n  user_relation_id: string,\n  user_id: string,\n  username: string,\n  nick_name: string,\n  role_id: string,\n  role_name: string,\n}\nexport type { WorkspaceItem, CreateWorkspaceMemberParamsItem, WorkspaceMemberItem }\n"
  },
  {
    "path": "ui/src/api/type/workspaceChatUser.ts",
    "content": "\nimport { SourceTypeEnum } from '@/enums/common'\n\ninterface ChatUserGroupItem {\n  id: string,\n  name: string,\n  is_auth: boolean\n}\n\ninterface ChatUserGroupUserItem {\n  id: string,\n  is_auth: boolean,\n  email: string,\n  phone: string,\n  nick_name: string,\n  username: string,\n  password: string,\n  source: string,\n  is_active: boolean,\n  create_time: string,\n  update_time: string,\n}\n\ninterface putUserGroupUserParams {\n  chat_user_id: string,\n  is_auth: boolean\n}\nexport type { ChatUserGroupItem, putUserGroupUserParams, ChatUserGroupUserItem }\n"
  },
  {
    "path": "ui/src/api/user/login.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post} from '@/request/index'\nimport type {LoginRequest} from '@/api/type/login'\nimport type {Ref} from 'vue'\nimport type {User} from \"@/api/type/user.ts\";\n\n/**\n * 登录\n * @param request 登录接口请求表单\n * @param loading 接口加载器\n * @returns 认证数据\n */\nconst login: (request: LoginRequest, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  request,\n  loading,\n) => {\n  return post('/user/login', request, undefined, loading)\n}\n\nconst ldapLogin: (request: LoginRequest, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  request,\n  loading,\n) => {\n  return post('/ldap/login', request, undefined, loading)\n}\n\n\n/**\n * 登出\n * @param loading 接口加载器\n * @returns\n */\nconst logout: (loading?: Ref<boolean>) => Promise<Result<boolean>> = (loading) => {\n  return post('/user/logout', undefined, undefined, loading)\n}\n\n/**\n * 获取验证码\n * @param loading 接口加载器\n */\nconst getCaptcha: (username?: string, loading?: Ref<boolean>) => Promise<Result<any>> = (username, loading) => {\n  return get('/user/captcha', {username}, loading)\n}\n\n/**\n * 获取登录方式\n */\nconst getAuthType: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get('auth/types', undefined, loading)\n}\n\n/**\n * 获取二维码类型\n */\nconst getQrType: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get('qr_type', undefined, loading)\n}\n\nconst getQrSource: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get('qr_type/source', undefined, loading)\n}\n\nconst getDingCallback: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  code,\n  loading\n) => {\n  return get('dingtalk', {code}, loading)\n}\n\nconst getDingOauth2Callback: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  code,\n  loading\n) => {\n  return get('dingtalk/oauth2', {code}, loading)\n}\n\nconst getWecomCallback: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  code,\n  loading\n) => {\n  return get('wecom', {code}, loading)\n}\nconst getLarkCallback: (code: string, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  code,\n  loading\n) => {\n  return get('lark/oauth2', {code}, loading)\n}\n\n/**\n * 设置语言\n * data: {\n * \"language\": \"string\"\n * }\n */\nconst postLanguage: (data: any, loading?: Ref<boolean>) => Promise<Result<User>> = (\n  data,\n  loading\n) => {\n  return post('/user/language', data, undefined, loading)\n}\nconst samlLogin: (loading?: Ref<boolean>) => Promise<Result<any>> = (\n  loading,\n) => {\n  return get('/saml2', '', loading)\n}\nexport default {\n  login,\n  logout,\n  getCaptcha,\n  getAuthType,\n  getDingCallback,\n  getQrType,\n  getWecomCallback,\n  postLanguage,\n  getDingOauth2Callback,\n  getLarkCallback,\n  getQrSource,\n  ldapLogin,\n  samlLogin\n}\n"
  },
  {
    "path": "ui/src/api/user/user.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post } from '@/request/index'\nimport type { User, ResetPasswordRequest, CheckCodeRequest } from '@/api/type/user'\nimport type { Ref } from 'vue'\n/**\n * 获取用户基本信息\n * @param loading 接口加载器\n * @returns 用户基本信息\n */\nconst getUserProfile: (loading?: Ref<boolean>) => Promise<Result<User>> = (loading) => {\n  return get('/user/profile', undefined, loading)\n}\n\n/**\n * 获取profile\n */\nconst getProfile: (loading?: Ref<boolean>) => Promise<Result<any>> = (loading) => {\n  return get('/profile', undefined, loading)\n}\n/**\n * 获取全部用户\n */\nconst getUserList: (loading?: Ref<boolean>) => Promise<Result<Record<string, any>[]>> = (\n  loading,\n) => {\n  return get('/user/list', undefined, loading)\n}\n\n/**\n * 获取全部用户\n */\nconst getAllMemberList: (arg: string, loading?: Ref<boolean>) => Promise<Result<Record<string, any>[]>> = (\n  arg,\n  loading,\n) => {\n  return get('/user/list', undefined, loading)\n}\n\n/**\n * 校验验证码\n * @param request 请求对象\n * @param loading 接口加载器\n * @returns\n */\nconst checkCode: (request: CheckCodeRequest, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  request,\n  loading,\n) => {\n  return post('/user/check_code', request, undefined, loading)\n}\n\n/**\n * 发送邮件\n * @param email  邮件地址\n * @param loading 接口加载器\n * @returns\n */\nconst sendEmit: (\n  email: string,\n  type: 'register' | 'reset_password',\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (email, type, loading) => {\n  return post('/user/send_email', { email, type }, undefined, loading)\n}\n\n/**\n * 重置密码\n * @param request 重置密码请求参数\n * @param loading 接口加载器\n * @returns\n */\nconst postResetPassword: (\n  request: ResetPasswordRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (request, loading) => {\n  return post('/user/re_password', request, undefined, loading)\n}\n\n/**\n * 重置密码\n * @param request 重置密码请求参数\n * @param loading 接口加载器\n * @returns\n */\nconst resetCurrentPassword: (\n  request: ResetPasswordRequest,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (request, loading) => {\n  return post('/user/current/reset_password', request, undefined, loading)\n}\n\nexport default {\n  getUserProfile,\n  getProfile,\n  getUserList,\n  getAllMemberList,\n  postResetPassword,\n  checkCode,\n  sendEmit,\n  resetCurrentPassword,\n}\n"
  },
  {
    "path": "ui/src/api/workspace/chat-user.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, put, post, del} from '@/request/index'\nimport type {pageRequest, PageList} from '@/api/type/common'\nimport type {ChatUserItem} from '@/api/type/systemChatUser'\nimport type {Ref} from 'vue'\n\nconst prefix = '/workspace/chat_user'\n\n\n/**\n * 用户列表\n */\nconst getChatUserList: (loading?: Ref<boolean>) => Promise<Result<ChatUserItem[]>> = (loading) => {\n  return get(`${prefix}/list`, undefined, loading)\n}\n\n/**\n * 用户分页列表\n * @query 参数\n username_or_nickname: string\n */\nconst getUserManage: (\n  page: pageRequest,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<PageList<ChatUserItem[]>>> = (page, params, loading) => {\n  return get(\n    `${prefix}/user_manage/${page.current_page}/${page.page_size}`,\n    params ? params : undefined,\n    loading,\n  )\n}\n\n/**\n * 删除用户\n * @param 参数 user_id,\n */\nconst delUserManage: (user_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  user_id,\n  loading,\n) => {\n  return del(`${prefix}/${user_id}`, undefined, {}, loading)\n}\n\n/**\n * 创建用户\n */\nconst postUserManage: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}`, data, undefined, loading)\n}\n\n/**\n * 编辑用户\n */\nconst putUserManage: (\n  user_id: string,\n  data: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (user_id, data, loading) => {\n  return put(`${prefix}/${user_id}`, data, undefined, loading)\n}\n\n/**\n * 修改用户密码\n */\nconst putUserManagePassword: (\n  user_id: string,\n  data: any,\n  loading?: Ref<boolean>\n) => Promise<Result<any>> = (user_id, data, loading) => {\n  return put(`${prefix}/${user_id}/re_password`, data, undefined, loading)\n}\n\n/**\n * 设置用户组\n */\nconst batchAddGroup: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/batch_add_group`, data, undefined, loading)\n}\n\n/**\n * 批量删除\n */\nconst batchDelete: (data: string[], loading?: Ref<boolean>) => Promise<Result<any>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}/batch_delete`, data, undefined, loading)\n}\nexport default {\n  getUserManage,\n  putUserManage,\n  delUserManage,\n  postUserManage,\n  putUserManagePassword,\n  getChatUserList,\n  batchAddGroup,\n  batchDelete,\n}\n"
  },
  {
    "path": "ui/src/api/workspace/folder.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, post, del, put } from '@/request/index'\nimport { type Ref } from 'vue'\n\nimport useStore from '@/stores'\nconst prefix: any = { _value: '/workspace/' }\nObject.defineProperty(prefix, 'value', {\n  get: function () {\n    const { user } = useStore()\n    return this._value + user.getWorkspaceId()\n  },\n})\n\n/**\n * 获得文件夹列表\n * @params 参数\n *  source : APPLICATION, KNOWLEDGE, TOOL\n *  data : {name: string}\n */\nconst getFolder: (\n  source: string,\n  data?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (source, data, loading) => {\n  return get(`${prefix.value}/${source}/folder`, data, loading)\n}\n\n/**\n * 添加文件夹\n * @params 参数\n *  source : APPLICATION, KNOWLEDGE, TOOL\n {\n \"name\": \"string\",\n \"desc\": \"string\",\n \"parent_id\": \"default\"\n }\n */\nconst postFolder: (\n  source: string,\n  data?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (source, data, loading) => {\n  return post(`${prefix.value}/${source}/folder`, data, null, loading)\n}\n\n/**\n * 获得文件夹详情\n * @params 参数\n *  folder_id\n *  source : APPLICATION, KNOWLEDGE, TOOL\n */\nconst getFolderDetail: (\n  folder_id: string,\n  source: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (folder_id, source, loading) => {\n  return get(`${prefix.value}/${source}/folder/${folder_id}`, null, loading)\n}\n/**\n * 修改文件夹\n * @params 参数\n *  folder_id: string,\n *  source : APPLICATION, KNOWLEDGE, TOOL\n {\n \"name\": \"string\",\n \"desc\": \"string\",\n \"parent_id\": \"default\"\n }\n */\nconst putFolder: (\n  folder_id: string,\n  source: string,\n  data?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<Array<any>>> = (folder_id, source, data, loading) => {\n  return put(`${prefix.value}/${source}/folder/${folder_id}`, data, {}, loading)\n}\n\n/**\n * 删除文件夹\n * @params 参数\n *  folder_id\n *  source : APPLICATION, KNOWLEDGE, TOOL\n */\nconst delFolder: (\n  folder_id: string,\n  source: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<boolean>> = (folder_id, source, loading) => {\n  return del(`${prefix.value}/${source}/folder/${folder_id}`, undefined, {}, loading)\n}\n\nexport default {\n  getFolder,\n  postFolder,\n  getFolderDetail,\n  putFolder,\n  delFolder,\n}\n"
  },
  {
    "path": "ui/src/api/workspace/resource-authorization.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, put, post, del } from '@/request/index'\nimport type { Ref } from 'vue'\nimport type { pageRequest } from '@/api/type/common'\nconst prefix = '/workspace'\n\n\n/**\n * 工作空间下各资源获取资源权限\n * @query 参数\n */\nconst getResourceAuthorization: (\n  workspace_id: string,\n  target: string,\n  resource: string,\n  page: pageRequest,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, target, resource, page, params, loading) => {\n  return get(\n    `${prefix}/${workspace_id}/resource_user_permission/resource/${target}/resource/${resource}/${page.current_page}/${page.page_size}`,\n    params,\n    loading,\n  )\n}\n\n/**\n * 工作空间下各资源修改成员权限\n * @param 参数 member_id\n * @param 参数 {\n     [\n      {\n        \"user_id\": \"string\",\n        \"permission\": \"NOT_AUTH\"\n      }\n    ]\n  }\n */\nconst putResourceAuthorization: (\n  workspace_id: string,\n  target: string,\n  resource: string,\n  body: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, target, resource, body, loading) => {\n  return put(\n    `${prefix}/${workspace_id}/resource_user_permission/resource/${target}/resource/${resource}`,\n    body,\n    {},\n    loading,\n  )\n}\n\nexport default {\n  getResourceAuthorization,\n  putResourceAuthorization\n}\n"
  },
  {
    "path": "ui/src/api/workspace/resource-mapping.ts",
    "content": "import { Result } from '@/request/Result'\nimport { get, put, post, del } from '@/request/index'\nimport type { Ref } from 'vue'\nimport type { pageRequest } from '@/api/type/common'\nconst prefix = '/workspace'\n\n/**\n * 工作空间下各个资源的映射关系\n * @query 参数\n */\nconst getResourceMapping: (\n  workspace_id: string,\n  resource: string,\n  resource_id: string,\n  page: pageRequest,\n  params?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, resource, resource_id, page, params, loading) => {\n  return get(\n    `${prefix}/${workspace_id}/resource_mapping/${resource}/${resource_id}/${page.current_page}/${page.page_size}`,\n    params,\n    loading,\n  )\n}\n\nexport default {\n  getResourceMapping,\n}\n"
  },
  {
    "path": "ui/src/api/workspace/role.ts",
    "content": "import { get, post, del } from '@/request/index'\nimport type { Ref } from 'vue'\nimport { Result } from '@/request/Result'\nimport type {\n  RoleItem,\n  RoleMemberItem,\n  CreateMemberParamsItem,\n} from '@/api/type/role'\nimport type { pageRequest, PageList } from '@/api/type/common'\n\nconst prefix = '/workspace/role'\n/**\n * 获取角色列表\n */\nconst getRoleList: (\n  loading?: Ref<boolean>,\n) => Promise<Result<{ internal_role: RoleItem[]; custom_role: RoleItem[] }>> = (loading) => {\n  return get(`${prefix}`, undefined, loading)\n}\n\n/**\n * 新建角色成员\n */\nconst CreateMember: (\n  role_id: string,\n  data: { members: CreateMemberParamsItem[] },\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (role_id, data, loading) => {\n  return post(`${prefix}/${role_id}/add_member`, data, undefined, loading)\n}\n\n/**\n * 获取角色成员列表\n */\nconst getRoleMemberList: (\n  role_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<PageList<RoleMemberItem[]>>> = (role_id, page, param, loading) => {\n  return get(\n    `${prefix}/${role_id}/user_list/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 删除角色成员\n */\nconst deleteRoleMember: (\n  role_id: string,\n  user_relation_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (role_id, user_relation_id, loading) => {\n  return del(`${prefix}/${role_id}/remove_member/${user_relation_id}`, undefined, {}, loading)\n}\n\nexport default {\n  getRoleList,\n  CreateMember,\n  getRoleMemberList,\n  deleteRoleMember,\n}\n"
  },
  {
    "path": "ui/src/api/workspace/user-group.ts",
    "content": "import {Result} from '@/request/Result'\nimport {get, post, del} from '@/request/index'\nimport type {Ref} from 'vue'\nimport type {ChatUserGroupUserItem,} from '@/api/type/systemChatUser'\nimport type {pageRequest, PageList, ListItem} from '@/api/type/common'\n\nconst prefix = '/workspace/group'\n\n/**\n * 获取用户组列表\n */\nconst getUserGroup: (loading?: Ref<boolean>) => Promise<Result<ListItem[]>> = () => {\n  return get(`${prefix}`)\n}\n\n/**\n * 创建用户组\n * @param 参数\n * {\n \"id\": \"string\",\n \"name\": \"string\"\n }\n */\nconst postUserGroup: (data: ListItem, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  data,\n  loading,\n) => {\n  return post(`${prefix}`, data, undefined, loading)\n}\n\n/**\n * 删除用户组\n * @param 参数 user_group_id\n */\nconst delUserGroup: (user_group_id: string, loading?: Ref<boolean>) => Promise<Result<boolean>> = (\n  user_group_id,\n  loading,\n) => {\n  return del(`${prefix}/${user_group_id}`, undefined, {}, loading)\n}\n\n/**\n * 给用户组添加用户\n */\nconst postAddMember: (\n  user_group_id: string,\n  body: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (user_group_id, body, loading) => {\n  return post(`${prefix}/${user_group_id}/add_member`, body, {}, loading)\n}\n\n/**\n * 从用户组删除用户\n */\nconst postRemoveMember: (\n  user_group_id: string,\n  body: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (user_group_id, body, loading) => {\n  return post(`${prefix}/${user_group_id}/remove_member`, body, {}, loading)\n}\n\n/**\n * 获取用户组的成员列表\n */\nconst getUserListByGroup: (\n  user_group_id: string,\n  page: pageRequest,\n  params ?: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<PageList<ChatUserGroupUserItem[]>>> = (user_group_id, page, params, loading) => {\n  return get(\n    `${prefix}/${user_group_id}/user_list/${page.current_page}/${page.page_size}`,\n    params ? params : undefined,\n    loading,\n  )\n}\nexport default {\n  getUserGroup,\n  postUserGroup,\n  delUserGroup,\n  postAddMember,\n  postRemoveMember,\n  getUserListByGroup\n}\n"
  },
  {
    "path": "ui/src/api/workspace/workspace.ts",
    "content": "import { Result } from '@/request/Result'\nimport type { Ref } from 'vue'\nimport { get, post, del } from '@/request/index'\nimport type {\n  WorkspaceItem,\n  CreateWorkspaceMemberParamsItem,\n  WorkspaceMemberItem,\n} from '@/api/type/workspace'\nimport type { pageRequest, PageList } from '@/api/type/common'\n\nconst prefix = '/workspace'\n\n/**\n * 获取首页的工作空间下拉列表\n */\nconst getWorkspaceListByUser: (loading?: Ref<boolean>) => Promise<Result<WorkspaceItem[]>> = (\n  loading,\n) => {\n  return get('/workspace/by_user', undefined, loading)\n}\n\n/**\n * 获取添加成员时的工作空间下拉列表\n */\nconst getWorkspaceList: (loading?: Ref<boolean>) => Promise<Result<Record<string, any>[]>> = (\n  loading,\n) => {\n  return get('/workspace/current_user', undefined, loading)\n}\n\n/**\n * 获取工作空间列表\n */\nconst getSystemWorkspaceList: (loading?: Ref<boolean>) => Promise<Result<WorkspaceItem[]>> = (\n  loading,\n) => {\n  return get(`${prefix}`, undefined, loading)\n}\n\n/**\n * 获取工作空间成员列表\n */\nconst getWorkspaceMemberList: (\n  workspace_id: string,\n  page: pageRequest,\n  param: any,\n  loading?: Ref<boolean>,\n) => Promise<Result<PageList<WorkspaceMemberItem[]>>> = (workspace_id, page, param, loading) => {\n  return get(\n    `${prefix}/${workspace_id}/user_list/${page.current_page}/${page.page_size}`,\n    param,\n    loading,\n  )\n}\n\n/**\n * 获取工作空间全部成员列表\n */\nconst getAllMemberList: (\n  workspace_id: string | null,\n  loading?: Ref<boolean>,\n) => Promise<Result<any[]>> = (workspace_id, loading) => {\n  return get(`${prefix}/${workspace_id}/user_list`, undefined, loading)\n}\n\n/**\n * 新建工作空间成员\n */\nconst CreateWorkspaceMember: (\n  workspace_id: string,\n  data: CreateWorkspaceMemberParamsItem[],\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, data, loading) => {\n  return post(`${prefix}/${workspace_id}/add_member`, data, undefined, loading)\n}\n\n/**\n * 删除工作空间成员\n */\nconst deleteWorkspaceMember: (\n  workspace_id: string,\n  user_relation_id: string,\n  loading?: Ref<boolean>,\n) => Promise<Result<any>> = (workspace_id, user_relation_id, loading) => {\n  return post(`${prefix}/${workspace_id}/remove_member/${user_relation_id}`, undefined, {}, loading)\n}\n\n/**\n * 获取添加成员时的角色下拉列表\n */\nconst getWorkspaceRoleList: (loading?: Ref<boolean>) => Promise<Result<Record<string, any>[]>> = (\n  loading,\n) => {\n  return get('/role_list/current_user', undefined, loading)\n}\n\nexport default {\n  getWorkspaceList,\n  getSystemWorkspaceList,\n  getWorkspaceMemberList,\n  getAllMemberList,\n  CreateWorkspaceMember,\n  deleteWorkspaceMember,\n  getWorkspaceRoleList,\n  getWorkspaceListByUser,\n}\n"
  },
  {
    "path": "ui/src/bus/index.ts",
    "content": "import mitt from \"mitt\";\nconst bus: any = {};\nconst emitter = mitt();\nbus.on = emitter.on;\nbus.off = emitter.off;\nbus.emit = emitter.emit;\n\nexport default bus;\n"
  },
  {
    "path": "ui/src/chat.ts",
    "content": "import '@/styles/index.scss'\nimport ElementPlus from 'element-plus'\nimport * as ElementPlusIcons from '@element-plus/icons-vue'\nimport zhCn from 'element-plus/es/locale/lang/zh-cn'\nimport enUs from 'element-plus/es/locale/lang/en'\nimport zhTW from 'element-plus/es/locale/lang/zh-tw'\nimport { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from './App.vue'\nimport router from '@/router/chat'\nimport i18n from '@/locales'\nimport Components from '@/components'\nimport directives from '@/directives'\n\nimport { getDefaultWhiteList } from 'xss'\nimport { config, XSSPlugin } from 'md-editor-v3'\nimport screenfull from 'screenfull'\n\nimport katex from 'katex'\nimport 'katex/dist/katex.min.css'\n\nimport Cropper from 'cropperjs'\n\nimport mermaid from 'mermaid'\n\nimport highlight from 'highlight.js'\nimport 'highlight.js/styles/atom-one-dark.css'\n\nconfig({\n  editorExtensions: {\n    highlight: {\n      instance: highlight,\n    },\n    screenfull: {\n      instance: screenfull,\n    },\n    katex: {\n      instance: katex,\n    },\n    cropper: {\n      instance: Cropper,\n    },\n    mermaid: {\n      instance: mermaid,\n    },\n  },\n  markdownItPlugins(plugins) {\n    return [\n      ...plugins,\n      {\n        type: 'xss',\n        plugin: XSSPlugin,\n        options: {\n          xss() {\n            return {\n              whiteList: Object.assign({}, getDefaultWhiteList(), {\n                video: ['src', 'controls', 'width', 'height', 'preload', 'playsinline'],\n                source: ['src', 'type'],\n                input: ['class', 'disabled', 'type', 'checked'],\n                iframe: [\n                  'class',\n                  'width',\n                  'height',\n                  'src',\n                  'title',\n                  'border',\n                  'frameborder',\n                  'framespacing',\n                  'allow',\n                  'allowfullscreen',\n                ],\n              }),\n              onTagAttr: (tag: string, name: any, value: any) => {\n                if (tag === 'video') {\n                  // 禁止自动播放\n                  if (name === 'autoplay') return ''\n\n                  // 限制 preload\n                  if (name === 'preload' && !['none', 'metadata'].includes(value)) {\n                    return 'preload=\"metadata\"'\n                  }\n                }\n                return undefined\n              },\n            }\n          },\n        },\n      },\n    ]\n  },\n})\nconst app = createApp(App)\napp.use(createPinia())\nfor (const [key, component] of Object.entries(ElementPlusIcons)) {\n  app.component(key, component)\n}\nconst locale_map: any = {\n  'zh-CN': zhCn,\n  'zh-Hant': zhTW,\n  'en-US': enUs,\n}\napp.use(ElementPlus, {\n  locale: locale_map[localStorage.getItem('MaxKB-locale') || navigator.language || 'en-US'],\n})\napp.use(directives)\napp.use(router)\napp.use(i18n)\napp.use(Components)\napp.mount('#app')\nexport { app }\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/answer-content/index.vue",
    "content": "<template>\n  <div class=\"item-content lighter\">\n    <div v-for=\"(answer_text, index) in answer_text_list\" :key=\"index\" class=\"mb-8\">\n      <div class=\"avatar mr-8\" v-if=\"showAvatar\">\n        <img v-if=\"application.avatar\" :src=\"application.avatar\" height=\"28px\" width=\"28px\" />\n        <LogoIcon v-else height=\"28px\" width=\"28px\" />\n      </div>\n      <div\n        class=\"content\"\n        @mouseup=\"openControl\"\n        :style=\"{\n          'padding-right': showUserAvatar ? 'var(--padding-left)' : '0',\n        }\"\n      >\n        <el-card shadow=\"always\" class=\"border-r-8\" style=\"--el-card-padding: 6px 16px\">\n          <MdRenderer\n            v-if=\"\n              (chatRecord.write_ed === undefined || chatRecord.write_ed === true) &&\n              answer_text.length == 0 &&\n              answer_text\n                .map((item) => item.content)\n                .join('')\n                .trim().length == 0\n            \"\n            :source=\"$t('chat.tip.answerMessage')\"\n          ></MdRenderer>\n          <template v-else-if=\"answer_text.length > 0\">\n            <MdRenderer\n              v-for=\"(answer, index) in answer_text\"\n              :key=\"index\"\n              :chat_record_id=\"answer.chat_record_id\"\n              :child_node=\"answer.child_node\"\n              :runtime_node_id=\"answer.runtime_node_id\"\n              :reasoning_content=\"answer.reasoning_content\"\n              :disabled=\"loading || type == 'log'\"\n              :source=\"answer.content\"\n              :send-message=\"chatMessage\"\n            ></MdRenderer>\n          </template>\n          <p v-else-if=\"chatRecord.is_stop\" shadow=\"always\" style=\"margin: 0.5rem 0\">\n            {{ $t('chat.tip.stopAnswer') }}\n          </p>\n          <p v-else shadow=\"always\" style=\"margin: 0.5rem 0\">\n            {{ $t('chat.tip.answerLoading') }} <span class=\"dotting\"></span>\n          </p>\n          <!-- 知识来源 -->\n          <KnowledgeSourceComponent\n            :data=\"chatRecord\"\n            :application=\"application\"\n            :type=\"type\"\n            :appType=\"application.type\"\n            :executionIsRightPanel=\"props.executionIsRightPanel\"\n            @open-execution-detail=\"emit('openExecutionDetail')\"\n            @openParagraph=\"emit('openParagraph')\"\n            @openParagraphDocument=\"(val: string) => emit('openParagraphDocument', val)\"\n            v-if=\"showSource(chatRecord) && index === chatRecord.answer_text_list.length - 1\"\n          />\n        </el-card>\n      </div>\n    </div>\n\n    <div\n      class=\"content\"\n      :style=\"{\n        'padding-left': showAvatar ? 'var(--padding-left)' : '0',\n        'padding-right': showUserAvatar ? 'var(--padding-left)' : '0',\n      }\"\n      v-if=\"!selection\"\n    >\n      <OperationButton\n        :type=\"type\"\n        :application=\"application\"\n        :chatRecord=\"chatRecord\"\n        @update:chatRecord=\"(event: any) => emit('update:chatRecord', event)\"\n        :loading=\"loading\"\n        :start-chat=\"startChat\"\n        :stop-chat=\"stopChat\"\n        :regenerationChart=\"regenerationChart\"\n      ></OperationButton>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted } from 'vue'\nimport KnowledgeSourceComponent from '@/components/ai-chat/component/knowledge-source-component/index.vue'\nimport MdRenderer from '@/components/markdown/MdRenderer.vue'\nimport OperationButton from '@/components/ai-chat/component/operation-button/index.vue'\nimport { type chatType } from '@/api/type/application'\nimport bus from '@/bus'\n\nconst props = defineProps<{\n  chatRecord: chatType\n  application: any\n  loading: boolean\n  sendMessage: (question: string, other_params_data?: any, chat?: chatType) => Promise<boolean>\n  chatManagement: any\n  type: 'log' | 'ai-chat' | 'debug-ai-chat' | 'share'\n  executionIsRightPanel?: boolean\n  selection?: boolean\n}>()\n\nconst emit = defineEmits([\n  'update:chatRecord',\n  'openExecutionDetail',\n  'openParagraph',\n  'openParagraphDocument',\n])\n\nconst showAvatar = computed(() => {\n  return props.application.show_avatar == undefined ? true : props.application.show_avatar\n})\nconst showUserAvatar = computed(() => {\n  return props.application.show_user_avatar == undefined ? true : props.application.show_user_avatar\n})\nconst chatMessage = (question: string, type: 'old' | 'new', other_params_data?: any) => {\n  if (type === 'old') {\n    add_answer_text_list(props.chatRecord.answer_text_list)\n    props.sendMessage(question, other_params_data, props.chatRecord).then(() => {\n      props.chatManagement.open(props.chatRecord.id)\n      props.chatManagement.write(props.chatRecord.id)\n    })\n  } else {\n    props.sendMessage(question, other_params_data)\n  }\n}\nconst add_answer_text_list = (answer_text_list: Array<any>) => {\n  answer_text_list.push([])\n}\n\nconst openControl = (event: any) => {\n  if (props.type !== 'log') {\n    bus.emit('open-control', event)\n  }\n}\n\nconst answer_text_list = computed(() => {\n  return props.chatRecord.answer_text_list.map((item) => {\n    if (typeof item == 'string') {\n      return [\n        {\n          content: item,\n          chat_record_id: undefined,\n          child_node: undefined,\n          runtime_node_id: undefined,\n          reasoning_content: undefined,\n        },\n      ]\n    } else if (item instanceof Array) {\n      return item\n    } else {\n      return [item]\n    }\n  })\n})\n\nfunction showSource(row: any) {\n  if (props.type === 'log') {\n    return true\n  } else if (row.write_ed && 500 !== row.status) {\n    return true\n  }\n  return false\n}\n\nconst regenerationChart = (chat: chatType) => {\n  const container = props.chatRecord?.upload_meta\n    ? props.chatRecord.upload_meta\n    : props.chatRecord.execution_details?.find((detail) => detail.type === 'start-node')\n\n  props.sendMessage(chat.problem_text, {\n    re_chat: true,\n    image_list: container?.image_list || [],\n    document_list: container?.document_list || [],\n    audio_list: container?.audio_list || [],\n    video_list: container?.video_list || [],\n    other_list: container?.other_list || [],\n  })\n}\nconst stopChat = (chat: chatType) => {\n  props.chatManagement.stop(chat.id)\n}\nconst startChat = (chat: chatType) => {\n  props.chatManagement.write(chat.id)\n}\n\nonMounted(() => {\n  bus.on('chat:stop', () => {\n    stopChat(props.chatRecord)\n  })\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/chat-input-operate/TouchChat.vue",
    "content": "<template>\n  <div class=\"touch-chat p-8 pb-0\">\n    <el-button\n      text\n      bg\n      class=\"microphone-button w-full\"\n      style=\"font-size: 1rem; padding: 1.2rem 0 !important; background-color: #eff0f1\"\n      @touchstart=\"onTouchStart\"\n      @touchmove=\"onTouchMove\"\n      @touchend=\"onTouchEnd\"\n      :disabled=\"disabled\"\n    >\n      {{ disabled ? $t('chat.inputPlaceholder.chatting') : $t('chat.inputPlaceholder.holdToTalk') }}\n    </el-button>\n    <!-- 使用 custom-class 自定义样式 -->\n    <transition name=\"el-fade-in-linear\">\n      <el-card\n        class=\"custom-speech-card white-bg\"\n        :class=\"isTouching ? '' : 'active'\"\n        v-if=\"dialogVisible\"\n      >\n        <p>\n          <el-text type=\"info\" v-if=\"isTouching\"\n            >00:{{ props.time < 10 ? `0${props.time}` : props.time }}</el-text\n          >\n          <span class=\"lighter\" v-else>\n            {{ message }}\n          </span>\n        </p>\n        <el-avatar :size=\"isTouching ? 43 : 50\" icon=\"Close\" class=\"close\" />\n        <!-- <div class=\"close\"></div> -->\n        <p class=\"lighter\" :style=\"{ visibility: isTouching ? 'visible' : 'hidden' }\">\n          {{ message }}\n        </p>\n        <div class=\"speech-img flex-center border-r-6 mt-16\">\n          <img v-if=\"isTouching\" src=\"@/assets/chat/acoustic-color.svg\" alt=\"\" />\n          <img v-else src=\"@/assets/chat/acoustic.svg\" alt=\"\" />\n        </div>\n      </el-card>\n    </transition>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { t } from '@/locales'\nconst props = defineProps({\n  time: {\n    type: Number,\n    default: 0,\n  },\n  start: {\n    type: Boolean,\n    default: false,\n  },\n  disabled: {\n    type: Boolean,\n    default: false,\n  },\n})\nconst emit = defineEmits(['TouchStart', 'TouchEnd'])\n// 移动端语音\nconst startY = ref(0)\nconst isTouching = ref(false)\nconst dialogVisible = ref(false)\nconst message = ref(t('chat.inputPlaceholder.holdToTalk'))\n\nwatch(\n  () => [props.time, props.start],\n  ([time, start]) => {\n    if (start) {\n      isTouching.value = true\n      dialogVisible.value = true\n      message.value = t('chat.inputPlaceholder.touchChatMessage')\n      if (time === 60) {\n        dialogVisible.value = false\n        emit('TouchEnd', isTouching.value)\n        isTouching.value = false\n      }\n    } else {\n      dialogVisible.value = false\n      isTouching.value = false\n    }\n  },\n)\nwatch(\n  () => props.start,\n  (val) => {\n    if (val) {\n      isTouching.value = true\n      dialogVisible.value = true\n      message.value = t('chat.inputPlaceholder.touchChatMessage')\n    } else {\n      dialogVisible.value = false\n      isTouching.value = false\n    }\n  },\n)\n\nfunction onTouchStart(event: any) {\n  // 阻止默认滚动行为\n  event.preventDefault()\n  if (props.disabled) {\n    return\n  }\n  emit('TouchStart')\n  startY.value = event.touches[0].clientY\n}\nfunction onTouchMove(event: any) {\n  if (!isTouching.value) return\n  // 阻止默认滚动行为\n  event.preventDefault()\n  const currentY = event.touches[0].clientY\n  const deltaY = currentY - startY.value\n  // 判断是否上滑\n  if (deltaY < -50) {\n    // -50 是一个阈值，可以根据需要调整\n    message.value = t('chat.inputPlaceholder.cancelTouchChat')\n    isTouching.value = false\n  }\n}\nfunction onTouchEnd() {\n  emit('TouchEnd', isTouching.value)\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.custom-speech-card {\n  position: fixed;\n  bottom: 10px;\n  left: 50%; /* 水平居中 */\n  transform: translateX(-50%);\n  width: 92%;\n  border: 1px solid #ffffff;\n  box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n  z-index: 999;\n  text-align: center;\n  color: var(--app-text-color-secondary);\n  -webkit-touch-callout: none;\n  -webkit-user-select: none;\n  -khtml-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n  .close {\n    box-shadow: 0px 4px 8px 0px rgba(var(--el-text-color-primary-rgb), 0.1);\n    border: 1px solid rgba(222, 224, 227, 1);\n    background: rgba(255, 255, 255, 1);\n    color: var(--app-text-color-secondary);\n    font-size: 1.6rem;\n    margin: 20px 0;\n  }\n  .speech-img {\n    text-align: center;\n    background: #ebf1ff;\n    padding: 8px;\n    img {\n      height: 25px;\n    }\n  }\n  &.active {\n    .close {\n      background: #f54a45;\n      color: #ffffff;\n      border: none;\n      font-size: 2rem;\n    }\n    .speech-img {\n      background: #eff0f1;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/chat-input-operate/index.vue",
    "content": "<template>\n  <div class=\"ai-chat__operate p-16\" @drop.prevent=\"handleDrop\" @dragover.prevent>\n    <div class=\"text-center mb-8\" v-if=\"loading\">\n      <el-button class=\"border-primary video-stop-button\" @click=\"stopChat\">\n        <app-icon iconName=\"app-video-stop\" class=\"mr-8\"></app-icon>\n        {{ $t('chat.operation.stopChat') }}\n      </el-button>\n    </div>\n\n    <div class=\"operate-textarea\">\n      <el-scrollbar max-height=\"136\">\n        <div\n          class=\"p-8-12\"\n          v-loading=\"uploadLoading\"\n          v-if=\"\n            uploadDocumentList.length ||\n            uploadImageList.length ||\n            uploadAudioList.length ||\n            uploadVideoList.length ||\n            uploadOtherList.length\n          \"\n        >\n          <el-row :gutter=\"10\">\n            <el-col\n              v-for=\"(item, index) in uploadDocumentList\"\n              :key=\"index\"\n              :xs=\"24\"\n              :sm=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              :md=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              :lg=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              :xl=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              class=\"mb-8\"\n            >\n              <el-card\n                shadow=\"never\"\n                style=\"--el-card-padding: 8px; max-width: 100%\"\n                class=\"file cursor\"\n              >\n                <div\n                  class=\"flex-between align-center\"\n                  @mouseenter.stop=\"mouseenter(item)\"\n                  @mouseleave.stop=\"mouseleave()\"\n                >\n                  <div class=\"flex align-center\">\n                    <img :src=\"getImgUrl(item && item?.name)\" alt=\"\" width=\"24\" />\n                    <div class=\"ml-4 ellipsis-1\" :title=\"item && item?.name\">\n                      {{ item && item?.name }}\n                    </div>\n                  </div>\n                  <div\n                    @click=\"deleteFile(item)\"\n                    class=\"delete-icon color-secondary\"\n                    v-if=\"showDelete === item.url\"\n                  >\n                    <el-icon style=\"font-size: 16px; top: 2px\">\n                      <CircleCloseFilled />\n                    </el-icon>\n                  </div>\n                </div>\n              </el-card>\n            </el-col>\n            <el-col\n              v-for=\"(item, index) in uploadOtherList\"\n              :key=\"index\"\n              :xs=\"24\"\n              :sm=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              :md=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              :lg=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              :xl=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              class=\"mb-8\"\n            >\n              <el-card\n                shadow=\"never\"\n                style=\"--el-card-padding: 8px; max-width: 100%\"\n                class=\"file cursor\"\n              >\n                <div\n                  class=\"flex-between align-center\"\n                  @mouseenter.stop=\"mouseenter(item)\"\n                  @mouseleave.stop=\"mouseleave()\"\n                >\n                  <div class=\"flex align-center\">\n                    <img :src=\"getImgUrl(item && item?.name)\" alt=\"\" width=\"24\" />\n                    <div class=\"ml-4 ellipsis-1\" :title=\"item && item?.name\">\n                      {{ item && item?.name }}\n                    </div>\n                  </div>\n                  <div\n                    @click=\"deleteFile(item)\"\n                    class=\"delete-icon color-secondary\"\n                    v-if=\"showDelete === item.url\"\n                  >\n                    <el-icon style=\"font-size: 16px; top: 2px\">\n                      <CircleCloseFilled />\n                    </el-icon>\n                  </div>\n                </div>\n              </el-card>\n            </el-col>\n\n            <el-col\n              :xs=\"24\"\n              :sm=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              :md=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              :lg=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              :xl=\"props.type === 'debug-ai-chat' ? 24 : 12\"\n              class=\"mb-8\"\n              v-for=\"(item, index) in uploadAudioList\"\n              :key=\"index\"\n            >\n              <el-card shadow=\"never\" style=\"--el-card-padding: 8px\" class=\"file cursor\">\n                <div\n                  class=\"flex-between align-center\"\n                  @mouseenter.stop=\"mouseenter(item)\"\n                  @mouseleave.stop=\"mouseleave()\"\n                >\n                  <div class=\"flex align-center\">\n                    <img :src=\"getImgUrl(item && item?.name)\" alt=\"\" width=\"24\" />\n                    <div class=\"ml-4 ellipsis-1\" :title=\"item && item?.name\">\n                      {{ item && item?.name }}\n                    </div>\n                  </div>\n                  <div\n                    @click=\"deleteFile(item)\"\n                    class=\"delete-icon color-secondary\"\n                    v-if=\"showDelete === item.url\"\n                  >\n                    <el-icon style=\"font-size: 16px; top: 2px\">\n                      <CircleCloseFilled />\n                    </el-icon>\n                  </div>\n                </div>\n              </el-card>\n            </el-col>\n          </el-row>\n          <el-space wrap>\n            <template v-for=\"(item, index) in uploadImageList\" :key=\"index\">\n              <div\n                class=\"file file-image cursor border border-r-6\"\n                @mouseenter.stop=\"mouseenter(item)\"\n                @mouseleave.stop=\"mouseleave()\"\n              >\n                <div\n                  @click=\"deleteFile(item)\"\n                  class=\"delete-icon color-secondary\"\n                  v-if=\"showDelete === item.url\"\n                >\n                  <el-icon style=\"font-size: 16px; top: 2px\">\n                    <CircleCloseFilled />\n                  </el-icon>\n                </div>\n                <el-image\n                  v-if=\"item.url\"\n                  :src=\"item.url\"\n                  alt=\"\"\n                  fit=\"cover\"\n                  style=\"width: 40px; height: 40px; display: block\"\n                  class=\"border-r-6\"\n                />\n              </div>\n            </template>\n          </el-space>\n          <el-space wrap>\n            <template v-for=\"(item, index) in uploadVideoList\" :key=\"index\">\n              <div\n                class=\"file file-image cursor border border-r-6\"\n                @mouseenter.stop=\"mouseenter(item)\"\n                @mouseleave.stop=\"mouseleave()\"\n              >\n                <div\n                  @click=\"deleteFile(item)\"\n                  class=\"delete-icon color-secondary\"\n                  v-if=\"showDelete === item.url\"\n                >\n                  <el-icon style=\"font-size: 16px; top: 2px\">\n                    <CircleCloseFilled />\n                  </el-icon>\n                </div>\n                <video\n                  v-if=\"item.url\"\n                  :src=\"item.url\"\n                  controls\n                  style=\"width: 100px; display: block\"\n                  class=\"border-r-6\"\n                  autoplay\n                />\n              </div>\n            </template>\n          </el-space>\n        </div>\n      </el-scrollbar>\n\n      <TouchChat\n        v-if=\"isMicrophone\"\n        @TouchStart=\"startRecording\"\n        @TouchEnd=\"TouchEnd\"\n        :time=\"recorderTime\"\n        :start=\"recorderStatus === 'START'\"\n        :disabled=\"loading\"\n      />\n      <el-input\n        v-else\n        ref=\"quickInputRef\"\n        v-model=\"inputValue\"\n        :autosize=\"{ minRows: 1, maxRows: isMobile ? 4 : 10 }\"\n        type=\"textarea\"\n        :placeholder=\"inputPlaceholder\"\n        :maxlength=\"100000\"\n        @keydown.enter=\"sendChatHandle($event)\"\n        @paste=\"handlePaste\"\n        class=\"chat-operate-textarea\"\n      />\n\n      <div class=\"operate flex-between\">\n        <div>\n          <slot name=\"userInput\" />\n        </div>\n        <div class=\"flex align-center\">\n          <template v-if=\"props.applicationDetails.stt_model_enable\">\n            <span v-if=\"mode === 'mobile'\">\n              <el-button text @click=\"switchMicrophone(!isMicrophone)\">\n                <!-- 键盘 -->\n                <AppIcon v-if=\"isMicrophone\" iconName=\"app-keyboard\"></AppIcon>\n                <el-icon v-else>\n                  <!-- 录音 -->\n                  <Microphone />\n                </el-icon>\n              </el-button>\n            </span>\n            <span class=\"flex align-center\" v-else>\n              <el-button\n                :disabled=\"loading\"\n                text\n                @click=\"startRecording\"\n                v-if=\"recorderStatus === 'STOP'\"\n              >\n                <el-icon>\n                  <Microphone />\n                </el-icon>\n              </el-button>\n\n              <div v-else class=\"operate flex align-center\">\n                <el-text type=\"info\"\n                  >00:{{ recorderTime < 10 ? `0${recorderTime}` : recorderTime }}</el-text\n                >\n                <el-button\n                  text\n                  type=\"primary\"\n                  @click=\"stopRecording\"\n                  :loading=\"recorderStatus === 'TRANSCRIBING'\"\n                >\n                  <AppIcon iconName=\"app-video-stop\"></AppIcon>\n                </el-button>\n              </div>\n            </span>\n          </template>\n\n          <template v-if=\"recorderStatus === 'STOP' || mode === 'mobile'\">\n            <span v-if=\"props.applicationDetails.file_upload_enable\" class=\"flex align-center ml-4\">\n              <!-- 如果URL地址 -->\n              <el-button\n                v-if=\"props.applicationDetails.file_upload_setting.url_upload\"\n                text\n                :disabled=\"checkMaxFilesLimit() || loading\"\n                class=\"mt-4\"\n                @click=\"openUrlSetting\"\n              >\n                <el-icon><Paperclip /></el-icon>\n              </el-button>\n              <!-- 没有URL地址 -->\n              <el-upload\n                v-else\n                action=\"#\"\n                multiple\n                :auto-upload=\"false\"\n                :show-file-list=\"false\"\n                :accept=\"getAcceptList()\"\n                :on-change=\"(file: any, fileList: any) => uploadFile(file, fileList)\"\n                ref=\"upload\"\n              >\n                <el-tooltip\n                  :disabled=\"mode === 'mobile'\"\n                  effect=\"dark\"\n                  placement=\"top\"\n                  popper-class=\"upload-tooltip-width\"\n                >\n                  <template #content>\n                    <div class=\"break-all pre-wrap\">\n                      {{ $t('chat.uploadFile.label') }}：{{ $t('chat.uploadFile.most')\n                      }}{{ props.applicationDetails.file_upload_setting.maxFiles\n                      }}{{ $t('chat.uploadFile.limit') }}\n                      {{ props.applicationDetails.file_upload_setting.fileLimit }}MB<br />{{\n                        $t('chat.uploadFile.fileType')\n                      }}：{{ getAcceptList().replace(/\\./g, '').replace(/,/g, '、').toUpperCase() }}\n                    </div>\n                  </template>\n                  <el-button text :disabled=\"checkMaxFilesLimit() || loading\" class=\"mt-4\">\n                    <el-icon><Paperclip /></el-icon>\n                  </el-button>\n                </el-tooltip>\n              </el-upload>\n            </span>\n            <el-divider\n              direction=\"vertical\"\n              v-if=\"\n                props.applicationDetails.file_upload_enable ||\n                props.applicationDetails.stt_model_enable\n              \"\n            />\n            <el-button\n              text\n              class=\"sent-button\"\n              :disabled=\"isDisabledChat || loading || uploadLoading\"\n              @click=\"sendChatHandle\"\n            >\n              <img\n                v-show=\"isDisabledChat || loading || uploadLoading\"\n                src=\"@/assets/chat/icon_send.svg\"\n                alt=\"\"\n              />\n              <SendIcon v-show=\"!isDisabledChat && !loading && !uploadLoading\" />\n            </el-button>\n          </template>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"text-center mt-8\" v-if=\"applicationDetails.disclaimer\">\n      <el-text type=\"info\" v-if=\"applicationDetails.disclaimer\" class=\"font-small\">\n        <auto-tooltip :content=\"applicationDetails.disclaimer_value\">\n          {{ applicationDetails.disclaimer_value }}\n        </auto-tooltip>\n      </el-text>\n    </div>\n\n    <!-- 弹出URL设置框 -->\n    <div class=\"popperURLSetting\" v-if=\"showURLSetting\">\n      <el-card\n        shadow=\"always\"\n        class=\"border-r-8\"\n        style=\"--el-card-padding: 16px\"\n        v-if=\"props.applicationDetails.file_upload_setting.url_upload\"\n      >\n        <el-form label-position=\"top\" ref=\"urlFormRef\" :model=\"urlForm\">\n          <el-form-item>\n            <template #label>\n              <div class=\"flex-between\">\n                <span>{{ $t('chat.uploadFile.urlTitle') }}</span>\n                <el-select\n                  :teleported=\"false\"\n                  v-model=\"urlForm.type\"\n                  size=\"small\"\n                  style=\"width: 85px\"\n                >\n                  <el-option\n                    v-for=\"option in fileUploadOptions\"\n                    :key=\"option.value\"\n                    :label=\"option.label\"\n                    :value=\"option.value\"\n                    v-show=\"option.visible\"\n                  />\n                </el-select>\n              </div>\n            </template>\n            <el-input\n              v-model=\"urlForm.source_url\"\n              :placeholder=\"$t('chat.uploadFile.urlPlaceholder')\"\n              :rows=\"5\"\n              type=\"textarea\"\n            />\n          </el-form-item>\n        </el-form>\n        <div class=\"text-right\">\n          <el-button @click=\"showURLSetting = false\">{{ $t('common.cancel') }}</el-button>\n          <el-button type=\"primary\" @click=\"saveUrl\">{{ $t('common.confirm') }} </el-button>\n        </div>\n        <div v-if=\"props.applicationDetails.file_upload_setting.local_upload\">\n          <el-divider style=\"margin: 16px 0\" />\n          <el-upload\n            action=\"#\"\n            multiple\n            :auto-upload=\"false\"\n            :show-file-list=\"false\"\n            :accept=\"getAcceptList()\"\n            :on-change=\"(file: any, fileList: any) => uploadFile(file, fileList)\"\n            ref=\"upload\"\n            class=\"import-button\"\n          >\n            <el-button class=\"w-full url-upload-button\"\n              >{{ $t('chat.uploadFile.localUpload') }}\n            </el-button>\n          </el-upload>\n        </div>\n      </el-card>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, nextTick, onMounted, reactive, ref, type Ref } from 'vue'\nimport { t } from '@/locales'\nimport Recorder from 'recorder-core'\nimport TouchChat from './TouchChat.vue'\nimport applicationApi from '@/api/application/application'\nimport { MsgAlert, MsgWarning } from '@/utils/message'\nimport { type chatType } from '@/api/type/application'\nimport { useRoute, useRouter } from 'vue-router'\nimport { getImgUrl } from '@/utils/common'\nimport bus from '@/bus'\nimport 'recorder-core/src/engine/mp3'\nimport 'recorder-core/src/engine/mp3-engine'\nimport chatAPI from '@/api/chat/chat'\n\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  query: { mode, question },\n} = route as any\nconst quickInputRef = ref()\nconst props = withDefaults(\n  defineProps<{\n    applicationDetails: any\n    type: 'log' | 'ai-chat' | 'debug-ai-chat' | 'share'\n    loading: boolean\n    isMobile: boolean\n    appId?: string\n    chatId: string\n    sendMessage: (question: string, other_params_data?: any, chat?: chatType) => void\n    openChatId: () => Promise<string>\n    validate: () => Promise<any>\n  }>(),\n  {\n    applicationDetails: () => ({}),\n    available: true,\n  },\n)\nconst emit = defineEmits(['update:chatId', 'update:loading', 'update:showUserInput', 'backBottom'])\nconst chartOpenId = ref<string>()\nconst chatId_context = computed({\n  get: () => {\n    if (chartOpenId.value) {\n      return chartOpenId.value\n    }\n    return props.chatId\n  },\n  set: (v) => {\n    chartOpenId.value = v\n    emit('update:chatId', v)\n  },\n})\nconst localLoading = computed({\n  get: () => {\n    return props.loading\n  },\n  set: (v) => {\n    emit('update:loading', v)\n  },\n})\n\nconst showURLSetting = ref(false)\nconst urlForm = reactive({\n  source_url: '',\n  type: '',\n})\n\nconst uploadLoading = computed(() => {\n  return Object.values(filePromisionDict.value).length > 0\n})\n\nconst inputPlaceholder = computed(() => {\n  return recorderStatus.value === 'START'\n    ? `${t('chat.inputPlaceholder.speaking')}...`\n    : recorderStatus.value === 'TRANSCRIBING'\n      ? `${t('chat.inputPlaceholder.recorderLoading')}...`\n      : `${t('chat.inputPlaceholder.default')}`\n})\n\nconst upload = ref()\n\nconst imageExtensions = ['JPG', 'JPEG', 'PNG', 'GIF', 'BMP']\nconst documentExtensions = ['PDF', 'DOCX', 'TXT', 'XLS', 'XLSX', 'MD', 'HTML', 'CSV']\nconst videoExtensions = ['MP4', 'AVI', 'MKV', 'MOV', 'FLV', 'WMV']\nconst audioExtensions = ['MP3', 'WAV', 'OGG', 'AAC', 'M4A']\nconst otherExtensions = ref(['PPT', 'DOC'])\n\nconst getAcceptList = () => {\n  const { image, document, audio, video, other } = props.applicationDetails.file_upload_setting\n  let accepts: any = []\n  if (image) {\n    accepts = [...imageExtensions]\n  }\n  if (document) {\n    accepts = [...accepts, ...documentExtensions]\n  }\n  if (audio) {\n    accepts = [...accepts, ...audioExtensions]\n  }\n  if (video) {\n    accepts = [...accepts, ...videoExtensions]\n  }\n  if (other) {\n    // 其他文件类型\n    otherExtensions.value = props.applicationDetails.file_upload_setting.otherExtensions\n    accepts = [...accepts, ...otherExtensions.value]\n  }\n\n  if (accepts.length === 0) {\n    return `.${t('chat.uploadFile.tipMessage')}`\n  }\n  return accepts.map((ext: any) => '.' + ext).join(',')\n}\n\nconst checkMaxFilesLimit = () => {\n  return (\n    props.applicationDetails.file_upload_setting.maxFiles <=\n    uploadImageList.value.length +\n      uploadDocumentList.value.length +\n      uploadAudioList.value.length +\n      uploadVideoList.value.length +\n      uploadOtherList.value.length\n  )\n}\nconst filePromisionDict: any = ref<any>({})\nconst uploadFile = async (file: any, fileList: any) => {\n  const { maxFiles, fileLimit } = props.applicationDetails.file_upload_setting\n  // 单次上传文件数量限制\n  const file_limit_once =\n    uploadImageList.value.length +\n    uploadDocumentList.value.length +\n    uploadAudioList.value.length +\n    uploadVideoList.value.length +\n    uploadOtherList.value.length\n  if (file_limit_once >= maxFiles) {\n    MsgWarning(t('chat.uploadFile.limitMessage1') + maxFiles + t('chat.uploadFile.limitMessage2'))\n    fileList.splice(0, fileList.length, ...fileList.slice(0, maxFiles))\n    return\n  }\n  if (fileList.filter((f: any) => f.size == 0).length > 0) {\n    // MB\n    MsgWarning(t('chat.uploadFile.sizeLimit2'))\n    // 空文件上传过滤\n    fileList.splice(0, fileList.length, ...fileList.filter((f: any) => f.size > 0))\n    return\n  }\n  if (fileList.filter((f: any) => f.size > fileLimit * 1024 * 1024).length > 0) {\n    // MB\n    MsgWarning(t('chat.uploadFile.sizeLimit') + fileLimit + 'MB')\n    // 只保留未超出大小限制的文件\n    fileList.splice(\n      0,\n      fileList.length,\n      ...fileList.filter((f: any) => f.size <= fileLimit * 1024 * 1024),\n    )\n    return\n  }\n  filePromisionDict.value[file.uid] = false\n  const inner = reactive(file)\n  fileAllList.value.push(inner)\n  if (!chatId_context.value) {\n    chatId_context.value = await props.openChatId()\n  }\n  const api =\n    props.type === 'debug-ai-chat'\n      ? applicationApi.postUploadFile(file.raw, 'TEMPORARY_120_MINUTE', 'TEMPORARY_120_MINUTE')\n      : chatAPI.postUploadFile(file.raw, chatId_context.value, 'CHAT')\n\n  api.then((ok) => {\n    inner.url = ok.data\n    const split_path = ok.data.split('/')\n    inner.file_id = split_path[split_path.length - 1]\n    delete filePromisionDict.value[file.uid]\n  })\n  showURLSetting.value = false\n}\n// 粘贴处理\nconst handlePaste = (event: ClipboardEvent) => {\n  if (!props.applicationDetails.file_upload_enable) return\n  const clipboardData = event.clipboardData\n  if (!clipboardData) return\n\n  // 获取剪贴板中的文件\n  const files = clipboardData.files\n  if (files.length === 0) return\n\n  // 转换 FileList 为数组并遍历处理\n  Array.from(files).forEach((rawFile: File) => {\n    // 创建符合 el-upload 要求的文件对象\n    const elFile = {\n      uid: Date.now(), // 生成唯一ID\n      name: rawFile.name,\n      size: rawFile.size,\n      raw: rawFile, // 原始文件对象\n      status: 'ready', // 文件状态\n      percentage: 0, // 上传进度\n    }\n\n    // 手动触发上传逻辑（模拟 on-change 事件）\n    uploadFile(elFile, [elFile])\n  })\n\n  // 阻止默认粘贴行为\n  event.preventDefault()\n}\n// 新增拖拽处理\nconst handleDrop = (event: DragEvent) => {\n  if (!props.applicationDetails.file_upload_enable) return\n  event.preventDefault()\n  const files = event.dataTransfer?.files\n  if (!files) return\n\n  Array.from(files).forEach((rawFile) => {\n    const elFile = {\n      uid: Date.now(),\n      name: rawFile.name,\n      size: rawFile.size,\n      raw: rawFile,\n      status: 'ready',\n      percentage: 0,\n    }\n    uploadFile(elFile, [elFile])\n  })\n}\n// 语音录制任务id\nconst intervalId = ref<any | null>(null)\n// 语音录制开始秒数\nconst recorderTime = ref(0)\n// START:开始录音 TRANSCRIBING:转换文字中\nconst recorderStatus = ref<'START' | 'TRANSCRIBING' | 'STOP'>('STOP')\n\nconst inputValue = ref<string>('')\n\nconst fileAllList = ref<Array<any>>([])\n\nconst fileFilter = (fileList: Array<any>, extensionList: Array<string>) => {\n  return fileList.filter((f) => {\n    return extensionList.includes(f.name.split('.').pop().toUpperCase())\n  })\n}\nconst uploadImageList = computed(() => fileFilter(fileAllList.value, imageExtensions))\nconst uploadDocumentList = computed(() => fileFilter(fileAllList.value, documentExtensions))\nconst uploadVideoList = computed(() => fileFilter(fileAllList.value, videoExtensions))\nconst uploadAudioList = computed(() => fileFilter(fileAllList.value, audioExtensions))\nconst uploadOtherList = computed(() =>\n  fileFilter(\n    fileAllList.value,\n    otherExtensions.value.map((item) => item.toUpperCase()),\n  ),\n)\n\nconst showDelete = ref('')\n\nconst isDisabledChat = computed(\n  () =>\n    !(\n      (inputValue.value.trim() ||\n        uploadImageList.value.length > 0 ||\n        uploadDocumentList.value.length > 0 ||\n        uploadVideoList.value.length > 0 ||\n        uploadAudioList.value.length > 0 ||\n        uploadOtherList.value.length > 0) &&\n      (props.appId || props.applicationDetails?.name)\n    ),\n)\n\n// 是否显示移动端语音按钮\nconst isMicrophone = ref(false)\nconst switchMicrophone = (status: boolean) => {\n  if (status) {\n    // 如果显示就申请麦克风权限\n    recorderManage.open(() => {\n      isMicrophone.value = true\n    })\n  } else {\n    // 关闭麦克风\n    recorderManage.close()\n    isMicrophone.value = false\n  }\n}\n\nconst TouchEnd = (bool?: boolean) => {\n  if (bool) {\n    stopRecording()\n    recorderStatus.value = 'STOP'\n  } else {\n    stopTimer()\n    recorderStatus.value = 'STOP'\n  }\n}\n// 取消录音控制台日志\nRecorder.CLog = function () {}\n\nclass RecorderManage {\n  recorder?: any\n  uploadRecording: (blob: Blob, duration: number) => void\n\n  constructor(uploadRecording: (blob: Blob, duration: number) => void) {\n    this.uploadRecording = uploadRecording\n  }\n\n  open(callback?: () => void) {\n    const recorder = new Recorder({\n      type: 'mp3',\n      bitRate: 128,\n      sampleRate: 16000,\n    })\n    if (!this.recorder) {\n      recorder.open(() => {\n        this.recorder = recorder\n        if (callback) {\n          callback()\n        }\n      }, this.errorCallBack)\n    }\n  }\n\n  start() {\n    if (this.recorder) {\n      this.recorder.start()\n      recorderStatus.value = 'START'\n      handleTimeChange()\n    } else {\n      const recorder = new Recorder({\n        type: 'mp3',\n        bitRate: 128,\n        sampleRate: 16000,\n      })\n      recorder.open(() => {\n        this.recorder = recorder\n        recorder.start()\n        recorderStatus.value = 'START'\n        handleTimeChange()\n      }, this.errorCallBack)\n    }\n  }\n\n  stop() {\n    if (this.recorder) {\n      this.recorder.stop(\n        (blob: Blob, duration: number) => {\n          if (mode !== 'mobile') {\n            this.close()\n          }\n          this.uploadRecording(blob, duration)\n        },\n        (err: any) => {\n          MsgAlert(t('common.tip'), err, {\n            confirmButtonText: t('chat.tip.confirm'),\n            dangerouslyUseHTMLString: true,\n            customClass: 'record-tip-confirm',\n          })\n        },\n      )\n    }\n  }\n\n  close() {\n    if (this.recorder) {\n      this.recorder.close()\n      this.recorder = undefined\n    }\n  }\n\n  private errorCallBack(err: any, isUserNotAllow: boolean) {\n    if (isUserNotAllow) {\n      MsgAlert(t('common.tip'), err, {\n        confirmButtonText: t('chat.tip.confirm'),\n        dangerouslyUseHTMLString: true,\n        customClass: 'record-tip-confirm',\n      })\n    } else {\n      MsgAlert(\n        t('common.tip'),\n        `${err}\n        <div style=\"width: 100%;height:1px;border-top:1px var(--el-border-color) var(--el-border-style);margin:10px 0;\"></div>\n        ${t('chat.tip.recorderTip')}\n    <img src=\"${new URL(`/tipIMG.jpg`, import.meta.url).href}\" style=\"width: 100%;\" />`,\n        {\n          confirmButtonText: t('chat.tip.confirm'),\n          dangerouslyUseHTMLString: true,\n          customClass: 'record-tip-confirm',\n        },\n      )\n    }\n  }\n}\n\nconst getSpeechToTextAPI = () => {\n  if (props.type === 'ai-chat') {\n    return (id?: any, data?: any, loading?: Ref<boolean>) => {\n      return chatAPI.speechToText(data, loading)\n    }\n  } else {\n    return applicationApi.speechToText\n  }\n}\nconst speechToTextAPI = getSpeechToTextAPI()\n// 上传录音文件\nconst uploadRecording = async (audioBlob: Blob) => {\n  try {\n    // 非自动发送切换输入框\n    if (!props.applicationDetails.stt_autosend) {\n      switchMicrophone(false)\n    }\n    recorderStatus.value = 'TRANSCRIBING'\n    const formData = new FormData()\n    formData.append('file', audioBlob, 'recording.mp3')\n    if (props.applicationDetails.stt_autosend) {\n      bus.emit('on:transcribing', true)\n    }\n    speechToTextAPI(props.applicationDetails.id as string, formData, localLoading)\n      .then((response) => {\n        inputValue.value = typeof response.data === 'string' ? response.data : ''\n        // 自动发送\n        if (props.applicationDetails.stt_autosend) {\n          nextTick(() => {\n            autoSendMessage()\n          })\n        } else {\n          switchMicrophone(false)\n        }\n      })\n      .catch((error) => {\n        console.error(`${t('chat.uploadFile.errorMessage')}:`, error)\n      })\n      .finally(() => {\n        recorderStatus.value = 'STOP'\n        bus.emit('on:transcribing', false)\n      })\n  } catch (error) {\n    recorderStatus.value = 'STOP'\n    console.error(`${t('chat.uploadFile.errorMessage')}:`, error)\n  }\n}\nconst recorderManage = new RecorderManage(uploadRecording)\n// 开始录音\nconst startRecording = () => {\n  recorderManage.start()\n}\n\n// 停止录音\nconst stopRecording = () => {\n  recorderManage.stop()\n}\n\nconst handleTimeChange = () => {\n  recorderTime.value = 0\n  if (intervalId.value) {\n    return\n  }\n  intervalId.value = setInterval(() => {\n    if (recorderStatus.value === 'STOP') {\n      clearInterval(intervalId.value!)\n      intervalId.value = null\n      return\n    }\n\n    recorderTime.value++\n\n    if (recorderTime.value === 60) {\n      if (mode !== 'mobile') {\n        stopRecording()\n        clearInterval(intervalId.value!)\n        intervalId.value = null\n        recorderStatus.value = 'STOP'\n      }\n    }\n  }, 1000)\n}\n// 停止计时的函数\nconst stopTimer = () => {\n  if (intervalId.value !== null) {\n    clearInterval(intervalId.value)\n    recorderTime.value = 0\n    intervalId.value = null\n  }\n}\n\nconst getQuestion = () => {\n  if (!inputValue.value.trim()) {\n    const fileLength = [\n      uploadImageList.value.length > 0,\n      uploadDocumentList.value.length > 0,\n      uploadAudioList.value.length > 0,\n      uploadVideoList.value.length > 0,\n      uploadOtherList.value.length > 0,\n    ]\n    if (fileLength.filter((f) => f).length > 1) {\n      return t('chat.uploadFile.otherMessage')\n    } else if (fileLength[0]) {\n      return t('chat.uploadFile.imageMessage')\n    } else if (fileLength[1]) {\n      return t('chat.uploadFile.documentMessage')\n    } else if (fileLength[2]) {\n      return t('chat.uploadFile.audioMessage')\n    } else if (fileLength[3]) {\n      return t('chat.uploadFile.videoMessage')\n    } else if (fileLength[4]) {\n      return t('chat.uploadFile.otherMessage')\n    }\n  }\n\n  return inputValue.value.trim()\n}\n\nfunction autoSendMessage() {\n  props\n    .validate()\n    .then(() => {\n      props.sendMessage(getQuestion(), {\n        image_list: uploadImageList.value,\n        document_list: uploadDocumentList.value,\n        audio_list: uploadAudioList.value,\n        video_list: uploadVideoList.value,\n        other_list: uploadOtherList.value,\n      })\n      inputValue.value = ''\n      fileAllList.value = []\n      if (upload.value) {\n        upload.value.clearFiles()\n      }\n\n      if (quickInputRef.value) {\n        quickInputRef.value.textarea.style.height = '45px'\n      }\n    })\n    .catch(() => {\n      emit('update:showUserInput', true)\n    })\n}\n\nfunction sendChatHandle(event?: any) {\n  const isMobile = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\n    navigator.userAgent,\n  )\n  // 如果是移动端，且按下回车键，不直接发送\n  if ((isMobile || mode === 'mobile') && event?.key === 'Enter') {\n    // 阻止默认事件\n    return\n  }\n  if (!event?.ctrlKey && !event?.shiftKey && !event?.altKey && !event?.metaKey) {\n    // 如果没有按下组合键，则会阻止默认事件\n    event?.preventDefault()\n    if (!isDisabledChat.value && !props.loading && !event?.isComposing && !uploadLoading.value) {\n      if (inputValue.value.trim() || fileAllList.value.length > 0) {\n        autoSendMessage()\n      }\n    }\n  } else {\n    // 如果同时按下ctrl/shift/cmd/opt +enter，则会换行\n    insertNewlineAtCursor(event)\n  }\n}\n\nconst insertNewlineAtCursor = (event?: any) => {\n  const textarea = quickInputRef.value.$el.querySelector(\n    '.el-textarea__inner',\n  ) as HTMLTextAreaElement\n  const startPos = textarea.selectionStart\n  const endPos = textarea.selectionEnd\n  // 阻止默认行为（避免额外的换行符）\n  event.preventDefault()\n  // 在光标处插入换行符\n  inputValue.value = inputValue.value.slice(0, startPos) + '\\n' + inputValue.value.slice(endPos)\n  nextTick(() => {\n    textarea.setSelectionRange(startPos + 1, startPos + 1) // 光标定位到换行后位置\n  })\n}\n\nfunction deleteFile(item: any) {\n  fileAllList.value = fileAllList.value.filter((i) => i != item)\n}\n\nfunction mouseenter(row: any) {\n  showDelete.value = row.url\n}\n\nfunction mouseleave() {\n  showDelete.value = ''\n}\n\nfunction stopChat() {\n  bus.emit('chat:stop')\n}\n\nonMounted(() => {\n  bus.on('chat-input', (message: string) => {\n    inputValue.value = message\n  })\n  if (question) {\n    inputValue.value = decodeURIComponent(question.trim())\n    sendChatHandle()\n    setTimeout(() => {\n      // 获取当前路由信息\n      const route = router.currentRoute.value\n      // 复制query对象\n      const query = { ...route.query }\n      // 删除特定的参数\n      delete query.question\n      const newRoute =\n        Object.entries(query)?.length > 0\n          ? route.path +\n            '?' +\n            Object.entries(query)\n              .map(([key, value]) => `${key}=${value}`)\n              .join('&')\n          : route.path\n\n      history.pushState(null, '', '/chat' + newRoute)\n    }, 100)\n  }\n  setTimeout(() => {\n    nextTick(() => {\n      quickInputRef.value.textarea.style.height = '0'\n    })\n  }, 800)\n})\n\nconst mime_types = {\n  html: 'text/html',\n  htm: 'text/html',\n  shtml: 'text/html',\n  css: 'text/css',\n  xml: 'text/xml',\n  gif: 'image/gif',\n  jpeg: 'image/jpeg',\n  jpg: 'image/jpeg',\n  js: 'application/javascript',\n  atom: 'application/atom+xml',\n  rss: 'application/rss+xml',\n  mml: 'text/mathml',\n  txt: 'text/plain',\n  jad: 'text/vnd.sun.j2me.app-descriptor',\n  wml: 'text/vnd.wap.wml',\n  htc: 'text/x-component',\n  avif: 'image/avif',\n  png: 'image/png',\n  svg: 'image/svg+xml',\n  svgz: 'image/svg+xml',\n  tif: 'image/tiff',\n  tiff: 'image/tiff',\n  wbmp: 'image/vnd.wap.wbmp',\n  webp: 'image/webp',\n  ico: 'image/x-icon',\n  jng: 'image/x-jng',\n  bmp: 'image/x-ms-bmp',\n  woff: 'font/woff',\n  woff2: 'font/woff2',\n  jar: 'application/java-archive',\n  war: 'application/java-archive',\n  ear: 'application/java-archive',\n  json: 'application/json',\n  hqx: 'application/mac-binhex40',\n  doc: 'application/msword',\n  pdf: 'application/pdf',\n  ps: 'application/postscript',\n  docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n  xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n  pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n  eps: 'application/postscript',\n  ai: 'application/postscript',\n  rtf: 'application/rtf',\n  m3u8: 'application/vnd.apple.mpegurl',\n  kml: 'application/vnd.google-earth.kml+xml',\n  kmz: 'application/vnd.google-earth.kmz',\n  xls: 'application/vnd.ms-excel',\n  eot: 'application/vnd.ms-fontobject',\n  ppt: 'application/vnd.ms-powerpoint',\n  odg: 'application/vnd.oasis.opendocument.graphics',\n  odp: 'application/vnd.oasis.opendocument.presentation',\n  ods: 'application/vnd.oasis.opendocument.spreadsheet',\n  odt: 'application/vnd.oasis.opendocument.text',\n  wmlc: 'application/vnd.wap.wmlc',\n  wasm: 'application/wasm',\n  '7z': 'application/x-7z-compressed',\n  cco: 'application/x-cocoa',\n  jardiff: 'application/x-java-archive-diff',\n  jnlp: 'application/x-java-jnlp-file',\n  run: 'application/x-makeself',\n  pl: 'application/x-perl',\n  pm: 'application/x-perl',\n  prc: 'application/x-pilot',\n  pdb: 'application/x-pilot',\n  rar: 'application/x-rar-compressed',\n  rpm: 'application/x-redhat-package-manager',\n  sea: 'application/x-sea',\n  swf: 'application/x-shockwave-flash',\n  sit: 'application/x-stuffit',\n  tcl: 'application/x-tcl',\n  tk: 'application/x-tcl',\n  der: 'application/x-x509-ca-cert',\n  pem: 'application/x-x509-ca-cert',\n  crt: 'application/x-x509-ca-cert',\n  xpi: 'application/x-xpinstall',\n  xhtml: 'application/xhtml+xml',\n  xspf: 'application/xspf+xml',\n  zip: 'application/zip',\n  bin: 'application/octet-stream',\n  exe: 'application/octet-stream',\n  dll: 'application/octet-stream',\n  deb: 'application/octet-stream',\n  dmg: 'application/octet-stream',\n  iso: 'application/octet-stream',\n  img: 'application/octet-stream',\n  msi: 'application/octet-stream',\n  msp: 'application/octet-stream',\n  msm: 'application/octet-stream',\n  mid: 'audio/midi',\n  midi: 'audio/midi',\n  kar: 'audio/midi',\n  mp3: 'audio/mpeg',\n  ogg: 'audio/ogg',\n  m4a: 'audio/x-m4a',\n  ra: 'audio/x-realaudio',\n  '3gpp': 'video/3gpp',\n  '3gp': 'video/3gpp',\n  ts: 'video/mp2t',\n  mp4: 'video/mp4',\n  mpeg: 'video/mpeg',\n  mpg: 'video/mpeg',\n  mov: 'video/quicktime',\n  webm: 'video/webm',\n  flv: 'video/x-flv',\n  m4v: 'video/x-m4v',\n  mng: 'video/x-mng',\n  asx: 'video/x-ms-asf',\n  asf: 'video/x-ms-asf',\n  wmv: 'video/x-ms-wmv',\n  avi: 'video/x-msvideo',\n  wav: 'audio/wav',\n  flac: 'audio/flac',\n  aac: 'audio/aac',\n  opus: 'audio/opus',\n  csv: 'text/csv',\n  tsv: 'text/tab-separated-values',\n  ics: 'text/calendar',\n}\n\nfunction getExtensionsByMime(mime: string): string[] {\n  return Object.entries(mime_types)\n    .filter(([key, value]) => value === mime)\n    .map(([key]) => key)\n}\n\nconst fileUploadOptions = computed(() => [\n  {\n    label: t('common.fileUpload.image'),\n    value: 'image',\n    visible: props.applicationDetails.file_upload_setting.image,\n  },\n  {\n    label: t('common.fileUpload.document'),\n    value: 'document',\n    visible: props.applicationDetails.file_upload_setting.document,\n  },\n  {\n    label: t('common.fileUpload.video'),\n    value: 'video',\n    visible: props.applicationDetails.file_upload_setting.video,\n  },\n  {\n    label: t('common.fileUpload.audio'),\n    value: 'audio',\n    visible: props.applicationDetails.file_upload_setting.audio,\n  },\n  {\n    label: t('common.fileUpload.other'),\n    value: 'other',\n    visible: props.applicationDetails.file_upload_setting.other,\n  },\n])\n\nfunction openUrlSetting() {\n  showURLSetting.value = true\n  const visibleOptions = fileUploadOptions.value.filter((option) => option.visible)\n  if (visibleOptions.length > 0) {\n    urlForm.type = visibleOptions[0].value\n  }\n}\n\nasync function saveUrl() {\n  const urls = urlForm.source_url.split('\\n')\n  if (urls.length === 0) {\n    MsgWarning(t('chat.uploadFile.invalidUrl'))\n    return\n  }\n  const { maxFiles, fileLimit } = props.applicationDetails.file_upload_setting\n  const file_limit_once =\n    uploadImageList.value.length +\n    uploadDocumentList.value.length +\n    uploadAudioList.value.length +\n    uploadVideoList.value.length +\n    uploadOtherList.value.length\n  if (\n    file_limit_once >= maxFiles ||\n    urls.length + file_limit_once >= fileLimit ||\n    urls.length > fileLimit\n  ) {\n    MsgWarning(t('chat.uploadFile.limitMessage1') + maxFiles + t('chat.uploadFile.limitMessage2'))\n    return\n  }\n  // 允许的 MIME 类型\n  const allowedTypes: Record<string, string[]> = {\n    image: imageExtensions\n      .map((ext) => mime_types[ext.toLowerCase() as keyof typeof mime_types])\n      .filter(Boolean) as string[],\n    document: documentExtensions\n      .map((ext) => mime_types[ext.toLowerCase() as keyof typeof mime_types])\n      .filter(Boolean) as string[],\n    audio: audioExtensions\n      .map((ext) => mime_types[ext.toLowerCase() as keyof typeof mime_types])\n      .filter(Boolean) as string[],\n    video: videoExtensions\n      .map((ext) => mime_types[ext.toLowerCase() as keyof typeof mime_types])\n      .filter(Boolean) as string[],\n    other: otherExtensions.value\n      .map((ext) => mime_types[ext.toLowerCase() as keyof typeof mime_types])\n      .filter(Boolean) as string[],\n  }\n\n  // 校验 URL 是否有效\n  const validUrls = urls\n    .map((u) => u.trim())\n    .filter((u) => {\n      try {\n        new URL(u)\n        return u !== ''\n      } catch {\n        return false\n      }\n    })\n\n  if (validUrls.length === 0) {\n    MsgWarning(t('chat.uploadFile.invalidUrl'))\n    return\n  }\n\n  const type = urlForm.type\n  const expectedTypes = allowedTypes[type] || []\n  const validFiles: any[] = []\n\n  // 异步校验单个 URL\n  async function processUrl(url: string) {\n    try {\n      const appId = props.appId || props.applicationDetails?.id\n      const res =\n        props.type === 'debug-ai-chat'\n          ? await applicationApi.getFile(appId, { url })\n          : await chatAPI.getFile(appId, { url })\n\n      if (res.data['status_code'] !== 200) {\n        MsgWarning(url + ' ' + t('chat.uploadFile.invalidUrl'))\n        return\n      }\n\n      const contentType = res.data['Content-Type'] || ''\n      const contentLength = res.data['Content-Length']\n      const fileSize = contentLength ? parseInt(contentLength, 10) : 0\n\n      // 类型校验\n      if (expectedTypes.length > 0 && !expectedTypes.some((type) => contentType.includes(type))) {\n        MsgWarning(url + ' ' + t('chat.uploadFile.urlErrorMessage'))\n        return\n      }\n\n      if (fileSize > fileLimit * 1024 * 1024) {\n        MsgWarning(url + ' ' + t('chat.uploadFile.sizeLimit') + fileLimit + 'MB')\n        return\n      }\n\n      // 文件名处理\n      let fileName = url.substring(url.lastIndexOf('/') + 1)\n      if (!fileName) fileName = `file_${Date.now()}`\n      if (!fileName.includes('.') && getExtensionsByMime(contentType)) {\n        fileName += '.' + getExtensionsByMime(contentType)[0]\n      }\n\n      const fileItem = {\n        uid: `${Date.now()}_${Math.random()}`,\n        name: fileName,\n        url: url,\n        type: contentType,\n        size: fileSize,\n        status: 'success',\n      }\n\n      // 文档/音频类型需要下载后上传\n      if (type === 'document' || type === 'audio' || type === 'other') {\n        const base64Data = res.data.content\n        const byteString = atob(base64Data.split(',')[1] || base64Data)\n        const mimeString = base64Data.split(',')[0]?.split(':')[1]?.split(';')[0] || contentType\n        const ab = new ArrayBuffer(byteString.length)\n        const ia = new Uint8Array(ab)\n        for (let i = 0; i < byteString.length; i++) ia[i] = byteString.charCodeAt(i)\n\n        const fileBlob = new Blob([ab], { type: mimeString })\n        const fileObj = new File([fileBlob], fileName, { type: mimeString })\n\n        const uploadFileItem = {\n          uid: fileItem.uid,\n          name: fileName,\n          size: fileSize,\n          raw: fileObj,\n          status: 'ready',\n          percentage: 0,\n        }\n\n        await uploadFile(uploadFileItem, [uploadFileItem])\n      } else {\n        validFiles.push(reactive(fileItem))\n      }\n    } catch (e) {\n      console.error(e)\n      return\n    }\n  }\n\n  // 并行处理所有 URL\n  await Promise.all(validUrls.map((url) => processUrl(url)))\n\n  if (validFiles.length > 0) {\n    fileAllList.value.push(...validFiles)\n  }\n\n  showURLSetting.value = false\n  urlForm.source_url = ''\n  urlForm.type = ''\n}\n</script>\n<style lang=\"scss\" scoped>\n.ai-chat__operate {\n  position: relative;\n  width: 100%;\n  box-sizing: border-box;\n  z-index: 10;\n\n  :deep(.operate-textarea) {\n    box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n    background-color: #ffffff;\n    border-radius: 8px;\n    border: 1px solid #ffffff;\n    box-sizing: border-box;\n\n    &:has(.el-textarea__inner:focus) {\n      border: 1px solid var(--el-color-primary);\n    }\n\n    .el-textarea__inner {\n      border-radius: 8px !important;\n      box-shadow: none;\n      resize: none;\n      padding: 13px 16px;\n      box-sizing: border-box;\n      min-height: 47px !important;\n      height: 0;\n    }\n\n    .operate {\n      padding: 6px 10px;\n\n      .el-icon {\n        font-size: 20px;\n      }\n\n      .sent-button {\n        max-height: none;\n\n        .el-icon {\n          font-size: 24px;\n        }\n      }\n\n      .el-loading-spinner {\n        margin-top: -15px;\n\n        .circular {\n          width: 31px;\n          height: 31px;\n        }\n      }\n    }\n  }\n\n  .file-image {\n    position: relative;\n    overflow: inherit;\n\n    .delete-icon {\n      position: absolute;\n      right: -5px;\n      top: -5px;\n      z-index: 1;\n    }\n  }\n\n  .upload-tooltip-width {\n    width: 300px;\n  }\n}\n\n@media only screen and (max-width: 768px) {\n  .ai-chat__operate {\n    position: fixed;\n    bottom: 0;\n    font-size: 1rem;\n\n    .el-icon {\n      font-size: 1.4rem !important;\n    }\n  }\n  .popperURLSetting {\n    right: 30px;\n  }\n}\n\n.popperURLSetting {\n  position: absolute;\n  z-index: 999;\n  right: 60px;\n  bottom: 65px;\n  width: calc(100% - 50px);\n  max-width: 320px;\n\n  .url-upload-button {\n    border-color: var(--el-color-primary);\n    color: var(--el-color-primary);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/control/index.vue",
    "content": "<template>\n  <div>\n    <vue3-menus v-model:open=\"isOpen\" :event=\"eventVal\" :zIndex=\"9999\" :menus=\"menus\" hasIcon>\n      <template #icon=\"{ menu }\"\n        ><AppIcon v-if=\"menu.icon\" :iconName=\"menu.icon\"></AppIcon\n      ></template>\n      <template #label=\"{ menu }\"> {{ menu.label }}</template>\n    </vue3-menus>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { Vue3Menus } from 'vue3-menus'\nimport { MsgSuccess } from '@/utils/message'\nimport bus from '@/bus'\nimport { ref, nextTick, onMounted } from 'vue'\nimport { t } from '@/locales'\nconst isOpen = ref<boolean>(false)\nconst eventVal = ref<any>({})\n\nfunction getSelection() {\n  const selection = window.getSelection()\n  if (selection) {\n    if (selection.rangeCount === 0) return undefined\n    const range = selection.getRangeAt(0)\n    const fragment = range.cloneContents() // 克隆选区内容\n    const div = document.createElement('div')\n    div.appendChild(fragment)\n    if (div.textContent) {\n      return div.textContent.trim()\n    }\n  }\n  return undefined\n}\n\n/**\n * 打开控制台\n * @param event\n */\nconst openControl = (event: any) => {\n  const c = getSelection()\n  if (c) {\n    if (!isOpen.value) {\n      nextTick(() => {\n        eventVal.value = event\n        isOpen.value = true\n      })\n    } else {\n      clearSelectedText()\n      isOpen.value = false\n    }\n    event.preventDefault()\n  } else {\n    isOpen.value = false\n  }\n}\n\nconst menus = ref([\n  {\n    label: t('common.copy'),\n    icon: 'app-copy',\n    click: () => {\n      const selectionText = getSelection()\n      if (selectionText) {\n        clearSelectedText()\n        if (\n          typeof navigator.clipboard === 'undefined' ||\n          typeof navigator.clipboard.writeText === 'undefined'\n        ) {\n          const input = document.createElement('input')\n          input.setAttribute('value', selectionText)\n          document.body.appendChild(input)\n          input.select()\n          try {\n            if (document.execCommand('copy')) {\n              MsgSuccess(t('common.copySuccess'))\n            }\n          } finally {\n            document.body.removeChild(input)\n          }\n        } else {\n          navigator.clipboard.writeText(selectionText).then(() => {\n            MsgSuccess(t('common.copySuccess'))\n          })\n        }\n      }\n    },\n  },\n  {\n    label: t('chat.quote'),\n    icon: 'app-quote',\n    click: () => {\n      bus.emit('chat-input', getSelection())\n      clearSelectedText()\n    },\n  },\n])\n/**\n * 清除选中文本\n */\nconst clearSelectedText = () => {\n  if (window.getSelection) {\n    const selection = window.getSelection()\n    if (selection) {\n      selection.removeAllRanges()\n    }\n  }\n}\nonMounted(() => {\n  bus.on('open-control', openControl)\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/knowledge-source-component/ExecutionDetailContent.vue",
    "content": "<template>\n  <div class=\"execution-details\">\n    <div v-if=\"isWorkFlow(props.appType)\">\n      <template v-for=\"(item, index) in arraySort(props.detail ?? [], 'index')\" :key=\"index\">\n        <ExecutionDetailCard :data=\"item\"> </ExecutionDetailCard>\n      </template>\n    </div>\n\n    <template v-else>\n      <div class=\"card-never border-r-6 mb-12\">\n        <h5 class=\"p-8-12\">\n          {{ $t('chat.paragraphSource.question') }}\n        </h5>\n        <div class=\"p-8-12 border-t-dashed lighter\">\n          <span class=\"mb-8\">user: {{ problem }}</span>\n        </div>\n      </div>\n      <div v-if=\"paddedProblem\" class=\"card-never border-r-6 mb-12\">\n        <h5 class=\"p-8-12\">\n          {{ $t('chat.paragraphSource.questionPadded') }}\n        </h5>\n        <div class=\"p-8-12 border-t-dashed lighter\">\n          <span class=\"mb-8\">user: {{ paddedProblem }}</span>\n        </div>\n      </div>\n      <div v-if=\"system\" class=\"card-never border-r-6 mb-12\">\n        <h5 class=\"p-8-12\">\n          {{ $t('views.application.form.roleSettings.label') }}\n        </h5>\n        <div class=\"p-8-12 border-t-dashed lighter\">\n          <span class=\"mb-8\">{{ system }}</span>\n        </div>\n      </div>\n\n      <div class=\"card-never border-r-6 mb-12\">\n        <h5 class=\"p-8-12\">\n          {{ $t('chat.history') }}\n        </h5>\n        <div class=\"p-8-12 border-t-dashed lighter\">\n          <div v-for=\"(msg, index) in historyRecord\" :key=\"index\">\n            <span>{{ msg.role }}: </span>\n            <span>{{ msg.content }}</span>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"card-never border-r-6 mb-12\">\n        <h5 class=\"p-8-12\">\n          {{ $t('chat.executionDetails.currentChat') }}\n        </h5>\n        <div class=\"p-8-12 border-t-dashed lighter\">\n          <div class=\"mb-8\">{{ $t('chat.executionDetails.knowedMessage') }}:</div>\n          <div v-for=\"(msg, index) in currentChat\" :key=\"index\">\n            <span>{{ msg.content }}</span>\n          </div>\n        </div>\n      </div>\n\n      <div class=\"card-never border-r-6 mb-12\">\n        <h5 class=\"p-8-12\">\n          {{ $t('chat.executionDetails.answer') }}\n        </h5>\n        <div class=\"p-8-12 border-t-dashed lighter\">\n          <div v-for=\"(msg, index) in AiResponse\" :key=\"index\">\n            <MdRenderer v-if=\"msg.content\" :source=\"msg.content\" noImgZoomIn></MdRenderer>\n            <template v-else> -</template>\n          </div>\n        </div>\n      </div>\n      <div v-if=\"errStepMsg\" class=\"card-never border-r-6 mb-12\">\n        <h5 class=\"p-8-12\">\n          {{ $t('chat.executionDetails.errLog') }}\n        </h5>\n        <div class=\"p-8-12 border-t-dashed lighter\">\n          <div>\n            <span>{{ errStepMsg }}</span>\n          </div>\n        </div>\n      </div>\n    </template>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport ExecutionDetailCard from '@/components/execution-detail-card/index.vue'\nimport { arraySort } from '@/utils/array'\nimport { isWorkFlow } from '@/utils/application'\nimport MdRenderer from '@/components/markdown/MdRenderer.vue'\n\nconst props = defineProps<{\n  detail?: any[]\n  appType?: string\n}>()\n\nconst errStepMsg = computed(() => {\n  const err_step = props.detail?.find((item) => item.status === 500)\n  if (err_step) {\n    return `${err_step.step_type}: ${err_step.err_message}`\n  }\n  return undefined\n})\n\nconst messageList = computed(() => {\n  const chat_step = props.detail?.find((item) => item.step_type == 'chat_step')\n  if (chat_step) {\n    return chat_step.message_list\n  }\n  return []\n})\nconst get_padding_problem = () => {\n  return props.detail?.find((item) => item.step_type == 'problem_padding')\n}\n\nconst get_padded_problem = () => {\n  return props.detail?.find((item) => item.step_type == 'problem_padding')\n}\n\nconst paddedProblem = computed(() => {\n  const problem_padded = get_padded_problem()\n  if (problem_padded) {\n    return problem_padded.padding_problem_text\n  } else {\n    return ''\n  }\n})\n\nconst problem = computed(() => {\n  const problem_padding = get_padding_problem()\n  if (problem_padding) {\n    return problem_padding.problem_text\n  }\n  const user_list = messageList.value.filter((item: any) => item.role == 'user')\n  if (user_list.length > 0) {\n    return user_list[user_list.length - 1].content\n  } else {\n    return ''\n  }\n})\n\nconst system = computed(() => {\n  const user_list = messageList.value.filter((item: any) => item.role == 'system')\n  if (user_list.length > 0) {\n    return user_list[user_list.length - 1].content\n  } else {\n    return ''\n  }\n})\n\nconst historyRecord = computed<any>(() => {\n  const messages = messageList.value.filter((item: any) => item.role != 'system')\n  if (messages.length > 2) {\n    return messages.slice(0, messages.length - 2)\n  }\n  return []\n})\n\nconst currentChat = computed(() => {\n  const messages = messageList.value.filter((item: any) => item.role != 'system')\n  return messages.slice(messages.length - 2, messages.length - 1)\n})\n\nconst AiResponse = computed(() => {\n  const messages = messageList.value?.filter((item: any) => item.role != 'system')\n  return messages.slice(messages.length - 1, messages.length)\n})\n</script>\n<style lang=\"scss\" scoped>\n.execution-details {\n  :deep(.md-editor-previewOnly) {\n    background: none !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/knowledge-source-component/ParagraphCard.vue",
    "content": "<template>\n  <CardBox\n    shadow=\"never\"\n    :title=\"index + 1 + '.' + data.title || '-'\"\n    class=\"paragraph-source-card cursor mb-8 paragraph-source-card-height\"\n    :style=\"{ height: data?.document_name?.trim() ? '300px' : '260px' }\"\n    :class=\"data.is_active ? '' : 'disabled'\"\n    :showIcon=\"false\"\n  >\n    <template #tag>\n      <div class=\"color-primary\">\n        {{ score?.toFixed(3) || data.similarity?.toFixed(3) }}\n      </div>\n    </template>\n\n    <el-scrollbar height=\"150\">\n      <MdPreview ref=\"editorRef\" editorId=\"preview-only\" :modelValue=\"content\" noImgZoomIn />\n    </el-scrollbar>\n\n    <template #footer>\n      <slot name=\"footer\">\n        <el-card\n          shadow=\"never\"\n          style=\"--el-card-padding: 8px\"\n          class=\"w-full mb-12\"\n          v-if=\"data?.document_name?.trim()\"\n        >\n          <el-text class=\"flex align-center item\">\n            <img :src=\"getImgUrl(data?.document_name?.trim())\" alt=\"\" width=\"20\" class=\"mr-4\" />\n            <div class=\"ml-8\">\n              <div class=\"ml-4\" v-if=\"data?.meta?.source_file_id || data?.meta?.source_url\">\n                <a\n                  :href=\"getFileUrl(data?.meta?.source_file_id) || data?.meta?.source_url\"\n                  target=\"_blank\"\n                  class=\"ellipsis-1\"\n                  :title=\"data?.document_name?.trim()\"\n                >\n                  <span :title=\"data?.document_name?.trim()\">{{ data?.document_name }}</span>\n                </a>\n              </div>\n              <div v-else @click=\"infoMessage(data)\">\n                <span class=\"ellipsis-1 break-all\" :title=\"data?.document_name?.trim()\">\n                  {{ data?.document_name?.trim() }}\n                </span>\n              </div>\n            </div>\n          </el-text>\n        </el-card>\n        <div class=\"flex align-center border-t\" style=\"padding: 12px 0 8px\">\n          <KnowledgeIcon :type=\"data?.knowledge_type\" :size=\"18\" class=\"mr-8\" />\n          <span class=\"ellipsis-1 break-all\" :title=\"data?.knowledge_name\">\n            {{ data?.knowledge_name || '-' }}\n          </span>\n        </div>\n      </slot>\n    </template>\n  </CardBox>\n</template>\n<script setup lang=\"ts\">\nimport { getImgUrl, getFileUrl } from '@/utils/common'\nimport { computed } from 'vue'\nimport { MsgInfo } from '@/utils/message'\nimport { t } from '@/locales'\nconst props = defineProps({\n  data: {\n    type: Object,\n    default: () => {},\n  },\n  content: {\n    type: String,\n    default: '',\n  },\n  index: {\n    type: Number,\n    default: 0,\n  },\n  score: {\n    type: Number,\n    default: null,\n  },\n})\nconst isMetaObject = computed(() => typeof props.data.meta === 'object')\nconst parsedMeta = computed(() => {\n  try {\n    return JSON.parse(props.data.meta)\n  } catch (e) {\n    return {}\n  }\n})\n\nconst meta = computed(() => (isMetaObject.value ? props.data.meta : parsedMeta.value))\nfunction infoMessage(data: any) {\n  if (data?.meta?.allow_download === false) {\n    MsgInfo(t('chat.noPermissionDownload'))\n  } else {\n    MsgInfo(t('chat.noDocument'))\n  }\n}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/knowledge-source-component/ParagraphDocumentContent.vue",
    "content": "<template>\n  <div style=\"width: 100%; height: calc(100vh - 57px)\">\n    <embed v-if=\"is_pdf\" style=\"width: 100%; height: 100%\" :src=\"pdfSrc\" />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nconst props = defineProps<{\n  detail?: any\n}>()\nconst is_pdf = computed(() => {\n  return props.detail?.meta?.source_file_id\n})\nconst pdfSrc = computed(() => {\n  return `${window.MaxKB.prefix}/oss/file/${props.detail?.meta?.source_file_id}`\n})\n</script>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/knowledge-source-component/ParagraphSourceContent.vue",
    "content": "<template>\n  <div class=\"paragraph-source-height\">\n    <div v-if=\"props.detail?.paragraph_list.length > 0\" class=\"w-full\">\n      <template v-for=\"(item, index) in props.detail.paragraph_list\" :key=\"index\">\n        <ParagraphCard :data=\"item\" :content=\"item.content\" :index=\"index\" />\n      </template>\n    </div>\n    <span v-else> {{ $t('chat.KnowledgeSource.noSource') }}</span>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport ParagraphCard from '@/components/ai-chat/component/knowledge-source-component/ParagraphCard.vue'\n\nconst props = defineProps<{\n  detail?: any\n}>()\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/knowledge-source-component/index.vue",
    "content": "<template>\n  <div class=\"chat-knowledge-source\">\n    <div\n      class=\"flex align-center mt-16\"\n      v-if=\"type === 'log' || type === 'debug-ai-chat' ? true : application.show_source\"\n    >\n      <span class=\"mr-4 color-secondary\">{{ $t('chat.KnowledgeSource.title') }}</span>\n      <el-divider direction=\"vertical\" />\n      <el-button type=\"primary\" class=\"mr-8\" link @click=\"openParagraph(data)\">\n        <AppIcon iconName=\"app-reference-outlined\" class=\"mr-4\"></AppIcon>\n        {{ $t('chat.KnowledgeSource.referenceParagraph') }}\n        {{ data.paragraph_list?.length || 0 }}</el-button\n      >\n    </div>\n\n    <div\n      class=\"mt-8\"\n      v-if=\"type === 'log' || type === 'debug-ai-chat' ? true : application.show_source\"\n    >\n      <el-row :gutter=\"8\" v-if=\"uniqueParagraphList?.length\">\n        <template v-for=\"(item, index) in uniqueParagraphList\" :key=\"index\">\n          <el-col :span=\"12\" class=\"mb-8\">\n            <el-card shadow=\"never\" style=\"--el-card-padding: 8px\">\n              <div class=\"flex-between\">\n                <div class=\"flex align-center\">\n                  <img\n                    src=\"@/assets/fileType/web-link-icon.svg\"\n                    alt=\"\"\n                    width=\"24\"\n                    v-if=\"item?.meta?.source_file_id || item?.meta?.source_url\"\n                  />\n                  <img v-else :src=\"getImgUrl(item && item?.document_name)\" alt=\"\" width=\"24\" />\n                  <div\n                    class=\"ml-4 ellipsis-1\"\n                    :title=\"item?.document_name\"\n                    v-if=\"showPDF(item)\"\n                    @click=\"openParagraphDocument(item)\"\n                  >\n                    <p>{{ item && item?.document_name }}</p>\n                  </div>\n                  <div\n                    class=\"ml-4\"\n                    v-else-if=\"item?.meta?.source_file_id || item?.meta?.source_url\"\n                  >\n                    <a\n                      :href=\"getFileUrl(item?.meta?.source_file_id) || item?.meta?.source_url\"\n                      target=\"_blank\"\n                      class=\"ellipsis-1\"\n                      :title=\"item?.document_name?.trim()\"\n                    >\n                      <span :title=\"item?.document_name?.trim()\">{{ item?.document_name }}</span>\n                    </a>\n                  </div>\n                  <div v-else @click=\"infoMessage(item)\">\n                    <span class=\"ellipsis-1 break-all\" :title=\"item?.document_name?.trim()\">\n                      {{ item?.document_name?.trim() }}\n                    </span>\n                  </div>\n                </div>\n              </div>\n            </el-card>\n          </el-col>\n        </template>\n      </el-row>\n    </div>\n\n    <div\n      v-if=\"type === 'log' || type === 'debug-ai-chat' ? true : application.show_exec\"\n      class=\"execution-details border-t color-secondary flex-between mt-12\"\n      style=\"padding-top: 12px; padding-bottom: 8px\"\n    >\n      <div>\n        <span class=\"mr-8\">\n          {{ $t('chat.KnowledgeSource.consume') }}: {{ data?.message_tokens + data?.answer_tokens }}\n        </span>\n        <span>\n          {{ $t('chat.KnowledgeSource.consumeTime') }}: {{ data?.run_time?.toFixed(2) }} s</span\n        >\n      </div>\n      <el-button\n        type=\"primary\"\n        link\n        @click=\"openExecutionDetail(data.execution_details)\"\n        style=\"padding: 0\"\n      >\n        <el-icon class=\"mr-4\"><Document /></el-icon>\n        {{ $t('chat.executionDetails.title') }}</el-button\n      >\n    </div>\n    <!-- 知识库引用/执行详情 dialog -->\n    <el-dialog\n      class=\"scrollbar-dialog\"\n      :title=\"dialogTitle\"\n      v-model=\"dialogVisible\"\n      destroy-on-close\n      append-to-body\n      align-center\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n    >\n      <template #header=\"{ titleId, titleClass }\">\n        <div class=\"flex-between\">\n          <span\n            class=\"medium ellipsis\"\n            style=\"max-width: 300px\"\n            :title=\"dialogTitle\"\n            :id=\"titleId\"\n            :class=\"titleClass\"\n          >\n            {{ dialogTitle }}\n          </span>\n          <!-- <div class=\"flex align-center mr-8\" v-if=\"dialogType === 'pdfDocument'\">\n            <span class=\"mr-4\">\n              <el-button text>\n                <el-icon> <Download /> </el-icon>\n              </el-button>\n            </span>\n            <span>\n              <el-button text> <app-icon iconName=\"app-export\" size=\"20\" /></el-button>\n            </span>\n            <el-divider direction=\"vertical\" />\n          </div> -->\n        </div>\n      </template>\n\n      <el-scrollbar>\n        <div class=\"mb-8 p-8\" style=\"max-height: calc(100vh - 260px)\">\n          <component\n            :is=\"currentComponent\"\n            :detail=\"currentChatDetail\"\n            :appType=\"appType\"\n          ></component>\n        </div>\n      </el-scrollbar>\n    </el-dialog>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, shallowRef } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport ExecutionDetailContent from './ExecutionDetailContent.vue'\nimport ParagraphDocumentContent from './ParagraphDocumentContent.vue'\nimport ParagraphSourceContent from './ParagraphSourceContent.vue'\nimport { arraySort } from '@/utils/array'\nimport { getImgUrl, getFileUrl } from '@/utils/common'\nimport { t } from '@/locales'\nimport { MsgInfo } from '@/utils/message'\nconst props = defineProps({\n  data: {\n    type: Object,\n    default: () => {},\n  },\n  type: {\n    type: String,\n    default: '',\n  },\n  appType: {\n    type: String,\n    default: '',\n  },\n  executionIsRightPanel: {\n    type: Boolean,\n    required: false,\n  },\n  application: {\n    type: Object,\n    default: () => {},\n  },\n})\n\nconst emit = defineEmits(['openExecutionDetail', 'openParagraph', 'openParagraphDocument'])\nconst showPDF = (item: any) => {\n  return (\n    item.document_name.toLocaleLowerCase().endsWith('.pdf') &&\n    item.meta?.source_file_id &&\n    props.executionIsRightPanel\n  )\n}\nconst dialogVisible = ref(false)\nconst dialogTitle = ref('')\nconst currentComponent = shallowRef<any>(null)\nconst currentChatDetail = ref<any>(null)\nconst dialogType = ref('')\n\nfunction infoMessage(data: any) {\n  if (data?.meta?.allow_download === false) {\n    MsgInfo(t('chat.noPermissionDownload'))\n  } else {\n    MsgInfo(t('chat.noDocument'))\n  }\n}\nfunction openParagraph(row: any, id?: string) {\n  dialogTitle.value = t('chat.KnowledgeSource.title')\n  const obj = cloneDeep(row)\n  obj.paragraph_list = id\n    ? obj.paragraph_list.filter((v: any) => v.knowledge_id === id)\n    : obj.paragraph_list\n  obj.paragraph_list = arraySort(obj.paragraph_list, 'similarity', true)\n  if (props.executionIsRightPanel) {\n    emit('openParagraph')\n    return\n  }\n  dialogType.value = ''\n  currentComponent.value = ParagraphSourceContent\n  currentChatDetail.value = obj\n  dialogVisible.value = true\n}\nfunction openExecutionDetail(row: any) {\n  dialogTitle.value = t('chat.executionDetails.title')\n  if (props.executionIsRightPanel) {\n    emit('openExecutionDetail')\n    return\n  }\n  dialogType.value = ''\n  currentComponent.value = ExecutionDetailContent\n  currentChatDetail.value = row\n  dialogVisible.value = true\n}\nfunction openParagraphDocument(row: any) {\n  if (props.executionIsRightPanel) {\n    emit('openParagraphDocument', row)\n    return\n  }\n  dialogType.value = 'pdfDocument'\n  currentComponent.value = ParagraphDocumentContent\n  dialogTitle.value = row.document_name\n  currentChatDetail.value = row\n  dialogVisible.value = true\n}\n\nconst uniqueParagraphList = computed(() => {\n  const seen = new Set()\n  return (\n    props.data.paragraph_list?.filter((paragraph: any) => {\n      const key = paragraph.document_name.trim()\n      if (seen.has(key)) {\n        return false\n      }\n      seen.add(key)\n      // 判断如果 meta 属性不是 {} 需要json解析 转对象\n      if (paragraph.meta && typeof paragraph.meta === 'string') {\n        paragraph.meta = JSON.parse(paragraph.meta)\n        paragraph.source_url = paragraph.meta.source_url\n      }\n      return true\n    }) || []\n  )\n})\n</script>\n<style lang=\"scss\" scoped>\n@media only screen and (max-width: 420px) {\n  .chat-knowledge-source {\n    .execution-details {\n      display: block;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/operation-button/ChatOperationButton.vue",
    "content": "<template>\n  <div class=\"chat-operation-button flex-between\">\n    <el-text type=\"info\">\n      <span class=\"ml-4\" v-if=\"data.create_time\">{{ datetimeFormat(data.create_time) }}</span>\n    </el-text>\n\n    <div>\n      <!-- 语音播放 -->\n      <span v-if=\"tts\">\n        <el-tooltip\n          v-if=\"audioManage?.isPlaying()\"\n          effect=\"dark\"\n          :content=\"$t('chat.operation.pause')\"\n          placement=\"top\"\n        >\n          <el-button\n            type=\"primary\"\n            text\n            :disabled=\"!data?.write_ed\"\n            @click=\"audioManage?.pause(true)\"\n          >\n            <AppIcon class=\"color-secondary\" iconName=\"app-video-pause\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n        <el-tooltip effect=\"dark\" :content=\"$t('chat.operation.play')\" placement=\"top\" v-else>\n          <el-button\n            text\n            :disabled=\"!data?.write_ed\"\n            @click=\"\n              () => {\n                bus.emit('play:pause', props.data.record_id)\n                audioManage?.play(props.data.answer_text, true, true)\n              }\n            \"\n          >\n            <AppIcon class=\"color-secondary\" iconName=\"app-video-play\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </span>\n      <span v-if=\"type == 'ai-chat' || type == 'log'\">\n        <span class=\"ml-8\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.copy')\" placement=\"top\">\n            <el-button text @click=\"copy(data)\">\n              <AppIcon class=\"color-secondary\" iconName=\"app-copy\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <span class=\"ml-8\">\n          <el-tooltip effect=\"dark\" :content=\"$t('chat.operation.regeneration')\" placement=\"top\">\n            <el-button :disabled=\"chat_loading\" text @click=\"regeneration\">\n              <AppIcon iconName=\"app-refresh\" class=\"color-secondary\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <span class=\"ml-8\" v-if=\"buttonData?.vote_status === '-1' && mode === 'mobile'\">\n          <el-tooltip effect=\"dark\" :content=\"$t('chat.operation.like')\" placement=\"top\">\n            <el-button text :disabled=\"loading\" @click=\"mobileVoteReasonHandler('0')\">\n              <AppIcon class=\"color-secondary\" iconName=\"app-like\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n\n        <el-popover\n          ref=\"likePopoverRef\"\n          trigger=\"click\"\n          placement=\"bottom-start\"\n          :width=\"360\"\n          popper-class=\"vote-popover\"\n          :persistent=\"false\"\n          v-if=\"buttonData?.vote_status === '-1' && mode !== 'mobile'\"\n        >\n          <template #reference>\n            <span class=\"ml-8\">\n              <el-tooltip effect=\"dark\" :content=\"$t('chat.operation.like')\" placement=\"top\">\n                <el-button text :disabled=\"loading\">\n                  <AppIcon class=\"color-secondary\" iconName=\"app-like\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n            </span>\n          </template>\n          <VoteReasonContent\n            vote-type=\"0\"\n            :chat-id=\"props.chatId\"\n            :record-id=\"props.data.record_id\"\n            @success=\"handleVoteSuccess\"\n            @close=\"closePopover\"\n          >\n          </VoteReasonContent>\n        </el-popover>\n        <span v-if=\"buttonData?.vote_status === '0'\" class=\"ml-8\">\n          <el-tooltip effect=\"dark\" :content=\"$t('chat.operation.cancelLike')\" placement=\"top\">\n            <el-button text @click=\"cancelVoteHandle('-1')\" :disabled=\"loading\">\n              <AppIcon class=\"color-secondary\" iconName=\"app-like-color\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <span v-if=\"buttonData?.vote_status === '-1' && mode === 'mobile'\" class=\"ml-8\">\n          <el-tooltip effect=\"dark\" :content=\"$t('chat.operation.oppose')\" placement=\"top\">\n            <el-button text :disabled=\"loading\" @click=\"mobileVoteReasonHandler('1')\">\n              <AppIcon class=\"color-secondary\" iconName=\"app-oppose\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n\n        <el-popover\n          ref=\"opposePopoverRef\"\n          trigger=\"click\"\n          placement=\"bottom-start\"\n          :width=\"360\"\n          popper-class=\"vote-popover\"\n          :persistent=\"false\"\n          v-if=\"buttonData?.vote_status === '-1' && mode !== 'mobile'\"\n        >\n          <template #reference>\n            <span class=\"ml-8\">\n              <el-tooltip effect=\"dark\" :content=\"$t('chat.operation.oppose')\" placement=\"top\">\n                <el-button text :disabled=\"loading\">\n                  <AppIcon class=\"color-secondary\" iconName=\"app-oppose\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n            </span>\n          </template>\n          <VoteReasonContent\n            vote-type=\"1\"\n            :chat-id=\"props.chatId\"\n            :record-id=\"props.data.record_id\"\n            @success=\"handleVoteSuccess\"\n            @close=\"closePopover\"\n          >\n          </VoteReasonContent>\n        </el-popover>\n        <span class=\"ml-8\" v-if=\"buttonData?.vote_status === '1'\">\n          <el-tooltip effect=\"dark\" :content=\"$t('chat.operation.cancelOppose')\" placement=\"top\">\n            <el-button text @click=\"cancelVoteHandle('-1')\" :disabled=\"loading\">\n              <AppIcon class=\"color-secondary\" iconName=\"app-oppose-color\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <span class=\"ml-8\">\n          <el-tooltip effect=\"dark\" :content=\"$t('chat.share')\" placement=\"top\">\n            <el-button text @click.stop=\"clickShareHandle(props.data.record_id)\" :disabled=\"chat_loading\">\n              <AppIcon class=\"color-secondary\" iconName=\"app-share\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n      </span>\n      <div ref=\"audioCiontainer\"></div>\n    </div>\n    <MobileVoteReasonDrawer\n      ref=\"mobileVoteReasonDrawerRef\"\n      :chat-id=\"props.chatId\"\n      :record-id=\"props.data.record_id\"\n      @success=\"handleVoteSuccess\"\n    />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { nextTick, onMounted, ref, onBeforeUnmount, type Ref } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { copyClick } from '@/utils/clipboard'\nimport applicationApi from '@/api/application/application'\nimport chatAPI from '@/api/chat/chat'\nimport { datetimeFormat } from '@/utils/time'\nimport { MsgError } from '@/utils/message'\nimport VoteReasonContent from '@/components/ai-chat/component/operation-button/VoteReasonContent.vue'\nimport MobileVoteReasonDrawer from '@/components/ai-chat/component/operation-button/MobileVoteReasonDrawer.vue'\nimport bus from '@/bus'\n\nconst route = useRoute()\nconst {\n  params: { id },\n  query: { mode },\n} = route as any\n\nconst props = withDefaults(\n  defineProps<{\n    data: any\n    type: 'log' | 'ai-chat' | 'debug-ai-chat' | 'share'\n    chatId: string\n    chat_loading: boolean\n    applicationId: string\n    tts: boolean\n    tts_type: string\n    tts_autoplay: boolean\n  }>(),\n  {\n    data: () => ({}),\n    type: 'ai-chat',\n  },\n)\n\nconst emit = defineEmits(['update:data', 'regeneration', 'clickShare'])\n\nconst clickShareHandle = (id: string) => {\n  bus.emit('click:share', id)\n}\n\nconst copy = (data: any) => {\n  try {\n    const text = data.answer_text_list\n      .map((item: Array<any>) => item.map((i) => i.content).join('\\n'))\n      .join('\\n\\n')\n    copyClick(removeFormRander(text))\n  } catch (e: any) {\n    copyClick(removeFormRander(data?.answer_text.trim()))\n  }\n}\n\nconst likePopoverRef = ref()\nconst opposePopoverRef = ref()\nconst closePopover = () => {\n  likePopoverRef.value.hide()\n  opposePopoverRef.value.hide()\n}\nconst mobileVoteReasonDrawerRef = ref<InstanceType<typeof MobileVoteReasonDrawer> | null>(null)\nconst mobileVoteReasonHandler = (voteStatus: string) => {\n  if (mobileVoteReasonDrawerRef.value) {\n    mobileVoteReasonDrawerRef.value.open(voteStatus)\n  }\n}\n\nconst audioPlayer = ref<HTMLAudioElement[] | null>([])\nconst audioCiontainer = ref<HTMLDivElement>()\nconst buttonData = ref(props.data)\nconst loading = ref(false)\nconst audioList = ref<string[]>([])\n\nfunction regeneration() {\n  emit('regeneration')\n}\n\nfunction handleVoteSuccess(voteStatus: string) {\n  buttonData.value['vote_status'] = voteStatus\n  emit('update:data', buttonData.value)\n  if (mode !== 'mobile') {\n    closePopover()\n  }\n}\n\nfunction cancelVoteHandle(val: string) {\n  chatAPI.vote(props.chatId, props.data.record_id, val, undefined, '', loading).then(() => {\n    buttonData.value['vote_status'] = val\n    emit('update:data', buttonData.value)\n  })\n}\n\nfunction markdownToPlainText(md: string) {\n  return (\n    md\n      // 移除图片 ![alt](url)\n      .replace(/!\\[.*?\\]\\(.*?\\)/g, '')\n      // 移除链接 [text](url)\n      .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1')\n      // 移除 Markdown 标题符号 (#, ##, ###)\n      .replace(/^#{1,6}\\s+/gm, '')\n      // 移除加粗 **text** 或 __text__\n      .replace(/\\*\\*(.*?)\\*\\*/g, '$1')\n      .replace(/__(.*?)__/g, '$1')\n      // 移除斜体 *text* 或 _text_\n      .replace(/\\*(.*?)\\*/g, '$1')\n      .replace(/_(.*?)_/g, '$1')\n      // 移除行内代码 `code`\n      .replace(/`(.*?)`/g, '$1')\n      // 移除代码块 ```code```\n      .replace(/```[\\s\\S]*?```/g, '')\n      // 移除video标签\n      .replace(/<video>[\\s\\S]*?<\\/video>/g, '')\n      // 移除html标签\n      .replace(/<[^>]+>/g, '')\n      // 移除多余的换行符\n      .replace(/\\n{2,}/g, '\\n')\n      .trim()\n  )\n}\n\nfunction removeFormRander(text: string) {\n  return text.replace(/<form_rander>[\\s\\S]*?<\\/form_rander>/g, '').trim()\n}\nfunction getKey(keys: Array<number>, index: number) {\n  // 从后往前查找第一个小于等于index的键\n  for (let i = keys.length - 1; i >= 0; i--) {\n    if (keys[i] <= index) {\n      return keys[i]\n    }\n  }\n  return 0\n}\nfunction smartSplit(\n  str: string,\n  minLengthConfig: any = {\n    0: 10,\n    1: 25,\n    3: 50,\n    5: 100,\n  },\n  is_end = false,\n) {\n  // 匹配中文逗号/句号，且后面至少还有20个字符（含任何字符，包括换行）\n  const regex = /([。？\\n])|(<audio[^>]*><\\/audio>)/g\n  // 拆分并保留分隔符\n  const parts = str.split(regex)\n  const result = []\n  const keys = Object.keys(minLengthConfig).map(Number)\n  let minLength = minLengthConfig[0]\n  let temp_str = ''\n  for (let i = 0; i < parts.length; i++) {\n    const content = parts[i]\n    if (content == undefined) {\n      continue\n    }\n    if (/^<audio[^>]*><\\/audio>$/.test(content)) {\n      if (temp_str.length > 0) {\n        result.push(temp_str)\n        temp_str = ''\n      }\n      result.push(content)\n      continue\n    }\n    temp_str += parts[i]\n    if (temp_str.length > minLength && /[。？\\n]$/.test(temp_str)) {\n      minLength = minLengthConfig[getKey(keys, i)]\n      result.push(temp_str)\n      temp_str = ''\n    }\n  }\n  if (temp_str.length > 0 && is_end) {\n    result.push(temp_str)\n  }\n  return result\n}\n\nenum AudioStatus {\n  /**\n   * 结束\n   */\n  END = 'END',\n  /**\n   * 播放中\n   */\n  PLAY_INT = 'PLAY_INT',\n  /**\n   * 刚挂载\n   */\n  MOUNTED = 'MOUNTED',\n  /**\n   * 就绪\n   */\n  READY = 'READY',\n  /**\n   * 错误\n   */\n  ERROR = 'ERROR',\n}\nconst getTextToSpeechAPI = () => {\n  if (props.type === 'ai-chat') {\n    return (application_id?: string, data?: any, loading?: Ref<boolean>) => {\n      return chatAPI.textToSpeech(data, loading)\n    }\n  } else {\n    return applicationApi.postTextToSpeech\n  }\n}\nconst textToSpeechAPI = getTextToSpeechAPI()\nclass AudioManage {\n  textList: Array<string>\n  statusList: Array<AudioStatus>\n  audioList: Array<HTMLAudioElement | SpeechSynthesisUtterance>\n  tryList: Array<number>\n  ttsType: string\n  root: Element\n  is_end: boolean\n  constructor(ttsType: string, root: HTMLDivElement) {\n    this.textList = []\n    this.audioList = []\n    this.statusList = []\n    this.tryList = []\n    this.ttsType = ttsType\n    this.root = root\n    this.is_end = false\n  }\n  appendTextList(textList: Array<string>) {\n    const newTextList = textList.slice(this.textList.length)\n    // 没有新增段落\n    if (newTextList.length <= 0) {\n      return 0\n    }\n    newTextList.forEach((text, index) => {\n      this.textList.push(text)\n      this.statusList.push(AudioStatus.MOUNTED)\n      this.tryList.push(1)\n      index = this.textList.length - 1\n      if (this.ttsType === 'TTS') {\n        const audioElement: HTMLAudioElement = document.createElement('audio')\n        audioElement.controls = false\n        audioElement.hidden = true\n        /**\n         * 播放结束事件\n         */\n        audioElement.onended = () => {\n          this.statusList[index] = AudioStatus.END\n          // 如果所有的节点都播放结束\n          if (this.statusList.every((item) => item === AudioStatus.END) && this.is_end) {\n            this.statusList = this.statusList.map((item) => AudioStatus.READY)\n            this.is_end = false\n          } else {\n            // next\n            this.play()\n          }\n        }\n        this.root.appendChild(audioElement)\n        if (/^<audio[^>]*><\\/audio>$/.test(text)) {\n          audioElement.src = text.match(/src=\"([^\"]*)\"/)?.[1] || ''\n          this.statusList[index] = AudioStatus.READY\n        } else {\n          textToSpeechAPI(\n            (props.applicationId as string) || (id as string),\n            { text: text },\n            loading,\n          )\n            .then(async (res: any) => {\n              if (res.type === 'application/json') {\n                const text = await res.text()\n                if (this.tryList[index] >= 3) {\n                  MsgError(text)\n                }\n                this.statusList[index] = AudioStatus.ERROR\n                throw ''\n              }\n              // 假设我们有一个 MP3 文件的字节数组\n              // 创建 Blob 对象\n              const blob = new Blob([res], { type: 'audio/mp3' })\n              // 创建对象 URL\n              const url = URL.createObjectURL(blob)\n              audioElement.src = url\n              this.statusList[index] = AudioStatus.READY\n              this.play()\n            })\n            .catch((err) => {\n              this.statusList[index] = AudioStatus.ERROR\n              this.play()\n            })\n        }\n\n        this.audioList.push(audioElement)\n      } else {\n        const speechSynthesisUtterance: SpeechSynthesisUtterance = new SpeechSynthesisUtterance(\n          text,\n        )\n        speechSynthesisUtterance.onend = () => {\n          this.statusList[index] = AudioStatus.END\n          // 如果所有的节点都播放结束\n          if (this.statusList.every((item) => item === AudioStatus.END)) {\n            this.statusList = this.statusList.map((item) => AudioStatus.READY)\n          } else {\n            // next\n            this.play()\n          }\n        }\n        speechSynthesisUtterance.onerror = (e) => {\n          this.statusList[index] = AudioStatus.READY\n        }\n\n        this.statusList[index] = AudioStatus.READY\n        this.audioList.push(speechSynthesisUtterance)\n        this.play()\n      }\n    })\n  }\n  reTryError() {\n    this.statusList.forEach((status, index) => {\n      if (status === AudioStatus.ERROR && this.tryList[index] <= 3) {\n        this.tryList[index]++\n        const audioElement = this.audioList[index]\n        if (audioElement instanceof HTMLAudioElement) {\n          const text = this.textList[index]\n          this.statusList[index] = AudioStatus.MOUNTED\n          textToSpeechAPI(\n            (props.applicationId as string) || (id as string),\n            { text: text },\n            loading,\n          )\n            .then(async (res: any) => {\n              if (res.type === 'application/json') {\n                const text = await res.text()\n                if (this.tryList[index] >= 3) {\n                  MsgError(text)\n                }\n                throw ''\n              }\n              // 假设我们有一个 MP3 文件的字节数组\n              // 创建 Blob 对象\n              const blob = new Blob([res], { type: 'audio/mp3' })\n\n              // 创建对象 URL\n              const url = URL.createObjectURL(blob)\n              audioElement.src = url\n              this.statusList[index] = AudioStatus.READY\n              this.play()\n            })\n            .catch((err) => {\n              console.log('err: ', err)\n              this.statusList[index] = AudioStatus.ERROR\n              this.play()\n            })\n        }\n      }\n    })\n  }\n  isPlaying() {\n    return this.statusList.some((item) => [AudioStatus.PLAY_INT].includes(item))\n  }\n  play(text?: string, is_end?: boolean, self?: boolean) {\n    if (is_end) {\n      this.is_end = true\n    }\n    if (self) {\n      this.tryList = this.tryList.map((item) => 0)\n    }\n    if (text) {\n      const textList = this.getTextList(text, is_end ? true : false)\n      if (this.appendTextList(textList) !== 0) {\n        // 没有新增段落\n        return\n      }\n    }\n    // 如果存在在阅读的元素则直接返回\n    if (this.statusList.some((item) => [AudioStatus.PLAY_INT].includes(item))) {\n      return\n    }\n    this.reTryError()\n\n    // 需要播放的内容\n    const index = this.statusList.findIndex((status) =>\n      [AudioStatus.MOUNTED, AudioStatus.READY].includes(status),\n    )\n    if (index < 0 || this.statusList[index] === AudioStatus.MOUNTED) {\n      return\n    }\n\n    const audioElement = this.audioList[index]\n\n    if (audioElement instanceof HTMLAudioElement) {\n      // 标签朗读\n      try {\n        this.statusList[index] = AudioStatus.PLAY_INT\n        const play = audioElement.play()\n        if (play instanceof Promise) {\n          play.catch((e) => {\n            this.statusList[index] = AudioStatus.READY\n          })\n        }\n      } catch (e: any) {\n        this.statusList[index] = AudioStatus.ERROR\n      }\n    } else {\n      if (window.speechSynthesis.paused && self) {\n        window.speechSynthesis.resume()\n        this.statusList[index] = AudioStatus.PLAY_INT\n      } else {\n        // 如果不是暂停状态，取消当前播放并重新开始\n        if (window.speechSynthesis.speaking) {\n          window.speechSynthesis.cancel()\n        }\n        // 等待取消完成后重新播放\n        setTimeout(() => {\n          if (speechSynthesis.speaking) {\n            return\n          }\n          speechSynthesis.speak(audioElement)\n          this.statusList[index] = AudioStatus.PLAY_INT\n        }, 500)\n      }\n    }\n  }\n  pause(self?: boolean) {\n    const index = this.statusList.findIndex((status) => status === AudioStatus.PLAY_INT)\n    if (index < 0) {\n      return\n    }\n    const audioElement = this.audioList[index]\n\n    if (audioElement instanceof HTMLAudioElement) {\n      if (this.statusList[index] === AudioStatus.PLAY_INT) {\n        // 标签朗读\n        this.statusList[index] = AudioStatus.READY\n        audioElement.pause()\n      }\n    } else {\n      this.statusList[index] = AudioStatus.READY\n      if (self) {\n        window.speechSynthesis.pause()\n      } else {\n        window.speechSynthesis.cancel()\n      }\n    }\n  }\n  getTextList(text: string, is_end: boolean) {\n    // 移除表单渲染器\n    text = removeFormRander(text)\n    // text 处理成纯文本\n    text = markdownToPlainText(text)\n    const split = smartSplit(\n      text,\n      {\n        0: 20,\n        1: 50,\n        5: 100,\n      },\n      is_end,\n    )\n\n    return split\n  }\n}\nconst audioManage = ref<AudioManage>()\nonMounted(() => {\n  if (audioCiontainer.value) {\n    audioManage.value = new AudioManage(props.tts_type, audioCiontainer.value)\n  }\n  bus.on('play:pause', (record_id: string) => {\n    if (record_id !== props.data.record_id) {\n      if (audioManage.value) {\n        audioManage.value?.pause()\n      }\n    }\n  })\n\n  bus.on('change:answer', (data: any) => {\n    const record_id = data.record_id\n    bus.emit('play:pause', record_id)\n    if (props.data.record_id == record_id) {\n      if (props.tts && props.tts_autoplay) {\n        if (audioManage.value) {\n          audioManage.value.play(props.data.answer_text, data.is_end)\n        }\n      }\n    }\n  })\n})\nonBeforeUnmount(() => {\n  bus.off('change:answer')\n  bus.off('play:pause')\n  if (audioManage.value) {\n    audioManage.value.pause()\n  }\n  if (window.speechSynthesis) {\n    window.speechSynthesis.cancel()\n  }\n})\n</script>\n<style lang=\"scss\">\n@media only screen and (max-width: 430px) {\n  .chat-operation-button {\n    display: block;\n    text-align: right;\n  }\n}\n.vote-popover {\n  padding: 20px 24px !important;\n  color: var(--el-text-color-primary) !important;\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/operation-button/LogOperationButton.vue",
    "content": "<template>\n  <div>\n    <div class=\"flex-between mt-8\">\n      <div>\n        <el-text type=\"info\">\n          <span class=\"ml-4\">{{ datetimeFormat(data.create_time) }}</span>\n        </el-text>\n      </div>\n      <div>\n        <!-- 语音播放 -->\n        <span v-if=\"tts\">\n          <el-tooltip\n            effect=\"dark\"\n            :content=\"$t('chat.operation.play')\"\n            placement=\"top\"\n            v-if=\"!audioPlayerStatus\"\n          >\n            <el-button text @click=\"playAnswerText(data?.answer_text)\">\n              <AppIcon iconName=\"app-video-play\" class=\"color-secondary\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n          <el-tooltip v-else effect=\"dark\" :content=\"$t('chat.operation.pause')\" placement=\"top\">\n            <el-button type=\"primary\" text @click=\"pausePlayAnswerText()\">\n              <AppIcon iconName=\"app-video-pause\" class=\"color-secondary\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <span class=\"ml-8\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.copy')\" placement=\"top\">\n            <el-button text @click=\"copyClick(data?.answer_text)\">\n              <AppIcon iconName=\"app-copy\" class=\"color-secondary\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n\n        <template v-if=\"permissionPrecise.chat_log_add_knowledge(id)\">\n          <span class=\"ml-8\" v-if=\"buttonData.improve_paragraph_id_list.length === 0\">\n            <el-tooltip effect=\"dark\" :content=\"$t('views.chatLog.editContent')\" placement=\"top\">\n              <el-button text @click=\"editContent(data)\">\n                <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </span>\n          <span v-else class=\"ml-8\">\n            <el-tooltip effect=\"dark\" :content=\"$t('views.chatLog.editMark')\" placement=\"top\">\n              <el-button text @click=\"editMark(data)\">\n                <AppIcon iconName=\"app-document-active\" class=\"primary\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </span>\n        </template>\n        <span class=\"ml-8\" v-if=\"buttonData?.vote_status === '0'\">\n          <el-button text disabled>\n            <AppIcon iconName=\"app-like-color\"></AppIcon>\n          </el-button>\n        </span>\n        <span class=\"ml-8\" v-if=\"buttonData?.vote_status === '1'\">\n          <el-button text disabled>\n            <AppIcon iconName=\"app-oppose-color\"></AppIcon>\n          </el-button>\n        </span>\n        <EditContentDialog ref=\"EditContentDialogRef\" @refresh=\"refreshContent\" />\n        <EditMarkDialog ref=\"EditMarkDialogRef\" @refresh=\"refreshMark\" />\n        <!-- 先渲染，不然不能播放   -->\n        <audio\n          ref=\"audioPlayer\"\n          v-for=\"item in audioList\"\n          :key=\"item\"\n          controls\n          hidden=\"hidden\"\n        ></audio>\n      </div>\n    </div>\n\n    <el-card\n      class=\"mt-16 layout-bg\"\n      shadow=\"always\"\n      v-if=\"buttonData?.vote_status !== '-1' && data.vote_reason\"\n    >\n      <VoteReasonContent\n        v-if=\"buttonData?.id\"\n        :vote-type=\"buttonData?.vote_status\"\n        :chat-id=\"buttonData?.chat_id\"\n        :record-id=\"buttonData?.id\"\n        readonly\n        :default-reason=\"data.vote_reason\"\n        :default-other-content=\"data.vote_other_content\"\n      >\n      </VoteReasonContent>\n    </el-card>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\nimport { copyClick } from '@/utils/clipboard'\nimport EditContentDialog from '@/views/chat-log/component/EditContentDialog.vue'\nimport EditMarkDialog from '@/views/chat-log/component/EditMarkDialog.vue'\nimport { datetimeFormat } from '@/utils/time'\nimport applicationApi from '@/api/application/application'\nimport { useRoute } from 'vue-router'\nimport permissionMap from '@/permission'\nimport { MsgError } from '@/utils/message'\nimport { t } from '@/locales'\nimport VoteReasonContent from '@/components/ai-chat/component/operation-button/VoteReasonContent.vue'\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\n\nconst props = defineProps({\n  data: {\n    type: Object,\n    default: () => {},\n  },\n  applicationId: {\n    type: String,\n    default: '',\n  },\n  tts: Boolean,\n  tts_type: String,\n})\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['application'][apiType.value]\n})\n\nconst emit = defineEmits(['update:data'])\n\nconst audioPlayer = ref<HTMLAudioElement[] | null>(null)\n\nconst EditContentDialogRef = ref()\nconst EditMarkDialogRef = ref()\n\nconst buttonData = ref(props.data)\nconst loading = ref(false)\nconst utterance = ref<SpeechSynthesisUtterance | null>(null)\nconst audioList = ref<string[]>([])\nconst currentAudioIndex = ref(0)\n\nfunction editContent(data: any) {\n  EditContentDialogRef.value.open(data)\n}\n\nfunction editMark(data: any) {\n  EditMarkDialogRef.value.open(data)\n}\n\nconst audioPlayerStatus = ref(false)\n\nfunction markdownToPlainText(md: string) {\n  return (\n    md\n      // 移除图片 ![alt](url)\n      .replace(/!\\[.*?\\]\\(.*?\\)/g, '')\n      // 移除链接 [text](url)\n      .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1')\n      // 移除 Markdown 标题符号 (#, ##, ###)\n      .replace(/^#{1,6}\\s+/gm, '')\n      // 移除加粗 **text** 或 __text__\n      .replace(/\\*\\*(.*?)\\*\\*/g, '$1')\n      .replace(/__(.*?)__/g, '$1')\n      // 移除斜体 *text* 或 _text_\n      .replace(/\\*(.*?)\\*/g, '$1')\n      .replace(/_(.*?)_/g, '$1')\n      // 移除行内代码 `code`\n      .replace(/`(.*?)`/g, '$1')\n      // 移除代码块 ```code```\n      .replace(/```[\\s\\S]*?```/g, '')\n      // 移除多余的换行符\n      .replace(/\\n{2,}/g, '\\n')\n      .trim()\n  )\n}\n\nfunction removeFormRander(text: string) {\n  return text.replace(/<form_rander>[\\s\\S]*?<\\/form_rander>/g, '').trim()\n}\n\nconst playAnswerText = (text: string) => {\n  if (!text) {\n    text = t('chat.tip.answerMessage')\n  }\n  // 移除表单渲染器\n  text = removeFormRander(text)\n  // text 处理成纯文本\n  text = markdownToPlainText(text)\n  // console.log(text)\n  audioPlayerStatus.value = true\n  // 分割成多份\n  audioList.value = text.split(/(<audio[^>]*><\\/audio>)/)\n  playAnswerTextPart()\n}\n\nconst playAnswerTextPart = () => {\n  // console.log(audioList.value, currentAudioIndex.value)\n  if (currentAudioIndex.value === audioList.value.length) {\n    audioPlayerStatus.value = false\n    currentAudioIndex.value = 0\n    return\n  }\n  if (audioList.value[currentAudioIndex.value].includes('<audio')) {\n    if (audioPlayer.value) {\n      audioPlayer.value[currentAudioIndex.value].src =\n        audioList.value[currentAudioIndex.value].match(/src=\"([^\"]*)\"/)?.[1] || ''\n      audioPlayer.value[currentAudioIndex.value].play() // 自动播放音频\n      audioPlayer.value[currentAudioIndex.value].onended = () => {\n        currentAudioIndex.value += 1\n        playAnswerTextPart()\n      }\n    }\n  } else if (props.tts_type === 'BROWSER') {\n    if (audioList.value[currentAudioIndex.value] !== utterance.value?.text) {\n      window.speechSynthesis.cancel()\n    }\n    if (\n      window.speechSynthesis.paused &&\n      audioList.value[currentAudioIndex.value] === utterance.value?.text\n    ) {\n      window.speechSynthesis.resume()\n      return\n    }\n    // 创建一个新的 SpeechSynthesisUtterance 实例\n    utterance.value = new SpeechSynthesisUtterance(audioList.value[currentAudioIndex.value])\n    utterance.value.onend = () => {\n      utterance.value = null\n      currentAudioIndex.value += 1\n      playAnswerTextPart()\n    }\n    utterance.value.onerror = () => {\n      audioPlayerStatus.value = false\n      utterance.value = null\n    }\n    // 调用浏览器的朗读功能\n    window.speechSynthesis.speak(utterance.value)\n  } else if (props.tts_type === 'TTS') {\n    // 恢复上次暂停的播放\n    if (audioPlayer.value && audioPlayer.value[currentAudioIndex.value]?.src) {\n      audioPlayer.value[currentAudioIndex.value].play()\n      return\n    }\n    applicationApi\n      .postTextToSpeech(\n        (props.applicationId as string) || (id as string),\n        { text: audioList.value[currentAudioIndex.value] },\n        loading,\n      )\n      .then(async (res: any) => {\n        if (res.type === 'application/json') {\n          const text = await res.text()\n          MsgError(text)\n          return\n        }\n        // 假设我们有一个 MP3 文件的字节数组\n        // 创建 Blob 对象\n        const blob = new Blob([res], { type: 'audio/mp3' })\n\n        // 创建对象 URL\n        const url = URL.createObjectURL(blob)\n\n        // 测试blob是否能正常播放\n        // const link = document.createElement('a')\n        // link.href = window.URL.createObjectURL(blob)\n        // link.download = \"abc.mp3\"\n        // link.click()\n\n        // 检查 audioPlayer 是否已经引用了 DOM 元素\n        if (audioPlayer.value) {\n          audioPlayer.value[currentAudioIndex.value].src = url\n          audioPlayer.value[currentAudioIndex.value].play() // 自动播放音频\n          audioPlayer.value[currentAudioIndex.value].onended = () => {\n            currentAudioIndex.value += 1\n            playAnswerTextPart()\n          }\n        } else {\n          console.error('audioPlayer.value is not an instance of HTMLAudioElement')\n        }\n      })\n      .catch((err) => {\n        console.log('err: ', err)\n      })\n  }\n}\n\nconst pausePlayAnswerText = () => {\n  audioPlayerStatus.value = false\n  if (props.tts_type === 'TTS') {\n    if (audioPlayer.value) {\n      audioPlayer.value?.forEach((item) => {\n        item.pause()\n      })\n    }\n  }\n  if (props.tts_type === 'BROWSER') {\n    window.speechSynthesis.pause()\n  }\n}\n\nfunction refreshMark() {\n  buttonData.value.improve_paragraph_id_list = []\n  emit('update:data', buttonData.value)\n}\nfunction refreshContent(data: any) {\n  buttonData.value = data\n  emit('update:data', buttonData.value)\n}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/operation-button/MobileVoteReasonDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"visible\"\n    direction=\"btt\"\n    size=\"-\"\n    footer-class=\"mobile-vote-drawer-footer\"\n    :modal=\"true\"\n  >\n    <template #header>\n      <h4 class=\"text-center\">{{ title }}</h4>\n    </template>\n    <template #default>\n      <el-space wrap :size=\"12\">\n        <template v-for=\"reason in reasons\" :key=\"reason.value\">\n          <el-check-tag\n            type=\"primary\"\n            :checked=\"selectedReason === reason.value\"\n            @change=\"selectReason(reason.value)\"\n          >\n            {{ reason.label }}</el-check-tag\n          >\n        </template>\n      </el-space>\n\n      <div v-if=\"selectedReason === 'other'\" class=\"mt-16\">\n        <el-input\n          v-model=\"feedBack\"\n          type=\"textarea\"\n          :autosize=\"{ minRows: 4, maxRows: 20 }\"\n          :placeholder=\"$t('chat.vote.placeholder')\"\n        >\n        </el-input>\n      </div>\n    </template>\n    <template #footer>\n      <el-space fill wrap :fill-ratio=\"40\" style=\"width: 100%\">\n        <el-button @click=\"visible = false\" size=\"large\"> {{ $t('common.cancel') }}</el-button>\n        <el-button :disabled=\"isSubmitDisabled\" type=\"primary\" size=\"large\" @click=\"voteHandle()\">\n          {{ $t('common.submit') }}</el-button\n        >\n      </el-space>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { t } from '@/locales'\nimport chatAPI from '@/api/chat/chat'\n\nconst props = defineProps<{\n  chatId: string\n  recordId: string\n  defaultReason?: string\n  defaultOtherContent?: string\n}>()\n\nconst visible = ref(false)\nconst voteType = ref<string>('') // '0' like, '1' oppose\nconst selectedReason = ref<string>('')\nconst feedBack = ref<string>('')\nconst loading = ref(false)\n\nconst selectReason = (value: string) => {\n  selectedReason.value = value\n}\n\nconst isSubmitDisabled = computed(() => {\n  if (!selectedReason.value) {\n    return true\n  }\n  if (selectedReason.value === 'other' && !feedBack.value.trim()) {\n    return true\n  }\n  return false\n})\n\nconst LIKE_REASONS = [\n  { label: t('chat.vote.accurate'), value: 'accurate' },\n  { label: t('chat.vote.complete'), value: 'complete' },\n  { label: t('chat.vote.other'), value: 'other' },\n]\n\nconst OPPOSE_REASONS = [\n  { label: t('chat.vote.inaccurate'), value: 'inaccurate' },\n  { label: t('chat.vote.irrelevantAnswer'), value: 'incomplete' },\n  { label: t('chat.vote.other'), value: 'other' },\n]\n\nconst title = computed(() => {\n  return voteType.value === '0' ? t('chat.vote.likeTitle') : t('chat.vote.opposeTitle')\n})\n\nconst reasons = computed(() => {\n  return voteType.value === '0' ? LIKE_REASONS : OPPOSE_REASONS\n})\n\nfunction voteHandle() {\n  chatAPI\n    .vote(\n      props.chatId,\n      props.recordId,\n      voteType.value,\n      selectedReason.value,\n      feedBack.value,\n      loading,\n    )\n    .then(() => {\n      emit('success', voteType.value)\n      visible.value = false\n    })\n}\n\nconst emit = defineEmits<{\n  success: [voteStatus: string]\n}>()\n\nconst open = (voteStatus: string) => {\n  selectedReason.value = ''\n  feedBack.value = ''\n  voteType.value = voteStatus\n  visible.value = true\n}\n\ndefineExpose({ open })\n</script>\n\n<style lang=\"scss\">\n.mobile-vote-drawer-footer {\n  padding: 0 24px 32px 24px;\n  border: none !important;\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/operation-button/ShareOperationButton.vue",
    "content": "<template>\n  <div>\n    <div class=\"flex-between mt-8\">\n      <div>\n        <el-text type=\"info\">\n          <span class=\"ml-4\">{{ datetimeFormat(data.create_time) }}</span>\n        </el-text>\n      </div>\n      <div>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.copy')\" placement=\"top\">\n          <el-button text @click=\"copyClick(data?.answer_text)\">\n            <AppIcon iconName=\"app-copy\" class=\"color-secondary\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </div>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\nimport { copyClick } from '@/utils/clipboard'\n\nimport { datetimeFormat } from '@/utils/time'\n\nimport { useRoute } from 'vue-router'\n\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\n\nconst props = defineProps({\n  data: {\n    type: Object,\n    default: () => {},\n  },\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/operation-button/VoteReasonContent.vue",
    "content": "<template>\n  <div>\n    <h4>{{ title }}</h4>\n    <div class=\"mt-16\">\n      <el-space wrap :size=\"12\">\n        <template v-for=\"reason in reasons\" :key=\"reason.value\">\n          <el-check-tag\n            type=\"primary\"\n            :checked=\"selectedReason === reason.value\"\n            @change=\"selectReason(reason.value)\"\n          >\n            {{ reason.label }}</el-check-tag\n          >\n        </template>\n      </el-space>\n    </div>\n    <div v-if=\"selectedReason === 'other'\" class=\"mt-16\">\n      <el-input\n        v-model=\"feedBack\"\n        type=\"textarea\"\n        :autosize=\"{ minRows: 4, maxRows: 20 }\"\n        :placeholder=\"$t('chat.vote.placeholder')\"\n        :readonly=\"readonly\"\n      >\n      </el-input>\n    </div>\n    <div v-if=\"!readonly\" class=\"dialog-footer mt-24 text-right\">\n      <el-button @click=\"emit('close')\"> {{ $t('common.cancel') }}</el-button>\n      <el-button :disabled=\"isSubmitDisabled\" type=\"primary\" @click=\"voteHandle()\">\n        {{ $t('common.submit') }}</el-button\n      >\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { t } from '@/locales'\nimport chatAPI from '@/api/chat/chat'\n\nconst props = defineProps<{\n  voteType: '0' | '1'\n  chatId: string\n  recordId: string\n  readonly?: boolean\n  defaultReason?: string\n  defaultOtherContent?: string\n}>()\n\nconst selectedReason = ref<string>(props.readonly ? props.defaultReason || '' : '')\nconst feedBack = ref<string>(props.readonly ? props.defaultOtherContent || '' : '')\nconst loading = ref(false)\n\nconst selectReason = (value: string) => {\n  if (props.readonly) {\n    return\n  }\n  selectedReason.value = value\n}\n\nconst isSubmitDisabled = computed(() => {\n  if (!selectedReason.value) {\n    return true\n  }\n  if (selectedReason.value === 'other' && !feedBack.value.trim()) {\n    return true\n  }\n  return false\n})\n\nconst LIKE_REASONS = [\n  { label: t('chat.vote.accurate'), value: 'accurate' },\n  { label: t('chat.vote.complete'), value: 'complete' },\n  { label: t('chat.vote.other'), value: 'other' },\n]\n\nconst OPPOSE_REASONS = [\n  { label: t('chat.vote.inaccurate'), value: 'inaccurate' },\n  { label: t('chat.vote.irrelevantAnswer'), value: 'incomplete' },\n  { label: t('chat.vote.other'), value: 'other' },\n]\n\nconst title = computed(() => {\n  return props.voteType === '0' ? t('chat.vote.likeTitle') : t('chat.vote.opposeTitle')\n})\n\nconst reasons = computed(() => {\n  return props.voteType === '0' ? LIKE_REASONS : OPPOSE_REASONS\n})\n\nfunction voteHandle() {\n  chatAPI\n    .vote(\n      props.chatId,\n      props.recordId,\n      props.voteType,\n      selectedReason.value,\n      feedBack.value,\n      loading,\n    )\n    .then(() => {\n      emit('success', props.voteType)\n      emit('close')\n    })\n}\n\nconst emit = defineEmits<{\n  success: [voteStatus: string]\n  close: []\n}>()\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/operation-button/index.vue",
    "content": "<template>\n  <div class=\"operation-button-container\">\n    <ShareOperationButton v-if=\"type === 'share'\" v-bind:data=\"chatRecord\" />\n\n    <LogOperationButton\n      v-else-if=\"type === 'log'\"\n      v-bind:data=\"chatRecord\"\n      @update:data=\"(event: any) => emit('update:chatRecord', event)\"\n      :applicationId=\"application.id\"\n      :tts=\"application.tts_model_enable\"\n      :tts_type=\"application.tts_type\"\n      :type=\"type\"\n    />\n\n    <div class=\"mt-8\" v-else>\n      <el-button\n        type=\"primary\"\n        v-if=\"chatRecord.is_stop && !chatRecord.write_ed\"\n        @click=\"startChat(chatRecord)\"\n        link\n        >{{ $t('chat.operation.continue') }}\n      </el-button>\n      <!-- <el-button type=\"primary\" v-else-if=\"!chatRecord.write_ed\" @click=\"stopChat(chatRecord)\" link\n        >{{ $t('chat.operation.stopChat') }}\n      </el-button> -->\n    </div>\n\n    <ChatOperationButton\n      v-show=\"chatRecord.write_ed && 500 != chatRecord.status\"\n      :tts=\"application.tts_model_enable\"\n      :tts_type=\"application.tts_type\"\n      :tts_autoplay=\"application.tts_autoplay\"\n      :data=\"chatRecord\"\n      :type=\"type\"\n      :applicationId=\"application.id\"\n      :chatId=\"chatRecord.chat_id\"\n      :chat_loading=\"loading\"\n      @regeneration=\"regenerationChart(chatRecord)\"\n    />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport ChatOperationButton from '@/components/ai-chat/component/operation-button/ChatOperationButton.vue'\nimport LogOperationButton from '@/components/ai-chat/component/operation-button/LogOperationButton.vue'\nimport ShareOperationButton from '@/components/ai-chat/component/operation-button/ShareOperationButton.vue'\nimport { type chatType } from '@/api/type/application'\ndefineProps<{\n  type: 'log' | 'ai-chat' | 'debug-ai-chat' | 'share'\n  chatRecord: chatType\n  application: any\n  loading: boolean\n  startChat: (chat_record: any) => void\n  stopChat: (chat_record: any) => void\n  regenerationChart: (chat_record: any) => void\n}>()\nconst emit = defineEmits(['update:chatRecord'])\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/prologue-content/index.vue",
    "content": "<template>\n  <!-- 开场白组件 -->\n  <div class=\"item-content mb-16\">\n    <div class=\"avatar mr-8\" v-if=\"prologue && showAvatar\">\n      <img v-if=\"application.avatar\" :src=\"application.avatar\" height=\"28px\" width=\"28px\" />\n      <LogoIcon v-else height=\"28px\" width=\"28px\" />\n    </div>\n    <div\n      class=\"content\"\n      v-if=\"prologue\"\n      :style=\"{\n        'padding-right': showUserAvatar ? 'var(--padding-left)' : '0',\n      }\"\n    >\n      <el-card shadow=\"always\" class=\"border-r-8\" style=\"--el-card-padding: 10px 16px 12px\">\n        <MdRenderer\n          :source=\"prologue\"\n          :send-message=\"sendMessage\"\n          reasoning_content=\"\"\n          :type=\"type\"\n          :selection=\"selection\"\n        ></MdRenderer>\n      </el-card>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { type chatType } from '@/api/type/application'\nimport { computed } from 'vue'\nimport MdRenderer from '@/components/markdown/MdRenderer.vue'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nconst props = defineProps<{\n  application: any\n  available: boolean\n  type: 'log' | 'ai-chat' | 'debug-ai-chat' | 'share'\n  sendMessage: (question: string, other_params_data?: any, chat?: chatType) => void\n  selection?: boolean\n}>()\n\nconst showAvatar = computed(() => {\n  return props.application.show_avatar == undefined ? true : props.application.show_avatar\n})\nconst showUserAvatar = computed(() => {\n  return props.application.show_user_avatar == undefined ? true : props.application.show_user_avatar\n})\n\nconst toQuickQuestion = (match: string, offset: number, input: string) => {\n  return `<quick_question>${match.replace('- ', '')}</quick_question>`\n}\nconst prologue = computed(() => {\n  const temp = props.available ? props.application?.prologue : t('chat.tip.prologueMessage')\n  if (temp) {\n    const tag_list = [\n      /<html_rander>[\\d\\D]*?<\\/html_rander>/g,\n      /<echarts_rander>[\\d\\D]*?<\\/echarts_rander>/g,\n      /<quick_question>[\\d\\D]*?<\\/quick_question>/g,\n      /<form_rander>[\\d\\D]*?<\\/form_rander>/g,\n    ]\n    let _temp = temp\n    for (const index in tag_list) {\n      _temp = _temp.replaceAll(tag_list[index], '')\n    }\n    const quick_question_list = _temp.match(/-\\s.+/g)\n    let result = temp\n    for (const index in quick_question_list) {\n      const quick_question = quick_question_list[index]\n      result = result.replace(quick_question, toQuickQuestion)\n    }\n    return result\n  }\n  return ''\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/question-content/index.vue",
    "content": "<template>\n  <!-- 问题内容 -->\n  <div @mouseenter.stop=\"showIcon = true\" @mouseleave.stop=\"showIcon = false\">\n    <div class=\"question-content item-content lighter\">\n      <div v-if=\"!isReQuestion\" class=\"content p-12-16 border-r-8\" :class=\"getClassName\">\n        <div class=\"text break-all pre-wrap\">\n          <div class=\"mb-8\" v-if=\"document_list.length\">\n            <el-space wrap class=\"w-full media-file-width\">\n              <template v-for=\"(item, index) in document_list\" :key=\"index\">\n                <el-card shadow=\"never\" style=\"--el-card-padding: 8px\" class=\"download-file cursor\">\n                  <div class=\"download-button flex align-center\" @click=\"downloadFile(item)\">\n                    <el-icon class=\"mr-4\">\n                      <Download />\n                    </el-icon>\n                    {{ $t('chat.download') }}\n                  </div>\n                  <div class=\"show flex align-center\">\n                    <img :src=\"getImgUrl(item && item?.name)\" alt=\"\" width=\"24\" />\n                    <div class=\"ml-4 ellipsis-1\" :title=\"item && item?.name\">\n                      {{ item && item?.name }}\n                    </div>\n                  </div>\n                </el-card>\n              </template>\n            </el-space>\n          </div>\n          <div class=\"mb-8\" v-if=\"image_list.length\">\n            <el-space wrap>\n              <template v-for=\"(item, index) in image_list\" :key=\"index\">\n                <div class=\"file cursor border-r-6\" v-if=\"item.url\">\n                  <el-image\n                    :src=\"item.url\"\n                    :zoom-rate=\"1.2\"\n                    :max-scale=\"7\"\n                    :min-scale=\"0.2\"\n                    :preview-src-list=\"getAttrsArray(image_list, 'url')\"\n                    :initial-index=\"index\"\n                    alt=\"\"\n                    fit=\"cover\"\n                    style=\"width: 170px; height: 170px; display: block\"\n                    class=\"border-r-6\"\n                  />\n                </div>\n              </template>\n            </el-space>\n          </div>\n          <div class=\"mb-8\" v-if=\"audio_list.length\">\n            <el-space wrap>\n              <template v-for=\"(item, index) in audio_list\" :key=\"index\">\n                <div class=\"file cursor border-r-6\" v-if=\"item.url\">\n                  <audio\n                    :src=\"item.url\"\n                    controls\n                    style=\"width: 350px; height: 43px\"\n                    class=\"border-r-6\"\n                  />\n                </div>\n              </template>\n            </el-space>\n          </div>\n          <div class=\"mb-8\" v-if=\"video_list.length\">\n            <el-space wrap>\n              <template v-for=\"(item, index) in video_list\" :key=\"index\">\n                <div class=\"file cursor border-r-6\" v-if=\"item.url\">\n                  <video\n                    :src=\"item.url\"\n                    style=\"width: 170px; display: block\"\n                    class=\"border-r-6\"\n                    controls\n                    autoplay\n                  />\n                </div>\n              </template>\n            </el-space>\n          </div>\n          <div class=\"mb-8\" v-if=\"other_list.length\">\n            <el-space wrap class=\"w-full media-file-width\">\n              <template v-for=\"(item, index) in other_list\" :key=\"index\">\n                <el-card shadow=\"never\" style=\"--el-card-padding: 8px\" class=\"download-file cursor\">\n                  <div class=\"download-button flex align-center\" @click=\"downloadFile(item)\">\n                    <el-icon class=\"mr-4\">\n                      <Download />\n                    </el-icon>\n                    {{ $t('chat.download') }}\n                  </div>\n                  <div class=\"show flex align-center\">\n                    <img :src=\"getImgUrl(item && item?.name)\" alt=\"\" width=\"24\" />\n                    <div class=\"ml-4 ellipsis-1\" :title=\"item && item?.name\">\n                      {{ item && item?.name }}\n                    </div>\n                  </div>\n                </el-card>\n              </template>\n            </el-space>\n          </div>\n          <span> {{ chatRecord.problem_text }}</span>\n        </div>\n      </div>\n      <div class=\"question-content__operate\" v-else>\n        <div class=\"operate-textarea\">\n          <el-input\n            ref=\"quickInputRef\"\n            v-model=\"editText\"\n            :autosize=\"{ minRows: 1, maxRows: 10 }\"\n            type=\"textarea\"\n            :placeholder=\"$t('chat.inputPlaceholder.default')\"\n            :maxlength=\"100000\"\n            @keydown.enter=\"sendReQuestionMessage\"\n            class=\"chat-operate-textarea\"\n          />\n\n          <div class=\"operate text-right\">\n            <el-button link @click=\"cancelReQuestion\">\n              <el-icon class=\"color-secondary\"><Close /> </el-icon>\n            </el-button>\n\n            <el-divider direction=\"vertical\" />\n            <el-button\n              text\n              class=\"sent-button\"\n              :disabled=\"!editText.trim() || editText.trim() === chatRecord.problem_text.trim()\"\n              @click=\"sendReQuestionMessage\"\n            >\n              <img\n                v-show=\"!editText.trim() || editText.trim() === chatRecord.problem_text.trim()\"\n                src=\"@/assets/chat/icon_send.svg\"\n                alt=\"\"\n              />\n              <SendIcon\n                v-show=\"editText.trim() && editText.trim() !== chatRecord.problem_text.trim()\"\n              />\n            </el-button>\n          </div>\n        </div>\n      </div>\n      <!-- <el-input v-else v-model=\"editText\">\n        <template #append>\n          <div class=\"flex\" style=\"gap: 8px\">\n            <el-button-group class=\"flex ml-8 mr-8\">\n              <el-button class=\"flex mr-8\" text @click=\"cancelReQuestion\"\n                ><el-icon><Close /></el-icon\n              ></el-button>\n              <el-button\n                :disabled=\"!editText.trim() || editText.trim() === chatRecord.problem_text.trim()\"\n                text\n                @click=\"sendReQuestionMessage(chatRecord)\"\n              >\n                <el-icon><Comment /></el-icon>\n              </el-button>\n            </el-button-group>\n          </div>\n        </template>\n      </el-input> -->\n      <div class=\"avatar ml-8\" v-if=\"showAvatar\">\n        <el-image\n          v-if=\"application.user_avatar\"\n          :src=\"application.user_avatar\"\n          alt=\"\"\n          fit=\"cover\"\n          style=\"width: 28px; height: 28px; display: block\"\n        />\n        <el-avatar v-else :size=\"28\">\n          <img src=\"@/assets/user-icon.svg\" style=\"width: 50%\" alt=\"\" />\n        </el-avatar>\n      </div>\n    </div>\n    <div class=\"question-edit-button text-right mt-4\" v-if=\"!selection\">\n      <div v-if=\"!isReQuestion && showIcon && props.type === 'ai-chat'\">\n        <el-tooltip effect=\"dark\" :content=\"$t('common.edit')\" placement=\"top\" v-if=\"props.isLast\">\n          <el-button text @click.stop=\"handleEdit(chatRecord)\">\n            <AppIcon class=\"color-secondary\" iconName=\"app-edit\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.copy')\" placement=\"top\">\n          <el-button text @click.stop=\"copyClick(chatRecord?.problem_text)\">\n            <AppIcon class=\"color-secondary\" iconName=\"app-copy\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </div>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { type chatType } from '@/api/type/application'\nimport { getImgUrl, downloadByURL } from '@/utils/common'\nimport { useRoute, useRouter } from 'vue-router'\nimport { onMounted, computed, ref, nextTick } from 'vue'\nimport { getAttrsArray } from '@/utils/array'\nimport { copyClick } from '@/utils/clipboard'\nconst route = useRoute()\nconst {\n  query: { mode },\n} = route as any\nconst props = defineProps<{\n  application: any\n  chatRecord: chatType\n  chatManagement: any\n  sendMessage: (question: string, other_params_data?: any, chat?: chatType) => Promise<boolean>\n  type: 'log' | 'ai-chat' | 'debug-ai-chat' | 'share'\n  isLast: boolean\n  selection?: boolean\n}>()\n\nconst showIcon = ref<boolean>(false)\nconst isReQuestion = ref<boolean>(false)\nconst editText = ref<string>('')\nconst direction = ref<'horizontal' | 'vertical'>('horizontal')\n\nconst showAvatar = computed(() => {\n  return props.application.show_user_avatar == undefined ? true : props.application.show_user_avatar\n})\n\nconst document_list = computed(() => {\n  if (props.chatRecord?.upload_meta) {\n    return props.chatRecord.upload_meta?.document_list || []\n  }\n  const startNode = props.chatRecord.execution_details?.find(\n    (detail) => detail.type === 'start-node',\n  )\n  return startNode?.document_list || []\n})\nconst image_list = computed(() => {\n  if (props.chatRecord?.upload_meta) {\n    return props.chatRecord.upload_meta?.image_list || []\n  }\n  const startNode = props.chatRecord.execution_details?.find(\n    (detail) => detail.type === 'start-node',\n  )\n  return startNode?.image_list || []\n})\nconst video_list = computed(() => {\n  if (props.chatRecord?.upload_meta) {\n    return props.chatRecord.upload_meta?.video_list || []\n  }\n  const startNode = props.chatRecord.execution_details?.find(\n    (detail) => detail.type === 'start-node',\n  )\n  return startNode?.video_list || []\n})\nconst audio_list = computed(() => {\n  if (props.chatRecord?.upload_meta) {\n    return props.chatRecord.upload_meta?.audio_list || []\n  }\n  const startNode = props.chatRecord.execution_details?.find(\n    (detail) => detail.type === 'start-node',\n  )\n  return startNode?.audio_list || []\n})\nconst other_list = computed(() => {\n  if (props.chatRecord?.upload_meta) {\n    return props.chatRecord.upload_meta?.other_list || []\n  }\n  const startNode = props.chatRecord.execution_details?.find(\n    (detail) => detail.type === 'start-node',\n  )\n  return startNode?.other_list || []\n})\nconst getClassName = computed(() => {\n  return document_list.value.length >= 2 || other_list.value.length >= 2\n    ? 'media_2'\n    : document_list.value.length\n      ? `media_${document_list.value.length}`\n      : other_list.value.length\n        ? `media_${other_list.value.length}`\n        : `media_0`\n})\n\nfunction downloadFile(item: any) {\n  downloadByURL(item.url, item.name)\n}\n\nfunction handleEdit(chatRecord: any) {\n  isReQuestion.value = true\n  editText.value = chatRecord.problem_text\n}\n\nconst cancelReQuestion = () => {\n  isReQuestion.value = false\n}\n\nconst emit = defineEmits(['reQuestion'])\nconst quickInputRef = ref()\nfunction sendReQuestionMessage(event?: any) {\n  const isMobile = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(\n    navigator.userAgent,\n  )\n  // 如果是移动端，且按下回车键，不直接发送\n  if ((isMobile || mode === 'mobile') && event?.key === 'Enter') {\n    // 阻止默认事件\n    return\n  }\n  if (!event?.ctrlKey && !event?.shiftKey && !event?.altKey && !event?.metaKey) {\n    // 如果没有按下组合键，则会阻止默认事件\n    event?.preventDefault()\n    if (editText.value.trim() && editText.value.trim() !== props.chatRecord.problem_text.trim()) {\n      const container = props.chatRecord?.upload_meta\n        ? props.chatRecord.upload_meta\n        : props.chatRecord.execution_details?.find((detail) => detail.type === 'start-node')\n\n      props.chatRecord.problem_text = editText.value\n      reset_answer_text_list(props.chatRecord.answer_text_list)\n      props.chatRecord.write_ed = false\n\n      isReQuestion.value = false\n      props.sendMessage(\n        editText.value,\n        {\n          re_chat: true,\n          image_list: container?.image_list || [],\n          document_list: container?.document_list || [],\n          audio_list: container?.audio_list || [],\n          video_list: container?.video_list || [],\n          other_list: container?.other_list || [],\n          chat_record_id: props.chatRecord.record_id\n            ? props.chatRecord.record_id\n            : props.chatRecord.id,\n        },\n        props.chatRecord,\n      )\n    }\n  } else {\n    // 如果同时按下ctrl/shift/cmd/opt +enter，则会换行\n    insertNewlineAtCursor(event)\n  }\n}\n\nconst insertNewlineAtCursor = (event?: any) => {\n  const textarea = quickInputRef.value.$el.querySelector(\n    '.el-textarea__inner',\n  ) as HTMLTextAreaElement\n  const startPos = textarea.selectionStart\n  const endPos = textarea.selectionEnd\n  // 阻止默认行为（避免额外的换行符）\n  event.preventDefault()\n  // 在光标处插入换行符\n  editText.value =\n    editText.value.trim().slice(0, startPos) + '\\n' + editText.value.trim().slice(endPos)\n  nextTick(() => {\n    textarea.setSelectionRange(startPos + 1, startPos + 1) // 光标定位到换行后位置\n  })\n}\n\nconst reset_answer_text_list = (answer_text_list: any) => {\n  answer_text_list.splice(0, answer_text_list.length)\n  answer_text_list.push([])\n}\n\nonMounted(() => {})\n</script>\n<style lang=\"scss\" scoped>\n.question-content {\n  display: flex;\n  justify-content: flex-end;\n  padding-left: var(--padding-left);\n  width: 100%;\n  box-sizing: border-box;\n\n  .content {\n    background: #d6e2ff;\n    padding-left: 16px;\n    padding-right: 16px;\n  }\n\n  .download-file {\n    height: 43px;\n\n    &:hover {\n      color: var(--el-color-primary);\n      border: 1px solid var(--el-color-primary);\n\n      .download-button {\n        display: block;\n        text-align: center;\n        line-height: 26px;\n      }\n\n      .show {\n        display: none;\n      }\n    }\n\n    .download-button {\n      display: none;\n    }\n  }\n\n  .media-file-width {\n    :deep(.el-space__item) {\n      width: 49% !important;\n    }\n  }\n\n  .media_2 {\n    flex: 1;\n  }\n\n  .media_0 {\n    flex: inherit;\n  }\n\n  .media_1 {\n    width: 50%;\n  }\n}\n\n.question-edit-button {\n  height: 28px;\n}\n\n@media only screen and (max-width: 768px) {\n  .question-content {\n    .media-file-width {\n      :deep(.el-space__item) {\n        min-width: 100% !important;\n      }\n    }\n\n    .media_1 {\n      width: 100%;\n    }\n  }\n}\n\n.debug-ai-chat {\n  .question-content {\n    .media-file-width {\n      :deep(.el-space__item) {\n        min-width: 100% !important;\n      }\n    }\n\n    .media_1 {\n      width: 100%;\n    }\n  }\n}\n\n.question-content {\n  &__operate {\n    position: relative;\n    width: 100%;\n    box-sizing: border-box;\n    z-index: 10;\n\n    :deep(.operate-textarea) {\n      box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n      background-color: #ffffff;\n      border-radius: 8px;\n      border: 1px solid #ffffff;\n      box-sizing: border-box;\n\n      &:has(.el-textarea__inner:focus) {\n        border: 1px solid var(--el-color-primary);\n      }\n\n      .el-textarea__inner {\n        border-radius: 8px !important;\n        box-shadow: none;\n        resize: none;\n        padding: 13px 16px;\n        box-sizing: border-box;\n        min-height: 47px !important;\n        height: 0;\n      }\n\n      .operate {\n        padding: 6px 10px;\n\n        .el-icon {\n          font-size: 20px;\n        }\n\n        .sent-button {\n          max-height: none;\n\n          .el-icon {\n            font-size: 24px;\n          }\n        }\n\n        .el-loading-spinner {\n          margin-top: -15px;\n\n          .circular {\n            width: 31px;\n            height: 31px;\n          }\n        }\n      }\n    }\n\n    .file-image {\n      position: relative;\n      overflow: inherit;\n\n      .delete-icon {\n        position: absolute;\n        right: -5px;\n        top: -5px;\n        z-index: 1;\n      }\n    }\n\n    .upload-tooltip-width {\n      width: 300px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/transition-content/index.vue",
    "content": "<template>\n  <!-- 问题内容 -->\n  <div class=\"question-content item-content mb-16 lighter\">\n    <div class=\"content p-12-16 border-r-8\">\n      <span> {{ text }}</span\n      ><span class=\"dotting\"></span>\n    </div>\n    <div class=\"avatar ml-8\" v-if=\"application.show_user_avatar\">\n      <el-image\n        v-if=\"application.user_avatar\"\n        :src=\"application.user_avatar\"\n        alt=\"\"\n        fit=\"cover\"\n        style=\"width: 28px; height: 28px; display: block\"\n      />\n      <el-avatar v-else>\n        <img src=\"@/assets/user-icon.svg\" style=\"width: 50%\" alt=\"\" />\n      </el-avatar>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\ndefineProps<{\n  text: string\n  application: any\n  type: 'log' | 'ai-chat' | 'debug-ai-chat' | 'share'\n}>()\n</script>\n<style lang=\"scss\" scoped>\n.question-content {\n  display: flex;\n  justify-content: flex-end;\n  padding-left: var(--padding-left);\n  width: 100%;\n  box-sizing: border-box;\n\n  .content {\n    background: #d6e2ff;\n    padding-left: 16px;\n    padding-right: 16px;\n  }\n\n  .download-file {\n    height: 43px;\n\n    &:hover {\n      color: var(--el-color-primary);\n      border: 1px solid var(--el-color-primary);\n\n      .download-button {\n        display: block;\n        text-align: center;\n        line-height: 26px;\n      }\n\n      .show {\n        display: none;\n      }\n    }\n\n    .download-button {\n      display: none;\n    }\n  }\n  .media-file-width {\n    :deep(.el-space__item) {\n      min-width: 40% !important;\n      flex-grow: 1;\n    }\n  }\n  .media_2 {\n    flex: 1;\n  }\n  .media_0 {\n    flex: inherit;\n  }\n  .media_1 {\n    width: 50%;\n  }\n}\n@media only screen and (max-width: 768px) {\n  .question-content {\n    .media-file-width {\n      :deep(.el-space__item) {\n        min-width: 100% !important;\n      }\n    }\n    .media_1 {\n      width: 100%;\n    }\n  }\n}\n.debug-ai-chat {\n  .question-content {\n    .media-file-width {\n      :deep(.el-space__item) {\n        min-width: 100% !important;\n      }\n    }\n    .media_1 {\n      width: 100%;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/component/user-form/index.vue",
    "content": "<template>\n  <div\n    v-if=\"\n      (inputFieldList.length > 0 || (type === 'debug-ai-chat' && apiInputFieldList.length > 0)) &&\n      type !== 'log'\n    \"\n    class=\"user-form-container mb-16 w-full\"\n  >\n    <el-card shadow=\"always\" class=\"border-r-8\" style=\"--el-card-padding: 16px 8px\">\n      <div class=\"flex align-center cursor w-full\" style=\"padding: 0 8px\">\n        <span class=\"break-all ellipsis-1 mr-16\" :title=\"inputFieldConfig.title\">\n          {{ inputFieldConfig.title }}\n        </span>\n      </div>\n\n      <el-scrollbar :max-height=\"first ? '' : 450\">\n        <div class=\"mt-16\" style=\"padding: 0 8px; height: calc(100% - 100px)\">\n          <DynamicsForm\n            :key=\"dynamicsFormRefresh\"\n            v-model=\"form_data_context\"\n            :model=\"form_data_context\"\n            label-position=\"top\"\n            require-asterisk-position=\"right\"\n            :render_data=\"inputFieldList\"\n            ref=\"dynamicsFormRef\"\n          />\n          <DynamicsForm\n            v-if=\"type === 'debug-ai-chat'\"\n            v-model=\"api_form_data_context\"\n            :model=\"api_form_data_context\"\n            label-position=\"top\"\n            require-asterisk-position=\"right\"\n            :render_data=\"apiInputFieldList\"\n            ref=\"dynamicsFormRef2\"\n          />\n        </div>\n      </el-scrollbar>\n\n      <div class=\"text-left ml-8\">\n        <el-button type=\"primary\" class=\"w-full\" v-if=\"first\" @click=\"confirmHandle\">\n          <AppIcon iconName=\"app-chat\" class=\"mr-4\"></AppIcon>\n          {{ $t('chat.operation.startChat') }}</el-button\n        >\n        <el-button type=\"primary\" v-if=\"!first\" @click=\"confirmHandle\">{{\n          $t('common.confirm')\n        }}</el-button>\n        <el-button v-if=\"!first\" @click=\"cancelHandle\">{{ $t('common.cancel') }}</el-button>\n      </div>\n    </el-card>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, computed, onMounted } from 'vue'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { useRoute } from 'vue-router'\nimport { MsgWarning } from '@/utils/message'\nimport { t } from '@/locales'\nconst route = useRoute()\nconst {\n  params: { accessToken },\n} = route\nconst props = defineProps<{\n  application: any\n  type: 'log' | 'ai-chat' | 'debug-ai-chat' | 'share'\n  api_form_data: any\n  form_data: any\n  first?: boolean\n}>()\n// 用于刷新动态表单\nconst dynamicsFormRefresh = ref(0)\nconst inputFieldList = ref<FormField[]>([])\nconst apiInputFieldList = ref<FormField[]>([])\nconst inputFieldConfig = ref({ title: t('chat.userInput') })\nconst firstMounted = ref(false)\n\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst dynamicsFormRef2 = ref<InstanceType<typeof DynamicsForm>>()\n\nconst emit = defineEmits(['update:api_form_data', 'update:form_data', 'confirm', 'cancel'])\n\nconst api_form_data_context = computed({\n  get: () => {\n    return props.api_form_data\n  },\n  set: (data) => {\n    emit('update:api_form_data', data)\n  },\n})\n\nconst form_data_context = computed({\n  get: () => {\n    return props.form_data\n  },\n  set: (data) => {\n    emit('update:form_data', data)\n  },\n})\n\nwatch(\n  () => props.application,\n  (data) => {\n    handleInputFieldList()\n  },\n)\n\nfunction handleInputFieldList() {\n  dynamicsFormRefresh.value++\n  const default_value: any = {}\n  props.application?.work_flow?.nodes\n    ?.filter((v: any) => v.id === 'base-node')\n    .map((v: any) => {\n      inputFieldList.value = v.properties.user_input_field_list\n        ? v.properties.user_input_field_list.map((v: any) => {\n            switch (v.type) {\n              case 'input':\n                return {\n                  field: v.variable,\n                  input_type: 'TextInput',\n                  label: v.name,\n                  default_value: default_value[v.variable],\n                  required: v.is_required,\n                }\n              case 'select':\n                return {\n                  field: v.variable,\n                  input_type: 'SingleSelect',\n                  label: v.name,\n                  default_value: default_value[v.variable],\n                  required: v.is_required,\n                  option_list: v.optionList.map((o: any) => {\n                    return { key: o, value: o }\n                  }),\n                }\n              case 'date':\n                return {\n                  field: v.variable,\n                  input_type: 'DatePicker',\n                  label: v.name,\n                  default_value: default_value[v.variable],\n                  required: v.is_required,\n                  attrs: {\n                    format: 'YYYY-MM-DD HH:mm:ss',\n                    'value-format': 'YYYY-MM-DD HH:mm:ss',\n                    type: 'datetime',\n                  },\n                }\n              default:\n                return v\n            }\n          })\n        : v.properties.input_field_list\n          ? v.properties.input_field_list\n              .filter((v: any) => v.assignment_method === 'user_input')\n              .map((v: any) => {\n                switch (v.type) {\n                  case 'input':\n                    return {\n                      field: v.variable,\n                      input_type: 'TextInput',\n                      label: v.name,\n                      default_value: default_value[v.variable],\n                      required: v.is_required,\n                    }\n                  case 'select':\n                    return {\n                      field: v.variable,\n                      input_type: 'SingleSelect',\n                      label: v.name,\n                      default_value: default_value[v.variable],\n                      required: v.is_required,\n                      option_list: v.optionList.map((o: any) => {\n                        return { key: o, value: o }\n                      }),\n                    }\n                  case 'date':\n                    return {\n                      field: v.variable,\n                      input_type: 'DatePicker',\n                      label: v.name,\n                      default_value: default_value[v.variable],\n                      required: v.is_required,\n                      attrs: {\n                        format: 'YYYY-MM-DD HH:mm:ss',\n                        'value-format': 'YYYY-MM-DD HH:mm:ss',\n                        type: 'datetime',\n                      },\n                    }\n                  default:\n                    break\n                }\n              })\n          : []\n\n      apiInputFieldList.value = v.properties.api_input_field_list\n        ? v.properties.api_input_field_list.map((v: any) => {\n            switch (v.type) {\n              case 'input':\n                return {\n                  field: v.variable,\n                  input_type: 'TextInput',\n                  label: v.variable,\n                  default_value: v.default_value || default_value[v.variable],\n                  required: v.is_required,\n                }\n              case 'select':\n                return {\n                  field: v.variable,\n                  input_type: 'SingleSelect',\n                  label: v.variable,\n                  default_value: v.default_value || default_value[v.variable],\n                  required: v.is_required,\n                  option_list: v.optionList.map((o: any) => {\n                    return { key: o, value: o }\n                  }),\n                }\n              case 'date':\n                return {\n                  field: v.variable,\n                  input_type: 'DatePicker',\n                  label: v.variable,\n                  default_value: v.default_value || default_value[v.variable],\n                  required: v.is_required,\n                  attrs: {\n                    format: 'YYYY-MM-DD HH:mm:ss',\n                    'value-format': 'YYYY-MM-DD HH:mm:ss',\n                    type: 'datetime',\n                  },\n                }\n              default:\n                break\n            }\n          })\n        : v.properties.input_field_list\n          ? v.properties.input_field_list\n              .filter((v: any) => v.assignment_method === 'api_input')\n              .map((v: any) => {\n                switch (v.type) {\n                  case 'input':\n                    return {\n                      field: v.variable,\n                      input_type: 'TextInput',\n                      label: v.name,\n                      default_value: default_value[v.variable],\n                      required: v.is_required,\n                    }\n                  case 'select':\n                    return {\n                      field: v.variable,\n                      input_type: 'SingleSelect',\n                      label: v.name,\n                      default_value: default_value[v.variable],\n                      required: v.is_required,\n                      option_list: v.optionList.map((o: any) => {\n                        return { key: o, value: o }\n                      }),\n                    }\n                  case 'date':\n                    return {\n                      field: v.variable,\n                      input_type: 'DatePicker',\n                      label: v.name,\n                      default_value: default_value[v.variable],\n                      required: v.is_required,\n                      attrs: {\n                        format: 'YYYY-MM-DD HH:mm:ss',\n                        'value-format': 'YYYY-MM-DD HH:mm:ss',\n                        type: 'datetime',\n                      },\n                    }\n                  default:\n                    break\n                }\n              })\n          : []\n\n      //\n      inputFieldConfig.value = v.properties.user_input_config?.title\n        ? v.properties.user_input_config\n        : { title: t('chat.userInput') }\n    })\n}\nconst getRouteQueryValue = (field: string) => {\n  let _value = route.query[field]\n  if (_value != null) {\n    if (_value instanceof Array) {\n      _value = _value\n        .map((item) => {\n          if (item != null) {\n            return decodeQuery(item)\n          }\n          return null\n        })\n        .filter((item) => item != null)\n    } else {\n      _value = decodeQuery(_value)\n    }\n    return _value\n  }\n  return null\n}\nconst validate = () => {\n  const promise_list = []\n  if (dynamicsFormRef.value) {\n    promise_list.push(dynamicsFormRef.value?.validate())\n  }\n  if (dynamicsFormRef2.value) {\n    promise_list.push(dynamicsFormRef2.value?.validate())\n  }\n  promise_list.push(validate_query())\n  return Promise.all(promise_list)\n}\nconst validate_query = () => {\n  // 浏览器query参数找到接口传参\n  const msg = []\n  for (const f of apiInputFieldList.value) {\n    if (f.required && !api_form_data_context.value[f.field]) {\n      msg.push(f.field)\n    }\n  }\n  if (msg.length > 0) {\n    MsgWarning(\n      `${t('chat.tip.inputParamMessage1')} ${msg.join('、')}${t('chat.tip.inputParamMessage2')}`,\n    )\n    return Promise.reject(false)\n  }\n  return Promise.resolve(false)\n}\n\nconst initRouteQueryValue = () => {\n  for (const f of apiInputFieldList.value) {\n    if (!api_form_data_context.value[f.field]) {\n      const _value = getRouteQueryValue(f.field)\n      if (_value != null) {\n        api_form_data_context.value[f.field] = _value\n      }\n    }\n  }\n  if (!api_form_data_context.value['asker']) {\n    const asker = getRouteQueryValue('asker')\n    if (asker) {\n      api_form_data_context.value['asker'] = getRouteQueryValue('asker')\n    }\n  }\n}\n\nconst decodeQuery = (query: string) => {\n  try {\n    return decodeURIComponent(query)\n  } catch (e) {\n    return query\n  }\n}\nconst confirmHandle = () => {\n  validate().then((ok) => {\n    localStorage.setItem(`${accessToken}userForm`, JSON.stringify(form_data_context.value))\n    emit('confirm')\n  })\n}\nconst cancelHandle = () => {\n  emit('cancel')\n}\nconst render = (data: any) => {\n  if (dynamicsFormRef.value) {\n    dynamicsFormRef.value?.render(inputFieldList.value, data)\n  }\n}\n\nconst renderDebugAiChat = (data: any) => {\n  if (dynamicsFormRef2.value) {\n    dynamicsFormRef2.value?.render(apiInputFieldList.value, data)\n  }\n}\ndefineExpose({ validate, render, renderDebugAiChat })\nonMounted(() => {\n  firstMounted.value = true\n  handleInputFieldList()\n  initRouteQueryValue()\n})\n</script>\n<style lang=\"scss\" scoped>\n.user-form-container {\n  padding: 0 24px;\n}\n@media only screen and (max-width: 768px) {\n  .user-form-container {\n    max-width: 100%;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/ai-chat/index.scss",
    "content": ".ai-chat {\n  --padding-left: 36px;\n  height: 100%;\n  display: flex;\n  flex-direction: column;\n  box-sizing: border-box;\n  position: relative;\n  color: var(--app-text-color);\n  box-sizing: border-box;\n\n  .back-bottom-button {\n    position: absolute;\n    right: 14px;\n    top: -30px;\n    z-index: 22;\n  }\n  &__content {\n    padding-top: 0;\n    box-sizing: border-box;\n\n    .avatar {\n      float: left;\n    }\n\n    .content {\n      :deep(ol) {\n        margin-left: 16px !important;\n      }\n    }\n  }\n  .video-stop-button {\n    box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n\n    &:hover {\n      background: #ffffff;\n    }\n  }\n  .el-checkbox-group {\n    font-size: inherit;\n    line-height: inherit;\n  }\n  .is-selected {\n    background: rgba(var(--el-text-color-primary-rgb), 0.08);\n  }\n  .mul-operation {\n    position: fixed;\n    bottom: 0;\n    right: 0;\n    padding: 16px 24px;\n    box-sizing: border-box;\n    background: #ffffff;\n    z-index: 22;\n    box-shadow: 0px -2px 4px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n  }\n}\n\n.chat-width {\n  max-width: 80%;\n  margin: 0 auto;\n}\n@media only screen and (max-width: 1000px) {\n  .chat-width {\n    max-width: 100% !important;\n    margin: 0 auto;\n  }\n}\n\n@media only screen and (max-width: 768px) {\n  .ai-chat {\n    height: calc(100% - 116px) !important;\n  }\n}\n.chat-mobile {\n  .el-button.is-text:not(.is-disabled):hover {\n    background: none;\n  }\n}\n.chat-pc {\n  &.openLeft {\n    .mul-operation {\n      margin-left: 281px;\n      width: calc(100% - 281px);\n    }\n  }\n  &.hideLeft {\n    .mul-operation {\n      margin-left: 65px;\n      width: calc(100% - 65px);\n    }\n  }\n}\n"
  },
  {
    "path": "ui/src/components/ai-chat/index.vue",
    "content": "<template>\n  <div\n    ref=\"aiChatRef\"\n    class=\"ai-chat\"\n    :class=\"type\"\n    :style=\"{\n      height: firsUserInput ? '100%' : undefined,\n    }\"\n  >\n    <div\n      v-show=\"showUserInputContent\"\n      :class=\"firsUserInput ? 'firstUserInput' : 'popperUserInput'\"\n    >\n      <UserForm\n        v-model:api_form_data=\"api_form_data\"\n        v-model:form_data=\"form_data\"\n        :application=\"applicationDetails\"\n        :type=\"type\"\n        :first=\"firsUserInput\"\n        @confirm=\"UserFormConfirm\"\n        @cancel=\"UserFormCancel\"\n        ref=\"userFormRef\"\n      >\n      </UserForm>\n    </div>\n    <template v-if=\"!(isUserInput || isAPIInput) || !firsUserInput || type === 'log'\">\n      <el-scrollbar ref=\"scrollDiv\" @scroll=\"handleScrollTop\">\n        <div\n          ref=\"dialogScrollbar\"\n          class=\"ai-chat__content p-16\"\n          id=\"chatListId\"\n          :style=\"{ marginBottom: selection ? '65px' : '' }\"\n        >\n          <PrologueContent\n            :type=\"type\"\n            :application=\"applicationDetails\"\n            :available=\"available\"\n            :send-message=\"sendMessage\"\n            v-if=\"!selection\"\n          ></PrologueContent>\n          <el-checkbox-group v-model=\"multipleSelectionChat\" @change=\"handleCheckedChatChange\">\n            <template v-for=\"(item, index) in chatList\" :key=\"index\">\n              <div class=\"flex-between w-full\">\n                <el-checkbox :value=\"item.record_id\" v-if=\"selection\" />\n                <div\n                  class=\"w-full border-r-8\"\n                  :class=\"[\n                    selection ? 'p-12 mt-8 mb-8 cursor' : 'mt-24',\n                    multipleSelectionChat.includes(item.record_id) ? 'is-selected' : '',\n                  ]\"\n                  @click=\"toggleSelect(item.record_id)\"\n                >\n                  <!-- 问题 -->\n                  <QuestionContent\n                    :chat-management=\"ChatManagement\"\n                    :type=\"type\"\n                    :application=\"applicationDetails\"\n                    :send-message=\"sendMessage\"\n                    :chat-record=\"item\"\n                    :is-last=\"index >= chatList.length - 1\"\n                    :selection=\"selection\"\n                  ></QuestionContent>\n                </div>\n              </div>\n              <div class=\"flex align-center w-full\">\n                <el-checkbox :value=\"item.record_id\" v-if=\"selection\" />\n                <div\n                  class=\"w-full border-r-8\"\n                  :class=\"[\n                    selection ? 'p-12 cursor' : '',\n                    multipleSelectionChat.includes(item.record_id) ? 'is-selected' : '',\n                  ]\"\n                  @click=\"toggleSelect(item.record_id)\"\n                >\n                  <!-- 回答 -->\n                  <AnswerContent\n                    :application=\"applicationDetails\"\n                    :loading=\"loading\"\n                    v-model:chat-record=\"chatList[index]\"\n                    :type=\"type\"\n                    :send-message=\"sendMessage\"\n                    :chat-management=\"ChatManagement\"\n                    :executionIsRightPanel=\"props.executionIsRightPanel\"\n                    @open-execution-detail=\"emit('openExecutionDetail', chatList[index])\"\n                    @openParagraph=\"emit('openParagraph', chatList[index])\"\n                    @openParagraphDocument=\"\n                      (val: any) => emit('openParagraphDocument', chatList[index], val)\n                    \"\n                    :selection=\"selection\"\n                  ></AnswerContent>\n                </div>\n              </div>\n            </template>\n          </el-checkbox-group>\n          <TransitionContent\n            v-if=\"transcribing\"\n            :text=\"t('chat.inputPlaceholder.recorderLoading')\"\n            :type=\"type\"\n            :application=\"applicationDetails\"\n          >\n          </TransitionContent>\n        </div>\n      </el-scrollbar>\n      <div style=\"position: relative\">\n        <!-- 置底按钮 -->\n        <el-button v-if=\"isBottom\" circle class=\"back-bottom-button\" @click=\"setScrollBottom\">\n          <el-icon>\n            <ArrowDownBold />\n          </el-icon>\n        </el-button>\n        <div class=\"mul-operation border-t w-full\" v-if=\"selection === true\">\n          <div class=\"flex-between chat-width\">\n            <el-checkbox v-model=\"checkAll\" @change=\"handleCheckAllChange\">\n              {{ $t('common.allCheck') }}\n            </el-checkbox>\n            <div>\n              <el-button @click=\"cancelCheckHandle\">\n                {{ $t('common.cancel') }}\n              </el-button>\n              <el-button\n                type=\"primary\"\n                @click=\"shareChatHandle\"\n                :disabled=\"shareLoading || multipleSelectionChat.length === 0\"\n              >\n                {{ $t('chat.copyLinkText') }}\n              </el-button>\n            </div>\n          </div>\n        </div>\n        <ChatInputOperate\n          :app-id=\"appId\"\n          :application-details=\"applicationDetails\"\n          :is-mobile=\"isMobile\"\n          :type=\"type\"\n          :send-message=\"sendMessage\"\n          :open-chat-id=\"openChatId\"\n          :validate=\"validate\"\n          :chat-management=\"ChatManagement\"\n          v-model:chat-id=\"chartOpenId\"\n          v-model:loading=\"loading\"\n          v-model:show-user-input=\"showUserInput\"\n          v-else-if=\"type !== 'log' && type !== 'share'\"\n        >\n          <template #userInput>\n            <el-button\n              v-if=\"isUserInput || isAPIInput\"\n              class=\"user-input-button mb-8\"\n              @click=\"toggleUserInput\"\n            >\n              <AppIcon iconName=\"app-edit\" :size=\"16\" class=\"mr-4\"></AppIcon>\n              <span class=\"ellipsis\">\n                {{ userInputTitle || $t('chat.userInput') }}\n              </span>\n            </el-button>\n          </template>\n        </ChatInputOperate>\n      </div>\n      <Control></Control>\n    </template>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport {\n  type Ref,\n  ref,\n  nextTick,\n  computed,\n  watch,\n  reactive,\n  onMounted,\n  onBeforeUnmount,\n  provide,\n  onBeforeMount,\n} from 'vue'\nimport { useRoute } from 'vue-router'\nimport applicationApi from '@/api/application/application'\nimport chatAPI from '@/api/chat/chat'\nimport SystemResourceManagementApplicationAPI from '@/api/system-resource-management/application.ts'\nimport syetrmResourceManagementChatLogApi from '@/api/system-resource-management/chat-log'\nimport chatLogApi from '@/api/application/chat-log'\nimport { ChatManagement, type chatType } from '@/api/type/application'\nimport { randomId } from '@/utils/common'\nimport useStore from '@/stores'\nimport { debounce } from 'lodash'\nimport AnswerContent from '@/components/ai-chat/component/answer-content/index.vue'\nimport QuestionContent from '@/components/ai-chat/component/question-content/index.vue'\nimport TransitionContent from '@/components/ai-chat/component/transition-content/index.vue'\nimport ChatInputOperate from '@/components/ai-chat/component/chat-input-operate/index.vue'\nimport PrologueContent from '@/components/ai-chat/component/prologue-content/index.vue'\nimport UserForm from '@/components/ai-chat/component/user-form/index.vue'\nimport Control from '@/components/ai-chat/component/control/index.vue'\nimport type { CheckboxValueType } from 'element-plus'\nimport { t } from '@/locales'\nimport bus from '@/bus'\nimport { throttle } from 'lodash-es'\nimport { copyClick } from '@/utils/clipboard'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nprovide('upload', (file: any, loading?: Ref<boolean>) => {\n  return props.type === 'debug-ai-chat'\n    ? applicationApi.postUploadFile(file, 'TEMPORARY_120_MINUTE', 'TEMPORARY_120_MINUTE', loading)\n    : chatAPI.postUploadFile(file, chartOpenId.value, 'CHAT', loading)\n})\nprovide('getSelectModelList', (params: any) => {\n  if (route.path.includes('resource-management')) {\n    return loadSharedApi({ type: 'model', systemType: 'systemManage' }).getSelectModelList(params)\n  } else {\n    return loadSharedApi({ type: 'model', systemType: 'workspace' }).getSelectModelList(params)\n  }\n})\n\nconst transcribing = ref<boolean>(false)\ndefineOptions({ name: 'AiChat' })\nconst route = useRoute()\nconst {\n  params: { accessToken, id },\n  query: { mode },\n} = route as any\nconst props = withDefaults(\n  defineProps<{\n    applicationDetails: any\n    type?: 'log' | 'ai-chat' | 'debug-ai-chat' | 'share'\n    appId?: string\n    record?: Array<chatType>\n    available?: boolean\n    chatId?: string\n    executionIsRightPanel?: boolean\n    chatRecord: chatType\n    selection?: boolean\n  }>(),\n  {\n    applicationDetails: () => ({}),\n    available: true,\n    type: 'ai-chat',\n  },\n)\nconst emit = defineEmits([\n  'refresh',\n  'scroll',\n  'openExecutionDetail',\n  'openParagraph',\n  'openParagraphDocument',\n  'update:selection',\n])\nconst { application, common, chatUser } = useStore()\nconst isMobile = computed(() => {\n  return common.isMobile() || mode === 'embed' || mode === 'mobile'\n})\nconst aiChatRef = ref()\nconst scrollDiv = ref()\nconst dialogScrollbar = ref()\nconst loading = ref(false)\nconst inputValue = ref<string>('')\nconst chartOpenId = ref<string>('')\nconst chatList = ref<any[]>([])\nconst form_data = ref<any>({})\nconst api_form_data = ref<any>({})\nconst userFormRef = ref<InstanceType<typeof UserForm>>()\n// 用户输入\nconst firsUserInput = ref(false)\nconst showUserInput = ref(false)\n\n// 初始表单数据（用于恢复）\nconst initialFormData = ref({})\nconst initialApiFormData = ref({})\n\nconst isUserInput = computed(\n  () =>\n    props.applicationDetails?.work_flow?.nodes?.filter((v: any) => v.id === 'base-node')[0]\n      ?.properties.user_input_field_list.length > 0,\n)\n\nconst userInputTitle = computed(\n  () =>\n    props.applicationDetails.work_flow?.nodes?.filter((v: any) => v.id === 'base-node')[0]\n      ?.properties?.user_input_config?.title,\n)\nconst isAPIInput = computed(\n  () =>\n    props.type === 'debug-ai-chat' &&\n    props.applicationDetails.work_flow?.nodes?.filter((v: any) => v.id === 'base-node')[0]\n      .properties.api_input_field_list.length > 0,\n)\nconst showUserInputContent = computed(() => {\n  return (\n    (((isUserInput.value || isAPIInput.value) && firsUserInput.value) || showUserInput.value) &&\n    props.type !== 'log'\n  )\n})\nwatch(\n  () => props.chatId,\n  (val) => {\n    if (val && val !== 'new') {\n      chartOpenId.value = val\n      firsUserInput.value = false\n    } else {\n      chartOpenId.value = ''\n      if (isUserInput.value) {\n        firsUserInput.value = true\n      } else if (props.type == 'debug-ai-chat' && isAPIInput.value) {\n        firsUserInput.value = true\n      }\n    }\n  },\n  { deep: true, immediate: true },\n)\n\nwatch(\n  () => props.applicationDetails,\n  () => {\n    chartOpenId.value = ''\n  },\n  { deep: true },\n)\n\nwatch(\n  () => props.record,\n  (value) => {\n    chatList.value = value ? value : []\n  },\n  {\n    immediate: true,\n  },\n)\n\n// 选择对话分享\nconst checkAll = ref(false)\nconst multipleSelectionChat = ref<any[]>([])\nconst shareLoading = ref(false)\n\nwatch(\n  () => props.selection,\n  (value) => {\n    if (value) {\n      if (value && multipleSelectionChat.value.length === 0) {\n        multipleSelectionChat.value = chatList.value.map((v) => v.record_id)\n        checkAll.value = true\n      }\n    } else {\n      checkAll.value = false\n      multipleSelectionChat.value = []\n    }\n  },\n  {\n    immediate: true,\n  },\n)\n\nfunction shareChatHandle() {\n  const validIds = new Set(chatList.value.map((v) => v.record_id))\n  const selectedIds = multipleSelectionChat.value.filter((id) => validIds.has(id))\n\n  const obj = {\n    chat_record_ids: selectedIds,\n    is_current_all: checkAll.value,\n  }\n  chatAPI.postShareChat(id || props.appId, chartOpenId.value, obj, shareLoading).then((res) => {\n    if (res.data?.link) {\n      copyClick(window.location.origin + '/chat/share/' + res.data.link)\n    }\n  })\n}\n\nconst handleCheckAllChange = (val: CheckboxValueType) => {\n  multipleSelectionChat.value = val ? chatList.value.map((v) => v.record_id) : []\n  checkAll.value = val as boolean\n}\nconst handleCheckedChatChange = (value: CheckboxValueType[]) => {\n  const checkedCount = value.length\n  checkAll.value = checkedCount === chatList.value.length\n}\n\nfunction toggleSelect(id: number) {\n  if (props.selection) {\n    const index = multipleSelectionChat.value.indexOf(id)\n    if (index === -1) {\n      multipleSelectionChat.value.push(id)\n    } else {\n      multipleSelectionChat.value.splice(index, 1)\n    }\n    checkAll.value = multipleSelectionChat.value.length === chatList.value.length\n  }\n}\n\nfunction cancelCheckHandle() {\n  checkAll.value = false\n  multipleSelectionChat.value = []\n  emit('update:selection', false)\n}\n\nconst toggleUserInput = () => {\n  showUserInput.value = !showUserInput.value\n  if (showUserInput.value) {\n    // 保存当前数据作为初始数据（用于可能的恢复）\n    initialFormData.value = JSON.parse(JSON.stringify(form_data.value))\n    initialApiFormData.value = JSON.parse(JSON.stringify(api_form_data.value))\n  }\n}\n\nfunction UserFormConfirm() {\n  firsUserInput.value = false\n  showUserInput.value = false\n}\n\nfunction UserFormCancel() {\n  // 恢复初始数据\n  form_data.value = JSON.parse(JSON.stringify(initialFormData.value))\n  api_form_data.value = JSON.parse(JSON.stringify(initialApiFormData.value))\n  userFormRef.value?.render(form_data.value)\n  showUserInput.value = false\n}\n\nconst validate = () => {\n  return userFormRef.value?.validate() || Promise.reject(false)\n}\n\nfunction sendMessage(val: string, other_params_data?: any, chat?: chatType): Promise<boolean> {\n  if (isUserInput.value) {\n    if (userFormRef.value) {\n      return userFormRef.value\n        ?.validate()\n        .then((ok) => {\n          const userFormData = accessToken\n            ? JSON.parse(localStorage.getItem(`${accessToken}userForm`) || '{}')\n            : {}\n          const newData = Object.keys(form_data.value).reduce((result: any, key: string) => {\n            result[key] = Object.prototype.hasOwnProperty.call(userFormData, key)\n              ? userFormData[key]\n              : form_data.value[key]\n            return result\n          }, {})\n          if (accessToken) {\n            localStorage.setItem(`${accessToken}userForm`, JSON.stringify(newData))\n          }\n\n          showUserInput.value = false\n\n          if (!loading.value && props.applicationDetails?.name) {\n            handleDebounceClick(val, other_params_data, chat)\n            return true\n          }\n          throw 'err: no send'\n        })\n        .catch((e) => {\n          if (isAPIInput.value && props.type !== 'debug-ai-chat') {\n            showUserInput.value = false\n          } else {\n            showUserInput.value = true\n          }\n\n          return false\n        })\n    } else {\n      return Promise.reject(false)\n    }\n  } else {\n    showUserInput.value = false\n    if (!loading.value && props.applicationDetails?.name) {\n      handleDebounceClick(val, other_params_data, chat)\n      return Promise.resolve(true)\n    }\n    return Promise.reject(false)\n  }\n}\n\nconst handleDebounceClick = debounce((val, other_params_data?: any, chat?: chatType) => {\n  chatMessage(chat, val, false, other_params_data)\n}, 200)\n\n/**\n * 打开对话id\n */\nconst openChatId: () => Promise<string> = () => {\n  const obj = props.applicationDetails\n  return getOpenChatAPI()(obj.id)\n    .then((res) => {\n      chartOpenId.value = res.data\n      return res.data\n    })\n    .catch((res) => {\n      return Promise.reject(res)\n    })\n}\n\nconst getChatMessageAPI = () => {\n  if (props.type === 'debug-ai-chat') {\n    return applicationApi.chat\n  } else {\n    return chatAPI.chat\n  }\n}\nconst getOpenChatAPI = () => {\n  if (props.type === 'debug-ai-chat') {\n    if (route.path.includes('resource-management')) {\n      return SystemResourceManagementApplicationAPI.open\n    } else {\n      return applicationApi.open\n    }\n  } else {\n    return (a?: string, loading?: Ref<boolean>) => {\n      return chatAPI.open(loading)\n    }\n  }\n}\n\nconst getChatRecordDetailsAPI = (row: any) => {\n  if (row.record_id) {\n    if (props.type === 'debug-ai-chat') {\n      if (route.path.includes('resource-management')) {\n        return syetrmResourceManagementChatLogApi.getChatRecordDetails(\n          id || props.appId,\n          row.chat_id,\n          row.record_id,\n          loading,\n        )\n      } else {\n        return chatLogApi.getChatRecordDetails(\n          id || props.appId,\n          row.chat_id,\n          row.record_id,\n          loading,\n        )\n      }\n    } else {\n      return chatAPI.getChatRecord(row.chat_id, row.record_id, loading)\n    }\n  }\n  return Promise.reject('404')\n}\n\n/**\n * 获取对话详情\n * @param row\n */\nfunction getSourceDetail(row: any) {\n  return getChatRecordDetailsAPI(row).then((res) => {\n    const exclude_keys = ['answer_text', 'id', 'answer_text_list']\n    Object.keys(res.data).forEach((key) => {\n      if (!exclude_keys.includes(key)) {\n        row[key] = res.data[key]\n      }\n    })\n  })\n}\n\n/**\n * 对话\n */\nfunction getChartOpenId(chat?: any, problem?: string, re_chat?: boolean, other_params_data?: any) {\n  return openChatId().then(() => {\n    chatMessage(chat, problem, re_chat, other_params_data)\n  })\n}\n\n/**\n * 获取一个递归函数,处理流式数据\n * @param chat    每一条对话记录\n * @param reader  流数据\n * @param stream  是否是流式数据\n */\nconst getWrite = (chat: any, reader: any, stream: boolean) => {\n  let tempResult = ''\n\n  const write_stream = async () => {\n    try {\n      while (true) {\n        const { done, value } = await reader.read()\n\n        if (done) {\n          ChatManagement.close(chat.id)\n          return\n        }\n\n        const decoder = new TextDecoder('utf-8')\n        let str = decoder.decode(value, { stream: true })\n\n        tempResult += str\n        const split = tempResult.match(/data:.*?}\\n\\n/g)\n        if (split) {\n          str = split.join('')\n          tempResult = tempResult.replace(str, '')\n\n          // 批量处理所有 chunk\n          for (const item of split) {\n            const chunk = JSON.parse(item.replace('data:', ''))\n            chat.chat_id = chunk.chat_id\n            chat.record_id = chunk.chat_record_id\n\n            if (!chunk.is_end) {\n              ChatManagement.appendChunk(chat.id, chunk)\n            }\n\n            if (chunk.is_end) {\n              return Promise.resolve()\n            }\n          }\n        }\n        // 如果没有匹配到完整chunk，继续读取下一块\n      }\n    } catch (e) {\n      return Promise.reject(e)\n    }\n  }\n\n  const write_json = async () => {\n    try {\n      while (true) {\n        const { done, value } = await reader.read()\n\n        if (done) {\n          const result_block = JSON.parse(tempResult)\n          if (result_block.code === 500) {\n            return Promise.reject(result_block.message)\n          } else {\n            if (result_block.content) {\n              ChatManagement.append(chat.id, result_block.content)\n            }\n          }\n          ChatManagement.close(chat.id)\n          return\n        }\n\n        if (value) {\n          const decoder = new TextDecoder('utf-8')\n          tempResult += decoder.decode(value)\n        }\n      }\n    } catch (e) {\n      return Promise.reject(e)\n    }\n  }\n\n  return stream ? write_stream : write_json\n}\nconst errorWrite = (chat: any, message?: string) => {\n  ChatManagement.addChatRecord(chat, 50, loading)\n  ChatManagement.write(chat.id)\n  ChatManagement.append(chat.id, message || t('chat.tip.error500Message'))\n  ChatManagement.updateStatus(chat.id, 500)\n  ChatManagement.close(chat.id)\n}\n\n// 保存上传文件列表\n\nfunction chatMessage(chat?: any, problem?: string, re_chat?: boolean, other_params_data?: any) {\n  loading.value = true\n  if (!chat) {\n    chat = reactive({\n      id: randomId(),\n      problem_text: problem ? problem : inputValue.value.trim(),\n      answer_text: '',\n      answer_text_list: [[]],\n      buffer: [],\n      reasoning_content: '',\n      reasoning_content_buffer: [],\n      write_ed: false,\n      is_stop: false,\n      record_id: '',\n      chat_id: '',\n      vote_status: '-1',\n      status: undefined,\n      upload_meta: {\n        image_list:\n          other_params_data && other_params_data.image_list ? other_params_data.image_list : [],\n        document_list:\n          other_params_data && other_params_data.document_list\n            ? other_params_data.document_list\n            : [],\n        audio_list:\n          other_params_data && other_params_data.audio_list ? other_params_data.audio_list : [],\n        video_list:\n          other_params_data && other_params_data.video_list ? other_params_data.video_list : [],\n        other_list:\n          other_params_data && other_params_data.other_list ? other_params_data.other_list : [],\n      },\n    })\n    chatList.value.push(chat)\n    ChatManagement.addChatRecord(chat, 50, loading)\n    ChatManagement.write(chat.id)\n    inputValue.value = ''\n    nextTick(() => {\n      // 将滚动条滚动到最下面\n      scrollDiv.value.setScrollTop(getMaxHeight())\n    })\n  }\n  if (chat.run_time) {\n    ChatManagement.addChatRecord(chat, 50, loading)\n    ChatManagement.write(chat.id)\n  }\n  if (!chartOpenId.value) {\n    getChartOpenId(chat, problem, re_chat, other_params_data).catch(() => {\n      errorWrite(chat)\n    })\n  } else {\n    const obj = {\n      message: chat.problem_text,\n      stream: true,\n      re_chat: re_chat || false,\n      ...other_params_data,\n      form_data: {\n        ...form_data.value,\n        ...api_form_data.value,\n      },\n    }\n    // 对话\n    getChatMessageAPI()(chartOpenId.value, obj)\n      .then((response) => {\n        if (response.status === 460) {\n          return Promise.reject(t('chat.tip.errorIdentifyMessage'))\n        } else if (response.status === 461) {\n          return Promise.reject(t('chat.tip.errorLimitMessage'))\n        } else {\n          nextTick(() => {\n            // 将滚动条滚动到最下面\n            scrollDiv.value.setScrollTop(getMaxHeight())\n          })\n          const reader = response.body.getReader()\n          // 处理流数据\n          const write = getWrite(\n            chat,\n            reader,\n            response.headers.get('Content-Type') !== 'application/json',\n          )\n          return write()\n        }\n      })\n      .then(() => {\n        if (props.chatId === 'new') {\n          emit('refresh', chartOpenId.value)\n        }\n        getSourceDetail(chat)\n        // if (props.type === 'debug-ai-chat') {\n        //   getSourceDetail(chat)\n        // } else {\n        //   if (\n        //     props.applicationDetails &&\n        //     (props.applicationDetails.show_exec || props.applicationDetails.show_source)\n        //   ) {\n        //     getSourceDetail(chat)\n        //   }\n        // }\n      })\n      .finally(() => {\n        ChatManagement.close(chat.id)\n      })\n      .catch((e: any) => {\n        errorWrite(chat, e + '')\n      })\n  }\n}\n\n/**\n * 滚动条距离最上面的高度\n */\nconst scrollTop = ref(0)\n\nconst scorll = ref(true)\nconst isBottom = ref(false)\n\nconst getMaxHeight = () => {\n  return dialogScrollbar.value!.scrollHeight\n}\n\n/**\n * 滚动滚动条到最上面\n * @param $event\n */\nconst handleScrollTop = ($event: any) => {\n  scrollTop.value = $event.scrollTop\n  if (\n    dialogScrollbar.value.scrollHeight - (scrollTop.value + scrollDiv.value.wrapRef.offsetHeight) <=\n    40\n  ) {\n    scorll.value = true\n  } else {\n    scorll.value = false\n  }\n  isBottom.value =\n    scrollTop.value + scrollDiv.value.wrapRef.offsetHeight < dialogScrollbar.value!.scrollHeight\n  emit('scroll', { ...$event, dialogScrollbar: dialogScrollbar.value, scrollDiv: scrollDiv.value })\n}\n/**\n * 处理跟随滚动条\n */\nconst handleScroll = () => {\n  if (props.type !== 'log' && scrollDiv.value) {\n    // 内部高度小于外部高度 就需要出滚动条\n    if (scrollDiv.value.wrapRef.offsetHeight < dialogScrollbar.value.scrollHeight) {\n      // 只有在用户已经在底部附近时才自动滚动到底部\n      const isNearBottom =\n        dialogScrollbar.value.scrollHeight -\n          (scrollTop.value + scrollDiv.value.wrapRef.offsetHeight) <=\n        40\n      if (scorll.value || isNearBottom) {\n        // 滚动到底部\n        scrollDiv.value.setScrollTop(dialogScrollbar.value.scrollHeight)\n      }\n    }\n  }\n}\nonBeforeMount(() => {\n  window.chatUserProfile = () => {\n    if (props.type === 'ai-chat') {\n      if (chatUser.chat_profile?.authentication_type === 'login') {\n        return chatUser.getChatUserProfile()\n      }\n    }\n    return Promise.resolve(null)\n  }\n})\n\nfunction parseTransform(transformStr: string) {\n  const result = { scale: 1, translateX: 0, translateY: 0, translateZ: 0 }\n\n  if (!transformStr || transformStr === 'none') return result\n\n  // 使用正则表达式匹配 scale 和 translate3d 的值\n  const scaleMatch = transformStr.match(/scale\\(([^)]+)\\)/)\n  const translateMatch = transformStr.match(/translate3d\\(([^)]+)\\)/)\n\n  if (scaleMatch) {\n    // scale可能是一个值，也可能是两个值（scaleX, scaleY）\n    const scaleValues = scaleMatch[1].split(',').map((v) => parseFloat(v.trim()))\n    result.scale = scaleValues[0]\n  }\n\n  if (translateMatch) {\n    const translateValues = translateMatch[1].split(',').map((v) => parseFloat(v.trim()))\n    ;[result.translateX, result.translateY, result.translateZ] = translateValues\n  }\n\n  return result\n}\n\nonMounted(() => {\n  if (isUserInput.value && localStorage.getItem(`${accessToken}userForm`)) {\n    const userFormData = JSON.parse(localStorage.getItem(`${accessToken}userForm`) || '{}')\n    form_data.value = userFormData\n  }\n  if (window.speechSynthesis) {\n    window.speechSynthesis.cancel()\n  }\n\n  const handleZoom = throttle((event: WheelEvent, target: HTMLElement) => {\n    // 2. 解析当前变换状态\n    const currentTransform = target.style.transform\n    const transformValues = parseTransform(currentTransform)\n    const { scale, translateX, translateY } = transformValues\n    // 确保scale是数值类型\n    const currentScale = Array.isArray(scale) ? scale[0] : scale\n\n    // 3. 计算缩放方向和新的缩放比例\n    const zoomIntensity = 0.05 // 每次滚轮的缩放步长\n    const zoomFactor = event.deltaY < 0 ? 1 + zoomIntensity : 1 - zoomIntensity\n    const newScale = Math.max(0.1, currentScale * zoomFactor) // 设置最小缩放限制\n    // 4. 计算新的平移值\n    const newTranslateX = (translateX * currentScale) / newScale\n    const newTranslateY = (translateY * currentScale) / newScale\n    // 5. 应用新的变换\n    target.style.transform = `scale(${newScale}) translate3d(${newTranslateX}px, ${newTranslateY}px, 0px)`\n  }, 50) // 50ms 内只执行一次\n\n  document.body.addEventListener(\n    'wheel',\n    (event) => {\n      // 1. 定位目标元素\n      if (event.target) {\n        const target = event.target as HTMLElement\n        // 假设打开状态的图片具有特定类名\n        if (target.classList && target.classList.contains('medium-zoom-overlay')) {\n          event.preventDefault()\n          event.stopPropagation()\n        }\n        if (target.classList && target.classList.contains('medium-zoom-image--opened')) {\n          event.preventDefault()\n          event.stopPropagation()\n\n          handleZoom(event, target)\n        }\n      }\n    },\n    { passive: false },\n  )\n\n  window.sendMessage = sendMessage\n  bus.on('on:transcribing', (status: boolean) => {\n    transcribing.value = status\n    nextTick(() => {\n      if (scorll.value) {\n        scrollDiv.value.setScrollTop(getMaxHeight())\n      }\n    })\n  })\n  bus.on('click:share', (id: string) => {\n    multipleSelectionChat.value.push(id)\n    checkAll.value = multipleSelectionChat.value.length === chatList.value.length\n    emit('update:selection', true)\n  })\n})\n\nonBeforeUnmount(() => {\n  window.sendMessage = null\n  window.chatUserProfile = null\n})\n\nfunction setScrollBottom() {\n  // 将滚动条滚动到最下面\n  scrollDiv.value.setScrollTop(getMaxHeight())\n}\n\nwatch(\n  chatList,\n  () => {\n    nextTick(() => {\n      handleScroll() // 确保 DOM 更新后再滚动\n    })\n  },\n  { deep: true, immediate: true },\n)\n\ndefineExpose({\n  setScrollBottom,\n  loading,\n})\n</script>\n<style lang=\"scss\">\n@use './index.scss';\n\n.firstUserInput {\n  height: 100%;\n  display: flex;\n  justify-content: center;\n  overflow: auto;\n\n  .user-form-container {\n    max-width: 70%;\n  }\n}\n\n.debug-ai-chat {\n  .user-form-container {\n    max-width: 100%;\n  }\n}\n\n.popperUserInput {\n  position: absolute;\n  z-index: 999;\n  left: 0;\n  bottom: 50px;\n  width: calc(100% - 50px);\n  max-width: 400px;\n}\n\n@media only screen and (max-width: 768px) {\n  .firstUserInput {\n    .user-form-container {\n      max-width: 100%;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/app-charts/components/BarCharts.vue",
    "content": "<template>\n  <div :id=\"id\" ref=\"BarChartRef\" :style=\"{ height: height, width: width }\" />\n</template>\n<script lang=\"ts\" setup>\nimport { onMounted, nextTick, watch, onBeforeUnmount } from 'vue'\nimport * as echarts from 'echarts'\nimport { numberFormat } from '@/utils/common'\nconst props = defineProps({\n  id: {\n    type: String,\n    default: 'barChartId',\n  },\n  width: {\n    type: String,\n    default: '100%',\n  },\n  height: {\n    type: String,\n    default: '200px',\n  },\n  option: {\n    type: Object,\n    required: true,\n  }, // option: { title , xData, yData, formatStr  }\n})\n\nconst color = ['rgba(82, 133, 255, 1)', 'rgba(255, 207, 47, 1)']\n\nconst areaColor = ['rgba(82, 133, 255, 0.2)', 'rgba(255, 207, 47, 0.2)']\n\nfunction initChart() {\n  let myChart = echarts?.getInstanceByDom(document.getElementById(props.id)!)\n  if (myChart === null || myChart === undefined) {\n    myChart = echarts.init(document.getElementById(props.id))\n  }\n  const series: any = []\n  if (props.option?.yData?.length) {\n    props.option?.yData.forEach((item: any, index: number) => {\n      series.push({\n        type: 'bar',\n        barWidth: '20',\n        itemStyle: {\n          color: color[index],\n        },\n        areaStyle: item.area\n          ? {\n              color: areaColor[index],\n            }\n          : null,\n        ...item,\n      })\n    })\n  }\n  const option = {\n    title: {\n      text: props.option?.title,\n      textStyle: {\n        fontSize: '16px',\n        color: '#1f2329',\n      }\n    },\n    tooltip: {\n      trigger: 'axis',\n      valueFormatter: (value: any) => numberFormat(value),\n    },\n    legend: {\n      right: 0,\n      itemWidth: 8,\n      textStyle: {\n        color: '#646A73',\n      },\n      icon: 'circle',\n    },\n    grid: {\n      left: '1%',\n      right: '1%',\n      bottom: '0',\n      top: '18%',\n      containLabel: true,\n    },\n    xAxis: {\n      type: 'category',\n      data: props.option.xData,\n    },\n    yAxis: {\n      type: 'value',\n      splitLine: {\n        lineStyle: {\n          color: '#EFF0F1',\n        },\n      },\n      axisLabel: {\n        formatter: (value: any) => {\n          return numberFormat(value)\n        },\n      },\n    },\n    dataZoom: [\n      {\n        type: 'inside',\n        show: props.option.dataZoom,\n      },\n      {\n        type: 'slider',\n        show: props.option.dataZoom,\n      },\n    ],\n    series: series,\n  }\n\n  // 渲染数据\n  myChart.setOption(option, true)\n}\n\nfunction changeChartSize() {\n  echarts.getInstanceByDom(document.getElementById(props.id)!)?.resize()\n}\n\nwatch(\n  () => props.option,\n  (val) => {\n    if (val) {\n      nextTick(() => {\n        initChart()\n      })\n    }\n  },\n)\n\nonMounted(() => {\n  nextTick(() => {\n    initChart()\n    window.addEventListener('resize', changeChartSize)\n  })\n})\n\nonBeforeUnmount(() => {\n  window.removeEventListener('resize', changeChartSize)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/app-charts/components/LineCharts.vue",
    "content": "<template>\n  <div :id=\"id\" ref=\"LineChartRef\" :style=\"{ height: height, width: width }\" />\n</template>\n<script lang=\"ts\" setup>\nimport { onMounted, nextTick, watch, onBeforeUnmount } from 'vue'\nimport * as echarts from 'echarts'\nimport { numberFormat } from '@/utils/common'\nconst props = defineProps({\n  id: {\n    type: String,\n    default: 'lineChartId',\n  },\n  width: {\n    type: String,\n    default: '100%',\n  },\n  height: {\n    type: String,\n    default: '200px',\n  },\n  option: {\n    type: Object,\n    required: true,\n  }, // option: { title , xData, yData, formatStr  }\n})\n\nconst color = ['rgba(82, 133, 255, 1)', 'rgba(255, 207, 47, 1)']\n\nconst areaColor = ['rgba(82, 133, 255, 0.2)', 'rgba(255, 207, 47, 0.2)']\n\nfunction initChart() {\n  let myChart = echarts?.getInstanceByDom(document.getElementById(props.id)!)\n  if (myChart === null || myChart === undefined) {\n    myChart = echarts.init(document.getElementById(props.id))\n  }\n  const series: any = []\n  if (props.option?.yData?.length) {\n    props.option?.yData.forEach((item: any, index: number) => {\n      series.push({\n        type: 'line',\n        itemStyle: {\n          color: color[index],\n        },\n        areaStyle: item.area\n          ? {\n              color: areaColor[index],\n            }\n          : null,\n        ...item,\n      })\n    })\n  }\n  const option = {\n    title: {\n      text: props.option?.title,\n      textStyle: {\n        fontSize: '16px',\n        color: '#1f2329',\n      },\n    },\n    tooltip: {\n      trigger: 'axis',\n      valueFormatter: (value: any) => numberFormat(value),\n    },\n    legend: {\n      right: 0,\n      itemWidth: 8,\n      textStyle: {\n        color: '#646A73',\n      },\n      icon: 'circle',\n    },\n    grid: {\n      left: '1%',\n      right: '1%',\n      bottom: '0',\n      top: '18%',\n      containLabel: true,\n    },\n    xAxis: {\n      type: 'category',\n      data: props.option.xData,\n    },\n    yAxis: {\n      type: 'value',\n      splitLine: {\n        lineStyle: {\n          color: '#EFF0F1',\n        },\n      },\n      axisLabel: {\n        formatter: (value: any) => {\n          return numberFormat(value)\n        },\n      },\n    },\n    series: series,\n  }\n\n  // 渲染数据\n  myChart.setOption(option, true)\n}\n\nfunction changeChartSize() {\n  echarts.getInstanceByDom(document.getElementById(props.id)!)?.resize()\n}\n\nwatch(\n  () => props.option,\n  (val) => {\n    if (val) {\n      nextTick(() => {\n        initChart()\n      })\n    }\n  },\n)\n\nonMounted(() => {\n  nextTick(() => {\n    initChart()\n    window.addEventListener('resize', changeChartSize)\n  })\n})\n\nonBeforeUnmount(() => {\n  window.removeEventListener('resize', changeChartSize)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/app-charts/index.vue",
    "content": "<template>\n  <component\n    :is=\"typeComponentMap[type]\"\n    :height=\"height\"\n    :option=\"option\"\n    :dataZoom=\"dataZoom\"\n    class=\"v-charts\"\n  />\n</template>\n<script lang=\"ts\" setup>\nimport line from './components/LineCharts.vue'\nimport bar from './components/BarCharts.vue'\ndefineOptions({ name: 'AppCharts' })\ndefineProps({\n  type: {\n    type: String,\n    default: 'line',\n  },\n  height: {\n    type: String,\n    default: '200px',\n  },\n  dataZoom: Boolean,\n  option: {\n    type: Object,\n    required: true,\n  }, // { title , xData, yData, formatStr  }\n})\n\nconst typeComponentMap = { line, bar } as any\n</script>\n"
  },
  {
    "path": "ui/src/components/app-icon/AppIcon.vue",
    "content": "<template>\n  <component\n    v-if=\"isIconfont\"\n    :is=\"\n      Object.keys(iconMap).includes(iconName)\n        ? iconMap[iconName].iconReader()\n        : iconMap['app-404'].iconReader()\n    \"\n    class=\"el-icon app-icon\"\n  >\n  </component>\n  <el-icon v-else-if=\"iconName\">\n    <component :is=\"iconName\" />\n  </el-icon>\n</template>\n<script setup lang=\"ts\">\nimport { iconMap } from './index'\nimport { computed } from 'vue'\ndefineOptions({ name: 'AppIcon' })\nconst props = withDefaults(\n  defineProps<{\n    iconName?: string\n  }>(),\n  {\n    iconName: 'app-404',\n  },\n)\n\nconst isIconfont = computed(() => props.iconName?.includes('app-'))\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/app-icon/KnowledgeIcon.vue",
    "content": "<template>\n  <el-avatar v-if=\"type == 1\" class=\"avatar-purple\" shape=\"square\" :size=\"size\">\n    <img src=\"@/assets/knowledge/icon_web.svg\" style=\"width: 58%\" alt=\"\" />\n  </el-avatar>\n  <el-avatar\n    v-else-if=\"type == 2\"\n    class=\"avatar-purple\"\n    shape=\"square\"\n    :size=\"size\"\n    style=\"background: none\"\n  >\n    <img src=\"@/assets/knowledge/logo_lark.svg\" style=\"width: 100%\" alt=\"\" />\n  </el-avatar>\n  <el-avatar\n    v-else-if=\"type == 4\"\n    class=\"avatar-orange\"\n    shape=\"square\"\n    :size=\"size\"\n  >\n    <img src=\"@/assets/knowledge/logo_workflow.svg\" style=\"width: 60%\" alt=\"\" />\n  </el-avatar>\n  <el-avatar v-else class=\"avatar-blue\" shape=\"square\" :size=\"size\">\n    <img src=\"@/assets/knowledge/icon_document.svg\" style=\"width: 58%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\">\ndefineOptions({ name: 'KnowledgeIcon' })\nconst props = defineProps({\n  type: {\n    type: [String, Number],\n    default: '',\n  },\n  size: {\n    type: [String, Number],\n    default: 32,\n  },\n})\n</script>\n"
  },
  {
    "path": "ui/src/components/app-icon/ToolIcon.vue",
    "content": "<template>\n  <el-avatar v-if=\"type == 'MCP'\" shape=\"square\" :size=\"size\">\n    <img src=\"@/assets/tool/icon_mcp.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n  <el-avatar v-else-if=\"type == 'SKILL'\" shape=\"square\" :size=\"size\">\n    <img src=\"@/assets/tool/icon_skill.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n  <el-avatar v-else-if=\"type == 'DATA_SOURCE'\" class=\"avatar-purple\" shape=\"square\" :size=\"size\">\n    <img src=\"@/assets/tool/icon_datasource.svg\" style=\"width: 58%\" alt=\"\" />\n  </el-avatar>\n  <el-avatar v-else class=\"avatar-green\" shape=\"square\" :size=\"size\">\n    <img src=\"@/assets/tool/icon_tool.svg\" style=\"width: 58%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\">\ndefineOptions({ name: 'ToolIcon' })\nconst props = defineProps({\n  type: {\n    type: [String, Number],\n    default: '',\n  },\n  size: {\n    type: [String, Number],\n    default: 32,\n  },\n})\n</script>\n"
  },
  {
    "path": "ui/src/components/app-icon/TriggerIcon.vue",
    "content": "<template>\n  <el-avatar v-if=\"type == 'EVENT'\" class=\"avatar-orange\" shape=\"square\" :size=\"size\">\n    <img src=\"@/assets/trigger/icon_event.svg\" style=\"width: 58%\" alt=\"\" />\n  </el-avatar>\n  <el-avatar v-else shape=\"square\" :size=\"size\">\n    <img src=\"@/assets/trigger/icon_scheduled.svg\" style=\"width: 58%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\">\ndefineOptions({ name: 'TriggerIcon' })\nconst props = defineProps({\n  type: {\n    type: [String, Number],\n    default: '',\n  },\n  size: {\n    type: [String, Number],\n    default: 32,\n  },\n})\n</script>\n"
  },
  {
    "path": "ui/src/components/app-icon/icons/about.ts",
    "content": "import { h } from 'vue'\nexport default {\n  'app-github': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9 23.5 23.2 38.1 55.4 38.1 91v112.5c0.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-trigger': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 18 18',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M9.16458 4.44856C9.09375 1.9795 7.06958 0 4.58333 0C2.05208 0 0 2.052 0 4.58314C0 7.06804 1.97708 9.09087 4.44458 9.1642L3.72792 7.37219C3.24691 7.22438 2.81234 6.95463 2.46643 6.58918C2.12052 6.22374 1.87505 5.77501 1.75388 5.28663C1.63271 4.79826 1.63995 4.28684 1.77492 3.80209C1.90988 3.31734 2.16797 2.87575 2.5241 2.52025C2.88022 2.16475 3.32227 1.90743 3.80727 1.77331C4.29227 1.63918 4.80372 1.63281 5.29191 1.75481C5.7801 1.87682 6.22842 2.12305 6.59329 2.46957C6.95816 2.81609 7.22717 3.25111 7.37417 3.73234L9.16458 4.44856Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M8.98125 17.1364C9.26292 17.8405 10.2629 17.8326 10.5333 17.1239L12.3442 12.3799L17.1263 10.5329C17.8325 10.26 17.8383 9.26295 17.1354 8.98171L5.35042 4.26774C4.67 3.99567 3.995 4.67106 4.26709 5.35103L8.98125 17.1364Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-help': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m0 85.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m-21.333333-298.666666h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333zM343.466667 396.032c0.554667-4.778667 1.109333-8.746667 1.664-11.946667 8.32-46.293333 29.397333-80.341333 63.189333-102.144 26.453333-17.28 59.008-25.941333 97.621333-25.941333 50.730667 0 92.842667 12.288 126.378667 36.864 33.578667 24.533333 50.346667 60.928 50.346667 109.141333 0 29.568-7.253333 54.485333-21.888 74.752-8.533333 12.245333-24.917333 27.946667-49.152 47.061334l-23.893334 18.773333c-13.013333 10.24-21.632 22.186667-25.898666 35.84-1.152 3.712-2.176 10.624-3.072 20.736a21.333333 21.333333 0 0 1-21.248 19.498667h-47.786667a21.333333 21.333333 0 0 1-21.248-23.296c2.773333-29.696 5.717333-48.469333 8.832-56.362667 5.845333-14.677333 20.906667-31.573333 45.141333-50.688l24.533334-19.413333c8.106667-6.144 49.749333-35.456 49.749333-61.44 0-25.941333-4.522667-35.498667-17.578667-49.749334-13.013333-14.208-42.368-18.773333-68.864-18.773333-26.026667 0-48.256 6.869333-59.136 24.405333-5.034667 8.106667-9.173333 16.768-12.117333 25.6a89.472 89.472 0 0 0-3.114667 13.098667 21.333333 21.333333 0 0 1-21.034666 17.706667H364.672a21.333333 21.333333 0 0 1-21.205333-23.722667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-user-manual': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M768 128H256a85.333333 85.333333 0 0 0-85.333333 85.333333v426.666667h512V64h85.333333v640a21.333333 21.333333 0 0 1-21.333333 21.333333H256a85.333333 85.333333 0 0 0-0.128 170.666667H832a21.333333 21.333333 0 0 0 21.333333-21.333333V341.333333h85.333334v597.333334a42.666667 42.666667 0 0 1-42.666667 42.666666H256c-94.293333 0-170.666667-76.16-170.666667-170.410666V213.248C85.333333 119.04 161.706667 42.666667 256 42.666667h469.333333a42.666667 42.666667 0 0 1 42.666667 42.666666v42.666667z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M277.333333 768a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h469.333334a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333h-469.333334z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-pricing': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M354.261333 774.314667c19.626667 6.613333 24.106667 25.813333 9.941334 40.704-21.504 21.802667-151.552 39.808-190.165334 45.482666-7.381333 1.109333-14.165333 3.328-21.248-0.725333-10.197333-5.674667-11.904-15.616-9.941333-28.885333 5.205333-35.84 21.76-127.018667 47.061333-193.365334 2.261333-5.930667 9.685333-6.869333 15.616-6.4 12.288 0 18.901333 5.461333 23.381334 18.005334 17.066667 48.981333 48.512 86.144 93.909333 111.445333 9.941333 5.461333 20.565333 9.941333 31.445333 13.738667zM902.698667 127.146667c2.346667 14.549333 3.968 28.842667 3.754666 43.605333-2.133333 73.386667-18.176 143.957333-44.8 212.394667-31.872 81.834667-78.549333 153.770667-143.914666 213.333333a18.133333 18.133333 0 0 0-6.4 16.853333c2.389333 22.016 4.053333 65.408 6.4 87.466667 5.205333 51.328-12.757333 93.269333-54.485334 123.050667-44.8 31.872-91.306667 61.44-137.258666 91.434666-29.013333 18.773333-64.64 1.621333-67.968-32.597333-3.754667-39.381333-6.613333-100.096-9.429334-139.477333-1.450667-19.925333-0.938667-19.925333-20.053333-22.485334-51.2-6.570667-91.050667-30.72-118.4-74.325333-14.165333-22.485333-21.248-47.36-23.594667-73.386667-0.725333-7.978667-4.010667-9.813333-11.349333-10.325333-41.258667-2.090667-103.893333-4.693333-145.152-7.722667-34.218667-2.56-51.669333-38.442667-33.28-68.437333 12.757333-21.12 26.453333-41.728 39.893333-62.592 14.378667-22.528 28.501333-44.8 42.922667-67.285333 26.410667-41.002667 64.384-63.061333 112.981333-63.530667 27.818667-0.213333 77.013333 4.693333 104.832 7.722667 5.418667 0.469333 9.216-0.213333 12.714667-4.181334 64.64-71.765333 144.384-120.277333 234.965333-152.618666a675.584 675.584 0 0 1 157.824-35.84c27.349333-2.858667 54.698667-3.797333 81.834667 1.152 14.848 2.816 15.616 3.498667 17.92 17.792z m-90.965334 65.92c-47.232 4.906667-93.184 15.36-137.941333 31.36-82.133333 29.312-148.138667 71.466667-199.850667 128.853333-23.381333 26.325333-53.248 35.242667-85.845333 32.426667-7.936-0.853333-15.829333-1.877333-23.765333-2.901334a634.453333 634.453333 0 0 0-70.954667-4.352c-19.029333 0.170667-30.72 6.826667-41.898667 24.149334l-21.333333 33.450666-24.618667 38.485334c17.792 1.066667 35.712 2.090667 53.802667 2.986666 20.48 1.322667 59.733333 6.186667 78.634667 21.973334 22.144 18.432 31.402667 41.514667 33.536 65.834666 1.365333 14.72 4.906667 26.197333 10.88 35.669334 13.269333 21.12 30.08 31.573333 57.6 35.114666l7.296 1.024c8.192 1.28 14.72 2.688 22.741333 5.546667 15.829333 5.632 30.421333 15.104 42.154667 29.866667 10.453333 13.141333 15.914667 48 18.773333 62.208 1.28 6.4 5.248 58.026667 6.229333 71.381333a2236.16 2236.16 0 0 0 76.501334-51.754667c16.170667-11.52 21.333333-23.338667 19.2-44.501333-1.024-9.813333-5.589333-80.256-6.4-87.722667a103.125333 103.125333 0 0 1 33.792-88.746666c53.546667-48.853333 93.696-108.885333 121.856-181.248 21.12-54.186667 33.749333-107.434667 37.802666-159.872a335.018667 335.018667 0 0 0-8.192 0.768zM672 405.333333a64 64 0 1 1 0-128 64 64 0 0 1 0 128z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n}\n\n"
  },
  {
    "path": "ui/src/components/app-icon/icons/application.ts",
    "content": "import { h } from 'vue'\nexport default {\n  'app-create-chat': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M8 1C11.866 1 15 4.13401 15 8C15 11.866 11.866 15 8 15H1.66667C1.29848 15 1 14.7015 1 14.3333V8C1 4.13401 4.13401 1 8 1ZM2.33333 13.6667H8C11.1296 13.6667 13.6667 11.1296 13.6667 7.99998C13.6667 4.87037 11.1296 2.33332 8 2.33332C4.87039 2.33332 2.33333 4.87037 2.33333 7.99998V13.6667Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M7.66667 5C7.48257 5 7.33333 5.14924 7.33333 5.33333V7.33333H5.33333C5.14924 7.33333 5 7.48257 5 7.66667V8.33333C5 8.51743 5.14924 8.66667 5.33333 8.66667H7.33333V10.6667C7.33333 10.8508 7.48257 11 7.66667 11H8.33333C8.51743 11 8.66667 10.8508 8.66667 10.6667V8.66667H10.6667C10.8508 8.66667 11 8.51743 11 8.33333V7.66667C11 7.48257 10.8508 7.33333 10.6667 7.33333H8.66667V5.33333C8.66667 5.14924 8.51743 5 8.33333 5H7.66667Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-access': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M490.368 48.554667a42.666667 42.666667 0 0 1 43.264 0l362.666667 213.333333A42.666667 42.666667 0 0 1 917.333333 298.666667v426.666666a42.666667 42.666667 0 0 1-21.034666 36.778667l-362.666667 213.333333a42.666667 42.666667 0 0 1-43.264 0l-362.666667-213.333333A42.666667 42.666667 0 0 1 106.666667 725.333333V298.666667a42.666667 42.666667 0 0 1 21.034666-36.778667l362.666667-213.333333zM192 323.072v377.856L512 889.173333l320-188.245333V323.072L512 134.826667 192 323.072z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M705.194667 441.472a42.666667 42.666667 0 1 0-45.226667-72.362667l-148.096 92.586667L363.946667 369.066667a42.666667 42.666667 0 1 0-45.312 72.362666L469.333333 535.722667V704a42.666667 42.666667 0 1 0 85.333334 0v-168.448l150.528-94.08z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-access-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M533.632 48.554667a42.666667 42.666667 0 0 0-43.264 0l-362.666667 213.333333A42.666667 42.666667 0 0 0 106.666667 298.666667v426.666666a42.666667 42.666667 0 0 0 21.034666 36.778667l362.666667 213.333333a42.666667 42.666667 0 0 0 43.264 0l362.666667-213.333333A42.666667 42.666667 0 0 0 917.333333 725.333333V298.666667a42.666667 42.666667 0 0 0-21.034666-36.778667l-362.666667-213.333333z m185.130667 334.08a42.666667 42.666667 0 0 1-13.568 58.837333L554.666667 535.552V704a42.666667 42.666667 0 1 1-85.333334 0v-168.277333l-150.613333-94.293334a42.666667 42.666667 0 0 1 45.226667-72.32l147.925333 92.586667 148.053333-92.586667a42.666667 42.666667 0 0 1 58.837334 13.568z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-user': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 24 24',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M15 13H9C6.23858 13 3 14.9314 3 18.4V21.1C3 21.597 3.44772 22 4 22H20C20.5523 22 21 21.597 21 21.1V18.4C21 14.9285 17.7614 13 15 13Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M7 6.99997C7 9.76139 9.23858 12 12 12C14.7614 12 17 9.76139 17 6.99997C17 4.23855 14.7614 1.99997 12 1.99997C9.23858 1.99997 7 4.23855 7 6.99997Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-question': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 24 24',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M12.7071 22.2009L17 18.5111H21.5C22.0523 18.5111 22.5 18.0539 22.5 17.4899V2.52112C22.5 1.95715 22.0523 1.49997 21.5 1.49997H2C1.44772 1.49997 1 1.95715 1 2.52112V17.4899C1 18.0539 1.44772 18.5111 2 18.5111H7L11.2929 22.2009C11.6834 22.5997 12.3166 22.5997 12.7071 22.2009ZM6.5 8.49997H7.5C8.05228 8.49997 8.5 8.94768 8.5 9.49997V10.5C8.5 11.0523 8.05228 11.5 7.5 11.5H6.5C5.94772 11.5 5.5 11.0523 5.5 10.5V9.49997C5.5 8.94768 5.94772 8.49997 6.5 8.49997ZM10.5 9.49997C10.5 8.94768 10.9477 8.49997 11.5 8.49997H12.5C13.0523 8.49997 13.5 8.94768 13.5 9.49997V10.5C13.5 11.0523 13.0523 11.5 12.5 11.5H11.5C10.9477 11.5 10.5 11.0523 10.5 10.5V9.49997ZM16.5 8.49997H17.5C18.0523 8.49997 18.5 8.94768 18.5 9.49997V10.5C18.5 11.0523 18.0523 11.5 17.5 11.5H16.5C15.9477 11.5 15.5 11.0523 15.5 10.5V9.49997C15.5 8.94768 15.9477 8.49997 16.5 8.49997Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-tokens': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 24 24',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M15.6 2.39996C12.288 2.39996 9.60002 5.08796 9.60002 8.39996C9.60002 9.11996 9.74402 9.79196 9.97202 10.428L2.47325 17.9267C2.42636 17.9736 2.40002 18.0372 2.40002 18.1035V21.1C2.40002 21.3761 2.62388 21.6 2.90002 21.6H4.30002C4.57617 21.6 4.80002 21.3761 4.80002 21.1V20.4H6.70003C6.97617 20.4 7.20002 20.1761 7.20002 19.9V18H8.40002L10.8 15.6H12L13.572 14.028C14.208 14.256 14.88 14.4 15.6 14.4C18.912 14.4 21.6 11.712 21.6 8.39996C21.6 5.08796 18.912 2.39996 15.6 2.39996ZM17.4 8.39996C16.404 8.39996 15.6 7.59596 15.6 6.59996C15.6 5.60396 16.404 4.79996 17.4 4.79996C18.396 4.79996 19.2 5.60396 19.2 6.59996C19.2 7.59596 18.396 8.39996 17.4 8.39996Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-user-stars': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 24 24',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M12 23C18.0751 23 23 18.0751 23 12C23 5.92484 18.0751 0.999969 12 0.999969C5.92487 0.999969 1 5.92484 1 12C1 18.0751 5.92487 23 12 23ZM8.5 10.5C7.67157 10.5 7 9.8284 7 8.99997C7 8.17154 7.67157 7.49997 8.5 7.49997C9.32843 7.49997 10 8.17154 10 8.99997C10 9.8284 9.32843 10.5 8.5 10.5ZM17 8.99997C17 9.8284 16.3284 10.5 15.5 10.5C14.6716 10.5 14 9.8284 14 8.99997C14 8.17154 14.6716 7.49997 15.5 7.49997C16.3284 7.49997 17 8.17154 17 8.99997ZM16.9779 13.4994C16.7521 16.0264 14.8169 18 12 18C9.18312 18 7.24789 16.0264 7.02213 13.4994C6.99756 13.2244 7.22386 13 7.5 13H16.5C16.7761 13 17.0024 13.2244 16.9779 13.4994Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-like': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M2.00518 14.6608H0.666612C0.666097 14.6874 0.666707 5.33317 0.666612 5.29087H2.00518C2.00004 5.33317 1.98014 14.6874 2.00518 14.6608ZM9.70096 5.28984H12.5717C14.5687 5.28984 15.0274 7.05264 14.5687 8.37353L12.5717 13.6308C12.4029 14.2423 11.8409 14.6665 11.1995 14.6665H3.33882C3.154 14.6665 3.00418 14.5167 3.00418 14.3319V5.62448C3.00418 5.43966 3.154 5.28984 3.33882 5.28984H4.02656C4.24449 5.28984 4.44877 5.18374 4.5741 5.00545L7.35254 1.05296C7.5406 0.753754 8.04824 0.52438 8.5893 0.770777C9.40089 1.14037 10.3724 1.94718 10.3724 3.28394C10.3724 3.78809 10.1486 4.45673 9.70096 5.28984ZM12.5717 6.62841H7.46215L8.52183 4.65626C8.87422 4.00045 9.03388 3.52351 9.03388 3.28394C9.03388 2.89556 8.9524 2.45627 8.25544 2.09612L5.26934 6.34402C5.14401 6.5223 4.93973 6.62841 4.72181 6.62841H4.34275V13.3279H11.1995C11.2411 13.3279 11.2734 13.3035 11.2813 13.2747L11.298 13.2142L13.3098 7.91815C13.5743 7.13902 13.3105 6.62841 12.5717 6.62841Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-like-color': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M2.00497 14.6608H2.00518C2.00511 14.6609 2.00504 14.6609 2.00497 14.6608H0.666612C0.666097 14.6874 0.666707 5.33317 0.666612 5.29087H2.00518C2.00006 5.33305 1.98026 14.6344 2.00497 14.6608Z',\n              fill: '#FFC60A',\n            }),\n            h('path', {\n              d: 'M12.5717 5.28984H9.70096C10.1486 4.45673 10.3724 3.78809 10.3724 3.28394C10.3724 1.94718 9.40089 1.14037 8.5893 0.770777C8.04824 0.52438 7.5406 0.753754 7.35254 1.05296L4.5741 5.00545C4.44877 5.18374 4.24449 5.28984 4.02656 5.28984H3.33882C3.154 5.28984 3.00418 5.43966 3.00418 5.62448V14.3319C3.00418 14.5167 3.154 14.6665 3.33882 14.6665H11.1995C11.8409 14.6665 12.4029 14.2423 12.5717 13.6308L14.5687 8.37353C15.0274 7.05264 14.5687 5.28984 12.5717 5.28984Z',\n              fill: '#FFC60A',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-oppose': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M2.00518 1.28008H0.666616C0.666616 1.33341 0.666504 10.6667 0.666616 10.65H2.00518C1.99984 10.6667 1.99984 1.33341 2.00518 1.28008ZM9.70097 10.6511H12.5717C14.5687 10.6511 15.0274 8.88828 14.5687 7.56739L12.5717 2.3101C12.4029 1.69862 11.8409 1.27441 11.1996 1.27441H3.33883C3.15401 1.27441 3.00418 1.42424 3.00418 1.60906V10.3164C3.00418 10.5013 3.15401 10.6511 3.33883 10.6511H4.02656C4.24449 10.6511 4.44877 10.7572 4.5741 10.9355L7.35254 14.888C7.5406 15.1872 8.04825 15.4165 8.58931 15.1701C9.40089 14.8005 10.3724 13.9937 10.3724 12.657C10.3724 12.1528 10.1486 11.4842 9.70097 10.6511ZM12.5717 9.31251H7.46216L8.52184 11.2847C8.87422 11.9405 9.03388 12.4174 9.03388 12.657C9.03388 13.0454 8.95241 13.4846 8.25545 13.8448L5.26935 9.5969C5.14402 9.41861 4.93974 9.31251 4.72181 9.31251H4.34275V2.61298H11.1996C11.2411 2.61298 11.2734 2.63737 11.2813 2.6662L11.298 2.72673L13.3098 8.02277C13.5743 8.8019 13.3105 9.31251 12.5717 9.31251Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-oppose-color': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M9.70106 10.7102H12.5718C14.5688 10.7102 15.0275 8.94736 14.5688 7.62647L12.5718 2.36918C12.403 1.7577 11.841 1.3335 11.1996 1.3335H3.33891C3.1541 1.3335 3.00427 1.48332 3.00427 1.66814V10.3755C3.00427 10.5603 3.1541 10.7102 3.33891 10.7102H4.02665C4.24458 10.7102 4.44886 10.8163 4.57419 10.9945L7.35263 14.947C7.54069 15.2462 8.04834 15.4756 8.58939 15.2292C9.40098 14.8596 10.3725 14.0528 10.3725 12.7161C10.3725 12.2119 10.1487 11.5433 9.70106 10.7102Z',\n              fill: '#F54A45',\n            }),\n            h('path', {\n              d: 'M2.00004 1.3335H0.661473C0.661473 1.3335 0.660982 10.7764 0.661473 10.7035H2.00001C1.99469 10.6868 1.9947 1.38674 2.00004 1.3335Z',\n              fill: '#F54A45',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-debug-outlined': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 14 14',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M2.63333 1.82346C2.81847 1.72056 3.04484 1.72611 3.22472 1.83795L10.8081 6.55299C10.9793 6.65945 11.0834 6.84677 11.0834 7.04838C11.0834 7.24999 10.9793 7.43731 10.8081 7.54376L3.22472 12.2588C3.04484 12.3707 2.81847 12.3762 2.63333 12.2733C2.44819 12.1704 2.33337 11.9752 2.33337 11.7634V2.33333C2.33337 2.12152 2.44819 1.92635 2.63333 1.82346ZM3.50004 3.38293V10.7138L9.39529 7.04838L3.50004 3.38293Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-save-outlined': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 14 14',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M1.16666 2.53734C1.16666 1.78025 1.7804 1.1665 2.53749 1.1665H11.4625C12.2196 1.1665 12.8333 1.78025 12.8333 2.53734V11.4623C12.8333 12.2194 12.2196 12.8332 11.4625 12.8332H2.53749C1.7804 12.8332 1.16666 12.2194 1.16666 11.4623V2.53734ZM2.53749 2.33317C2.42473 2.33317 2.33332 2.42458 2.33332 2.53734V11.4623C2.33332 11.5751 2.42473 11.6665 2.53749 11.6665H11.4625C11.5753 11.6665 11.6667 11.5751 11.6667 11.4623V2.53734C11.6667 2.42457 11.5753 2.33317 11.4625 2.33317H2.53749Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M3.79166 1.74984C3.79166 1.42767 4.05282 1.1665 4.37499 1.1665H9.33332C9.65549 1.1665 9.91666 1.42767 9.91666 1.74984V6.99984C9.91666 7.322 9.65549 7.58317 9.33332 7.58317H4.37499C4.05282 7.58317 3.79166 7.322 3.79166 6.99984V1.74984ZM4.95832 2.33317V6.4165H8.74999V2.33317H4.95832Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M7.58333 3.2085C7.9055 3.2085 8.16667 3.46966 8.16667 3.79183V4.9585C8.16667 5.28066 7.9055 5.54183 7.58333 5.54183C7.26117 5.54183 7 5.28066 7 4.9585V3.79183C7 3.46966 7.26117 3.2085 7.58333 3.2085Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M2.62415 1.74984C2.62415 1.42767 2.88531 1.1665 3.20748 1.1665H10.4996C10.8217 1.1665 11.0829 1.42767 11.0829 1.74984C11.0829 2.072 10.8217 2.33317 10.4996 2.33317H3.20748C2.88531 2.33317 2.62415 2.072 2.62415 1.74984Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-history-outlined': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M18.6667 10.0001C18.6667 14.6025 14.9358 18.3334 10.3334 18.3334C7.68359 18.3334 5.32266 17.0967 3.79633 15.1689L5.12054 14.1563C6.3421 15.6864 8.22325 16.6667 10.3334 16.6667C14.0153 16.6667 17 13.682 17 10.0001C17 6.31818 14.0153 3.33341 10.3334 3.33341C7.03005 3.33341 4.28786 5.73596 3.75889 8.88897H4.3469C4.70187 8.88897 4.9136 9.28459 4.7167 9.57995L3.32493 11.6676C3.14901 11.9315 2.76125 11.9315 2.58533 11.6676L1.19356 9.57995C0.996651 9.28459 1.20838 8.88897 1.56336 8.88897H2.07347C2.61669 4.8119 6.10774 1.66675 10.3334 1.66675C14.9358 1.66675 18.6667 5.39771 18.6667 10.0001Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M10.8334 9.7223V7.11119C10.8334 6.86573 10.6344 6.66675 10.3889 6.66675H9.61115C9.36569 6.66675 9.16671 6.86573 9.16671 7.11119V10.9445C9.16671 11.19 9.36569 11.389 9.61115 11.389H13.1667C13.4122 11.389 13.6112 11.19 13.6112 10.9445V10.1667C13.6112 9.92129 13.4122 9.7223 13.1667 9.7223H10.8334Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-fitview': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M128 85.333333h192a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334H170.666667v149.333333a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333V128a42.666667 42.666667 0 0 1 42.666667-42.666667z m768 853.333334h-192a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334H853.333333v-149.333333a21.333333 21.333333 0 0 1 21.333334-21.333333h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333V896a42.666667 42.666667 0 0 1-42.666667 42.666667zM85.333333 896v-192a21.333333 21.333333 0 0 1 21.333334-21.333333h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333V853.333333h149.333333a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334H128a42.666667 42.666667 0 0 1-42.666667-42.666667zM938.666667 128v192a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333V170.666667h-149.333333a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334H896a42.666667 42.666667 0 0 1 42.666667 42.666667z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M512 512m-170.666667 0a170.666667 170.666667 0 1 0 341.333334 0 170.666667 170.666667 0 1 0-341.333334 0Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-retract': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M5.44661 0.747985C5.55509 0.639506 5.73097 0.639506 5.83945 0.747985L8.00004 2.90858L10.1606 0.748004C10.2691 0.639525 10.445 0.639525 10.5534 0.748004L11.1034 1.29798C11.2119 1.40645 11.2119 1.58233 11.1034 1.69081L8.7488 4.04544L8.74644 4.04782L8.19647 4.59779C8.16892 4.62534 8.13703 4.64589 8.10299 4.65945C8.003 4.6993 7.88453 4.67875 7.80359 4.59781L7.25362 4.04784L7.25003 4.04419L4.89664 1.69079C4.78816 1.58232 4.78816 1.40644 4.89664 1.29796L5.44661 0.747985Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M1.99999 5.82774C1.63181 5.82774 1.33333 6.12622 1.33333 6.49441V9.16107C1.33333 9.52926 1.63181 9.82774 2 9.82774H14C14.3682 9.82774 14.6667 9.52926 14.6667 9.16107V6.49441C14.6667 6.12622 14.3682 5.82774 14 5.82774H1.99999ZM13.3333 7.16108V8.49441H2.66666V7.16108H13.3333Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M10.1605 14.9075C10.269 15.016 10.4449 15.016 10.5534 14.9075L11.1033 14.3575C11.2118 14.249 11.2118 14.0732 11.1033 13.9647L8.75 11.6113L8.74637 11.6076L8.1964 11.0577C8.11546 10.9767 7.99699 10.9562 7.897 10.996C7.86296 11.0096 7.83107 11.0301 7.80352 11.0577L7.25354 11.6077L7.25117 11.6101L4.89657 13.9647C4.78809 14.0731 4.78809 14.249 4.89657 14.3575L5.44654 14.9075C5.55502 15.016 5.7309 15.016 5.83938 14.9075L7.99995 12.7469L10.1605 14.9075Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-extend': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M10.5534 5.07974C10.4449 5.18822 10.269 5.18822 10.1605 5.07974L7.99992 2.91915L5.83935 5.07972C5.73087 5.1882 5.555 5.1882 5.44652 5.07972L4.89654 4.52975C4.78807 4.42127 4.78807 4.24539 4.89654 4.13691L7.25117 1.78229L7.25352 1.77991L7.80349 1.22994C7.83019 1.20324 7.86098 1.18311 7.89384 1.16955C7.99448 1.12801 8.11459 1.14813 8.19638 1.22992L8.74635 1.77989L8.74998 1.78359L11.1033 4.13693C11.2118 4.24541 11.2118 4.42129 11.1033 4.52977L10.5534 5.07974Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M5.83943 10.9202C5.73095 10.8118 5.55507 10.8118 5.44659 10.9202L4.89662 11.4702C4.78814 11.5787 4.78814 11.7546 4.89662 11.863L7.24997 14.2164L7.25359 14.2201L7.80357 14.7701C7.8862 14.8527 8.00795 14.8724 8.10922 14.8291C8.14091 14.8156 8.17059 14.7959 8.19645 14.77L8.74642 14.2201L8.74873 14.2177L11.1034 11.8631C11.2119 11.7546 11.2119 11.5787 11.1034 11.4702L10.5534 10.9202C10.4449 10.8118 10.2691 10.8118 10.1606 10.9202L8.00002 13.0808L5.83943 10.9202Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M2.00004 6C1.63185 6 1.33337 6.29848 1.33337 6.66667V9.33333C1.33337 9.70152 1.63185 10 2.00004 10H14C14.3682 10 14.6667 9.70152 14.6667 9.33333V6.66667C14.6667 6.29848 14.3682 6 14 6H2.00004ZM13.3334 7.33333V8.66667H2.66671V7.33333H13.3334Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-beautify': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M739.6864 689.92l4.2496 3.584 136.4992 135.936a34.1504 34.1504 0 0 1-43.9296 51.968l-4.1984-3.584-136.5504-135.936a34.1504 34.1504 0 0 1 43.9296-51.968zM663.4496 151.552a34.1504 34.1504 0 0 1 51.2512 30.464l-5.9392 216.6272 156.4672 146.1248a34.1504 34.1504 0 0 1-8.6528 55.808l-4.8128 1.792-202.8032 61.0816-87.4496 197.12a34.1504 34.1504 0 0 1-56.32 9.216l-3.2768-4.096-119.5008-178.432-209.9712-24.064a34.1504 34.1504 0 0 1-26.1632-50.176l2.7648-4.3008 129.28-171.7248-42.5472-212.3776a34.1504 34.1504 0 0 1 40.448-40.1408l4.6592 1.3312 198.912 72.3456z m-18.6368 89.7536l-144.5376 83.968a34.1504 34.1504 0 0 1-28.8256 2.56L314.5728 270.592l33.792 167.8848c1.4848 7.68 0.3584 15.5136-3.1744 22.3232l-3.072 4.9152-102.656 136.2944 166.4 19.1488c8.2944 0.9216 15.872 4.864 21.4016 10.9568l3.072 3.9424 93.8496 140.032 68.7104-154.7776a34.1504 34.1504 0 0 1 16.7936-17.0496l4.608-1.792 160.9216-48.4864-124.2624-116.0192a34.1504 34.1504 0 0 1-10.4448-20.0704l-0.3584-5.7856 4.6592-170.9056z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-chat-record': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M11.3333 7.33334C11.3333 6.96515 11.6318 6.66667 12 6.66667H14.6667C15.0349 6.66667 15.3333 6.96515 15.3333 7.33334V12.6667C15.3333 13.0349 15.0349 13.3333 14.6667 13.3333H13.2761L12.4714 14.1381C12.2111 14.3984 11.7889 14.3984 11.5286 14.1381L10.7239 13.3333H7.33334C6.96515 13.3333 6.66667 13.0349 6.66667 12.6667V10C6.66667 9.63182 6.96515 9.33334 7.33334 9.33334H11.3333V7.33334ZM12.6667 8.00001V10C12.6667 10.3682 12.3682 10.6667 12 10.6667H8.00001V12H11C11.1768 12 11.3464 12.0702 11.4714 12.1953L12 12.7239L12.5286 12.1953C12.6536 12.0702 12.8232 12 13 12H14V8.00001H12.6667Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M1.33334 1.33333C0.965149 1.33333 0.666672 1.63181 0.666672 1.99999V10C0.666672 10.3682 0.965149 10.6667 1.33334 10.6667H2.72386L3.86193 11.8047C4.12228 12.0651 4.54439 12.0651 4.80474 11.8047L5.94281 10.6667H12C12.3682 10.6667 12.6667 10.3682 12.6667 10V1.99999C12.6667 1.63181 12.3682 1.33333 12 1.33333H1.33334ZM4.66667 5.99999C4.66667 6.36818 4.36819 6.66666 4.00001 6.66666C3.63182 6.66666 3.33334 6.36818 3.33334 5.99999C3.33334 5.6318 3.63182 5.33333 4.00001 5.33333C4.36819 5.33333 4.66667 5.6318 4.66667 5.99999ZM7.33334 5.99999C7.33334 6.36818 7.03486 6.66666 6.66667 6.66666C6.29848 6.66666 6 6.36818 6 5.99999C6 5.6318 6.29848 5.33333 6.66667 5.33333C7.03486 5.33333 7.33334 5.6318 7.33334 5.99999ZM10 5.99999C10 6.36818 9.70153 6.66666 9.33334 6.66666C8.96515 6.66666 8.66667 6.36818 8.66667 5.99999C8.66667 5.6318 8.96515 5.33333 9.33334 5.33333C9.70153 5.33333 10 5.6318 10 5.99999Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-video-play': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m469.333333-384c0 259.2-210.133333 469.333333-469.333333 469.333333S42.666667 771.2 42.666667 512 252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M686.890667 539.776l-253.141334 159.274667a32.298667 32.298667 0 0 1-44.8-10.453334 32.896 32.896 0 0 1-4.949333-17.322666V352.768a32.64 32.64 0 0 1 32.512-32.768c6.101333 0 12.074667 1.706667 17.28 4.992l253.098667 159.232a32.853333 32.853333 0 0 1 0 55.552z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-video-pause': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M405.333333 341.333333a21.333333 21.333333 0 0 0-21.333333 21.333334v298.666666a21.333333 21.333333 0 0 0 21.333333 21.333334h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333334v-298.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-42.666667zM576 341.333333a21.333333 21.333333 0 0 0-21.333333 21.333334v298.666666a21.333333 21.333333 0 0 0 21.333333 21.333334h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333334v-298.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-42.666667z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M512 42.666667C252.8 42.666667 42.666667 252.8 42.666667 512s210.133333 469.333333 469.333333 469.333333 469.333333-210.133333 469.333333-469.333333S771.2 42.666667 512 42.666667zM128 512a384 384 0 1 1 768 0 384 384 0 0 1-768 0z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-video-stop': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M981.333333 512c0 259.2-210.133333 469.333333-469.333333 469.333333S42.666667 771.2 42.666667 512 252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333z m-85.333333 0a384 384 0 1 0-768 0 384 384 0 0 0 768 0zM384 341.333333h256c23.466667 0 42.666667 19.072 42.666667 42.666667v256c0 23.552-19.2 42.666667-42.666667 42.666667H384c-23.466667 0-42.666667-19.114667-42.666667-42.666667V384c0-23.594667 19.2-42.666667 42.666667-42.666667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-chat': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 64c247.424 0 448 200.576 448 448S759.424 960 512 960H106.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V512C64 264.576 264.576 64 512 64z m-362.666667 810.666667H512A362.666667 362.666667 0 1 0 149.333333 512v362.666667z m170.666667-298.666667h213.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-213.333333A21.333333 21.333333 0 0 1 298.666667 640v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333z m0-170.666667h384a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-384A21.333333 21.333333 0 0 1 298.666667 469.333333v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-reference-outlined': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M121.216 714.368c-7.082667-17.493333-7.466667-83.413333-7.424-104.32 0.341333-142.72 34.048-256.426667 88.32-330.112C262.4 198.229333 351.701333 161.024 460.8 172.8c7.893333 0.853333 11.946667 7.338667 10.581333 16.981333l-7.381333 51.285334c-1.749333 12.202667-9.813333 12.885333-17.621333 12.202666-138.709333-11.946667-232.576 84.053333-245.76 296.704a165.632 165.632 0 0 1 83.754666-22.528c91.050667 0 164.906667 72.96 164.906667 162.944C449.28 780.373333 375.466667 853.333333 284.373333 853.333333c-82.858667 0-151.424-60.330667-163.157333-138.965333z m438.570667 0c-7.082667-17.493333-7.509333-83.413333-7.466667-104.32 0.426667-142.72 34.090667-256.426667 88.405333-330.112 60.202667-81.706667 149.504-118.912 258.645334-107.136 7.893333 0.853333 11.946667 7.338667 10.581333 16.981333l-7.381333 51.285334c-1.749333 12.202667-9.813333 12.885333-17.621334 12.202666-138.752-11.946667-232.576 84.053333-245.76 296.704a165.632 165.632 0 0 1 83.712-22.528c91.093333 0 164.906667 72.96 164.906667 162.944 0 90.026667-73.813333 162.944-164.906667 162.944-82.773333 0-151.381333-60.330667-163.114666-138.965333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-quote': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M800.768 477.184c-14.336 0-30.72 2.048-45.056 4.096 18.432-51.2 77.824-188.416 237.568-315.392 36.864-28.672-20.48-86.016-59.392-57.344-155.648 116.736-356.352 317.44-356.352 573.44v20.48c0 122.88 100.352 223.232 223.232 223.232S1024 825.344 1024 702.464c0-124.928-100.352-225.28-223.232-225.28zM223.232 477.184c-14.336 0-30.72 2.048-45.056 4.096 18.432-51.2 77.824-188.416 237.568-315.392 36.864-28.672-20.48-86.016-59.392-57.344C200.704 225.28 0 425.984 0 681.984v20.48c0 122.88 100.352 223.232 223.232 223.232s223.232-100.352 223.232-223.232c0-124.928-100.352-225.28-223.232-225.28z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-mobile-open-history': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 21 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M3.01237 4.16663H17.179C17.4568 4.16663 17.5957 4.30551 17.5957 4.58329V5.41663C17.5957 5.6944 17.4568 5.83329 17.179 5.83329H3.01237C2.73459 5.83329 2.5957 5.6944 2.5957 5.41663V4.58329C2.5957 4.30551 2.73459 4.16663 3.01237 4.16663Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M3.01237 9.16663H17.179C17.4568 9.16663 17.5957 9.30552 17.5957 9.5833V10.4166C17.5957 10.6944 17.4568 10.8333 17.179 10.8333H3.01237C2.73459 10.8333 2.5957 10.6944 2.5957 10.4166V9.5833C2.5957 9.30552 2.73459 9.16663 3.01237 9.16663Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M3.01237 14.1667H17.179C17.4568 14.1667 17.5957 14.3056 17.5957 14.5833V15.4167C17.5957 15.6944 17.4568 15.8333 17.179 15.8333H3.01237C2.73459 15.8333 2.5957 15.6944 2.5957 15.4167V14.5833C2.5957 14.3056 2.73459 14.1667 3.01237 14.1667Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-keyboard': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M373.333333 352a53.333333 53.333333 0 1 1-106.666666 0 53.333333 53.333333 0 0 1 106.666666 0zM320 576a53.333333 53.333333 0 1 0 0-106.666667 53.333333 53.333333 0 0 0 0 106.666667zM565.333333 352a53.333333 53.333333 0 1 1-106.666666 0 53.333333 53.333333 0 0 1 106.666666 0zM512 576a53.333333 53.333333 0 1 0 0-106.666667 53.333333 53.333333 0 0 0 0 106.666667zM757.333333 352a53.333333 53.333333 0 1 1-106.666666 0 53.333333 53.333333 0 0 1 106.666666 0zM704 576a53.333333 53.333333 0 1 0 0-106.666667 53.333333 53.333333 0 0 0 0 106.666667zM362.666667 661.333333a42.666667 42.666667 0 1 0 0 85.333334h298.666666a42.666667 42.666667 0 1 0 0-85.333334h-298.666666z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M512 42.666667C252.8 42.666667 42.666667 252.8 42.666667 512s210.133333 469.333333 469.333333 469.333333 469.333333-210.133333 469.333333-469.333333S771.2 42.666667 512 42.666667zM128 512a384 384 0 1 1 768 0 384 384 0 0 1-768 0z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-pdf-export': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M3.33366 5.83342V16.6667H16.667V10.8334H18.3337V17.5001C18.3337 17.9603 17.9606 18.3334 17.5003 18.3334H2.50033C2.04009 18.3334 1.66699 17.9603 1.66699 17.5001V5.00008C1.66699 4.53984 2.04009 4.16675 2.50033 4.16675H9.16699V5.83342H3.33366Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M18.3335 2.50008V8.33342H16.6668V4.51175L11.6876 9.49091C11.6095 9.56903 11.5035 9.61291 11.393 9.61291C11.2825 9.61291 11.1766 9.56903 11.0984 9.49091L10.5093 8.90175C10.4312 8.82361 10.3873 8.71765 10.3873 8.60716C10.3873 8.49668 10.4312 8.39072 10.5093 8.31258L15.4884 3.33341H11.6668V1.66675H17.5001C17.7211 1.66675 17.9331 1.75455 18.0894 1.91083C18.2457 2.06711 18.3335 2.27907 18.3335 2.50008Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-clock': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M469.333333 320a21.333333 21.333333 0 0 1 21.333334-21.333333h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333V469.333333h149.333333a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-213.333333a21.333333 21.333333 0 0 1-21.333334-21.333334v-213.333333z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M512 981.333333c259.2 0 469.333333-210.133333 469.333333-469.333333S771.2 42.666667 512 42.666667 42.666667 252.8 42.666667 512s210.133333 469.333333 469.333333 469.333333z m0-85.333333a384 384 0 1 1 0-768 384 384 0 0 1 0 768z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-generate-star': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M384 832c-12.8 0-25.6-8.533333-29.866667-21.333333l-34.133333-119.466667c-17.066667-55.466667-55.466667-93.866667-110.933333-110.933333L85.333333 541.866667c-12.8-4.266667-21.333333-17.066667-21.333333-29.866667 0-12.8 8.533333-25.6 21.333333-29.866667l119.466667-34.133333c55.466667-17.066667 93.866667-55.466667 110.933333-110.933333L354.133333 213.333333c4.266667-12.8 17.066667-21.333333 29.866667-21.333333 12.8 0 25.6 8.533333 29.866667 21.333333l34.133333 119.466667c17.066667 55.466667 55.466667 93.866667 110.933333 110.933333l119.466667 34.133334c12.8 4.266667 21.333333 17.066667 21.333333 29.866666 0 12.8-8.533333 25.6-21.333333 29.866667l-119.466667 34.133333c-55.466667 17.066667-93.866667 55.466667-110.933333 110.933334l-34.133333 128c-4.266667 12.8-17.066667 21.333333-29.866667 21.333333z m384-384c-12.8 0-25.6-8.533333-29.866667-25.6l-12.8-42.666667c-8.533333-38.4-42.666667-72.533333-81.066666-81.066666l-42.666667-12.8c-12.8-4.266667-25.6-17.066667-25.6-29.866667 0-12.8 8.533333-25.6 25.6-29.866667l42.666667-12.8c38.4-8.533333 72.533333-42.666667 81.066666-81.066666l12.8-42.666667c4.266667-12.8 17.066667-25.6 29.866667-25.6 12.8 0 25.6 8.533333 29.866667 25.6l12.8 42.666667c8.533333 38.4 42.666667 72.533333 81.066666 81.066666l42.666667 12.8c12.8 4.266667 25.6 17.066667 25.6 29.866667 0 12.8-8.533333 25.6-25.6 29.866667l-42.666667 12.8c-38.4 8.533333-72.533333 42.666667-81.066666 81.066666l-12.8 42.666667c-4.266667 17.066667-17.066667 25.6-29.866667 25.6z m-64 512c-12.8 0-25.6-8.533333-29.866667-21.333333l-17.066666-51.2c-4.266667-17.066667-21.333333-34.133333-38.4-38.4l-51.2-17.066667c-12.8-4.266667-21.333333-17.066667-21.333334-29.866667 0-12.8 8.533333-25.6 21.333334-29.866666l51.2-17.066667c17.066667-4.266667 34.133333-21.333333 38.4-38.4l17.066666-51.2c4.266667-12.8 17.066667-21.333333 29.866667-21.333333 12.8 0 25.6 8.533333 29.866667 21.333333l17.066666 51.2c4.266667 17.066667 21.333333 34.133333 38.4 38.4l51.2 17.066667c12.8 4.266667 21.333333 17.066667 21.333334 29.866666 0 12.8-8.533333 25.6-21.333334 29.866667l-51.2 17.066667c-17.066667 4.266667-34.133333 21.333333-38.4 38.4l-17.066666 51.2c-4.266667 12.8-17.066667 21.333333-29.866667 21.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-raisehand': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M919.466667 347.733333c0-64-53.333333-117.333333-117.333334-117.333333-12.8 0-23.466667 2.133333-34.133333 4.266667-12.8-51.2-57.6-89.6-115.2-89.6-10.666667 0-21.333333 2.133333-32 4.266666v-14.933333C620.8 70.4 567.466667 17.066667 503.466667 17.066667S386.133333 70.4 386.133333 134.4v14.933333c-10.666667-2.133333-21.333333-4.266667-32-4.266666-64 0-117.333333 53.333333-117.333333 117.333333v174.933333l-4.266667-2.133333c-53.333333-34.133333-110.933333-21.333333-151.466666 4.266667-40.533333 25.6-51.2 83.2-21.333334 121.6l232.533334 300.8c61.866667 87.466667 166.4 142.933333 283.733333 142.933333 91.733333 0 177.066667-25.6 241.066667-83.2s102.4-140.8 102.4-247.466667V347.733333zM836.266667 422.4V674.133333c0 85.333333-32 145.066667-76.8 183.466667-44.8 40.533333-108.8 61.866667-185.6 61.866667-89.6 0-168.533333-42.666667-215.466667-108.8v-2.133334l-230.4-298.666666c23.466667-14.933333 42.666667-14.933333 59.733333-4.266667 2.133333 0 2.133333 2.133333 4.266667 2.133333L260.266667 554.666667c12.8 6.4 29.866667 6.4 42.666666 0 12.8-8.533333 21.333333-21.333333 21.333334-36.266667V264.533333c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v234.666667c0 23.466667 19.2 42.666667 42.666666 42.666667s42.666667-19.2 42.666667-42.666667V134.4c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v362.666667c0 23.466667 19.2 42.666667 42.666667 42.666666s42.666667-19.2 42.666666-42.666666v-234.666667c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v236.8c0 23.466667 19.2 42.666667 42.666667 42.666667s42.666667-19.2 42.666667-42.666667V349.866667c0-17.066667 14.933333-32 32-32s32 14.933333 32 32v72.533333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-share': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512.682667 320.426667V85.76c0-5.76 2.304-11.306667 6.4-15.36a22.186667 22.186667 0 0 1 31.146666 0l421.504 418.816a32.298667 32.298667 0 0 1 0 46.08l-421.546666 418.389333a22.101333 22.101333 0 0 1-15.530667 6.357334 21.845333 21.845333 0 0 1-21.973333-21.717334v-233.642666h-43.861334c-170.197333 0-299.093333 34.176-377.386666 134.698666-6.485333 8.362667-13.525333 16.896-23.808 30.165334a11.861333 11.861333 0 0 1-7.68 4.906666c-5.888 0.768-10.496-2.346667-11.946667-9.216A355.029333 355.029333 0 0 1 42.666667 804.096C42.666667 541.269333 253.098667 320.426667 512.682667 320.426667z m0 85.589333c-168.32 0-324.352 124.714667-363.264 270.805333 87.381333-52.224 238.122667-58.026667 362.154666-58.026666H597.333333v149.248l277.632-255.829334L597.333333 234.666667v170.709333l-84.608 0.64z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/components/app-icon/icons/document.ts",
    "content": "import { h } from 'vue'\nexport default {\n  'app-document': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M13.3333 2.50016H4.16667V17.5002H15.8333V5.01641H13.75C13.6395 5.01641 13.5335 4.97251 13.4554 4.89437C13.3772 4.81623 13.3333 4.71025 13.3333 4.59975V2.50016ZM3.33333 0.833496H14.2379C14.3474 0.833465 14.4558 0.855013 14.557 0.896908C14.6582 0.938804 14.7501 1.00023 14.8275 1.07766L17.2563 3.50725C17.4124 3.66356 17.5001 3.87548 17.5 4.09641V18.3335C17.5 18.5545 17.4122 18.7665 17.2559 18.9228C17.0996 19.079 16.8877 19.1668 16.6667 19.1668H3.33333C3.11232 19.1668 2.90036 19.079 2.74408 18.9228C2.5878 18.7665 2.5 18.5545 2.5 18.3335V1.66683C2.5 1.44582 2.5878 1.23385 2.74408 1.07757C2.90036 0.921293 3.11232 0.833496 3.33333 0.833496ZM6.66667 8.3335H13.3333C13.4438 8.3335 13.5498 8.3774 13.628 8.45554C13.7061 8.53368 13.75 8.63966 13.75 8.75016V9.5835C13.75 9.694 13.7061 9.79998 13.628 9.87812C13.5498 9.95626 13.4438 10.0002 13.3333 10.0002H6.66667C6.55616 10.0002 6.45018 9.95626 6.37204 9.87812C6.2939 9.79998 6.25 9.694 6.25 9.5835V8.75016C6.25 8.63966 6.2939 8.53368 6.37204 8.45554C6.45018 8.3774 6.55616 8.3335 6.66667 8.3335ZM6.66667 12.5002H10.4167C10.4714 12.5002 10.5256 12.5109 10.5761 12.5319C10.6267 12.5528 10.6726 12.5835 10.7113 12.6222C10.75 12.6609 10.7807 12.7068 10.8016 12.7574C10.8226 12.8079 10.8333 12.8621 10.8333 12.9168V13.7502C10.8333 13.8049 10.8226 13.8591 10.8016 13.9096C10.7807 13.9602 10.75 14.0061 10.7113 14.0448C10.6726 14.0835 10.6267 14.1142 10.5761 14.1351C10.5256 14.1561 10.4714 14.1668 10.4167 14.1668H6.66667C6.55616 14.1668 6.45018 14.1229 6.37204 14.0448C6.2939 13.9667 6.25 13.8607 6.25 13.7502V12.9168C6.25 12.8063 6.2939 12.7003 6.37204 12.6222C6.45018 12.5441 6.55616 12.5002 6.66667 12.5002Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-document-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M3.3335 2.08333C3.3335 1.6231 3.70659 1.25 4.16683 1.25H12.3842C12.4959 1.25 12.603 1.29489 12.6813 1.37459L16.5473 5.30784C16.6239 5.38576 16.6668 5.49065 16.6668 5.59992V17.9167C16.6668 18.3769 16.2937 18.75 15.8335 18.75H4.16683C3.70659 18.75 3.3335 18.3769 3.3335 17.9167V2.08333Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M12.5 1.2666C12.568 1.28633 12.6306 1.32327 12.6812 1.37472L16.5472 5.30797C16.5788 5.34017 16.6047 5.37698 16.6242 5.4168H13.4459C12.9235 5.4168 12.5 4.99328 12.5 4.47085V1.2666Z',\n              fill: '#2B5FD9',\n            }),\n            h('path', {\n              d: 'M6.71305 7.72705C6.48293 7.72705 6.29639 7.9136 6.29639 8.14372V8.82554C6.29639 9.05565 6.48294 9.2422 6.71305 9.2422H13.2871C13.5172 9.2422 13.7038 9.05565 13.7038 8.82554V8.14372C13.7038 7.9136 13.5172 7.72705 13.2871 7.72705H6.71305Z',\n              fill: 'white',\n            }),\n            h('path', {\n              d: 'M6.71305 11.5149C6.48293 11.5149 6.29639 11.7015 6.29639 11.9316V12.6134C6.29639 12.8435 6.48294 13.0301 6.71305 13.0301H9.58342C9.81354 13.0301 10.0001 12.8435 10.0001 12.6134V11.9316C10.0001 11.7015 9.81354 11.5149 9.58342 11.5149H6.71305Z',\n              fill: 'white',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-document-refresh': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 170.666667a85.333333 85.333333 0 0 1 85.333333-85.333334h256a85.333333 85.333333 0 0 1 85.333334 85.333334v256a85.333333 85.333333 0 0 1-85.333334 85.333333h-256a85.333333 85.333333 0 0 1-85.333333-85.333333V170.666667z m85.333333 0v256h256V170.666667h-256zM85.333333 597.333333a85.333333 85.333333 0 0 1 85.333334-85.333333h256a85.333333 85.333333 0 0 1 85.333333 85.333333v256a85.333333 85.333333 0 0 1-85.333333 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-256z m85.333334 0v256h256v-256H170.666667zM128 298.666667a213.333333 213.333333 0 0 1 213.333333-213.333334h85.333334v85.333334H341.333333a128 128 0 0 0-128 128h57.514667a12.8 12.8 0 0 1 9.728 21.12l-100.181333 116.906666a12.8 12.8 0 0 1-19.456 0l-100.181334-116.906666A12.8 12.8 0 0 1 70.485333 298.666667H128zM896 725.333333a213.333333 213.333333 0 0 1-213.333333 213.333334h-85.333334v-85.333334h85.333334a128 128 0 0 0 128-128v-21.333333h-57.514667a12.8 12.8 0 0 1-9.728-21.12l100.181333-116.906667a12.8 12.8 0 0 1 19.456 0l100.181334 116.906667a12.8 12.8 0 0 1-9.728 21.12H896v21.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-tag': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 85.333333a42.666667 42.666667 0 0 1 30.165333 12.501334l345.045334 345.045333a119.466667 119.466667 0 0 1 0 168.448l-275.413334 275.370667a119.466667 119.466667 0 0 1-169.002666 0.042666l-344.96-344.533333A42.666667 42.666667 0 0 1 85.333333 512V128a42.666667 42.666667 0 0 1 42.666667-42.666667h384z m-17.706667 85.333334H170.666667v323.669333l332.458666 332.074667a34.133333 34.133333 0 0 0 18.773334 9.557333l5.376 0.426667a34.133333 34.133333 0 0 0 24.149333-10.026667l275.242667-275.2a34.133333 34.133333 0 0 0 0.085333-48.042667L494.293333 170.666667zM352 298.666667a53.333333 53.333333 0 1 1 0 106.666666 53.333333 53.333333 0 0 1 0-106.666666z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/components/app-icon/icons/folder.ts",
    "content": "import { h } from 'vue'\nexport default {\n  'app-folder': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M42.666667 170.666667a42.666667 42.666667 0 0 1 42.666666-42.666667h357.632a42.666667 42.666667 0 0 1 38.144 23.594667L512 213.333333h426.666667a42.666667 42.666667 0 0 1 42.666666 42.666667v597.333333a42.666667 42.666667 0 0 1-42.666666 42.666667H85.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V170.666667z',\n              fill: '#FFA53D',\n            }),\n            h('path', {\n              d: 'M42.666667 256a42.666667 42.666667 0 0 1 42.666666-42.666667h853.333334a42.666667 42.666667 0 0 1 42.666666 42.666667v597.333333a42.666667 42.666667 0 0 1-42.666666 42.666667H85.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V256z',\n              fill: '#FFC60A',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-all-menu': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M2.91683 2.0835H8.3335C8.79373 2.0835 9.16683 2.45659 9.16683 2.91683V8.3335C9.16683 8.79373 8.79373 9.16683 8.3335 9.16683H2.91683C2.45659 9.16683 2.0835 8.79373 2.0835 8.3335V2.91683C2.0835 2.45659 2.45659 2.0835 2.91683 2.0835ZM3.75016 3.75016V7.50016H7.50016V3.75016H3.75016Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M2.91683 10.8335H8.3335C8.79373 10.8335 9.16683 11.2066 9.16683 11.6668V17.0835C9.16683 17.5437 8.79373 17.9168 8.3335 17.9168H2.91683C2.45659 17.9168 2.0835 17.5437 2.0835 17.0835V11.6668C2.0835 11.2066 2.45659 10.8335 2.91683 10.8335ZM3.75016 16.2502H7.50016V12.5002H3.75016V16.2502Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M11.6668 2.0835H17.0835C17.5437 2.0835 17.9168 2.45659 17.9168 2.91683V8.3335C17.9168 8.79373 17.5437 9.16683 17.0835 9.16683H11.6668C11.2066 9.16683 10.8335 8.79373 10.8335 8.3335V2.91683C10.8335 2.45659 11.2066 2.0835 11.6668 2.0835ZM12.5002 7.50016H16.2502V3.75016H12.5002V7.50016Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M11.6668 10.8335H17.0835C17.5437 10.8335 17.9168 11.2066 17.9168 11.6668V17.0835C17.9168 17.5437 17.5437 17.9168 17.0835 17.9168H11.6668C11.2066 17.9168 10.8335 17.5437 10.8335 17.0835V11.6668C10.8335 11.2066 11.2066 10.8335 11.6668 10.8335ZM12.5002 12.5002V16.2502H16.2502V12.5002H12.5002Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-all-menu-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M8.33317 1.6665H2.49984C2.0396 1.6665 1.6665 2.0396 1.6665 2.49984V8.33317C1.6665 8.79341 2.0396 9.1665 2.49984 9.1665H8.33317C8.79341 9.1665 9.1665 8.79341 9.1665 8.33317V2.49984C9.1665 2.0396 8.79341 1.6665 8.33317 1.6665Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M8.33317 10.8332H2.49984C2.0396 10.8332 1.6665 11.2063 1.6665 11.6665V17.4998C1.6665 17.9601 2.0396 18.3332 2.49984 18.3332H8.33317C8.79341 18.3332 9.1665 17.9601 9.1665 17.4998V11.6665C9.1665 11.2063 8.79341 10.8332 8.33317 10.8332Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M17.4998 1.6665H11.6665C11.2063 1.6665 10.8332 2.0396 10.8332 2.49984V8.33317C10.8332 8.79341 11.2063 9.1665 11.6665 9.1665H17.4998C17.9601 9.1665 18.3332 8.79341 18.3332 8.33317V2.49984C18.3332 2.0396 17.9601 1.6665 17.4998 1.6665Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M17.4508 10.8332H11.7155C11.2282 10.8332 10.8332 11.2282 10.8332 11.7155V17.4508C10.8332 17.9381 11.2282 18.3332 11.7155 18.3332H17.4508C17.9381 18.3332 18.3332 17.9381 18.3332 17.4508V11.7155C18.3332 11.2282 17.9381 10.8332 17.4508 10.8332Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-add-folder': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M42.666667 170.666667a42.666667 42.666667 0 0 1 42.666666-42.666667h357.632a42.666667 42.666667 0 0 1 38.144 23.594667L512 213.333333h426.666667a42.666667 42.666667 0 0 1 42.666666 42.666667v597.333333a42.666667 42.666667 0 0 1-42.666666 42.666667H85.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V170.666667zM5.33317 8.33333C5.33317 8.14924 5.48241 8 5.6665 8H7.33317V6.33333C7.33317 6.14924 7.48241 6 7.6665 6H8.33317C8.51726 6 8.6665 6.14924 8.6665 6.33333V8H10.3332C10.5173 8 10.6665 8.14924 10.6665 8.33333V9C10.6665 9.18409 10.5173 9.33333 10.3332 9.33333H8.6665V11C8.6665 11.1841 8.51726 11.3333 8.33317 11.3333H7.6665C7.48241 11.3333 7.33317 11.1841 7.33317 11V9.33333H5.6665C5.48241 9.33333 5.33317 9.18409 5.33317 9V8.33333Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M0.666504 13.3333V2.66667C0.666504 2.29848 0.964981 2 1.33317 2H6.92115C7.17366 2 7.4045 2.14267 7.51743 2.36852L7.99984 3.33333H14.6348C15.0205 3.33333 15.3332 3.63181 15.3332 4V13.3333C15.3332 13.7015 15.0205 14 14.6348 14H1.36492C0.979194 14 0.666504 13.7015 0.666504 13.3333ZM1.99984 4.66667V12.6667H13.9998V4.66667H1.99984Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-folder-asc': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M3.98719 1.70871C4.40186 1.27104 5.13758 1.56405 5.13758 2.16672V14.3337C5.13748 14.422 5.10235 14.5066 5.03992 14.5691C4.97746 14.6315 4.89287 14.6667 4.80457 14.6667H4.19715C4.15338 14.6667 4.10966 14.6581 4.06922 14.6413C4.02897 14.6246 3.99264 14.5998 3.9618 14.5691C3.93085 14.5381 3.90629 14.5011 3.88953 14.4607C3.87285 14.4204 3.86419 14.3773 3.86414 14.3337V3.83957L2.39246 5.37277C2.33146 5.43553 2.24851 5.47242 2.16102 5.47433C2.07339 5.47614 1.98833 5.4428 1.92469 5.38254L1.43739 4.9216C1.4056 4.89149 1.38003 4.85514 1.36219 4.81516C1.34436 4.77518 1.33505 4.73196 1.33387 4.6882C1.33271 4.64453 1.33975 4.60108 1.35535 4.56027C1.37102 4.51939 1.39457 4.4817 1.42469 4.44992L3.98719 1.70871ZM15.0829 11.9997C15.2209 11.9997 15.3327 12.1118 15.3329 12.2497V13.0837C15.3327 13.2216 15.2208 13.3337 15.0829 13.3337H6.24989C6.11199 13.3336 6.00008 13.2216 5.99989 13.0837V12.2497C6.00004 12.1118 6.11196 11.9998 6.24989 11.9997H15.0829ZM13.0829 7.77805C13.221 7.77805 13.3329 7.88997 13.3329 8.02805V8.86105C13.3329 8.99912 13.221 9.11105 13.0829 9.11105H6.24989C6.11187 9.11099 5.99989 8.99909 5.99989 8.86105V8.02805C5.99989 7.89001 6.11187 7.77811 6.24989 7.77805H13.0829ZM11.0829 3.55539C11.2208 3.55539 11.3327 3.66748 11.3329 3.80539V4.63937C11.3327 4.77731 11.2209 4.88937 11.0829 4.88937H6.24989C6.11197 4.88931 6.00005 4.77727 5.99989 4.63937V3.80539C6.00007 3.66752 6.11198 3.55545 6.24989 3.55539H11.0829Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M3.98719 1.70871C4.40186 1.27104 5.13758 1.56405 5.13758 2.16672V14.3337C5.13748 14.422 5.10235 14.5066 5.03992 14.5691C4.97746 14.6315 4.89287 14.6667 4.80457 14.6667H4.19715C4.15338 14.6667 4.10966 14.6581 4.06922 14.6413C4.02897 14.6246 3.99264 14.5998 3.9618 14.5691C3.93085 14.5381 3.90629 14.5011 3.88953 14.4607C3.87285 14.4204 3.86419 14.3773 3.86414 14.3337V3.83957L2.39246 5.37277C2.33146 5.43553 2.24851 5.47242 2.16102 5.47433C2.07339 5.47614 1.98833 5.4428 1.92469 5.38254L1.43739 4.9216C1.4056 4.89149 1.38003 4.85514 1.36219 4.81516C1.34436 4.77518 1.33505 4.73196 1.33387 4.6882C1.33271 4.64453 1.33975 4.60108 1.35535 4.56027C1.37102 4.51939 1.39457 4.4817 1.42469 4.44992L3.98719 1.70871ZM15.0829 11.9997C15.2209 11.9997 15.3327 12.1118 15.3329 12.2497V13.0837C15.3327 13.2216 15.2208 13.3337 15.0829 13.3337H6.24989C6.11199 13.3336 6.00008 13.2216 5.99989 13.0837V12.2497C6.00004 12.1118 6.11196 11.9998 6.24989 11.9997H15.0829ZM13.0829 7.77805C13.221 7.77805 13.3329 7.88997 13.3329 8.02805V8.86105C13.3329 8.99912 13.221 9.11105 13.0829 9.11105H6.24989C6.11187 9.11099 5.99989 8.99909 5.99989 8.86105V8.02805C5.99989 7.89001 6.11187 7.77811 6.24989 7.77805H13.0829ZM11.0829 3.55539C11.2208 3.55539 11.3327 3.66748 11.3329 3.80539V4.63937C11.3327 4.77731 11.2209 4.88937 11.0829 4.88937H6.24989C6.11197 4.88931 6.00005 4.77727 5.99989 4.63937V3.80539C6.00007 3.66752 6.11198 3.55545 6.24989 3.55539H11.0829Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-folder-desc': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M5.13997 13.9987C5.13997 14.6014 4.40397 14.8947 3.98931 14.457L1.42664 11.7154C1.39654 11.6836 1.37299 11.6462 1.35735 11.6053C1.34171 11.5644 1.33428 11.5208 1.33549 11.477C1.3367 11.4332 1.34652 11.3901 1.36439 11.3502C1.38226 11.3102 1.40783 11.2741 1.43964 11.244L1.92331 10.7854C1.9551 10.7553 1.99252 10.7317 2.03342 10.7161C2.07432 10.7004 2.1179 10.693 2.16167 10.6942C2.20544 10.6954 2.24854 10.7052 2.28851 10.7231C2.32849 10.741 2.36455 10.7666 2.39464 10.7984L3.80664 12.3254V1.83204C3.80664 1.74363 3.84176 1.65885 3.90427 1.59633C3.96678 1.53382 4.05157 1.4987 4.13997 1.4987H4.80664C4.89505 1.4987 4.97983 1.53382 5.04234 1.59633C5.10485 1.65885 5.13997 1.74363 5.13997 1.83204V13.9987ZM6 2.92793C6 2.78986 6.11193 2.67793 6.25 2.67793H15.0833C15.2214 2.67793 15.3333 2.78986 15.3333 2.92793V3.76127C15.3333 3.89934 15.2214 4.01127 15.0833 4.01127H6.25C6.11193 4.01127 6 3.89934 6 3.76127V2.92793ZM6 7.148C6 7.00993 6.11193 6.898 6.25 6.898H13.0833C13.2214 6.898 13.3333 7.00993 13.3333 7.148V7.98133C13.3333 8.1194 13.2214 8.23133 13.0833 8.23133H6.25C6.11193 8.23133 6 8.1194 6 7.98133V7.148ZM6.25 11.1224C6.11193 11.1224 6 11.2343 6 11.3724V12.2057C6 12.3438 6.11193 12.4557 6.25 12.4557H11.0833C11.2214 12.4557 11.3333 12.3438 11.3333 12.2057V11.3724C11.3333 11.2343 11.2214 11.1224 11.0833 11.1224H6.25Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M5.13997 13.9987C5.13997 14.6014 4.40397 14.8947 3.98931 14.457L1.42664 11.7154C1.39654 11.6836 1.37299 11.6462 1.35735 11.6053C1.34171 11.5644 1.33428 11.5208 1.33549 11.477C1.3367 11.4332 1.34652 11.3901 1.36439 11.3502C1.38226 11.3102 1.40783 11.2741 1.43964 11.244L1.92331 10.7854C1.9551 10.7553 1.99252 10.7317 2.03342 10.7161C2.07432 10.7004 2.1179 10.693 2.16167 10.6942C2.20544 10.6954 2.24854 10.7052 2.28851 10.7231C2.32849 10.741 2.36455 10.7666 2.39464 10.7984L3.80664 12.3254V1.83204C3.80664 1.74363 3.84176 1.65885 3.90427 1.59633C3.96678 1.53382 4.05157 1.4987 4.13997 1.4987H4.80664C4.89505 1.4987 4.97983 1.53382 5.04234 1.59633C5.10485 1.65885 5.13997 1.74363 5.13997 1.83204V13.9987ZM6 2.92793C6 2.78986 6.11193 2.67793 6.25 2.67793H15.0833C15.2214 2.67793 15.3333 2.78986 15.3333 2.92793V3.76127C15.3333 3.89934 15.2214 4.01127 15.0833 4.01127H6.25C6.11193 4.01127 6 3.89934 6 3.76127V2.92793ZM6 7.148C6 7.00993 6.11193 6.898 6.25 6.898H13.0833C13.2214 6.898 13.3333 7.00993 13.3333 7.148V7.98133C13.3333 8.1194 13.2214 8.23133 13.0833 8.23133H6.25C6.11193 8.23133 6 8.1194 6 7.98133V7.148ZM6.25 11.1224C6.11193 11.1224 6 11.2343 6 11.3724V12.2057C6 12.3438 6.11193 12.4557 6.25 12.4557H11.0833C11.2214 12.4557 11.3333 12.3438 11.3333 12.2057V11.3724C11.3333 11.2343 11.2214 11.1224 11.0833 11.1224H6.25Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-folder-custom': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M10.6667 11.6667C10.7551 11.6667 10.8399 11.7018 10.9024 11.7643C10.9649 11.8268 11 11.9116 11 12V13.3333C11 13.3771 10.9914 13.4204 10.9746 13.4609C10.9579 13.5013 10.9333 13.5381 10.9024 13.569C10.8714 13.6 10.8347 13.6245 10.7942 13.6413C10.7538 13.658 10.7104 13.6667 10.6667 13.6667H9.33333C9.28956 13.6667 9.24621 13.658 9.20577 13.6413C9.16533 13.6245 9.12858 13.6 9.09763 13.569C9.06668 13.5381 9.04213 13.5013 9.02537 13.4609C9.00862 13.4204 9 13.3771 9 13.3333V12C9 11.9562 9.00862 11.9129 9.02537 11.8724C9.04213 11.832 9.06668 11.7952 9.09763 11.7643C9.12858 11.7333 9.16533 11.7088 9.20577 11.692C9.24621 11.6753 9.28956 11.6667 9.33333 11.6667H10.6667ZM6.66667 11.6667C6.75507 11.6667 6.83986 11.7018 6.90237 11.7643C6.96488 11.8268 7 11.9116 7 12V13.3333C7 13.3771 6.99138 13.4204 6.97463 13.4609C6.95787 13.5013 6.93332 13.5381 6.90237 13.569C6.87142 13.6 6.83467 13.6245 6.79423 13.6413C6.75379 13.658 6.71044 13.6667 6.66667 13.6667H5.33333C5.28956 13.6667 5.24621 13.658 5.20577 13.6413C5.16533 13.6245 5.12858 13.6 5.09763 13.569C5.06668 13.5381 5.04213 13.5013 5.02537 13.4609C5.00862 13.4204 5 13.3771 5 13.3333V12C5 11.9116 5.03512 11.8268 5.09763 11.7643C5.16014 11.7018 5.24493 11.6667 5.33333 11.6667H6.66667ZM9.33333 7H10.6667C10.7551 7 10.8399 7.03511 10.9024 7.09763C10.9649 7.16014 11 7.24492 11 7.33333V8.66666C11 8.71044 10.9914 8.75378 10.9746 8.79422C10.9579 8.83467 10.9333 8.87141 10.9024 8.90236C10.8714 8.93332 10.8347 8.95787 10.7942 8.97462C10.7538 8.99137 10.7104 9 10.6667 9H9.33333C9.28956 9 9.24621 8.99137 9.20577 8.97462C9.16533 8.95787 9.12858 8.93332 9.09763 8.90236C9.06668 8.87141 9.04213 8.83467 9.02537 8.79422C9.00862 8.75378 9 8.71044 9 8.66666V7.33333C9 7.28955 9.00862 7.24621 9.02537 7.20577C9.04213 7.16533 9.06668 7.12858 9.09763 7.09763C9.12858 7.06667 9.16533 7.04212 9.20577 7.02537C9.24621 7.00862 9.28956 7 9.33333 7V7ZM5.33333 7H6.66667C6.75507 7 6.83986 7.03511 6.90237 7.09763C6.96488 7.16014 7 7.24492 7 7.33333V8.66666C7 8.71044 6.99138 8.75378 6.97463 8.79422C6.95787 8.83467 6.93332 8.87141 6.90237 8.90236C6.87142 8.93332 6.83467 8.95787 6.79423 8.97462C6.75379 8.99137 6.71044 9 6.66667 9H5.33333C5.28956 9 5.24621 8.99137 5.20577 8.97462C5.16533 8.95787 5.12858 8.93332 5.09763 8.90236C5.06668 8.87141 5.04213 8.83467 5.02537 8.79422C5.00862 8.75378 5 8.71044 5 8.66666V7.33333C5 7.24492 5.03512 7.16014 5.09763 7.09763C5.16014 7.03511 5.24493 7 5.33333 7V7ZM10.6667 2.33333C10.7104 2.33333 10.7538 2.34195 10.7942 2.3587C10.8347 2.37545 10.8714 2.40001 10.9024 2.43096C10.9333 2.46191 10.9579 2.49866 10.9746 2.5391C10.9914 2.57954 11 2.62289 11 2.66666V4C11 4.0884 10.9649 4.17319 10.9024 4.2357C10.8399 4.29821 10.7551 4.33333 10.6667 4.33333H9.33333C9.28956 4.33333 9.24621 4.32471 9.20577 4.30796C9.16533 4.2912 9.12858 4.26665 9.09763 4.2357C9.06668 4.20474 9.04213 4.168 9.02537 4.12756C9.00862 4.08711 9 4.04377 9 4V2.66666C9 2.62289 9.00862 2.57954 9.02537 2.5391C9.04213 2.49866 9.06668 2.46191 9.09763 2.43096C9.12858 2.40001 9.16533 2.37545 9.20577 2.3587C9.24621 2.34195 9.28956 2.33333 9.33333 2.33333H10.6667V2.33333ZM6.66667 2.33333C6.71044 2.33333 6.75379 2.34195 6.79423 2.3587C6.83467 2.37545 6.87142 2.40001 6.90237 2.43096C6.93332 2.46191 6.95787 2.49866 6.97463 2.5391C6.99138 2.57954 7 2.62289 7 2.66666V4C7 4.0884 6.96488 4.17319 6.90237 4.2357C6.83986 4.29821 6.75507 4.33333 6.66667 4.33333H5.33333C5.28956 4.33333 5.24621 4.32471 5.20577 4.30796C5.16533 4.2912 5.12858 4.26665 5.09763 4.2357C5.06668 4.20474 5.04213 4.168 5.02537 4.12756C5.00862 4.08711 5 4.04377 5 4V2.66666C5 2.62289 5.00862 2.57954 5.02537 2.5391C5.04213 2.49866 5.06668 2.46191 5.09763 2.43096C5.12858 2.40001 5.16533 2.37545 5.20577 2.3587C5.24621 2.34195 5.28956 2.33333 5.33333 2.33333H6.66667V2.33333Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M10.6667 11.6667C10.7551 11.6667 10.8399 11.7018 10.9024 11.7643C10.9649 11.8268 11 11.9116 11 12V13.3333C11 13.3771 10.9914 13.4204 10.9746 13.4609C10.9579 13.5013 10.9333 13.5381 10.9024 13.569C10.8714 13.6 10.8347 13.6245 10.7942 13.6413C10.7538 13.658 10.7104 13.6667 10.6667 13.6667H9.33333C9.28956 13.6667 9.24621 13.658 9.20577 13.6413C9.16533 13.6245 9.12858 13.6 9.09763 13.569C9.06668 13.5381 9.04213 13.5013 9.02537 13.4609C9.00862 13.4204 9 13.3771 9 13.3333V12C9 11.9562 9.00862 11.9129 9.02537 11.8724C9.04213 11.832 9.06668 11.7952 9.09763 11.7643C9.12858 11.7333 9.16533 11.7088 9.20577 11.692C9.24621 11.6753 9.28956 11.6667 9.33333 11.6667H10.6667ZM6.66667 11.6667C6.75507 11.6667 6.83986 11.7018 6.90237 11.7643C6.96488 11.8268 7 11.9116 7 12V13.3333C7 13.3771 6.99138 13.4204 6.97463 13.4609C6.95787 13.5013 6.93332 13.5381 6.90237 13.569C6.87142 13.6 6.83467 13.6245 6.79423 13.6413C6.75379 13.658 6.71044 13.6667 6.66667 13.6667H5.33333C5.28956 13.6667 5.24621 13.658 5.20577 13.6413C5.16533 13.6245 5.12858 13.6 5.09763 13.569C5.06668 13.5381 5.04213 13.5013 5.02537 13.4609C5.00862 13.4204 5 13.3771 5 13.3333V12C5 11.9116 5.03512 11.8268 5.09763 11.7643C5.16014 11.7018 5.24493 11.6667 5.33333 11.6667H6.66667ZM9.33333 7H10.6667C10.7551 7 10.8399 7.03511 10.9024 7.09763C10.9649 7.16014 11 7.24492 11 7.33333V8.66666C11 8.71044 10.9914 8.75378 10.9746 8.79422C10.9579 8.83467 10.9333 8.87141 10.9024 8.90236C10.8714 8.93332 10.8347 8.95787 10.7942 8.97462C10.7538 8.99137 10.7104 9 10.6667 9H9.33333C9.28956 9 9.24621 8.99137 9.20577 8.97462C9.16533 8.95787 9.12858 8.93332 9.09763 8.90236C9.06668 8.87141 9.04213 8.83467 9.02537 8.79422C9.00862 8.75378 9 8.71044 9 8.66666V7.33333C9 7.28955 9.00862 7.24621 9.02537 7.20577C9.04213 7.16533 9.06668 7.12858 9.09763 7.09763C9.12858 7.06667 9.16533 7.04212 9.20577 7.02537C9.24621 7.00862 9.28956 7 9.33333 7V7ZM5.33333 7H6.66667C6.75507 7 6.83986 7.03511 6.90237 7.09763C6.96488 7.16014 7 7.24492 7 7.33333V8.66666C7 8.71044 6.99138 8.75378 6.97463 8.79422C6.95787 8.83467 6.93332 8.87141 6.90237 8.90236C6.87142 8.93332 6.83467 8.95787 6.79423 8.97462C6.75379 8.99137 6.71044 9 6.66667 9H5.33333C5.28956 9 5.24621 8.99137 5.20577 8.97462C5.16533 8.95787 5.12858 8.93332 5.09763 8.90236C5.06668 8.87141 5.04213 8.83467 5.02537 8.79422C5.00862 8.75378 5 8.71044 5 8.66666V7.33333C5 7.24492 5.03512 7.16014 5.09763 7.09763C5.16014 7.03511 5.24493 7 5.33333 7V7ZM10.6667 2.33333C10.7104 2.33333 10.7538 2.34195 10.7942 2.3587C10.8347 2.37545 10.8714 2.40001 10.9024 2.43096C10.9333 2.46191 10.9579 2.49866 10.9746 2.5391C10.9914 2.57954 11 2.62289 11 2.66666V4C11 4.0884 10.9649 4.17319 10.9024 4.2357C10.8399 4.29821 10.7551 4.33333 10.6667 4.33333H9.33333C9.28956 4.33333 9.24621 4.32471 9.20577 4.30796C9.16533 4.2912 9.12858 4.26665 9.09763 4.2357C9.06668 4.20474 9.04213 4.168 9.02537 4.12756C9.00862 4.08711 9 4.04377 9 4V2.66666C9 2.62289 9.00862 2.57954 9.02537 2.5391C9.04213 2.49866 9.06668 2.46191 9.09763 2.43096C9.12858 2.40001 9.16533 2.37545 9.20577 2.3587C9.24621 2.34195 9.28956 2.33333 9.33333 2.33333H10.6667V2.33333ZM6.66667 2.33333C6.71044 2.33333 6.75379 2.34195 6.79423 2.3587C6.83467 2.37545 6.87142 2.40001 6.90237 2.43096C6.93332 2.46191 6.95787 2.49866 6.97463 2.5391C6.99138 2.57954 7 2.62289 7 2.66666V4C7 4.0884 6.96488 4.17319 6.90237 4.2357C6.83986 4.29821 6.75507 4.33333 6.66667 4.33333H5.33333C5.28956 4.33333 5.24621 4.32471 5.20577 4.30796C5.16533 4.2912 5.12858 4.26665 5.09763 4.2357C5.06668 4.20474 5.04213 4.168 5.02537 4.12756C5.00862 4.08711 5 4.04377 5 4V2.66666C5 2.62289 5.00862 2.57954 5.02537 2.5391C5.04213 2.49866 5.06668 2.46191 5.09763 2.43096C5.12858 2.40001 5.16533 2.37545 5.20577 2.3587C5.24621 2.34195 5.28956 2.33333 5.33333 2.33333H6.66667V2.33333Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/components/app-icon/icons/knowledge.ts",
    "content": "import { h } from 'vue'\nexport default {\n  'app-vectorization': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 170.666667a85.333333 85.333333 0 0 1 85.333333-85.333334h256a85.333333 85.333333 0 0 1 85.333334 85.333334v256a85.333333 85.333333 0 0 1-85.333334 85.333333h-256a85.333333 85.333333 0 0 1-85.333333-85.333333V170.666667z m85.333333 0v256h256V170.666667h-256zM85.333333 597.333333a85.333333 85.333333 0 0 1 85.333334-85.333333h256a85.333333 85.333333 0 0 1 85.333333 85.333333v256a85.333333 85.333333 0 0 1-85.333333 85.333334H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333334v-256z m85.333334 0v256h256v-256H170.666667zM128 298.666667a213.333333 213.333333 0 0 1 213.333333-213.333334h85.333334v85.333334H341.333333a128 128 0 0 0-128 128h57.514667a12.8 12.8 0 0 1 9.728 21.12l-100.181333 116.906666a12.8 12.8 0 0 1-19.456 0l-100.181334-116.906666A12.8 12.8 0 0 1 70.485333 298.666667H128zM896 725.333333a213.333333 213.333333 0 0 1-213.333333 213.333334h-85.333334v-85.333334h85.333334a128 128 0 0 0 128-128v-21.333333h-57.514667a12.8 12.8 0 0 1-9.728-21.12l100.181333-116.906667a12.8 12.8 0 0 1 19.456 0l100.181334 116.906667a12.8 12.8 0 0 1-9.728 21.12H896v21.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-problems': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m0 85.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m-21.333333-298.666666h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333zM343.466667 396.032c0.554667-4.778667 1.109333-8.746667 1.664-11.946667 8.32-46.293333 29.397333-80.341333 63.189333-102.144 26.453333-17.28 59.008-25.941333 97.621333-25.941333 50.730667 0 92.842667 12.288 126.378667 36.864 33.578667 24.533333 50.346667 60.928 50.346667 109.141333 0 29.568-7.253333 54.485333-21.888 74.752-8.533333 12.245333-24.917333 27.946667-49.152 47.061334l-23.893334 18.773333c-13.013333 10.24-21.632 22.186667-25.898666 35.84-1.152 3.712-2.176 10.624-3.072 20.736a21.333333 21.333333 0 0 1-21.248 19.498667h-47.786667a21.333333 21.333333 0 0 1-21.248-23.296c2.773333-29.696 5.717333-48.469333 8.832-56.362667 5.845333-14.677333 20.906667-31.573333 45.141333-50.688l24.533334-19.413333c8.106667-6.144 49.749333-35.456 49.749333-61.44 0-25.941333-4.522667-35.498667-17.578667-49.749334-13.013333-14.208-42.368-18.773333-68.864-18.773333-26.026667 0-48.256 6.869333-59.136 24.405333-5.034667 8.106667-9.173333 16.768-12.117333 25.6a89.472 89.472 0 0 0-3.114667 13.098667 21.333333 21.333333 0 0 1-21.034666 17.706667H364.672a21.333333 21.333333 0 0 1-21.205333-23.722667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-hit-test': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n\n          [\n            h('path', {\n              d: 'M1.6665 9.99986C1.6665 5.3975 5.39748 1.66653 9.99984 1.66653H10.8332V3.3332H9.99984C6.31795 3.3332 3.33317 6.31797 3.33317 9.99986C3.33317 13.6818 6.31795 16.6665 9.99984 16.6665C13.6817 16.6665 16.6665 13.6818 16.6665 9.99986V9.16653H18.3332V9.99986C18.3332 14.6022 14.6022 18.3332 9.99984 18.3332C5.39748 18.3332 1.6665 14.6022 1.6665 9.99986Z',\n              fill: 'currentColor',\n              fillRule: 'evenodd',\n              clipRule: 'evenodd',\n            }),\n            h('path', {\n              d: 'M5.4165 9.99986C5.4165 7.46854 7.46852 5.41653 9.99984 5.41653H10.8332V7.0832H9.99984C8.38899 7.0832 7.08317 8.38902 7.08317 9.99986C7.08317 11.6107 8.38899 12.9165 9.99984 12.9165C11.6107 12.9165 12.9165 11.6107 12.9165 9.99986V9.16653H14.5832V9.99986C14.5832 12.5312 12.5312 14.5832 9.99984 14.5832C7.46852 14.5832 5.4165 12.5312 5.4165 9.99986Z',\n              fill: 'currentColor',\n              fillRule: 'evenodd',\n              clipRule: 'evenodd',\n            }),\n            h('path', {\n              d: 'M13.2138 6.78296C13.5394 7.10825 13.5397 7.63588 13.2144 7.96147L10.5894 10.5889C10.2641 10.9145 9.73644 10.9147 9.41085 10.5894C9.08527 10.2641 9.08502 9.73651 9.41031 9.41092L12.0353 6.7835C12.3606 6.45792 12.8882 6.45767 13.2138 6.78296Z',\n              fill: 'currentColor',\n              fillRule: 'evenodd',\n              clipRule: 'evenodd',\n            }),\n            h('path', {\n              d: 'M15.1942 1.72962C15.506 1.8584 15.7095 2.16249 15.7095 2.49986V4.29161H17.4998C17.8365 4.29161 18.1401 4.49423 18.2693 4.80516C18.3985 5.11608 18.3279 5.47421 18.0904 5.71284L15.8508 7.96276C15.6944 8.11987 15.4819 8.2082 15.2602 8.2082H12.6248C12.1645 8.2082 11.7914 7.8351 11.7914 7.37486V4.76086C11.7914 4.54046 11.8787 4.32904 12.0342 4.17287L14.2856 1.91186C14.5237 1.6728 14.8824 1.60085 15.1942 1.72962ZM13.4581 5.105V6.54153H14.9139L15.4945 5.95828H14.8761C14.4159 5.95828 14.0428 5.58518 14.0428 5.12495V4.51779L13.4581 5.105Z',\n              fill: 'currentColor',\n              fillRule: 'evenodd',\n              clipRule: 'evenodd',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-quxiaoguanlian': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M544 298.688a32 32 0 0 1 32-32h320c41.216 0 74.688 33.408 74.688 74.624V640c0 41.216-33.472 74.688-74.688 74.688h-85.312a32 32 0 1 1 0-64H896a10.688 10.688 0 0 0 10.688-10.688V341.312A10.688 10.688 0 0 0 896 330.688H576a32 32 0 0 1-32-32zM53.312 341.312c0-41.216 33.472-74.624 74.688-74.624h106.688a32 32 0 1 1 0 64H128a10.688 10.688 0 0 0-10.688 10.624V640c0 5.888 4.8 10.688 10.688 10.688h320a32 32 0 1 1 0 64H128A74.688 74.688 0 0 1 53.312 640V341.312zM282.432 100.416a32 32 0 0 1 43.84 11.392l426.624 725.312a32 32 0 0 1-55.168 32.448L271.104 144.256a32 32 0 0 1 11.328-43.84zM650.688 490.688a32 32 0 0 1 32-32H768a32 32 0 1 1 0 64h-85.312a32 32 0 0 1-32-32zM224 490.688a32 32 0 0 1 32-32h85.312a32 32 0 1 1 0 64H256a32 32 0 0 1-32-32z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-drag-outlined': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M682.666667 746.666667a21.333333 21.333333 0 0 1 21.333333 21.333333v85.333333a21.248 21.248 0 0 1-21.333333 21.333334h-85.333334a21.248 21.248 0 0 1-21.333333-21.333334v-85.333333a21.248 21.248 0 0 1 21.333333-21.333333h85.333334z m-256 0a21.333333 21.333333 0 0 1 21.333333 21.333333v85.333333a21.248 21.248 0 0 1-21.333333 21.333334H341.333333a21.290667 21.290667 0 0 1-21.333333-21.333334v-85.333333a21.333333 21.333333 0 0 1 21.333333-21.333333h85.333334z m170.666666-298.666667h85.333334a21.333333 21.333333 0 0 1 21.333333 21.333333v85.333334a21.248 21.248 0 0 1-21.333333 21.333333h-85.333334a21.248 21.248 0 0 1-21.333333-21.333333v-85.333334a21.248 21.248 0 0 1 21.333333-21.333333z m-256 0h85.333334a21.333333 21.333333 0 0 1 21.333333 21.333333v85.333334a21.248 21.248 0 0 1-21.333333 21.333333H341.333333a21.290667 21.290667 0 0 1-21.333333-21.333333v-85.333334a21.333333 21.333333 0 0 1 21.333333-21.333333z m341.333334-298.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v85.333333a21.333333 21.333333 0 0 1-21.333333 21.333333h-85.333334a21.333333 21.333333 0 0 1-21.333333-21.333333V170.666667a21.290667 21.290667 0 0 1 21.333333-21.333334h85.333334z m-256 0a21.333333 21.333333 0 0 1 21.333333 21.333334v85.333333a21.333333 21.333333 0 0 1-21.333333 21.333333H341.333333a21.333333 21.333333 0 0 1-21.333333-21.333333V170.666667a21.333333 21.333333 0 0 1 21.333333-21.333334h85.333334z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-workflow': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M163.029333 207.189333C204.586667 179.114667 252.842667 170.666667 285.056 170.666667H640a42.666667 42.666667 0 1 0 0 85.333333H285.098667c-20.096 0-50.432 5.76-74.325334 21.930667-21.546667 14.506667-40.106667 38.656-40.106666 83.754666 0 45.141333 18.645333 69.845333 40.448 84.821334 24.021333 16.512 54.272 22.528 73.984 22.528h457.173333c32.554667 0 80.170667 8.832 120.96 37.376 43.093333 30.122667 75.434667 80.341333 75.434667 154.453333 0 74.154667-32.341333 124.458667-75.306667 154.752-40.746667 28.672-88.405333 37.717333-121.088 37.717333H384a42.666667 42.666667 0 1 0 0-85.333333h358.272c19.669333 0 48.896-5.973333 71.936-22.186667 20.778667-14.634667 39.125333-39.168 39.125333-84.906666s-18.346667-70.101333-38.997333-84.608c-22.997333-16.042667-52.224-21.930667-72.064-21.930667H285.098667c-32.682667 0-80.938667-9.045333-122.368-37.546667C119.04 486.698667 85.333333 436.309333 85.333333 361.642667c0-74.794667 33.792-124.842667 77.696-154.453334z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M384 768a42.666667 42.666667 0 1 0 0 85.333333H128a42.666667 42.666667 0 1 1 0-85.333333h256zM640 256a42.666667 42.666667 0 1 0 0-85.333333h253.653333a42.666667 42.666667 0 1 1 0 85.333333H640z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M640 170.666667a42.666667 42.666667 0 1 0 0 85.333333 42.666667 42.666667 0 0 0 0-85.333333z m-128 42.666666a128 128 0 1 1 256 0 128 128 0 0 1-256 0zM384 768a42.666667 42.666667 0 1 0 0 85.333333 42.666667 42.666667 0 0 0 0-85.333333z m-128 42.666667a128 128 0 1 1 256 0 128 128 0 0 1-256 0z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-execution-record': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M682.666667 42.666667H341.333333c-26.197333 0-42.666667 16.512-42.666666 42.666666v42.666667H170.666667c-29.269333 0-42.666667 16.512-42.666667 42.666667v768c0 26.197333 13.397333 42.666667 42.666667 42.666666h682.666666c29.269333 0 42.666667-16.512 42.666667-42.666666V170.666667c0-26.197333-13.397333-42.666667-42.666667-42.666667h-128v85.333333h85.333334v682.666667H213.333333V213.333333h85.333334v42.666667c0 26.154667 16.469333 42.666667 42.666666 42.666667h341.333334c26.154667 0 42.666667-16.512 42.666666-42.666667V85.333333c0-26.197333-16.512-42.666667-42.666666-42.666666zM384 213.333333V128h256v85.333333H384z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M321.024 469.333333h381.952c12.373333 0 22.357333 9.557333 22.357333 21.333334v42.666666c0 11.776-10.026667 21.333333-22.357333 21.333334H321.024A21.845333 21.845333 0 0 1 298.666667 533.333333v-42.666666c0-11.776 10.026667-21.333333 22.357333-21.333334zM702.976 640H321.024a21.845333 21.845333 0 0 0-22.357333 21.333333v42.666667c0 11.776 10.026667 21.333333 22.357333 21.333333h381.952c12.373333 0 22.357333-9.557333 22.357333-21.333333v-42.666667c0-11.776-10.026667-21.333333-22.357333-21.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-to-import-doc': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M682.666667 128H213.333333v768h597.333334V256.853333h-106.666667a21.333333 21.333333 0 0 1-21.333333-21.333333V128zM170.666667 42.666667h558.293333a42.666667 42.666667 0 0 1 30.208 12.501333l124.373333 124.373333a42.666667 42.666667 0 0 1 12.458667 30.165334V938.666667a42.666667 42.666667 0 0 1-42.666667 42.666666H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666666V85.333333a42.666667 42.666667 0 0 1 42.666667-42.666666z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M469.333333 362.666667a21.333333 21.333333 0 0 1 21.333334-21.333334h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333334V469.333333h106.666666a21.333333 21.333333 0 0 1 21.333334 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333334 21.333334H554.666667v106.666666a21.333333 21.333333 0 0 1-21.333334 21.333334h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333334V554.666667H362.666667a21.333333 21.333333 0 0 1-21.333334-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333334-21.333334H469.333333V362.666667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-template-center': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M213.333333 128h469.333334v107.52a21.333333 21.333333 0 0 0 21.333333 21.333333H810.666667V896H213.333333V128z m515.626667-85.333333H170.666667a42.666667 42.666667 0 0 0-42.666667 42.666666v853.333334a42.666667 42.666667 0 0 0 42.666667 42.666666h682.666666a42.666667 42.666667 0 0 0 42.666667-42.666666V209.749333a42.666667 42.666667 0 0 0-12.501333-30.208l-124.330667-124.373333A42.666667 42.666667 0 0 0 729.002667 42.666667zM320 341.333333a21.333333 21.333333 0 0 0-21.333333 21.333334v42.666666a21.333333 21.333333 0 0 0 21.333333 21.333334h384a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 0-21.333333-21.333334h-384z m149.333333 192a21.333333 21.333333 0 0 1 21.333334-21.333333h213.333333a21.333333 21.333333 0 0 1 21.333333 21.333333v213.333334a21.333333 21.333333 0 0 1-21.333333 21.333333h-213.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-213.333334zM320 512a21.333333 21.333333 0 0 0-21.333333 21.333333v213.333334a21.333333 21.333333 0 0 0 21.333333 21.333333h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-213.333334a21.333333 21.333333 0 0 0-21.333333-21.333333h-42.666667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/components/app-icon/icons/menu.ts",
    "content": "import { h } from 'vue'\nexport default {\n  'app-resource-authorization': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M10.354 0.484228C10.1252 0.417397 9.88209 0.417366 9.6533 0.484138L2.56643 2.55237C2.0332 2.70799 1.66663 3.19683 1.66663 3.75232V7.92864C1.66663 12.9588 4.8543 17.43 9.59603 19.076C9.85818 19.167 10.144 19.167 10.4061 19.076C15.1466 17.4299 18.3333 12.9597 18.3333 7.93073V3.75223C18.3333 3.19687 17.9669 2.7081 17.4338 2.55238L10.354 0.484228ZM3.33329 4.06476L10.0034 2.11815L16.6666 4.0646V7.93073C16.6666 12.199 13.9934 15.9986 10.001 17.4512C6.00742 15.9986 3.33329 12.1981 3.33329 7.92864V4.06476Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M10 10C8.61917 10 7.5 8.87917 7.5 7.5C7.5 6.12083 8.61917 5 10 5C11.3808 5 12.5 6.12083 12.5 7.5C12.5 8.87917 11.3808 10 10 10ZM10 8.33333C10.4604 8.33333 10.8333 7.95833 10.8333 7.5C10.8333 7.04167 10.4604 6.66667 10 6.66667C9.53958 6.66667 9.16667 7.04167 9.16667 7.5C9.16667 7.95833 9.53958 8.33333 10 8.33333Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M10.8333 14.5918C10.8333 14.8173 10.6467 15 10.4166 15H9.58329C9.35317 15 9.16663 14.8173 9.16663 14.5918L9.16663 8.7415C9.16663 8.51607 9.35317 8.33333 9.58329 8.33333H10.4166C10.6467 8.33333 10.8333 8.51607 10.8333 8.7415V14.5918Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M10.3571 12.5C10.1599 12.5 10 12.3135 10 12.0834V11.25C10 11.0199 10.1599 10.8334 10.3571 10.8334H12.1429C12.3401 10.8334 12.5 11.0199 12.5 11.25V12.0834C12.5 12.3135 12.3401 12.5 12.1429 12.5H10.3571Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-resource-authorization-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M9.65332 0.483805C9.88209 0.417057 10.1257 0.416982 10.3545 0.483805L17.4336 2.55216C17.9667 2.70789 18.333 3.197 18.333 3.75236V7.93107C18.3329 12.9599 15.1465 17.4295 10.4062 19.0756C10.1441 19.1666 9.85786 19.1666 9.5957 19.0756C4.85437 17.4295 1.66718 12.959 1.66699 7.92912V3.75236C1.66699 3.19688 2.03317 2.70779 2.56641 2.55216L9.65332 0.483805ZM10 5.00041C8.61917 5.00041 7.5 6.12124 7.5 7.50041C7.50016 8.58756 8.19605 9.51433 9.16699 9.85783V14.5922C9.16717 14.8174 9.35315 15.0002 9.58301 15.0004H10.417C10.6469 15.0002 10.8328 14.8174 10.833 14.5922V12.5004H12.1426C12.3398 12.5004 12.5 12.3135 12.5 12.0834V11.2504C12.5 11.0203 12.3398 10.8334 12.1426 10.8334H10.833V9.85783C11.8039 9.51433 12.4998 8.58756 12.5 7.50041C12.5 6.12124 11.3808 5.00041 10 5.00041ZM10 6.66642C10.4604 6.66642 10.833 7.04207 10.833 7.50041C10.8328 7.95825 10.4608 8.33289 10.001 8.33341H9.99902C9.53918 8.33288 9.16719 7.95825 9.16699 7.50041C9.16699 7.04207 9.53958 6.66642 10 6.66642Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-shared': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M10.4015 9.13532C10.3663 9.01295 10.3474 8.88365 10.3474 8.74993C10.3474 7.98287 10.9692 7.36104 11.7363 7.36104C12.5033 7.36104 13.1251 7.98287 13.1251 8.74993C13.1251 9.51699 12.5033 10.1388 11.7363 10.1388C11.3532 10.1388 11.0064 9.98377 10.7551 9.733L9.25154 10.6215C9.2868 10.7439 9.3057 10.8732 9.3057 11.0069C9.3057 11.0989 9.29675 11.1889 9.27967 11.2759L11.195 12.1952C11.4497 11.8932 11.831 11.7013 12.2571 11.7013C13.0242 11.7013 13.646 12.3231 13.646 13.0902C13.646 13.8573 13.0242 14.4791 12.2571 14.4791C11.49 14.4791 10.8682 13.8573 10.8682 13.0902C10.8682 12.9982 10.8772 12.9082 10.8942 12.8212L8.97894 11.9019C8.72417 12.2039 8.3429 12.3958 7.91681 12.3958C7.14975 12.3958 6.52792 11.7739 6.52792 11.0069C6.52792 10.2398 7.14975 9.61799 7.91681 9.61799C8.29985 9.61799 8.64667 9.77304 8.89793 10.0238L10.4015 9.13532Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M0.833344 3.33333V16.6667C0.833344 17.1269 1.22421 17.5 1.70636 17.5H18.2937C18.7758 17.5 19.1667 17.1269 19.1667 16.6667V5C19.1667 4.53976 18.7758 4.16667 18.2937 4.16667H10L9.397 2.96066C9.25584 2.67834 8.96729 2.5 8.65165 2.5H1.66668C1.20644 2.5 0.833344 2.8731 0.833344 3.33333ZM2.50001 15.8333V5.83333H17.5V15.8333H2.50001Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-shared-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M0.833334 3.33333C0.833334 2.8731 1.20643 2.5 1.66667 2.5H8.65164C8.96728 2.5 9.25583 2.67834 9.39699 2.96066L10 4.16667H18.3333C18.7936 4.16667 19.1667 4.53976 19.1667 5V16.6667C19.1667 17.1269 18.7936 17.5 18.3333 17.5H1.66667C1.20643 17.5 0.833334 17.1269 0.833334 16.6667V3.33333Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M10.5403 9.27428C10.505 9.15191 10.4861 9.02261 10.4861 8.88889C10.4861 8.12183 11.1079 7.5 11.875 7.5C12.6421 7.5 13.2639 8.12183 13.2639 8.88889C13.2639 9.65595 12.6421 10.2778 11.875 10.2778C11.492 10.2778 11.1451 10.1227 10.8939 9.87195L9.39028 10.7604C9.42555 10.8828 9.44444 11.0121 9.44444 11.1458C9.44444 11.2379 9.43549 11.3278 9.41841 11.4149L11.3337 12.3342C11.5885 12.0321 11.9697 11.8403 12.3958 11.8403C13.1629 11.8403 13.7847 12.4621 13.7847 13.2292C13.7847 13.9962 13.1629 14.6181 12.3958 14.6181C11.6288 14.6181 11.0069 13.9962 11.0069 13.2292C11.0069 13.1371 11.0159 13.0472 11.033 12.9601L9.11769 12.0408C8.86291 12.3429 8.48164 12.5347 8.05556 12.5347C7.28849 12.5347 6.66667 11.9129 6.66667 11.1458C6.66667 10.3788 7.28849 9.75694 8.05556 9.75694C8.43859 9.75694 8.78541 9.912 9.03667 10.1628L10.5403 9.27428Z',\n              fill: 'white',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-setting': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M184.704 841.941333l-13.269333-14.421333a465.536 465.536 0 0 1-101.802667-176.938667l-5.76-18.602666L151.253333 512 63.872 392.021333l5.76-18.602666a465.493333 465.493333 0 0 1 101.802667-176.938667l13.226666-14.464 146.901334 16.042667 59.648-135.936 19.114666-4.309334A462.634667 462.634667 0 0 1 512 46.506667c34.56 0 68.565333 3.797333 101.717333 11.264l19.114667 4.266666 59.648 135.978667 146.858667-16.042667 13.269333 14.506667a465.493333 465.493333 0 0 1 101.802667 176.896l5.76 18.602667L872.789333 512l87.381334 119.978667-5.76 18.602666a465.493333 465.493333 0 0 1-101.802667 176.938667l-13.226667 14.421333-146.901333-16.042666-59.648 135.978666-19.114667 4.309334a462.549333 462.549333 0 0 1-203.392 0l-19.114666-4.266667-59.648-136.021333-146.858667 16.085333z m148.693333-94.293333a63.488 63.488 0 0 1 65.024 37.632l47.786667 108.970667a386.133333 386.133333 0 0 0 131.584 0l47.786667-108.970667a63.488 63.488 0 0 1 65.066666-37.589333l117.504 12.8c28.373333-34.133333 50.773333-72.96 66.048-114.773334l-70.186666-96.341333a63.488 63.488 0 0 1 0-74.752l70.186666-96.341333a387.925333 387.925333 0 0 0-66.048-114.773334l-117.504 12.8a63.488 63.488 0 0 1-65.024-37.589333l-47.786666-109.013333a386.261333 386.261333 0 0 0-131.584 0l-47.786667 109.013333a63.488 63.488 0 0 1-65.066667 37.589333l-117.504-12.8c-28.416 34.133333-50.773333 72.96-66.048 114.773334l70.144 96.341333c16.213333 22.272 16.213333 52.48 0 74.752l-70.144 96.341333c15.274667 41.813333 37.632 80.64 66.048 114.773334l117.504-12.8zM512 705.962667c-106.752 0-193.237333-86.869333-193.237333-193.92 0-107.093333 86.485333-193.962667 193.237333-193.962667 106.709333 0 193.194667 86.869333 193.194667 193.962667 0 107.093333-86.485333 193.92-193.194667 193.92z m0-77.568c63.786667 0 115.626667-52.053333 115.626667-116.352A116.010667 116.010667 0 0 0 512 395.648 116.010667 116.010667 0 0 0 396.373333 512 116.010667 116.010667 0 0 0 512 628.352z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-setting-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M167.125333 830.208A468.864 468.864 0 0 1 64 651.776l74.666667-101.973333a64 64 0 0 0 0-75.605334L64 372.224a468.906667 468.906667 0 0 1 103.125333-178.432l125.44 13.653333a64 64 0 0 0 65.493334-37.802666l50.944-115.626667A470.613333 470.613333 0 0 1 512 42.666667c35.413333 0 69.845333 3.925333 102.997333 11.349333l50.944 115.626667a64 64 0 0 0 65.493334 37.802666l125.44-13.653333A468.821333 468.821333 0 0 1 960 372.224l-74.666667 101.973333a64 64 0 0 0 0 75.605334l74.666667 101.973333a468.778667 468.778667 0 0 1-103.125333 178.432l-125.44-13.653333a64 64 0 0 0-65.493334 37.802666l-50.944 115.626667c-33.152 7.424-67.626667 11.349333-102.997333 11.349333-35.413333 0-69.845333-3.925333-102.997333-11.349333l-50.944-115.626667a64 64 0 0 0-65.493334-37.802666l-125.44 13.653333zM512 682.666667a170.666667 170.666667 0 1 0 0-341.333334 170.666667 170.666667 0 0 0 0 341.333334z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-role': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M12.5 4.16667C11.35 4.16667 10.4167 5.09958 10.4167 6.25C10.4167 7.40042 11.35 8.33333 12.5 8.33333C13.65 8.33333 14.5833 7.40042 14.5833 6.25C14.5833 5.09958 13.65 4.16667 12.5 4.16667ZM8.75 6.25C8.75 4.17875 10.4292 2.5 12.5 2.5C14.5708 2.5 16.25 4.17875 16.25 6.25C16.25 8.32125 14.5708 10 12.5 10C10.4292 10 8.75 8.32125 8.75 6.25ZM10.2792 12.5C8.7625 12.5 7.5 13.7488 7.5 15.3333V16.6667H17.5V15.3333C17.5 13.7488 16.2375 12.5 14.7208 12.5H10.2792ZM5.83333 15.3333C5.83333 12.8479 7.825 10.8333 10.2792 10.8333H14.7208C17.175 10.8333 19.1667 12.8479 19.1667 15.3333V17.5833C19.1667 17.9975 18.8333 18.3333 18.425 18.3333H6.575C6.16667 18.3333 5.83333 17.9975 5.83333 17.5833V15.3333Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M7.08333 4.99998H2.5V16.6666H3.75C3.98012 16.6666 4.16667 16.8532 4.16667 17.0833V17.9166C4.16667 18.1468 3.98012 18.3333 3.75 18.3333H1.94036C1.25 18.3333 0.833334 17.9166 0.833334 17.0833V4.44034C0.833334 3.74998 1.25 3.33331 1.94036 3.33331H7.08333C7.31345 3.33331 7.5 3.51986 7.5 3.74998V4.58331C7.5 4.81343 7.31345 4.99998 7.08333 4.99998Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M3.66667 7.49998H7.16667C7.25507 7.49998 7.33986 7.54388 7.40237 7.62202C7.46488 7.70016 7.5 7.80614 7.5 7.91665V8.74998C7.5 8.86049 7.46488 8.96647 7.40237 9.04461C7.33986 9.12275 7.25507 9.16665 7.16667 9.16665H3.66667C3.57826 9.16665 3.49348 9.12275 3.43097 9.04461C3.36845 8.96647 3.33333 8.86049 3.33333 8.74998V7.91665C3.33333 7.80614 3.36845 7.70016 3.43097 7.62202C3.49348 7.54388 3.57826 7.49998 3.66667 7.49998Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M3.58333 9.99998H5.58333C5.64964 9.99998 5.71323 10.0439 5.76011 10.122C5.80699 10.2002 5.83333 10.3061 5.83333 10.4166V11.25C5.83333 11.3605 5.80699 11.4665 5.76011 11.5446C5.71323 11.6227 5.64964 11.6666 5.58333 11.6666H3.58333C3.51703 11.6666 3.45344 11.6227 3.40656 11.5446C3.35967 11.4665 3.33333 11.3605 3.33333 11.25V10.4166C3.33333 10.3061 3.35967 10.2002 3.40656 10.122C3.45344 10.0439 3.51703 9.99998 3.58333 9.99998Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-role-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M12.5 2.5C10.4292 2.5 8.75 4.17875 8.75 6.25C8.75 8.32125 10.4292 10 12.5 10C14.5708 10 16.25 8.32125 16.25 6.25C16.25 4.17875 14.5708 2.5 12.5 2.5Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M10.2792 10.8333C7.825 10.8333 5.83333 12.8479 5.83333 15.3333V17.5833C5.83333 17.9975 6.16667 18.3333 6.575 18.3333H18.425C18.8333 18.3333 19.1667 17.9975 19.1667 17.5833V15.3333C19.1667 12.8479 17.175 10.8333 14.7208 10.8333H10.2792Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M7.08333 4.99998H2.5V16.6666H3.75C3.98012 16.6666 4.16667 16.8532 4.16667 17.0833V17.9166C4.16667 18.1468 3.98012 18.3333 3.75 18.3333H1.94036C1.25 18.3333 0.833334 17.9166 0.833334 17.0833V4.44034C0.833334 3.74998 1.25 3.33331 1.94036 3.33331H7.08333C7.31345 3.33331 7.5 3.51986 7.5 3.74998V4.58331C7.5 4.81343 7.31345 4.99998 7.08333 4.99998Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M3.66667 7.49998H7.16667C7.25507 7.49998 7.33986 7.54388 7.40237 7.62202C7.46488 7.70016 7.5 7.80614 7.5 7.91665V8.74998C7.5 8.86049 7.46488 8.96647 7.40237 9.04461C7.33986 9.12275 7.25507 9.16665 7.16667 9.16665H3.66667C3.57826 9.16665 3.49348 9.12275 3.43097 9.04461C3.36845 8.96647 3.33333 8.86049 3.33333 8.74998V7.91665C3.33333 7.80614 3.36845 7.70016 3.43097 7.62202C3.49348 7.54388 3.57826 7.49998 3.66667 7.49998Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M3.58333 9.99998H5.58333C5.64964 9.99998 5.71323 10.0439 5.76011 10.122C5.80699 10.2002 5.83333 10.3061 5.83333 10.4166V11.25C5.83333 11.3605 5.80699 11.4665 5.76011 11.5446C5.71323 11.6227 5.64964 11.6666 5.58333 11.6666H3.58333C3.51703 11.6666 3.45344 11.6227 3.40656 11.5446C3.35967 11.4665 3.33333 11.3605 3.33333 11.25V10.4166C3.33333 10.3061 3.35967 10.2002 3.40656 10.122C3.45344 10.0439 3.51703 9.99998 3.58333 9.99998Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-workspace': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M523.477333 113.92l429.568 273.408a21.333333 21.333333 0 0 1 0 36.010667L523.52 696.704a21.333333 21.333333 0 0 1-22.912 0L70.954667 423.338667a21.333333 21.333333 0 0 1 0-36.010667l429.610666-273.365333a21.333333 21.333333 0 0 1 22.912 0zM201.6 405.333333L512 602.88l310.4-197.546667L512 207.786667 201.6 405.333333z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M110.805333 592.469333a21.333333 21.333333 0 0 0-29.354666 7.04l-22.314667 36.394667a21.333333 21.333333 0 0 0 7.04 29.312l390.613333 239.530667a84.992 84.992 0 0 0 89.088 0l390.613334-239.530667a21.333333 21.333333 0 0 0 7.04-29.312l-22.314667-36.394667a21.333333 21.333333 0 0 0-29.312-7.04L506.88 828.586667a10.666667 10.666667 0 0 1-11.136 0l-384.981333-236.074667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-workspace-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M10.2237 2.22566L18.6143 7.56512C18.8716 7.72885 18.8716 8.10444 18.6143 8.26817L10.2237 13.6076C10.0872 13.6945 9.91279 13.6945 9.7763 13.6076L1.38573 8.26817C1.12844 8.10444 1.12844 7.72885 1.38573 7.56512L9.7763 2.22566C9.91279 2.13881 10.0872 2.13881 10.2237 2.22566Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M2.1637 11.5717C1.96752 11.4515 1.71097 11.513 1.59069 11.7092L1.15509 12.4196C1.03481 12.6158 1.09633 12.8723 1.29251 12.9926L8.9218 17.6705C9.45711 17.9987 10.1262 17.9987 10.6615 17.6705L18.2908 12.9926C18.487 12.8723 18.5485 12.6158 18.4282 12.4196L17.9926 11.7092C17.8723 11.513 17.6158 11.4515 17.4196 11.5717L9.90055 16.182C9.83373 16.223 9.74957 16.223 9.68275 16.182L2.1637 11.5717Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-user-chat': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M426.666667 512a213.333333 213.333333 0 1 1 0.085333-426.752A213.333333 213.333333 0 0 1 426.666667 512z m0-85.333333a128 128 0 0 0 0-256 128 128 0 0 0 0 256z m-384 384a256 256 0 0 1 256-256h256a256 256 0 0 1 256 256v108.330666c0 23.552-19.2 42.666667-42.666667 42.666667H85.333333c-23.466667 0-42.666667-19.114667-42.666666-42.666667V810.666667z m682.666666 0a170.666667 170.666667 0 0 0-170.666666-170.666667H298.666667a170.666667 170.666667 0 0 0-170.666667 170.666667v65.664h597.333333V810.666667z m21.333334-426.666667h213.333333a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-213.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333z m128 170.666667h85.333333a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-85.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-user-chat-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M213.333333 298.666667a213.333333 213.333333 0 1 0 426.752-0.085334A213.333333 213.333333 0 0 0 213.333333 298.666667zM298.666667 554.666667a256 256 0 0 0-256 256v108.330666c0 23.552 19.2 42.666667 42.666666 42.666667h682.666667c23.466667 0 42.666667-19.114667 42.666667-42.666667V810.666667a256 256 0 0 0-256-256H298.666667zM960 384h-213.333333a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h213.333333a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333zM960 554.666667h-85.333333a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h85.333333a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-resource-management': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M1.25 3.33335C1.25 2.41288 1.99619 1.66669 2.91667 1.66669H7.91667C8.16398 1.66669 8.39852 1.77654 8.55685 1.96653L10.3903 4.16669H17.0833C18.0038 4.16669 18.75 4.91287 18.75 5.83335V9.08335C18.75 9.3595 18.5261 9.58335 18.25 9.58335H17.5833C17.3072 9.58335 17.0833 9.3595 17.0833 9.08335V5.83335H10C9.75268 5.83335 9.51814 5.7235 9.35982 5.53351L7.52635 3.33335H2.91667V16.6667H8.66667C8.94281 16.6667 9.16667 16.8905 9.16667 17.1667C9.16667 17.3889 9.16667 17.6111 9.16667 17.8334C9.16667 18.1095 8.94281 18.3334 8.66667 18.3334H2.91667C1.9962 18.3334 1.25 17.5872 1.25 16.6667V3.33335Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M10.9148 16.7795C10.9584 16.829 11.0239 16.8533 11.0895 16.8461L12.2101 16.7242C12.4809 16.6948 12.7397 16.8442 12.8496 17.0935L13.3048 18.1263C13.3315 18.1868 13.3853 18.2314 13.4501 18.2444C13.742 18.3027 14.0439 18.3334 14.353 18.3334C14.662 18.3334 14.9639 18.3027 15.2558 18.2444C15.3207 18.2314 15.3745 18.1868 15.4012 18.1263L15.8564 17.0935C15.9663 16.8442 16.225 16.6948 16.4959 16.7242L17.6164 16.8461C17.6821 16.8533 17.7475 16.829 17.7912 16.7795C18.1889 16.3281 18.4992 15.7978 18.6956 15.2151C18.7167 15.1525 18.705 15.0838 18.666 15.0305L17.9991 14.1191C17.8382 13.8993 17.8382 13.6007 17.9991 13.3809L18.666 12.4696C18.705 12.4163 18.7167 12.3475 18.6956 12.2849C18.4992 11.7022 18.1889 11.1719 17.7912 10.7206C17.7475 10.671 17.6821 10.6468 17.6164 10.6539L16.4959 10.7758C16.225 10.8053 15.9663 10.6559 15.8564 10.4065L15.4012 9.37373C15.3745 9.31319 15.3207 9.26862 15.2558 9.25565C14.9639 9.1973 14.662 9.16669 14.353 9.16669C14.0439 9.16669 13.742 9.1973 13.4501 9.25565C13.3853 9.26862 13.3315 9.31319 13.3048 9.37373L12.8496 10.4065C12.7397 10.6559 12.4809 10.8053 12.2101 10.7758L11.0895 10.6539C11.0239 10.6468 10.9584 10.671 10.9148 10.7206C10.517 11.1719 10.2067 11.7022 10.0104 12.2849C9.98927 12.3475 10.001 12.4163 10.04 12.4696L10.7069 13.3809C10.8677 13.6007 10.8677 13.8993 10.7069 14.1191L10.04 15.0305C10.001 15.0838 9.98927 15.1525 10.0104 15.2151C10.2067 15.7978 10.517 16.3281 10.9148 16.7795ZM16.0196 13.75C16.0196 14.6705 15.2735 15.4167 14.353 15.4167C13.4325 15.4167 12.6863 14.6705 12.6863 13.75C12.6863 12.8295 13.4325 12.0834 14.353 12.0834C15.2735 12.0834 16.0196 12.8295 16.0196 13.75Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-agent': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M448 533.333333a21.333333 21.333333 0 0 0-21.333333-21.333333H341.333333a21.333333 21.333333 0 0 0-21.333333 21.333333v85.333334a21.333333 21.333333 0 0 0 21.333333 21.333333h85.333334a21.333333 21.333333 0 0 0 21.333333-21.333333v-85.333334zM704 533.333333a21.333333 21.333333 0 0 0-21.333333-21.333333h-85.333334a21.333333 21.333333 0 0 0-21.333333 21.333333v85.333334a21.333333 21.333333 0 0 0 21.333333 21.333333h85.333334a21.333333 21.333333 0 0 0 21.333333-21.333333v-85.333334z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M426.666667 64a21.333333 21.333333 0 0 1 21.333333-21.333333h128a21.333333 21.333333 0 0 1 21.333333 21.333333V170.666667h-42.666666v85.333333h234.666666a85.333333 85.333333 0 0 1 85.333334 85.333333v469.333334a85.333333 85.333333 0 0 1-85.333334 85.333333h-554.666666a85.333333 85.333333 0 0 1-85.333334-85.333333V341.333333a85.333333 85.333333 0 0 1 85.333334-85.333333H469.333333V170.666667h-42.666666V64zM234.666667 341.333333v469.333334h554.666666V341.333333h-554.666666zM0 490.666667a21.333333 21.333333 0 0 1 21.333333-21.333334h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v170.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333334v-170.666666zM938.666667 490.666667a21.333333 21.333333 0 0 1 21.333333-21.333334h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v170.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333334v-170.666666z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-agent-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M597.333333 106.666667a21.333333 21.333333 0 0 0-21.333333-21.333334h-128a21.333333 21.333333 0 0 0-21.333333 21.333334V213.333333h42.666666v85.333334H234.666667a85.333333 85.333333 0 0 0-85.333334 85.333333v469.333333a85.333333 85.333333 0 0 0 85.333334 85.333334h554.666666a85.333333 85.333333 0 0 0 85.333334-85.333334V384a85.333333 85.333333 0 0 0-85.333334-85.333333H554.666667V213.333333h42.666666V106.666667z m-298.666666 469.333333a21.333333 21.333333 0 0 1 21.333333-21.333333h85.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v85.333333a21.333333 21.333333 0 0 1-21.333334 21.333334h-85.333333a21.333333 21.333333 0 0 1-21.333333-21.333334v-85.333333z m405.333333-21.333333a21.333333 21.333333 0 0 1 21.333333 21.333333v85.333333a21.333333 21.333333 0 0 1-21.333333 21.333334h-85.333333a21.333333 21.333333 0 0 1-21.333334-21.333334v-85.333333a21.333333 21.333333 0 0 1 21.333334-21.333333h85.333333zM85.333333 533.333333a21.333333 21.333333 0 0 0-21.333333-21.333333h-42.666667a21.333333 21.333333 0 0 0-21.333333 21.333333v170.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h42.666667a21.333333 21.333333 0 0 0 21.333333-21.333333v-170.666667zM938.666667 533.333333a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333v170.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-170.666667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-knowledge': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M341.333333 106.666667c69.802667 0 131.754667 33.536 170.666667 85.333333a212.992 212.992 0 0 1 170.666667-85.333333h234.666666a42.666667 42.666667 0 0 1 42.666667 42.666666V768a42.666667 42.666667 0 0 1-42.666667 42.666667H640a85.333333 85.333333 0 0 0-85.333333 85.333333 42.666667 42.666667 0 1 1-85.333334 0 85.333333 85.333333 0 0 0-85.333333-85.333333H106.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V149.333333a42.666667 42.666667 0 0 1 42.666667-42.666666H341.333333zM149.333333 725.333333H384a169.813333 169.813333 0 0 1 85.333333 22.869334V320a128 128 0 0 0-128-128H149.333333V725.333333zM682.666667 192a128 128 0 0 0-128 128v428.202667A169.813333 169.813333 0 0 1 640 725.333333h234.666667V192H682.666667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-knowledge-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M341.333333 106.666667c69.802667 0 131.754667 33.536 170.666667 85.333333a212.992 212.992 0 0 1 170.666667-85.333333h234.666666a42.666667 42.666667 0 0 1 42.666667 42.666666V768a42.666667 42.666667 0 0 1-42.666667 42.666667H640a85.333333 85.333333 0 0 0-85.333333 85.333333 42.666667 42.666667 0 1 1-85.333334 0 85.333333 85.333333 0 0 0-85.333333-85.333333H106.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V149.333333a42.666667 42.666667 0 0 1 42.666667-42.666666H341.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-tool': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M277.333333 320H170.666667v128h106.666666v-42.666667h85.333334v42.666667h298.666666v-42.666667h85.333334v42.666667H853.333333v-128H277.333333z m0-85.333333v-85.333334a42.666667 42.666667 0 0 1 42.666667-42.666666h384a42.666667 42.666667 0 0 1 42.666667 42.666666v85.333334H896a42.666667 42.666667 0 0 1 42.666667 42.666666v597.333334a42.666667 42.666667 0 0 1-42.666667 42.666666H128a42.666667 42.666667 0 0 1-42.666667-42.666666v-597.333334a42.666667 42.666667 0 0 1 42.666667-42.666666h149.333333z m85.333334 0h298.666666v-42.666667h-298.666666v42.666667z m298.666666 298.666666h-298.666666v42.666667h-85.333334v-42.666667H170.666667v298.666667h682.666666v-298.666667h-106.666666v42.666667h-85.333334v-42.666667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-tool-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M277.333333 149.333333v85.333334H128a42.666667 42.666667 0 0 0-42.666667 42.666666V426.666667h213.333334V384h85.333333v42.666667h256V384h85.333333v42.666667h213.333334V277.333333a42.666667 42.666667 0 0 0-42.666667-42.666666h-149.333333v-85.333334a42.666667 42.666667 0 0 0-42.666667-42.666666h-384a42.666667 42.666667 0 0 0-42.666667 42.666666z m384 85.333334h-298.666666v-42.666667h298.666666v42.666667z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M938.666667 512h-213.333334v42.666667h-85.333333v-42.666667H384v42.666667H298.666667v-42.666667H85.333333v362.666667a42.666667 42.666667 0 0 0 42.666667 42.666666h768a42.666667 42.666667 0 0 0 42.666667-42.666666V512z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-model': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M42.666667 326.144a42.666667 42.666667 0 0 1 25.002666-38.826667l426.666667-193.962666a42.666667 42.666667 0 0 1 35.328 0l426.666667 193.92a42.666667 42.666667 0 0 1 25.002666 38.826666v415.530667a42.666667 42.666667 0 0 1-23.594666 38.144l-426.666667 213.333333a42.666667 42.666667 0 0 1-38.144 0l-426.666667-213.333333A42.666667 42.666667 0 0 1 42.666667 741.632V326.144z m777.301333-7.082667L512 179.072 202.368 319.786667l307.925333 132.693333 309.674667-133.418667zM554.666667 526.250667v359.68l341.333333-170.666667V379.221333l-341.333333 147.029334zM128 380.672v334.592l341.333333 170.666667v-358.229334L128 380.672z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-model-active': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M0.666748 6.07278C0.666748 5.71259 1.0361 5.47056 1.36635 5.61435L6.95893 8.04931C7.14136 8.12874 7.25933 8.30877 7.25933 8.50774V14.5055C7.25933 14.8817 6.85964 15.1231 6.5267 14.9481L0.915073 11.9985C0.840862 11.9578 0.778897 11.8989 0.735342 11.8276C0.691787 11.7564 0.668161 11.6753 0.666813 11.5924L0.666748 11.585V6.07278ZM14.6312 5.60774C14.9618 5.46158 15.3334 5.70361 15.3334 6.06503V11.585C15.3334 11.6691 15.3104 11.7518 15.2668 11.8244C15.2231 11.8971 15.1604 11.9571 15.0851 11.9985L9.47345 14.9481C9.14051 15.1231 8.74081 14.8817 8.74082 14.5055L8.74083 8.53793C8.74083 8.33999 8.8576 8.16069 9.03863 8.08064L14.6312 5.60774ZM7.76 1.39457C7.83327 1.35437 7.91597 1.33325 8.00008 1.33325C8.0842 1.33325 8.16689 1.35437 8.24016 1.39457L13.55 3.75304C13.9482 3.92991 13.9454 4.49602 13.5455 4.66894L8.19851 6.98075C8.07189 7.0355 7.92827 7.0355 7.80165 6.98075L2.45469 4.66894C2.05476 4.49602 2.05196 3.92991 2.45016 3.75304L7.76 1.39457Z',\n              fill: 'currentColor',\n            }),\n\n          ],\n        ),\n      ])\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/components/app-icon/icons/system.ts",
    "content": "import { h } from 'vue'\nexport default {\n  'app-add-users': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 20 20',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M6.24984 5.41667C6.24984 6.7975 7.37067 7.91667 8.74984 7.91667C10.129 7.91667 11.2498 6.7975 11.2498 5.41667C11.2498 4.03583 10.129 2.91667 8.74984 2.91667C7.37067 2.91667 6.24984 4.03583 6.24984 5.41667ZM8.74984 1.25C11.0498 1.25 12.9165 3.11542 12.9165 5.41667C12.9165 7.71792 11.0498 9.58333 8.74984 9.58333C6.44984 9.58333 4.58317 7.71792 4.58317 5.41667C4.58317 3.11542 6.44984 1.25 8.74984 1.25ZM3.43734 15C3.37067 15.2663 3.33317 15.5454 3.33317 15.8333V16.6667H10.854C11.0841 16.6667 11.2706 16.8532 11.2706 17.0833V17.9167C11.2706 18.1468 11.0841 18.3333 10.854 18.3333H2.49984C2.0415 18.3333 1.6665 17.9604 1.6665 17.5V15.8333C1.6665 13.0721 3.904 10.8333 6.6665 10.8333H10.854C11.0841 10.8333 11.2706 11.0199 11.2706 11.25V12.0833C11.2706 12.3135 11.0841 12.5 10.854 12.5H6.6665C5.11234 12.5 3.80817 13.5625 3.43734 15ZM15.4165 11.6667C15.6466 11.6667 15.8332 11.8532 15.8332 12.0833V14.1667H17.9165C18.1466 14.1667 18.3332 14.3532 18.3332 14.5833V15.4167C18.3332 15.6468 18.1466 15.8333 17.9165 15.8333H15.8332V17.9167C15.8332 18.1468 15.6466 18.3333 15.4165 18.3333H14.5832C14.3531 18.3333 14.1665 18.1468 14.1665 17.9167V15.8333H12.0832C11.8531 15.8333 11.6665 15.6468 11.6665 15.4167V14.5833C11.6665 14.3532 11.8531 14.1667 12.0832 14.1667H14.1665V12.0833C14.1665 11.8532 14.3531 11.6667 14.5832 11.6667H15.4165Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-delete-users': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M661.333333 277.333333a213.333333 213.333333 0 1 0-426.752 0.085334A213.333333 213.333333 0 0 0 661.333333 277.333333z m-213.333333 128a128.042667 128.042667 0 0 1 0-256 128.042667 128.042667 0 0 1 0 256zM170.666667 810.666667c0-14.762667 1.92-29.013333 5.333333-42.666667 18.986667-73.6 85.76-128 165.333333-128h171.733334a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333H341.333333a256 256 0 0 0-256 256v85.333333c0 23.552 19.2 42.666667 42.666667 42.666667h385.066667a21.333333 21.333333 0 0 0 21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 0-21.333333-21.333334H170.666667v-42.666666zM776.405333 663.893333l62.634667 62.677334H618.666667a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h220.928l-63.189334 63.189333a21.333333 21.333333 0 0 0 0 30.165334l30.165334 30.208a21.333333 21.333333 0 0 0 30.165333 0l150.826667-150.869334a21.333333 21.333333 0 0 0 0-30.165333l-150.826667-150.869333a21.333333 21.333333 0 0 0-30.165333 0l-30.165334 30.208a21.333333 21.333333 0 0 0 0 30.165333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-admin-operation': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M805.290667 298.666667a170.752 170.752 0 0 1-330.581334 0H112.682667c-9.514667 0-12.970667-1.024-16.426667-2.858667a19.370667 19.370667 0 0 1-8.106667-8.106667C86.357333 284.330667 85.333333 280.832 85.333333 271.36V240.64c0-9.472 0.981333-12.928 2.858667-16.426667a19.370667 19.370667 0 0 1 8.064-8.064C99.712 214.314667 103.168 213.333333 112.64 213.333333h362.026667a170.752 170.752 0 0 1 330.581333 0h106.026667c9.514667 0 12.970667 0.981333 16.426666 2.816a19.370667 19.370667 0 0 1 8.106667 8.106667c1.834667 3.413333 2.816 6.912 2.816 16.384v30.677333c0 9.472-0.981333 12.928-2.858667 16.426667a19.370667 19.370667 0 0 1-8.064 8.064c-3.456 1.834667-6.912 2.858667-16.426666 2.858667h-106.026667zM640 341.333333a85.333333 85.333333 0 1 0 0-170.666666 85.333333 85.333333 0 0 0 0 170.666666zM549.290667 810.666667a170.752 170.752 0 0 1-330.581334 0H112.682667c-9.514667 0-12.970667-1.024-16.426667-2.858667a19.370667 19.370667 0 0 1-8.106667-8.106667c-1.834667-3.413333-2.816-6.912-2.816-16.384v-30.677333c0-9.472 0.981333-12.928 2.858667-16.426667a19.370667 19.370667 0 0 1 8.064-8.064c3.456-1.834667 6.912-2.816 16.426667-2.816h106.026666a170.752 170.752 0 0 1 330.581334 0h362.026666c9.514667 0 12.970667 0.981333 16.426667 2.816a19.370667 19.370667 0 0 1 8.106667 8.106667c1.834667 3.413333 2.816 6.912 2.816 16.384v30.634667c0 9.514667-0.981333 12.970667-2.858667 16.469333a19.370667 19.370667 0 0 1-8.064 8.064c-3.456 1.834667-6.912 2.858667-16.426667 2.858667h-362.026666zM384 853.333333a85.333333 85.333333 0 1 0 0-170.666666 85.333333 85.333333 0 0 0 0 170.666666z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-operate-log': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M213.333333 128v768h597.333334V128H213.333333zM170.666667 42.666667h682.666666c23.552 0 42.666667 20.010667 42.666667 44.714666v849.237334c0 24.704-19.114667 44.714667-42.666667 44.714666H170.666667c-23.552 0-42.666667-20.010667-42.666667-44.714666V87.381333C128 62.677333 147.114667 42.666667 170.666667 42.666667z m149.333333 256h170.666667a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-170.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333z m0 170.666666h384a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334h-384a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334z m0 170.666667h384a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-384a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-resource-mapping': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M3.64023 7.55547C4.16284 6.83981 4.62542 5.57247 5.08688 3.7308C5.35033 2.67938 5.67074 2.22214 5.94869 2.22214C6.19415 2.22214 6.39314 2.02316 6.39314 1.7777C6.39314 1.53224 6.19415 1.33325 5.94869 1.33325C5.11916 1.33325 4.57762 2.10607 4.22465 3.51476C3.8289 5.09417 3.42652 6.20022 3.07385 6.81123C2.9227 6.71944 2.74528 6.66658 2.55552 6.66658H1.88886C1.33657 6.66658 0.888855 7.1143 0.888855 7.66658V8.33325C0.888855 8.88554 1.33657 9.33325 1.88886 9.33325H2.55552C2.72067 9.33325 2.87647 9.29322 3.01375 9.22232C3.33858 9.88602 3.69875 10.9575 4.05283 12.4188C4.40498 13.8722 4.9433 14.6666 5.7777 14.6666C6.02316 14.6666 6.22214 14.4676 6.22214 14.2221C6.22214 13.9767 6.02316 13.7777 5.7777 13.7777C5.50461 13.7777 5.18098 13.3001 4.91672 12.2095C4.49159 10.455 4.06638 9.20685 3.59354 8.44436H6.46662C6.57707 8.44436 6.66662 8.35482 6.66662 8.24436V7.75547C6.66662 7.64502 6.57707 7.55547 6.46662 7.55547H3.64023Z',\n              fill: '#646A73',\n            }),\n            h('path', {\n              d: 'M7.99998 2.11103C7.99998 1.92694 8.14922 1.7777 8.33332 1.7777H14.7778C14.9619 1.7777 15.1111 1.92693 15.1111 2.11103V3.22214C15.1111 3.40624 14.9619 3.55547 14.7778 3.55547H8.33332C8.14922 3.55547 7.99998 3.40624 7.99998 3.22214V2.11103Z',\n              fill: '#646A73',\n            }),\n            h('path', {\n              d: 'M8.33332 7.11103C8.14922 7.11103 7.99998 7.26027 7.99998 7.44436V8.55547C7.99998 8.73957 8.14922 8.88881 8.33332 8.88881H14.7778C14.9619 8.88881 15.1111 8.73957 15.1111 8.55547V7.44436C15.1111 7.26027 14.9619 7.11103 14.7778 7.11103H8.33332Z',\n              fill: '#646A73',\n            }),\n            h('path', {\n              d: 'M7.99998 12.7777C7.99998 12.5936 8.14922 12.4444 8.33332 12.4444H14.7778C14.9619 12.4444 15.1111 12.5936 15.1111 12.7777V13.8888C15.1111 14.0729 14.9619 14.2221 14.7778 14.2221H8.33332C8.14922 14.2221 7.99998 14.0729 7.99998 13.8888V12.7777Z',\n              fill: '#646A73',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/components/app-icon/icons/tool.ts",
    "content": "import { h } from 'vue'\nexport default {\n  'app-tool-store': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M836.992 85.333333l6.485333 0.512a42.666667 42.666667 0 0 1 33.28 26.794667l64.042667 165.76c26.965333 69.845333 5.034667 142.634667-44.117333 188.074667 0.085333 0.938667 0.170667 1.877333 0.170666 2.858666v426.666667a42.666667 42.666667 0 0 1-42.666666 42.666667h-682.666667a42.666667 42.666667 0 0 1-42.666667-42.666667V469.333333c0-0.938667 0.042667-1.877333 0.128-2.773333C79.786667 421.12 57.856 348.330667 84.821333 278.528L148.906667 112.64l2.773333-5.888A42.666667 42.666667 0 0 1 188.672 85.333333h648.32z m-185.301333 368.725334A170.24 170.24 0 0 1 523.648 512h-21.76a170.24 170.24 0 0 1-128.085333-57.941333 170.922667 170.922667 0 0 1-159.616 55.04V853.333333h597.333333v-344.192a171.050667 171.050667 0 0 1-159.829333-55.082666z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/components/app-icon/icons/trigger.ts",
    "content": "import { h } from 'vue'\nexport default {\n  'app-schedule-report': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M840.832 55.168A42.666667 42.666667 0 0 1 853.333333 85.333333v341.333334h-85.333333V128H170.666667v768h213.333333v85.333333H128a42.666667 42.666667 0 0 1-42.666667-42.666666V85.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h682.666667a42.666667 42.666667 0 0 1 30.165333 12.501333z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M277.333333 256a21.333333 21.333333 0 0 0-21.333333 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333333 21.333333h384a21.333333 21.333333 0 0 0 21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333334-21.333333h-384zM256 448a21.333333 21.333333 0 0 1 21.333333-21.333333h170.666667a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333h-170.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667zM758.741333 734.592h78.378667a10.666667 10.666667 0 0 1 10.666667 10.666667v45.482666a10.666667 10.666667 0 0 1-10.666667 10.666667h-134.528a10.666667 10.666667 0 0 1-10.666667-10.666667V656.213333a10.666667 10.666667 0 0 1 10.666667-10.666666h45.482667a10.666667 10.666667 0 0 1 10.666666 10.666666v78.378667z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M469.333333 768a256 256 0 1 0 512 0 256 256 0 0 0-512 0z m376.661334 120.661333a170.666667 170.666667 0 1 1-241.322667-241.322666 170.666667 170.666667 0 0 1 241.322667 241.322666z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/components/app-icon/index.ts",
    "content": "import { h } from 'vue'\nconst iconsImport: any = import.meta.glob('./icons/*.ts', { eager: true, import: 'default' })\nconst dynamicIcons = Object.values(iconsImport).reduce(\n  (acc: Record<string, any>, module) => ({\n    ...acc,\n    ...(typeof module === 'object' && module !== null ? module : {}),\n  }),\n  {} as Record<string, any>,\n)\nexport const iconMap: any = {\n  'app-warning': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 234.666667A53.333333 53.333333 0 1 1 512 341.333333a53.333333 53.333333 0 0 1 0-106.666666zM522.666667 384h-64a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h21.333333v213.333334H426.666667a21.333333 21.333333 0 0 0-21.333334 21.333333v42.666667a21.333333 21.333333 0 0 0 21.333334 21.333333h192a21.333333 21.333333 0 0 0 21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 0-21.333333-21.333333h-53.333334v-256a42.666667 42.666667 0 0 0-42.666666-42.666667z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M512 981.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m0-85.333333a384 384 0 1 0 0-768 384 384 0 0 0 0 768z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-warning-colorful': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M42.666667 512c0 259.2 210.133333 469.333333 469.333333 469.333333s469.333333-210.133333 469.333333-469.333333S771.2 42.666667 512 42.666667 42.666667 252.8 42.666667 512z m469.333333-277.333333A53.333333 53.333333 0 1 1 512 341.333333a53.333333 53.333333 0 0 1 0-106.666666zM458.666667 384h64a42.666667 42.666667 0 0 1 42.666666 42.666667v256h53.333334a21.333333 21.333333 0 0 1 21.333333 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333333 21.333333H426.666667a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333h53.333333v-213.333334h-21.333333a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333z',\n              fill: '#3370FF',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-copy': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M213.333333 341.333333v512h426.666667V341.333333H213.333333z m512-42.666666v602.069333c0 20.949333-17.834667 37.930667-39.808 37.930667H167.808C145.834667 938.666667 128 921.685333 128 900.736V293.973333C128 272.981333 145.834667 256 167.808 256H682.666667a42.666667 42.666667 0 0 1 42.666666 42.666667z m158.165334-200.832A42.538667 42.538667 0 0 1 896 128v533.333333a21.333333 21.333333 0 0 1-21.333333 21.333334h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333334V170.666667H405.333333a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334H853.333333c11.776 0 22.442667 4.778667 30.165334 12.501334z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-magnify': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M366.165333 593.749333a21.333333 21.333333 0 0 1 30.208 0l30.165334 30.165334a21.333333 21.333333 0 0 1 0 30.208l-170.752 170.666666H377.173333a21.333333 21.333333 0 0 1 21.333334 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333334 21.333334H156.458667a42.538667 42.538667 0 0 1-42.666667-42.666667v-220.16a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333v113.493333l167.04-167.04z m500.992-480a42.538667 42.538667 0 0 1 42.666667 42.666667v220.16a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-113.493333l-167.04 167.04a21.333333 21.333333 0 0 1-30.165334 0l-30.165333-30.165334a21.333333 21.333333 0 0 1 0-30.165333l170.709333-170.666667h-121.344a21.333333 21.333333 0 0 1-21.333333-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h220.672z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-minify': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M384.341333 597.205333a42.538667 42.538667 0 0 1 42.666667 42.666667v220.16a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-113.493333l-167.04 167.04a21.333333 21.333333 0 0 1-30.165334 0l-30.165333-30.208a21.333333 21.333333 0 0 1 0-30.165334l170.709333-170.666666H163.669333a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334h220.672zM849.92 110.506667a21.333333 21.333333 0 0 1 30.165333 0l30.165334 30.165333a21.333333 21.333333 0 0 1 0 30.165333l-170.709334 170.666667h121.344a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-220.672a42.538667 42.538667 0 0 1-42.666666-42.666666v-220.16a21.333333 21.333333 0 0 1 21.333333-21.333334h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v113.493333l167.04-166.997333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-disabled': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 21.333333C241.024 21.333333 21.333333 241.024 21.333333 512S241.024 1002.666667 512 1002.666667 1002.666667 782.976 1002.666667 512 782.976 21.333333 512 21.333333z m297.685333 697.856L304.810667 214.314667a362.666667 362.666667 0 0 1 504.874666 504.874666zM149.333333 512c0-77.056 24.021333-148.48 64.981334-207.189333l504.874666 504.874666A362.666667 362.666667 0 0 1 149.333333 512z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-go': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M2.66671 4.66665V13.3333H13.3334V8.66665H14.6667V14C14.6667 14.3682 14.3682 14.6666 14 14.6666H2.00004C1.63185 14.6666 1.33337 14.3682 1.33337 14V3.99998C1.33337 3.63179 1.63185 3.33331 2.00004 3.33331H7.33337V4.66665H2.66671Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M14.6665 1.99998V6.66665H13.3332V3.60931L9.34987 7.59265C9.28736 7.65514 9.20259 7.69024 9.11421 7.69024C9.02582 7.69024 8.94105 7.65514 8.87854 7.59265L8.40721 7.12131C8.34472 7.0588 8.30961 6.97403 8.30961 6.88565C8.30961 6.79726 8.34472 6.71249 8.40721 6.64998L12.3905 2.66665H9.33321V1.33331H13.9999C14.1767 1.33331 14.3463 1.40355 14.4713 1.52858C14.5963 1.6536 14.6665 1.82317 14.6665 1.99998Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'right-outlined': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 12 12',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M8.13909 6L4.07322 1.93414C3.97559 1.83651 3.97559 1.67822 4.07322 1.58059L4.42678 1.22703C4.52441 1.1294 4.6827 1.1294 4.78033 1.22703L9.19975 5.64645C9.39501 5.84171 9.39501 6.15829 9.19975 6.35356L4.78033 10.773C4.6827 10.8706 4.52441 10.8706 4.42678 10.773L4.07322 10.4194C3.97559 10.3218 3.97559 10.1635 4.07322 10.0659L8.13909 6Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-migrate': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M729.002667 42.666667a42.752 42.752 0 0 1 30.165333 12.501333l124.330667 124.416a42.624 42.624 0 0 1 12.501333 30.122667V512h-85.333333V256.853333h-106.666667a21.333333 21.333333 0 0 1-21.333333-21.333333V128H213.333333v768h213.333334v85.333333H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666666V85.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h558.336z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M731.178667 603.562667a21.12 21.12 0 0 1 29.994666 0l165.077334 165.973333c16.597333 16.64 16.597333 43.690667 0 60.330667l-165.12 165.930666a21.12 21.12 0 0 1-29.952 0l-30.037334-30.165333a21.418667 21.418667 0 0 1 0-30.165333l89.856-90.325334-258.389333-1.706666a21.333333 21.333333 0 0 1-21.12-21.248l-0.170667-40.448a21.290667 21.290667 0 0 1 21.12-21.418667h0.213334l266.154666 1.749333-97.706666-98.133333a21.418667 21.418667 0 0 1 0-30.165333l30.08-30.165334z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-export': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M791.04 554.24l-386.432-1.728a21.248 21.248 0 0 1-21.12-21.248L383.36 490.88c-0.064-11.776 9.408-21.376 21.12-21.44h0.192l394.112 1.728-97.664-98.112a21.44 21.44 0 0 1 0-30.208l30.08-30.144a21.12 21.12 0 0 1 29.952 0l165.12 165.952a42.88 42.88 0 0 1 0 60.288l-165.12 165.952a21.12 21.12 0 0 1-30.016 0l-30.016-30.144a21.44 21.44 0 0 1 0-30.208L791.04 554.24z m-132.672-383.552H170.24v682.624h488.128c11.712 0 21.184 9.6 21.184 21.376v42.624a21.248 21.248 0 0 1-21.248 21.376h-530.56A42.56 42.56 0 0 1 85.376 896V128c0-23.552 19.008-42.688 42.496-42.688h530.56c11.712 0 21.184 9.6 21.184 21.376v42.624a21.248 21.248 0 0 1-21.248 21.376z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-import': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M519.381333 554.24l92.416 90.325333c8.533333 8.32 8.533333 21.845333 0 30.165334l-30.848 30.165333a22.186667 22.186667 0 0 1-30.890666 0L411.178667 569.173333l-30.890667-30.165333a41.984 41.984 0 0 1 0-60.330667l169.813333-165.973333a22.186667 22.186667 0 0 1 30.848 0l30.848 30.208c8.533333 8.32 8.533333 21.845333 0 30.165333l-100.437333 98.133334 405.376-1.706667h0.213333c12.032 0 21.76 9.642667 21.717334 21.418667l-0.170667 40.405333a21.589333 21.589333 0 0 1-21.76 21.248l-397.354667 1.706667zM674.688 170.666667H172.629333v682.666666h502.058667c12.032 0 21.802667 9.557333 21.802667 21.333334v42.666666c0 11.776-9.770667 21.333333-21.845334 21.333334H129.024A43.178667 43.178667 0 0 1 85.333333 896V128c0-23.552 19.541333-42.666667 43.648-42.666667h545.706667c12.032 0 21.802667 9.557333 21.802667 21.333334v42.666666c0 11.776-9.770667 21.333333-21.845334 21.333334z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-download': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 16 16',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M14 12.3333V14C14 14.3681 13.7015 14.6666 13.3333 14.6666H2.66667C2.29848 14.6666 2 14.3681 2 14V12.3333C2 12.1492 2.14924 12 2.33333 12H3C3.18409 12 3.33333 12.1492 3.33333 12.3333V13.3333H12.6667V12.3333C12.6667 12.1492 12.8159 12 13 12H13.6667C13.8508 12 14 12.1492 14 12.3333ZM8.66667 9.3571L10.6736 7.35013C10.8038 7.21995 11.0149 7.21995 11.1451 7.35013L11.6165 7.82153C11.7466 7.9517 11.7466 8.16276 11.6165 8.29293L8.31663 11.5928C8.25154 11.6579 8.16623 11.6904 8.08092 11.6904C7.99562 11.6904 7.91031 11.6579 7.84522 11.5928L4.54539 8.29293C4.41521 8.16276 4.41521 7.9517 4.54539 7.82153L5.01679 7.35013C5.14697 7.21995 5.35802 7.21995 5.4882 7.35013L7.33334 9.19526V1.99996C7.33334 1.81586 7.48257 1.66663 7.66667 1.66663H8.33334C8.51743 1.66663 8.66667 1.81586 8.66667 1.99996V9.3571Z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-upload': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M896 789.333333V896a42.666667 42.666667 0 0 1-42.666667 42.666667H170.666667a42.666667 42.666667 0 0 1-42.666667-42.666667v-106.666667a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333V853.333333h597.333334v-64a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333z m-341.333333-512l128.426666 128.426667a21.333333 21.333333 0 0 0 30.208 0l30.165334-30.165333a21.333333 21.333333 0 0 0 0-30.165334l-211.2-211.2a21.248 21.248 0 0 0-30.165334 0l-211.2 211.2a21.333333 21.333333 0 0 0 0 30.165334l30.165334 30.165333a21.333333 21.333333 0 0 0 30.165333 0L469.333333 287.701333v460.501334a21.333333 21.333333 0 0 0 21.333334 21.333333h42.666666a21.333333 21.333333 0 0 0 21.333334-21.333333V277.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-404': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            style: 'height:14px;width:14px',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M260.266667 789.333333c-21.333333 0-38.4-17.066667-38.4-38.4v-59.733333H38.4c-12.8 0-29.866667-8.533333-34.133333-21.333333-4.266667-17.066667-4.266667-29.866667 4.266666-42.666667l221.866667-294.4c8.533333-12.8 25.6-17.066667 42.666667-12.8 17.066667 4.266667 25.6 21.333333 25.6 38.4v256h34.133333c21.333333 0 38.4 17.066667 38.4 38.4s-17.066667 38.4-38.4 38.4H298.666667v59.733333c0 21.333333-17.066667 38.4-38.4 38.4z m-145.066667-179.2h106.666667V469.333333l-106.666667 140.8zM913.066667 742.4c-21.333333 0-38.4-17.066667-38.4-38.4v-59.733333h-183.466667c-12.8 0-29.866667-8.533333-34.133333-21.333334-8.533333-12.8-4.266667-29.866667 4.266666-38.4l221.866667-294.4c8.533333-12.8 25.6-17.066667 42.666667-12.8 17.066667 4.266667 25.6 21.333333 25.6 38.4v256h34.133333c21.333333 0 38.4 17.066667 38.4 38.4s-17.066667 38.4-38.4 38.4h-34.133333v59.733334c0 17.066667-17.066667 34.133333-38.4 34.133333zM768 567.466667h106.666667V426.666667L768 567.466667zM533.333333 597.333333c-46.933333 0-85.333333-25.6-119.466666-68.266666-29.866667-38.4-42.666667-93.866667-42.666667-145.066667 0-55.466667 17.066667-106.666667 42.666667-145.066667 29.866667-42.666667 72.533333-68.266667 119.466666-68.266666 46.933333 0 85.333333 25.6 119.466667 68.266666 29.866667 38.4 42.666667 93.866667 42.666667 145.066667 0 55.466667-17.066667 106.666667-42.666667 145.066667-34.133333 46.933333-76.8 68.266667-119.466667 68.266666z m0-362.666666c-55.466667 0-98.133333 68.266667-98.133333 149.333333s46.933333 149.333333 98.133333 149.333333c55.466667 0 98.133333-68.266667 98.133334-149.333333s-46.933333-149.333333-98.133334-149.333333z',\n              fill: '#978CFF',\n            }),\n            h('path', {\n              d: 'M354.133333 691.2a162.133333 21.333333 0 1 0 324.266667 0 162.133333 21.333333 0 1 0-324.266667 0Z',\n              fill: '#E3E5FC',\n            }),\n            h('path', {\n              d: 'M8.533333 832a162.133333 21.333333 0 1 0 324.266667 0 162.133333 21.333333 0 1 0-324.266667 0Z',\n              fill: '#E3E5FC',\n            }),\n            h('path', {\n              d: 'M661.333333 797.866667a162.133333 21.333333 0 1 0 324.266667 0 162.133333 21.333333 0 1 0-324.266667 0Z',\n              fill: '#E3E5FC',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-edit': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M524.032 239.701333l85.973333 85.973334 63.786667-63.829334-86.314667-86.784-63.445333 64.64z m25.685333 146.346667l-85.418666-85.418667-292.266667 297.984v0.128l82.56 82.56h0.170667l294.954666-295.253333z m199.68-77.226667l0.256 0.256L290.730667 768H128a42.666667 42.666667 0 0 1-42.666667-42.666667v-162.730666l443.306667-446.72-0.426667-0.426667 30.08-30.037333a42.666667 42.666667 0 0 1 60.330667 0l0.085333 0.042666 146.517334 147.328a42.666667 42.666667 0 0 1-0.085334 60.245334l-15.786666 15.786666zM106.666667 853.333333h810.666666a21.333333 21.333333 0 0 1 21.333334 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333334 21.333334h-810.666666a21.333333 21.333333 0 0 1-21.333334-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333334-21.333334z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-delete': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M341.333333 170.666667V128a42.666667 42.666667 0 0 1 42.666667-42.666667h256a42.666667 42.666667 0 0 1 42.666667 42.666667v42.666667h228.650666c9.514667 0 12.970667 0.981333 16.426667 2.858666a19.370667 19.370667 0 0 1 8.106667 8.064c1.834667 3.456 2.816 6.912 2.816 16.426667v30.634667c0 9.514667-0.981333 12.970667-2.858667 16.426666a19.370667 19.370667 0 0 1-8.064 8.106667c-3.456 1.834667-6.912 2.816-16.426667 2.816H853.333333v640a42.666667 42.666667 0 0 1-42.666666 42.666667H213.333333a42.666667 42.666667 0 0 1-42.666666-42.666667V256H112.682667c-9.514667 0-12.970667-0.981333-16.426667-2.858667a19.370667 19.370667 0 0 1-8.106667-8.064C86.357333 241.621333 85.333333 238.165333 85.333333 228.693333v-30.634666c0-9.514667 0.981333-12.970667 2.858667-16.426667a19.370667 19.370667 0 0 1 8.064-8.106667C99.712 171.690667 103.168 170.666667 112.64 170.666667H341.333333zM256 256v597.333333h512V256H256z m149.333333 85.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v384a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-384a21.333333 21.333333 0 0 1 21.333333-21.333334z m170.666667 0h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333334v384a21.333333 21.333333 0 0 1-21.333333 21.333333h-42.666667a21.333333 21.333333 0 0 1-21.333333-21.333333v-384a21.333333 21.333333 0 0 1 21.333333-21.333334z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-more': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M768 448h85.333333a21.248 21.248 0 0 1 21.333334 21.333333v85.333334a21.248 21.248 0 0 1-21.333334 21.333333h-85.333333a21.333333 21.333333 0 0 1-21.333333-21.333333v-85.333334a21.248 21.248 0 0 1 21.333333-21.333333z m-597.333333 0h85.333333a21.290667 21.290667 0 0 1 21.333333 21.333333v85.333334a21.333333 21.333333 0 0 1-21.333333 21.333333H170.666667a21.290667 21.290667 0 0 1-21.333334-21.333333v-85.333334a21.333333 21.333333 0 0 1 21.333334-21.333333z m298.666666 0h85.333334a21.248 21.248 0 0 1 21.333333 21.333333v85.333334a21.248 21.248 0 0 1-21.333333 21.333333h-85.333334a21.333333 21.333333 0 0 1-21.333333-21.333333v-85.333334a21.248 21.248 0 0 1 21.333333-21.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-key': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 512a85.333333 85.333333 0 0 1 42.666667 159.232V746.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333v-75.434667A85.333333 85.333333 0 0 1 512 512z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M512 85.333333c129.578667 0 234.666667 104.96 234.666667 234.666667V341.333333H896c23.552 0 42.666667 19.2 42.666667 42.666667v512c0 23.466667-19.114667 42.666667-42.666667 42.666667H128c-23.594667 0-42.666667-19.2-42.666667-42.666667V384c0-23.466667 19.072-42.666667 42.666667-42.666667h149.333333v-21.333333C277.333333 190.293333 382.421333 85.333333 512 85.333333zM170.666667 853.333333h682.666666V426.666667H170.666667v426.666666z m341.333333-682.666666a149.290667 149.290667 0 0 0-149.333333 149.333333V341.333333h298.666666v-21.333333C661.333333 237.44 594.474667 170.666667 512 170.666667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-sync': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M295.509333 775.893333a341.333333 341.333333 0 0 0 553.941334-315.562666l-40.149334 23.765333a21.333333 21.333333 0 0 1-31.744-22.869333l30.72-142.72a21.333333 21.333333 0 0 1 26.965334-15.957334l139.818666 41.898667a21.333333 21.333333 0 0 1 4.736 38.826667l-52.394666 30.933333c7.381333 31.402667 11.264 64.128 11.264 97.792 0 235.648-191.018667 426.666667-426.666667 426.666667a425.216 425.216 0 0 1-294.4-117.802667l77.909333-44.970667zM715.392 237.866667a341.333333 341.333333 0 0 0-542.890667 309.930666l46.805334-26.624a21.333333 21.333333 0 0 1 31.317333 23.338667L217.6 686.72a21.333333 21.333333 0 0 1-27.221333 15.488l-139.093334-44.202667a21.333333 21.333333 0 0 1-4.096-38.869333l45.866667-26.112C87.978667 566.784 85.333333 539.690667 85.333333 512 85.333333 276.352 276.352 85.333333 512 85.333333c108.373333 0 207.232 40.362667 282.453333 106.88l-79.061333 45.653334z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-generate-question': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M551.850667 369.792c9.386667 0 17.066667 7.637333 17.066666 17.066667v51.2a17.066667 17.066667 0 0 1-17.066666 17.066666h-110.933334c-6.997333 0-12.8 5.034667-14.037333 11.648L426.666667 469.333333v341.333334c0 6.997333 5.034667 12.8 11.690666 13.994666l2.56 0.213334H896c6.997333 0 12.8-4.992 13.994667-11.648l0.256-2.56v-341.333334c0-6.997333-5.034667-12.8-11.690667-13.994666l-2.56-0.213334h-110.933333a17.066667 17.066667 0 0 1-17.066667-17.066666v-51.2a17.066667 17.066667 0 0 1 17.066667-17.066667H896c53.162667 0 96.597333 41.642667 99.413333 94.08l0.170667 5.461333v341.333334a99.541333 99.541333 0 0 1-94.122667 99.413333l-5.461333 0.128H440.917333a99.541333 99.541333 0 0 1-99.413333-94.08L341.333333 810.666667v-341.333334c0-53.162667 41.642667-96.554667 94.122667-99.413333l5.461333-0.128h110.933334z m59.733333-256c53.12 0 96.554667 41.642667 99.413333 94.08l0.128 5.461333v341.333334a99.541333 99.541333 0 0 1-94.122666 99.413333l-5.418667 0.128h-110.933333a17.066667 17.066667 0 0 1-17.066667-17.066667v-51.2c0-9.386667 7.637333-17.066667 17.066667-17.066666h110.933333c6.954667 0 12.8-4.992 13.994667-11.648l0.213333-2.56V213.333333c0-6.997333-5.034667-12.8-11.690667-13.994666l-2.56-0.213334H156.501333c-6.997333 0-12.8 5.034667-13.994666 11.648L142.208 213.333333v341.333334c0 6.997333 5.034667 12.8 11.690667 13.994666l2.56 0.213334h110.933333c9.386667 0 17.066667 7.68 17.066667 17.066666v51.2a17.066667 17.066667 0 0 1-17.066667 17.066667h-110.933333a99.541333 99.541333 0 0 1-99.413334-94.08L56.874667 554.666667V213.333333c0-53.162667 41.685333-96.554667 94.122666-99.413333l5.461334-0.128h455.082666z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-lock': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M277.333333 341.333333v-21.333333C277.333333 190.293333 382.421333 85.333333 512 85.333333s234.666667 104.96 234.666667 234.666667V341.333333H896c23.552 0 42.666667 19.2 42.666667 42.666667v512c0 23.466667-19.114667 42.666667-42.666667 42.666667H128c-23.594667 0-42.666667-19.2-42.666667-42.666667V384c0-23.466667 19.072-42.666667 42.666667-42.666667h149.333333z m384-21.333333C661.333333 237.44 594.474667 170.666667 512 170.666667a149.290667 149.290667 0 0 0-149.333333 149.333333V341.333333h298.666666v-21.333333zM170.666667 426.666667v426.666666h682.666666V426.666667H170.666667z m341.333333 341.333333a128.042667 128.042667 0 0 1 0-256 128.042667 128.042667 0 0 1 0 256z m0-85.333333c23.594667 0 42.666667-19.2 42.666667-42.666667s-19.072-42.666667-42.666667-42.666667c-23.594667 0-42.666667 19.2-42.666667 42.666667s19.072 42.666667 42.666667 42.666667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-operation': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M85.333333 234.666667a149.333333 149.333333 0 0 1 292.48-42.666667H917.333333a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333H377.813333A149.418667 149.418667 0 0 1 85.333333 234.666667z m21.333334 320a21.333333 21.333333 0 0 1-21.333334-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333334-21.333334h262.186666a149.418667 149.418667 0 0 1 286.293334 0H917.333333a21.333333 21.333333 0 0 1 21.333334 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333334 21.333334h-262.186666a149.418667 149.418667 0 0 1-286.293334 0H106.666667z m405.333333 21.333333a64 64 0 1 0 0-128 64 64 0 0 0 0 128z m-405.333333 256A21.333333 21.333333 0 0 1 85.333333 810.666667v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333h539.52a149.418667 149.418667 0 0 1 292.48 42.666666 149.333333 149.333333 0 0 1-292.48 42.666667H106.666667z m682.666666-106.666667a64 64 0 1 0 0 128 64 64 0 0 0 0-128zM234.666667 298.666667a64 64 0 1 0 0-128 64 64 0 0 0 0 128z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n\n  'app-password-hide': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M512 640c-28.032 0-55.466667-2.218667-82.090667-6.4l-21.248 79.274667a21.333333 21.333333 0 0 1-26.154666 15.061333L341.333333 716.885333a21.333333 21.333333 0 0 1-15.061333-26.112l20.821333-77.653333a473.770667 473.770667 0 0 1-97.152-45.653333l-67.84 67.84a21.333333 21.333333 0 0 1-30.122666 0l-30.165334-30.208a21.333333 21.333333 0 0 1 0-30.165334l59.733334-59.733333A386.389333 386.389333 0 0 1 104.789333 416.426667a37.76 37.76 0 0 1 7.594667-45.397334c10.496-9.514667 17.877333-16 24.32-22.442666a170.24 170.24 0 0 0 1.834667-1.92c9.301333-9.6 25.173333-6.016 30.634666 6.186666C222.336 471.936 349.568 554.666667 512 554.666667c155.648 0 285.866667-80.512 338.090667-190.976 1.365333-2.858667 2.901333-6.485333 4.437333-10.325334a18.346667 18.346667 0 0 1 29.866667-6.613333l27.392 27.434667a36.565333 36.565333 0 0 1 6.997333 42.666666c-1.792 3.456-3.541333 6.698667-5.034667 9.301334a390.4 390.4 0 0 1-76.928 94.293333l54.442667 54.485333a21.333333 21.333333 0 0 1 0 30.165334l-30.165333 30.165333a21.333333 21.333333 0 0 1-30.165334 0l-63.658666-63.658667a475.306667 475.306667 0 0 1-90.282667 41.514667l20.778667 77.653333a21.333333 21.333333 0 0 1-15.061334 26.112l-41.216 11.093334a21.333333 21.333333 0 0 1-26.154666-15.104l-21.248-79.317334c-26.581333 4.266667-54.058667 6.442667-82.090667 6.442667z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-add-outlined': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M469.333333 469.333333V112.682667c0-9.514667 0.981333-12.970667 2.858667-16.426667a19.370667 19.370667 0 0 1 8.064-8.106667c3.456-1.834667 6.912-2.816 16.426667-2.816h30.634666c9.514667 0 12.970667 0.981333 16.426667 2.858667a19.370667 19.370667 0 0 1 8.106667 8.064c1.834667 3.456 2.816 6.912 2.816 16.426667V469.333333h356.650666c9.514667 0 12.970667 0.981333 16.426667 2.858667a19.370667 19.370667 0 0 1 8.106667 8.064c1.834667 3.456 2.816 6.912 2.816 16.426667v30.634666c0 9.514667-0.981333 12.970667-2.858667 16.426667a19.370667 19.370667 0 0 1-8.064 8.106667c-3.456 1.834667-6.912 2.816-16.426667 2.816H554.666667v356.650666c0 9.514667-0.981333 12.970667-2.858667 16.426667a19.370667 19.370667 0 0 1-8.064 8.106667c-3.456 1.834667-6.912 2.816-16.426667 2.816h-30.634666c-9.514667 0-12.970667-0.981333-16.426667-2.858667a19.370667 19.370667 0 0 1-8.106667-8.064c-1.834667-3.456-2.816-6.912-2.816-16.426667V554.666667H112.682667c-9.514667 0-12.970667-0.981333-16.426667-2.858667a19.370667 19.370667 0 0 1-8.106667-8.064C86.357333 540.288 85.333333 536.832 85.333333 527.36v-30.634667c0-9.514667 0.981333-12.970667 2.858667-16.426666a19.370667 19.370667 0 0 1 8.064-8.106667c3.456-1.834667 6.912-2.816 16.426667-2.816H469.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-add-circle-outlined': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M469.333333 469.333333V320a21.333333 21.333333 0 0 1 21.333334-21.333333h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333V469.333333h149.333333a21.333333 21.333333 0 0 1 21.333333 21.333334v42.666666a21.333333 21.333333 0 0 1-21.333333 21.333334H554.666667v149.333333a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333V554.666667H320a21.333333 21.333333 0 0 1-21.333333-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333333-21.333334H469.333333z m42.666667 426.666667a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m0 85.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-refresh': {\n    iconReader: () => {\n      return h('i', [\n        h(\n          'svg',\n          {\n            style: { height: '100%', width: '100%' },\n            viewBox: '0 0 1024 1024',\n            version: '1.1',\n            xmlns: 'http://www.w3.org/2000/svg',\n          },\n          [\n            h('path', {\n              d: 'M757.12 341.333333a298.666667 298.666667 0 1 0 41.173333 256h88.192A384 384 0 1 1 810.666667 270.634667V149.333333a21.333333 21.333333 0 0 1 21.333333-21.333333h42.666667a21.333333 21.333333 0 0 1 21.333333 21.333333V384a42.666667 42.666667 0 0 1-42.666667 42.666667h-234.666666a21.333333 21.333333 0 0 1-21.333334-21.333334v-42.666666a21.333333 21.333333 0 0 1 21.333334-21.333334h138.453333z',\n              fill: 'currentColor',\n            }),\n          ],\n        ),\n      ])\n    },\n  },\n  'app-unlink': {\n  iconReader: () => {\n    return h('i', [\n      h(\n        'svg',\n        {\n          style: { height: '100%', width: '100%' },\n          viewBox: '0 0 16 16',\n          fill: 'none',\n          xmlns: 'http://www.w3.org/2000/svg',\n        },\n        [\n          h('g', { 'clip-path': 'url(#clip0_10754_9765)' }, [\n            h('path', {\n              d: 'M1.23567 0.764126L0.76429 1.23549C0.634122 1.36565 0.634122 1.57668 0.76429 1.70685L13.9629 14.905C14.0931 15.0351 14.3042 15.0351 14.4343 14.905L14.9057 14.4336C15.0359 14.3034 15.0359 14.0924 14.9057 13.9622L1.70705 0.764126C1.57688 0.633963 1.36584 0.633963 1.23567 0.764126Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M9.77756 6.94871V3.33311C9.77756 3.22403 9.69895 3.1333 9.59528 3.11448L9.55534 3.1109H5.93959L4.60626 1.77762H9.55534C10.3858 1.77762 11.0643 2.42839 11.1086 3.24777L11.1109 3.33311V8.28199L9.77756 6.94871Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M0.888669 3.71681V8.66623L0.890971 8.75157C0.93528 9.57095 1.61375 10.2217 2.44422 10.2217H4.17756C4.32483 10.2217 4.44422 10.1023 4.44422 9.95506V9.15509C4.44422 9.00782 4.32483 8.88844 4.17756 8.88844H2.44422L2.40428 8.88486C2.30061 8.86604 2.222 8.77531 2.222 8.66623V5.05009L0.888669 3.71681Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M5.33311 8.16107V12.6661L5.33542 12.7514C5.37972 13.5708 6.0582 14.2216 6.88867 14.2216H11.3938L10.0605 12.8883H6.88867L6.84872 12.8847C6.74506 12.8659 6.66645 12.7751 6.66645 12.6661V9.49435L5.33311 8.16107Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M8.60626 5.77746L8.88867 6.05986V6.04411C8.88867 5.89684 8.76928 5.77746 8.622 5.77746H8.60626Z',\n              fill: 'currentColor',\n            }),\n            h('path', {\n              d: 'M15.5542 12.7251L14.222 11.393V7.33295C14.222 7.22386 14.1434 7.13313 14.0397 7.11431L13.9998 7.11073H12.2664C12.1192 7.11073 11.9998 6.99135 11.9998 6.84408V6.04411C11.9998 5.89684 12.1192 5.77746 12.2664 5.77746H13.9998C14.8303 5.77746 15.5087 6.42822 15.553 7.2476L15.5553 7.33295V12.6661C15.5553 12.6858 15.555 12.7055 15.5542 12.7251Z',\n              fill: 'currentColor',\n            }),\n          ]),\n          h('defs', [\n            h('clipPath', { id: 'clip0_10754_9765' }, [\n              h('rect', { width: '16', height: '15.9993', fill: 'currentColor' }),\n            ]),\n          ]),\n        ],\n      ),\n    ])\n  },\n},\n  // 动态加载的图标\n  ...dynamicIcons,\n}\n"
  },
  {
    "path": "ui/src/components/app-table/index.vue",
    "content": "<template>\n  <div class=\"app-table\" :class=\"quickCreate ? 'table-quick-append' : ''\">\n    <el-table\n      :max-height=\"tableHeight\"\n      v-bind=\"$attrs\"\n      ref=\"appTableRef\"\n      :tooltip-options=\"{\n        popperClass: 'max-w-350',\n      }\"\n    >\n      <template #append v-if=\"quickCreate\">\n        <div v-if=\"showInput\">\n          <el-input\n            ref=\"quickInputRef\"\n            v-model=\"inputValue\"\n            :placeholder=\"`${$t('common.inputPlaceholder')} ${quickCreateName}`\"\n            class=\"w-500 mr-12\"\n            autofocus\n            :maxlength=\"quickCreateMaxlength || '-'\"\n            :show-word-limit=\"quickCreateMaxlength ? true : false\"\n            @keydown.enter=\"submitHandle\"\n            clearable\n          />\n\n          <el-button type=\"primary\" @click=\"submitHandle\" :disabled=\"loading\">{{\n            $t('common.create')\n          }}</el-button>\n          <el-button @click=\"showInput = false\" :disabled=\"loading\">{{\n            $t('common.cancel')\n          }}</el-button>\n        </div>\n        <div v-else @click=\"quickCreateHandle\" class=\"w-full\">\n          <el-button type=\"primary\" link class=\"quich-button\">\n            <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n            <span class=\"ml-4\">{{ quickCreatePlaceholder }}</span>\n          </el-button>\n        </div>\n      </template>\n      <slot></slot>\n    </el-table>\n    <div class=\"app-table__pagination mt-16\" v-if=\"$slots.pagination || paginationConfig\">\n      <slot name=\"pagination\">\n        <el-pagination\n          v-model:current-page=\"paginationConfig.current_page\"\n          v-model:page-size=\"paginationConfig.page_size\"\n          :page-sizes=\"paginationConfig.page_sizes || pageSizes\"\n          :total=\"paginationConfig.total\"\n          layout=\"total, prev, pager, next, sizes\"\n          @size-change=\"handleSizeChange\"\n          @current-change=\"handleCurrentChange\"\n        />\n      </slot>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, nextTick, watch, computed, onMounted } from 'vue'\nimport { MsgError } from '@/utils/message'\nimport { t } from '@/locales'\ndefineOptions({ name: 'AppTable' })\n\nimport useStore from '@/stores'\nconst { common } = useStore()\n\nconst props = defineProps({\n  paginationConfig: {\n    type: Object,\n    default: () => {},\n  },\n  quickCreate: {\n    type: Boolean,\n    default: false,\n  },\n  quickCreateName: {\n    type: String,\n    default: t('components.quickCreateName'),\n  },\n  quickCreatePlaceholder: {\n    type: String,\n    default: t('components.quickCreatePlaceholder'),\n  },\n  quickCreateMaxlength: {\n    type: Number,\n    default: () => 0,\n  },\n  storeKey: String,\n  maxTableHeight: {\n    type: Number,\n    default: 300,\n  },\n})\nconst emit = defineEmits(['changePage', 'sizeChange', 'creatQuick'])\n\nconst paginationConfig = computed(() => props.paginationConfig)\n\nconst pageSizes = [10, 20, 50, 100]\n\nconst quickInputRef = ref()\nconst appTableRef = ref()\n\nconst loading = ref(false)\nconst showInput = ref(false)\nconst inputValue = ref('')\nconst tableHeight = ref<number | string>('')\nwatch(showInput, (bool: boolean) => {\n  if (!bool) {\n    inputValue.value = ''\n  }\n})\n\nfunction submitHandle() {\n  if (inputValue.value) {\n    loading.value = true\n    emit('creatQuick', inputValue.value)\n    setTimeout(() => {\n      showInput.value = false\n      loading.value = false\n    }, 200)\n  } else {\n    MsgError(`${props.quickCreateName} ${t('dynamicsForm.tip.requiredMessage')}`)\n  }\n}\n\nfunction quickCreateHandle() {\n  showInput.value = true\n  nextTick(() => {\n    quickInputRef.value?.focus()\n  })\n}\n\nfunction handleSizeChange() {\n  emit('sizeChange')\n  if (props.storeKey) {\n    common.savePage(props.storeKey, props.paginationConfig)\n  }\n}\nfunction handleCurrentChange() {\n  emit('changePage')\n  if (props.storeKey) {\n    common.savePage(props.storeKey, props.paginationConfig)\n  }\n}\n\nfunction clearSelection() {\n  appTableRef.value?.clearSelection()\n}\nfunction toggleRowSelection(row: any, selected?: boolean, ignoreSelectable = true) {\n  appTableRef.value?.toggleRowSelection(row, selected, ignoreSelectable)\n}\nfunction getSelectionRows() {\n  return appTableRef.value?.getSelectionRows()\n}\ndefineExpose({\n  clearSelection,\n  toggleRowSelection,\n  getSelectionRows,\n})\n\nonMounted(() => {\n  tableHeight.value = window.innerHeight - props.maxTableHeight\n  window.onresize = () => {\n    return (() => {\n      tableHeight.value = window.innerHeight - props.maxTableHeight\n    })()\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.app-table {\n  &__pagination {\n    display: flex;\n    justify-content: flex-end;\n  }\n  .quich-button {\n    &:hover {\n      color: var(--el-button-text-color);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/app-table-infinite-scroll/index.vue",
    "content": "<template>\n  <el-table\n    :max-height=\"tableHeight\"\n    v-bind=\"$attrs\"\n    ref=\"appTableRef\"\n    :height=\"tableHeight + 'px'\"\n    v-el-table-infinite-scroll=\"load\"\n    :infinite-scroll-disabled=\"disabled\"\n  >\n    <slot></slot>\n  </el-table>\n</template>\n<script setup lang=\"ts\">\nimport { ref, nextTick, watch, computed, onMounted } from 'vue'\nimport { default as vElTableInfiniteScroll } from 'el-table-infinite-scroll'\ndefineOptions({ name: 'AppTableInfiniteScroll' })\n\nconst props = defineProps({\n  paginationConfig: {\n    type: Object,\n    required: true,\n    default: () => ({\n      current_page: 1,\n      page_size: 50,\n      total: 0,\n    }),\n  }, // option: { current_page , page_size, total  }\n  maxTableHeight: {\n    type: Number,\n    default: 300,\n  },\n})\nconst emit = defineEmits(['changePage'])\n\nconst appTableRef = ref()\n\nconst tableHeight = ref<number | string>('')\nconst disabled = ref(false)\n\nconst load = () => {\n  if (disabled.value) return\n\n  // props.paginationConfig.current_page++;\n  if (\n    props.paginationConfig.current_page * props.paginationConfig.page_size <=\n    props.paginationConfig.total\n  ) {\n    emit('changePage')\n  }\n\n  if (\n    props.paginationConfig.current_page * props.paginationConfig.page_size ===\n    props.paginationConfig.total\n  ) {\n    disabled.value = true\n  }\n}\ndefineExpose({})\n\nonMounted(() => {\n  tableHeight.value = window.innerHeight - props.maxTableHeight\n  window.onresize = () => {\n    return (() => {\n      tableHeight.value = window.innerHeight - props.maxTableHeight\n    })()\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/auto-tooltip/index.vue",
    "content": "<template>\n  <el-tooltip\n    v-bind=\"$attrs\"\n    :disabled=\"!(containerWeight > contentWeight)\"\n    effect=\"dark\"\n    placement=\"bottom\"\n    popper-class=\"auto-tooltip-popper\"\n  >\n    <div ref=\"tagLabel\" :class=\"['auto-tooltip', className]\" :style=\"style\">\n      <slot></slot>\n    </div>\n  </el-tooltip>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted, nextTick } from 'vue'\ndefineOptions({ name: 'AutoTooltip' })\nconst props = defineProps({ className: String, style: Object })\nconst tagLabel = ref()\nconst containerWeight = ref(0)\nconst contentWeight = ref(0)\n\nonMounted(() => {\n  nextTick(() => {\n    containerWeight.value = tagLabel.value?.scrollWidth\n    contentWeight.value = tagLabel.value?.clientWidth\n  })\n  window.addEventListener('resize', function () {\n    containerWeight.value = tagLabel.value?.scrollWidth\n    contentWeight.value = tagLabel.value?.clientWidth\n  })\n})\n</script>\n<style lang=\"scss\" scoped>\n.auto-tooltip {\n  overflow: hidden;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/back-button/index.vue",
    "content": "<template>\n  <el-button class=\"back-button cursor mr-4\" text @click=\"jump\">\n    <el-icon :size=\"20\">\n      <Back />\n    </el-icon>\n  </el-button>\n</template>\n\n<script setup lang=\"ts\">\nimport { useRouter, type RouteLocationRaw } from 'vue-router'\ndefineOptions({ name: 'BackButton' })\nconst router = useRouter()\nconst props = defineProps({\n  to: String\n})\n\nconst emit = defineEmits(['click'])\n/* 上一层路由 */\nconst back: any = router.options.history.state.back\nfunction jump() {\n  if (props.to === '-1') {\n    if (back) {\n      router.push(back)\n    } else {\n      router.go(-1)\n    }\n  } else if (props.to) {\n    router.push(props.to as RouteLocationRaw)\n  } else {\n    emit('click')\n  }\n}\n</script>\n\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/card-box/index.vue",
    "content": "<template>\n  <el-card shadow=\"hover\" class=\"card-box\" @mouseenter=\"cardEnter()\" @mouseleave=\"cardLeave()\">\n    <div class=\"card-header\">\n      <slot name=\"header\">\n        <div class=\"title flex align-center\">\n          <div class=\"mr-12 flex align-center\" v-if=\"showIcon\">\n            <slot name=\"icon\">\n              <el-avatar shape=\"square\" :size=\"32\" class=\"avatar-blue\">\n                <img src=\"@/assets/knowledge/icon_document.svg\" style=\"width: 58%\" alt=\"\" />\n              </el-avatar>\n            </slot>\n          </div>\n          <div style=\"width: 90%\" class=\"mt-4\">\n            <slot name=\"title\">\n              <span class=\"ellipsis-1\" :title=\"title\" style=\"width: 80%\">\n                {{ title }}\n              </span>\n            </slot>\n            <slot name=\"subTitle\"> </slot>\n          </div>\n\n          <div class=\"status-tag\">\n            <slot name=\"tag\" :hoverShow=\"show\"> <!-- 放标签 --> </slot>\n          </div>\n        </div>\n      </slot>\n    </div>\n    <div class=\"description break-all mt-12\">\n      <slot>\n        <div class=\"content color-secondary\">\n          {{ description }}\n        </div>\n      </slot>\n    </div>\n\n    <div class=\"card-footer flex-between\" v-if=\"$slots.footer || $slots.mouseEnter\">\n      <div style=\"flex: 1\">\n        <slot name=\"footer\"></slot>\n      </div>\n      <div @mouseenter=\"subHoveredEnter\">\n        <slot name=\"mouseEnter\" v-if=\"$slots.mouseEnter && show\" />\n      </div>\n    </div>\n  </el-card>\n</template>\n<script setup lang=\"ts\">\nimport { ref, useSlots } from 'vue'\nimport { t } from '@/locales'\ndefineOptions({ name: 'CardBox' })\nconst props = withDefaults(\n  defineProps<{\n    /**\n     * 标题\n     */\n    title?: string\n    /**\n     * 描述\n     */\n    description?: string\n    /**\n     * 是否展示icon\n     */\n    showIcon?: boolean\n  }>(),\n  { title: t('common.title'), description: '', showIcon: true, border: true },\n)\n\nconst show = ref(false)\n// card上面存在dropdown菜单\nconst subHovered = ref(false)\nfunction cardEnter() {\n  show.value = true\n  subHovered.value = false\n}\n\nfunction cardLeave() {\n  show.value = subHovered.value\n}\n\nfunction subHoveredEnter() {\n  subHovered.value = true\n}\n\n</script>\n<style lang=\"scss\" scoped>\n.card-box {\n  font-size: 14px;\n  position: relative;\n  min-height: var(--card-min-height);\n  min-width: var(--card-min-width);\n  line-height: 20px !important;\n  .card-header {\n    margin-top: -5px;\n  }\n  .description {\n    line-height: 22px;\n    font-weight: 400;\n    min-height: 70px;\n    .content {\n      display: -webkit-box;\n      height: var(--app-card-box-description-height, 40px);\n      -webkit-box-orient: vertical;\n      -webkit-line-clamp: 2;\n      overflow: hidden;\n    }\n  }\n\n  .card-footer {\n    position: absolute;\n    bottom: 8px;\n    left: 0;\n    min-height: 30px;\n    font-weight: 400;\n    padding: 0 16px;\n    width: 100%;\n    box-sizing: border-box;\n  }\n  .status-tag {\n    position: absolute;\n    right: 16px;\n    top: 14px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/card-checkbox/index.vue",
    "content": "<template>\n  <el-card\n    shadow=\"hover\"\n    class=\"card-checkbox cursor\"\n    :class=\"modelValue.includes(toModelValue) ? 'active' : ''\"\n    @click=\"checked\"\n  >\n    <div class=\"flex-between\">\n      <div class=\"flex align-center\">\n        <slot name=\"icon\">\n          <KnowledgeIcon :type=\"data.type\" />\n        </slot>\n        <slot></slot>\n      </div>\n      <el-checkbox v-bind:modelValue=\"modelValue.includes(toModelValue)\" @change=\"checkboxChange\">\n      </el-checkbox>\n    </div>\n  </el-card>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\ndefineOptions({ name: 'CardCheckbox' })\nconst props = defineProps<{\n  data: any\n\n  modelValue: Array<any>\n\n  valueField?: string\n}>()\n\nconst toModelValue = computed(() => (props.valueField ? props.data[props.valueField] : props.data))\n\nconst emit = defineEmits(['update:modelValue', 'change'])\n\nconst checked = () => {\n  const value = props.modelValue ? props.modelValue : []\n  if (props.modelValue.includes(toModelValue.value)) {\n    emit(\n      'update:modelValue',\n      value.filter((item) => item !== toModelValue.value),\n    )\n  } else {\n    emit('update:modelValue', [...value, toModelValue.value])\n  }\n  checkboxChange()\n}\n\nfunction checkboxChange() {\n  emit('change')\n}\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/codemirror-editor/index.vue",
    "content": "<template>\n  <div class=\"codemirror-editor w-full\">\n    <form @submit.prevent>\n      <Codemirror\n        v-model=\"data\"\n        ref=\"cmRef\"\n        :extensions=\"extensions\"\n        :style=\"codemirrorStyle\"\n        :tab-size=\"4\"\n        :autofocus=\"true\"\n        v-bind=\"$attrs\"\n      />\n    </form>\n    <div class=\"codemirror-editor__footer\">\n      <el-button text type=\"info\" @click=\"openCodemirrorDialog\" class=\"magnify\">\n        <AppIcon iconName=\"app-magnify\" style=\"font-size: 16px\"></AppIcon>\n      </el-button>\n    </div>\n    <!-- Codemirror 弹出层 -->\n    <el-dialog v-model=\"dialogVisible\" :title=\"title\" append-to-body fullscreen>\n      <form @submit.prevent>\n        <Codemirror\n          v-model=\"cloneContent\"\n          :extensions=\"extensions\"\n          :style=\"codemirrorStyle\"\n          :tab-size=\"4\"\n          :autofocus=\"true\"\n          style=\"\n            height: calc(100vh - 160px) !important;\n            border: 1px solid #bbbfc4;\n            border-radius: 4px;\n          \"\n        />\n      </form>\n      <template #footer>\n        <div class=\"dialog-footer mt-24\">\n          <el-button type=\"primary\" @click=\"submitDialog\"> {{ $t('common.confirm') }}</el-button>\n        </div>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { Codemirror } from 'vue-codemirror'\nimport { python } from '@codemirror/lang-python'\nimport { oneDark } from '@codemirror/theme-one-dark'\nimport { linter, type Diagnostic } from '@codemirror/lint'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { throttle } from 'lodash'\n\ndefineOptions({ name: 'CodemirrorEditor' })\n\nconst props = defineProps<{\n  title: string\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue', 'submitDialog'])\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst data = computed({\n  set: (value) => {\n    emit('update:modelValue', value)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\n\nfunction getRangeFromLineAndColumn(state: any, line: number, column: number, end_column?: number) {\n  const l = state.doc.line(line)\n  const lineLength = l.length\n  const safeColumn = Math.max(0, Math.min(column, lineLength))\n  const fromPos = l.from + safeColumn\n  let safeEndColumn\n  if (end_column !== undefined) {\n    safeEndColumn = Math.max(0, Math.min(end_column, lineLength))\n  } else {\n    safeEndColumn = lineLength\n  }\n  const toPos = l.from + safeEndColumn\n  const finalFrom = Math.min(fromPos, toPos)\n  const finalTo = Math.max(fromPos, toPos)\n  return {\n    from: finalFrom,\n    to: finalTo,\n  }\n}\nconst asyncLint = throttle(async (view: any) => {\n  const sendString = view.state.doc.toString()\n  const res = await loadSharedApi({ type: 'tool', systemType: apiType.value }).postPylint(\n    view.state.doc.toString(),\n  )\n  if (sendString !== view.state.doc.toString()) {\n    return []\n  }\n  return res.data\n}, 500)\n\nconst regexpLinter = linter(async (view) => {\n  const currentstate = view.state\n  const diagnostics: Diagnostic[] = []\n  const lintResults = await asyncLint(view)\n  if (!lintResults || lintResults.length === 0) {\n    return diagnostics\n  }\n  // 限制诊断数量，避免过多诊断信息\n  const maxDiagnostics = 50\n  const limitedResults = lintResults.slice(0, maxDiagnostics)\n\n  limitedResults.forEach((element: any) => {\n    try {\n      const range = getRangeFromLineAndColumn(\n        currentstate,\n        element.line,\n        element.column,\n        element.endColumn,\n      )\n      // 验证范围有效性\n      if (range.from >= 0 && range.to >= range.from) {\n        diagnostics.push({\n          from: range.from,\n          to: range.to,\n          severity: element.type === 'error' ? 'error' : 'warning',\n          message: element.message,\n        })\n      }\n    } catch (error) {\n      // console.error('Error processing lint result:', error)\n    }\n  })\n  return diagnostics\n})\nconst extensions = [python(), oneDark, regexpLinter]\nconst codemirrorStyle = {\n  height: '210px!important',\n  width: '100%',\n}\nconst cmRef = ref<InstanceType<typeof Codemirror>>()\n// 弹出框相关代码\nconst dialogVisible = ref<boolean>(false)\n\nconst cloneContent = ref<string>('')\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    emit('submitDialog', cloneContent.value)\n  }\n})\n\nconst openCodemirrorDialog = () => {\n  cloneContent.value = props.modelValue\n  dialogVisible.value = true\n}\n\nfunction submitDialog() {\n  emit('submitDialog', cloneContent.value)\n  dialogVisible.value = false\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.codemirror-editor {\n  position: relative;\n\n  &__footer {\n    position: absolute;\n    bottom: 10px;\n    right: 10px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/common-list/index.vue",
    "content": "<template>\n  <div class=\"common-list\">\n    <ul v-if=\"data.length > 0\">\n      <template v-for=\"(item, index) in data\" :key=\"index\">\n        <li\n          @click.stop=\"clickHandle(item, index)\"\n          :class=\"current === item[props.valueKey] ? 'active color-primary-1' : ''\"\n          class=\"cursor\"\n          @mouseenter.stop=\"mouseenter(item)\"\n          @mouseleave.stop=\"mouseleave()\"\n        >\n          <slot :row=\"item\" :index=\"index\"> </slot>\n        </li>\n      </template>\n    </ul>\n    <slot name=\"empty\" v-else>\n      <el-empty :description=\"$t('common.noData')\" />\n    </slot>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\n\ndefineOptions({ name: 'CommonList' })\n\nconst props = withDefaults(\n  defineProps<{\n    data: Array<any>\n    defaultActive?: string\n    valueKey?: string // 唯一标识的键名\n  }>(),\n  {\n    data: () => [],\n    defaultActive: '',\n    valueKey: 'id',\n  },\n)\n\nconst current = ref<number | string>(0)\n\nwatch(\n  () => props.defaultActive,\n  (val) => {\n    current.value = val\n  },\n  { immediate: true },\n)\n\nconst emit = defineEmits(['click', 'mouseenter', 'mouseleave'])\n\nfunction mouseenter(row: any) {\n  emit('mouseenter', row)\n}\nfunction mouseleave() {\n  emit('mouseleave')\n}\n\nfunction clickHandle(row: any, index: number) {\n  current.value = row[props.valueKey]\n  emit('click', row)\n}\n\nfunction clearCurrent() {\n  current.value = 0\n}\ndefineExpose({\n  clearCurrent,\n})\n</script>\n<style lang=\"scss\" scoped>\n/* 通用 ui li样式 */\n.common-list {\n  li {\n    padding: 8px;\n    font-weight: 400;\n    font-size: 14px;\n    margin-bottom: 4px;\n    min-height: 24px;\n    line-height: 24px;\n    &.active {\n      background: var(--el-color-primary-light-9);\n      border-radius: var(--app-border-radius-small);\n      color: var(--el-color-primary);\n      font-weight: 500;\n      &:hover {\n        background: var(--el-color-primary-light-9);\n      }\n    }\n    &:hover {\n      border-radius: var(--app-border-radius-small);\n      background: rgba(var(--el-text-color-primary-rgb), 0.1);\n    }\n    &.is-active {\n      &:hover {\n        color: var(--el-color-primary);\n        background: var(--el-color-primary-light-9);\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/Demo.vue",
    "content": "<template>\n  <div style=\"width: 1024px\">\n    <DynamicsForm\n      v-model=\"form_data\"\n      :model=\"form_data\"\n      :render_data=\"damo_data\"\n      ref=\"dynamicsFormRef\"\n      :other-params=\"{ current_workspace_id: 'default' }\"\n    >\n      <template #default=\"scope\">\n        <el-form-item label=\"其他字段\">\n          <el-input v-model=\"scope.form_value['zha']\" /> </el-form-item\n      ></template>\n    </DynamicsForm>\n    <el-button @click=\"click\">点我校验</el-button>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport type { FormField } from '@/components/dynamics-form/type'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport { ref } from 'vue'\nimport type { Dict } from '@/api/type/common'\n\nconst damo_data: Array<FormField> = [\n  {\n    field: 'aaa',\n    input_type: 'Tree',\n    attrs: {\n      lazy: true,\n      url: '/workspace/${current_workspace_id}/knowledge/${current_knowledge_id}/datasource/tool/019aa0bb-552d-73a3-b0c6-1809eaedb139/get_file_list',\n    },\n    label: '',\n  },\n  {\n    field: 'aa',\n    input_type: 'LocalFileUpload',\n    attrs: {\n      file_count_limit: 10,\n      file_size_limit: 10,\n      file_type_list: ['TXT'],\n    },\n    label: '',\n  },\n  {\n    field: 'name',\n    input_type: 'PasswordInput',\n    label: {\n      label: '用戶名',\n      input_type: 'SettingLabel',\n      field: 'name_setting',\n      relation_show_field_dict: {\n        name: {\n          values: ['01993837-5b09-7f20-9360-801d11d43d28'],\n        },\n      },\n      relation_trigger_field_dict: {\n        name: {\n          values: ['01993837-5b09-7f20-9360-801d11d43d28'],\n          request:\n            'self.children=()=>request.get(extra.renderTemplate(trigger_setting.url)).then(ok=>{return ok})',\n          url: '/workspace/${current_workspace_id}/model/${trigger_value}/model_params_form',\n        },\n      },\n      children: [],\n    },\n    required: false,\n  },\n  { field: 'json_text', input_type: 'JsonInput', label: 'aa', required: false },\n  {\n    field: 'array_object_card_field',\n    input_type: 'ArrayObjectCard',\n    label: '測試',\n    trigger_type: 'CHILD_FORMS',\n    attrs: { 'label-width': '120px', 'label-suffix': ':ssss', 'label-position': 'top' },\n    required: false,\n    children: [\n      { field: 'name1', input_type: 'TextInput', label: '用戶名1' },\n      { field: 'name2', input_type: 'TextInput', label: '用戶名2' },\n      { field: 'name3', input_type: 'TextInput', label: '用戶名3' },\n    ],\n  },\n  {\n    field: 'maxkb_tokens',\n    input_type: 'Slider',\n    default_value: 1,\n    attrs: {\n      min: 0,\n      max: 10,\n      step: 1,\n      precision: 1,\n      'show-input-controls': false,\n      'show-input': true,\n    },\n    label: { label: '温度', attrs: { tooltip: 'sss' }, input_type: 'TooltipLabel' },\n  },\n  {\n    field: 'object_card_field',\n    input_type: 'ObjectCard',\n    label: '測試',\n    trigger_type: 'CHILD_FORMS',\n    attrs: { 'label-width': '120px', 'label-suffix': ':ssss', 'label-position': 'left' },\n    required: false,\n    children: [\n      { field: 'name1', input_type: 'TextInput', label: '用戶名1' },\n      { field: 'name2', input_type: 'TextInput', label: '用戶名2' },\n      { field: 'name3', input_type: 'TextInput', label: '用戶名3' },\n    ],\n  },\n  {\n    field: 'tab_card_field',\n    input_type: 'TabCard',\n    label: '測試',\n    trigger_type: 'CHILD_FORMS',\n    attrs: { 'label-width': '120px', 'label-suffix': ':ssss', 'label-position': 'left' },\n    required: false,\n    props_info: { tabs_label: '用户' },\n    children: [\n      { field: 'name1', input_type: 'TextInput', label: '用戶名1' },\n      { field: 'name2', input_type: 'TextInput', label: '用戶名2' },\n      { field: 'name3', input_type: 'TextInput', label: '用戶名3' },\n    ],\n  },\n  {\n    field: 'single_select_field',\n    input_type: 'SingleSelect',\n    text_field: 'name',\n    value_field: 'id',\n    required: true,\n    attrs: { placeholder: '请选择' },\n    required_asterisk: true,\n    label: {\n      label: '测试单选',\n      input_type: 'SettingLabel',\n      field: 'name_setting',\n      relation_show_field_dict: {\n        single_select_field: {\n          values: [],\n        },\n      },\n      relation_trigger_field_dict: {\n        single_select_field: {\n          values: [],\n          request:\n            'self.children=()=>request.get(extra.renderTemplate(trigger_setting.url)).then(ok=>{return ok})',\n          url: '/workspace/${current_workspace_id}/model/${trigger_value}/model_params_form',\n        },\n      },\n      children: [],\n    },\n    relation_trigger_field_dict: {\n      name: {\n        values: [],\n        url: '/workspace/${current_workspace_id}/model_list?model_type=LLM',\n        change_field: 'option_list',\n      },\n    },\n  },\n  {\n    field: 'multi_select_field',\n    input_type: 'MultiSelect',\n    default_value: ['test1'],\n    relation_show_field_dict: {\n      'object_card_field.name1': [],\n    },\n    label: '测试多选下拉',\n    required: true,\n    attrs: { placeholder: '请选择' },\n    option_list: [\n      {\n        key: '测试',\n        value: 'test',\n      },\n      {\n        key: '测试1',\n        value: 'test1',\n      },\n    ],\n  },\n  {\n    field: 'radio_field',\n    input_type: 'Radio',\n    label: '测试单选',\n    required: true,\n    attrs: { placeholder: '请选择' },\n    option_list: [\n      {\n        key: '测试',\n        value: 'test',\n      },\n      {\n        key: '测试1',\n        value: 'test1',\n      },\n    ],\n  },\n  {\n    field: 'radio_button_field',\n    input_type: 'RadioButton',\n    label: '测试单选',\n    required: true,\n    attrs: { placeholder: '请选择' },\n    option_list: [\n      {\n        key: '测试',\n        value: 'test',\n      },\n      {\n        key: '测试1',\n        value: 'test1',\n      },\n    ],\n  },\n  {\n    field: 'radio_card_field',\n    input_type: 'RadioCard',\n    label: '测试单选1',\n    required: true,\n    attrs: { placeholder: '请选择' },\n    option_list: [\n      {\n        key: '测试',\n        value: 'test',\n      },\n      {\n        key: '测试111111',\n        value: 'test1',\n      },\n    ],\n  },\n  {\n    field: 'table_radio_field',\n    input_type: 'TableRadio',\n    label: '表格单选',\n    required: true,\n    attrs: { placeholder: '请选择' },\n    props_info: {\n      active_msg: '当前选中',\n      table_columns: [\n        {\n          property: '`${row.key}${row.number}`',\n          label: '名称',\n          type: 'eval',\n        },\n        {\n          property: 'ProgressTableItem',\n          label: '数值',\n          type: 'component',\n          value_field: 'number',\n          attrs: {\n            color: [\n              { color: '#f56c6c', percentage: 20 },\n              { color: '#e6a23c', percentage: 40 },\n              { color: '#5cb87a', percentage: 60 },\n              { color: '#1989fa', percentage: 80 },\n              { color: '#6f7ad3', percentage: 100 },\n            ],\n          },\n          props_info: {\n            view_card: [\n              {\n                type: 'eval',\n                title: '测试',\n                value_field:\n                  '`${parseFloat(row.number).toLocaleString(\"zh-CN\",{style: \"decimal\",maximumFractionDigits:1})}%&nbsp;&nbsp;&nbsp;`',\n              },\n              {\n                type: 'eval',\n                title: '名称',\n                value_field: '`${row.key}&nbsp;&nbsp;&nbsp;`',\n              },\n            ],\n          },\n        },\n      ],\n      style: { width: '500px' },\n    },\n    option_list: [\n      {\n        key: '测试',\n        value: 'test',\n        number: 10,\n      },\n      {\n        key: '测试111111',\n        value: 'test1',\n        number: 100,\n      },\n    ],\n  },\n  {\n    field: 'table_checkbox_field',\n    input_type: 'TableCheckbox',\n    label: '表格多选',\n    required: true,\n    attrs: { placeholder: '请选择' },\n    props_info: {\n      active_msg: '当前选中',\n      table_columns: [\n        {\n          property: '`${row.key}${row.number}`',\n          label: '名称',\n          type: 'eval',\n        },\n        {\n          property: 'ProgressTableItem',\n          label: '数值',\n          type: 'component',\n          value_field: 'number',\n          attrs: {\n            color: [\n              { color: '#f56c6c', percentage: 20 },\n              { color: '#e6a23c', percentage: 40 },\n              { color: '#5cb87a', percentage: 60 },\n              { color: '#1989fa', percentage: 80 },\n              { color: '#6f7ad3', percentage: 100 },\n            ],\n          },\n          props_info: {\n            view_card: [\n              {\n                type: 'eval',\n                title: '测试',\n                value_field:\n                  '`${parseFloat(row.number).toLocaleString(\"zh-CN\",{style: \"decimal\",maximumFractionDigits:1})}%&nbsp;&nbsp;&nbsp;`',\n              },\n              {\n                type: 'eval',\n                title: '名称',\n                value_field: '`${row.key}&nbsp;&nbsp;&nbsp;`',\n              },\n            ],\n          },\n        },\n      ],\n      style: { width: '500px' },\n    },\n    option_list: [\n      {\n        key: '测试',\n        value: 'test',\n        number: 10,\n      },\n      {\n        key: '测试111111',\n        value: 'test1',\n        number: 100,\n      },\n    ],\n  },\n]\nconst form_data = ref<Dict<any>>({})\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst click = () => {\n  dynamicsFormRef.value?.validate()\n}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/DemoConstructor.vue",
    "content": "<template>\n  <el-row :gutter=\"12\">\n    <el-col :span=\"12\">\n      <el-card shadow=\"never\">\n        <DynamicsFormConstructor\n          v-model=\"item\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n          ref=\"DynamicsFormConstructorRef\"\n        ></DynamicsFormConstructor>\n        <el-button @click=\"add_field\">添加</el-button>\n      </el-card></el-col\n    >\n    <el-col :span=\"12\">\n      <el-card shadow=\"never\">\n        <DynamicsForm\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n          v-model=\"form_data\"\n          :model=\"form_data\"\n          :render_data=\"form_item_list\"\n          ref=\"dynamicsFormRef\"\n        >\n        </DynamicsForm>\n        <el-button @click=\"validate\">校验</el-button>\n      </el-card></el-col\n    >\n  </el-row>\n</template>\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nconst DynamicsFormConstructorRef = ref<InstanceType<typeof DynamicsFormConstructor>>()\n\nconst form_item_list = ref<Array<any>>([])\nconst add_field = () => {\n  if (DynamicsFormConstructorRef.value) {\n    DynamicsFormConstructorRef.value.validate().then(() => {\n      form_item_list.value.push(DynamicsFormConstructorRef.value?.getData())\n    })\n  }\n}\n\nconst form_data = ref({})\nconst item = ref({})\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst validate = () => {\n  dynamicsFormRef.value\n    ?.validate()\n    .then((ok) => {})\n    .catch((e) => {})\n}\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/FormItem.vue",
    "content": "<template>\n  <el-form-item\n    v-loading=\"loading\"\n    :style=\"formItemStyle\"\n    :prop=\"formfield.field\"\n    :key=\"formfield.field\"\n    :rules=\"rules\"\n    :class=\"formfield.required_asterisk ? 'hide-asterisk' : ''\"\n  >\n    <template #label v-if=\"formfield.label\">\n      <FormItemLabel v-if=\"isString(formfield.label)\" :form-field=\"formfield\"></FormItemLabel>\n      <component\n        v-else\n        :is=\"formfield.label.input_type\"\n        :label=\"formfield.label\"\n        v-model=\"labelValue\"\n        :form-value=\"formValue\"\n        v-bind=\"label_attrs\"\n      ></component>\n    </template>\n    <component\n      ref=\"componentFormRef\"\n      :view=\"view\"\n      v-model=\"itemValue\"\n      :is=\"formfield.input_type\"\n      :form-field=\"formfield\"\n      :other-params=\"otherParams\"\n      :style=\"componentStyle\"\n      :field=\"formfield.field\"\n      v-bind=\"attrs\"\n      :formfield-list=\"formfieldList\"\n    ></component>\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted, type Ref } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport FormItemLabel from './FormItemLabel.vue'\nimport type { Dict } from '@/api/type/common'\nimport bus from '@/utils/bus'\nimport { t } from '@/locales'\nimport { get } from 'lodash'\nconst props = defineProps<{\n  // 双向绑定的值\n  modelValue: any\n\n  // 表单Item\n  formfield: FormField\n  // 是否只读\n  view: boolean\n  // 调用接口所需要的其他参数\n  otherParams: any\n  // 获取Options\n  trigger: (\n    trigger_field: string,\n    trigger_value: any,\n    trigger_setting: any,\n    self: any,\n    loading: Ref<boolean>,\n  ) => void\n  // 初始化默认数据\n  initDefaultData: (formItem: FormField) => void\n  // 默认每个宽度\n  defaultItemWidth: string\n  // 表单收集数据\n  formValue: Dict<any>\n\n  formfieldList: Array<FormField>\n\n  parent_field?: string\n}>()\n\nconst emit = defineEmits(['change', 'changeLabel'])\n\nconst loading = ref<boolean>(false)\n\nconst isString = (value: any) => {\n  return typeof value === 'string'\n}\nconst labelValue = computed({\n  get: () => {\n    return props.formValue[props.formfield.label.field]\n  },\n  set: (value: any) => {\n    emit('changeLabel', value)\n    bus.emit(props.formfield.label.field, value)\n  },\n})\nconst itemValue = computed({\n  get: () => {\n    return props.modelValue\n  },\n  set: (value: any) => {\n    emit('change', value)\n    if (props.parent_field) {\n      bus.emit(props.parent_field + '.' + props.formfield.field, value)\n    } else {\n      bus.emit(props.formfield.field, value)\n    }\n  },\n})\nconst componentFormRef = ref<any>()\nconst label_attrs = computed(() => {\n  return props.formfield.label &&\n    typeof props.formfield.label !== 'string' &&\n    props.formfield.label.attrs\n    ? props.formfield.label.attrs\n    : {}\n})\nconst props_info = computed(() => {\n  return props.formfield.props_info ? props.formfield.props_info : {}\n})\n/**\n * 表单 item style\n */\nconst formItemStyle = computed(() => {\n  return props_info.value.item_style ? props_info.value.item_style : {}\n})\n\n/**\n * 表单错误Msg\n */\nconst errMsg = computed(() => {\n  return props_info.value.err_msg\n    ? props_info.value.err_msg\n    : isString(props.formfield.label)\n      ? props.formfield.label + ' ' + t('dynamicsForm.tip.requiredMessage')\n      : props.formfield.label.label + ' ' + t('dynamicsForm.tip.requiredMessage')\n})\n/**\n * 反序列化\n * @param rule\n */\nconst to_rule = (rule: any) => {\n  if (rule.validator) {\n    // eslint-disable-next-line prefer-const, @typescript-eslint/no-unused-vars\n    let validator = (rule: any, value: string, callback: any) => {}\n    eval(rule.validator)\n    return { ...rule, validator }\n  }\n  return rule\n}\n\n/**\n * 校验\n */\nconst rules = computed(() => {\n  return props_info.value.rules\n    ? props_info.value.rules.map(to_rule)\n    : {\n        message: errMsg.value,\n        trigger: props.formfield.input_type === 'Slider' ? 'blur' : ['blur', 'change'],\n        required: props.formfield.required === false ? false : true,\n      }\n})\n\n/**\n * 组件样式\n */\nconst componentStyle = computed(() => {\n  return props_info.value.style ? props_info.value.style : {}\n})\n\n/**\n * 组件attrs\n */\nconst attrs = computed(() => {\n  return props.formfield.attrs ? props.formfield.attrs : {}\n})\nconst initTrigger = (self: any, trigger_field_dict?: Dict<any>) => {\n  if (trigger_field_dict) {\n    Object.keys(trigger_field_dict).forEach((key) => {\n      const setting = trigger_field_dict[key]\n      const triggerValues = setting['values']\n      const value = get(props.formValue, key)\n      if (triggerValues && triggerValues.length > 0) {\n        if (triggerValues.includes(value)) {\n          props.trigger(key, value, setting, self, loading)\n        }\n      } else {\n        props.trigger(key, value, setting, self, loading)\n      }\n    })\n  }\n}\nonMounted(() => {\n  props.initDefaultData(props.formfield)\n  initTrigger(props.formfield, props.formfield.relation_trigger_field_dict)\n  initTrigger(props.formfield.label, props.formfield.label?.relation_trigger_field_dict)\n  isString(props.formfield.label)\n    ? undefined\n    : onTrigger(props.formfield.label, props.formfield.label.relation_trigger_field_dict)\n  onTrigger(props.formfield, props.formfield.relation_trigger_field_dict)\n})\nconst onTrigger = (self: any, trigger_field_dict?: Dict<any>) => {\n  if (trigger_field_dict) {\n    const keys = Object.keys(trigger_field_dict)\n    keys.forEach((key) => {\n      const setting = trigger_field_dict[key]\n      const values: Array<any> = setting.values\n      // 添加关系\n      bus.on(key, (v: any) => {\n        if (values && values.length > 0) {\n          if (values.includes(v)) {\n            props.trigger(key, v, setting, self, loading)\n          }\n        } else {\n          props.trigger(key, v, setting, self, loading)\n        }\n      })\n    })\n  }\n}\nconst validate = () => {\n  if (props.formfield.trigger_type === 'CHILD_FORMS' && componentFormRef.value) {\n    return componentFormRef.value.validate()\n  }\n  return Promise.resolve()\n}\ndefineExpose({ validate })\n</script>\n<style lang=\"scss\" scoped>\n.hide-asterisk {\n  ::after {\n    display: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/FormItemLabel.vue",
    "content": "<template>\n  {{ formField.label }}\n</template>\n<script setup lang=\"ts\">\nimport type { FormField } from '@/components/dynamics-form/type'\nconst props = defineProps<{\n  // 表单Item\n  formField: FormField\n}>()\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/data.ts",
    "content": "import { t } from '@/locales'\nconst input_type_list = [\n  {\n    label: t('dynamicsForm.input_type_list.TextInput'),\n    value: 'TextInput',\n  },\n  {\n    label: t('dynamicsForm.input_type_list.TextareaInput'),\n    value: 'TextareaInput',\n  },\n  {\n    label: t('dynamicsForm.input_type_list.JsonInput'),\n    value: 'JsonInput',\n  },\n  {\n    label: t('dynamicsForm.input_type_list.PasswordInput'),\n    value: 'PasswordInput',\n  },\n  {\n    label: t('dynamicsForm.input_type_list.SingleSelect'),\n    value: 'SingleSelect',\n  },\n  {\n    label: t('dynamicsForm.input_type_list.MultiSelect'),\n    value: 'MultiSelect',\n  },\n  {\n    label: t('dynamicsForm.input_type_list.RadioCard'),\n    value: 'RadioCard',\n  },\n  {\n    label: t('dynamicsForm.input_type_list.RadioRow'),\n    value: 'RadioRow',\n  },\n  {\n    label: t('dynamicsForm.input_type_list.MultiRow'),\n    value: 'MultiRow',\n  },\n  {\n    label: t('dynamicsForm.input_type_list.Slider'),\n    value: 'Slider',\n  },\n  {\n    label: t('dynamicsForm.input_type_list.SwitchInput'),\n    value: 'SwitchInput',\n  },\n\n  {\n    label: t('dynamicsForm.input_type_list.DatePicker'),\n    value: 'DatePicker',\n  },\n\n  {\n    label: t('dynamicsForm.input_type_list.UploadInput'),\n    value: 'UploadInput',\n  },\n\n  {\n    label: t('dynamicsForm.input_type_list.Model'),\n    value: 'Model',\n  },\n]\nexport { input_type_list }\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/index.vue",
    "content": "<template>\n  <el-form\n    @submit.prevent\n    ref=\"ruleFormRef\"\n    class=\"mb-24\"\n    label-width=\"auto\"\n    :model=\"form_data\"\n    v-bind=\"$attrs\"\n  >\n    <el-form-item\n      :label=\"$t('dynamicsForm.paramForm.field.label')\"\n      :required=\"true\"\n      prop=\"field\"\n      :rules=\"rules.field\"\n    >\n      <el-input\n        v-model=\"form_data.field\"\n        :maxlength=\"64\"\n        :placeholder=\"$t('dynamicsForm.paramForm.field.placeholder')\"\n        show-word-limit\n      />\n    </el-form-item>\n    <el-form-item\n      :label=\"$t('dynamicsForm.paramForm.name.label')\"\n      :required=\"true\"\n      prop=\"label\"\n      :rules=\"rules.label\"\n    >\n      <el-input\n        v-model=\"form_data.label\"\n        :maxlength=\"64\"\n        show-word-limit\n        :placeholder=\"$t('dynamicsForm.paramForm.name.placeholder')\"\n      />\n    </el-form-item>\n    <el-form-item :label=\"$t('dynamicsForm.paramForm.tooltip.label')\">\n      <el-input\n        v-model=\"form_data.tooltip\"\n        :maxlength=\"128\"\n        show-word-limit\n        :placeholder=\"$t('dynamicsForm.paramForm.tooltip.placeholder')\"\n      />\n    </el-form-item>\n    <el-form-item\n      :label=\"$t('dynamicsForm.paramForm.required.label')\"\n      :required=\"true\"\n      prop=\"required\"\n      :rules=\"rules.required\"\n      @click.prevent\n    >\n      <el-switch v-model=\"form_data.required\" :active-value=\"true\" :inactive-value=\"false\" />\n    </el-form-item>\n    <el-form-item\n      :label=\"$t('dynamicsForm.paramForm.input_type.label')\"\n      :required=\"true\"\n      prop=\"input_type\"\n      :rules=\"rules.input_type\"\n    >\n      <el-select\n        v-model=\"form_data.input_type\"\n        :placeholder=\"$t('dynamicsForm.paramForm.input_type.placeholder')\"\n      >\n        <el-option\n          v-for=\"input_type in input_type_list\"\n          :key=\"input_type.value\"\n          :label=\"input_type.label\"\n          :value=\"input_type.value\"\n        />\n      </el-select>\n    </el-form-item>\n    <component\n      v-if=\"form_data.input_type\"\n      ref=\"componentFormRef\"\n      v-model=\"form_data\"\n      :is=\"form_data.input_type\"\n    ></component>\n  </el-form>\n</template>\n<script setup lang=\"ts\">\nimport { onMounted, ref, nextTick } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport _ from 'lodash'\nimport { input_type_list as input_type_list_data } from '@/components/dynamics-form/constructor/data'\nimport { t } from '@/locales'\nconst props = withDefaults(\n  defineProps<{\n    modelValue?: any\n    input_type_list?: Array<{ label: string; value: string }>\n  }>(),\n  {\n    input_type_list: () =>\n      input_type_list_data.map((item) => ({\n        label: item.label,\n        value: item.value + 'Constructor',\n      })),\n  },\n)\nconst emit = defineEmits(['update:modelValue'])\n\nconst ruleFormRef = ref<FormInstance>()\n\nconst componentFormRef = ref<any>()\nconst form_data = ref<any>({\n  label: '',\n  field: '',\n  tooltip: '',\n  required: false,\n  input_type: '',\n})\nconst rules = {\n  label: [{ required: true, message: t('dynamicsForm.paramForm.name.requiredMessage') }],\n  field: [{ required: true, message: t('dynamicsForm.paramForm.field.requiredMessage') }],\n  required: [{ required: true, message: t('dynamicsForm.paramForm.required.requiredMessage') }],\n  input_type: [{ required: true, message: t('dynamicsForm.paramForm.input_type.requiredMessage') }],\n}\nconst getData = () => {\n  let label: string | any = form_data.value.label\n  if (form_data.value.tooltip) {\n    label = {\n      input_type: 'TooltipLabel',\n      label: form_data.value.label,\n      attrs: { tooltip: form_data.value.tooltip },\n      props_info: {},\n    }\n  }\n  return {\n    label: label,\n    required: form_data.value.required,\n    field: form_data.value.field,\n    default_value: form_data.value.default_value,\n    show_default_value: form_data.value.show_default_value,\n    ...componentFormRef.value.getData(),\n  }\n}\n\nconst validate = () => {\n  if (ruleFormRef.value) {\n    return ruleFormRef.value?.validate()\n  }\n  return Promise.resolve()\n}\n\nonMounted(() => {\n  if (props.modelValue) {\n    rander(props.modelValue)\n  }\n})\nconst rander = (data: any) => {\n  form_data.value.required = data.required ? data.required : false\n  form_data.value.field = data.field\n  if (data.show_default_value !== undefined) {\n    form_data.value.show_default_value = data.show_default_value\n  }\n  if (data.input_type) {\n    form_data.value.input_type = data.input_type + 'Constructor'\n  }\n\n  if (data.label && data.label.input_type === 'TooltipLabel') {\n    form_data.value.tooltip = data.label.attrs.tooltip\n    form_data.value.label = data.label.label\n  } else {\n    form_data.value.label = data.label\n  }\n  nextTick(() => {\n    componentFormRef.value?.rander(data)\n  })\n}\n\ndefineExpose({ getData, validate, rander })\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/DatePickerConstructor.vue",
    "content": "<template>\n  <el-form-item :label=\"$t('dynamicsForm.DatePicker.dataType.label')\" required>\n    <el-select\n      @change=\"type_change\"\n      v-model=\"formValue.type\"\n      :placeholder=\"$t('dynamicsForm.DatePicker.dataType.placeholder')\"\n    >\n      <el-option\n        v-for=\"input_type in type_list\"\n        :key=\"input_type.value\"\n        :label=\"input_type.label\"\n        :value=\"input_type.value\"\n      />\n    </el-select>\n  </el-form-item>\n  <el-form-item :label=\"$t('dynamicsForm.DatePicker.format.label')\" required>\n    <el-select\n      v-model=\"formValue.format\"\n      filterable\n      default-first-option\n      allow-create\n      :placeholder=\"$t('dynamicsForm.DatePicker.format.placeholder')\"\n    >\n      <el-option\n        v-for=\"input_type in type_dict[formValue.type]\"\n        :key=\"input_type.value\"\n        :label=\"input_type.value\"\n        :value=\"input_type.value\"\n      />\n    </el-select>\n  </el-form-item>\n  <el-form-item\n    class=\"defaultValueItem\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :label=\"$t('dynamicsForm.default.label')\"\n    :rules=\"\n      formValue.required\n        ? [\n            {\n              required: true,\n              message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}`,\n            },\n          ]\n        : []\n    \"\n  >\n    <div class=\"defaultValueCheckbox\">\n      <el-checkbox\n        v-model=\"formValue.show_default_value\"\n        :label=\"$t('dynamicsForm.default.show')\"\n      />\n    </div>\n    <el-date-picker\n      v-model=\"formValue.default_value\"\n      :type=\"formValue.type\"\n      :placeholder=\"$t('dynamicsForm.DatePicker.placeholder')\"\n      :format=\"formValue.format\"\n      :value-format=\"formValue.format\"\n    />\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onBeforeMount } from 'vue'\nimport moment from 'moment'\nimport { t } from '@/locales'\nconst type_list = [\n  {\n    label: t('dynamicsForm.DatePicker.year'),\n    value: 'year',\n  },\n  {\n    label: t('dynamicsForm.DatePicker.month'),\n    value: 'month',\n  },\n  {\n    label: t('dynamicsForm.DatePicker.date'),\n    value: 'date',\n  },\n  {\n    label: t('dynamicsForm.DatePicker.datetime'),\n    value: 'datetime',\n  },\n]\nconst type_dict: any = {\n  year: [{ value: 'YYYY' }],\n  month: [{ value: 'YYYY-MM' }],\n  date: [{ value: 'YYYY-MM-DD' }],\n  datetime: [{ value: 'YYYY-MM-DD HH:mm:ss' }],\n}\n\nconst type_change = () => {\n  formValue.value.format = type_dict[formValue.value.type][0].value\n  formValue.value.default_value = moment().format(formValue.value.format)\n}\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\n\nconst getData = () => {\n  return {\n    input_type: 'DatePicker',\n    attrs: {\n      type: formValue.value.type,\n      format: formValue.value.format,\n      'value-format': formValue.value.format,\n    },\n    default_value: formValue.value.default_value,\n    show_default_value: formValue.value.show_default_value,\n  }\n}\nconst rander = (form_data: any) => {\n  formValue.value.type = form_data.attrs.type\n  formValue.value.format = form_data.attrs?.format\n  formValue.value.default_value = form_data.default_value\n}\ndefineExpose({ getData, rander })\nonBeforeMount(() => {\n  formValue.value.type = 'datetime'\n  formValue.value.format = 'YYYY-MM-DD HH:mm:ss'\n  formValue.value.default_value = moment().format(formValue.value.format)\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.defaultValueItem {\n  position: relative;\n  .defaultValueCheckbox {\n    position: absolute;\n    right: 0;\n    top: -35px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/JsonInputConstructor.vue",
    "content": "<template>\n  <el-form-item v-if=\"getModel\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.AssignmentMethod.label', '赋值方式') }}\n      </div>\n    </template>\n\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-radio-group v-model=\"formValue.default_value_assignment_method\">\n        <el-radio :value=\"item.value\" size=\"large\" v-for=\"(item,index) in assignment_method_option_list\" :key=\"index\"\n          >{{ item.label }}\n          <el-popover\n            width=\"300px\"\n            v-if=\"item.value == 'ref_variables'\"\n            class=\"box-item\"\n            placement=\"top-start\"\n            :persistent=\"false\"\n          >\n            {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover') }}:\n            {{ $t('dynamicsForm.AssignmentMethod.ref_variables.json_format') }}\n\n            <template #reference>\n              <el-icon><InfoFilled /></el-icon>\n            </template>\n          </el-popover>\n        </el-radio>\n      </el-radio-group>\n    </el-row>\n  </el-form-item>\n  <el-form-item\n    v-if=\"formValue.default_value_assignment_method == 'ref_variables'\"\n    :required=\"true\"\n    prop=\"default_value\"\n    :rules=\"[default_ref_variables_value_rule]\"\n  >\n    <NodeCascader\n      ref=\"nodeCascaderRef\"\n      :nodeModel=\"model\"\n      class=\"w-full\"\n      :placeholder=\"$t('workflow.variable.placeholder')\"\n      v-model=\"formValue.default_value\"\n    />\n  </el-form-item>\n\n  <el-form-item\n    class=\"defaultValueItem\"\n    :label=\"$t('dynamicsForm.default.label')\"\n    :required=\"formValue.required\"\n    v-if=\"formValue.default_value_assignment_method == 'custom'\"\n    prop=\"default_value\"\n    :rules=\"[default_value_rule]\"\n  >\n    <div class=\"defaultValueCheckbox\">\n      <el-checkbox\n        v-model=\"formValue.show_default_value\"\n        :label=\"$t('dynamicsForm.default.show')\"\n      />\n    </div>\n    <JsonInput ref=\"jsonInputRef\" v-model=\"formValue.default_value\"> </JsonInput>\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, ref, inject, watch } from 'vue'\nimport { t } from '@/locales'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport JsonInput from '@/components/dynamics-form/items/JsonInput.vue'\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst getModel = inject('getModel') as any\n\nconst assignment_method_option_list = computed(() => {\n  const option_list = [\n    {\n      label: t('common.custom'),\n      value: 'custom',\n    },\n  ]\n  if (getModel) {\n    option_list.push({\n      label: t('workflow.variable.Referencing'),\n      value: 'ref_variables',\n    })\n  }\n  return option_list\n})\n\nconst model = computed(() => {\n  if (getModel) {\n    return getModel()\n  } else {\n    return null\n  }\n})\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\nconst jsonInputRef = ref<InstanceType<typeof JsonInput>>()\nconst getData = () => {\n  return {\n    input_type: 'JsonInput',\n    attrs: {},\n    props_info: {\n      rules: [\n        {\n          required: formValue.value.required,\n          validator: `validator = (rule, value, callback) => {\n            return componentFormRef.value?.validate_rules(rule, value, callback);\n\n}`,\n          trigger: 'blur',\n        },\n      ],\n    },\n    default_value: formValue.value.default_value,\n    show_default_value: formValue.value.show_default_value,\n    default_value_assignment_method: formValue.value.default_value_assignment_method || 'custom',\n  }\n}\n\nconst default_value_rule = {\n  required: true,\n  validator: (rule: any, value: any, callback: any) => {\n    jsonInputRef.value?.validate_rules(rule, value, callback)\n    return true\n  },\n  trigger: 'blur',\n}\nconst default_ref_variables_value_rule = {\n  required: true,\n  validator: (rule: any, value: any, callback: any) => {\n    if (!(Array.isArray(value) && value.length > 1)) {\n      callback(\n        t('workflow.variable.Referencing') + t('common.required'),\n      )\n    }\n\n    return true\n  },\n  trigger: 'blur',\n}\n\nconst rander = (form_data: any) => {\n  formValue.value.default_value = form_data.default_value\n  formValue.value.default_value_assignment_method =\n    form_data.default_value_assignment_method || 'custom'\n}\ndefineExpose({ getData, rander })\nonMounted(() => {\n  formValue.value.default_value = {}\n  formValue.value.default_value_assignment_method = 'custom'\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.defaultValueItem {\n  position: relative;\n  .defaultValueCheckbox {\n    position: absolute;\n    right: 0;\n    top: -35px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/ModelConstructor.vue",
    "content": "<template>\n  <el-form-item\n    :label=\"$t('views.model.modelForm.model_type.label')\"\n    required\n    prop=\"model_type\"\n    :rules=\"[{ required: true, message: $t('views.model.modelForm.model_type.requiredMessage') }]\"\n  >\n    <el-select\n      v-model=\"formValue.model_type\"\n      :placeholder=\"$t('views.model.modelForm.model_type.placeholder')\"\n      @change=\"handleModelTypeChange\"\n    >\n      <el-option\n        v-for=\"item in modelTypeList\"\n        :key=\"item.value\"\n        :label=\"item.text\"\n        :value=\"item.value\"\n      />\n    </el-select>\n  </el-form-item>\n\n  <el-form-item\n    :label=\"$t('views.model.modelForm.model_type.kexuan', '可选模型')\"\n    required\n    prop=\"provider_list\"\n    :rules=\"[\n      {\n        required: true,\n        message: $t('views.model.modelForm.model_type.requiredMessage'),\n        type: 'array',\n      },\n    ]\"\n  >\n    <div class=\"flex-between w-full\">\n      <ModelSelect\n        multiple\n        v-model=\"selectedIds\"\n        :placeholder=\"$t('views.application.form.voicePlay.placeholder')\"\n        :options=\"groupedModelOptions\"\n        @change=\"handleProviderListChange\"\n        :model-type=\"formValue.model_type\"\n      >\n        <template #tag>\n          <el-tag\n            v-for=\"provider in formValue.provider_list\"\n            :key=\"provider.model_id\"\n            closable\n            type=\"info\"\n            @close=\"removeSelectedModel(provider.model_id)\"\n            style=\"margin-right: 4px\"\n          >\n            <div class=\"flex align-center\">\n              <span\n                v-html=\"\n                  relatedObject(\n                    providerOptions,\n                    getModelInfo(provider.model_id)?.provider,\n                    'provider',\n                  )?.icon\n                \"\n                class=\"model-icon mr-4\"\n              ></span>\n              <span class=\"mr-4\">{{\n                relatedObject(\n                  providerOptions,\n                  getModelInfo(provider.model_id)?.provider,\n                  'provider',\n                )?.name\n              }}</span>\n              <span class=\"mr-4\"> > </span>\n              <span>{{ getModelInfo(provider.model_id)?.name }}</span>\n            </div>\n          </el-tag>\n        </template>\n      </ModelSelect>\n    </div>\n  </el-form-item>\n  <el-form-item\n    :label=\"$t('views.model.modelForm.model_type.moren', '默认模型')\"\n    required\n    :rules=\"[\n      {\n        required: true,\n        message: $t('views.model.modelForm.model_type.requiredMessage'),\n      },\n    ]\"\n    v-if=\"formValue.provider_list && formValue.provider_list.length > 0\"\n  >\n    <div class=\"flex-between w-full\">\n      <el-select\n        v-model=\"formValue.default_value\"\n        value-key=\"model_id\"\n        placeholder=\"请选择默认模型\"\n      >\n        <el-option-group\n          v-for=\"(modelList, providerName) in selectedModelsOptions\"\n          :key=\"providerName\"\n          :label=\"relatedObject(providerOptions, providerName, 'provider')?.name\"\n        >\n          <el-option\n            v-for=\"item in modelList\"\n            :key=\"item.id\"\n            :label=\"item.name\"\n            :value=\"getProviderItem(item.id)\"\n          >\n            <div class=\"flex\">\n              <span\n                v-html=\"relatedObject(providerOptions, providerName, 'provider')?.icon\"\n                class=\"model-icon mr-8\"\n              ></span>\n              <span>{{ item.name }}</span>\n            </div>\n          </el-option>\n        </el-option-group>\n      </el-select>\n      <div class=\"ml-8\">\n        <el-button @click=\"openParamSetting\" @refreshForm=\"handleParamRefresh\">\n          <el-icon>\n            <Operation />\n          </el-icon>\n        </el-button>\n      </div>\n    </div>\n  </el-form-item>\n  <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"handleParamRefresh\" />\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, inject, ref } from 'vue'\nimport { modelTypeList } from '@/views/model/component/data'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\nimport { groupBy } from 'lodash'\nimport { providerList as providerOptions } from '../../items/model/provider-data'\nimport { relatedObject } from '@/utils/array'\n\nconst getSelectModelList = inject('getSelectModelList') as Function\nconst getModelParamsForm = inject('getModelParamsForm') as Function\n\nconst props = defineProps<{\n  modelValue: any\n}>()\n\nconst emit = defineEmits(['update:modelValue'])\n\nconst formValue = computed({\n  set: (item: any) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\n\nconst selectedIds = computed({\n  get: () => (formValue.value.provider_list || []).map((p: any) => p.model_id),\n  set: (newIds: string[]) => {\n    const oldList = formValue.value.provider_list || []\n    const newList = newIds.map((id: string) => {\n      const existing = oldList.find((p: any) => p.model_id === id)\n      return existing || { model_id: id, model_params_setting: {} }\n    })\n    formValue.value.provider_list = newList\n    // find new model then get it default value\n    const oldIds = oldList.map((p: any) => p.model_id)\n    const addedIds = newIds.filter((id: string) => !oldIds.includes(id))\n    addedIds.forEach((id: string) => {\n      fetchDefaultParams(id)\n    })\n  },\n})\n\nconst selectedModelsOptions = computed(() => {\n  const ids = (formValue.value.provider_list || []).map((p: any) => p.model_id)\n  const filtered = rawModelOptions.value.filter((m: any) => ids.includes(m.id))\n  return groupBy(filtered, 'provider')\n})\n\nfunction fetchDefaultParams(modelId: string) {\n  if (!getModelParamsForm) return\n  getModelParamsForm(modelId).then((res: any) => {\n    const formFields = res?.data || []\n    const defaults = (res?.data || [])\n      .map((item: any) => {\n        if (item.show_default_value === false) {\n          return { [item.field]: undefined }\n        } else {\n          return { [item.field]: item.default_value }\n        }\n      })\n      .reduce((x: any, y: any) => ({ ...x, ...y }), {})\n    // update to model_params_setting\n    const target = formValue.value.provider_list.find((p: any) => p.model_id === modelId)\n    if (target) {\n      target.model_params_setting = defaults\n      target.model_form_field = formFields\n    }\n  })\n}\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\n\nconst openParamSetting = () => {\n  const dv = formValue.value.default_value\n  if (!dv?.model_id) return\n  AIModeParamSettingDialogRef.value?.open(dv.model_id, undefined, dv?.model_params_setting)\n}\n\nconst handleParamRefresh = (paramData: any) => {\n  const dv = formValue.value.default_value\n  if (dv?.model_id) {\n    formValue.value.default_value = { ...dv, model_params_setting: paramData }\n    const target = formValue.value.provider_list.find((p: any) => p.model_id === dv.model_id)\n    if (target) {\n      target.model_params_setting = paramData\n    }\n  }\n}\n\nconst rawModelOptions = ref<any[]>([])\nconst groupedModelOptions = ref<Record<string, any[]>>({})\n\nconst fetchModelByType = (type: string) => {\n  if (!type || !getSelectModelList) return\n\n  getSelectModelList({ model_type: type }).then((res: any) => {\n    rawModelOptions.value = res?.data || []\n\n    groupedModelOptions.value = groupBy(res?.data, 'provider')\n  })\n}\n\nconst handleModelTypeChange = (val: string) => {\n  formValue.value.provider_list = []\n  formValue.value.default_value = ''\n\n  if (val) {\n    fetchModelByType(val)\n  } else {\n    rawModelOptions.value = []\n    groupedModelOptions.value = {}\n  }\n}\n\nconst getModelInfo = (modelId: string) => {\n  return rawModelOptions.value.find((item: any) => item.id === modelId)\n}\n\n// default_value 赋值\nconst getProviderItem = (modelId: string) => {\n  const found = formValue.value.provider_list.find((p: any) => p.model_id === modelId)\n  if (found) {\n    const { model_form_field, ...rest } = found\n    return rest\n  }\n  return { model_id: modelId, model_params_setting: {} }\n}\n\nfunction handleProviderListChange() {\n  const ids = (formValue.value.provider_list || []).map((p: any) => p.model_id)\n  const currentId = formValue.value.default_value?.model_id\n\n  if (currentId && !ids.includes(currentId)) {\n    formValue.value.default_value = {}\n  }\n}\n\nfunction removeSelectedModel(modelId: string) {\n  formValue.value.provider_list = formValue.value.provider_list.filter(\n    (p: any) => p.model_id !== modelId,\n  )\n  handleProviderListChange()\n}\n\nconst getData = () => {\n  const providerList = (formValue.value.provider_list || []).map((p: any) => {\n    const modelInfo = getModelInfo(p.model_id)\n    return {\n      model_id: p.model_id,\n      model_name: modelInfo?.name || '',\n      provider: modelInfo?.provider || '',\n      model_params_setting: p.model_params_setting || {},\n      model_form_field: p.model_form_field || [],\n    }\n  })\n  return {\n    input_type: 'Model',\n    model_type: formValue.value.model_type,\n    default_value: formValue.value.default_value,\n    attrs: {\n      provider_list: providerList,\n    },\n  }\n}\n\nconst rander = (form_data: any) => {\n  formValue.value.model_type = form_data.model_type\n  formValue.value.provider_list = form_data.attrs?.provider_list || []\n  formValue.value.default_value = form_data.default_value || ''\n\n  if (form_data.model_type) {\n    fetchModelByType(form_data.model_type)\n  }\n}\n\ndefineExpose({ getData, rander })\n</script>\n<style lang=\"scss\" scoped>\n// AI模型选择：添加模型hover样式\n\n.model-icon {\n  width: 18px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/MultiRowConstructor.vue",
    "content": "<template>\n  <el-form-item v-if=\"getModel\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.AssignmentMethod.label', '赋值方式') }}\n      </div>\n    </template>\n\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-radio-group @change=\"formValue.option_list = []\" v-model=\"formValue.assignment_method\">\n        <el-radio :value=\"item.value\" size=\"large\" v-for=\"(item,index) in assignment_method_option_list\" :key=\"index\"\n          >{{ item.label }}\n          <el-popover\n            width=\"300px\"\n            v-if=\"item.value == 'ref_variables'\"\n            class=\"box-item\"\n            placement=\"top-start\"\n            :persistent=\"false\"\n          >\n            {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover') }}:<br />\n            [<br />\n            {<br />\n            \"label\": \"xx\",<br />\n            \"value\": \"xx\",<br />\n            \"default\": false<br />\n            }<br />\n            ]<br />\n            label: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_label') }}\n            {{ $t('common.required') }}<br />\n            value: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_value') }}\n            {{ $t('common.required') }}<br />\n            default:{{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_default') }}\n            <template #reference>\n              <el-icon><InfoFilled /></el-icon>\n            </template>\n          </el-popover>\n        </el-radio>\n      </el-radio-group>\n    </el-row>\n  </el-form-item>\n  <el-form-item\n    v-if=\"formValue.assignment_method == 'ref_variables'\"\n    :required=\"true\"\n    prop=\"option_list\"\n    :rules=\"[default_ref_variables_value_rule]\"\n  >\n    <NodeCascader\n      ref=\"nodeCascaderRef\"\n      :nodeModel=\"model\"\n      class=\"w-full\"\n      :placeholder=\"$t('workflow.variable.placeholder')\"\n      v-model=\"formValue.option_list\"\n    />\n  </el-form-item>\n  <el-form-item v-if=\"formValue.assignment_method == 'custom'\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.Select.label') }}\n        <el-button link type=\"primary\" @click.stop=\"addOption()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n    </template>\n\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-col :span=\"10\">\n        {{ $t('dynamicsForm.tag.label') }}\n      </el-col>\n      <el-col :span=\"12\">\n        {{ $t('dynamicsForm.Select.label') }}\n      </el-col>\n    </el-row>\n    <el-row\n      style=\"width: 100%\"\n      v-for=\"(option, $index) in formValue.option_list\"\n      :key=\"$index\"\n      :gutter=\"10\"\n      class=\"mb-8\"\n    >\n      <el-col :span=\"10\">\n        <el-input\n          v-model=\"formValue.option_list[$index].label\"\n          :placeholder=\"$t('dynamicsForm.tag.placeholder')\"\n        />\n      </el-col>\n      <el-col :span=\"12\">\n        <el-input\n          v-model=\"formValue.option_list[$index].value\"\n          :placeholder=\"$t('dynamicsForm.Select.label')\"\n        />\n      </el-col>\n      <el-col :span=\"1\">\n        <el-button link class=\"ml-8\" @click.stop=\"delOption($index)\">\n          <AppIcon iconName=\"app-delete\"></AppIcon>\n        </el-button>\n      </el-col>\n    </el-row>\n  </el-form-item>\n  <el-form-item\n    v-if=\"formValue.assignment_method == 'custom'\"\n    class=\"defaultValueItem\"\n    :label=\"$t('dynamicsForm.default.label')\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :rules=\"\n      formValue.required\n        ? [\n            {\n              required: true,\n              message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}`,\n            },\n          ]\n        : []\n    \"\n  >\n    <div class=\"defaultValueCheckbox\">\n      <el-checkbox\n        v-model=\"formValue.show_default_value\"\n        :label=\"$t('dynamicsForm.default.show')\"\n      />\n    </div>\n    <MultiRow\n      :form-field=\"formField\"\n      v-model=\"formValue.default_value\"\n      :other-params=\"{}\"\n      field=\"default_value\"\n    >\n    </MultiRow>\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, inject } from 'vue'\nimport MultiRow from '@/components/dynamics-form/items/MultiRow.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { t } from '@/locales'\nconst getModel = inject('getModel') as any\n\nconst assignment_method_option_list = computed(() => {\n  const option_list = [\n    {\n      label: t('common.custom'),\n      value: 'custom',\n    },\n  ]\n  if (getModel) {\n    option_list.push({\n      label: t('workflow.variable.Referencing'),\n      value: 'ref_variables',\n    })\n  }\n  return option_list\n})\n\nconst model = computed(() => {\n  if (getModel) {\n    return getModel()\n  } else {\n    return null\n  }\n})\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\n\nconst default_ref_variables_value_rule = {\n  required: true,\n  validator: (rule: any, value: any, callback: any) => {\n    console.log(value.length)\n    if (!(Array.isArray(value) && value.length > 1)) {\n      callback(t('workflow.variable.Referencing') + t('common.required'))\n    }\n\n    return true\n  },\n  trigger: 'blur',\n}\nconst addOption = () => {\n  formValue.value.option_list.push({ value: '', label: '' })\n}\n\nconst delOption = (index: number) => {\n  const option = formValue.value.option_list[index]\n  if (option.value && formValue.value.default_value == option.value) {\n    formValue.value.default_value = ''\n  }\n  formValue.value.option_list.splice(index, 1)\n}\nconst formField = computed<FormField>(() => {\n  return { field: '', ...getData() }\n})\nconst getData = () => {\n  return {\n    input_type: 'MultiRow',\n    attrs: {},\n    default_value: formValue.value.default_value,\n    text_field: 'label',\n    value_field: 'value',\n    option_list: formValue.value.option_list,\n    assignment_method: formValue.value.assignment_method || 'custom',\n  }\n}\nconst rander = (form_data: any) => {\n  formValue.value.option_list = form_data.option_list || []\n  formValue.value.default_value = form_data.default_value\n  formValue.value.assignment_method = form_data.assignment_method || 'custom'\n}\n\ndefineExpose({ getData, rander })\nonMounted(() => {\n  formValue.value.option_list = []\n  formValue.value.default_value = ''\n  formValue.value.assignment_method = 'custom'\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n  addOption()\n})\n</script>\n<style lang=\"scss\" scoped>\n.defaultValueItem {\n  position: relative;\n  .defaultValueCheckbox {\n    position: absolute;\n    right: 0;\n    top: -35px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/MultiSelectConstructor.vue",
    "content": "<template>\n  <el-form-item v-if=\"getModel\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.AssignmentMethod.label', '赋值方式') }}\n      </div>\n    </template>\n\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-radio-group @change=\"formValue.option_list = []\" v-model=\"formValue.assignment_method\">\n        <el-radio :value=\"item.value\" size=\"large\" v-for=\"(item,index) in assignment_method_option_list\" :key=\"index\"\n          >{{ item.label }}\n          <el-popover\n            width=\"300px\"\n            v-if=\"item.value == 'ref_variables'\"\n            class=\"box-item\"\n            placement=\"top-start\"\n            :persistent=\"false\"\n          >\n            {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover') }}:<br />\n            [<br />\n            {<br />\n            \"label\": \"xx\",<br />\n            \"value\": \"xx\",<br />\n            \"default\": false<br />\n            }<br />\n            ]<br />\n            label: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_label') }}\n            {{ $t('common.required') }}<br />\n            value: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_value') }}\n            {{ $t('common.required') }}<br />\n            default: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_default') }}\n            <template #reference>\n              <el-icon><InfoFilled /></el-icon>\n            </template>\n          </el-popover>\n        </el-radio>\n      </el-radio-group>\n    </el-row>\n  </el-form-item>\n  <el-form-item\n    v-if=\"formValue.assignment_method == 'ref_variables'\"\n    :required=\"true\"\n    prop=\"option_list\"\n    :rules=\"[default_ref_variables_value_rule]\"\n  >\n    <NodeCascader\n      ref=\"nodeCascaderRef\"\n      :nodeModel=\"model\"\n      class=\"w-full\"\n      :placeholder=\"$t('workflow.variable.placeholder')\"\n      v-model=\"formValue.option_list\"\n    />\n  </el-form-item>\n\n  <el-form-item v-if=\"formValue.assignment_method == 'custom'\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.Select.label') }}\n        <el-button link type=\"primary\" @click.stop=\"addOption()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n    </template>\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-col :span=\"10\">\n        {{ $t('dynamicsForm.tag.label') }}\n      </el-col>\n      <el-col :span=\"12\">\n        {{ $t('dynamicsForm.Select.label') }}\n      </el-col>\n    </el-row>\n    <el-row\n      style=\"width: 100%\"\n      v-for=\"(option, $index) in formValue.option_list\"\n      :key=\"$index\"\n      :gutter=\"10\"\n      class=\"mb-8\"\n    >\n      <el-col :span=\"10\">\n        <el-input\n          v-model=\"formValue.option_list[$index].label\"\n          :placeholder=\"$t('dynamicsForm.tag.placeholder')\"\n        />\n      </el-col>\n      <el-col :span=\"12\">\n        <el-input\n          v-model=\"formValue.option_list[$index].value\"\n          :placeholder=\"$t('dynamicsForm.Select.label')\"\n        />\n      </el-col>\n      <el-col :span=\"1\">\n        <el-button link class=\"ml-8\" @click.stop=\"delOption($index)\">\n          <AppIcon iconName=\"app-delete\"></AppIcon>\n        </el-button>\n      </el-col>\n    </el-row>\n  </el-form-item>\n  <el-form-item\n    v-if=\"formValue.assignment_method == 'custom'\"\n    class=\"defaultValueItem\"\n    :label=\"$t('dynamicsForm.default.label')\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :rules=\"\n      formValue.required\n        ? [\n            {\n              required: true,\n              message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}`,\n            },\n          ]\n        : []\n    \"\n  >\n    <div class=\"defaultValueCheckbox\">\n      <el-checkbox\n        v-model=\"formValue.show_default_value\"\n        :label=\"$t('dynamicsForm.default.show')\"\n      />\n    </div>\n    <el-select\n      class=\"m-2\"\n      multiple\n      collapse-tags\n      filterable\n      clearable\n      :reserve-keyword=\"false\"\n      v-model=\"formValue.default_value\"\n      :teleported=\"false\"\n      popper-class=\"max-w-350\"\n    >\n      <el-option\n        v-for=\"(option, index) in formValue.option_list\"\n        :key=\"index\"\n        :label=\"option.label\"\n        :value=\"option.value\"\n      />\n    </el-select>\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, inject, watch } from 'vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport { t } from '@/locales'\nconst getModel = inject('getModel') as any\n\nconst assignment_method_option_list = computed(() => {\n  const option_list = [\n    {\n      label: t('common.custom'),\n      value: 'custom',\n    },\n  ]\n  if (getModel) {\n    option_list.push({\n      label: t('workflow.variable.Referencing'),\n      value: 'ref_variables',\n    })\n  }\n  return option_list\n})\n\nconst model = computed(() => {\n  if (getModel) {\n    return getModel()\n  } else {\n    return null\n  }\n})\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\n\nconst default_ref_variables_value_rule = {\n  required: true,\n  validator: (rule: any, value: any, callback: any) => {\n    console.log(value.length)\n    if (!(Array.isArray(value) && value.length > 1)) {\n      callback(t('workflow.variable.Referencing') + t('common.required'))\n    }\n\n    return true\n  },\n  trigger: 'blur',\n}\nconst addOption = () => {\n  formValue.value.option_list.push({ value: '', label: '' })\n}\n\nconst delOption = (index: number) => {\n  const option = formValue.value.option_list[index]\n  if (option.value && formValue.value.default_value == option.value) {\n    formValue.value.default_value = ''\n  }\n  formValue.value.option_list.splice(index, 1)\n}\n\nconst getData = () => {\n  return {\n    input_type: 'MultiSelect',\n    attrs: {},\n    default_value: formValue.value.default_value,\n    show_default_value: formValue.value.show_default_value,\n    text_field: 'label',\n    value_field: 'value',\n    option_list: formValue.value.option_list,\n    assignment_method: formValue.value.assignment_method || 'custom',\n  }\n}\nconst rander = (form_data: any) => {\n  formValue.value.option_list = form_data.option_list || []\n  formValue.value.default_value = form_data.default_value\n  formValue.value.assignment_method = form_data.assignment_method || 'custom'\n}\n\ndefineExpose({ getData, rander })\nonMounted(() => {\n  formValue.value.option_list = []\n  formValue.value.default_value = ''\n  formValue.value.assignment_method = 'custom'\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n  addOption()\n})\n</script>\n<style lang=\"scss\" scoped>\n.defaultValueItem {\n  position: relative;\n  .defaultValueCheckbox {\n    position: absolute;\n    right: 0;\n    top: -35px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/PasswordInputConstructor.vue",
    "content": "<template>\n  <el-form-item :label=\"$t('dynamicsForm.TextInput.length.label')\" required>\n    <el-row class=\"w-full\">\n      <el-col :span=\"11\">\n        <el-form-item\n          :rules=\"[\n            {\n              required: true,\n              message: $t('dynamicsForm.TextInput.length.minRequired'),\n              trigger: 'change'\n            }\n          ]\"\n          prop=\"minlength\"\n        >\n          <el-input-number\n            style=\"width: 100%\"\n            :min=\"1\"\n            :step=\"1\"\n            step-strictly\n            v-model=\"formValue.minlength\"\n            controls-position=\"right\"\n          />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"2\" class=\"text-center\">\n        <span>-</span>\n      </el-col>\n      <el-col :span=\"11\">\n        <el-form-item\n          :rules=\"[\n            {\n              required: true,\n              message: $t('dynamicsForm.TextInput.length.maxRequired'),\n              trigger: 'change'\n            }\n          ]\"\n          prop=\"maxlength\"\n        >\n          <el-input-number\n            style=\"width: 100%\"\n            :min=\"formValue.minlength > formValue.maxlength ? formValue.minlength : 1\"\n            step-strictly\n            :step=\"1\"\n            v-model=\"formValue.maxlength\"\n            controls-position=\"right\"\n          />\n        </el-form-item>\n      </el-col>\n    </el-row>\n  </el-form-item>\n\n  <el-form-item\n    class=\"defaultValueItem\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :label=\"$t('dynamicsForm.default.label')\"\n    :rules=\"\n      formValue.required ? [{ required: true, message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}` }, ...rules] : rules\n    \"\n  >\n    <div class=\"defaultValueCheckbox\">\n      <el-checkbox\n        v-model=\"formValue.show_default_value\"\n        :label=\"$t('dynamicsForm.default.show')\"\n      />\n    </div>\n\n    <el-input\n      v-model=\"formValue.default_value\"\n      :maxlength=\"formValue.maxlength\"\n      :minlength=\"formValue.minlength\"\n      :placeholder=\"$t('dynamicsForm.default.placeholder')\"\n      show-word-limit\n      type=\"password\"\n      show-password\n    />\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, watch } from 'vue'\nimport { t } from '@/locales'\n\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  }\n})\nwatch(\n  () => formValue.value.minlength,\n  () => {\n    if (formValue.value.minlength > formValue.value.maxlength) {\n      formValue.value.maxlength = formValue.value.minlength\n    }\n  }\n)\nconst getData = () => {\n  return {\n    input_type: 'PasswordInput',\n    attrs: {\n      maxlength: formValue.value.maxlength,\n      minlength: formValue.value.minlength,\n      'show-word-limit': true,\n      type: 'password',\n      'show-password': true\n    },\n    default_value: formValue.value.default_value,\n    show_default_value: formValue.value.show_default_value,\n    props_info: {\n      rules: formValue.value.required\n        ? [\n          {\n            required: true,\n            message: `${formValue.value.label} ${t('dynamicsForm.default.requiredMessage')}`\n          },\n          {\n            min: formValue.value.minlength,\n            max: formValue.value.maxlength,\n            message: `${formValue.value.label}${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,\n            trigger: 'blur'\n          }\n        ]\n        : [\n          {\n            min: formValue.value.minlength,\n            max: formValue.value.maxlength,\n            message: `${formValue.value.label}${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,\n            trigger: 'blur'\n          }\n        ]\n    }\n  }\n}\nconst rander = (form_data: any) => {\n  const attrs = form_data.attrs || {}\n  formValue.value.minlength = attrs.minlength\n  formValue.value.maxlength = attrs.maxlength\n  formValue.value.default_value = form_data.default_value\n  formValue.value.show_default_value = form_data.show_default_value\n  formValue.value.show_password = attrs['show-password']\n}\nconst rangeRules = [\n  {\n    required: true,\n    validator: (rule: any, value: any, callback: any) => {\n      if (!formValue.value.minlength) {\n        callback(new Error(t('dynamicsForm.TextInput.length.requiredMessage4')))\n      }\n      if (!formValue.value.maxlength) {\n        callback(new Error(t('dynamicsForm.TextInput.length.requiredMessage4')))\n      }\n      return true\n    },\n    message: `${formValue.value.label} ${t('dynamicsForm.default.requiredMessage')}`\n  }\n]\nconst rules = computed(() => [\n  {\n    min: formValue.value.minlength,\n    max: formValue.value.maxlength,\n    message: `${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,\n    trigger: 'blur'\n  }\n])\n\ndefineExpose({ getData, rander })\nonMounted(() => {\n  formValue.value.minlength = 0\n  formValue.value.maxlength = 200\n  formValue.value.default_value = ''\n  formValue.value.show_password = true\n\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.defaultValueItem {\n  position: relative;\n\n  .defaultValueCheckbox {\n    position: absolute;\n    right: 0;\n    top: -35px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/RadioCardConstructor.vue",
    "content": "<template>\n  <el-form-item v-if=\"getModel\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.AssignmentMethod.label', '赋值方式') }}\n      </div>\n    </template>\n\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-radio-group @change=\"formValue.option_list = []\" v-model=\"formValue.assignment_method\">\n        <el-radio :value=\"item.value\" size=\"large\" v-for=\"(item,index) in assignment_method_option_list\" :key=\"index\"\n          >{{ item.label }}\n          <el-popover\n            width=\"300px\"\n            v-if=\"item.value == 'ref_variables'\"\n            class=\"box-item\"\n            placement=\"top-start\"\n            :persistent=\"false\"\n          >\n            {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover') }}:<br />\n            [<br />\n            {<br />\n            \"label\": \"xx\",<br />\n            \"value\": \"xx\",<br />\n            \"default\": false<br />\n            }<br />\n            ]<br />\n            label: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_label') }}\n            {{ $t('common.required') }}<br />\n            value: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_value') }}\n            {{ $t('common.required') }}<br />\n            default:{{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_default') }}\n            <template #reference>\n              <el-icon><InfoFilled /></el-icon>\n            </template>\n          </el-popover>\n        </el-radio>\n      </el-radio-group>\n    </el-row>\n  </el-form-item>\n  <el-form-item\n    v-if=\"formValue.assignment_method == 'ref_variables'\"\n    :required=\"true\"\n    prop=\"option_list\"\n    :rules=\"[default_ref_variables_value_rule]\"\n  >\n    <NodeCascader\n      ref=\"nodeCascaderRef\"\n      :nodeModel=\"model\"\n      class=\"w-full\"\n      :placeholder=\"$t('workflow.variable.placeholder')\"\n      v-model=\"formValue.option_list\"\n    />\n  </el-form-item>\n  <el-form-item v-if=\"formValue.assignment_method === 'custom'\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.Select.label') }}\n        <el-button link type=\"primary\" @click.stop=\"addOption()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n    </template>\n\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-col :span=\"10\">\n        {{ $t('dynamicsForm.tag.label') }}\n      </el-col>\n      <el-col :span=\"12\">\n        {{ $t('dynamicsForm.Select.label') }}\n      </el-col>\n    </el-row>\n    <el-row\n      style=\"width: 100%\"\n      v-for=\"(option, $index) in formValue.option_list\"\n      :key=\"$index\"\n      :gutter=\"10\"\n      class=\"mb-8\"\n    >\n      <el-col :span=\"10\">\n        <el-input\n          v-model=\"formValue.option_list[$index].label\"\n          :placeholder=\"$t('dynamicsForm.tag.placeholder')\"\n        />\n      </el-col>\n      <el-col :span=\"12\">\n        <el-input\n          v-model=\"formValue.option_list[$index].value\"\n          :placeholder=\"$t('dynamicsForm.Select.label')\"\n        />\n      </el-col>\n      <el-col :span=\"1\">\n        <el-button link class=\"ml-8\" @click.stop=\"delOption($index)\">\n          <AppIcon iconName=\"app-delete\"></AppIcon>\n        </el-button>\n      </el-col>\n    </el-row>\n  </el-form-item>\n\n  <el-form-item\n    v-if=\"formValue.assignment_method === 'custom'\"\n    class=\"defaultValueItem\"\n    :label=\"$t('dynamicsForm.default.label')\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :rules=\"\n      formValue.required\n        ? [\n            {\n              required: true,\n              message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}`,\n            },\n          ]\n        : []\n    \"\n  >\n    <div class=\"defaultValueCheckbox\">\n      <el-checkbox\n        v-model=\"formValue.show_default_value\"\n        :label=\"$t('dynamicsForm.default.show')\"\n      />\n    </div>\n    <RadioCard\n      :form-field=\"formField\"\n      v-model=\"formValue.default_value\"\n      :other-params=\"{}\"\n      field=\"default_value\"\n    >\n    </RadioCard>\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, inject } from 'vue'\nimport RadioCard from '@/components/dynamics-form/items/radio/RadioCard.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport { t } from '@/locales'\nconst getModel = inject('getModel') as any\n\nconst assignment_method_option_list = computed(() => {\n  const option_list = [\n    {\n      label: t('common.custom'),\n      value: 'custom',\n    },\n  ]\n  if (getModel) {\n    option_list.push({\n      label: t('workflow.variable.Referencing'),\n      value: 'ref_variables',\n    })\n  }\n  return option_list\n})\n\nconst model = computed(() => {\n  if (getModel) {\n    return getModel()\n  } else {\n    return null\n  }\n})\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\nconst default_ref_variables_value_rule = {\n  required: true,\n  validator: (rule: any, value: any, callback: any) => {\n    console.log(value.length)\n    if (!(Array.isArray(value) && value.length > 1)) {\n      callback(\n        t('workflow.variable.Referencing') + t('common.required'),\n      )\n    }\n\n    return true\n  },\n  trigger: 'blur',\n}\nconst addOption = () => {\n  formValue.value.option_list.push({ value: '', label: '' })\n}\n\nconst delOption = (index: number) => {\n  const option = formValue.value.option_list[index]\n  if (option.value && formValue.value.default_value == option.value) {\n    formValue.value.default_value = ''\n  }\n  formValue.value.option_list.splice(index, 1)\n}\nconst formField = computed(() => {\n  return { field: '', ...getData() }\n})\nconst getData = () => {\n  return {\n    input_type: 'RadioCard',\n    attrs: {},\n    default_value: formValue.value.default_value,\n    show_default_value: formValue.value.show_default_value,\n    text_field: 'label',\n    value_field: 'value',\n    option_list: formValue.value.option_list,\n    assignment_method: formValue.value.assignment_method || 'custom',\n  }\n}\nconst rander = (form_data: any) => {\n  formValue.value.option_list = form_data.option_list || []\n  formValue.value.default_value = form_data.default_value\n  formValue.value.assignment_method = form_data.assignment_method || 'custom'\n}\n\ndefineExpose({ getData, rander })\nonMounted(() => {\n  formValue.value.option_list = []\n  formValue.value.default_value = ''\n  formValue.value.assignment_method = 'custom'\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n  addOption()\n})\n</script>\n<style lang=\"scss\" scoped>\n.defaultValueItem {\n  position: relative;\n  .defaultValueCheckbox {\n    position: absolute;\n    right: 0;\n    top: -35px;\n  }\n}\n\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/RadioRowConstructor.vue",
    "content": "<template>\n  <el-form-item v-if=\"getModel\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.AssignmentMethod.label', '赋值方式') }}\n      </div>\n    </template>\n\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-radio-group @change=\"formValue.option_list = []\" v-model=\"formValue.assignment_method\">\n        <el-radio\n          :value=\"item.value\"\n          size=\"large\"\n          v-for=\"(item, index) in assignment_method_option_list\"\n          :key=\"index\"\n          >{{ item.label }}\n          <el-popover\n            width=\"300px\"\n            v-if=\"item.value == 'ref_variables'\"\n            class=\"box-item\"\n            placement=\"top-start\"\n            :persistent=\"false\"\n          >\n            {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover') }}:<br />\n            [<br />\n            {<br />\n            \"label\": \"xx\",<br />\n            \"value\": \"xx\",<br />\n            \"default\": false<br />\n            }<br />\n            ]<br />\n            label: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_label') }}\n            {{ $t('common.required') }}<br />\n            value: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_value') }}\n            {{ $t('common.required') }}<br />\n            default:{{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_default') }}\n            <template #reference>\n              <el-icon><InfoFilled /></el-icon>\n            </template>\n          </el-popover>\n        </el-radio>\n      </el-radio-group>\n    </el-row>\n  </el-form-item>\n  <el-form-item\n    v-if=\"formValue.assignment_method == 'ref_variables'\"\n    :required=\"true\"\n    prop=\"option_list\"\n    :rules=\"[default_ref_variables_value_rule]\"\n  >\n    <NodeCascader\n      ref=\"nodeCascaderRef\"\n      :nodeModel=\"model\"\n      class=\"w-full\"\n      :placeholder=\"$t('workflow.variable.placeholder')\"\n      v-model=\"formValue.option_list\"\n    />\n  </el-form-item>\n  <el-form-item v-if=\"formValue.assignment_method == 'custom'\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.Select.label') }}\n        <el-button link type=\"primary\" @click.stop=\"addOption()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n    </template>\n\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-col :span=\"10\">\n        {{ $t('dynamicsForm.tag.label') }}\n      </el-col>\n      <el-col :span=\"12\">\n        {{ $t('dynamicsForm.Select.label') }}\n      </el-col>\n    </el-row>\n    <el-row\n      style=\"width: 100%\"\n      v-for=\"(option, $index) in formValue.option_list\"\n      :key=\"$index\"\n      :gutter=\"10\"\n      class=\"mb-8\"\n    >\n      <el-col :span=\"10\">\n        <el-input\n          v-model=\"formValue.option_list[$index].label\"\n          :placeholder=\"$t('dynamicsForm.tag.placeholder')\"\n        />\n      </el-col>\n      <el-col :span=\"12\">\n        <el-input\n          v-model=\"formValue.option_list[$index].value\"\n          :placeholder=\"$t('dynamicsForm.Select.label')\"\n        />\n      </el-col>\n      <el-col :span=\"1\">\n        <el-button link class=\"ml-8\" @click.stop=\"delOption($index)\">\n          <AppIcon iconName=\"app-delete\"></AppIcon>\n        </el-button>\n      </el-col>\n    </el-row>\n  </el-form-item>\n  <el-form-item\n    v-if=\"formValue.assignment_method == 'custom'\"\n    class=\"defaultValueItem\"\n    :label=\"$t('dynamicsForm.default.label')\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :rules=\"\n      formValue.required\n        ? [\n            {\n              required: true,\n              message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}`,\n            },\n          ]\n        : []\n    \"\n  >\n    <div class=\"defaultValueCheckbox\">\n      <el-checkbox\n        v-model=\"formValue.show_default_value\"\n        :label=\"$t('dynamicsForm.default.show')\"\n      />\n    </div>\n    <RadioRow\n      :form-field=\"formField\"\n      v-model=\"formValue.default_value\"\n      :other-params=\"{}\"\n      field=\"default_value\"\n    >\n    </RadioRow>\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, inject, watch } from 'vue'\nimport RadioRow from '@/components/dynamics-form/items/radio/RadioRow.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { t } from '@/locales'\nconst getModel = inject('getModel') as any\n\nconst assignment_method_option_list = computed(() => {\n  const option_list = [\n    {\n      label: t('common.custom'),\n      value: 'custom',\n    },\n  ]\n  if (getModel) {\n    option_list.push({\n      label: t('workflow.variable.Referencing'),\n      value: 'ref_variables',\n    })\n  }\n  return option_list\n})\n\nconst model = computed(() => {\n  if (getModel) {\n    return getModel()\n  } else {\n    return null\n  }\n})\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\n\nconst default_ref_variables_value_rule = {\n  required: true,\n  validator: (rule: any, value: any, callback: any) => {\n    console.log(value.length)\n    if (!(Array.isArray(value) && value.length > 1)) {\n      callback(t('workflow.variable.Referencing') + t('common.required'))\n    }\n\n    return true\n  },\n  trigger: 'blur',\n}\nconst addOption = () => {\n  formValue.value.option_list.push({ value: '', label: '' })\n}\n\nconst delOption = (index: number) => {\n  const option = formValue.value.option_list[index]\n  if (option.value && formValue.value.default_value == option.value) {\n    formValue.value.default_value = ''\n  }\n  formValue.value.option_list.splice(index, 1)\n}\nconst formField = computed<FormField>(() => {\n  return { field: '', ...getData() }\n})\nconst getData = () => {\n  return {\n    input_type: 'RadioRow',\n    attrs: {},\n    default_value: formValue.value.default_value,\n    text_field: 'label',\n    value_field: 'value',\n    option_list: formValue.value.option_list,\n    assignment_method: formValue.value.assignment_method || 'custom',\n  }\n}\nconst rander = (form_data: any) => {\n  formValue.value.option_list = form_data.option_list || []\n  formValue.value.default_value = form_data.default_value\n  formValue.value.assignment_method = form_data.assignment_method || 'custom'\n}\n\ndefineExpose({ getData, rander })\nonMounted(() => {\n  formValue.value.option_list = []\n  formValue.value.default_value = ''\n  formValue.value.assignment_method = 'custom'\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n  addOption()\n})\n</script>\n<style lang=\"scss\" scoped>\n.defaultValueItem {\n  position: relative;\n  .defaultValueCheckbox {\n    position: absolute;\n    right: 0;\n    top: -35px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/SingleSelectConstructor.vue",
    "content": "<template>\n  <el-form-item v-if=\"getModel\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.AssignmentMethod.label', '赋值方式') }}\n      </div>\n    </template>\n\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-radio-group @change=\"formValue.option_list = []\" v-model=\"formValue.assignment_method\">\n        <el-radio\n          :value=\"item.value\"\n          size=\"large\"\n          v-for=\"(item, index) in assignment_method_option_list\"\n          :key=\"index\"\n          >{{ item.label }}\n          <el-popover\n            width=\"300px\"\n            v-if=\"item.value == 'ref_variables'\"\n            class=\"box-item\"\n            placement=\"top-start\"\n            :persistent=\"false\"\n          >\n            {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover') }}:<br />\n            [<br />\n            {<br />\n            \"label\": \"xx\",<br />\n            \"value\": \"xx\",<br />\n            \"default\": false<br />\n            }<br />\n            ]<br />\n            label: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_label') }}\n            {{ $t('common.required') }}<br />\n            value: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_value') }}\n            {{ $t('common.required') }}<br />\n            default: {{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover_default') }}\n            <template #reference>\n              <el-icon><InfoFilled /></el-icon>\n            </template>\n          </el-popover>\n        </el-radio>\n      </el-radio-group>\n    </el-row>\n  </el-form-item>\n  <el-form-item\n    v-if=\"formValue.assignment_method == 'ref_variables'\"\n    :required=\"true\"\n    prop=\"option_list\"\n    :rules=\"[default_ref_variables_value_rule]\"\n  >\n    <NodeCascader\n      ref=\"nodeCascaderRef\"\n      :nodeModel=\"model\"\n      class=\"w-full\"\n      :placeholder=\"$t('workflow.variable.placeholder')\"\n      v-model=\"formValue.option_list\"\n    />\n  </el-form-item>\n  <el-form-item v-if=\"formValue.assignment_method == 'custom'\">\n    <template #label>\n      <div class=\"flex-between\">\n        {{ $t('dynamicsForm.Select.label') }}\n        <el-button link type=\"primary\" @click.stop=\"addOption()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n    </template>\n\n    <el-row style=\"width: 100%\" :gutter=\"10\">\n      <el-col :span=\"10\">\n        {{ $t('dynamicsForm.tag.label') }}\n      </el-col>\n      <el-col :span=\"12\">\n        {{ $t('dynamicsForm.Select.label') }}\n      </el-col>\n    </el-row>\n    <el-row\n      style=\"width: 100%\"\n      v-for=\"(option, $index) in formValue.option_list\"\n      :key=\"$index\"\n      :gutter=\"10\"\n      class=\"mb-8\"\n    >\n      <el-col :span=\"10\">\n        <el-input\n          v-model=\"formValue.option_list[$index].label\"\n          :placeholder=\"$t('dynamicsForm.tag.placeholder')\"\n        />\n      </el-col>\n      <el-col :span=\"12\">\n        <el-input\n          v-model=\"formValue.option_list[$index].value\"\n          :placeholder=\"$t('dynamicsForm.Select.label')\"\n        />\n      </el-col>\n      <el-col :span=\"1\">\n        <el-button link class=\"ml-8\" @click.stop=\"delOption($index)\">\n          <AppIcon iconName=\"app-delete\"></AppIcon>\n        </el-button>\n      </el-col>\n    </el-row>\n  </el-form-item>\n  <el-form-item\n    v-if=\"formValue.assignment_method == 'custom'\"\n    class=\"defaultValueItem\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :label=\"$t('dynamicsForm.default.label')\"\n    :rules=\"\n      formValue.required\n        ? [\n            {\n              required: true,\n              message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}`,\n            },\n          ]\n        : []\n    \"\n  >\n    <div class=\"defaultValueCheckbox\">\n      <el-checkbox\n        v-model=\"formValue.show_default_value\"\n        :label=\"$t('dynamicsForm.default.show')\"\n      />\n    </div>\n\n    <el-select v-model=\"formValue.default_value\" :teleported=\"false\" popper-class=\"max-w-350\">\n      <el-option\n        v-for=\"(option, index) in formValue.option_list\"\n        :key=\"index\"\n        :label=\"option.label\"\n        :value=\"option.value\"\n      />\n    </el-select>\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, inject, watch } from 'vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport { t } from '@/locales'\nconst getModel = inject('getModel') as any\n\nconst assignment_method_option_list = computed(() => {\n  const option_list = [\n    {\n      label: t('common.custom'),\n      value: 'custom',\n    },\n  ]\n  if (getModel) {\n    option_list.push({\n      label: t('workflow.variable.Referencing'),\n      value: 'ref_variables',\n    })\n  }\n  return option_list\n})\n\nconst model = computed(() => {\n  if (getModel) {\n    return getModel()\n  } else {\n    return null\n  }\n})\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\n\nconst default_ref_variables_value_rule = {\n  required: true,\n  validator: (rule: any, value: any, callback: any) => {\n    console.log(value.length)\n    if (!(Array.isArray(value) && value.length > 1)) {\n      callback(t('workflow.variable.Referencing') + t('common.required'))\n    }\n\n    return true\n  },\n  trigger: 'blur',\n}\nconst addOption = () => {\n  formValue.value.option_list.push({ value: '', label: '' })\n}\n\nconst delOption = (index: number) => {\n  const option = formValue.value.option_list[index]\n  if (option.value && formValue.value.default_value == option.value) {\n    formValue.value.default_value = ''\n  }\n  formValue.value.option_list.splice(index, 1)\n}\n\nconst getData = () => {\n  return {\n    input_type: 'SingleSelect',\n    attrs: {},\n    default_value: formValue.value.default_value,\n    show_default_value: formValue.value.show_default_value,\n    text_field: 'label',\n    value_field: 'value',\n    option_list: formValue.value.option_list,\n    assignment_method: formValue.value.assignment_method || 'custom',\n  }\n}\nconst rander = (form_data: any) => {\n  formValue.value.option_list = form_data.option_list || []\n  formValue.value.default_value = form_data.default_value\n  formValue.value.show_default_value = form_data.show_default_value\n  formValue.value.assignment_method = form_data.assignment_method || 'custom'\n}\n\ndefineExpose({ getData, rander })\nonMounted(() => {\n  formValue.value.option_list = []\n  formValue.value.default_value = ''\n  formValue.value.assignment_method = 'custom'\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n  addOption()\n})\n</script>\n<style lang=\"scss\" scoped>\n.defaultValueItem {\n  position: relative;\n  .defaultValueCheckbox {\n    position: absolute;\n    right: 0;\n    top: -35px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/SliderConstructor.vue",
    "content": "<template>\n  <el-form-item\n    :label=\"$t('dynamicsForm.Slider.showInput.label')\"\n    required\n    prop=\"showInput\"\n    @click.prevent\n  >\n    <el-switch v-model=\"formValue.showInput\" />\n  </el-form-item>\n  <el-form-item :label=\"$t('dynamicsForm.Slider.valueRange.label')\" required>\n    <el-col :span=\"11\" style=\"padding-left: 0\">\n      <el-form-item\n        :rules=\"[\n          {\n            required: true,\n            message: $t('dynamicsForm.Slider.valueRange.minRequired'),\n            trigger: 'change',\n          },\n        ]\"\n        prop=\"min\"\n      >\n        <el-input-number style=\"width: 100%\" v-model=\"formValue.min\" controls-position=\"right\"\n      /></el-form-item>\n    </el-col>\n    <el-col :span=\"2\" class=\"text-center\">\n      <span class=\"text-gray-500\">-</span>\n    </el-col>\n    <el-col :span=\"11\">\n      <el-form-item\n        :rules=\"[\n          {\n            required: true,\n            message: $t('dynamicsForm.Slider.valueRange.maxRequired'),\n            trigger: 'change',\n          },\n        ]\"\n        prop=\"max\"\n        ><el-input-number\n          prop=\"max\"\n          style=\"width: 100%\"\n          v-model=\"formValue.max\"\n          :min=\"formValue.min > formValue.max ? formValue.min : undefined\"\n          controls-position=\"right\"\n      /></el-form-item>\n    </el-col>\n  </el-form-item>\n  <el-col :span=\"11\" style=\"padding-left: 0\">\n    <el-form-item\n      :label=\"$t('dynamicsForm.Slider.step.label')\"\n      required\n      prop=\"step\"\n      :rules=\"step_rules\"\n    >\n      <el-input-number\n        style=\"width: 100%\"\n        v-model=\"formValue.step\"\n        :min=\"0\"\n        controls-position=\"right\"\n      />\n    </el-form-item>\n  </el-col>\n\n  <el-form-item\n    :label=\"$t('dynamicsForm.default.label')\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :rules=\"\n      formValue.required\n        ? [{ required: true, message: $t('dynamicsForm.default.requiredMessage') }]\n        : []\n    \"\n  >\n    <el-slider\n      v-model=\"formValue.default_value\"\n      :show-input=\"formValue.showInput\"\n      :show-input-controls=\"false\"\n      :max=\"formValue.max\"\n      :min=\"formValue.min\"\n      :step=\"formValue.step == 0 ? 0.1 : formValue.step\"\n      :precision=\"formValue.precision\"\n    />\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onBeforeMount, watch } from 'vue'\nimport { t } from '@/locales'\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\n\nconst getData = () => {\n  return {\n    input_type: 'Slider',\n    attrs: {\n      min: formValue.value.min,\n      max: formValue.value.max,\n      step: formValue.value.step,\n      precision: formValue.value.precision,\n      'show-input-controls': false,\n      'show-input': formValue.value.showInput,\n    },\n    props_info: {\n      rules: [\n        {\n          message: formValue.value.label + ' ' + t('dynamicsForm.tip.requiredMessage'),\n          trigger: 'blur',\n          required: formValue.value.required,\n        },\n      ],\n    },\n    show_default_value: true,\n    default_value: formValue.value.default_value,\n  }\n}\nwatch(\n  () => formValue.value.min,\n  () => {\n    if (formValue.value.min > formValue.value.max) {\n      formValue.value.max = formValue.value.min\n    }\n  },\n)\nconst rander = (form_data: any) => {\n  const attrs = form_data.attrs\n  formValue.value.option_list = form_data.option_list\n  formValue.value.min = attrs.min\n  formValue.value.max = attrs.max\n  formValue.value.step = attrs.step\n  formValue.value.showInput = attrs['show-input']\n  formValue.value.default_value = form_data.default_value\n}\nconst step_rules = [\n  {\n    required: true,\n    validator: (rule: any, value: any, callback: any) => {\n      if (value === 0) {\n        callback(new Error(t('dynamicsForm.Slider.step.requiredMessage2')))\n        return false\n      }\n      if (!value) {\n        callback(new Error(t('dynamicsForm.Slider.step.requiredMessage1')))\n        return false\n      }\n\n      return true\n    },\n    trigger: 'blur',\n  },\n]\ndefineExpose({ getData, rander })\nonBeforeMount(() => {\n  formValue.value.min = 0\n  formValue.value.max = 20\n  formValue.value.step = 0.1\n  formValue.value.default_value = 1\n  formValue.value.showInput = true\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/SwitchInputConstructor.vue",
    "content": "<template>\n  <el-form-item\n    :label=\"$t('dynamicsForm.default.label')\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :rules=\"\n      formValue.required\n        ? [\n            {\n              required: true,\n              message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}`,\n            },\n          ]\n        : []\n    \"\n    @click.prevent\n  >\n    <el-switch v-model=\"formValue.default_value\" />\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted } from 'vue'\n\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\n\nconst getData = () => {\n  return {\n    input_type: 'SwitchInput',\n    show_default_value: true,\n    attrs: {},\n    default_value: formValue.value.default_value,\n  }\n}\n\nconst rander = (form_data: any) => {\n  formValue.value.default_value = form_data.default_value || false\n}\ndefineExpose({ getData, rander })\nonMounted(() => {\n  formValue.value.default_value = false\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/TextInputConstructor.vue",
    "content": "<template>\n  <el-form-item :label=\"$t('dynamicsForm.TextInput.length.label')\" required>\n    <el-row class=\"w-full\">\n      <el-col :span=\"11\">\n        <el-form-item\n          :rules=\"[\n            {\n              required: true,\n              message: $t('dynamicsForm.TextInput.length.minRequired'),\n              trigger: 'change'\n            }\n          ]\"\n          prop=\"minlength\"\n        >\n          <el-input-number\n            style=\"width: 100%\"\n            :min=\"1\"\n            :step=\"1\"\n            step-strictly\n            v-model=\"formValue.minlength\"\n            controls-position=\"right\"\n          />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"2\" class=\"text-center\">\n        <span>-</span>\n      </el-col>\n      <el-col :span=\"11\">\n        <el-form-item\n          :rules=\"[\n            {\n              required: true,\n              message: $t('dynamicsForm.TextInput.length.maxRequired'),\n              trigger: 'change'\n            }\n          ]\"\n          prop=\"maxlength\"\n        >\n          <el-input-number\n            style=\"width: 100%\"\n            :min=\"formValue.minlength > formValue.maxlength ? formValue.minlength : 1\"\n            step-strictly\n            :step=\"1\"\n            v-model=\"formValue.maxlength\"\n            controls-position=\"right\"\n        /></el-form-item>\n      </el-col>\n    </el-row>\n  </el-form-item>\n\n  <el-form-item\n    class=\"defaultValueItem\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :label=\"$t('dynamicsForm.default.label')\"\n    :rules=\"\n      formValue.required ? [{ required: true, message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}` }, ...rules] : rules\n    \"\n  >\n    <div class=\"defaultValueCheckbox\">\n      <el-checkbox\n        v-model=\"formValue.show_default_value\"\n        :label=\"$t('dynamicsForm.default.show')\"\n      />\n    </div>\n\n    <el-input\n      v-model=\"formValue.default_value\"\n      :maxlength=\"formValue.maxlength\"\n      :minlength=\"formValue.minlength\"\n      :placeholder=\"$t('dynamicsForm.default.placeholder')\"\n      show-word-limit\n      type=\"text\"\n    />\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, watch } from 'vue'\nimport { t } from '@/locales'\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  }\n})\nwatch(\n  () => formValue.value.minlength,\n  () => {\n    if (formValue.value.minlength > formValue.value.maxlength) {\n      formValue.value.maxlength = formValue.value.minlength\n    }\n  }\n)\nconst getData = () => {\n  return {\n    input_type: 'TextInput',\n    attrs: {\n      maxlength: formValue.value.maxlength,\n      minlength: formValue.value.minlength,\n      'show-word-limit': true\n    },\n    default_value: formValue.value.default_value,\n    show_default_value: formValue.value.show_default_value,\n    props_info: {\n      rules: formValue.value.required\n        ? [\n            { required: true, message: `${formValue.value.label} ${t('dynamicsForm.default.requiredMessage')}` },\n            {\n              min: formValue.value.minlength,\n              max: formValue.value.maxlength,\n              message: `${formValue.value.label}${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,\n              trigger: 'blur'\n            }\n          ]\n        : [\n            {\n              min: formValue.value.minlength,\n              max: formValue.value.maxlength,\n              message: `${formValue.value.label}${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,\n              trigger: 'blur'\n            }\n          ]\n    }\n  }\n}\nconst rander = (form_data: any) => {\n  const attrs = form_data.attrs || {}\n  formValue.value.minlength = attrs.minlength\n  formValue.value.maxlength = attrs.maxlength\n  formValue.value.default_value = form_data.default_value\n  formValue.value.show_default_value = form_data.show_default_value\n}\nconst rangeRules = [\n  {\n    required: true,\n    validator: (rule: any, value: any, callback: any) => {\n      if (!formValue.value.minlength) {\n        callback(new Error(t('dynamicsForm.TextInput.length.requiredMessage4')))\n      }\n      if (!formValue.value.maxlength) {\n        callback(new Error(t('dynamicsForm.TextInput.length.requiredMessage4')))\n      }\n      return true\n    },\n    message: `${formValue.value.label} ${t('dynamicsForm.default.requiredMessage')}`\n  }\n]\nconst rules = computed(() => [\n  {\n    min: formValue.value.minlength,\n    max: formValue.value.maxlength,\n    message: `${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,\n    trigger: 'blur'\n  }\n])\n\ndefineExpose({ getData, rander })\nonMounted(() => {\n  formValue.value.minlength = 0\n  formValue.value.maxlength = 200\n  formValue.value.default_value = ''\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.defaultValueItem {\n  position: relative;\n  .defaultValueCheckbox {\n    position: absolute;\n    right: 0;\n    top: -35px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/TextareaInputConstructor.vue",
    "content": "<template>\n  <el-form-item :label=\"$t('dynamicsForm.TextInput.length.label')\" required>\n    <el-row class=\"w-full\">\n      <el-col :span=\"11\">\n        <el-form-item\n          :rules=\"[\n            {\n              required: true,\n              message: $t('dynamicsForm.TextInput.length.minRequired'),\n              trigger: 'change',\n            },\n          ]\"\n          prop=\"minlength\"\n        >\n          <el-input-number\n            style=\"width: 100%\"\n            :min=\"1\"\n            :step=\"1\"\n            step-strictly\n            v-model=\"formValue.minlength\"\n            controls-position=\"right\"\n          />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"2\" class=\"text-center\">\n        <span>-</span>\n      </el-col>\n      <el-col :span=\"11\">\n        <el-form-item\n          :rules=\"[\n            {\n              required: true,\n              message: $t('dynamicsForm.TextInput.length.maxRequired'),\n              trigger: 'change',\n            },\n          ]\"\n          prop=\"maxlength\"\n        >\n          <el-input-number\n            style=\"width: 100%\"\n            :min=\"formValue.minlength > formValue.maxlength ? formValue.minlength : 1\"\n            step-strictly\n            :step=\"1\"\n            v-model=\"formValue.maxlength\"\n            controls-position=\"right\"\n        /></el-form-item>\n      </el-col>\n    </el-row>\n  </el-form-item>\n\n  <el-form-item\n    class=\"defaultValueItem\"\n    :required=\"formValue.required\"\n    prop=\"default_value\"\n    :label=\"$t('dynamicsForm.default.label')\"\n    :rules=\"\n      formValue.required\n        ? [\n            {\n              required: true,\n              message: `${$t('dynamicsForm.default.label')}${$t('dynamicsForm.default.requiredMessage')}`,\n            },\n            ...rules,\n          ]\n        : rules\n    \"\n  >\n    <div class=\"defaultValueCheckbox\">\n      <el-checkbox\n        v-model=\"formValue.show_default_value\"\n        :label=\"$t('dynamicsForm.default.show')\"\n      />\n    </div>\n\n    <el-input\n      v-model=\"formValue.default_value\"\n      :maxlength=\"formValue.maxlength\"\n      :minlength=\"formValue.minlength\"\n      :placeholder=\"$t('dynamicsForm.default.placeholder')\"\n      show-word-limit\n      :rows=\"3\"\n      type=\"textarea\"\n    />\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, watch } from 'vue'\nimport { t } from '@/locales'\nconst props = defineProps<{\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\nwatch(\n  () => formValue.value.minlength,\n  () => {\n    if (formValue.value.minlength > formValue.value.maxlength) {\n      formValue.value.maxlength = formValue.value.minlength\n    }\n  },\n)\nconst getData = () => {\n  return {\n    input_type: 'TextareaInput',\n    attrs: {\n      maxlength: formValue.value.maxlength,\n      minlength: formValue.value.minlength,\n      'show-word-limit': true,\n      rows: 3,\n    },\n    default_value: formValue.value.default_value,\n    show_default_value: formValue.value.show_default_value,\n    props_info: {\n      rules: formValue.value.required\n        ? [\n            {\n              required: true,\n              message: `${formValue.value.label} ${t('dynamicsForm.default.requiredMessage')}`,\n            },\n            {\n              min: formValue.value.minlength,\n              max: formValue.value.maxlength,\n              message: `${formValue.value.label}${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,\n              trigger: 'blur',\n            },\n          ]\n        : [\n            {\n              min: formValue.value.minlength,\n              max: formValue.value.maxlength,\n              message: `${formValue.value.label}${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,\n              trigger: 'blur',\n            },\n          ],\n    },\n  }\n}\nconst rander = (form_data: any) => {\n  const attrs = form_data.attrs || {}\n  formValue.value.minlength = attrs.minlength\n  formValue.value.maxlength = attrs.maxlength\n  formValue.value.default_value = form_data.default_value\n  formValue.value.show_default_value = form_data.show_default_value\n}\nconst rangeRules = [\n  {\n    required: true,\n    validator: (rule: any, value: any, callback: any) => {\n      if (!formValue.value.minlength) {\n        callback(new Error(t('dynamicsForm.TextInput.length.requiredMessage4')))\n      }\n      if (!formValue.value.maxlength) {\n        callback(new Error(t('dynamicsForm.TextInput.length.requiredMessage4')))\n      }\n      return true\n    },\n    message: `${formValue.value.label} ${t('dynamicsForm.default.requiredMessage')}`,\n  },\n]\nconst rules = computed(() => [\n  {\n    min: formValue.value.minlength,\n    max: formValue.value.maxlength,\n    message: `${t('dynamicsForm.TextInput.length.requiredMessage1')} ${formValue.value.minlength} ${t('dynamicsForm.TextInput.length.requiredMessage2')} ${formValue.value.maxlength} ${t('dynamicsForm.TextInput.length.requiredMessage3')}`,\n    trigger: 'blur',\n  },\n])\n\ndefineExpose({ getData, rander })\nonMounted(() => {\n  formValue.value.minlength = 0\n  formValue.value.maxlength = 200\n  formValue.value.default_value = ''\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.defaultValueItem {\n  position: relative;\n  .defaultValueCheckbox {\n    position: absolute;\n    right: 0;\n    top: -35px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/constructor/items/UploadInputConstructor.vue",
    "content": "<template>\n  <el-form-item :label=\"$t('dynamicsForm.UploadInput.limit.label')\" required prop=\"limit\">\n    <el-input-number\n      style=\"width: 100%\"\n      v-model=\"formValue.limit\"\n      :rules=\"[\n        {\n          required: true,\n          message: $t('dynamicsForm.UploadInput.limit.required'),\n          trigger: 'change',\n        },\n      ]\"\n      :min=\"0\"\n      controls-position=\"right\"\n    />\n  </el-form-item>\n  <el-form-item\n    :label=\"$t('dynamicsForm.UploadInput.max_file_size.label')\"\n    required\n    prop=\"max_file_size\"\n    :rules=\"[\n      {\n        required: true,\n        message: $t('dynamicsForm.UploadInput.max_file_size.required'),\n        trigger: 'change',\n      },\n    ]\"\n  >\n    <el-input-number\n      style=\"width: 100%\"\n      v-model=\"formValue.max_file_size\"\n      :min=\"0\"\n      controls-position=\"right\"\n    />\n  </el-form-item>\n  <el-form-item\n    :label=\"$t('dynamicsForm.UploadInput.accept.label')\"\n    required\n    prop=\"accept\"\n    :rules=\"[\n      {\n        required: true,\n        message: $t('dynamicsForm.UploadInput.accept.required'),\n        trigger: 'change',\n      },\n    ]\"\n  >\n    <el-space wrap :size=\"6\" class=\"mt-4\">\n      <el-tag\n        v-for=\"tag in formValue.accept\"\n        :key=\"tag\"\n        closable\n        :disable-transitions=\"false\"\n        @close=\"handleClose(tag)\"\n        type=\"info\"\n        effect=\"plain\"\n      >\n        {{ tag }}\n      </el-tag>\n      <el-input\n        v-if=\"inputVisible\"\n        ref=\"InputRef\"\n        v-model=\"inputValue\"\n        size=\"small\"\n        @keyup.enter=\"handleInputConfirm\"\n        @blur=\"handleInputConfirm\"\n        :style=\"{\n          '--el-input-border-radius': '4px',\n        }\"\n      />\n      <el-button v-else class=\"button-new-tag\" size=\"small\" @click=\"showInput\">\n        <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon\n        >{{ $t('common.fileUpload.addExtensions') }}\n      </el-button>\n    </el-space>\n  </el-form-item>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, ref, nextTick } from 'vue'\nimport { ElMessage, type InputInstance } from 'element-plus'\nimport { t } from '@/locales'\nconst props = defineProps<{\n  modelValue: any\n}>()\n\nconst inputValue = ref('')\n\nconst inputVisible = ref(false)\nconst InputRef = ref<InputInstance>()\nconst handleClose = (tag: string) => {\n  formValue.value.accept.splice(formValue.value.accept.indexOf(tag), 1)\n}\n\nconst showInput = () => {\n  inputVisible.value = true\n  nextTick(() => {\n    InputRef.value!.input!.focus()\n  })\n}\n\nconst handleInputConfirm = () => {\n  if (formValue.value.accept.find((item: string) => item === inputValue.value)) {\n    ElMessage.warning(t('common.fileUpload.existingExtensionsTip'))\n    return\n  }\n  if (inputValue.value) {\n    formValue.value.accept.push(inputValue.value)\n  }\n  inputVisible.value = false\n  inputValue.value = ''\n}\nconst emit = defineEmits(['update:modelValue'])\nconst formValue = computed({\n  set: (item) => {\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\n\nconst rander = (form_data: any) => {\n  formValue.value.default_value = []\n  formValue.value.limit = form_data.attrs.limit || 3\n  formValue.value.max_file_size = form_data.max_file_size || 10\n  formValue.value.accept = form_data.attrs.accept\n    ? form_data.attrs.accept.split(',').map((item: string) => item.substring(1))\n    : ['jpg']\n}\nconst getData = () => {\n  return {\n    input_type: 'UploadInput',\n    attrs: {\n      accept: formValue.value.accept.map((item: any) => '.' + item).join(','),\n      limit: formValue.value.limit,\n    },\n    max_file_size: formValue.value.max_file_size,\n    default_value: [],\n    show_default_value: formValue.value.show_default_value,\n  }\n}\ndefineExpose({ getData, rander })\n\nonMounted(() => {\n  formValue.value.default_value = []\n  formValue.value.limit = 3\n  formValue.value.max_file_size = 10\n  formValue.value.accept = ['jpg']\n  if (formValue.value.show_default_value === undefined) {\n    formValue.value.show_default_value = true\n  }\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/index.ts",
    "content": "import type { App } from 'vue'\nimport type { Dict } from '@/api/type/common'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nlet components: Dict<any> = import.meta.glob('@/components/dynamics-form/**/**.vue', {\n  eager: true,\n})\ncomponents = {\n  ...components,\n  ...import.meta.glob('@/components/dynamics-form/**/**/**.vue', {\n    eager: true,\n  }),\n}\n\nconst install = (app: App) => {\n  Object.keys(components).forEach((key: string) => {\n    const commentName: string = key\n      .substring(key.lastIndexOf('/') + 1, key.length)\n      .replace('.vue', '')\n    if (key !== '/src/components/dynamics-form/constructor/index.vue') {\n      app.component(commentName, components[key].default)\n    }\n  })\n  app.component('DynamicsForm', DynamicsForm)\n}\nexport default { install }\n"
  },
  {
    "path": "ui/src/components/dynamics-form/index.vue",
    "content": "<template>\n  <el-form\n    @submit.prevent\n    ref=\"ruleFormRef\"\n    label-width=\"130px\"\n    label-suffix=\":\"\n    v-loading=\"loading\"\n    v-bind=\"$attrs\"\n    label-position=\"top\"\n    require-asterisk-position=\"right\"\n  >\n    <slot :form_value=\"formValue\"></slot>\n    <template v-for=\"item in formFieldList\" :key=\"item.field\">\n      <FormItem\n        ref=\"formFieldRef\"\n        :key=\"item.field\"\n        v-if=\"show(item)\"\n        @change=\"change(item, $event)\"\n        @changeLabel=\"changeLabel(item, $event)\"\n        v-bind:modelValue=\"formValue[item.field]\"\n        :formfield=\"item\"\n        :trigger=\"trigger\"\n        :view=\"view\"\n        :initDefaultData=\"initDefaultData\"\n        :defaultItemWidth=\"defaultItemWidth\"\n        :other-params=\"otherParams\"\n        :form-value=\"formValue\"\n        :formfield-list=\"formFieldList\"\n        :parent_field=\"parent_field\"\n      >\n      </FormItem>\n    </template>\n  </el-form>\n</template>\n<script lang=\"ts\" setup>\nimport type { Dict } from '@/api/type/common'\nimport FormItem from '@/components/dynamics-form/FormItem.vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { ref, onBeforeMount, watch, type Ref, nextTick } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport type Result from '@/request/Result'\nimport _ from 'lodash'\nimport { get, post, put, del } from '@/request/index'\nconst request = {\n  get,\n  post,\n  put,\n  del,\n}\ndefineOptions({ name: 'dynamicsForm' })\n\nconst props = withDefaults(\n  defineProps<{\n    // 页面渲染数据\n    render_data:\n      | Promise<Result<Array<FormField>>>\n      | string\n      | Array<FormField>\n      | (() => Promise<Result<Array<FormField>>>)\n    // 调用接口所需要的其他参数\n    otherParams?: any\n    // 是否只读\n    view?: boolean\n    // 默认每个宽度\n    defaultItemWidth?: string\n\n    parent_field?: string\n\n    modelValue?: Dict<any>\n  }>(),\n  { view: false, defaultItemWidth: '75%', otherParams: () => {} },\n)\n\nconst formValue = ref<Dict<any>>({})\n\nconst loading = ref<boolean>(false)\n\nconst formFieldList = ref<Array<FormField>>([])\n\nconst ruleFormRef = ref<FormInstance>()\n\nconst formFieldRef = ref<Array<InstanceType<typeof FormItem>>>([])\n/**\n * 当前 field是否展示\n * @param field\n */\nconst show = (field: FormField) => {\n  if (field.relation_show_field_dict) {\n    const keys = Object.keys(field.relation_show_field_dict)\n    for (const index in keys) {\n      const key = keys[index]\n      const v = _.get(formValue.value, key)\n      if (v && v !== undefined && v !== null) {\n        const values = field.relation_show_field_dict[key]\n        if (values && values.length > 0) {\n          return values.includes(v)\n        } else {\n          return true\n        }\n      } else {\n        return false\n      }\n    }\n  }\n  return true\n}\n\nconst emit = defineEmits(['update:modelValue'])\n/**\n * 表单字段修改\n * @param field\n * @param value\n */\nconst change = (field: FormField, value: any) => {\n  formValue.value[field.field] = value\n}\n\n/**\n * 表单字段修改\n * @param field\n * @param value\n */\nconst changeLabel = (field: FormField, value: any) => {\n  formValue.value[field.label.field] = value\n}\n\nwatch(\n  formValue,\n  () => {\n    emit('update:modelValue', formValue.value)\n  },\n  { deep: true },\n)\nfunction renderTemplate(template: string, data: any) {\n  return template.replace(/\\$\\{(\\w+)\\}/g, (match, key) => {\n    return data[key] !== undefined ? data[key] : match\n  })\n}\n/**\n * 触发器,用户获取子表单 或者 下拉选项\n * @param field\n * @param loading\n */\nconst trigger = (\n  trigger_field: string,\n  trigger_value: any,\n  trigger_setting: any,\n  self: any,\n  loading: Ref<boolean>,\n) => {\n  const request_call = new Function(\n    'self',\n    'trigger_setting',\n    'request',\n    'extra',\n    trigger_setting.request\n      ? trigger_setting.request\n      : 'return  request.get(extra.renderTemplate(trigger_setting.url));',\n  )(self, trigger_setting, request, {\n    renderTemplate: (url: string) =>\n      renderTemplate(url, {\n        trigger_value: trigger_value,\n        ...props.otherParams,\n      }),\n  })\n\n  if (!trigger_setting.change && !trigger_setting.change_field) {\n    return\n  }\n  request_call.then((ok: any) => {\n    new Function(\n      'self',\n      'trigger_setting',\n      'response',\n      'extra',\n      trigger_setting.change\n        ? trigger_setting.change\n        : `self[trigger_setting.change_field]=[\n        ...response.data.shared_model.map((m) => {\n          return { ...m, type: 'share' }\n        }),\n        ...response.data.model.map((m) => {\n          return { ...m, type: 'workspace' }\n        })\n      ];`,\n    )(self, trigger_setting, ok, { form_data: formValue, getDefault: getFormDefaultValue })\n  })\n}\n/**\n * 初始化默认数据\n */\nconst initDefaultData = (formField: FormField) => {\n  if (\n    formField.default_value &&\n    (formValue.value[formField.field] === undefined ||\n      formValue.value[formField.field] === null ||\n      !formValue.value[formField.field]) &&\n    formValue.value[formField.field] != false\n  ) {\n    if (formField.show_default_value === true) {\n      formValue.value[formField.field] = formField.default_value\n    }\n  }\n}\n\nonBeforeMount(() => {\n  render(props.render_data, props.modelValue)\n})\n\nconst render = (\n  render_data:\n    | string\n    | Array<FormField>\n    | Promise<Result<Array<FormField>>>\n    | (() => Promise<Result<Array<FormField>>>),\n  data?: Dict<any>,\n) => {\n  console.log(data, '-----')\n  formFieldList.value = []\n  nextTick(() => {\n    if (typeof render_data == 'string') {\n      get(render_data, {}, loading).then((ok) => {\n        formFieldList.value = ok.data\n      })\n    } else if (render_data instanceof Array) {\n      formFieldList.value = render_data\n    } else if (typeof render_data === 'function') {\n      render_data().then((ok: any) => {\n        formFieldList.value = ok.data\n        const form_data = data ? data : {}\n        if (form_data) {\n          const value = getFormDefaultValue(formFieldList.value, form_data)\n          formValue.value = _.cloneDeep(value)\n        }\n      })\n    } else {\n      render_data.then((ok) => {\n        formFieldList.value = ok.data\n      })\n    }\n    const form_data = data ? data : {}\n    if (form_data) {\n      const value = getFormDefaultValue(formFieldList.value, form_data)\n      formValue.value = _.cloneDeep(value)\n    }\n  })\n}\nconst getFormDefaultValue = (fieldList: Array<any>, form_data?: any) => {\n  form_data = form_data ? form_data : {}\n  console.log(form_data)\n  const value = fieldList\n    .map((item) => {\n      if (form_data[item.field] !== undefined) {\n        if (item.value_field && item.option_list && item.option_list.length > 0) {\n          const value_field = item.value_field\n          const find = item.option_list?.find((i: any) => {\n            if (typeof form_data[item.field] === 'string') {\n              return i[value_field] === form_data[item.field]\n            } else {\n              return form_data[item.field].indexOf([value_field]) === -1\n            }\n          })\n          if (find) {\n            return { [item.field]: form_data[item.field] }\n          }\n          if (item.show_default_value === true || item.show_default_value === undefined) {\n            return { [item.field]: item.default_value }\n          }\n        } else {\n          return { [item.field]: form_data[item.field] }\n        }\n      }\n      if (item.show_default_value === true || item.show_default_value === undefined) {\n        return { [item.field]: item.default_value }\n      }\n      return {}\n    })\n    .reduce((x, y) => ({ ...x, ...y }), {})\n  console.log(value)\n  return value\n}\n/**\n * 校验函数\n */\nconst validate = () => {\n  return Promise.all([\n    ...formFieldRef.value.map((item) => item.validate()),\n    ruleFormRef.value ? ruleFormRef.value.validate() : Promise.resolve(),\n  ])\n}\n\n// 暴露获取当前表单数据函数\ndefineExpose({\n  initDefaultData,\n  validate,\n  render,\n  ruleFormRef,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/DatePicker.vue",
    "content": "<template>\n  <el-date-picker v-bind=\"$attrs\" />\n</template>\n<script setup lang=\"ts\"></script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/JsonInput.vue",
    "content": "<template>\n  <div style=\"width: 100%\" class=\"function-CodemirrorEditor\">\n    <Codemirror\n      v-bind=\"$attrs\"\n      ref=\"cmRef\"\n      v-model=\"model_value\"\n      :extensions=\"extensions\"\n      :style=\"codemirrorStyle\"\n      :tab-size=\"4\"\n      :autofocus=\"true\"\n    />\n    <div class=\"function-CodemirrorEditor__format\">\n      <el-button text type=\"info\" @click=\"format\" class=\"magnify\">\n        <el-icon><DocumentChecked /></el-icon>\n      </el-button>\n    </div>\n    <div class=\"function-CodemirrorEditor__footer\">\n      <el-button text type=\"info\" @click=\"openCodemirrorDialog\" class=\"magnify\">\n        <AppIcon iconName=\"app-magnify\" style=\"font-size: 16px\"></AppIcon>\n      </el-button>\n    </div>\n    <!-- Codemirror 弹出层 -->\n    <el-dialog\n      v-model=\"dialogVisible\"\n      :title=\"$t('dynamicsForm.default.label')\"\n      append-to-body\n      fullscreen\n    >\n      <Codemirror\n        v-model=\"cloneContent\"\n        :extensions=\"extensions\"\n        :style=\"codemirrorStyle\"\n        :tab-size=\"4\"\n        :autofocus=\"true\"\n        style=\"\n          height: calc(100vh - 160px) !important;\n          border: 1px solid #bbbfc4;\n          border-radius: 4px;\n        \"\n      />\n      <template #footer>\n        <div class=\"dialog-footer mt-24\">\n          <el-button type=\"primary\" @click=\"submitDialog\"> {{ $t('common.confirm') }}</el-button>\n        </div>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { json, jsonParseLinter } from '@codemirror/lang-json'\nimport { oneDark } from '@codemirror/theme-one-dark'\nimport { Codemirror } from 'vue-codemirror'\nimport { linter } from '@codemirror/lint'\nimport { computed, ref } from 'vue'\nimport { t } from '@/locales'\nconst props = withDefaults(defineProps<{ modelValue?: any }>(), { modelValue: () => {} })\nconst emit = defineEmits(['update:modelValue'])\n\nconst cache_model_value_str = ref<string>()\n\nconst model_value = computed({\n  get: () => {\n    if (cache_model_value_str.value) {\n      return cache_model_value_str.value\n    }\n    return JSON.stringify(props.modelValue, null, 4)\n  },\n  set: (v: string) => {\n    if (!v) {\n      emit('update:modelValue', JSON.parse('{}'))\n    } else {\n      try {\n        cache_model_value_str.value = v\n        const result = JSON.parse(v)\n        emit('update:modelValue', result)\n      } catch (e) {}\n    }\n  },\n})\n\nconst extensions = [json(), linter(jsonParseLinter()), oneDark]\n\nconst codemirrorStyle = {\n  height: '210px!important',\n  width: '100%',\n}\n\n// 弹出框相关代码\nconst dialogVisible = ref<boolean>(false)\n\nconst cloneContent = ref<string>('')\n\nconst openCodemirrorDialog = () => {\n  cloneContent.value = model_value.value\n  dialogVisible.value = true\n}\n\nconst format = () => {\n  try {\n    const json_str = JSON.parse(model_value.value)\n    model_value.value = JSON.stringify(json_str, null, 4)\n  } catch (e) {}\n}\n\nfunction submitDialog() {\n  model_value.value = cloneContent.value\n  dialogVisible.value = false\n}\n/**\n * 校验格式\n * @param rule\n * @param value\n * @param callback\n */\nconst validate_rules = (rule: any, value: any, callback: any) => {\n  if (model_value.value) {\n    try {\n      JSON.parse(model_value.value)\n    } catch (e) {\n      callback(new Error(t('dynamicsForm.tip.jsonMessage')))\n      return false\n    }\n  }\n  return true\n}\n\ndefineExpose({ validate_rules: validate_rules })\n</script>\n<style lang=\"scss\">\n.function-CodemirrorEditor__footer {\n  position: absolute;\n  bottom: 10px;\n  right: 10px;\n}\n.function-CodemirrorEditor {\n  position: relative;\n}\n.function-CodemirrorEditor__format {\n  position: absolute;\n  top: 10px;\n  right: 10px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/MultiRow.vue",
    "content": "<template>\n  <div class=\"multi_row\">\n    <div\n      v-for=\"item in option_list\"\n      :key=\"item.value\"\n      class=\"item\"\n      :class=\"[\n        inputDisabled ? 'is-disabled' : '',\n        _value.includes(item[valueField]) ? 'active' : '',\n      ]\"\n      @click=\"selected(item[valueField])\"\n    >\n      {{ item[textField] }}\n    </div>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { computed, inject } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { useFormDisabled, formItemContextKey } from 'element-plus'\nconst inputDisabled = useFormDisabled()\nconst props = defineProps<{\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n  // 选中的值\n  modelValue?: any\n}>()\nconst elFormItem = inject(formItemContextKey, void 0)\nconst selected = (activeValue: string | number) => {\n  if (_value.value.includes(activeValue)) {\n    emit(\n      'update:modelValue',\n      props.modelValue.filter((i: any) => i != activeValue),\n    )\n  } else {\n    emit('update:modelValue', reset(activeValue))\n  }\n  if (elFormItem?.validate) {\n    elFormItem.validate('change')\n  }\n}\nconst reset = (activeValue: string | number) => {\n  const _result = props.modelValue ? [...props.modelValue, activeValue] : [activeValue]\n  return _result.filter((r) => option_value_list.value.includes(r))\n}\n\nconst _value = computed(() => {\n  return props.modelValue ? props.modelValue : []\n})\nconst emit = defineEmits(['update:modelValue'])\n\nconst textField = computed(() => {\n  return props.formField.text_field ? props.formField.text_field : 'key'\n})\n\nconst valueField = computed(() => {\n  return props.formField.value_field ? props.formField.value_field : 'value'\n})\nconst option_value_list = computed(() => {\n  return option_list.value.map((item) => item[valueField.value])\n})\nconst option_list = computed(() => {\n  return props.formField.option_list ? props.formField.option_list : []\n})\n</script>\n<style lang=\"scss\" scoped>\n.multi_row {\n  // height: 32px;\n  display: inline-flex;\n  border: 1px solid #bbbfc4;\n  border-radius: 4px;\n  font-weight: 400;\n  font-size: 14px;\n  color: var(--el-text-color-primary);\n  padding: 3px 4px;\n  box-sizing: border-box;\n  white-space: nowrap;\n  .is-disabled {\n    border: 1px solid var(--el-card-border-color);\n    background-color: var(--el-fill-color-light);\n    color: var(--el-text-color-placeholder);\n    cursor: not-allowed;\n    &:hover {\n      cursor: not-allowed;\n    }\n  }\n  .active {\n    border-radius: 4px;\n    background: var(--el-color-primary-light-9);\n    color: var(--el-color-primary);\n  }\n  .item {\n    cursor: pointer;\n    margin: 0px 2px;\n    padding: 2px 8px;\n    height: 20px;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    &:last-child {\n      margin: 0 4px 0 2px;\n    }\n    &:first-child {\n      margin: 0 2px 0 4px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/PasswordInput.vue",
    "content": "<template>\n  <el-input v-bind=\"$attrs\" :show-password=\"true\"></el-input>\n</template>\n<script setup lang=\"ts\"></script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/TextInput.vue",
    "content": "<template>\n  <el-input v-bind=\"$attrs\" />\n</template>\n<script setup lang=\"ts\"></script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/TextareaInput.vue",
    "content": "<template>\n  <el-input v-bind=\"$attrs\" type=\"textarea\"></el-input>\n</template>\n<script setup lang=\"ts\"></script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/complex/ArrayObjectCard.vue",
    "content": "<template v-loading=\"_loading\">\n  <div class=\"arrt-object-card flex w-full\">\n    <el-card class=\"box-card\" :style=\"style\" v-for=\"(item, index) in _data\" :key=\"index\">\n      <DynamicsForm\n        :style=\"formStyle\"\n        :view=\"view\"\n        ref=\"ceFormRef\"\n        v-model=\"_data[index]\"\n        :model=\"_data[index]\"\n        :other-params=\"other\"\n        :render_data=\"render_data()\"\n        v-bind=\"attr\"\n        :parent_field=\"formField.field + '.' + index\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n      ></DynamicsForm>\n      <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n        <el-button text @click.stop=\"deleteKnowledge(item)\" class=\"delete-button\">\n          <AppIcon iconName=\"app-delete\"></AppIcon>\n        </el-button>\n      </el-tooltip>\n    </el-card>\n    <el-card shadow=\"never\" class=\"card-add box-card\" @click=\"add_card\">\n      <div class=\"flex-center\">\n        <AppIcon iconName=\"app-add-outlined\" class=\"add-icon layout-bg p-8 border-r-6\" />\n        <span>{{ add_msg }}</span>\n      </div>\n    </el-card>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport _ from 'lodash'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport Result from '@/request/Result'\nconst props = defineProps<{\n  modelValue?: Array<any>\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n}>()\n\nconst render_data = () => {\n  return Promise.resolve(Result.success(props.formField.children as Array<FormField>))\n}\nconst deleteKnowledge = (item: any) => {\n  _data.value = _data.value.filter((row) => row !== item)\n}\nconst emit = defineEmits(['update:modelValue', 'change'])\n\n// 校验实例对象\nconst dynamicsFormRef = ref<Array<InstanceType<typeof DynamicsForm>>>([])\n\nconst _data = computed<Array<any>>({\n  get() {\n    if (props.modelValue) {\n      return props.modelValue\n    } else {\n      emit('update:modelValue', [{}])\n      return []\n    }\n  },\n  set(value) {\n    emit('update:modelValue', value)\n  },\n})\n\nconst props_info = computed(() => {\n  return props.formField.props_info ? props.formField.props_info : {}\n})\nconst add_msg = computed(() => {\n  return props_info.value.add_msg ? props_info.value.add_msg : '添加'\n})\n/**\n * 添加一个card\n */\nconst add_card = () => {\n  _data.value = [..._data.value, {}]\n}\n\n/**\n * 组件样式\n */\nconst formStyle = computed(() => {\n  return props_info.value.form_style ? props_info.value.form_style : {}\n})\nconst style = computed(() => {\n  return props_info.value.style ? props_info.value.style : {}\n})\nconst attr = computed(() => {\n  if (props.formField.attrs) {\n    return props.formField.attrs\n  }\n  return {}\n})\n\n/**\n * 校验方法\n */\nfunction validate() {\n  return Promise.all(dynamicsFormRef.value.map((item) => item.validate()))\n}\nconst other = computed(() => {\n  return { ...(props.formValue ? props.formValue : {}), ...props.otherParams }\n})\n\ndefineExpose({\n  validate,\n  field: props.field,\n})\n</script>\n<style lang=\"scss\" scoped>\n.arrt-object-card {\n  .box-card {\n    width: 30%;\n    position: relative;\n    margin: 10px;\n    padding-top: 20px;\n  }\n  .card-add {\n    display: inline-flex;\n    justify-content: center;\n    align-items: center;\n    font-size: 16px;\n    cursor: pointer;\n    min-height: var(--card-min-height);\n    border: 1px dashed var(--el-color-primary);\n    background: var(--el-disabled-bg-color);\n    padding-bottom: 20px;\n\n    .add-icon {\n      font-size: 14px;\n      border: 1px solid var(--app-border-color-dark);\n      margin-right: 12px;\n    }\n    &:hover {\n      color: var(--el-color-primary);\n      background: #ffffff;\n      .add-icon {\n        background: #ffffff;\n        border-color: var(--el-color-primary);\n      }\n    }\n  }\n  .delete-button {\n    position: absolute;\n    right: 12px;\n    top: 10px;\n    height: auto;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/complex/ObjectCard.vue",
    "content": "<template>\n  <el-card :style=\"style\">\n    <DynamicsForm\n      :read-only=\"view\"\n      :style=\"formStyle\"\n      ref=\"dynamicsFormRef\"\n      v-model=\"data\"\n      :other-params=\"other\"\n      :render_data=\"formField.children ? formField.children : []\"\n      v-bind=\"$attrs\"\n      :parent_field=\"formField.field\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n    ></DynamicsForm>\n  </el-card>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nconst emit = defineEmits(['update:modelValue', 'change'])\n\nconst props = defineProps<{\n  modelValue?: any\n  formValue?: any\n  formfieldList?: Array<FormField>\n  otherParams: any\n  formField: FormField\n  view?: boolean\n}>()\n\nconst data = computed({\n  get: () => {\n    if (props.modelValue) {\n      return props.modelValue\n    }\n    return {}\n  },\n  set: ($event) => {\n    emit('update:modelValue', $event)\n  },\n})\n\nconst other = computed(() => {\n  return { ...(props.formfieldList ? props.formfieldList : {}), ...props.otherParams }\n})\n// 校验实例对象\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\n/**\n * 组件样式\n */\nconst formStyle = computed(() => {\n  return props_info.value.form_style ? props_info.value.form_style : {}\n})\nconst props_info = computed(() => {\n  return props.formField.props_info ? props.formField.props_info : {}\n})\n\nconst style = computed(() => {\n  return props_info.value.style ? props_info.value.style : {}\n})\n/**\n * 校验方法\n */\nfunction validate() {\n  if (dynamicsFormRef.value) {\n    return dynamicsFormRef.value.validate()\n  }\n  return Promise.resolve()\n}\ndefineExpose({\n  validate,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/complex/TabCard.vue",
    "content": "<template v-loading=\"_loading\">\n  <div style=\"width: 100%\">\n    <el-tabs v-model=\"activeTab\" editable @edit=\"handleTabsEdit\" type=\"card\">\n      <el-tab-pane\n        v-for=\"(item, index) in _data\"\n        :key=\"index\"\n        :label=\"tabs_label + (index + 1)\"\n        :name=\"index\"\n      >\n        <template v-if=\"formField.children\">\n          <el-card :style=\"style\">\n            <DynamicsForm\n              :style=\"formStyle\"\n              :view=\"view\"\n              ref=\"ceFormRef\"\n              v-model=\"_data[index]\"\n              :model=\"_data[index]\"\n              :other-params=\"other\"\n              :render_data=\"render_data()\"\n              v-bind=\"attr\"\n              :parent_field=\"formField.field + '.' + index\"\n              label-position=\"top\"\n              require-asterisk-position=\"right\"\n            ></DynamicsForm>\n          </el-card>\n        </template>\n      </el-tab-pane>\n    </el-tabs>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport _ from 'lodash'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport Result from '@/request/Result'\nimport type { TabPaneName } from 'element-plus'\n\nconst props = defineProps<{\n  modelValue?: Array<any>\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n}>()\n\nconst render_data = () => {\n  return Promise.resolve(Result.success(props.formField.children as Array<FormField>))\n}\n\nconst emit = defineEmits(['update:modelValue', 'change'])\n\n// 校验实例对象\nconst dynamicsFormRef = ref<Array<InstanceType<typeof DynamicsForm>>>([])\n\nconst _data = computed<Array<any>>({\n  get() {\n    if (props.modelValue) {\n      return props.modelValue\n    } else {\n      emit('update:modelValue', [{}])\n      return []\n    }\n  },\n  set(value) {\n    emit('update:modelValue', value)\n  },\n})\n\nconst props_info = computed(() => {\n  return props.formField.props_info ? props.formField.props_info : {}\n})\n\nconst tabs_label = computed(() => {\n  return props_info.value.tabs_label ? props_info.value.tabs_label : 'label'\n})\n/**\n * 组件样式\n */\nconst formStyle = computed(() => {\n  return props_info.value.form_style ? props_info.value.form_style : {}\n})\n\nconst attr = computed(() => {\n  if (props.formField.attrs) {\n    return props.formField.attrs\n  }\n  return {}\n})\nconst activeTab = ref(0)\n\n/**\n * 校验方法\n */\nfunction validate() {\n  return Promise.all(dynamicsFormRef.value.map((item) => item.validate()))\n}\nconst other = computed(() => {\n  return { ...(props.formValue ? props.formValue : {}), ...props.otherParams }\n})\nconst style = computed(() => {\n  return props_info.value.style ? props_info.value.style : {}\n})\n\nconst handleTabsEdit = (targetName: TabPaneName | undefined, action: 'remove' | 'add') => {\n  if (action === 'add') {\n    _data.value = [..._data.value, {}]\n    activeTab.value = _data.value.length\n  } else if (action === 'remove') {\n    const update_value = _data.value.filter((item, index) => index != targetName)\n    _data.value = update_value\n    activeTab.value = update_value.length - 1\n  }\n}\n\ndefineExpose({\n  validate,\n  field: props.field,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/label/SettingLabel.vue",
    "content": "<template>\n  <div class=\"flex-between w-full my-required\">\n    <div>\n      <span> {{ label.label }}<span class=\"color-danger\">*</span></span>\n    </div>\n\n    <el-tooltip v-if=\"label.attrs?.tooltip\" effect=\"dark\" placement=\"right\">\n      <template #content\n        ><div style=\"max-width: 200px\">{{ label.attrs.tooltip }}</div></template\n      >\n      <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\" style=\"flex-shrink: 0\"></AppIcon>\n    </el-tooltip>\n    <el-button v-if=\"show(label)\" type=\"primary\" link @click=\"open()\">\n      <AppIcon iconName=\"app-setting\"></AppIcon>\n    </el-button>\n    <el-dialog\n      destroy-on-close\n      v-model=\"dialogVisible\"\n      title=\"Tips\"\n      width=\"500\"\n      :before-close=\"close\"\n    >\n      <DynamicsForm\n        :read-only=\"view\"\n        ref=\"dynamicsFormRef\"\n        :render_data=\"label.children ? label.children : []\"\n        label-position=\"top\"\n        v-model=\"form_data\"\n        require-asterisk-position=\"right\"\n        :model=\"form_data\"\n      ></DynamicsForm>\n      <template #footer>\n        <div class=\"dialog-footer\">\n          <el-button @click=\"close\">取消</el-button>\n          <el-button type=\"primary\" @click=\"submit\"> 确定 </el-button>\n        </div>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport { ref } from 'vue'\nimport { cloneDeep, get } from 'lodash'\nconst props = defineProps<{\n  label: any\n  modelValue?: any\n  formValue: any\n  view?: boolean\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst dialogVisible = ref<boolean>(false)\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst form_data = ref<any>(undefined)\nconst open = () => {\n  if (props.modelValue) {\n    form_data.value = cloneDeep(props.modelValue)\n  }\n  dialogVisible.value = true\n}\nconst close = () => {\n  dialogVisible.value = false\n  form_data.value = undefined\n}\n/**\n * 当前 field是否展示\n * @param field\n */\nconst show = (field: any) => {\n  if (field.relation_show_field_dict) {\n    const keys = Object.keys(field.relation_show_field_dict)\n    for (const index in keys) {\n      const key = keys[index]\n      const v = get(props.formValue, key)\n      if (v && v !== undefined && v !== null) {\n        const values = field.relation_show_field_dict[key]\n        if (values && values.length > 0) {\n          return values.includes(v)\n        } else {\n          return true\n        }\n      } else {\n        return false\n      }\n    }\n  }\n  return true\n}\nconst submit = () => {\n  dynamicsFormRef.value?.validate().then(() => {\n    dialogVisible.value = false\n    emit('update:modelValue', form_data.value)\n    form_data.value = undefined\n  })\n}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/label/TooltipLabel.vue",
    "content": "<template>\n  <div class=\"flex align-center\" style=\"display: inline-flex\">\n    <div class=\"flex-between mr-4\">\n      <span>{{ label.label }}</span>\n    </div>\n    <el-tooltip v-if=\"label.attrs.tooltip\" effect=\"dark\" placement=\"right\">\n      <template #content\n        ><div style=\"max-width: 200px\">{{ label.attrs.tooltip }}</div></template\n      >\n      <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\" style=\"flex-shrink: 0\"></AppIcon>\n    </el-tooltip>\n  </div>\n</template>\n<script setup lang=\"ts\">\ndefineProps<{\n  label: any\n}>()\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/layout/RowLayout.vue",
    "content": "<template>\n  <el-row> </el-row>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport _ from 'lodash'\n\nconst props = defineProps<{\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n}>()\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/model/Model.vue",
    "content": "<template>\n  <div class=\"flex-between w-full\">\n    <el-select\n      :model-value=\"model_value?.model_id\"\n      @change=\"handleModelChange\"\n      v-bind=\"$attrs\"\n      popper-class=\"select-model\"\n    >\n      <el-option-group\n        v-for=\"(modelList, providerName) in groupedOptions\"\n        :key=\"providerName\"\n        :label=\"relatedObject(providerList, providerName, 'provider')?.name\"\n      >\n        <el-option\n          v-for=\"item in modelList\"\n          :key=\"item.model_id\"\n          :label=\"item.model_name\"\n          :value=\"item.model_id\"\n        >\n          <div class=\"flex\">\n            <span\n              v-html=\"relatedObject(providerList, providerName, 'provider')?.icon\"\n              class=\"model-icon mr-8\"\n            >\n            </span>\n            <span>{{ item.model_name }}</span>\n          </div>\n        </el-option>\n      </el-option-group>\n    </el-select>\n    <div class=\"ml-4\">\n      <el-button @click=\"openParamSetting\" :disabled=\"!model_value?.model_id\">\n        <el-icon>\n          <Operation />\n        </el-icon>\n      </el-button>\n    </div>\n  </div>\n  <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"handleParamRefresh\" />\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { groupBy } from 'lodash'\nimport { relatedObject } from '@/utils/array'\nimport type { FormField } from '../../type'\nimport { providerList } from './provider-data'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\n\nconst props = withDefaults(\n  defineProps<{\n    modelValue?: { model_id: string; model_params_setting: Record<string, any> } | null\n    formField: FormField\n  }>(),\n  {\n    modelValue: null,\n  },\n)\n\nconst emit = defineEmits(['update:modelValue', 'change'])\n\nconst model_value = computed({\n  get: () => props.modelValue,\n  set: (value) => {\n    emit('update:modelValue', value)\n    emit('change', props.formField)\n  },\n})\n\nconst groupedOptions = computed(() => {\n  const list = (props.formField.attrs?.provider_list as any[]) || []\n  return groupBy(list, 'provider')\n})\n\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\n\nfunction openParamSetting() {\n  if (!model_value.value?.model_id) return\n\n  const model_form_field =\n    props.formField.attrs?.provider_list.find(\n      (p: any) => p.model_id === model_value.value?.model_id,\n    ).model_form_field || []\n\n  AIModeParamSettingDialogRef.value?.open(\n    model_value.value.model_id,\n    undefined,\n    model_value.value.model_params_setting,\n    model_form_field,\n  )\n}\n\nfunction handleParamRefresh(paramData: any) {\n  if (model_value.value) {\n    model_value.value = {\n      ...model_value.value,\n      model_params_setting: paramData,\n    }\n  }\n}\n\nconst handleModelChange = (selectedId: string) => {\n  const list = (props.formField.attrs?.provider_list as any[]) || []\n  const selectedItem = list.find((p) => p.model_id === selectedId)\n  model_value.value = {\n    model_id: selectedId,\n    model_params_setting: selectedItem?.model_params_setting || {},\n  }\n}\n</script>\n<style lang=\"scss\" scoped>\n// AI模型选择：添加模型hover样式\n.select-model {\n  .el-select-dropdown__footer {\n    &:hover {\n      background-color: var(--el-fill-color-light);\n    }\n  }\n\n  .model-icon {\n    width: 18px;\n  }\n\n  .check-icon {\n    position: absolute;\n    right: 10px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/model/provider-data.ts",
    "content": "export const providerList = [\n  {\n    \"provider\": \"model_azure_provider\",\n    \"name\": \"Azure OpenAI\",\n    \"icon\": \"<svg t=\\\"1724827784525\\\" class=\\\"icon\\\" viewBox=\\\"0 0 1083 1024\\\" version=\\\"1.1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\" p-id=\\\"5331\\\" width=\\\"100%\\\" height=\\\"100%\\\"><path d=\\\"M540.115607 419.535168L460.236208 3.644671 376.260809 1.620347c-31.825387-5.244301-54.020439 8.390289-66.588115 40.908208C297.107977 75.046474 194.101272 379.938035 0.654058 957.203237-1.245965 999.731792 12.341272 1020.99311 41.418728 1020.99311h275.015029c14.599399-4.010173 25.104277-17.02178 31.514636-39.031861 6.410358-22.010081 70.46659-209.486428 192.168694-562.427561z\\\" fill=\\\"#0D559D\\\" p-id=\\\"5332\\\"></path><path d=\\\"M669.463676 658.929202l-351.23052 6.189873c-29.527306 6.969711-32.546035 22.615306-9.054705 46.941226 35.237734 36.486659 357.010497 317.661965 372.719722 311.580115 9.100578-3.52185 16.537896-4.005734 26.473064-9.14941 24.994775-12.939098 21.644578-70.568694-10.05059-172.887307l-28.855491-182.674497z\\\" fill=\\\"#0078D4\\\" p-id=\\\"5333\\\"></path><path d=\\\"M371.417526 0.331468l350.216879-0.325549a47.405873 47.405873 0 0 1 31.042589 11.556994c7.219792 6.252023 13.292763 14.842081 18.220393 25.765734 9.603699 21.293873 112.287815 325.368601 308.052347 912.225665a54.729249 54.729249 0 0 1 1.284439 30.169526c-6.225387 25.780532-19.432324 39.509827-39.623768 41.181965-24.348116 2.018405-144.088046 2.962497-359.221272 2.833758 30.551306-9.66141 44.410821-28.275422 41.578543-55.843515C720.153156 940.511445 618.336185 634.30289 417.516763 49.264462h0.00296a84.346821 84.346821 0 0 0-13.869873-25.255213C392.112092 9.559306 382.194682 2.04652 373.893179 1.47237c-10.553711-0.728046-11.379422-1.109827-2.475653-1.140902z\\\" fill=\\\"#2FA7E7\\\" p-id=\\\"5334\\\"></path></svg>\"\n  },\n  {\n    \"provider\": \"model_wenxin_provider\",\n    \"name\": \"千帆大模型\",\n    \"icon\": \"<svg width=\\\"100%\\\" height=\\\"100%\\\"  viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\">\\n<path d=\\\"M2.47044 5.80656L12.1704 0.387207L21.4823 5.80656L17.3963 8.21545C17.2829 8.28231 17.1432 8.28719 17.0254 8.22842L12.3407 5.89151C12.2333 5.83793 12.107 5.83699 11.9988 5.88897L7.11512 8.23493C7.00066 8.28991 6.86647 8.28551 6.75588 8.22314L2.47044 5.80656Z\\\" fill=\\\"#5BCA87\\\"/>\\n<path d=\\\"M11.2964 23.613L1.74219 17.9418V7.18647L5.92448 9.51244C6.03922 9.577 6.11329 9.69523 6.12119 9.82642L6.435 15.0425C6.4422 15.1621 6.50449 15.2716 6.60371 15.3391L11.082 18.3858C11.1869 18.4572 11.2502 18.5753 11.2513 18.702L11.2964 23.613Z\\\" fill=\\\"#2464F5\\\"/>\\n<path d=\\\"M12.7041 23.613L22.2583 17.9418V7.18647L18.076 9.51244C17.9613 9.577 17.8872 9.69523 17.8793 9.82642L17.5655 15.0425C17.5583 15.1621 17.496 15.2716 17.3968 15.3391L12.9185 18.3858C12.8136 18.4572 12.7503 18.5753 12.7492 18.702L12.7041 23.613Z\\\" fill=\\\"#EC5D3E\\\"/>\\n</svg>\"\n  },\n  {\n    \"provider\": \"model_ollama_provider\",\n    \"name\": \"Ollama\",\n    \"icon\": \"<svg version=\\\"1.1\\\" id=\\\"Layer_1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" x=\\\"0px\\\" y=\\\"0px\\\" width=\\\"100%\\\" height=\\\"100%\\\" viewBox=\\\"0 0 181 256\\\" enable-background=\\\"new 0 0 181 256\\\" xml:space=\\\"preserve\\\">  <image id=\\\"image0\\\" width=\\\"181\\\" height=\\\"256\\\" x=\\\"0\\\" y=\\\"0\\\"\\n    href=\\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALUAAAEACAYAAAD1IzfbAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABzUSURBVHgB7Z0JtGRVdYZ/bTUMDiiKMnYLhKFVJCBOKDxdIM4oEiUaYiNxJRpFkhVwjLTLKVFwiONSGWIaUIFAUNAElUYDRkQFFRAE+8nUzDLP0N7v3aruetU1nLPPuVX3Vu1vrU033f3O+Netc8/ZZ2/JcRzHcRzHcRzHcRzHcRzHcRzHcRzHcRzHcRzHcRzHcRzHcRzHcerIw1RPaNdWhT279euC1p/fV9hvC/tJYSvlVM3GhT2vsO0Ke1Trzx4s7PLCzm39ukrOQDYo7JDCLlQ5WP3sIZXC/tvC1pGTE8aTcWV8GedB88A8MV8byFkLnsQHFXaTBg9iL5st7A2q77dOU2D8GMdZxc8B88b8LZAzx2aFnaX4gey2E+RPDCuMG+OXOgfM42aacnYs7GqlD2bbLi7sqXJiYLwYt1xzwHzuqCll58JuVr7BbNtVhW0jJwTGifHKPQfM686aMrYu7DrlH8y2/b6wTeQMgvFhnKqaA+Z3a00J6xf2K1U3mG37/8L+TE4vGBfGp+o5YJ7X1xTwFVU/mG37rJxeMC6jmoOvaMLZS8P3PnMaBwV7yOmE8WBcRjUHzPdemlDY1L9MoxvMtlHnunKAcRjXHEzkIdmhGv1gtu2DcoBxGNccHKoJg819y2lhLrutsCdruqH/jMO45oD5H8nh2MM1Gt5R2BM0Ph6jCXxSREL/H6Pxwfy/QxMCa6lrZf+E31nYWwp7d2H3J5RzR2FP0nRCv+m/dewYd8afebgzoRx0MBFr6zfLPggPFPaKjrL2V9ruyb9oOqHf1jFjvPfvKIv5eCChvDdrAjhb9gH4SI/yDk8oD7+ER2q6oL8p/jWH9yjzIwnlna2Gg2+B9cnKZYBeJ4L82UXGMrHXarqgv9axYpz7zcFvjWWih0b75iyVfUD3GVDuXgnlnqLpgv5ax2rQock+CeUuVYP5tWydxmdg2M7MD41l31vYhpoO6Cf9tYzTD4eUzfxYfXh+rYaCh5b1k/yWgPL3qLj8SYB+WscoxL0gpfxGevBxvcfS2VsU5tnF9SPrN8G3NR3QT+uTNOR6HPN0i7GOg9RArAP6NYXzTmMddxW2niYb+kc/LePzzvBq5uZrKh4sjyjsVtk6u6fC4VDBumZ8qSYb+mcZF8Yz5pBqT2M96OMRahBc5bF0lGtAsfvIpxvr+qQmG/pnGZfTFQfzZb2WV8mVr6p8P54vG2eoPJKN4WTZmNFkMyMbsePJfJ0hG1adDKQqUe8iG99XPKep/NTHwo3nx2oyoV+WG92M42mKxzJvYNXJWBgWYamfWU+arPulL9JkQr8s4/Er2djGWN+FqoAqntR4YVnEeWNhv5ONH8hGo54UEVj7ZR1H5u1GxYNOsnvtVSFqggla3mp/JtsyAv5PNp6lycTaL+s4Mm8/UzzoZDtlpootFesS4heyc45sPF3pcEixuco3+cUq+8//EzGUO4Ebac0dSSb/BpX7x/xKbAxib+AcdIHKr/87lI61X9ZxBObvZYqH8TpfGalC1H8uGxfIDmF9ryhsC8XBUS0eZ/fG/Zi2VOns8+LCXlDYUwJ/jg/ARq3fL+rx9/gpc5q3XOXLF7/epTjoj+UImvFLCY9snT+rXkbKkbK9NOygNKwnmIsDy9++sA/JfjRvMW6Z/Fdhbyzs0QpjsbGu1BO+HYz1HqkGwJ5lbMfwsU0NY/BR2QZ17wFlcqfv71QGGF81Zru9sKNUBkEf5Jext7H8jyoN5s/iO2/d4+5LFS+Klhh2rC3vVhrW7aEte/zZwsI+VdiVhX1Z9dgl4Ul9gMp1Lx8ynt69Tl+3lI3U7TXm7zrFkz3mYV1EPat0LpWNzTt+jyCObJX1j4U9TvWE3Y1jVb5gHqj54t5cNqzj18ms4ql9IE8iyVu++r6hdDYw1n2iSgeef5fdOWrcdklhr1O5LDnRWMYTlc43jHVnzUCQe/djY9m4Qeng14vnV+zT9YUqRfF4NRe2xRDzctnmgHGzHJ50Y51H2nyVMpFb1I+SjZuUh2sUL+qNNDnMyMY1yoN1Hq266UnuNXXofm031ysPnobORq5xs86jVTc9yS1q6ycu9oChH1fLsZBr3KzzWOsntZXblIdb5FjINW655jGJuoj6ZuUhVznTxkSNf6PuiAWQaxmTCttUvHzNqjzq5lCC0zZuX2/Ysq1Un3wodRm3LEyaqHNsDVrga/fMwn5U2E9VetvdPuRn2FPGAYvTSo6+cezfUePJ3DuucauESRP1Ko0OvmrZGyZTLGK+T3HQ1j+07MTWn22q0neDdMp4/41qeTjKcaucuqypmwRRO/G7QIA4O+EiGivofrAL8cXCdld5oPIJ5dvDnxrqIupcEe5TnaL6wZPsOypvP/MEPb6we1Qtl6sMdL6wsINV7XZlrnEbZ6aC1dRF1LkCNuY6xOmEK06I+VWF/USjhxdN8h7iTI/Iq9i2zDVutQi8mVvUD8pGrkDoOdeGnLK9qbDdVGaHHTc8TVmObFvYMcrb11xlWefRqpue5Ba19bjV6gjVzU7KwzKV9/yOU/1eoniq4lfNdbIrlYdc42adx1q7N+DLa3E9/LTSIYDLH431t42tuTeqOeBZSESlVYnGuOUI7PNpY/1WH/CRwBahpVPLlM57jXW3jdgVofcV6wT72u9Temrm9yqdZca6s24t596n5jY0hw6xb8Ghn1ReRJ6h8mtuM5Unckwqp3Vvlx3WzK9WMw8hEMXHVO6W/Id652gJgR0WHIse3iqTF1R8nFkacNk4ZGvR8sRFLw8oI1WcXnEtKPba+2xhT239nrt4nKxxzX9Rq6wdWr8PvVEdw1kqdzaGnQA2AdbZLEeqyMVOPJJZlaelv2v9npzj52tNrJIV6h36YRCUVfvERmcq/uuHTypPCsJepSSfjDWOtGuxt5qRVyotiWqs8R7y1Va9lvyKZ6oBWNdVo7ZZTW6+8n9QM+YAy/E+NY8qDl9WqP7wJMO/wnKlvwlw1J5dLBWRXS9ViPpy1Z8Pq1x6TCo8AcnbcoXqTxP0ol1V76+75Zo878R+cFMeZ6s6z8euagBss3GkW8cBpF2LNF2k5HIfxXzU5aLEUPBiq+MgHqHpgyA/qSetVdnxqoAq1tTE3cjly5ETgrV8TNMHXn0fVj1BK1WcPWSFE7/zVM+nwiGaXjhl5HSwjvPCC3ttc8UT6YjA23UcONZuMQkvJxFia9dxbjB0U7tIWU/QaIORx9qxcvCVqfNOyG9U6qgW8AlrJyGq62AtlAPvUL0ju6Kj5Cd2qkMTYvmuytQRVUFncXohUQ4CJfEPnmPtQO2kLCMWG0fexNIgxjShYTk15NvjGE2Gs1IuiG29r9a4CPAiyfiuULlL0j5lJbQva13mmNs2O7R+tuqY3RerTIj0B40BPKu4eVHFJxanpm8Vtr8yBw90kuBhQZySw1Tt+xO6GrnnHhVerfydwa2RsAN1jeDvrIFveVyEv6TS9TS3FtDXyITNHvQKKWsH2AbEddHjkDQTliokQmKZl1MX6KzyMw+eoOcrX6P5NP61XMyTAgI8RrYsXf0MvVX2zY3wvqM8DaXTOJZvIGcSIS4g3ne5hI3uKnnwfVx5GkgMutfImXS4nZ7zwsjHlZk9lecrhS02a54/p3nwMolfd47rZehvT2WC4+UcfgNkNvVdjenkpcrzEokOs7g7nJShMafIfnXfmQyIR5jDBfYkJTKToRGnKnOiGqexPFfl7fNUTc3ICEK8OLHyH6uaGBROc2FdnOpYhS5ND8q3J1bMlk6O1MDO5EE+9dSndXRELu6NXZdQIb4bz5Tj9Ifj9RRRo8+o+43vSazwrXKcweBdmXo6/Z7QylgD35BQES+G48gw5TQPgn2SZsSqteCc6inrHXxzN5XjhHOY7HrDn37oVjFP2J8nVHKwHCcOliFcUrBq7k3DKtg5ofBL5PvRjo19ZNfdmcMK/2xC4W+Q49hghUDge4vuyKDw1H4FE19upbHgC+U+0U4ar5D9gdp3F2QmodC3yHHS4GltDbPxs36FHmEskO0/Pwp3cvD3smkQt9TNehV4obHAHOniHAe4WGB1UT2wuzD2lq2XAP5CjpMP622Z47oL2s9YEJm4/PTQyQmp/yxaJFbInBbbOxbPk41TWwU6Ti6+X9hdioc19Vwex7aod5aN78lx8oKgfyQbczpG1Dyyd1A8BBo8R46Tn+WygYPUnKgJQGJJkMmeouVrwnGGcbZsPI3/IOrYlMttfi7HqQaCTz6keOa0jKgXysZv5DjVwF71rOLZgv8g6s1k4xI5TnVcqniIp70AJyZrgJkVcoAXbYK98+a9WOVX4KYte6TK4Ob8ygOEr1SiFV3X+vXqluFPfJHKJV07Bt20MysbmyBqazqCqzVa+PA9p7BNVE469ePIcqtGD/uhhB7eo7DdFH5rHmFzU2OL1v9v1ePfkBqPLS32awmMeKVGD2NNcHU+mHxouTZFFq1RjvW1ssEDREcr/vTmfo0OAnt/U73vst3T+runq3pIskPOFLYxc4aqHeaoc06r3lEk+WEch431jhoNb5VtzIgEZRL1KBK581T7SGEPBLSHAecqWRVH9mwTfU3l9uWqMdpdrXY8Tflh3Bi/kEuwzAfzUrX//BLZxmmGHz7a8IMrVD1fVHy7PqB8sD4+UeXNilU1sgdb7VqsfHzA0I4vqlqWyDY+M/zw0YYfrDpzktXBiq/rvZQGb9AEWgn5hhinPdBqZ2q2WMbLupzaT9WxRLY2zfDDRxt+8B5VBxmgLpOtQxi7CNavxr8q7PqEusdh17fabYFxuiihbuZpgarBuqZ+IT98tPGHq7rtssTYnk57keLgbf/4DPWO045X/PbsizLUu0TVcJixPVvzSb1ZNqoIWkN73qd09oj4t7zxszVY5VfpKKD99CNmJyhmnPrBfFXx0mjNn3k/+9Q3yQbX0i9TXtgbtfqidLJJ4L9jPUkS0scqHV7gcB0gPhxf6StU7rWyn95es7LLgAB4IDBpjCEvfGyTIcbUr3LGDmeg1xf2PwH/PnSchtXJvP1UeVkkGysRdXA8si5I3HiG8rKrRgdxSr6utAA8iJYsCacXdpbKgOIh/L7Hn/HB2r2wl6tM9GR9UlEOlzf+RuW+8ihg3nKL2vJw4wFN7Gu9WLa1y5eVn6XGtnTb4UPq4UlmTa7DrsPJKvNnV/WStKBV/smy78Lc3+rnIA43lt1tS5UXXKEtW6m/aBewqWwdOU/5WWpsS7ftO6AO1pH3GspEJEcVtrVGy9atei0fQvo5aN28r6HMUYh6d2M7vtUugHXeLYYCeMxbLhcM4lDZOtNp9OXRfcrfVrZkOvhhVHGSF8PTWu2IbTv93bZPmYyTZe677VDlxXIYhB3WWciPjYW8Snl5gbEdnfalPmWvp/gIQDjwEH2qLjfmaQftoV0x/aDf6/UpMzWiP/YC5WW5sR2v7SzkU8ZCcq+r2Rn4rbEtGP4R/YIFfj6yrF8pz05MFdAu2hfTn8/3KYvxSvFrYb5ybunhuGVNdDRvm9m6tuLt/xHKy97GtrBtdkCfMndT3FEwuxm5l1a5oX20M2Z8dutT1gGyH5XvrbwcYGzHiu6CyCJqddx5hfLzmcg2MCHv6lMWH7qYpxrOQk2Js007aW9o3xiHfg8hxi9W2J9Rfn4Q2Ya2HdOrsPOMhf238sPX2ccU9kFjy2tQBoOYdB/EMWla4HjaS7tD+3jggLIYx5AtROaF+cl9kriN7A/XN/YqcKmxMAahqm0uHL6/p/4DzQnezICf56l0ucL6QcLJpuZOp92hiVwZj0FLxhmV49pvrpmP56saYt972sYafLW3YudbPXkPz5cNnNerTDNHgwmNRtAdnkx4CZ7dskFX6XlXOEHDYT+Xq2IXqLkwf5zqheSA/0uVy5Z+8ATetWXkZEE0LF1+IrtbxTA4QeUDt57i4WT7Jb3+AoFbw/nS6W1UP/B/CGn/B5UHjqj/SaXn41dUuoR2L2cWqfxW5N/gaB/rUTgI+hHS3xC/kFHzOdm0N2xJpXcnFHyK6gWf/JD1IRce1lE6RLq6tEf5hJLAnwPBE8u7+zSTb5oPKQ/04w8KWzJafUuqYHvZt/Hu0JBlIx21HCG37dWqD6EviG9THgbFVUa41w/5+2cpD29ThqfbCGGpc6bsmjsqpJJjEyrAzTL1elEuQvrBEbFlDdcLy/F7p71XeaA/IUffx6oecFM+ZdyCIvbyj1JCALAMqUOmrpBdj6BPeSCxR9fdluNyRJujAuq7XOOHl9uU08wfKoKYk6pexs8/SeNjfYV9MPdVPo6Tfbxo6y7KR8gJMXWur/FBEKUUlwgs6ubOs5UesIW9zsdrPCxWWBtzviwRdela2cbqc8rLUwLrfabGAxGtUi78YstlcDT7ZmKl2Nc1HmY0vG1V7LfyFh+TZ5sHBzsiVVw2uCmg/hmNHoR4mtJ0xbg9VwYWFXZnYuVVnjYOYkbD21bVQQtbd6TDHubUz/3OKvxm2lygeor66Up/WP6nEnh/hga8S6NnRsPbtVzV8oYBdbNLVHVC1eWqp6jfpTQ9Ef1g40EVDNul+KTSn2jbqp7kuEk9iBsG/B054O9WtVTdPyupejhE5fj1ZZioOeV5c+tXK6s0ekJSKxCOt6qLszAo/sbiiuum7M0D/p0lBUUqKXr4pQK2YUP2k3EeSrnONI790KsC/g1HyjmDLHaz04C/Y+lRZfhh+hVy9B8yTrlJ0QNjlsXHKHX/dXuNHp5UIWFpD1Z1DLuYYI1/F8LBGt53xqfKb4t+oIeU7eLjlMh2Sov+OU4np3M1vH1V5oFcMaTuJaqOczS87+dqfJwiu6bQ43ZK4MiEyjk2XqTxcYTC2rmTquHUIfXOqBp2Uli/j9D4WKQ0t4IjZYRTqZCv8H52kMbLHgpr56mqBk7r+k0cDvpVhV0Y9mFqW47gkCkcJLu20KXpNDjU4byXsQ2Y+5Z5LNQfemxd1SEI21fE1fhflUmJvl3YO9VKtlMB9COkv1VEAYiF+kMOiPpZ9MUOXiCuSKjwJaoHn1BYewmSWSeneQu0n36E9PcTqgfoxKox9Bn1ovvyhMq44l6XiEYLFX6jgvuOuXyrRw3tpv0h/WQ8FqoeoBNrSATs5YogJar+bqoXX1V427kp3TRh096YEAlfVb1AL1atHR9aCUED7zBWwlZSXZ7SbTguJm50TB+erGZAO0O279rGONTt+By9xPSh09Dpo0Mqeb2xglUaHg95XIQcRnQaDkfj3h0YBu2jnTH9qvKwKYXKNbfMWDgvKXWNbmS53MmpF/uhdXtq0x78H2JP5eh/Ha7Z9QLdhL7kdtuyYYW3c8BYCv831Ru+di0Dd3th/6rxf21v0moH7bE8cOrqtdcG/Vh0h14Hbk8+11gwNor84KkQhcl68YFdAw5N9tHoXibXa9V3ouxxMejvc1R/Ui4PDLwFY70UwH3Eur0g9oNcKiknpRg3oAl1tbSwV6qM8Zza/4e1ynllq9wzlJ4P/Z5Wf5sA/e8Xw2+Yvb+zoO7HtnU7ru2g0gS+qzL71UmyP3FxHd1D818mWePyNc+TESd2xpYg4EwWx+XEBeEiMtGEGCte8nDO4RYHt7pZHuRc8/KBeJ3K7b4mwJigI0saEnT70V5/wYDGbH112vPUPPhKtr6c1N2uUTOWHN2gI0t/0W3PB0JoWIFu4wk0Dr/cHPAkJf9hnQSZamepmmzEowAdWSNdrb7w0aluqwsmSZAeVDNhCUAeSaIj3aNmQ/vpB/25Ws0EHf1YNlbrt1PUz5CNs9VsGMiPq3QVPV3NhHbTfvrR1AdMG6ueeuoXt0jLYz9nfOU6wJOuKUuSs1rtnSTQk2Usvt2rsEsNBfHGP67QYlXD3ienVanbarntrla7TBGKGgB6stxhvLS7ILadLJv7V2ry2UBlGjRulFgdvVLtjlb9B7TaM+mgq9gxQr/zzgo2lm2wo0KpTgDkU9ldZcYF9rlJHmQ96Rs0ORe3yn93q76QPC6TBLqyjN1c5Kb24Yv11sdlmi7IsnBWy9rwdGAwN1RawPmbWsbBzSpNN+jK8q6Gjle2Rf1E2RhHMJS6gQCvaZmTB6uu5nTc3tLbWDaauh/q1BurruZ03Ba11efgRjlOfqy6evjq/8h+zH2rHCc/Vl3N6bgtaqsD+b1ynPxYdTWn49Qn9bVynPxYdTXvSW2lqkhDznSTpKu2qO+UDeuuieMMwqqrOR23RZ30tuk4mUnajWv/sPUEqylBX5xmYdXVnI7bor5NNqwnkY4zCKuu5nScuvwISZbjOLFYdTWX8LUtaqvfwpZynPxYdTW3FdgW9UrZyJIpyXG6sOpqLUcoS7J4Lnv6XrWTE/RkCTa0+sCmc+vE4huN83oTwo05zQE9WS5FrNZvp6gvlI1JvSvnjAernlbrN4eo65Y5wGk2Vj311K815NP18pNFJw/oCD1ZdNgz9B1BD+/JWaDjRGJ9sKLbdduFdD5h7y7sF7KxjxwnHauO0O3d7f/pXjZYQx7sp+YGiXTqAfrZTzYG6jYl/VdVWWOd6SA0W28vG/hymZLzpanBFZ16gH4suhua8wWOMRZOtE1r5FRnukE36Meiu2NCKniZsXDsBDlOPOjGqrmgnDacva80VsCnrYlpGZzxgV6sT2l0Gux7ZM1ph50r3wlxwkAn6MWqtajcnfizPpBQ2T/LcYaDTqwaQ5/RftcnJ1RIYHBrDhlnOkAfKQHtT5aBlOy32OWFPUGOszboAn2k6MvsHfq9xIqPl+OsDbpI0VVSwtNdZH8zxcjd8Ro5zhrQgyWnS9vQ4y5KZFlCA7BL5ThrsCTM6rRlygDZU29NbMi2cpxSByk6QodDs/mGOPcT1f19SsPvMTqQqgN0mC17BeK3ZkzClshxSh1YNYT+gm5YhV7DYmG/QnbukuOk6QD9PaSMpPi6YgvlOGU4sRQdZfPZX6ew3yU05JdynDWgB6uW0OE6ysBBCY3AXi3HWQN6SNHTQUqESDlXJTTgbDnO2qALq6bQY1Ja6/0TKseeL8dZG3SRoqv9lUDKJ+okOU5/0MfIVwCc/ljP6O+Th/l1BoM+0IlFX+iy7yn1oH1qYjA8TDa+IPf5cAaDPr4gG+jSFCPkfNk+Rdwbe4wcZzjoxHof9nxFwia5delxiBwnHPRiXYJsoQgONFZEPLMN5TjhoBd0Y9Hbgb0K7LemnpGN09TKkOQ4gaCX02RjJuYfz8r2ybEG+HOmG3Rj0dtsaAUbGSu4v7DHy3HiQTfox6K7jboL67X8eJZsECP4j3KceNCNNTb6WnrtJWprkMcfyXHsWPWzll57iXqxbJwrx7Fj1c9aeu0l6u1l4zw5jh2rfoL0eqPiF+u3yTN0OWmgH3QUq70bexXUCRmOLIcnZBvNen/MmTrQjyXrMnpdt/MPukW9mWxcIsdJx6qjebrtFvXGsnGlHCcdq47m6bZb1I+VjavkOOlYdTRPt92ifqJs3CzHSceqo3m6zbVjcY0cJ50sOuoW9eNkw3c+nBxYdTRPt92itjok+fLDyYFVR/N0m2v5cZscJ50sOuoW9SrZ8NwuTg6sOpqn225R3ygb1q1Ax+nEqqN5uu0W9Z2y4bfHnRxYgz/O022u5ceT5TjpDE190YeBy4+VsrG5HCedqJAHHczTba419VZynHSsOhq4praevVuvgDlOJ1YdDdQtMcpuUbyjNsnP/Sa5kwL6QUex2kOv82I+9npRvFjxLChsTzmOHfSzQPGg14EvihAdeK+FB7JxUrDqJ0ivS2QLKnK7EtMWOFMLukE/Ft0tCalgkbFw7JlynHjQjVVzi7oL67X8mC3sQtlYV44Tj1U3F6lHPL1+XnonKJ4HZbsN7Djo5kHF861ef/gncq3kkB0tBUAAAAAASUVORK5CYII=\\\" ></image>\\n</svg>\\n\"\n  },\n  {\n    \"provider\": \"model_openai_provider\",\n    \"name\": \"OpenAI\",\n    \"icon\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"100%\\\" height=\\\"100%\\\" fill=\\\"currentColor\\\" viewBox=\\\"0 0 24 24\\\" color=\\\"var(--gray-900)\\\"><path d=\\\"M22.418 9.822a5.903 5.903 0 0 0-.52-4.91 6.1 6.1 0 0 0-2.822-2.513 6.204 6.204 0 0 0-3.78-.389A6.055 6.055 0 0 0 13.232.518 6.129 6.129 0 0 0 10.726 0a6.185 6.185 0 0 0-3.615 1.153A6.052 6.052 0 0 0 4.88 4.187a6.102 6.102 0 0 0-2.344 1.018A6.008 6.008 0 0 0 .828 7.087a5.981 5.981 0 0 0 .754 7.09 5.904 5.904 0 0 0 .52 4.911 6.101 6.101 0 0 0 2.821 2.513 6.205 6.205 0 0 0 3.78.389 6.057 6.057 0 0 0 2.065 1.492 6.13 6.13 0 0 0 2.505.518 6.185 6.185 0 0 0 3.617-1.154 6.052 6.052 0 0 0 2.232-3.035 6.101 6.101 0 0 0 2.343-1.018 6.009 6.009 0 0 0 1.709-1.883 5.981 5.981 0 0 0-.756-7.088Zm-9.143 12.609a4.583 4.583 0 0 1-2.918-1.04c.037-.02.102-.056.144-.081l4.844-2.76a.783.783 0 0 0 .397-.68v-6.738L17.79 12.3a.072.072 0 0 1 .04.055v5.58a4.473 4.473 0 0 1-1.335 3.176 4.596 4.596 0 0 1-3.219 1.321Zm-9.793-4.127a4.432 4.432 0 0 1-.544-3.014c.036.021.099.06.144.085l4.843 2.76a.796.796 0 0 0 .795 0l5.913-3.369V17.1a.071.071 0 0 1-.029.062L9.708 19.95a4.617 4.617 0 0 1-3.458.447 4.556 4.556 0 0 1-2.768-2.093ZM2.208 7.872A4.527 4.527 0 0 1 4.58 5.9l-.002.164v5.52a.768.768 0 0 0 .397.68l5.913 3.369-2.047 1.166a.075.075 0 0 1-.069.006l-4.896-2.792a4.51 4.51 0 0 1-2.12-2.73 4.45 4.45 0 0 1 .452-3.411Zm16.818 3.861-5.913-3.368 2.047-1.166a.074.074 0 0 1 .07-.006l4.896 2.789a4.526 4.526 0 0 1 1.762 1.815 4.448 4.448 0 0 1-.418 4.808 4.556 4.556 0 0 1-2.049 1.494v-5.686a.767.767 0 0 0-.395-.68Zm2.038-3.025a6.874 6.874 0 0 0-.144-.085l-4.843-2.76a.797.797 0 0 0-.796 0L9.368 9.23V6.9a.072.072 0 0 1 .03-.062l4.895-2.787a4.608 4.608 0 0 1 4.885.207 4.51 4.51 0 0 1 1.599 1.955c.333.788.433 1.654.287 2.496ZM8.255 12.865 6.208 11.7a.071.071 0 0 1-.04-.056v-5.58c0-.854.248-1.69.713-2.412a4.54 4.54 0 0 1 1.913-1.658 4.614 4.614 0 0 1 4.85.616c-.037.02-.102.055-.144.08L8.657 5.452a.782.782 0 0 0-.398.68l-.004 6.734ZM9.367 10.5 12.001 9l2.633 1.5v3L12.001 15l-2.634-1.5v-3Z\\\"></path></svg>\"\n  },\n  {\n    \"provider\": \"model_docker_ai_provider\",\n    \"name\": \"Docker AI\",\n    \"icon\": \"<svg width=\\\"24\\\" height=\\\"24\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\">\\n<path d=\\\"M13.1753 11.2238H10.6946V8.98998H13.1753V11.2238ZM13.1753 3.5H10.6946V5.80958H13.1753V3.5ZM16.107 8.98998H13.6263V11.2238H16.107V8.98998ZM10.2436 6.26392H7.76291V8.53563H10.2436V6.26392ZM13.1753 6.26392H10.6946V8.53563H13.1753V6.26392ZM23.5866 10.0501C23.0604 9.67149 21.7824 9.55791 20.8428 9.74722C20.73 8.83853 20.2038 8.04343 19.3018 7.32405L18.7756 6.9833L18.4373 7.51336C17.7607 8.5735 17.5728 10.3151 18.2869 11.451C17.9487 11.6403 17.3097 11.8675 16.4828 11.8675H0.0953884C-0.242884 13.7984 0.320904 16.2973 1.74917 18.0011C3.13984 19.667 5.24465 20.5 7.95083 20.5C13.8518 20.5 18.2494 17.7739 20.279 12.7762C21.0683 12.7762 22.8348 12.7762 23.6993 11.0724C23.7369 10.9967 23.9624 10.5802 24 10.4287L23.5866 10.0501ZM4.38018 8.98998H1.89951V11.2238H4.38018V8.98998ZM7.31188 8.98998H4.83121V11.2238H7.31188V8.98998ZM10.2436 8.98998H7.76291V11.2238H10.2436V8.98998ZM7.31188 6.26392H4.83121V8.53563H7.31188V6.26392Z\\\" fill=\\\"#1D63ED\\\"/>\\n</svg>\\n\"\n  },\n  {\n    \"provider\": \"model_kimi_provider\",\n    \"name\": \"Kimi\",\n    \"icon\": \"<svg t=\\\"1724827730361\\\" class=\\\"icon\\\" viewBox=\\\"0 0 1075 1024\\\" version=\\\"1.1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\" p-id=\\\"4327\\\" width=\\\"100%\\\" height=\\\"100%\\\"><path d=\\\"M1049.6 0m0 186.1632l0 651.6736q0 186.1632-186.1632 186.1632l-651.6736 0q-186.1632 0-186.1632-186.1632l0-651.6736q0-186.1632 186.1632-186.1632l651.6736 0q186.1632 0 186.1632 186.1632Z\\\" fill=\\\"#000000\\\" p-id=\\\"4328\\\"></path><path d=\\\"M605.7984 459.9808c57.1392 1.28 104.1408 52.3264 104.1408 115.1488v232.5504a2.048 2.048 0 0 1-2.048 2.0992h-99.9936a2.048 2.048 0 0 1-2.048-2.048l-1.7408-294.0928c0-1.2288-2.2528-1.4848-2.6112-0.256-13.312 43.9296-52.736 56.32-99.84 56.32H329.984a2.048 2.048 0 0 0-2.048 2.048v235.9296a2.048 2.048 0 0 1-2.0992 2.0992H220.4672a2.048 2.048 0 0 1-2.048-2.048V241.5104a2.048 2.048 0 0 1 2.048-2.048h105.3696a2.048 2.048 0 0 1 2.048 2.048v216.32c0 1.1264 0.9728 2.048 2.0992 2.048h135.2192a2.048 2.048 0 0 0 1.8944-1.2288l96.9216-218.0096a2.048 2.048 0 0 1 1.8944-1.2288h116.7872c1.536 0 2.56 1.5872 1.8944 2.9696l-66.2528 142.2848c-19.7632 36.1984-34.304 61.8496-67.2768 72.8064-1.1776 0.3584-0.9216 2.4576 0.3584 2.4576h54.3744z\\\" fill=\\\"#FFFFFF\\\" p-id=\\\"4329\\\"></path><path d=\\\"M752.9472 227.9936c-11.776 9.8304-19.456 25.9072-19.456 50.176 0 22.784 7.2704 40.448 18.1248 50.9952-5.632 9.3696-11.4176 15.9744-15.7696 19.456-0.7168 0.6144-0.2048 2.2528 0.7168 2.2016l64.512-4.6592c14.336-1.1264 26.624-6.5536 36.9664-15.7696 12.3904-10.496 19.456-28.5696 19.456-52.224 0-24.2688-7.0656-40.3456-19.456-50.176a64.7168 64.7168 0 0 0-43.008-14.7968c-16.384 0-30.3616 4.9664-42.0864 14.7968z\\\" fill=\\\"#007AFF\\\" p-id=\\\"4330\\\"></path></svg>\"\n  },\n  {\n    \"provider\": \"model_zhipu_provider\",\n    \"name\": \"智谱 AI\",\n    \"icon\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" width=\\\"100%\\\" height=\\\"100%\\\"  fill=\\\"none\\\" viewBox=\\\"0 0 30 32\\\" id=\\\"icon-pure-logo\\\"><g clip-path=\\\"url(#icon-pure-logo_a)\\\" fill=\\\"#1665FF\\\"><path d=\\\"M15 32a.377.377 0 0 0 .377-.375.377.377 0 0 0-.378-.375.377.377 0 0 0-.378.375c0 .207.17.375.378.375Zm3.907-20.375a2.26 2.26 0 0 0 2.27-2.25 2.26 2.26 0 0 0-2.27-2.25 2.26 2.26 0 0 0-2.269 2.25 2.26 2.26 0 0 0 2.27 2.25Zm3.908 6.625a2.26 2.26 0 0 0 2.27-2.25 2.26 2.26 0 0 0-2.27-2.25A2.26 2.26 0 0 0 20.546 16a2.26 2.26 0 0 0 2.27 2.25Zm-15.63 0A2.26 2.26 0 0 0 9.454 16a2.26 2.26 0 0 0-2.27-2.25A2.26 2.26 0 0 0 4.917 16a2.26 2.26 0 0 0 2.269 2.25Zm17.647-6.501a1.38 1.38 0 0 0 1.386-1.375A1.38 1.38 0 0 0 24.832 9a1.38 1.38 0 0 0-1.386 1.374c0 .76.621 1.375 1.386 1.375ZM15 6.25a1.38 1.38 0 0 0 1.385-1.375c0-.76-.62-1.375-1.386-1.375a1.38 1.38 0 0 0-1.386 1.375c0 .759.62 1.374 1.386 1.374Zm-9.833 5.499a1.38 1.38 0 0 0 1.386-1.375A1.38 1.38 0 0 0 5.167 9a1.38 1.38 0 0 0-1.386 1.374c0 .76.621 1.375 1.386 1.375Zm0 11.251a1.38 1.38 0 0 0 1.386-1.374c0-.76-.62-1.375-1.386-1.375a1.38 1.38 0 0 0-1.386 1.375A1.38 1.38 0 0 0 5.167 23ZM15 28.625a1.38 1.38 0 0 0 1.385-1.374c0-.76-.62-1.375-1.386-1.375a1.38 1.38 0 0 0-1.386 1.375c0 .759.62 1.374 1.386 1.374ZM24.832 23a1.38 1.38 0 0 0 1.386-1.374c0-.76-.62-1.375-1.386-1.375a1.38 1.38 0 0 0-1.386 1.375A1.38 1.38 0 0 0 24.832 23ZM22.059 4.751a.88.88 0 0 0 .883-.875.88.88 0 0 0-.883-.876.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875Zm-14.118 0a.88.88 0 0 0 .883-.875A.88.88 0 0 0 7.94 3a.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875ZM.883 16.876a.88.88 0 0 0 .883-.875.88.88 0 0 0-.883-.876.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875ZM7.941 29a.88.88 0 0 0 .883-.875.88.88 0 0 0-.883-.876.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875Zm14.118 0a.88.88 0 0 0 .883-.875.88.88 0 0 0-.883-.876.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875Zm7.058-12.124a.88.88 0 0 0 .883-.875.88.88 0 0 0-.883-.876.88.88 0 0 0-.883.876.88.88 0 0 0 .883.875Zm-.503-8.251a.377.377 0 0 0 .378-.375.377.377 0 0 0-.378-.375.377.377 0 0 0-.378.375c0 .207.17.375.378.375ZM15 .75a.377.377 0 0 0 .377-.375A.377.377 0 0 0 15 0a.377.377 0 0 0-.378.375c0 .207.17.375.378.375ZM1.386 8.625a.377.377 0 0 0 .378-.375.377.377 0 0 0-.378-.375.377.377 0 0 0-.378.375c0 .207.17.375.378.375Zm0 15.625a.377.377 0 0 0 .378-.374.377.377 0 0 0-.378-.375.377.377 0 0 0-.378.375c0 .207.17.375.378.375Zm27.228-.125a.377.377 0 0 0 .378-.375.377.377 0 0 0-.378-.375.377.377 0 0 0-.378.375c0 .207.17.375.378.375Z\\\"></path><path d=\\\"M19.538 20.5c-1.007-.374-2.142.126-2.646 1-.505.876-1.513 1.375-2.647 1-.63-.126-1.008-.625-1.261-1 0-.125-.127-.25-.127-.5 0-1 .756-1.75 1.64-1.875h.25c1.765.125 3.277-1.375 3.404-3.126.127-1.75-1.386-3.25-3.152-3.375h-.378c-1.008 0-1.64-.75-1.64-1.75 0-.249 0-.5.127-.624v-.125c0-.126.127-.126.127-.25.378-1.25-.252-2.5-1.512-2.874-1.135-.375-2.52.25-2.9 1.5-.377 1.125.252 2.375 1.387 2.874.168.084.336.126.505.126h.126c.883.125 1.64.875 1.64 1.75 0 .374-.127.624-.252.875-.252.5-.505 1-.505 1.625 0 .626.127 1.375.505 1.875.126.25.251.625.251.876 0 .875-.63 1.625-1.512 1.75h-.378c-1.261.249-2.018 1.5-1.764 2.75.253 1.25 1.512 2 2.646 1.75.63-.126 1.135-.501 1.386-1 .505-.876 1.64-1.375 2.647-1 .505.126 1.008.5 1.262 1 .251.375.756.75 1.26 1 1.262.374 2.396-.25 2.9-1.5.504-1.126-.127-2.376-1.387-2.751h-.002Z\\\"></path></g><defs><clipPath id=\\\"icon-pure-logo_a\\\"><path fill=\\\"#fff\\\" d=\\\"M0 0h30v32H0z\\\"></path></clipPath></defs></svg>\"\n  },\n  {\n    \"provider\": \"model_xf_provider\",\n    \"name\": \"讯飞星火\",\n    \"icon\": \"<svg t=\\\"1713509569091\\\" class=\\\"icon\\\" viewBox=\\\"0 0 1024 1024\\\" version=\\\"1.1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\" p-id=\\\"4361\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\"  width=\\\"100%\\\" height=\\\"100%\\\" ><path d=\\\"M500.1216 971.40736c-55.58272-4.28032-102.11328-16.7936-147.74272-39.7312-115.0976-57.83552-192.88064-168.5504-208.81408-297.12384-2.8672-23.18336-2.90816-69.18144-0.06144-93.3888a387.95264 387.95264 0 0 1 82.65728-196.46464c6.22592-7.7824 47.616-49.7664 94.49472-95.92832 112.8448-111.104 111.53408-109.85472 113.29536-108.09344 1.024 1.024 0.75776 5.59104-0.8192 14.45888-3.13344 17.57184-3.13344 55.99232 0.04096 77.0048 9.66656 64.49152 37.66272 124.3136 87.16288 186.32704 12.6976 15.91296 59.22816 63.24224 76.57472 77.88544 20.13184 16.9984 33.1776 37.53984 39.34208 61.8496 4.07552 16.0768 4.07552 42.10688 0 58.20416-10.24 40.57088-40.8576 72.58112-81.6128 85.38112-9.35936 2.92864-13.84448 3.39968-32.68608 3.39968s-23.3472-0.47104-32.768-3.39968c-29.02016-9.07264-56.40192-30.06464-32.52224-24.94464 12.94336 2.7648 29.65504-3.2768 37.49888-13.57824 10.81344-14.1312 12.57472-29.53216 5.09952-44.48256-3.76832-7.53664-6.8608-10.91584-19.12832-20.82816-33.1776-26.86976-65.7408-59.5968-88.8832-89.25184-11.81696-15.17568-28.8768-40.59136-33.95584-50.5856-1.92512-3.7888-4.15744-6.90176-4.95616-6.90176-2.00704 0-17.92 24.43264-24.73984 37.96992-7.84384 15.52384-15.33952 37.888-19.12832 57.0368-4.64896 23.3472-4.64896 59.14624-0.04096 82.16576 17.77664 88.63744 82.78016 154.74688 171.02848 173.8752 12.3904 2.70336 19.39456 3.23584 42.496 3.23584 23.08096 0 30.1056-0.53248 42.47552-3.23584 43.04896-9.3184 78.4384-28.672 109.6704-59.904 32.72704-32.72704 52.26496-69.44768 61.29664-115.3024 4.54656-23.06048 4.56704-56.36096 0.04096-79.29856-3.87072-19.5584-8.76544-35.16416-15.85152-50.3808-6.69696-14.35648-6.0416-15.21664 9.1136-12.0832 40.89856 8.45824 85.6064 31.41632 114.40128 58.75712 34.6112 32.84992 49.27488 65.45408 49.27488 109.71136 0 24.00256-3.4816 41.6768-13.35296 68.17792-20.54144 54.9888-50.54464 100.61824-93.7984 142.66368-51.26144 49.80736-116.8384 85.03296-183.95136 98.79552-30.45376 6.2464-76.53376 9.89184-101.1712 7.9872z m391.53664-433.93024c-17.05984-32.93184-41.75872-56.48384-76.8-73.19552-18.80064-8.97024-35.67616-14.52032-68.75136-22.58944-44.46208-10.8544-66.2528-18.2272-93.16352-31.62112-26.2144-13.04576-46.16192-27.3408-66.3552-47.5136-26.70592-26.74688-42.63936-52.4288-54.35392-87.67488-10.36288-31.27296-10.0352-27.2384-10.62912-128.96256-0.45056-78.37696-0.2048-92.0576 1.51552-92.0576 1.1264 0 45.8752 42.98752 99.40992 95.51872 190.95552 187.37152 194.58048 191.11936 216.7808 224.78848 20.13184 30.53568 39.26016 72.0896 48.76288 105.92256 4.7104 16.73216 11.0592 48.18944 11.81696 58.40896 0.7168 9.8304-2.84672 9.35936-8.23296-1.024z\\\" fill=\\\"#3DC8F9\\\" p-id=\\\"4362\\\"></path><path d=\\\"M523.12064 53.8624c-1.7408 0-1.96608 13.68064-1.51552 92.0576 0.57344 101.74464 0.24576 97.6896 10.6496 128.96256 11.6736 35.2256 27.62752 60.928 54.33344 87.6544 20.19328 20.19328 40.1408 34.48832 66.3552 47.5136 26.91072 13.4144 48.70144 20.80768 93.14304 31.6416 33.09568 8.0896 49.9712 13.6192 68.75136 22.58944 35.04128 16.71168 59.74016 40.2432 76.8 73.19552 5.40672 10.38336 8.97024 10.8544 8.25344 1.024-0.75776-10.21952-7.12704-41.6768-11.81696-58.40896-9.50272-33.83296-28.63104-75.3664-48.76288-105.92256-22.20032-33.66912-25.82528-37.41696-216.7808-224.78848-53.5552-52.5312-98.304-95.51872-99.40992-95.51872z\\\" fill=\\\"#EA0100\\\" p-id=\\\"4363\\\"></path><path d=\\\"M391.3728 762.30656s86.2208 88.41216 241.9712 100.06528c155.7504 11.63264 193.536-45.44512 193.536-45.44512s76.3904-100.864 65.08544-177.54112c-11.32544-76.67712-71.02464-131.21536-174.96064-154.89024 0 0 31.90784 80.2816 20.5824 128.14336-11.32544 47.86176-20.0704 138.93632-159.5392 186.7776 0 0-102.68672 30.208-186.6752-37.10976z\\\" fill=\\\"#1652D8\\\" p-id=\\\"4364\\\"></path></svg>\"\n  },\n  {\n    \"provider\": \"model_deepseek_provider\",\n    \"name\": \"DeepSeek\",\n    \"icon\": \"<svg width=\\\"100%\\\" height=\\\"100%\\\" viewBox=\\\"0 0 50 50\\\" fill=\\\"none\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"\\n\\txmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\">\\n\\t<path id=\\\"path\\\"\\n\\t\\td=\\\"M48.8354 10.0479C48.3232 9.79199 48.1025 10.2798 47.8032 10.5278C47.7007 10.6079 47.6143 10.7119 47.5273 10.8076C46.7793 11.624 45.9048 12.1597 44.7622 12.0957C43.0923 12 41.666 12.5356 40.4058 13.8398C40.1377 12.2319 39.2476 11.272 37.8926 10.6558C37.1836 10.3359 36.4668 10.0156 35.9702 9.31982C35.6235 8.82373 35.5293 8.27197 35.356 7.72754C35.2456 7.3999 35.1353 7.06396 34.7651 7.00781C34.3633 6.94385 34.2056 7.2876 34.0479 7.57568C33.418 8.75195 33.1733 10.0479 33.1973 11.3599C33.2524 14.312 34.4736 16.6641 36.8999 18.3359C37.1758 18.5278 37.2466 18.7197 37.1597 19C36.9946 19.5757 36.7974 20.1357 36.624 20.7119C36.5137 21.0801 36.3486 21.1597 35.9624 21C34.6309 20.4321 33.481 19.5918 32.4644 18.5757C30.7393 16.8721 29.1792 14.9917 27.2334 13.52C26.7764 13.1758 26.3193 12.856 25.8467 12.5518C23.8618 10.584 26.1069 8.96777 26.627 8.77588C27.1704 8.57568 26.8159 7.8877 25.0591 7.896C23.3022 7.90381 21.6953 8.50391 19.647 9.30371C19.3477 9.42383 19.0322 9.51172 18.7095 9.58398C16.8501 9.22363 14.9199 9.14355 12.9033 9.37598C9.10596 9.80762 6.07275 11.6396 3.84326 14.7681C1.16455 18.5278 0.53418 22.7998 1.30664 27.2559C2.11768 31.9521 4.46582 35.8398 8.07373 38.8799C11.8159 42.0322 16.1255 43.5762 21.041 43.2803C24.0269 43.104 27.3516 42.6963 31.1016 39.4561C32.0469 39.936 33.0396 40.1279 34.686 40.272C35.9546 40.3921 37.1758 40.208 38.1211 40.0078C39.6021 39.688 39.4995 38.2881 38.9639 38.0322C34.623 35.9678 35.5762 36.8081 34.71 36.1279C36.9155 33.4639 40.2402 30.6958 41.54 21.728C41.6426 21.0161 41.5557 20.5679 41.54 19.9917C41.5322 19.6396 41.6108 19.5039 42.0049 19.4639C43.0923 19.3359 44.1479 19.0317 45.1167 18.4878C47.9292 16.9199 49.064 14.3438 49.3315 11.2559C49.3711 10.7837 49.3237 10.2959 48.8354 10.0479ZM24.3262 37.8398C20.1196 34.4639 18.0791 33.3521 17.2358 33.3999C16.4482 33.4482 16.5898 34.3682 16.7632 34.9678C16.9443 35.5601 17.1812 35.9683 17.5117 36.4878C17.7402 36.832 17.8979 37.3442 17.2832 37.728C15.9282 38.584 13.5728 37.4399 13.4624 37.3838C10.7207 35.7358 8.42822 33.5601 6.81348 30.584C5.25342 27.7197 4.34766 24.6479 4.19775 21.3677C4.1582 20.5757 4.38672 20.2959 5.15869 20.1519C6.17529 19.96 7.22314 19.9199 8.23926 20.0718C12.5327 20.7119 16.1885 22.6719 19.2529 25.7759C21.002 27.5439 22.3252 29.6558 23.6885 31.7202C25.1377 33.9121 26.6978 36 28.6831 37.7119C29.3843 38.312 29.9434 38.7681 30.479 39.104C28.8643 39.2881 26.1699 39.3281 24.3262 37.8398ZM26.3433 24.6001C26.3433 24.248 26.6191 23.9678 26.9658 23.9678C27.0444 23.9678 27.1152 23.9839 27.1782 24.0078C27.2651 24.04 27.3438 24.0879 27.4067 24.1602C27.5171 24.272 27.5801 24.4321 27.5801 24.6001C27.5801 24.9521 27.3042 25.2319 26.9575 25.2319C26.6108 25.2319 26.3433 24.9521 26.3433 24.6001ZM32.6064 27.8799C32.2046 28.0479 31.8027 28.1919 31.4165 28.208C30.8179 28.2397 30.1641 27.9922 29.8096 27.688C29.2583 27.2158 28.8643 26.9521 28.6987 26.1279C28.6279 25.7759 28.6675 25.2319 28.7305 24.9199C28.8721 24.248 28.7144 23.8159 28.2495 23.4238C27.8716 23.104 27.3911 23.0161 26.8633 23.0161C26.666 23.0161 26.4849 22.9277 26.3511 22.856C26.1304 22.7441 25.9492 22.4639 26.1226 22.1201C26.1777 22.0078 26.4458 21.7358 26.5088 21.688C27.2256 21.272 28.0527 21.4077 28.8169 21.7197C29.5259 22.0161 30.0615 22.5601 30.834 23.3281C31.6216 24.2559 31.7632 24.5117 32.2124 25.208C32.5669 25.752 32.8901 26.312 33.1104 26.9521C33.2446 27.3521 33.0713 27.6802 32.6064 27.8799Z\\\"\\n\\t\\tfill=\\\"#4D6BFE\\\" fill-opacity=\\\"1.000000\\\" fill-rule=\\\"nonzero\\\" />\\n</svg>\"\n  },\n  {\n    \"provider\": \"model_gemini_provider\",\n    \"name\": \"Gemini\",\n    \"icon\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" version=\\\"1.1\\\" id=\\\"Layer_1\\\" x=\\\"0px\\\" y=\\\"0px\\\" width=\\\"100%\\\" height=\\\"100%\\\" viewBox=\\\"0 0 256 256\\\" enable-background=\\\"new 0 0 256 256\\\" xml:space=\\\"preserve\\\"><script xmlns=\\\"\\\"/>  <image id=\\\"image0\\\" width=\\\"256\\\" height=\\\"256\\\" x=\\\"0\\\" y=\\\"0\\\" xlink:href=\\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo AAB1MAAA6mAAADqYAAAXcJy6UTwAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxIAAAsSAdLd fvwAABsOSURBVHja7Z3viyTHece/tQhu99Xuf7CbGMdExt61ZQghkBsTQggJ3DrY2I5/3JxlO5JP 1o0k66etvdmzfluWRvJZJwnHmktwEoINewQMIQTvEgiB5KTdJCaQvJn9D25ezeybq7zo6pnunuru 6p7uru6u7wdmZ7Znpquqp59vPfVU9dNCSglCiJus2K4AIcQeFABCHIYCQIjDUAAIcRgKACEOQwEg xGEoAIQ4DAWAEIehABDiMBQAQhyGAkCIw1AACHEYCgAhDkMBcJif3zjr/uLG2YHtehB73GO7AsQe EtgFMLJdD2IPegBusyGBO7YrQexBAXAYCZy3XQdiFwqA40igY7sOxB4UAEf5u7fONpgMjjAI6CgS 2LFdB2IfegAOIyUggS3b9SD2oAA4ipy/2LRdF2IPDgHcpcMYAKEH4Cg0fgLQA3AWCWzYrgOxDz0A R5HAjvSe8TdvnXVs14fYgQLgLhsAhwKuQwFwFCmx7Rs/pwLdhQLgIDd/fLbhv1bDgC3bdSJ2oAC4 iTf+Vy4ABcBdKAAOclcZfEAEtmzXidiBAuAmW8Hgn5S8LNhVKAAOItUqQP8BAH/91tmW7XqR6qEA uMkOMDd+NRTYsV0pUj0UAMf4y+tnWxJY943/rnrm5cFuQgFwDAl0IMPuv3ru2K4bqR5eC+AYfk8v JAARWgm4Y7tupHroATiGLgCotq8Pf3y2Y7t+pFooAA7xzvWzDQDbgDL+xaFAx3YdSbVQANxiN2jw d4GoCHRsV5BUCwXAIfwefiEAKMPvE3egADiElJ4HMPs/8gCw/tMfMzeAS1AAHOGtH53t+vP/mgCg nyEY8O4XSByBAuAIM/deRlYABl6r93Zt15VUBwXAHeYBQBlaARgVgc2fcjrQGSgADvAjz/3fBAIG LzXDgPmja7vOpBooAA4gI9N/ahsS4gG7tutMqkFIKZffC6ktb7x5tiEERgDWBYDoA2LeC8y2AVgB PnH/Q+eObdeflAs9gJajev/1wP+z55R4QM923Un5UABajgS6MVf/pcUDdn9yfZ48lLQTCkCLee3N sy0A5yOr/WASD1BrBnZtt4GUCwWg3fRCxq0RgcBdgnXi0LPdAFIuDAK2lFffnG4IiJFQ4/9Z4E8s BPsQfF8TFPz0Nx46d2i7PaQc6AG0FC/4J9ejrn40HrAQAFyMB3Rtt4WUBwWgrUj0pXoRs+R3ISjo v44MBS6+e50Zg9sKBaCFvPzGtCOBzXlvPv8b7Omj22aCsBgP6NtuEykHCkA76QNR414Ugbie398Q 8BDoBbQUCkDLeNHr/c8n9ObedvUnw/qAvu22keKhALSPfnxvHo4HaFKCJcUD6AW0EApAi3h+4PX+ QFJvrg8KBj+bEA/o224jKRYKQLsYSJnQmycEBQ3jARffoRfQKigALeH7g2lXqpTfQOzy3iLiAUPb bSXFQQFoCTIY+TdY8psrHuC9f/6d60wc2hYoAC1gfzDtA9gM9doB6y0sHjB/DG23mRQDBaDh9F+f biBy0Y/PsvGABA9i853rZz3bbSfLQwFoONIL/K2r11ku901KCbYYI0DYg7gL9N9hvoDGQwFoMM++ Pu1I4KI2qIf88QDDoOA6hwLNhwLQbAaAgcGmxAPuqi0m8QCEv3/hbQYEGw0FoKF87/VpXwLbxgYr E3vz+HhAugcx5FCguVAAGsgzr023JHDV/9/YYA22Ga0ZQEg8Nu9yhWBjoQA0EAkMcxmsQTxAQi5+ Pz0ecOXt67ybUBOhADSMp16b9gCcDxplmsGGsv4UuD4A4e8P3+ZQoHFQABrEE69NdxC42i+wPNc4 ig+E4wHxKcEyxwO2JYcCjYMC0CyGdxGe8w+KQHB76qq+pEVCWBQPw3jAlRucFWgUFICG8J3XpgOp LvZZapGPwfezxAM0HsQBhwLNgQLQAB774XQXEleA5N7cJB6QZX1A1kVC6jUXCDUICkDNeeSH061g 1N8nflWfpocuIR6QMry4cIPXCjQCCkD9OYDXq5pMxyUGBYPbs8cDZOz3Aa0gvX6DU4O1hwJQY3o/ nA5lYLVfhmv2w9tgYLBIFofMwwvvBeMBNYcCUFMefnXalcBFIGNvjpwGG4kH6IcXmZOIbErPgyE1 hQJQQ7796nRHAu+Z9OZJUXxEtmeJB2QNCsbWT+L8jetnA9vHlOihANSMy57xHwJmvXloG3QGW2A8 ICEomBIPuHLj+lnX9rEli/DuwDXiwVenGwI4FMD27G6+gO6OvQDUtsDdfkXkfd3dgKN3CYbmu0Lo tyO0P5FYl9m28H4/8cBD545tH2cyhx5ATXjgB9MNeD3/dmG9ORJ66CQPImGfS8QDICUOedFQvaAA 1IeBlJHr+wOvi44HZA0Khr6fNx7gTWdyZqBGUABqwDd/MB36EX+g4N5cvVhY5IN4g53tZ8l4QIwg bUrgkElE6gEFwDLfCBi/ufudsTePfHdh/0llIoNxA6aCtH2XIlALKAAWuT9i/P6zWQ6/jL05shlx tC5Z4gGGgrTtz3YQe1AALPG1V6ZDyPBCn+xz8uHtpgabNK2YJx6wRFLR7bevnw1t/xYuw2lAC3Rf mQ4FcDFt2s1sOi5+ii9uOk43rVhcmWLx+3Htm9flJoDeXzx07o7t38Y1KAAVc1EZP2BosCnGozNU wzl5vSEnlZlhfYBx+9SLFeAEQIciUC0cAlTIV17xxvxp7nf2HH7mQcHgZ8uIB+RMKgrpJTs5fJeB wUqhB1ABX3p5uiGAgQAuJrnfiav6RMG9eUx5s/2neCXpwwuR04PAiQA636QnUAkUgJL54svTjRW1 wm8Zg0Xke1XEA5YXpOzxAPV6rETg2Pbv13Y4BCiRL7w83QFwqNxbADnm1JHuvmeJ7sdn/dGUmeDy l5RU1N+2Lr3hQKfin8w5KAAl8fmXph0o448znqKn46qIB2QTpFxJRWciAOBX7/IqwlKhAJTA516a diXwK6lSeANJi2g0BpFksNLIeBbLNOjNyxGkzElFo3V5712uFSgNxgAK5rMvzaf5skzHhbahmnhA zJx86D1dO8qOB8S07wRA5xsMDhYKBaAgPvPSdGvFS3+1ncl4gNRr9osKFtoXJKEtK4MgjQXQ+TqD g4XBIUAB7L447UDiWAZu3JG2JNYoQJbB/S5keAHULalo9DvrEvjgJ0w5Xhj0AJbkwovTvlC36jbp zUPbkNjbASjG/daWWYYHIUx6c5GrfZpjeksIdO+/zCHBMlAAcvKnL063BDAUwHkjVzjyHlCB+60p sx6ClHuRULQuYwjs3n/53KGl06DxUABy8CcvTHeFwBDAeqHGg5wG25R4QOi9peMBwXa8IYD+1+gN ZIYCkIE/fmG6Ibz73l2IGkhZV9wZexAx+6yvIOVKKppU5gmA7tcuM0CYBQqAIX/0wrSjXP5NwzFq Ze43NPu1MbzILkgZ4wFmx3RfAINL9AaMoACk8IfPqwt5ROQSXpRoPOpF2R5EPQSpmHhApH2nALqX GBtIhQKQwB88P9kVEEMBrFflfof2E2ewSWU2UpBE+PvFCdJNAfQu0huIhQKg4dPPT7ZEIMIPiIIu kV3OeIBFI2mHIInF/RYnSGMB9C9ePjco6XRpNBSAAOefm2wIoAeBq4snl5ideFrDQjaDNTKemPKA 5dzvegqSWTzA6JjqPYgTAfS+ymFBCAqA4vefm3ThJe1Yjz+5RGHuN2BoPOpF2QZbD0EqJR4QLfOW EOh95VvnRiWcRo3DeQH4vecmHeEZ/na68Yi0k6uBOfzqJkjCvH0xddEdU41A3BRA78vfcjs+4KwA /O73Jx0B9IXAeSBLD1NePEB3wpbtftdPkHInFc1T5liJ/+BLjgqBcwLwO9+fdAD0hb+EN+lEVhuW iQcATcnhVydBKjAekFTmfPtMCP7cMSFwRgA+5ff4kbX7+Xu75HgAgPgxal7jUS+YVLQ0QZoJwRcd EYLWC8Anr026AugK4LzOeIC8V6SJ3Maj2+fygpS/zPoKkjBuX8GCNBbAgQD6X2h5sLCVArB9TU3n Ad0VYBNYPLlC24CcxpMtHqA7Yct2v0s3npxlxh9TlBYPyHlMbwmBwecfbOf0YasE4GP7k44AugAu LrMkNovB6kQgX2+nKbMMDyKhLvUUpPwXDWUqM/1YnwpveDD83IPtGR40XgDu3Z9sAdgVQE8ELtSp 9hLZauIBZoJUVA4/TZnWBElo216EIOWcMbkpgIPPPnjuAA2nkQLwkf5kA57R7wqhLs1V79m5RFYs nMw5epgCBSl/mfUVJLFYpjVBmm07FV6sYPhnDzbzMuTGCMCH+pONFWAXnuFfSDu5gPQftOh4gEkP U7jxaMq01psXfkyj24W2rCzHNIsHkdQ+TZmnQuAAwPAzDzRHDGotAL9xdbIDv6cHtpN+0HpcIltM PKCo9hUdoNTts1pBqiYekHjOiJg2h987hecZHF54oN7DhFoJwJZn8DvwevoO1Lp8YLkftNpLZJeI ByS1L5cgMalo0QHKrO1Tz7fg3R/y8ELNvAOrArB1ddKBZ+g76nld97m8P6idS2TLiQeYCFIVZdZD kApLKlqAIOnfi5YVYAxPDI7hCcIhLFKJAGxdnWzAM/LgYzvLPpIOsLHBJpxcST9oWfEAbVkxdUlq 39IGW4bxlFBmuH1CW1YWgzU6pgaCpC1TLJaVwAnCojBK/0oxFCYAASP3n7fUYwcxPXumigaeizRY IN8Pmn5yiRoJkr59zRakwpOKZj+mGc+lDIzhicExgJH/+sIDxa8/EFLKoPHG0Yn8v6UeQEEGblTZ wHNugy1IHMqKBxi1L+lEjqnLrM45BanyIY2RIJWSVLSQ9iXEA5blSD0fqudjAHcAIM9wwheAO6jI iJelEINNOblMflAzgxWpJ1G1Acpiy6yHIFWSRKSwGZOKOUp4b3jhgXPDe9Q/B/Bc9+PABzqB1+er r7sef8Ai1D8ycFRF4DMi8J6Ad1+8FczvOeefJFK9P9uv+p6IlOXvEyJ8vzoRqRdC35EI/uyh+kXq ovs+UuoSbF9SOxbql1RmbFsSPqsK17ZP1eWuCBx/02Ma2K+MLdN7YVznHMc0ri55zpmKRSDObsdQ HnyuGIBmyNDx31KPDWQM8mUhqsRJvV2iS2jQw2hVP7MHwaSiuY+pUftKTSpaiodUECfw3P9j9QzM hwY+ibGDUmcBtq5OtjAPBO6o14V4E0WcXAAKCfBkiQeYCFJhxhNT3qzOBiezeftsC1LpSUULF4cM HMELBo7gGfioqJkCK+sAAgt+OsgxJTirPOphPLNtqWUyqWi5glRdPCCpfUvEA8ZQU4GYR/5HKJFa rARUQ4qOeuxCXcNv1ACk/6D1uUSWSUXLFyRh3r6YuuiOaT5BSm3fKdQKQXjGfoyKqYUARFFDh114 1/anegfL/KDRk2v2WSD2hF6uh2FS0XIFqdKkosbH1Dd4IXAolNF/psIFP3HUUgCCKO9gVz0uaBsR eI4a0DI/6BI/dMrJ5UZSURvxgODxNe7N8x5Ts/YdCXVh0GdreMlw7QUgSJIYLPx4orgfGpHvFdPb MalouYJkJakoxDyf4CEEDr5Q8+xBjRKAIDoxKMRgRcyJrDYwqWiTBEkYt29JQRoLby3NwZe/Ve/L f6M0VgCCBGIGPRFIC7aswc4+i8WTC5rvlh0PMBGkLMZjYrClG0/OMuOPaVgAShakW77hf7WhdyBu hQAEUUlEesJLIrJerMGmnLBJJ7LawKSiVQtS4UlETuElBz24dNl+EG9ZWicAPr/pDRG6AugJEfAK UFZvzqSi9RWkQpKK3hTA8OsPtSs9eGsFIMiH+vq7AqW6hCk9DJOKNkmQxGKZ6e0bC5UK/JsPNb+3 1+GEAPh8uD/ZgicEF9NOLmDRQJhUtOmCJLRlaco7FUD/wW+fG+Y/25qBUwLg8+H+ZEsEhCD3yQWk Go9un/EGy6Si5QpSajzgaAUYXnbA8H2cFACf34oKQcxJVO01+0wqWq4gaUXgSAD9h7/drvG9CU4L gM9HfCEQc48AYFLRMsqshyDNROBIAP3ew+4Zvg8FIMBv9ydbEBgIzO82pDuZASYVLb03L6HMwD5P BUTv0YebtWinDCgAGu7dn3QQmTWIO7mAxRNWe9JmNlgmFS1BkMZCoPf4w6vDMs6bJkIBSODe/UlX eNNA63mMJ/S5lN5Ob7BMKlqQIPnTeYMnrqzeKfGUaRwUgBQ+uj/ZANBfAa4AZr2dictrZrBMKlqA IN0C0Hv6yuqogtOlcVAADPno/mRnBRgC2K7K/fY+V208ILSfOINNKrM+gnQKoPvd3uph+WdHc6EA ZORj+5O+AK4CZi5vJuNRGxeNhElFMx7T/Wd7q/1qzohmQwHIwcf2JzvC9wZEwcajXjCpaK72HQmg t9dbPbZxXjQRCsASfHx/0hdCeQPIbzyzbUgzWCYVjTmm4xWg339kdWDpVGgsFIAl+fj+ZEcIHAiV yJRJRSsXpBMBdK89wl4/DxSAAvj4/mRDCAyFykzEpKLhuujKKkiQ9p97hGP9ZaAAFMj2tUlPAK8X 5gqrDUwquujyC4HdFx5hhH9ZKAAFs31tsiO8pJCbOoMszv12NqnokQB2X3yUC3qKgAJQAjvXJhsA DoW/ZgDxxgMwqWgGQXrjlUdXe7Z/3zZBASiRnWuT4QpwEVg8oUPbkNd4nEkqOhZA7wePcg1/0VAA SuYTgbgAkGIkScajNjiYVHQsgM6rjzHKXwYry++CJPHB3tpAApekd+NHyMjDx7/nfXB78Dn4fmgb 5Oy9uP1LubjP2M8m1C+0TVsX4G5CO6J1uZvQPvXviQS2aPzlQQGogA/21oYS6PgikNt4EDag4Pez GnFUHIoTpPl7aWVq2zd/3ALQee0xBvvKhEOACvnktckOvODgehnxgNQofsZ4wOz7ScMLTZkFDC9u vvnYatf27+UC9AAq5P29tWMAO/Bc2/y9ecpQwMT9Lqw3R04PQoY/G/gcjb9CKAAV8/7e2kgCHfgi AL3x6MQBCBvswhh62XhAoYKULR6gni/96Ds0/iqhAFjg/b21O74IpPW8pmPw4Pbc8YCkzxoI0pLx gEvXv8NpvqqhAFjCFwGpRGCxN483HmBxmwxsTTXYBIPMI0jB/eYUmUtv0fitQAGwSFQEYo0ns8Em xwN0Y/DQ9zMK0pIexKUbNH5rUAAs8/7e2h0AuxIYZwqwIc1g4+MBs/0YBgXj6lKAB/HGO4/T+G1C AagBt/fWRgA6kIsiAMQaz+w51WCTPAiDbcUEKBc8iJvvPs51/bahANSE23trxzIgAsDy8QDpxwOQ YLAFxwMMBenmTx5ntL8OUABqhBKB3rLxAF1QMGqwoR66wHiAts7hbScS6Nk+1sSDKwFryH3XJl0h 8J7pFXcNSip6KoCd957g8t66QA+ghtzeWxtC4qZpPMDgoprZcCApig+DbXkClGrbWAK7NP56QQGo Kf+xt9aFxFHWMXjws5kNNvPwIpMg9W4+wav66gYFoMZIYBcSp7E9dNKcfEnxgJyCtP9XT3C6r44w BlBz7rs22REicAWh2r5MEpGKk4re+tmTq7u2jyPRQw+g5tzeWzuWEr1i1wdUlkTkBEDX9jEk8VAA GkAwKAho3PcC4wFGAUCzeMAYQPdnTzLoV2coAA1BrQ84ybJIKE88QBcUhGafBvGA3t8+yaBf3WEM oEHc591z4IMGJBW9+fdPcaVfE6AH0CDUSsFHiogHzD9XeDyAK/0aBD2ABvKpa5NDCJwvLodfOJ8g NN81zFE4FkDn50/R9W8K9AAaiAS6yHjRUHIA0DyJCJAYD+jT+JsFBaCB3PbyCvZrllT01i+eWh3Y PjYkGxSAhnLbu+HIyWJvjvTeXL0oMKnoWHK+v5FQAJpNN8/y3ORFQtnXDEige/A05/ubCAWgwahZ gTf0vXl8PABY3JYnHqC+d+vW06sHto8FyQcFoOFIoC+BcWJvnuDS54kHBPY5Bl3/RkMBaDgqqWgv q/sefD9rPGC2H4nuP9D1bzRcB9AS7rs2GQlgU7cGAIhfHwAkreoTSesAjn75zGrHdrvJctADaAkS 6Mav9ssXD0hIKkrXvyVQAFrC+3trhxIqg9CS8QCDi4YGv3xmdWS7zWR5KADtog9kn84Lvm8QFBxL YGC7oaQYKAAtwvcCAMT05vr1ARmTig7+8RkG/toCBaB99JN78+RFQsHPajwI9v4tgwLQMt7fWzuE igWkxQNMlvxG4gGDf/oue/82QQFoIVLFAtTrXMt7/Q2B77D3byEUgBbyQXBGADDN4TffBm08YPDP 3127Y7ttpFgoAC1FAsP0Jb/GSUXZ+7cUCkBLOd5bG0rgFEib4jOKBxz8ir1/K6EAtJvBQm9ucAkw Frf1bTeElAMFoMUEhwFGC4L0Q4Gjo++tjWy3hZQDBaDFnOyt3ZHQ3GU4WzxgYLsdpDwoAO1nCJgu +V2IB5z+y/fWDmw3gJQHBaDlnHhTgrNgoI9hPGBou/6kXCgAbjDURveREAD0xGFou+KkXCgADiDl fBhgGg+QwK1/ZfCv9VAAHOA/r66NpMSJ/79ZSjAc2K43KR8KgDsMU27sERwGjP/t2bWh7QqT8qEA OIIM9OgGKcEOsuybNBcKgCP819W1kQRODFOCHdiuL6kGCoBbDIN5AQHtUOD035/l3L8rUAAcIjQM CIz5IwHAg4y7JQ2GAuAQ/+0PA9T/MTf6PLBdT1IdFADHkMBhQgBwfNtLKUYcgQLgHgehcX84HnBg u3KkWigAjvHrq14PH/IA5kOBQ9v1I9VCAXAQGcgaHIkHHNquG6kWCoCbHAIL6wDGJ3tc++8aFAAH kcCxZknwse16keqhALiI9AQgIgKHtqtFqocC4CD/018babIAH9uuF6keCoCjhG4cAgqAq1AAHEUC o+BKwF9fZQDQRSgA7jIK5AE4sl0ZYgcKgKNIYKSeAem9Ju5xj+0KEGuMAqsBR7YrQ+xAD8BRgh4A A4DuQgFwlP/rrwU9gDu260PswCGA4ygROLZdD2IHegAOI+GlCv/fPm/97Sr0ABxGeq7/ybL7Ic2F HoDLyJkIEEehB+AwEhgJufx+SHOhADiM5Py/83AIQIjD0ANwGE4BEnoAbjNiENBtKABuM7JdAWIX CgC5Y7sCxB4UALJhuwLEHgwCus3xaJ/LgF1GSMmVIIS4CocAhDgMBYAQh6EAEOIwFABCHIYCQIjD UAAIcRgKACEOQwEgxGEoAIQ4DAWAEIehABDiMBQAQhzm/wHS6OKA3WyTvAAAACV0RVh0ZGF0ZTpj cmVhdGUAMjAyNC0xMC0yOVQxMDowMjo0NiswMDowMEJ9WiYAAAAldEVYdGRhdGU6bW9kaWZ5ADIw MjQtMTAtMjlUMTA6MDI6NDYrMDA6MDAzIOKaAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDI0LTEw LTI5VDEwOjAyOjQ2KzAwOjAwZDXDRQAAAABJRU5ErkJggg==\\\"/>\\n<script xmlns=\\\"\\\"/></svg>\"\n  },\n  {\n    \"provider\": \"model_volcanic_engine_provider\",\n    \"name\": \"火山引擎\",\n    \"icon\": \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\" standalone=\\\"no\\\"?>\\n<!DOCTYPE svg PUBLIC \\\"-//W3C//DTD SVG 1.1//EN\\\" \\\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\\\">\\n<svg version=\\\"1.1\\\" id=\\\"Layer_1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" x=\\\"0px\\\" y=\\\"0px\\\" width=\\\"100%\\\" height=\\\"100%\\\" viewBox=\\\"0 0 800 800\\\" enable-background=\\\"new 0 0 800 800\\\" xml:space=\\\"preserve\\\">  <image id=\\\"image0\\\" width=\\\"800\\\" height=\\\"800\\\" x=\\\"0\\\" y=\\\"0\\\"\\n    href=\\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAMgCAYAAADbcAZoAAAAAXNSR0IArs4c6QAAIABJREFUeF7s3QecVOW9//HvzOwCy7J0WHqVZpckgppE4j+amJvEa3LTTTeJxiTXVE3sGmPEDoINewEligVUxIaoWJAmvcMCy7LA9j4z538PY0EEdsrp5zOvl6+b6DnP83vevyd6v54WMQzDED8EEEAAAQQQQAABBBBAwH4BI0IAsV+ZGRBAAAEEEEAAAQQQQGCvAAGEjYAAAggggAACCCCAAAKOCRBAHKNmIgQQQAABBBBAAAEEECCAsAcQQAABBBBAAAEEEEDAMQECiGPUTIQAAggggAACCCCAAAIEEPYAAggggAACCCCAAAIIOCZAAHGMmokQQAABBBBAAAEEEECAAMIeQAABBBBAAAEEEEAAAccECCCOUTMRAggggAACCCCAAAIIEEDYAwgggAACCCCAAAIIIOCYAAHEMWomQgABBBBAAAEEEEAAAQIIewABBBBAAAEEEEAAAQQcEyCAOEbNRAgggAACCCCAAAIIIEAAYQ8ggAACCCCAAAIIIICAYwIEEMeomQgBBBBAAAEEEEAAAQQIIOwBBBBAAAEEEEAAAQQQcEyAAOIYNRMhgAACCCCAAAIIIIAAAYQ9gAACCCCAAAIIIIAAAo4JEEAco2YiBBBAAAEEEEAAAQQQIICwBxBAAAEEEEAAAQQQQMAxAQKIY9RMhAACCCCAAAIIIIAAAgQQ9gACCCCAAAIIIIAAAgg4JkAAcYyaiRBAAAEEEEAAAQQQQIAAwh5AAAEEEEAAAQQQQAABxwQIII5RMxECCCCAAAIIIIAAAggQQNgDCCCAAAIIIIAAAggg4JgAAcQxaiZCAAEEEEAAAQQQQAABAgh7AAEEEEAAAQQQQAABBBwTIIA4Rs1ECCCAAAIIIIAAAgggQABhDyCAAAIIIIAAAggggIBjAgQQx6iZCAEEEEAAAQQQQAABBAgg7AEEEEAAAQQQQAABBBBwTIAA4hg1EyGAAAIIIIAAAggggAABhD2AAAIIIIAAAggggAACjgkQQByjZiIEEEAAAQQQQAABBBAggLAHEEAAAQQQQAABBBBAwDEBAohj1EyEAAIIIIAAAggggAACBBD2AAIIIIAAAggggAACCDgmQABxjJqJEEAAAQQQQAABBBBAgADCHkAAAQQQQAABBBBAAAHHBAggjlEzEQIIIIAAAggggAACCBBA2AMIIIAAAggggAACCCDgmAABxDFqJkIAAQQQQAABBBBAAAECCHsAAQQQQAABBBBAAAEEHBMggDhGzUQIIIAAAggggAACCCBAAGEPIIAAAggggAACCCCAgGMCBBDHqJkIAQQQQAABBBBAAAEECCDsAQQQQAABBBBAAAEEEHBMgADiGDUTIYAAAggggAACCCCAAAGEPYAAAggggAACCCCAAAKOCRBAHKNmIgQQQAABBBBAAAEEECCAsAcQQAABBBBAAAEEEEDAMQECiGPUTIQAAggggAACCCCAAAIEEPYAAggggAACCCCAAAIIOCZAAHGMmokQQAABBBBAAAEEEECAAMIeQAABBBBAAAEEEEAAAccECCCOUTMRAggggAACCCCAAAIIEEDYAwgggAACCCCAAAIIIOCYAAHEMWomQgABBBBAAAEEEEAAAQIIewABBBBAAAEEEEAAAQQcEyCAOEbNRAgggAACCCCAAAIIIEAAYQ8ggAACCCCAAAIIIICAYwIEEMeomQgBBBBAAAEEEEAAAQQIIOwBBBAIl4CRlJobpESL1FQjNdRITbVSolmKt6T+bzIutTSm/tj755ukZEKKN39sZZ5vHqeIlN/u4z8fi0mxNlIsX4rmS23aSXkFUiwv9ef2/tFGalsotStK/WGe36ZAisTC1QtWiwACCCAQSgECSCjbzqIRCLCAGTDq9kg1O6WqHVJDZSo4NFSl/nNDdSpQmOHCPNYLv7y2Ul4bqV0nqX0nqaCTlN9eat9RKuwmte8qdSqW8vYJOl6omxoQQAABBBDIQoAAkgUapyCAgMsCibjUWCVVbpcqtkq1u1JXMap3SrXl3gkWVjJFolJBZ6lDN6ltB6moh9Sln9SpT+rPmSGGHwIIIIAAAj4QIID4oEmUiECoBcxbnypKpJ3rpT1bpKptUnN96kqGV65guN0g82pJUbfUFRQzlHQfLHUdKBV0dLsy5kcAAQQQQOBTAgQQNgUCCHhHwHwGo3KrtGujVL5O2lMi1VdKRsI7NfqpkjbtpY69pOLhUo/DpK4DpHYd/LQCakUAAQQQCKAAASSATWVJCPhGwLyVqmyVtGO1VLpCqir1Tem+LdR8vqT7EKn34VLxYVJRsW+XQuEIIIAAAv4UIID4s29UjYA/Bcy3TplXN7Ytk8rWpp7d4OqGu700nyfpMUTqe5TUa5RU2NXdepgdAQQQQCDwAgSQwLeYBSLgokBjrVRXngocWxZJ1TtcLIap0xIwr5D0Gin1P1bq3Ffq0D2t0zgIAQQQQACBdAUIIOlKcRwCCKQnULtH2rFS2rZU2rVBaqpL7zyO8p5AJCJ1HSQNODZ1laTbEMn8c/wQQAABBBDIQYAAkgMepyKAgFK3UO3eIpWvl7YslPZslgwDmiAKtO8s9TlK6nOE1GsEr/4NYo9ZEwIIIOCAAAHEAWSmQCCQArs3SjvWSJsXSFXbCR2BbPIhFtWuY+rKSD/z6sjQ1Bfe+SGAAAIIIJCGAAEkDSQOQQAB80qHkfrw347l0vq3eJ6DTfGxQPsuUu9RUv/jUm/X4jYtdgcCCCCAwCEECCBsDwQQOLRAY420bp5UukratZ4rHeyXQwuYb9EaMFoa+Dmpa3+0EEAAAQQQ+JQAAYRNgQACnxYwPwi4/X1p87vS1mW8Kpc9kp1AN/MB9tHS4DFSu6LsxuAsBBBAAIHACRBAAtdSFoRADgLVZdLa11IPkzdU5TAQpyKwj0Be29TX2Ed9OfVFdm7RYnsggAACoRYggIS6/SweAUnJeOo7HateTH0kkDdYsS3sFCjoLA09URrxJaltoZ0zMTYCCCCAgEcFCCAebQxlIWC7QLxZWvmCtP5Nqb7S9umYAIFPCLQtkvocLo08RerCsyLsDgQQQCBMAgSQMHWbtSJgCuwpkVbMlrYuTV394IeA2wJd+klH/ZfU92huz3K7F8yPAAIIOCBAAHEAmSkQcF0g3iSVrZFWvyztWO16ORSAwAEFzG+LHHFa6g1aPLTOJkEAAQQCK0AACWxrWRgCkszbrDa+nXqwvHIbJAj4Q8AMH0NOlIZ9Xirs5o+aqRIBBBBAIG0BAkjaVByIgI8EzCse5pusljwlNVT7qHBKRWAfAfPtWUNOkIafLHUshgYBBBBAICACBJCANJJlILBXwHymY+VLqSse9RWgIBAMgVi+NGSsNOIUgkgwOsoqEEAg5AIEkJBvAJYfEIGmWmmzecXjSamlMSCLYhkI7CeQly8NGpMKIp16wYMAAggg4FMBAohPG0fZCOwVML/ZsXKOtGYuVzzYEuERMK+IDPti6o+iHuFZNytFAAEEAiJAAAlII1lGyATMW602vC0tm8U3PELWepa7j0B+O2nwGOmIr0jmBw75IYAAAgj4QoAA4os2USQC+wiYXy1f/IRUtQMWBBAwBczX95pfVh/2BalNe0wQQAABBDwuQADxeIMoD4GPBMwPCC5+UtqxEhQEEDiQgHk71uGnpd6cFYlihAACCCDgUQECiEcbQ1kIfCTQ0iDNf1Davowvl7MtEEhHoPtQ6ZhvSsXD0jmaYxBAAAEEHBYggDgMznQIpC2QiEtrX5WWzkx9UJAfAghkJtD/OGn0/0iFXTI7j6MRQAABBGwVIIDYysvgCGQhYL7ZqnSl9M5DPGCeBR+nIPAJAfNjhkd+VRp6ktS2AzgIIIAAAh4QIIB4oAmUgMBHAg1V0oJpUskSUBBAwEqBDt2lsT+WenJblpWsjIUAAghkI0AAyUaNcxCwWsD8eOCmd6QFj0lG0urRGQ8BBPYKRKSBo6Vjz5QKu2KCAAIIIOCSAAHEJXimReAjgfJ10oLpUkUJKAgg4IRAQSfpiNNTr+2NRJyYkTkQQAABBPYRIICwHRBwS6ChQlr9irTypdQXzfkhgICzAt0GSif8TOpY7Oy8zIYAAgiEXIAAEvINwPJdEti2VFo0Q6ouc6kApkUAgb0C0TzpqP+SRoyTzAfW+SGAAAII2C5AALGdmAkQ2EegsVpaOktaNw8WBBDwkkCnPtJJP5c69/VSVdSCAAIIBFKAABLItrIoTwpsX556w1Xtbk+WR1EIhF4gr5105OnSyC+lrozwQwABBBCwRYAAYgsrgyKwj0Bzg7T82dSzHvwQQMD7Al0HSif9Uirq7v1aqRABBBDwoQABxIdNo2QfCezaKL3ziFS5zUdFUyoCCKhtoXTUN6ThXwQDAQQQQMBiAQKIxaAMh8BeAfOtVsufT/2RaAEFAQT8KtB7lDTmx1L7zn5dAXUjgAACnhMggHiuJRTke4GmWunVSdLuzb5fCgtAAAFJ7YqkE34q9T4cDgQQQAABCwQIIBYgMgQCHwmYr9c1b7lqqAYFAQSCJGB+sPDw06RjzgjSqlgLAggg4IoAAcQVdiYNnEC8UXr/OWnlnMAtjQUhgMA+Aj2HSSf8RCrsBgsCCCCAQJYCBJAs4TgNgY8E6nZL8x+Qdq4FBQEEwiDQplD6wtlS8YgwrJY1IoAAApYLEEAsJ2XAUAlsWya9MUWKN4dq2SwWgdALRKLSqC9Lx3xTMv8zPwQQQACBtAUIIGlTcSAC+wiYb7la8pS06mUpGYcGAQTCKtDnqNQtWeZre/khgAACCKQlQABJi4mDENhHoL5SWvS4tPk9WBBAAIHU8yBf+p3UsRgNBBBAAIE0BAggaSBxCAIfCVRul+beJpnPffBDAAEEPhRoUyB99gfSoM9iggACCCDQigABhC2CQLoCWxamXrHbXJ/uGRyHAAJhEojGpJGnSMeeGaZVs1YEEEAgYwECSMZknBBKgVUvSktnSfGmUC6fRSOAQAYC/Y6Wjj9Latchg5M4FAEEEAiPAAEkPL1mpdkImA+bv/uItO6NbM7mHAQQCKtAx16p50IKu4ZVgHUjgAACBxUggLA5EDiYgHmr1Rt3S6UrMUIAAQQyFzAfTje/F9J1YObncgYCCCAQYAECSICby9JyEDAfMn9lolS9M4dBOBUBBEIvEIlIR/xcOpqH00O/FwBAAIGPBAggbAYE9hfYtV56+Vae92BnIICANQLP70rql3+K6OSxEWsGZBQEEEDA3wIEEH/3j+qtFtiySFrwqNRYbfXIjIcAAmEVeG+PoRlbkpoxJapjDieEhHUfsG4EEOAKCHsAgU8JrJkrLXycL5uzNRBAwFqBqhbp+uUJdegg3TU+quOPJYRYK8xoCCDgMwGugPisYZRrk8DiJ6WVcyTzrVf8EEAAASsFkpJuWJ7QniapS2dp+m0xDRts5QyMhQACCPhKgADiq3ZRrOUCZuBY+rS0fLblQzMgAggg8JHA1E1JLd2T+jcc3btKD9wU1REjuBLCFkEAgVAKEEBC2XYWvVfADB8Lpklr5wGCAAII2Cvw1q6kntry8SXW4p7SgzdFNWIoIcReeUZHAAEPChBAPNgUSnJAoKVRem+6tGG+A5MxBQIIhF6gokUa/37iEw69e0rTJkc1qB8hJPQbBAAEwiVAAAlXv1mtKRBvlt6YIm1bhgcCCCDgjIB57eOfSxOqj39yvoF9pftvjmpwf0KIM51gFgQQ8IAAAcQDTaAEBwUaKqW3HpJKVzg4KVMhgAACkqasT2p91affdHHYIOnhiTH16gETAgggEAoBAkgo2swi9wqYz3y8PEEqWw0IAggg4LzAqzuTmr31wK/aGz5EeuiWmIoJIc43hhkRQMBpAQKI0+LM545AS4P00s3SnhJ35mdWBBBAYFO9oTtWmS/lPfBv5FBp2qTY3lf18kMAAQQCLEAACXBzWdoHAmb4eHWSVL4BEgQQQMA9gYakdOXiTz6Ivn81nztauuPamLp1ca9OZkYAAQRsFiCA2AzM8C4LxJukOddLFdtcLoTpEUAg9ALmzVc3rkxoV8OhKY4ZJT1wS0ydO4aeDAAEEAimAAEkmH1lVaaA+bareXfywDm7AQEEvCPw1Nak3tp54OdA9q3ylBOkO8bH1CbfO7VTCQIIIGCRAAHEIkiG8ZhAMi69doe0fbnHCqMcBBAItcCSyqSmbWg9gJhIJ302ojuujaqoMNRkLB4BBIInQAAJXk9ZUTIhvTtVWv8mFggggIC3BHY2STctP/RzIJ+4EnJiRHffEFWUz4R4q5FUgwACuQgQQHLR41zvCZiv2jW/cL7mVe/VRkUIIIBAsyFdtyyh2pb0Lb55akTjL4qqoF3653AkAggg4GEBAoiHm0NpWQgse1ZaOjOLEzkFAQQQcEjg/vVJrTrABwkPNf0Zp0U04cqoQxUyDQIIIGCrAAHEVl4Gd1Rg5Rxp0QxHp2QyBBBAIGOBuTuTev4gHyQ81GA/+25EF/8hqvy8jKfkBAQQQMBLAgQQL3WDWrIX2PiW9NaDqa+d80MAAQS8LLC53tDth/gg4aFq/81ZEf3jd1wJ8XJ/qQ0BBFoVIIC0SsQBnhcoXSHNvV0y33zFDwEEEPC6QDofJDxkCPlRRBecF1WMHOL1VlMfAggcWIAAws7wt0DNLun5ayTza+f8EEAAAT8ImBdqxy9PqLIp+2rPPzuiP55NAslekDMRQMBFAQKIi/hMnaNAU60083KpqT7HgTgdAQQQcFjgia1JvZvGBwkPVda5Z0V0IbdjOdw5pkMAAQsECCAWIDKECwLmFY8XrpeqSl2YnCkRQACBHAXe3pPUk5tyf2jt8j9F9fPv8pGQHNvB6Qgg4KwAAcRZb2azQiDeLL06Sdq51orRGAMBBBBwXqC0UZqwIv0PEh6qwkv+ENXZPySEON9FZkQAgSwFCCBZwnGaiwILn5BWvehiAUyNAAII5ChQl0h9kLDJggxiRo8r/hzVT79DCMmxLZyOAALOCBBAnHFmFqsEVrwgLX7SqtEYBwEEEHBHICnpttUJba2zbv7L/hjVL75HCLFOlJEQQMAmAQKITbAMa4PA9hXSG1OklkYbBmdIBBBAwGGBp7cmNT/HB9H3LTkWkW66Iirzq+n8EEAAAQ8LEEA83BxK20egdrc053qpoQoWBBBAIBgC7+0x9J9N5rUQ634dO0jXXBjV179MCLFOlZEQQMBiAQKIxaAMZ4NAvEmafZ1Utd2GwRkSAQQQcElgU72hO7L8IvqhSm7TRrrr2qjGnUAIcam1TIsAAocWIICwQzwuYEjzH5Q2vuXxOikPAQQQyFAg/n9v4b18cUKJ3N/G+6mZu3WRrr4gqtPHEUIybAuHI4CA/QIEEPuNmSEXgRVzpMUzchmBcxFAAAFvCpi54461CW2usae+wgLpjmuj+sLxhBB7hBkVAQSyFCCAZAnHaQ4IlK+XXrpFSsYdmIwpEEAAARcEnt+W1NwyGy6BfLCWrl2ku6+LavSRhBAX2suUCCBwYAECCDvDmwJ1FdIL10kNld6sj6oQQAABKwTe2WVoxhZrH0Tfv67C9tK0yVEdPZIQYkXPGAMBBHIWIIDkTMgA1gsYqSsfZWusH5oREUAAAS8JbG4wdPtKewOIud5ePaSHJ0Z12CBCiJf6Ty0IhFSAABLSxnt62eYzH+azH/wQQACBoAvUJ6UbliVU78CtpuaD6Q9NiOrwYYSQoO8r1oeAxwUIIB5vUOjK2/ye9MbdoVs2C0YAgZAKmE9/3L4mqS219j0Hsi9t317S43fG1LtnSMFZNgIIeEGAAOKFLlBDSqBuj/Tcv6TmekQQQACB8AjMKEnqnXJnAoip2qdYmn57TP16h8eYlSKAgKcECCCeakeIizGM1EPnuzeFGIGlI4BAKAXm7kzq+a3OBRAT2QwfT98Tk3lbFj8EEEDAYQECiMPgTHcQgaUzpWXPwoMAAgiET2BzvaHbbfgiemuSI4dK990YU+/i1o7kryOAAAKWChBALOVksKwEzO99zL2NW6+ywuMkBBDwvUBzUrpiSUJJZy+C7HU7fLj0xJ0xFbTzPSMLQAAB/wgQQPzTq2BW2lSXuvWqZmcw18eqEEAAgdYEzNwxcXVCpXWtHWnPXx9znHTvjTGZX07nhwACCDggQABxAJkpDiGw8D/SqpchQgABBMItMHVjUksrXLgE8gH76V+KaNLVUcWi4e4Dq0cAAUcECCCOMDPJAQU2vCW99QA4CCCAAAIvliX10jb3AojZgW+fHtH1l0YV5TMhbEgEELBXgABiry+jH0ygqVaaeZXUVIMRAggggMDqWkP3rbH/i+itSZ/3s4j+dg6XQVpz4q8jgEBOAgSQnPg4OWuB+fdLG9/O+nRORAABBAIlUNkiXb88oYT7GUTn/Diiv59HCAnUBmMxCHhLgADirX6Eo5p186R3poZjrawSAQQQSEcgbkg3r0hod1M6R9t7TCQi/eEXEf3pV4QQe6UZHYHQChBAQtt6lxZeXynNvlZqqHKpAKZFAAEEPCrw4MakVrj4IPr+LBf9Pqpf/4gHQjy6XSgLAT8LEED83D0/1s6tV37sGjUjgIATArO2JfV6mbsPou+7zjb50vUXR3XGVwghTvSfORAIkQABJETNdn2pJYukeXe5XgYFIIAAAp4UeGdPUjM2eSeAmEjm7VhTrovqy58nhHhy01AUAv4UIID4s2/+q7qhQnp+PLde+a9zVIwAAk4JlDVJNy9PODVd2vMUFUq3XRPVF44nhKSNxoEIIHAoAQII+8MZgfemS6tfcWYuZkEAAQT8KGC+AOvKpQk1xb1XfV6e9MjEqMYcRwjxXneoCAHfCRBAfNcyHxa8c4304s0+LJySEUAAAQcFzJuvbl+b1JYab92G9SFB9y7SvTdGdfQoQoiD24KpEAiiAAEkiF311JoM6anLpLpdnqqKYhBAAAFPCkzblNSSPd4MICZY967S/TdFdeQIQognNxBFIeAPAQKIP/rk3yqXPSctfca/9VM5Aggg4KTAS2VJvbjNuwHEtOjdU5o2OapB/QghTu4N5kIgQAIEkAA103NLaaiWnrpYSnrwfmbPYVEQAgggIGl1raH71njgc+itdGNwf+nBCTH1703bEEAAgYwFCCAZk3FC2gLz7pRKFqd9OAcigAACoReobJGuX5ZQwtsXQfb2aVB/adqk2N4rIvwQQACBDAQIIBlgcWgGAqUrpVcmZnAChyKAAAIIKG6kAkhViz8wBg+QHrgppgF9/VEvVSKAgCcECCCeaEPAijBvuZo9XqrYGrCFsRwEEEDAZgHzwsfE1QmV1tk8kYXDDxkoPXFHTF06WzgoQyGAQJAFCCBB7q5ba1v+vLTkabdmZ14EEEDA3wL/KUnqvXIf3IO1D/NRI6W7r4+puLu/7akeAQQcESCAOMIcokkaqqTZ10r1lSFaNEtFAAEELBSYV57UsyX+CiDm8kcOlR64hRBi4VZgKASCKkAACWpn3VrXWw9IG95ya3bmRQABBPwvsKne0B2rvP8mrANJmyHkkVtj6tbF/31gBQggYJsAAcQ22hAOvGuj9NLNUsInD0+GsEUsGQEEfCDQmJSuWJzwQaUHLvGYUeYX0wkhvm0ghSNgvwABxH7jkMxgSK/fLW1ZGJL1skwEEEDAJgHz5qt/Lk2o3sffUBo+RHry7pgKC2xCYlgEEPCzAAHEz93zUu1bFkmvT5Hkv9uWvcRILQgggMDev41OXpvQ1hp/Y4w5VrrtGq6E+LuLVI+ALQIEEFtYQzao+drdF2+SzFuw+CGAAAII5C4woySpd3z2JqwDrfrYI6Tpt8fUJj93E0ZAAIHACBBAAtNKFxeyeYH0xj0uFsDUCCCAQMAE3iw39EyJPx9E378VY46T7rw2ps4dA9YkloMAAtkKEECyleO8lEBzg/T8v6Ta3YgggAACCFglsKnR0B0rghFATJMjR0oz7uJKiFX7g3EQ8LkAAcTnDXS9/BVzpMUzXC+DAhBAAIFACVTFpX8v9e+bsA7UjOOPle4az5WQQG1UFoNAdgIEkOzcOMsUaKqVnvsXHx1kNyCAAAJWCyQM6ZLFCRkBe7GHeTvWQxO4EmL1fmE8BHwmQADxWcM8Ve7y2dKSpzxVEsUggAACgRAwc8fE1QmV1gViOZ9YxNfGRTT+kqiKCoO3NlaEAAJpCRBA0mLioE8JNFRLz10tNfr8NZG0FgEEEPCqwOMlSS0IwJuwDuR7yokRTbiKEOLVvUddCNgsQACxGTiww7//rPT+zMAuj4UhgAACrgu8Vp7UcyUBuwdrH9WTx0Y06WpCiOsbjQIQcF6AAOK8uf9njLdIT18iNVb7fy2sAAEEEPCqwJpaQ/euCc6bsA7kPG5sRLcSQry6BakLAbsECCB2yQZ53GXPSku5+hHkFrM2BBDwgEBNQvrXkmC9CetArObtWHffEFU04gF0SkAAAScECCBOKAdpjkSL9NTFPPsRpJ6yFgQQ8KaAefPVZUsSagl+BtF/nRLRDZdGVdDOm72gKgQQsFSAAGIpZwgGW/GCtPjJECyUJSKAAAIuC5gB5IblCe1ucrkQh6b/5mkRTbwy6tBsTIMAAi4KEEBcxPfd1EZSmnmFVFPuu9IpGAEEEPClwAMbklpZGdwH0fdvyunjIrrpcq6E+HKzUjQC6QsQQNK34si1c6V3H8UBAQQQQMApgRfKknplW3gCiOl66hcjmjKeKyFO7THmQcAFAQKIC+i+nNL8Gq/57Ed9hS/Lp2gEEEDAlwKLKww9ujHYb8I6UGO+eWpE4y/iSogvNy1FI9C6AAGkdSOOMAU2vye9cTcWCCCAAAJOCmxvlCauCMFT6AdA5ZkQJ3cacyHgqAABxFFuH082+zpp90YfL4DSEUAAAR8K1CelqxaHM4CY7frWVyP65wVRFRb4sHmUjAACBxMggLA3WhfYsVp6ecL/vQ8yXLchtw40dlTGAAAgAElEQVTDEQgggIDNAubfdi9dklA8vBlEPzozoiv+HFV+ns3YDI8AAk4JEECckvbzPK/dJm19388roHYEEEDAnwJmAJm8OqGtdf6s36qqv316RNdeRAixypNxEHBZgADicgM8P31VqfT8NVIi7vlSKRABBBAIpMATJUm9W84laK6EBHJ7s6hwChBAwtn39Fe94FFpzdz0j+dIBBBAAAFrBV7dmdTsrQQQU/WsMyO6+gJe0WvtDmM0BBwXIIA4Tu6jCVsapGculxprfFQ0pSKAAAIBE1hRbejBdeF7Fe/B2vj9MyK6+q9R5fFMSMB2OssJkQABJETNznipa16RFkzP+DROQAABBBCwUGBHo3RLSF/FeyDGSET65fciuuR8roRYuM0YCgEnBQggTmr7ba5ZV0nmMyD8EEAAAQTcE6hqka5bllCCu7A+0YQffyuiK/4SVYwc4t7mZGYEshMggGTnFvyzylZLL90S/HWyQgQQQMDrAs2GdP2yhGpavF6ps/WZV0L+9xdR/fFXEWcnZjYEEMhVgACSq2BQz3/zXmnTu0FdHetCAAEE/CNgXvi4bW1CJTyPd8Cm/elXUf3hFxGZgYQfAgj4QoAA4os2OVxk7S7JvP0qwb9tc1ie6RBAAIEDCzxektQCXsV70O3xj99H9ZsfkUD43w8CPhEggPikUY6WuXSmtOxZR6dkMgQQQACBQwi8Vp7UcyU8BHKoTfL7n0f0l9/wQAj/Q0LABwIEEB80ydESDUOaeaVUU+botEyGAAIIIHAIgcUVhh7dyKt4W9skfz8vqnN+zJWQ1pz46wi4LEAAcbkBnpt+51rpxZs8VxYFIYAAAqEW2FBr6K41BJB0NsHF/xvRr37AlZB0rDgGAZcECCAuwXt22rcfkta/6dnyKAwBBBAIpUBVQvr3kkQo157pos3rH1f/LaoffYsrIZnacTwCDgkQQByC9sU09ZXSM5fx8LkvmkWRCCAQKgHz2sflSxNqiYdq2Vkv1nwj1mV/jOrn3yWEZI3IiQjYJ0AAsc/WfyNvekd68z7/1U3FCCCAQNAFzMfPJ65OqLQu6Cu1bn2xiDThqqi+/mVCiHWqjISAJQIEEEsYAzLInBul8nUBWQzLQAABBAImMH1LUgt38SasTNpqfiX9kvO5EpKJGcci4IAAAcQBZF9M0VApPXmRZL4Fix8CCCCAgPcEXtyR1Evb+Zt0pp1p00aafHVUp36BKyGZ2nE8AjYJEEBsgvXdsMtnS0ue8l3ZFIwAAgiERmD+rqSe3kIAyabhnTpI114c1enjCCHZ+HEOAhYLEEAsBvXtcM9cLtXs9G35FI4AAggEXmB5taGH1vEq3mwbXVgg3XIlV0Ky9eM8BCwUIIBYiOnboSq3Ss/+y7flUzgCCCAQCoHtjdLEFbyKN5dmd2gv3XVdVCd+hishuThyLgI5ChBAcgQMxOmLnpBWvhiIpbAIBBBAILACdQnpuuUJNfEq3px63L5AutsMIZ8lhOQEyckIZC9AAMneLiBnGtKTF0v1FQFZD8tAAAEEAiqQMKTrVyRU2RTQBTq4rK5dpPtujOqYUYQQB9mZCoEPBQggYd8Le0qk568JuwLrRwABBPwhMGV9UuureBDdim51LJIeuCmq444khFjhyRgIZCBAAMkAK5CHLnhMWvNqIJfGohBAAIHACTy+JakFfAvEsr726iE9NCGqYYMJIZahMhACrQsQQFo3Cu4R5jc/ZvxDaqwK7hpZGQIIIBAkgWe3JzVvB1dArOxpty7SE3fGNKi/laMyFgIIHEKAABLm7bFns/T8tWEWYO0IIICAvwRe35XULL4FYnnT+vaS7rkhqpFDuRJiOS4DIvBpAQJImHfFe9Ol1a+EWYC1I4AAAv4SWFFt6EG+BWJL07p3lZ66O6Z+vW0ZnkERQOBjAQJImHfDc/+SKraGWYC1I4AAAv4SKG+Rbnyfb4HY1bX+fVLPhAzqx5UQu4wZFwFJBJCwboOacmnmFZLBR3XDugVYNwII+FAgnpQuWUwAsbN1XAmxU5exEdgrQAAJ60ZY8YK0+Mmwrp51I4AAAv4UMB8/H29+C6TRn/X7perhQ6QHbo6pd0+/VEydCPhKgADiq3ZZWOyLt0g7V1s4IEMhgAACCDgicNfapDbU8CYsu7GLe0pP3MEzIXY7M34oBQggYWx7S6M04+9SnK/phrH9rBkBBHwu8HhJUgvKCSBOtNG8EjJtckzdOjsxG3MgEBoBAkhoWr3PQrcslF6fEsaVs2YEEEDA/wIv70hqznYCiFOdNL8PMnViTH16OTUj8yAQeAECSOBbfIAFvvOwtO6NMK6cNSOAAAL+F5i/K6mn+RaIo40cdZh07w0x9S52dFomQyCoAgSQoHb2YOsy33r19GVS3e6wrZz1IoAAAsEQWFppaOoGXmHodDcPHy5NmxRTpyKnZ2Y+BAInQAAJXEtbWRBfPw9bx1kvAggETaCk0dDkFQQQN/p6zKiI7hwfVa8ebszOnAgERoAAEphWprmQ92dK7z+b5sEchgACCCDgOYGqFum65QklyCCu9GbsaOmua2PqyJUQV/yZNBACBJBAtDGDRbw8QdqxKoMTOBQBBBBAwFMCzYY0/v2E6uKeKitUxXzumIjuujaqLrwdK1R9Z7GWCRBALKP0wUCJFumJv0st9T4olhIRQAABBA4oYL7/asLqhHbUAeSmwNjPSA/dElN+nptVMDcCvhQggPiybVkWXbpSemVilidzGgIIIICAZwQe3JjUigpexet2Q8adIE3+V0yFBW5XwvwI+EqAAOKrduVY7NKZ0jKe/8hRkdMRQAAB9wX4GKH7PfiwgnEnRDTluihXQrzTEirxvgABxPs9sq7Cl26RylZbNx4jIYAAAgi4I/DstqTmlXEFxB39T8968lhpynUxtcn3SkXUgYCnBQggnm6PhcW1NEozLpTizRYOylAIIIAAAq4IzNuV1LN8jNAV+4NN+t+nRXTj5VHFop4qi2IQ8KIAAcSLXbGjpu3LpFcn2zEyYyKAAAIIOC3wfpWhR9bzHl6n3Vub7xv/L6Kbr4wqL9bakfx1BEItQAAJS/vNb3+Y3wDhhwACCCDgf4GSRmnyioT/FxLAFXzvGxH9+x9RRSMBXBxLQsAaAQKINY7eH+XlidKOld6vkwoRQAABBFoXqE1IVy8hgLQu5c4RP/9ORH8+J6qiQnfmZ1YEPC5AAPF4gywpLxmXHv+r1NJkyXAMggACCCDgsoB589VlSxKKk0Fc7sTBp//BN1NXQvghgMCnBAggYdgUuzZJL4wPw0pZIwIIIBAOAfP9VzesSmg3H5b1dMPPOSui88+OqqCdp8ukOAScFiCAOC3uxnwb35bm3+/GzMyJAAIIIGCXwJS1Sa2v4VW8dvlaNe5ZZ0Z09QVcCbHKk3ECIUAACUQbW1nEO49I614Pw0pZIwIIIBAegYc3JrWMr6H7ouHn/TSiv51LCPFFsyjSCQECiBPKbs5hGNKsK6XqMjerYG4EEEAAAasFpm9OauFuroBY7WrXeD//bkSX/4kQYpcv4/pKgADiq3ZlUWx9pfTUxZLB6+Kz0OMUBBBAwLsCc3Ym9fJWAoh3O/Tpyn79o4gu+j0hxE89o1ZbBAggtrB6aNDy9dKcGzxUEKUggAACCFgi8Ha5oSdL+LdLlmA6OMjvfhbRX88hhDhIzlTeEyCAeK8n1la0dp707lRrx2Q0BBBAAAH3BVbVGrp/DQHE/U5kVkEkIl1wbkTn/oQQkpkcRwdIgAASoGYecCmv3yVtWRT0VbI+BBBAIHwC2xuliXwN3beN/99fRPSnXxNCfNtACs9FgACSi54fzn36Mqm23A+VUiMCCCCAQCYC1XHpumUJxbkIkgmbZ46NRaW/nhvVuT+OeKYmCkHAIQECiEPQrkwTb5am/1Ey34TFDwEEEEAgWALNhjR+WUJ1LcFaV9hW8/fzojqHEBK2tod9vQSQIO+AqlJp1lVBXiFrQwABBMIrYF74MK+AVDaH1yAIKzevhFzw26h+cxZXQoLQT9aQlgABJC0mnx60Yb701oM+LZ6yEUAAAQQOKWBe3J6wOqEddUD5XcCMHv+8ICrzq+n8EAiBAAEkyE1+d5q09rUgr5C1IYAAAuEVMAPIHauT2lzHfbZB2AX5+dI//xLV988ghAShn6zh0P/+JGIYPCEQ1E0y53qpfENQV8e6EEAAAQSmbUlqyS4CSFB2AldCgtJJ1tGKAFdAgrpFEi3SkxdJTbVBXSHrQgABBBB4ZmtSb+4kgARpJxS0la65MKozT+dKSJD6ylo+IUAACeqGqCmXZl7OG7CC2l/WhQACCJgCc3cm9fxWAkjQdoP5scIJV0b1zVMJIUHrLevZK0AACepG2LlOevHGoK6OdSGAAAIImALvVSX1n/UEkCDuhvbtpGsvIoQEsbesiQAS2D2wfr70Nm/ACmx/WRgCCCBgCqytNnTPOr5EGNTdUFgg3XBpVKd/iSshQe1xSNfFFZCgNv69x6TVrwZ1dawLAQQQQMAUKI9LNy5NgBFgAfM7IXdfH9WXTiSEBLjNYVsaASSoHX95orRjZVBXx7oQQAABBEyB2oR09RICSNB3Q1GhdN3FXAkJep9DtD4CSFCb/dQlUt3uoK6OdSGAAAIImALm0x8XLUqIF+oHfz+YV0IemhDViZ/lSkjwux34FRJAgthiIylN/V0QV8aaEEAAAQT2FTADyJXvJ9TYgksYBDp3lO68NqoxxxFCwtDvAK+RABLE5tZXSk/+I4grY00IIIAAAvsHkGvfT6iKABKajdGmjfTQLYSQ0DQ8mAslgASxr7yCN4hdZU0IIJCtQCQqmX/E8iRFpKj532MfjxbNk8zvLhzoZ37U9cOfkZCSH7xw6sM/n4xnW5U155lXQCauTqi0zprxGMUfAuaVEPM7ISeP5UqIPzpGlfsJEECCuCU2zJfe4hW8QWwta0IgtAJti6S2hVK7Iim/nRTLl/LaSPkFUpv2Un57qU2BlNf24/9rHmP+OTNs7A0d0Y/Dh/mfP/xFY6m/dqCfGTD2fb4i+UEgSXwQPMwgYh4Tb5aaG6R4U+o/tzRILfVSc5PUXCe1NEqJZsk8Pt4iNdVKzfVSY7Vk3jaby++ejUmtreBbILkY+vHcwvbStMlRHT2SEOLH/oW8ZgJIEDfAkqel5c8HcWWsCQEEgiJgXnEwg4P5R/suqT/MYGH+94LOUkFHqX1XqaCT1K7DwQOC3z3M8NFUlwoiDdWpYFJfkfpzZmAx/3tdhdRQmQoyZrjZ/4HzaRuTWkIA8ftWyKr+7l2kyddENeZYQkhWgJzklgABxC15O+d9425p83t2zsDYCCCAQHoCHbpJnXpLRb1SocK8ilHYTerQ9YOrFu3TGyfsR5lBJd4oNdZL9Xuk2t1SS53UVC89tKxBM5Y3KrqrvZqqzPvM+IVJgCshYep2YNZKAAlMK/dZyHP/kiq2BnFlrAkBBLwmYN661L6z1KG71LlvKmy07ZD67+Z/Nm9v4mevwP3Vm3Vb5Ya9kxjVbZQsbadEXb6SFW2UKClUfEc7NW9tr+SetkrG+Tfl9nbDndG7d5UemRjViKH0150OMGuGAgSQDMF8cfiMv0sNVb4olSIRQMBHAuYzF537Sd0GpsJG1/6psGE+n7H3AW9+rgg8VVuqa/asOuTcRjKiaF2+4rvbKF6dr0hZgZo3FKlxa4GMzR0UbzzIQzCurIhJsxEo7indc11UR44ghGTjxzmOChBAHOV2aLLpf5RamhyajGkQQCBwAuZVDfPZi859pG6DpW6DpK79UmHjYA9rBw7BRwua17Bbfy1fmlvFtflK7Ginlk0dFN9UpPjGQjWXFihRS7LMDdbZs7t2kaZNimrEEEKIs/LMlqEAASRDMM8fbr5h5dH/9XyZFIgAAh4SMF9D2/MwqcdQqfuQ1O1T5m1V5luk+HlfYFlztc7eYf2Df5GmmBLlbdVS0UbJDR0VX9NRLRsL1bSrrfdRQlwhV0JC3Hz/LJ0A4p9epVdpVak066r0juUoBBAIn4D59qnCrqmrGr0Pl3ocJpkPinNlw797oaSlQd8pfcu5BTTmKVFaoJZ1RWpZ01FNa4oUL28ng+dLnOtBKzP17CZNvyOqQf24EuKZplDIvgIEkKDthx2rpJcnBG1VrAcBBLIVMF9ta77itnh46g8zdJjfyuAXHIFdiWZ9Z/tbajC/lOjWLxFRcksHNa8tUvOKzmopa6v41kIlm3i2xK2W9Oud+mL64AGEELd6wLwHFSCABG1zbHxbmn9/0FbFehBAIBOB7oOlrgOlXiNSVzrM5zn4BVeg3kjoW9vmq/LDryR6Zan1eUpsKlLj+53VvL6DkqUFai5r55XqQlFH72Jp6q1RDe5PCAlFw/2zSAKIf3qVXqUrZkuLn0rvWI5CAIFgCJjPa3QZIPU9UhowOvUxP37hEYgbxt5bsErND4V4/GeUtlfzqo5qWtJVLdvbpa6ScOuWrV3r30d68GauhNiKzOCZChBAMhXz+vELHpPWvOr1KqkPAQRyETCf4+g+NHV1o/8xUtcBPDCei6ffzzUkfXf72yqJ1/tuKUZNvuKrO6rpve5qLilQy4YiAokNXTRDyLTJMfXrZcPgDIlA5gIEkMzNvH0GX0H3dn+oDoFsBdoVpd5S1WOINGiMZP53fgiYAmYA+emOd7Wmudb/IA15alnVUc0rO+99yL1pXZGSfKPEkr4OHiA9eEtM/XtbMhyDIJCLAAEkFz0vnvvizdLONV6sjJoQQCBTAfNh8cHHSz2HScUjCB2Z+oXleDOAnFO2UEuagvcFWqPGDCSd1bK+SE3msyQbOshI8DxDtnt7UH/p4YlcCcnWj/MsEyCAWEbpkYFmj5d2b/JIMZSBAAIZC5ivyO17lNRrVOr/mrdb8UPgUAJmAPlL+VK90bA7+FC726l5c3s1v99FTYu6qmVHO5lfeeeXvsCAvtJjt8XUu2f653AkAhYLEEAsBnV9uFn/lKq2u14GBSCAQAYC5gf/hpyYutphfn2c1+RmgMehewX+uXulZtbtCJeGIRnbC9W8pb0a3+ip5uWdFa+Lhcsgy9UeNkh6ZGJMxT2yHIDTEMhNgACSm5/3zn7mMqmm3Ht1URECCHxSwPw+h3mVo/9xUr+jCB3sj9wEJlas18M1W3IbxOdnG40xxdd23PtxxIZ3u6llUyFXRw7R06EDpcfviKlLZ583nvL9KEAA8WPXDlXzExdKjdVBWxXrQSA4Auabq0aewjMdwemoN1ZyX/Vm3V65wRvFeKSK5PZCtWwsVOObPdS0pIsSzXwUcf/WjBwqPXBLTMXdPdI0ygiLAAEkaJ2e/meppSFoq2I9CPhboLCbNOQEadBnpSLuu/Z3Mz1a/fSarbqhYq1Hq/NAWcmI4muL1PBGTzUt6KaWXW09UJQ3SuBKiDf6ELIqCCBBa/ijf5AS8aCtivUg4D8B82OA5mtzj/iK1H2I/+qnYn8JvFC/U5fuWu6vol2sNrGzQE0Luqrx3e5qWVOkZEu4r46YV0Luvj6mfryi18VdGaqpCSBBa/cjvw3ailgPAv4S6NBDGjJWOuzzvDbXX53zd7XzG3brj+VL/b0Il6o3P4bY9E53NczrqeY1HUP7IcQjR0iP3R5TYYFLjWDaMAkQQILWbQJI0DrKevwgYL7FqniYNHyc1OdIP1RMjUETWNFcrV/seC9oy3J+PfV5al7YTXXzeiq+roPiNfnO1+DijMeMkib/iyshLrYgLFMTQILWaQJI0DrKerwskN9eOuxEadgXpQ48xOnlVgW+tm3xRn17+/zAr9PRBTbH9oaRhje7q3F1RyUr2jg6vVuTmSHk3htj6tbFrQqYNwQCBJCgNZkAErSOsh7PCUSk7oOlQcdLw77AhwI915+QFlSdjOu0rfNCunoHlt0SVcv7XVT/Tnc1L+iqeHWwr4wQQhzYU+GeggAStP4TQILWUdbjGYGI1P8YaeiJUvFIKZbnmcooBAGZX0M/YcsrSDggYNTnqWVJFzW8011N73YL7Ot9CSEObKbwTkEACVrvCSBB6yjrcVvA/GDg4DGp1+h2HeB2NcyPwIEFzABycslcNRtJiBwUMOryFF/TUfUv9lbD0i4ymoL1Nq3hQ6RZ98fUJtgXfBzcMUz1gQABJGhbgQAStI6yHrcEIlFp1JdT4aMTr6Z0qw3Mm6aAGUBOK5mnGoP3sKdJZvlhxu62anyrx94PHzauLbJ8fLcGHHOsdOf4mDp3dKsC5g2gAAEkaE0lgASto6zHaYG2RdKIk6VhJ0ttC52enfkQyE7ADCBf3/aGdieasxuAsywVSGwpVMvyzqp9vo9aSv3/XtujRkhPTOFKiKWbJNyDEUCC1n8CSNA6ynqcEjBvtTrq69LQsZL5dit+CPhJwAwg39o+X6XxRj+VHfxa4xG1bChS02u9VPdaDyUbY75d8/HHSndxJcS3/fNY4QQQjzUk53IIIDkTMkDIBAo6SyO+lHqjlRlC+CHgRwEzgPxo+zvaEK/zY/nhqDkZUXxZZ9XO7KemZZ19+cHDI0dKM+7iSkg4NqytqySA2MrrwuAEEBfQmdKXAmbYOOYM6bCTpChvtPJlDyn6YwEzgJxTtlBLmqpg8YNATb4a5vRW7Uu9FC/317/54EqIHzaY52skgHi+RRkWSADJEIzDQyfQrkga/kVp+JekNtxqFbr+B3XBZgD5S/lSvdGwO6hLDOy6Eps6qH5WP9W92V1Giz/eosWVkMBuR6cWRgBxStqpeQggTkkzj98EzAfKzS+WH36alNfWb9VTLwKHFjADyCW7luvF+p1Q+VWgPk8Nr/dU/Ws91bK2owyzqR7+feFzEd1waVTFPTxcJKV5VYAA4tXOZFsXASRbOc4LqkAsXxo2Thr1Jcl83oMfAkEV+PeeVXqytjSoywvVupJbOqjumX5qWNhFiRrvfoTjlBMjmnBVVEW8MTBU+9OCxRJALED01BAEEE+1g2JcFMjLl/qPlo74qtSx2MVCmBoBhwQmVKzTIzUlDs3GNI4INMTU+G53Nc4tVuOyTjKMiCPTZjLJyWMjmnQ1ISQTM44VASRom4AAErSOsp5sBPofK406Teo+KJuzOQcBfwpMqdqoKVWb/Fk8VR9awNDe27IaXu2lxre7ee6qyLixEd1KCGEXpy9AAEnfyh9HEkD80SeqtEegS1/piNOlAaPtGZ9REfCywNSaEt1Ssc7LJVKbBQJGdf7eINLwarGat3rnTRrcjmVBc8MzBAEkaL0mgASto6wnHYG2HaRjviEN+pyU5683WqazPI5BIC2BGbXbde2e1Wkdy0EBEEhG1LygayqMvNfNEw+tcyUkAPvKmSUQQJxxdm4WAohz1szkvkB+W2noSdLhX5HM1+vyQyDMAs/Xleny3SvCTBDatcc3d1Dz3GLVzu3p+u1ZY46LaNrkqKLee1wltPvDgwsngHiwKTmVRADJiY+TfSTQe5Q0+n+kTr19VDSlImCjwNyGXbqg/H0bZ2BorwsYVW3U9HZ31T7XRy3b3Ls9iwfTvb5TXK+PAOJ6CywugABiMSjDeU6gQ3fps9+T+hzhudIoCAFXBd5rrNR5Oxe5WgOTe0cgsaqTqh8crMa1HV0paszoiKZN4kqIK/jen5QA4v0eZVYhASQzL472kUBEOupr0qj/x3MePuoapToosKK5Rr/YscDBGZnKDwJx8+1ZL/RW3es9ZSScvS+KKyF+2CGu1EgAcYXdxkkJIDbiMrRrAn2OlD73A6mwi2slMDECnhdY11Kns0rf8XydFOiOgFHZRg3P9VXN832UbIg5VsTY0RE9Ojnq2HxM5AsBAogv2pRBkQSQDLA41PMC7btIx/+Q26083ygK9ITA9nijvrV9vidqoQjvChgNeWp4vo/qX++hlhJnPmH+1XER/fvCqLp09q4LlTkqQABxlNuByQggDiAzhe0C0bzUF8yHnyy1deafj7aviQkQsFugPNGsb2x7w+5pGD8oAvGImt7sqdqZ/dS8yf6/0X7z1IjGXxRVAa9KD8oOymUdBJBc9Lx4LgHEi12hpkwEYt2kL58tdRuYyVkciwACexLN+hoBhI2QqYD5lfUlXVX7VH81Lu+U6dkZHX/6uIhuupwQkhFaMA8mgAStrwSQoHU0POtpSUpLmg1dcE1ExcXhWTcrRcAqgepkXKdtnWfVcIwTQoHEmk6qmTZobxAxDHsAuBJij6vPRiWA+KxhrZZLAGmViAM8KLCq2tAbNYZuvSGiUYc5+5YWD3JQEgJZCdQbCZ1S8lpW53ISAvsKxDcUqX5WX9W/0UNG0vq/J3MlJPT7jQAStC1AAAlaR4O9nrikGZuTWrTb0IMTovrC8db/gy7YgqwOgY8FmoykTi6ZCwkClgnsDSJ7H1jvKSNu7d+fCSGWtcmPAxFA/Ni1Q9VMAAlaR4O7nqWVhmaVJFUTly47P6qff8/af7gFV46VIXBggYQMnbTlVXgQsFwgUVKoupn9VD/P2iBy2hcjums8r+i1vGHeH5AA4v0eZVYhASQzL452XqAxIT1ZktTSCmPvPca/+2lEfz2XfwA53wlmDJqAecv+CVteCdqyWI+HBBKbC1X7bD81WBhEuBLioQY7VwoBxDlrZ2YigDjjzCzZCayqMfTs1qTKG1Lnn/6liG69Kqq8vOzG4ywEEPhYwAwgp5bMU61h3tzIDwH7BKy+IsKVEPt65dGRCSAebUzWZRFAsqbjRBsFkh8867GkwpD5tivzN2yw9PidMXUqsnFihkYgRAIEkBA12yNLNa+I1D01QPXzu8tI5HYbLVdCPNJUZ8oggDjj7NwsBBDnrJkpPYGyJunB9Qntbvz4+OLu0mO3xzSoX3pjcBQCCLQuYAYQ80vopfF9/sfW+mkcgUDOAskdBap5cIjq3+2W01infjGiKTwTkpOhT04mgPikUWmXSQBJm4oDbRYwrzii5dwAACAASURBVHS8UGro9bIPLnl8MF/7AuneG6IaOzq3f1tmc/kMj4DvBAggvmtZ4ApObi1U9YND1LCoS9Zr++q4iP59YVRdOmc9BCd6X4AA4v0eZVYhASQzL462R2B7g/TopoR2fvCsx76zXPDbiH77Ex46t0eeUcMsYAaQs0rf0fqWujAzsHYPCCTWdVT1vUPVuDa7e2z5WKEHmmhvCQQQe32dH50A4rw5M34skDSkpVWGHt+YVPwAX9H9n69FdMOlhA/2DAJ2CJj/k/tJ6bta21Jrx/CMiUDGAi0Luqnm0UFq2lyY8blnfjWiv58XVXGPjE/lBO8LEEC836PMKiSAZObF0dYJ1CWkp0qSWrbH0AGyh4YMkP5zZ0zduKxuHTojIbCPgPm/u7N3vKflzdW4IOAdgWRETS/1VvXT/RQva5dRXaecGNGEq6Iqyjy/ZDQPBzsuQABxnNzmCQkgNgMz/AEFNtRJ0zYkVNNyYKBePaT7b4pq5GE898EWQsAuATOAnLdzkRY2Vto1BeMikL1Ac1QNz/VVzcx+SlTlpz3OyWMjmnQ1ISRtMH8cSADxR5/Sr5IAkr4VR+YuYP4/PLO3GXqjPKn4J581/8Tg1/49ou+fwa1XuYszAgIHFyCAsDv8IGBU56vuPwNV+2qxjMZYWiWPGxvRrYSQtKx8chABxCeNSrtMAkjaVByYo0BFszR9c1Ibaw50w9XHg//42xH986+Ejxy5OR2BVgXM/yVeUP6+XmvY1eqxHICA2wKJsgLVTR+outd6plXKmOMimjY5qigX0tPy8vhBBBCPNyjj8gggGZNxQhYCa2oMPbUlqT1Nhz758GHS1Ekxde6YxSScggACGQkQQDLi4mCPCMQXd1X19IFqSuONWVwJ8UjTci+DAJK7obdGIIB4qx9BrObFsqRe2nboqx7mugsKpBl3RTWK5z6CuA1YkwcFCCAebAolpSeQiKh5fk9VPjxQid2HflCdKyHpkXr8KAKIxxuUcXkEkIzJOCFNgcaENHVTUmuqWg8f5pBX/y2qs77FtfI0eTkMAUsErti9Us/V7bBkLAZBwGkBo6qN6mf1Vc1zfWQ0Hfz5EB5Md7ozls9HALGc1OUBCSAuNyCg0+9okh5Yl1BFK7dcfbj8M06LaMKVPPcR0O3AsjwscNXulZpFAPFwhygtHYFkeTvV3D1M9QsP/kX1MaMjmjaJZ0LS8fTgMQQQDzYlp5IIIDnxcfJ+Aua1jgV7DD2x6RCvuNrvnD7F0jP3xdT94P/cwBkBBGwSIIDYBMuwrgi0LO6qmqmD1bTxwB8CGXucdNNlMfXp5Up5TJq9AAEkeztvnkkA8WZf/FhVU1J6vjSpt8rSu+XKXGMkor3/RmrsaG698mPPqdn/AgQQ//eQFXxSwGiJqn5WP9U+NlDJ+Kf/2TLmOOmhCTG1Sf/TIhC7L0AAcb8H1lZAALHWM6yj7WmRpm5IaGtdZgLn/iSiC3/LrVeZqXE0AtYJ3FCxRtNrtlk3ICMh4BGBxM4C1TwwRI3vdpOx378XO/4Y6ZYruBLikValUwYBJB0lPx1DAPFTt7xZ64Y6Q9PWJ1UTz6y+EUOlx27jlbuZqXE0AtYK3FSxVo/WbLV2UEZDwEMCLe/0UOX9gxUv/+TbsswrIXdeyz+DPNSqQ5VCAPFJo9IukwCSNhUH7icQN6Q3yw09tzX95z0+HKKoUJp+e1SjhnHrFRsLATcFCCBu6jO3UwJGfZ7qn+mnmhn9ZSQ//ufO8cdKd5khpJNTlTBPlgIEkCzhPHsaAcSzrfF0YQlDeqIkqYW70n/eY98F/f7nEf3lN9x65ekmU1woBAggoWgzi/xAIL6lUNW3jVDT+g4fmRwzSrr3xpi68SIUL+8TAoiXu5NNbQSQbNTCfU5lk/To5oQ21WbnMPKwiJ59IKoY+SM7QM5CwEIBAoiFmAzlD4FERA3P9Ff1rL5KVqWeRCeEeL51BBDPtyjDAgkgGYKF/PBtDdLDG9L/vsf+XG3bSDPvi2r4EG69CvlWYvkeESCAeKQRlOG4QGJ7e9XcN1QNi1OXPgghjrcgkwkJIJlo+eFYAogfuuSNGt/eZWjW1qRaMn/k46MFnH92RH88m0sf3ugoVSAgEUDYBaEWMK+GvFasmocGK1GTr+FDpOnmy1F4JsRr24IA4rWO5FoPASRXwXCc/3KZoRe3JZXdEx8poyOGm1c/YoqSP8KxaVilLwTurNqge6o2+6JWikTALoH4jgLVPTxY9W9318ih0gO3xFTc3a7ZGDcLAQJIFmiePoUA4un2uF6c+XHBmduTWrAzl+ghFRZI0++I6ojh3HrlelMpAIF9BKZUbdSUqk2YIICApOaXe6ni7sM0uE9Uj98RU5fOsHhEgADikUZYVgYBxDLKwA1kho9pm5JaVZlb+DBhfvbdiK74E5c+ArdJWJDvBQggvm8hC7BYIF7WTjWTR2hQcyfdfUNM/XpZPAHDZSNAAMlGzcvnEEC83B33atvTLN2zNqHdTbnXMGyw9PQ9MbUvyH0sRkAAAWsFCCDWejJacATq/jNQXd8ZoGkT8tS7Z3DW5dOVEEB82riDlk0ACVpHc1/P6mpDUzcl1ZThl80PNvMjE6M66XPcepV7ZxgBAesFCCDWmzJicAQSmzqo6zMjddf5RRrQNzjr8uFKCCA+bNohSyaABK2jua1nSaWhJ7ck1WhR+Pj26RHdeBm3XuXWFc5GwD4BAoh9towcEIH6PHV8fYDu/OoADerHv0xzqasEEJfgbZuWAGIbre8GfqXM0AvbcnjH7n4r7t5VemlaTJ07+o6CghEIjQABJDStZqE5CvTY1k23HD5CQzq1zXEkTs9CgACSBZqnTyGAeLo9jhX37DZD88qsCx9m4ZeeH9Evv8/VD8eayEQIZCFwc8VaTavZmsWZnIJA+ASOb9dFE3oeG76Fu79iAoj7PbC2AgKItZ5+Gy1uSDNKDC3cZW34OGxQRLMfjiov5jcR6kUgXAJ8iDBc/Wa1uQmYN2BNKf6MjmjLpf3cJDM+mwCSMZnHTyCAeLxBNpZnvmb34Y1Jra3K/TW7+5YZiUivPBrT4AE2Fs/QCCBgiQABxBJGBgmRwE87DtS5nYeEaMWeWCoBxBNtsLAIAoiFmD4aqrJJenhTQlvrrC/6rG9FdPXfuPXKellGRMB6AQKI9aaMGGyBnrG2mtZnjNpHuMTvYKcJIA5iOzIVAcQRZk9NsqdJumNtQtXN1pdV1EF64eGY+hRbPzYjIoCA9QIEEOtNGTHYAuZtWHcUH6ej2/KZdAc7TQBxENuRqQggjjB7ZpKKFumuNQlVWPCBwQMt6tp/RPX9b/KaQs80nEIQaEWAAMIWQSBzgTM79NEFXUdkfiJnZCtAAMlWzqvnEUC82hnr69rWYOjuNUk1JKwf2xxx+GDpybtjKmxvz/iMigAC1gsQQKw3ZcTgC5i3X83u9wXlmw898nNCgADihLKTcxBAnNR2b641NYamb06q1obbrj5c1Z3/juor4/ibsXtdZmYEMhe4avdKzarbkfmJnIFAiAVSt2GN1tFtO4VYwdGlE0Ac5XZgMgKIA8guT7Gs0tCjG5MyX7lr1+8rJ0d057U8eG6XL+MiYJcAAcQuWcYNusB3ivrpz12GBX2ZXlkfAcQrnbCqDgKIVZLeHGdhhaEnNiWVsDF85OdJT98T1eHDufrhzV1AVQgcXIAAwu5AIDuBgXkFerTP2OxO5qxMBQggmYp5/XgCiNc7lH19C3YbemJzUjZmj73Fffv0iG68jKsf2XeKMxFwT+DK3Sv1LLdgudcAZvatQEwRze7/eXWI5Pl2DT4qnADio2alVSoBJC0m3x30ZrmhZ0qs/br5gRC6dJKeeyCm3rx213d7hIIRMP/lxAXl7+u1hl1gIIBAhgLmNf8ruh+u09rzD8AM6bI5nACSjZqXzyGAeLk72dX2+k5Ds7baHz7M6i7536jO/gG3XmXXKc5CwF0BAoi7/szuf4H/7tBHF/I6XicaSQBxQtnJOQggTmrbP5eT4aNvL2nmfTF15VtM9jeWGRCwQYAAYgMqQ4ZKYEheez3SZ0yo1uzSYgkgLsHbNi0BxDZaxwd+tczQ7G3OXPkwF3fp+VH98vtc/XC80UyIgEUCZgD5/c5FWtBYadGIDINAuAS6RPM1ve9YngOxv+0EEPuNnZ2BAOKst12zzd5uaG5ZUobdT5x/sIBhg6XZD8cU49lzu1rKuAjYLmD+7eK8nYu0kABiuzUTBFPA/FdwU3p9Rke06RjMBXpnVQQQ7/TCmkoIINY4ujnKyzsMzdnu3JUPc60TrozqjNO4+uFm35kbgVwFzAByTtlCLWmqynUozkcgtAJ/7TJc3y7qG9r1O7RwAohD0I5NQwBxjNqWieZsN/SKg1c+zEUM6ie9NC2mPN48aEtPGRQBpwTMAPKT0ne1tqXWqSmZB4HACXyzQx/9gwfR7e4rAcRuYafHJ4A4LW7dfG5c+TCrnzI+qlO/yNUP6zrJSAi4I0AAccedWYMl8MWCbhrf4+hgLcp7qyGAeK8nuVVEAMnNz62znXzb1b5rPGyw9NLUmFvLZl4EELBQwAwg39o+X6XxRgtHZSgEwiUwKL+9pvXmTVg2d50AYjOw48MTQBwnz3lCp992tW/B998c1bixXP3IuYkMgIAHBAggHmgCJfheIE8RvT5gnO/X4fEFEEA83qCMyyOAZEzm6gnzdxl6eouzD5x/uODhQyKa8wivvXJ1AzA5AhYKmAHk1JJ5qjXiFo7KUAiES8D8V3JP9jlBxXntwrVwZ1dLAHHW2/7ZCCD2G1s1wzt7DM3Y5E74MNcw9daoTvwsVz+s6ifjIOC2gBlAvlwyT3UEELdbwfw+FjD/qXhH8Wgd3baTj1fh+dIJIJ5vUYYFEkAyBHPp8IUVhv6zMSmHPvPxqVUOHyLNeYRnP1xqP9MiYIuA+feTE7a8YsvYDIpAWATMAHJl98N1avvisCzZjXUSQNxQt3NOAoidutaMvbTC0KObkkq6lT4k3X9TVONO4OqHNR1lFAS8IZCQoZO2vOqNYqgCAR8L/KnLMH23qJ+PV+D50gkgnm9RhgUSQDIEc/jwVdWGpm5Iqtm9O680YkhEzz4YVR4XQBzuPtMhYK9Ak5HUySVz7Z2E0REIgcCvOg3SLzsNDsFKXVsiAcQ1epsmJoDYBGvBsJvqDD24Pql6l58Pve1fUX3tFK5+WNBShkDAUwL1RkKnlLzmqZooBgE/CpzdaZDOJoDY2ToCiJ26boxNAHFDvfU598Slu1YnVNnU+rF2HnHYIOn5h2LK56vndjIzNgKuCFQn4zpt6zxX5mZSBIIkQACxvZsEENuJHZ6AAOIweBrT7WmWJq1KuH7lwyz14j9E9Ksf8urdNNrGIQj4TmBXollf3/aG7+qmYAS8JkAAsb0jBBDbiR2egADiMHgr01U0SXetTaii2f26+vWWZj8UU4dC92uhAgQQsF6gLNGkM7a9af3AjIhAyAQIILY3nABiO7HDExBAHAY/xHRNSenONQltr/dGTX/+TVR/+DnPfnijG1SBgPUCW+MN+p/tb1k/MCMiEDIBAojtDSeA2E7s8AQEEIfBDzKdGT7uW5/UphoX37W7T22dO0ovPBJTcXdv+FAFAghYL7C6uVY/3fGu9QMzIgIhEyCA2N5wAojtxA5PQABxGPwA05nh47HNSa2o8Eb4MEv81Q8juvgPPPvh/u6gAgTsE1jUWKlzdy6ybwJGRiAkAgQQ2xtNALGd2OEJCCAOgx9guue3G5q7w8UPfexXU36+9NLUmAbyTSX3NwcVIGCjwJsNu/Wn8qU2zsDQCIRDgABie58JILYTOzwBAcRh8P2me6HU0Cul3gkfZnlnfiWim6/g6oe7O4PZEbBf4KX6nbpo13L7J2IGBAIuQACxvcEEENuJHZ6AAOIw+D7TvV1u6KmSpLxz41WquP/cEdXnjuHhc/d2BjMj4IzAM3Wlunr3KmcmYxYEAixAALG9uQQQ24kdnoAA4jD4B9OtrzU0ZY23rnyYpR0xXHr2gZg7KMyKAAKOCkytKdEtFescnZPJEAiiwK87DdYvOg0K4tK8siYCiFc6YVUdBBCrJNMfZ3ujobvXJD3xocH9q77mwqh++N9c/Ui/mxyJgH8F7qnapDurNvp3AVSOgEcEzu9ymL5f1N8j1QSyDAJI0NpKAHG2o5Ut0uTVCdV44EOD+6/c/PDgK4/F1CbfWRNmQwABdwQmVqzTwzUl7kzOrAgESOCiriP0jQ59ArQizy2FAOK5luRYEAEkR8AMTm8xpFtXJrSzMYOTHDz01z+M6CJeveugOFMh4K7Av/es1pO1290tgtkR8LmAec/ANd2P1Lj2PXy+Ek+XTwDxdHuyKI4AkgVaFqe0JKW71ya1uc5rj5ynFhOLSq/PiKpPMbdfZdFeTkHAdwLm34ku3rVc5puw+CGAQPYC5j81J/U8TqPbdc5+EM5sTYAA0pqQ3/46AcSZjs3cZuiNMu89dP7h6r84JqIHb+HVu87sBmZBwH0BM4D8cecSvdW4x/1iqAABHwuYAeTeXp/VyDZFPl6F50sngHi+RRkWSADJECyLw18qNfSix771sf8yHp0c1djRXP3Ior2cgoAvBcwA8uuy9/R+U7Uv66doBLwiYP6T88k+J6g4r51XSgpiHQSQoHWVAGJvR9+vMjR1vfe+9bHvqkcMlV54mFfv2rsTGB0BbwmYAeQH29/Wpni9twqjGgR8JmDeOzC3/zjlR/iXeDa2jgBiI64rQxNA7GPf2mDo3rXefN3uvqu+4LcR/fYn3H5l305gZAS8J2AGkDO2vamdiSbvFUdFCPhIoEs0X8/1+7yPKvZlqQQQX7btEEUTQOzpaEWTdPuahKpb7BnfqlHbtpEWzIqpI7euWkXKOAj4QsAMIKdvfV2VSY//TcoXmhQZZoGh+e31cO8xYSZwYu0EECeUnZyDAGK9dlNSum11QmUN1o9t9YinfiGiKddx9cNqV8ZDwOsCZgA5peQ1NRgJr5dKfQh4WuAzbTtrUvFxnq4xAMURQALQxE8sgQBifUcf35LUgl3efN3u/qt9ZGJUJ32O+1at3wWMiIC3Bcy/Q5205VV5+wk1bxtSHQKmwOcLuun6HkeDYa8AAcReX+dHJ4BYa/5ymaE527z7ut19V9urh/TmU7G93wDhhwAC4RKoNeL6csm8cC2a1SJgg8DXCnvp0m6jbBiZIfcRIIAEbTsQQKzr6KIKQ49t9Ef4MFf9t3MjOu+npA/rdgAjIeAfgR3xRv339vn+KZhKEfCowE86DtRvOw/xaHWBKYsAEphWfrAQAog1Hd3VbGjSqqQa49aM58Qocx6JaTh/z3SCmjkQ8JzAquYa/WzHAs/VRUEI+E3goq4j9Y0Ovf1Wtt/qJYD4rWOt1UsAaU2o9b9eF5cmrU7IfPOVX36jj4xoxhSufvilX9SJgNUCCxor9budi6welvEQCJWA+QTlpJ7HaXS7zqFatwuLJYC4gG7rlASQ3Hkf3JTUij3+eOj8w9XefHlUZ36Vh89z7z4jIOBPgTl1O3XJ7uX+LJ6qEfCIQFQRPd5nrHrzFXS7O0IAsVvY6fEJILmJv7AjqVe2+yt8FLSTXp0eVa8eBJDcus/ZCPhX4InabRq/Z41/F0DlCHhAwPwI4eN9T1D7SMwD1QS6BAJI0NpLAMm+owv3GJq+yT8PnX+40jO+EtGEK7j9KvvOcyYC/heYUrVRU6o2+X8hrAABFwWG5LXXI334CKEDLSCAOIDs6BQEkOy4y5qkKWsSqvXhR4Qn/TOqr3+Zqx/ZdZ6zEAiGwHV71ujx2m3BWAyrQMAlgdFtO2syHyF0Qp8A4oSyk3MQQDLXbkxIt6xKqNJHD51/uMoeXaXXZ8TUrm3m6+YMBBAIhoB50+ilu5ZrTv3OYCyIVSDgkgDfAHEMngDiGLVDExFAMod+eHNSy3b767mPD1f5i+9FdNkfuf0q865zBgLBETD/7vWHnYv0bmNlcBbFShBwQeD3nYfqRx0HuDBz6KYkgASt5QSQzDo6uzSpV0v9GT7MlU6bFNUJn+H2q8y6ztEIBEvA/DvYr8re07Km6mAtjNUg4KCA+U/SG3ocoxMLujo4a2inIoAErfUEkPQ7urLa0CPrk4r7NH/07SW9Mj2mtvnpr5kjEUAgeALm38LOKn1H61vqgrc4VoSAQwJmAHmqz4nqmcc9zQ6QE0AcQHZ0CgJIetwVzdJtaxKqaU7veC8ede6PI7rwPG6/8mJvqAkBJwXMAPKd7W9pa7zByWmZC4FACbSNRDW3/8mBWpOHF0MA8XBzsiqNANI6W4sh3bUmqZI6n176+GCJj90e1Zhjuf2q9Y5zBALBFmgwEjpz23xVJn34Gr9gt4bV+UhgcF57TeUVvE51jADilLRT8xBAWpeevSOpV332scH9VzWwr/TyozHl5bW+Xo5AAIFgC1QkW/YGkEYjEeyFsjoEbBQ4rbBYV3Y73MYZGHofAQJI0LYDAeTQHX1vt6H/bPbfxwb3XxVvvwra/3JZDwLZC2xpadB3S9/KfgDORAABnd/lMH2/qD8SzggQQJxxdm4WAsjBrSuapAmrE2qMO9cPu2aaemtUJ36W26/s8mVcBPwksLixUuf8//buA8yK8uz/+G/OOdsrW1l6EawoYAnG2GOMvcbee43GJGo0sXfsvSUaNfYeTSwxiQ0UEQREmgjSWcpSl112z5n3HYiJBdhT5syZeeZ7rivX/5+Xmee578/97G7uM/PMNI4OUsjEioCvBJy/pvfWDdKgwkpfxWVwMDQgphWXBmTdFXVeNnj3xLgWBfBlg9/PqKFeGv5yVBb9h2k/vuSDQFoC7zQ36tKF49M6l5MQQEBy/py+1W1HlUW4r9mj9UAD4hG0Z9PQgKyb+tVZCQ1vDPam828y22c3S/dex9OvPPuhYiIEfC7w4vI5uqlpks+jJDwE/CtQHc3X61138G+A5kVGA2JaTWlAfljRUYttPTc9+Ps+vsnskVsi2m0HLn+Y9rNLPgikK3D/kq/06LKv0z2d8xAIvcBPiqp1c+2WoXfwEIAGxENsT6aiAfkuc2OrdO/EuFoNeThMXY303vNRFRV6spyYBAEEAiBw7aKJ+uvKuQGIlBAR8KfAmZV9dHx5T38GZ2ZUNCCm1ZUG5H8VbU1ID0+Ja5ZBLwfeeYilx27n9ivTfm7JB4F0BZwbSy9oHKvhLYvSHYLzEAi1gHM/wV11W2mbwqpQO3icPA2Ix+BZn44G5H/Eb8xN6N25Zuz7+CarW/4Q0aH7cPtV1n+QmACBgAg4v+GOmzdCU1Yb9E1LQOwJ0wyBmCw912WIGmLcWuBhRWlAPMT2ZCoakLXMU1fY+uPkhExqP5zbrj56JarKCk+WEpMggEAABJzfcfvNHqaFcQMe8RcAb0I0T6BHrEjPdPmRrDXPwuLjkQANiEfQnk1DAyItbZMemByX894Pkz7Oez+c93/wQQABBL4RcBqQ3Wa+p1W8BZ1FgUBaAvuXdtElVRundS4npS1AA5I2nU9PDHsDkrClP09NaPIyk659rF1s559s6Ven0oD49EePsBDIiUCrndDOM9/NydxMikDQBZxrHlfUbKY9i+uDnkrQ4qcBCVrFOoo37A3IR4tsvfK1OY/c/Xa9//ZYVJv372gF8O8IIBAmgbntLTpozvAwpUyuCLgm4DQgf27YRv3zylwbk4GSEqABSYopQAeFuQGZucrW/RMTcq6CmPbp10t688moolwAMa205INARgKftS7VGfNHZTQGJyMQVoH6aIFe6rK9Ihb7PzxeAzQgHoNnfbqwNiBtCemuSXEtWJV14pxMcMDPLN15Fd1HTvCZFAEfC7zT3KhLF473cYSEhoB/BX5WXKerajb3b4DmRkYDYlptw9qAvDk3oX8b9sjdb6/NB2+MaM+d+YbGtJ9X8kEgU4Gnls3SHUumZDoM5yMQOgHnL+rV1ZvrpyV1ocvdBwnTgPigCK6GEMYGZNwSW09+Zea+j28WxxfvRFVS4upSYTAEEDBA4NamKXp2+SwDMiEFBLwVcBqQJxq2U988/rh6K79mNhqQHKBndcqwNSAr49JdE+JaujqrrDkdvH9v6e2nojmNgckRQMB/As52twsXjNP7qxb6LzgiQsDnAs6LB59tGKI89n/kolI0ILlQz+acYWtAnpye0LjFBu46/9YiOfM4Sxefxf6PbP7cMDYCQRRwfvM5G9DHtC4NYvjEjEBOBQ4q7aKLeP9HrmpAA5Ir+WzNG6YG5LMltp4x/NYrZ508c29EQwaz/yNbPzOMi0BQBZwG5Ji5IzS1bWVQUyBuBHIi4PxFvbF2gHYqqsnJ/EzKLVjGrYGwNCDOW85vnxDXarO3figvJn3xr6jy84xbqiSEAAIZCrTbtg6eM1yN8dYMR+J0BMIlEJWlF7oMUedYYbgS90+2XAHxTy3ciSQsDcidE+Oa2+yOmZ9H2W6g9Nz97P/wc42IDYFcCTQl2nTQ7OFqseO5CoF5EQikwMCCCt1fPziQsRsSNA2IIYX8bxphaEDeW2Dr7zMNv/Txn4r+7pyIzjiG269M+zklHwTcEJiyeoWOnfeJG0MxBgKhEji1ordOrugVqpx9liwNiM8KknE4pjcgi1fbumdiQs3tGVMFYoDnH4ho261oQAJRLIJEwGOBYS2LdEHjWI9nZToEgi3g/EW9p26QBhdWBjuRYEdPAxLs+v0wepMbkIQt3Tc5oVkrzX7q1TdVLS6S3n8xqppOpq1S8kEAATcEXlw+Rzc1TXJjKMZAIDQCXWKFeobH7+a63jQgua6A2/Ob3IB8uMDWayG59cpZF1tvaenFB3n8rts/I4yHgCkCdzV9qb8sn2lKOuSBgCcC+5U26NKqTTyZi0nWK0ADYtriMLUBWbhauntiXK0hufXKWZcXnGrpvJNpQEz7GSUfBNwQKkpCuwAAIABJREFUcK4DX7Lwc/2reYEbwzEGAqEQcG6/uq5mC+1aXBuKfH2cJA2Ij4uTVmgmNiCtCemxqQl9tTwct159U/jHbo9o5yHs/0jrB4GTEDBcwPlt+MvGz/RJS5PhmZIeAu4JlEaier3rT1Rg8eWee6ppjUQDkhabj08ysQF5f4Gtv4Xo1itneVmW9NGrUXXmSxof/7QRGgK5E3AakBPmjtSktuW5C4KZEQiYwPaFVbqtbquARW1kuDQgppXVtAbEeerVbeMTag/XxQ9t3Fd66y+8/8O0n0/yQcAtAedB5D+d+Z6aeQeIW6SMEwKBS6o20f6lDSHI1Pcp0oD4vkQpBmhSA+L0HA9NSWhayG69ckp+3CGWrv4tl4hTXP4cjkBoBBbEW7Xf7GGhyZdEEchUoMSK6oWu26sykpfpUJyfuQANSOaG/hrBpAYkbE+9+vZKevimiPbYif0f/vrpIhoE/CMwrnWpTp0/yj8BEQkCPhfYo7hOV9ds7vMoQxMeDYhppTalAVm8Wrp/clzLV5tWoeTy+efTUfXlJa3JYXEUAiEU+PvKebpy0YQQZk7KCKQncHn1ZtqrpD69kznLbQEaELdFcz2eKQ3Io1MTmrQ0ZBs//rN46mukD1+OKi+W69XE/Agg4FeBh5dO08NLp/s1POJCwFcCzm1Xz3cdolKLP6w+KQwNiE8K4VoYJjQgY5bYevorZ4tlOD8/28nSQzex/yOc1SdrBJITuLlpsp5fPju5gzkKgZAL7FxUoxtrB4RcwVfp04D4qhwuBBP0BqRptXTvpLhWtLmAEdAhLj8/opOOYP9HQMtH2AhkXcC5NnzRgnF6b9XCrM/FBAgEXcD5a3ptzRbajZcP+qmUNCB+qoYbsQS9AXlhZkIjF4Tz1qtv6s8LCN34SWAMBMwVcH5DHjN3hKa2rTQ3STJDwCWBumiBnu8yRPm8fNAlUVeGoQFxhdFHgwS5AZnebOuBieG99eqbZTT8lai6sE/ORz9VhIKAvwScd38cPHu4liRCfKnYXyUhGh8L7Flcpyt5+pXfKkQD4reKZBpPUBuQ1rh024S4lob0qVff1L1Xd+nd53gBYaY/B5yPgMkCs9tbdOic4Qr3tWKTK0xubgk4t189VL+1tigod2tIxnFHgAbEHUf/jBLUBuStubb+NZerH/vsZune69iA7p+fKCJBwH8CI1oW65eNY/wXGBEh4DOBLtFCvdh1e59FRTiSaEBMWwZBbEAWr7Z12/iE2vk6T78909I5x9OAmPZzST4IuCnwworZGrp4sptDMhYCRgqcUdlbJ5TzUi0fFpcGxIdFySikIDYg90xKaNZKug+n8A/cENHPd+EJWBn9EHAyAoYL/GnpdD24dJrhWZIeApkJOF/lvdr1x6qJFmQ2EGdnQ4AGJBuquRwzaA3IqCZbz03j1itnzUSja/d/dO+SyxXE3Agg4HeBqxZN0N9WzvN7mMSHQE4Fti6o1D31g3IaA5OvV4AGxLTFEaQGZGW7dPekuJa0mlaF9PLp2ll67/moYryoNT1AzkIgBALOteJT5o3U+NXLQ5AtKSKQnoBzH8HQ2gH6SVFNegNwVrYFaECyLez1+EFqQF6YkdDIhdx69c0a2W0HS4/cwv4Pr39mmA+BIAk4vzF/Put9LU20BylsYkXAU4GaaL6e6zJERRZPlfQUPvnJaECStwrGkUFpQL5aaeuRKQm1c/fVfxfWKUda+sN5NCDB+EkjSgRyI9AYb9X+s4flZnJmRSAgAieU99QZlX0CEm0ow6QBMa3sQWhA4pIenBzXjBWm6WeWz/UXR3TUgWxAz0yRsxEwW2BkS5POafzM7CTJDoEMBGKWtWbzeVUkP4NRODXLAjQgWQb2fPggNCAjFtt6aTqXPr6/OF59JKKtNqUB8fyHhgkRCJDAs8tn6damKQGKmFAR8FZgt+JaXVezhbeTMluqAjQgqYr5/Xi/NyBNq6V7J8W1os3vkt7GlxeTxrwVVUmxt/MyGwIIBEvgriVf6i/LZgYraKJFwCMB5yu8e+oGanBhJ49mZJo0BWhA0oTz7Wl+b0BenZXQ8EY2nn9/AfXvI739JJvlfPuDRWAI+EDA+c155aIv9MbK+T6IhhAQ8J9An7wS/aVhW1nibgL/Vec7EdGA+LxAKYfn5wZkVrN078S4aD9+WNadh1h67HY2oKe84DkBgRAJOL87j5v3iaasZgNdiMpOqikIXF69qfYq6ZzCGRyaIwEakBzBZ21avzYgbQnp4S8TmrGC9mNdxT/uUEtX/4YGJGs/GAyMgAECzXZc+88aphU2j+A1oJyk4LJAp2ienu8yRCUWL9NymTYbw9GAZEM1l2P6tQEZudjWC2w8X+/SuOHiiI7kCVi5/NFhbgR8L/Bl20odM3eE7+MkQARyIXBEWXed32mjXEzNnKkL0ICkbubvM/zYgCxZLd3DxvMNLpyXH45o0Bbcs+rvny6iQyC3Av9obtTvF47PbRDMjoAPBYqtqB5r2FbdYkU+jI6Q1iFAA2LasvBjA/KP+bbemc1jd9e31grypWEvR1VTZdpqJB8EEHBT4IllM3T3kqluDslYCBghsHdJZ11WvakRuYQkCRoQ0wrttwbEeezu0PFx2Wz9WO9S69YgffBiVBYXQEz7cSQfBFwVGLp4kl5YMcfVMRkMgaALOC8evK9ukAYUVAQ9lTDFTwNiWrX91oA8+lVck5aYpuxuPjwBy11PRkPARAHnO5wz5o/SmNalJqZHTgikLbBHca2u5sWDafvl6EQakBzBZ21aPzUg05ttPTCRW686Kvaxh1i65rc8AasjJ/4dgTALJGTr57M+0LIET8AK8zog9+8KOH85b68bqO148WDQlgYNSNAq1lG8fmpA7pgY17zmjiLm3y84NaLzTub+K1YCAgisX2BW+yodOucjiBBA4FsC2xdW6ba6rTAJngANSPBqtuGI/dKADF9o69UZXP1IZn3dfXVE++1BA5KMFccgEFaBES2L9cvGMWFNn7wR+IGA81fz5tottUNRNTrBE6ABCV7N/N+AtNnSzePjWrbaNN3s5PPqIxFttSkNSHZ0GRUBMwReWzFP1yyeYEYyZIGACwKb5pfp4fqtFeUJLi5oej4EDYjn5Fme0A9XQP4+J6H35vHYq2RL/dkbUXWqTPZojkMAgTAK8ASsMFadnNcn4Hxld1XNZtqjuB6kYArQgASzbuuPOtcNiPPSwdsmxLU6bppsdvKprZZGvh7NzuCMigACRgg4X+ecPv9TjW1dZkQ+JIFApgL98kr1eMO2mQ7D+bkToAHJnX12Zs51A/LE9ITGL+bqR7LV3WYr6YUHaECS9eI4BMIo0GzHdeDsYTwBK4zFJ+cfCDhXP26oHaCdi2rQCa4ADUhwa7fuyHPZgMxrke6cwEsHU1lTu+1g6ZFbeARvKmYci0DYBGa0N+uwOR+HLW3yRWCdAt1jRXquyxB0gi1AAxLs+v0w+lw2II9PT+gLrn6ktKQO28/S0EtpQFJC42AEQibwSUuTzm38LGRZky4CPxRwrn7cWDNAOxVz9SPg64MGJOAF/EH4uWpAJi+39cgUHrub6nr67RmWzjmBBiRVN45HIEwCzyybpduWTAlTyuSKwDoFeuUV6+mGH6ETfAEakODX8LsZ5KIBcXZ8OLdezVtlmmb287nzqogO+BmP4M2+NDMgEEwB5/frFYu+0Jsr5wczAaJGwCUB5y/lA/WDtWVBhUsjMkwOBWhAcoiflalz0YCMX2briS+5+pFOQV94IKJttqIBSceOcxAIg4DTgBwzd4Smtq0MQ7rkiMB6BQYUlOuh+q0RMkOABsSMOv4vC68bkLgt3fpFXItbTZPMfj4RS3r/xai6NWR/LmZAAIFgCrTbtnae9a7iNk8XDGYFidoNAedrur80bKc+eSVuDMcYuRegAcl9DdyNwOsG5L3GhP4+iz+M6VSxvFT66JWoSvh9mg4f5yAQCoHZ7S06ZM7wUORKkgisT2DHomoNrd0SIHMEaEDMqeXaTLxsQFri0tDxcTW3m6boTT49uqy9AsIHAQQQWJ/AO82NunTheIAQCK2A85gW5+pHb65+mLQGaEBMqqbXDci7jQm9wdWPtJfQgI2l1/5MA5I2ICciEAKBO5q+1FPLZ4YgU1JEYN0Ch5V10wWd+sFjlgANiFn19O4KyJLVa/d+tLH3PO0ltN1W0nO8BT1tP05EwHQB5+bWU+d/qs9bl5meKvkhsE6B8khMTzZsp5poAUJmCdCAmFVP7xqQt+fZ+uccuo9M1s/Pd7X0wPW8AyQTQ85FwGQBpwHZc9b7WpbgPleT60xu6xc4qaKnTqvoA5F5AjQgptXUiz0gy9ukGz6PK8He84yWz4mHWbriAhqQjBA5GQGDBRYn2rT3rA8MzpDUEFi/QEOsUE81/EiFFn8nDVwnNCCmFdWLBuRvcxJ6fx7dR6Zr53fnRHTGMbwDJFNHzkfAVIEPVi3SbxaMNTU98kJgvQLOX8aba7fUDkXVKJkpQANiWl2z3YAsaZVu+iIuHkmf+crhLeiZGzICAiYL3Ltkqh5bNsPkFMkNgXUKbFVQofvrB8kSX9IZukRoQEwrbLYbkL/OSmhYI1c/3Fg3T9wZ0Y7b8cvVDUvGQMA0Aee37JnzR+uz1iWmpUY+CGxQICJLf+68jfrllyJlrgANiGm1zWYD0tQm3TaeJ1+5tWb+9nhEm/ejAXHLk3EQMEkgLlt7zHxfzXbcpLTIBYEOBXjsbodEJhxAA2JCFb+dQzYbkOdmJDRqIVc/3FozI1+PqpbbW93iZBwEjBJYEG/VfrOHGZUTySDQkUBVNE+P1m+ruhiP3e3IKuD/TgMS8AL+IPxsNSALW6VbxzvfyfFxS2DqB1HFYm6NxjgIIGCSwPurFum3bEA3qaTkkoTALyv76qjyHkkcySEBF6ABCXgBPWtAXpyZ0CcLaD/cWi8F+dLk93gLuluejIOAaQL3LJmqx9mAblpZyWcDApvll+mPnbdm43k4VgkNiGl1zsYVkIWrpTt567mrS6WqUhr9Bg2Iq6gMhoAhAs5XPafPH6WxrUsNyYg0ENiwgLMb8k/122jTgjKowiFAA2JanbPRgDz7dUKjF3H1w8210lAnffQqDYibpoyFgCkCzm/b3Wa+p1VsQDelpOTRgcA+JQ36Q/UmOIVHgAbEtFq73YCsufoxIa42HsTi6lLZuI/01pM0IK6iMhgChgjMaF+lw+Z8ZEg2pIHAhgW6xorW3HpVGcmDKjwCNCCm1drtBuSlGQmN4MlXri+T7baSnnuABsR1WAZEwACBN1bO1xWLvjAgE1JAYMMCzq1XV9Vspj2K66EKlwANiGn1drMBWexc/ZgYV2u7aUq5z2e3HSw9cksk94EQAQII+E7gukWT9OrKOb6Li4AQcFvgJ0XVurl2S7eHZTz/C9CA+L9GqUXoZgPy8oyEPubqR2oFSPLoQ/a2dOtlNCBJcnEYAqERcPZ/nDr/U33euiw0OZNoOAUqInn6S8O2qonyzo8QrgAaENOK7lYDsqxduuOLuJq5+pGVJXLCYZauvIAGJCu4DIpAgAVa7IQOmj1MTYm2AGdB6Ah0LHBOZV8dwzs/OoYy8wgaENPq6lYD8trshD6cz5OvsrU+zjnB0m/PoAHJli/jIhBUgfGrl+vkeSODGj5xI5CUQL+8Ev2587aKWM4uED4hFKABMa3objQgzXHp9glxLV9tmo5/8rnobEtnHUsD4p+KEAkC/hD4y/KZuqvpS38EQxQIZEEgz7L0ZMN26h4rzsLoDBkQARqQgBQq6TDdaED+MS+hd+Zw9SNp9DQOvPq3lo47hAYkDTpOQcBYAee37oULxun9VQuNzZHEEDi/00Y6oqw7EOEWoAExrf6ZNiCtCemmz9n7ke11cfMfIvrFPlx6zrYz4yMQJAGnATl8zsea0d4cpLCJFYGkBTbPL9cD9YMV49arpM0MPZAGxLTCZtqAjGqy9dy0hGksvsvn/usj2mtXGhDfFYaAEMihwKz2VTqUFxDmsAJMnU2BEiuqBztvrb55JdmchrGDIUADEow6JR9lJg2I03bcOj6uRa3Jz8eR6Qk8PDSiPXakAUlPj7MQMFPg7yvn60peQGhmcclKp1X01kkVvZBAwBGgATFtHWTSgExYZuuxL7n64cWaePreiLYfTAPihTVzIBAUgduapuiZ5bOCEi5xIpC0wNYFlbqjbiC3XiUtZvyBNCCmlTiTBuTOSXHNXWmaiD/zeea+iIYMogHxZ3WICgHvBZz9H2fMH6UxrUu9n5wZEciiQHkkpj/Wb6PueUVZnIWhAyZAAxKwgnUYbroNyMxVtu6dwNWPDoFdOuC1RyMasAkNiEucDINA4AWWJNp04OzharHjgc+FBBD4tsBZlX11HC8cZFF8V4AGxLQVkW4D8sjUuCbzxZtny+Hvj0e0WT8aEM/AmQgBnwsMa1mkCxrH+jxKwkMgNYGdi2p0Y+2A1E7i6DAI0ICYVuV0GpDG1dLtn8fFmz+8Ww00IN5ZMxMCQRC4tWmKnmX/RxBKRYxJCtTHCvRg/WDVRwuTPIPDQiRAA2JasdNpQJ6fkdCnC2k/vFwLNCBeajMXAv4WcH77ntv4mUa2NPk7UKJDIEmBmCxdVb25diupTfIMDguZAA2IaQVPtQFpjkvXj42rnf7D06VAA+IpN5Mh4GuBVjuhfWd9qOV2u6/jJDgEkhU4oqybzu/UL9nDOS58AjQgptU81QbkjTkJvTuP7sPrdUAD4rU48yHgX4FPW5bo7MbR/g2QyBBIQaB7rEhPNmynPCuSwlkcGjIBGhDTCp5KA+I88+rqsXG18KWb58uABsRzciZEwLcCf1w6XQ8tnebb+AgMgWQF8i1LzzQMUUOMfR/JmoX0OBoQ0wqfSgMyeomtZ7/i0bu5WAM0ILlQZ04E/CfgXH/+zYKx+nDVIv8FR0QIpChwcdXGOrC0S4pncXgIBWhATCt6Kg3IXRPjmtNsmkAw8qEBCUadiBKBbAussuPaf/YwLU9wKTrb1oyfXYE9SzrriupNZIlHzGdX2ojRaUCMKOO3kki2AZm20taDk7j6kav604DkSp55EfCXwIiWJv2y8TN/BUU0CKQo0COvSI913laFVjTFMzk8pAI0IKYVPtkG5PFpCX3RxObzXNX/5YcjGrQF3xLlyp95EfCLAPs//FIJ4khXoMiK6o+dt1afvJJ0h+C88AnQgJhW82QakEWrpTsmxNUWNy374OTz9L0RbT+YBiQ4FSNSBNwXcL4Ccq5+fML7P9zHZUTPBC6r3lR7///tV3wQSEGABiQFrEAcmkwD8ubchP49l6sfuSzoE3dGtON2NCC5rAFzI5BrgZV2XHvN+kCrbW6HzXUtmD89gb1KOuvy6k3TO5mzwixAA2Ja9TtqQNoS0i1fxLV0tWmZByufB2+MaM+daUCCVTWiRcBdgZGtTTpnPvs/3FVlNK8E+ueV6s76gaqM5Hk1JfOYI0ADYk4t12bSUQMybEFCf53J1Y9c1/2OKyM6cE8akFzXgfkRyKXAo8u+1v1LvsplCMyNQFoCddF83V43kH0faelxkiQaENOWQUcNyJ2TEpq7kgYk13W//uKIjjqQBiTXdWB+BHIl4PwWPm3+KI1rXZqrEJgXgbQEnL9cV9Vspj2K69M6n5MQoAExcA1sqAGZ2Wzr3onca+yHsl/2K0snHx7xQyjEgAACORBojLfqkDnD1WbzhVAO+JkyA4EzK/vo+PKeGYzAqQhwBcS4NbChBuSVWQl91MgfOz8U/fxTLP3qFBoQP9SCGBDIhcCwlkW6oHFsLqZmTgTSFtixqFrX1WyhPIu/X2kjcqIjwC1Ypq2D9TUgK9rXbj5v4WW7vij5SYdbuvxX/AL3RTEIAoEcCAxdPFkvrJidg5mZEoH0BLrFivTHztuoIhJLbwDOQuB/AjQgpq2G9TUgHy1K6JWvufrhl3ofurelWy6jAfFLPYgDAS8FnN/Ezu1Xc9pbvJyWuRBIWyAmS082bKceecVpj8GJCHxLgAbEtOWwrgbE+WN3z6SEZrP53Dfl3m0HS4/cQgPim4IQCAIeCsyPt+qA2cM8nJGpEEhfICJLN9RuoZ2KatIfhDMR+K4ADYhpK2JdDciMZlv3sfncV6XeeoD04kNRX8VEMAgg4I3A31bO01WLJngzGbMgkKEAm84zBOT0dQnQgJi2LtbVgLw0M6ERC7j9yk+17tVNevd5GhA/1YRYEPBCwPlNfNGCcXpv1UIvpmMOBDIS2K+kQZdUbyxLPDY+I0hO/r4ADYhpa+L7Dcjydmno53E5b0Dn4x+B+hppxGs0IP6pCJEg4I3ASrtd+80apmY77s2EzIJAmgIDCsr1YP3WtB5p+nHaBgVoQExbIN9vQMYtsfXkV3QffqtzcZE04V80IH6rC/EgkG2BT1uW6OzG0dmehvERyEigIVqoe+sHqSFWmNE4nIzAegRoQExbGt9vQO6dnNDMFdx+5bc6RyPS1A+jsriq7bfSEA8CWRW4e8lUPbFsRlbnYHAEMhGoiubp9tqB6p9fmskwnIvAhgRoQExbH99uQBattnXz51z98GuNR74eVW21X6MjLgQQcFvA+SroyDkfa3p7s9tDMx4Crgg434ldU7O5di+uc2U8BkGAKyAhWQPfbkBen5PQB/O4+uHX0v/1kYi23JRLIH6tD3Eh4LbAonib9pn9gdvDMh4Crgg4f41+WbmRjizv7sp4DILABgS4AmLa8vimAVltS0PHxeW8AZ2PPwXuvz6ivXalAfFndYgKAfcFePyu+6aM6J7AIaVd9OtO/RXh3mD3UBlpfQI0IKatjW8akEnLbD36Jbdf+bm+fzg/olOOoAHxc42IDQG3BJxr0RcsGKPhqxa7NSTjIOCawB4ldbqqenOeeOWaKAN1IEADYtoS+aYBeWxaQhOauP3Kz/U9+QhLl53P29D9XCNiQ8AtgYRs7TLzXa22+b3slinjuCPgbDa/p26QyiIxdwZkFAQ6FqAB6dgoWEc4DciqhHT1mLj4O+fv2u29q6X7rqcB8XeViA4BdwRGtjTpnMbP3BmMURBwSaBrrFD31Q9SXZTH7bpEyjDJCdCAJOcUnKOcBmT4woRencG3bH6v2oBNpdce4V0gfq8T8SHghsDQxZP0woo5bgzFGAi4IlBoRfRY523VI6/YlfEYBIEUBGhAUsAKxKFOA3L3pLhmrwxEuKEOslc36d3naUBCvQhIPhQCzm68E+eO1KS25aHIlyT9L1AVydMddQPVj3d9+L9YZkZIA2JaXe8/Tbp+bNy0tIzMp6pSeu+FqMpKjEyPpBBA4D8CU9tW6pi5I8R1aZaEHwSisjS0dkv9uKjKD+EQQzgFaEBMq/vJhyT0j9n8mQtCXSOWcwUkoh5deRJWEOpFjAikK/Dw0ul6eOm0dE/nPARcE3D+2lxY1V8HlXZ1bUwGQiANARqQNNB8fcr2u8c1h9uvfF2jbwf3yC0R7bYDDUhgCkagCKQo4HwddFbjaI1uWZLimRyOgPsCZ1T20QnlPd0fmBERSE2ABiQ1L/8f3XMIt1/5v0r/i/DSX1o67SiehBWkmhErAqkINMZX65A5w9TGYwlTYePYLAicUtFbp1T0ysLIDIlAygI0ICmT+fwEGhCfF+h74R11kKXrL6IBCVbViBaB5AWeXj5Ttzd9mfwJHIlAFgT2K+2iS6r6y+JVg1nQZcg0BGhA0kDz9Sk0IL4uzw+C+9lOlh66iQYkWFUjWgSSE1hz+9X8URrdujS5EzgKgSwI7FfSWRdU9VeRxVMXs8DLkOkJ0ICk5+bfs2hA/FubdUXWs+vaJ2HxQQAB8wSa4m06eM5wrbK5Nda86gYjox8XVum62gFy3vnBBwEfCdCA+KgYroRCA+IKo2eDWJY0/p9RlRR5NiUTIYCARwKvrZyraxZN9Gg2pkHguwLbFnbSDbVbqMSKQYOA3wRoQPxWkUzjoQHJVND78196KKLBA3gSlvfyzIhA9gSc268uWDBGw1ctzt4kjIzAegScFwzeWTtQnaJ5GCHgRwEaED9WJZOYaEAy0cvNuVf+OqITfkEDkht9ZkUgOwItdkL7zvpQK+z27EzAqAisR6BPXonuqRtE88EK8bMADYifq5NObDQg6ajl9pwTD7N0xQXcn5vbKjA7Au4K/KO5Ub9fON7dQRkNgQ4EesaKdE/9INVEC7BCwM8CNCB+rk46sdGApKOW23N23M7SE3fSgOS2CsyOgHsCzu1Xly/8Qm81z3dvUEZCoAMB58rHvfWDVBnhtisWi+8FaEB8X6IUA6QBSRHMB4d3rpP+/WxURYU+CIYQEEAgY4FliTYdMucjLU9w+1XGmAyQlECXWOGa264aYvwhSQqMg3ItQAOS6wq4PT8NiNui2R8vGpH+9lhEm2zEPpDsazMDAtkX+PvK+bpy0RfZn4gZEJBUE83Xn+q3UV2M265YEIERoAEJTKmSDJQGJEkonx12w+8sHXkAt2H5rCyEg0DKAs7tV5ctHK+3mxtTPpcTEEhVwLnycVfdQHWN8Sz3VO04PqcCNCA55c/C5DQgWUD1YMgjD7B0w+9oQDygZgoEsirQlFitg2d/xMsHs6rM4I5A97yiNY/a5bYr1kMABWhAAli0DYZMAxLMiv5okKVn76MBCWb1iBqB/wn8u3mhLl44DhIEsirQPVak2+u24spHVpUZPIsCNCBZxM3J0DQgOWHPeNK8PGniv6KK8cLajC0ZAIFcCTi3X1268HP9s3lBrkJg3hAIcOUjBEU2P0UaENNqTAMS3Io6V0CcKyF8EEAgmAKL4m06bM5HWsnLB4NZwABE3SuvWLfUbKmueez5CEC5CHH9AjQgpq0OGpDgVvS8ky1dcCq3YQW3gkQedoF/Ny/QxQs/DzsD+WdJwHnJ4C11W6kbG86zJMywHgrQgHiI7clUNCCeMGdlkj13tvQxZXTVAAAgAElEQVTgjTQgWcFlUASyLODcfnV+4xh93LI4yzMxfBgFNskv1c21W/KG8zAW38ycaUBMqysNSHArWlctvfcCLyQMbgWJPMwC09uaddS8EUrYTivCBwH3BJw3nDsvGewU5Q3n7qkyUo4FaEByXADXp6cBcZ3U0wGfuS+iIewD8dScyRBwQ+DFFbN10+LJbgzFGAj8V2BgQYXuqhukPIv9gSwLowRoQIwqpyQakGBX9NJzLZ12NLdhBbuKRB82gYSk0+Z/qs9bl4UtdfLNooBz25XTfJRFeDxiFpkZOjcCNCC5cc/erDQg2bP1YuSdhlh6/HYaEC+smQMBtwQmr16h4+Z94tZwjIOAflJUraG1A2SJKx8sByMFaEBMKysNSLArGo1In78TVTFPWAx2IYk+VAKPLJuuB5ZMC1XOJJs9gQNLu+jcTn1VYnHlI3vKjJxjARqQHBfA9elpQFwn9XzAB2+IaM9d+NbLc3gmRCANgbhsHTR7uBrjrWmczSkIfFfgkNJu+m1VP1gQMF2ABsS0CtOABL+ipx1t6dJzuQ0r+JUkgzAIjG5dqjPnjwpDquSYZYEzK/vouPIe3HaVZWeG94UADYgvyuBiEDQgLmLmaKje3aW3n4oqj6vvOaoA0yKQvMBViybobyvnJX8CRyLwPQHnevfpFX10QkVPbBAIiwANiGmVpgEJfkWdpy2+81REfXtxG1bwq0kGJgu02bZ+PusDrbTbTU6T3LIoELMs/bpTfx1U2iWLszA0Ar4ToAHxXUkyDIgGJENAn5z+h/MiOuVIGhCflIMwEFinwD+aG/X7hePRQSAtgapovi6p2mTNE6/4IBAyARoQ0wpOA2JGRQdubumVP7IPxIxqkoWJAs77zs+eP1qjWpeYmB45ZVmgIhrTnbUDtXF+WZZnYngEfClAA+LLsmQQFA1IBng+O/WfT0fVt5fPgiIcBBBYIzCrvUVHzf1Yq23nNYR8EEheoEusUPfUDVJDrDD5kzgSAbMEaEDMqidvQjepnr8/z9KpR3IVxKSakos5AvcsmarHl80wJyEy8USgZ16xHqwbrIponifzMQkCPhWgAfFpYdIOiysgadP57sRBW0gvPRSVsymdDwII+EcgIVv7zx6uhbz7wz9FCUAk2xV20rW1W6iMFwwGoFqEmGUBGpAsA3s+/EY/iauNB7J47p6NCWMx6c0nItqIp2Flg5cxEUhbYNiqxbpgwZi0z+fE8AnsWdxZV9Rswjs+wld6Ml63AA2IaSuj/05xta42Lavw5nPBaZbOO4nbsMK7AsjcbwLO5vNfN47RsJbFfguNeHwqcEJ5T51R2cen0REWAjkRoAHJCXsWJ6UBySJuDoZuqJWGv8ptWDmgZ0oE1ikwP96qw+Z8pFY2n7NCOhAoiUR1dmVfHVzaFSsEEPiuAA2IaSti013jal5lWlbhzufNJ6LaZKNwG5A9An4R+NPS6Xpw6TS/hEMcPhUosWK6tHoT7VZc69MICQuBnArQgOSUPwuTD9gjrmXLszAwQ+ZM4PD9Ld10Cbdh5awATIzAfwScN58fN2+EprU1Y4LAegW6xgo1tHZL9ckrQQkBBNYtQANi2srYeu+4FnJrslFlLS+TRv09qryYUWmRDAKBE3h31UJdtGBc4OImYO8EBhZU6PLqzXjHh3fkzBRMARqQYNZt/VEP2T+uuY2mZUU+N/4uoiMO4Hm8rAQEciXgbD6/fOEXeqt5fq5CYF6fC+xRUqeLO22skgjfFvm8VISXewEakNzXwN0Idjo0rq9nuTsmo+VeYPAWll58KMI7QXJfCiIIqcCc9hYdPvcjObdh8UHg+wKnVfSW87SrCC9uYnEgkIwADUgySkE6Zo+j45o8NUgRE2syAtGI9NLDEW21GVdBkvHiGATcFnho6TT9cel0t4dlvIALOJvNT6vsrcPLugU8E8JHwFMBGhBPuT2YbL8T4xo7wYOJmMJzgSP2t3Qjm9E9d2dCBJyrHofOGS7nEbx8EPhGoNCK6ObaLbVNYSdQEEAgNQEakNS8/H/0Uecm9OEn3CLg/0qlHmFenjTu7aiKClM/lzMQQCB9gWEti3RB49j0B+BM4wR65xXrttqt1DnGL2TjiktCXgjQgHih7OUcv7wsoVfeogHx0tzLuc4+3tKFZ/JIXi/NmSvcAs5v0/MaP9OIlqZwQ5D9fwV2KqrR5TWbyrn9ig8CCKQlQAOSFpuPT7ru7oQeeIIGxMclyii02mrpn89EVV6a0TCcjAACSQqMbV2m0+ePki1+ryZJZvRhx5T10CmVveXcfsUHAQTSFqABSZvOpyc++GRC197JH0qflseVsK75raVjD+GPnyuYDIJABwJDF0/WCytm4xRyAefRuhd06qd9SjqHXIL0EXBFgAbEFUYfDfLaO7bOvjTho4gIxW2BjXpLbz4RVSzq9siMhwAC3xZYEF+to+Z+rOWJdmBCLNDw//s8rqjeTFsVVIRYgdQRcFWABsRVTh8MNm6itO8JcR9EQgjZFBj6+4gO25dH8mbTmLEReGr5LN3RNAWIEAtsnF+qG2sGsNk8xGuA1LMiQAOSFdYcDrp0ubTlHjQgOSyBJ1Nvuan07H08EcsTbCYJpUCrndARcz/W3PaWUOZP0tJhZd10amVvlbHZnOWAgNsCNCBui/phvE12jmsVj6v3QymyGsPtV0R00M+5CpJVZAYPrcC7zQt10cJxoc0/zImXWjEdV95Tx1X0CDMDuSOQTQEakGzq5mrsIQfENXd+rmZnXq8ENt1Ieu3P7AXxypt5wiPgPMbjpHkjNWH18vAkTaZrBJzm4+a6ARpYUIkIAghkT4AGJHu2uRv58LMS+mgUT8LKXQW8m/n6iy0ddSBPxPJOnJnCIDC1baWOnjsiDKmS47cEBhVU6rLqTeVsOueDAAJZFaABySpvjgb/3Q0JPfkyDUiO+D2ddqOe0osPR1VR5um0TIaAsQLOb85LFo7Xv5objc2RxH4ocFBpF53TqS8vF2RxIOCNAA2IN87ezvLES7YuvZFH8XqrnrvZLj/f0klHcBUkdxVgZpME5sZbdOicjxS3+RLHpLquL5cCK6LzO22kg0q7hiFdckTALwI0IH6phJtxfDrO1sGn0oC4aernsYqLpDf/ElGPLmxI93OdiC0YArc2TdGzy2cFI1iizEigc6xAt9cOVK+84ozG4WQEEEhZgAYkZbIAnLCoSRq8F4/iDUCpXAvx2IMtXXMhV0FcA2WgUAosS7TrwNnD1Gzz+9P0BfCjwipdUrWx6tnvYXqpyc+fAjQg/qxLZlHFE9Kmu8TVujqzcTg7OAIF+dJTd0e09ZZcBQlO1YjUbwJ/WjpdDy6d5rewiMdFgTzL0uFl3XV2ZR9Z4veli7QMhUAqAjQgqWgF6dgDT45r9PggRUysmQo4zcez90YUi2U6EucjED6BNtvWAXOGaXGcb25MrX5VNF+XVG2inxRVm5oieSEQFAEakKBUKtU4r749oYefZhNlqm5BP/7aiyI65iC+1Qt6HYnfe4Gnl8/S7U1TvJ+YGT0RGFhQoStrNlN9lEfsegLOJAhsWIAGxNQV8urbts79AxvRTa3v+vKqrZLefiqqThVhy5x8EUhfIC5bR84ZoRntzekPwpm+FTi5opeOLu+hYivq2xgJDIGQCdCAmFrwaTOkXQ5jI6Wp9d1QXvv+1NI917AhPYy1J+f0BF5fOU9XL5qQ3smc5VuBmmiBLqraWDtyy5Vva0RgoRWgATG19EuXSzsdGteSpaZmSF7rE4hGpfuui2jPnbkVi1WCQEcCzo2qx84boS9Xr+zoUP49QAID8st1Zc3m6sJTrgJUNUINkQANiMnFPurchD78hH0gJtd4fbnVVUvvvRBVEbc7h7H85JyCwEerFuv8BWNSOIND/SwQkaXDy7vprIo+yrO4EuznWhFbqAVoQEwu/91/TmjofTQgJtd4Q7nt/hNLfxwakcWFkLAuAfLuQMD57Xju/M80srUJKwMEiv5/j8cVNZtpp8JqWfziM6CipGCwAA2IwcXVqHHSQaeyD8TkGneU242XRHTE/nQgHTnx7+EU+Lhlsc5r5OqHCdXfqqBCV1VvrvpYgQnpkAMCpgvQgJhc4ZXN0ua7x2VzEcTkMm8wt4oy6Y0noupSH1oCEkdgnQLOr8XfLBirD1ctQijAAs7XK8eW99BZlX0DnAWhIxA6ARoQ00u+97FxjefR9qaXeYP5DRks/eXOKC8oDPUqIPnvC3za0qRzGj8T388Ed210jhXqwk799WOechXcIhJ5WAVoQEyv/G0PJ3T7w/yJNb3OHeV32tGWLj2XDZkdOfHv4RBwfiNeuGCc3l+1MBwJG5jl9kVVurJ6M5VH8gzMjpQQMF6ABsT0En86ztbBp/JCQtPrnEx+d14Z0QF7sh8kGSuOMVtgeMtiXdA4hqsfASyzJUsXdOqnX5R1DWD0hIwAAv8RoAExfSk0LZV2cd4Hstz0TMmvI4HqTtIz90bUrzdNSEdW/Lu5As7XMRdx9SOQBd4kv0yXVG2i/vmlgYyfoBFA4L8CNCBhWAwn/Sahdz7gNqww1LqjHDfvJz1+V1TVlR0dyb8jYKbAp61NOmc+ez+CVt2jy3voyLLuqonmBy104kUAgR8K0ICEYVU89oKtPwzlNqww1DqZHPfezdKdV0WUF0vmaI5BwBwB57fgxQvG6T32fgSmqJ1jBTqzoq/2LOFRfoEpGoEi0LEADUjHRsE/4svptnY/ggYk+JV0L4Mzj7N08VlsSndPlJGCIPBRy2Kdz3s/glCqNTFuV9hJl1ZtovpYYWBiJlAEEEhKgAYkKSYDDtrzmLgmfmlAIqTgmsBvTo/o3BPZD+IaKAP5WsC5CfWcxtH6tGWJr+MkOKk0EtNJFb10VFl3OBBAwEwBGhAz6/rDrO5+NKGh97MPJCz1TibPWEy679qIfrYzTUgyXhwTbAGn8Ti7cXSwkwhB9Bvll+oPVZtqYzaah6DapBhiARqQsBT/kzG2Dj2d27DCUu9U8nzhwYi22ZImJBUzjg2WwGo7oZPmjdSXbSuDFXiIoo3KWvNG8xMreqnA4vbQEJWeVMMpQAMSlrq3tUn7nRjXBG7DCkvJk86zvkb6820RbdqPJiRpNA4MlMBbKxt12aLxgYo5TMFWRvJ0Tc0W2qaQx/OFqe7kGmoBGpAwlf+aOxN66EluwwpTzZPNtaJcevWPUfXilutkyTguIAJx2Tpizsea2b4qIBGHK8y9SjqvebFgWYTH8oWr8mQbcgEakDAtgM8n2drneG7DClPNU8m1oU56/A5eVJiKGcf6X+Dl5XN0Q9Mk/wcasgiro/m6sKq/di6qDVnmpIsAApJoQMK2DHY/Iq4vp4cta/JNVqBrZ+nBm6Laon+yZ3AcAv4VWGXH9Ys5H2thvNW/QYYwsj1L6nRGRV818HjdEFaflBFYI0ADEraFcMefbN36IFdBwlb3VPKtqpTuviaiHbZhT0gqbhzrP4EHlnylR5Z97b/AQhqR81LB0yr6aO+SziEVIG0EEPiPAA1I2JaCc/XD2YzezO3QYSt9Svk6G9PvvyGiwVvQhKQEx8G+EViSaNPxcz/RfK5++KIm2xR2WvN43fpYgS/iIQgEEMipAA1ITvlzNPmJv07onx+yGT1H/IGZNj9PuvaiiA7blyYkMEUjULW1S2+/Z+vB9smat8kcRHIs4Oz1OK2itw4o7ZLjSJgeAQR8JEAD4qNieBbK86/b+vXV3IblGXiAJ4pGpYvOsnT60TyXP8BlDEXoti09+Yqtx5+39WVzs6qv/kxWWVsocvdrkjsUVetXnfqpW6zIryESFwII5EaABiQ37rmd1XknyI8OiGvR4tzGwezBETj5CEu/OyeiPJ6UGZyihSTS5SukF/5m69HnE5o2Y23SlWdPUtHO80Mi4L80K6KxNZvMD+Kqh/+KQ0QI+EOABsQfdfA+imvuSOihp7gNy3v54M44ZJCle6+LqLpTcHMgcnMEWlqk+x5P6Pm/SbPm/u93WdGWS1T5+7HmJBqwTIYUVuk3Vf256hGwuhEuAh4L0IB4DO6b6abNlH52VFyruUPBNzUJQiB9ekgPXB9R/77sCwlCvUyMcd4C6fEXEnr6VVsLv38VN2Kr7trPFO273MTUfZ1TVTRfJ1X01KGl3XwdJ8EhgIAvBGhAfFGGHAVxyGlxjeSLwhzpB3faThXS738Z0aH70IQEt4rBi3z6zLVXPN74t60ly9Ydf8mu81V+Ji8d9Lq62xZ20tU1m6sykuf11MyHAALBFKABCWbd3InaeRKW80QsPgikI3Dw3pauuiCistJ0zuYcBDoWcDaWfzTa1r2P2nr/E1vOf1/fJ1LSrtobRilS39LxwBzhikBVNE/nVm6kvXivhyueDIJAiARoQEJU7HWmuvvhcX3Je7rCvgzSzr9vL+nG30W07VZcDUkbkRN/ILBkqfSPYbbu/lNCzu2iyXzKj56mkgOSPDiZATlmvQIFVkQ/KarRpVWbqDgSRQoBBBBIVYAGJFUx045//AVbvx/KVRDT6uplPgX50pnHRnTW8Zac/z8fBNIVmDhVevUtW0++klDTkuRHiTWsUs31o2UVtyd/EkemJdA3r0SnVfbRzkU1aZ3PSQgggIAkGpCwL4MVK6Vt9+XN6GFfB27kP2gL6dY/RNSnJ1dD3PAMyxjOiwM/n2jroSdt/e1fG77Nan0mnc6epEIeu5vVJeNc9dituE4XVfVXocVVj6xiMzgC5gvQgJhf444zvOvRhG6+n0fydizFER0JRCLSb8+I6NiDLfaGdIQV8n9fvER66Q1bT71ia8q09H//FA9sUuUl45T+CCEvRBLpb5RXoguq+mtwQWUSR3MIAggg0KEADUiHRCE4oHmV9KP94lq2IgTJkqInAn17SpecE9FPd+RqiCfgAZnEeXfHx2Nsvf4Pe03zkeljwCPF7aq5YqyivfjllY0lUGbFdGBpV53dqU82hmdMBBAIrwANSHhr/93Mr70zoQef5DtE1oO7AgfvZemc4yNyNqvzCa9A40LpsRcSeus9afJX6d1mtS69kj3nqvzkKeGFzWLmWxdW6uJOm6h7XlEWZ2FoBBAIqQANSEgL/4O0ndshtt0nrvY4Igi4K1BSLB2+n6Wzj4+opsrdsRnNvwLOuzrees/Wq2/bev9j97/ciNa3qG7oKKmQjeduroKaaL7OruzLo3XdRGUsBBD4vgANCGvifwI3P5DQXY+4/z8UMEbAEehcK517oqX994iovAwTUwWGjbT18pu2/jXMVuOi7GVZddYUFewyN3sThGxk52bJ/UobdGJ5LzXECkOWPekigIDHAjQgHoP7ejrnKsjOh7IXxNdFMiC4nt2kEw+L6LB9LTlXR/gEW8B5itWY8bZe/+fa/8xfkP188vuuUPX1o7I/UUhm6JtfovMr+8l5ozkfBBBAwAMBGhAPkAM1xQ33JHTf41wFCVTRAhpsj67ScYdYOuKAiMpKAppEiMP+crr01CsJvf2+NHuerXav7oSK2Kq/4xPeeO7C2otals6s6KMDS7uoNBJzYUSGQAABBJISoAFJiilEB61cJQ3+eVwtrSFKmlRzKlBYIB17iKWzjouoiqd85rQWG5rcaTBGj7f1ylu23vlAmjM/N19UlB8yQyWHT/etU1AC276wShdVbazO3G4VlJIRJwImCdCAmFRNt3J54kVbl97E29Hd8mSc5AQsS9p9B0snHm5p24GWCvKSO4+jsicwb8HapuPtd2399R+ZPzY300jZeJ6poNY0HBd26q8fF1VnPhgjIIAAAukJ0ICk52b2Wc77QPY9Pq6vZ5udJ9n5V6BXd0snHWZpvz0srop4WCbblmbOkd4bYeuvb9n6bIIt590dfvnUXvq5Ylst9ks4gYrDud3q0NKuOrmit8q53SpQtSNYBAwUoAExsKiupPTca7Z+cw1XQVzBZJC0BSKW9ONtLB3/C0uDN7dUw5e2aVuu78S5jdLEqbb+PWztVY5FTa5P4cqApbvOV9mZk1wZK2yDOO/0uKxqU9Vzu1XYSk++CPhVgAbEr5XxQ1y/OCOhEZ/l5j5vP+RPDP4ScDaq77K9pUP2tjRgU0s1PLAnrQItbJI+HmXriym23vtYmvSVrVaf7/mKVrSp+rpRitb6PNC0KpK9k2qjBTq9orf2Ke0sS86DdvkggAACvhCgAfFFGXwaxOjPbR1+VkKtq30aIGGFViAWk3bYxtKOP5J+vHVEm/WTnD0kfL4r4Dwid9Yc6dNxtiZNtfXuR7YmfRU8paqzJ6tg53nBCzxHEZdFYtq/tMuaJ1zF+MHIURWYFgEENiBAA8Ly2LDAhdcm9MxfuQrCOvGvgPO/r3p3tzRksDRwM2nI4Iicd42E8eO8y8e5ajlukq2vZ0mjxklzG20lAnw3ZdE2i9Xp1+NlR/k9lMya3rGoWmdU9JXzbg8+CCCAgE8FaEB8WhjfhLVwkfSzY+K+vS/cN1AE4iuBjXpJ22xpqX9fS5tuJG3Wz1Jlua9CzCiYZcvXNhaTv5JmzbM1eao0fLStufMzGtZ3Jzu3XtVeMVZW15W+i81vAfXOK9FJ5b20R0md30IjHgQQQOD7AjQgrImOBR591tbltwb4K9SOU+QIwwVKS6Q+PS317yV1a9CaxqRvD0u9ukmFhf5NvmmJNPGrtbdPzZ4rLV4qTZ7mPKnK1pKlkvPUKpM/5Yd/rZJDvjY5xYxzK7AiOquyj/YorldVND/j8RgAAQQQ8ECABsQD5MBP4ewBOfa8hD4ebfj/2gl8pUggVQFnL0nfHlJttaXyUqlrZ6lbV0v11VLnWuf/LpUUWSoqlAoKMt9n4mz2dn6enBd+Nq+yNX+B1lxdnDVPalxoy2k4lq7QmisZX8+21bwq1YzMOT6v1wrV3jBadoTfO+uqqvNY3b2LO+vo8h7qlVdsTuHJBAEEwiBAAxKGKruR48Qvbe1/EhvS3bBkjGAJFBdLBflSUcHaRiQ/T3Ial1hUikSkvHW8MPGbp0qtbpecN4g7/3Eaj7Y2aVXr2qdO8XCH9a8DKz+hups/VaRziDuwDfyYbJZfrtMr+2i7gkpZbDIP1i8UokUAAUeABoR1kLzALQ8kdOcjfBuZvBhHIoBAOgKdTvhKhXvPSudUo89x3mJ+akVv7Vlcz9OtjK40ySFgvAANiPEldjHBllbp4NPiGs+7wFxUZSgEEPi2QMFGK1R97SjZPFb5vywOxTHlPXRyRS8VWlEWDAIIIBB0ARqQoFfQ6/idl5bteXTC+M2vXrsyHwIISNHyNtVc+5ki9dx65awHp/H4cVG1zq/sp+55RSwRBBBAwBQBGhBTKullHnc9ktDND3ArlpfmzIVAGAQqjpmm4v1nhiHVDnPcNL9M53fqp60KKjo8lgMQQACBgAnQgASsYL4IN57QmqsgU6bRhPiiIASBgAECBRsvV/VVnzk7Ew3IJv0UusQKdVJFb+1TUi9rzTUQPggggIBxAjQgxpXUo4SmzbR14CmJNe8i4IMAAghkIrDm1qsbRylS3ZrJMIE+13mHx34lDTpxzT6PSKBzIXgEEECgAwEaEJZI+gJ3/imhWx4M97eV6etxJgIIfCNQefJUFe05O5QgzjWOfUsadEplL9VHffxWzFBWh6QRQCBLAjQgWYINxbDOW5hPvzihN9+lCQlFwUkSgSwIlG6/SGW/Gp+Fkf09pNN4bF9YrbMr+6hvfqm/gyU6BBBAwF0BGhB3PcM32qy50pHnJDRjNk1I+KpPxghkJhCtXq2a60Yp0ml1ZgMF7OyN88p0RmVvbV9UHbDICRcBBBBwRYAGxBXGkA8ydsLat6Q7V0T4IIAAAkkJWFLVReNVMHhRUoebcFDXWJHOrOyjnxbXmZAOOSCAAALpCtCApCvHed8VeORZW1fcmoAFAQQQSEqg/KCZKjlyWlLHBv2gAiui8zttpD1LOquYFwkGvZzEjwACmQvQgGRuyAjfCJx/RUIvvcFlEFYEAghsWCC/7wpVXT5GVmHcaCqn2Ti0rNuat5iXR2JG50pyCCCAQAoCNCApYHFoBwLLVkiHnRHXhC+hQgABBNYtYOUnVH/TaFldVhpNtG9pg86q6CPn8bp8EEAAAQS+I0ADwoJwV2DmHGmv4+JavsLdcRkNAQTMEOh0+hQV7j7XjGS+l4XzZKudimp1bqe+6hYrMjJHkkIAAQRcEKABcQGRIb4n8M8PbZ1yYUJxs++uoO4IIJCiQMmu81V+5qQUz/L/4VHL0sCCCp1f2U/9eKSu/wtGhAggkGsBGpBcV8DU+R960tYN9yTUThNiaonJC4GUBPK6r1TVZWMVqWhL6Ty/HzywoFInV/TStoWd/B4q8SGAAAJ+EaAB8UslTIvDeSTvhdcm9OxrbEo3rbbkg0CqApGSdlVfNk6x3stTPdW3x/fNK9GpFX20S3GNb2MkMAQQQMCnAjQgPi2MMWH94vSERoyhCTGmoCSCQBoC5UdNU8mBM9M403+n9Mkr0fEVPfWzojpZlrPrgw8CCCCAQIoCNCApgnF4igJLlkpHnJPQhCk0ISnScTgCRgiU7jpfZQbs+3Aaj1+UddP+JQ1y9nzwQQABBBBIW4AGJG06TkxaYPZ86RenxzV7XtKncCACCBggEOvcoprrRskqbQ9sNl1ihTquvKf2KKlTicW7PAJbSAJHAAE/CdCA+KkaJscyZZp0wMlxrWw2OUtyQwCBbwScfR+1N4xWpH5VIFGcx+geXNZFB5d2VSFvLw9kDQkaAQR8K0AD4tvSGBjYp2NtnfTrhJaYsw/VwCqREgLuCFT9aoIKtl/gzmAejlIZydPpFX20e0kdby/30J2pEEAgVAI0IKEqtw+S/fdwWydckJDzlCw+CCBgpkDFQTNVfOS0QCVXFy3QYWXddHBZVxVzxSNQtSNYBBAInAANSOBKZkDAr79j69dXJbSq1YBkSAEBBL4jkHU6SEMAABF4SURBVL/JMtVcOUa2FYxvGZwrHidU9NLeJZ254sFaRgABBLwRoAHxxplZvi/w9Ku2LrouAQwCCBgkEKtpVfWVYxSpbfF9Vp2ieTqxvJf2LW3giofvq0WACCBgmAANiGEFDVQ6ThNy5W0JNQdzj2qgrAkWgWwLRIrbVXv5OEV8/rLBmmi+jinvocNLu/Eej2wvCsZHAAEE1i1AA8LKyK3AX16ydcmNXAnJbRWYHYHMBSqO+0rF+87KfKAsjVAdzdfJ5b20d2mDCq1IlmZhWAQQQACBJARoQJJA4pAsCzhXQn5/U0JtwX1VQJaFGB4BfwuUHzRTJT7ddN4lVqSTK3ppn5LO/kYkOgQQQCA8AjQg4am1vzN9/vW1TQgb0/1dJ6JD4PsCBZsvUaeLxssqjPsKZ+O8Mh1W3lU/L+7Mm8t9VRmCQQABBJznlNg8EJWF4A+BN/5t64zf8Yhef1SDKBDoWCDqvOn86tGKVLR1fLAHR1iyNLiwQvuXdNHPiuvY4+GBOVMggAACaQjQgKSBxilZFHj7fVu/uYqXFWaRmKERcEUgWtqm6qvHKNq12ZXxMh3kJ0U1a95cvl1BlWKWlelwnI8AAgggkD0BGpDs2TJyugJTpkm/OCOupqXpjsB5CCCQTQGrIKHqX05U3rYLszlNh2MXWBFtX1SlI8p6aGBBRYfHcwACCCCAgC8EaEB8UQaC+IHA1K+lk3+T0LSZwXiZGSVEIEwCFUdNU/GBM3Oa8v6lDdqvpIsGFJTnNA4mRwABBBBIWYAGJGUyTvBMYF6jdNrFCY35gibEM3QmQqADgYp956j4uC9z4lRixbRPaWcdUdZNztOt+CCAAAIIBFKABiSQZQtR0HMbpd8PTegf79OEhKjspOpTgeLBi1V50Xjn8SWeRlgcieqk/7y1vDKS5+ncTIYAAggg4LoADYjrpAyYFYGb7kvonj97+z96spIIgyIQUIFofYtqbxwlq9i7F/b0yCvW0WXdtUdJvYqtaEDlCBsBBBBA4HsCNCAsieAIPPGSrctvTqjdX68bCA4gkSKQpkC0vE01t45UpNybx+06+zqOLOuh3Ypr04yY0xBAAAEEfCxAA+Lj4hDaOgTGTrB13K8SaloCDwIIeCEQKY6r9vKxivRentXpnAfn/qioSieX99YWBWVy3unBBwEEEEDASAEaECPLanhSzub0ky9M6POJ3JJleKlJL8cCkeJ2dTpvovIHLc5aJGWRmHYsqtFZlX1UEy3I2jwMjAACCCDgGwEaEN+UgkBSEmhukS65IaGX37Rl04ekZMfBCCQrUHXmFBXsOjfZw1M6rkusUAeUdNEBpV1UGWVjeUp4HIwAAggEW4AGJNj1I/qnXrHXPCWr3bt9saAjYL6AZav8yOkqcfldH1HL0oD8cu1b0kU/L6nnjeXmryQyRAABBNYlQAPCugi+wMQvpXMvT2jyVC6FBL+aZOAHgbK956js+KmuPW43Zln6eXFn7VvasKYBcRoRPggggAACoRWgAQlt6Q1LfPES6bq7EnrudZoQw0pLOh4LlOw8X+VnT3Jl1qponnYvrl/zKN3OsUJXxmQQBBBAAIHAC9CABL6EJPBfAWcvyJMv27rmzoSaVwGDAAKpChQPbFL5eRNklWR2T2OvvBIdWdZNuxTXqoIXB6ZaBo5HAAEETBegATG9wmHMb+Yc6bSLEvpiCldDwlh/ck5PIL/vCtVcMUZ2QXov2imwImueZrV3aWf9uLA6vSA4CwEEEEAgDAI0IGGochhzbGuX7nokofufsNXaGkYBckYgeYFY12ZVX/OZImlc+aiIxHRwaVftU9qgrrFC3t+RPDtHIoAAAmEVoAEJa+XDkvekr6RTfxvX17PDkjF5IpCaQLR+laovH6toTWqder+8Uh1X0VM7FdXIufrBBwEEEEAAgSQFaECShOKwAAs4G9QfejKhB/9iqz29u0sCnD2hI7B+gVhFm2pvGC1VtyTF5Lyd3Hl87mFl3dQ/r5SnWSWlxkEIIIAAAt8ToAFhSYRHYPJX9pq9IdNmhidnMkVgfQLRijZVXzdK0dqOr3w4Lw10mg7nUbq8NJA1hQACCCCQoQANSIaAnB4wgZZW6U9PJ3TPY7ZWrAxY8ISLgEsC0bI21Vw5VpFu6/8hKLKi2qKgXCeW99LgwkqXZmYYBBBAAAEEnNdM2c7DS/kgEC6BWXOlC69N6MORLP9wVZ5snSsftdeMllW/7tuueuYVa/eiOh1Y2kV1sQLAEEAAAQQQcFuABsRtUcYLlsA/P7R1+a0JzWCTerAKR7RpCcSqW1X9my8U6bv8O+eXWDFtXVihPUo666dFtbJ4U3lavpyEAAIIIJCUAA1IUkwcZLTAqhbplgcTevpVW8tXGJ0qyYVYwHnEbu2lnyuy0bL/KtTHCrRPSWftXdKgbrGiEOuQOgIIIICAhwI0IB5iM5XPBZyrIHf8KaHnX+e2LJ+XivBSFHCufHQ6e7JiWzSp2Ipq+6Iq7VZUr91LalMcicMRQAABBBDIWIAGJGNCBjBOYNhIW7f/0dbHo2lEjCtuCBOKFLer9g+fq8fGbWv2dfykqEa98opDKEHKCCCAAAI+EaAB8UkhCMNnAomE9O5Htm64N6GJX/osOMJBIEmBypqEDrl0kfYbXKhBBTzJKkk2DkMAAQQQyK4ADUh2fRk96AKr26SX37R1958T+pr3hwS9nKGJf5stpSMPiOinO1qqLA9N2iSKAAIIIBAMARqQYNSJKHMtsHKV9PIbCd39qK0583MdDfMj8EOBhlrpoL0t7b2rpQGbWBAhgAACCCDgVwEaEL9Whrj8KbCy+T9XRB5N0Ij4s0ShiioSkfbd3dLBe1v60SBLxYWhSp9kEUAAAQSCKUADEsy6EXWuBRK29MLrCd31iK2veYdIrssRuvk37y8deWBEe+0i1VRxtSN0C4CEEUAAgWAL0IAEu35E7weBf3xg6/4nbI0cY8vmwVl+KImRMfTuLu29m6WjDoyoW4ORKZIUAggggEA4BGhAwlFnsvRC4NOxth56yl7z9KzmVV7MyBymCxQXSfs4t1jtZWmbLS3l55meMfkhgAACCIRAgAYkBEUmRY8FZs6V7n8soXeG2ZrLhnWP9YM/XWmJ1mwiP3w/Swf8zJKzz4MPAggggAACBgnQgBhUTFLxmUBrq/TiGwk99YqtsRPE7Vk+q4/fwhm4ubTfTy3tubOl7l3Y1+G3+hAPAggggIBrAjQgrlEyEAIbEBg/WXrtnYReeoOrIiyUtQKxmLTFxtJOP7L0i30i6tEVGQQQQAABBEIhQAMSijKTpG8EnL0h73xo669v2Xr7A1vOG9f5hEugtkY65sCIdthWGri5pbxYuPInWwQQQACB0AvQgIR+CQCQM4HGRdJ7H9l67vWERo6V2ttzFgoTZ1mgV/e17+tw3tXhXPHggwACCCCAQIgFaEBCXHxS94mA8+jer2bYev0d6e33nf0iPMvXJ6XJKIxO5dLhB1jacTtLWw+wVMRLAjPy5GQEEEAAAWMEaECMKSWJGCMwaao06nNbL7+Z0IjPxG1aAamsZUndu0j772Fpu4GWdtjGWrPPgw8CCCCAAAIIfEeABoQFgYCfBebMt+VsYP/r22vfL7JkmZ+jDWdsAzdz3tUR0W47SD27sacjnKuArBFAAAEEUhCgAUkBi0MRyKmAs2F94lRb73xg61/DbH0+SWpdndOQQje5c5XDeQu5s49jr10sbb2VpWJurQrdOiBhBBBAAIGMBGhAMuLjZARyKNDWJn36ua33P5ZGjLE1ZZqtpiU5DMjAqZ03kdfVSNtttXYD+U93tFRZbmCipIQAAggggIB3AjQg3lkzEwLZFXBefDh2oq2PRq39z5gvpOUrszunaaM7VzP69NSaTeM7/8jSZhtLDXU8tcq0OpMPAggggEBOBWhAcsrP5AhkWWBuozTmC1ufjlu7l2TWXFvzGrl1KxaVaqqkqkppy80sbdHP0o+3lfr0sOTcZsUHAQQQQAABBLImQAOSNVoGRsCnAgsW2Zr69dqrJc4Tt+Y12po5R5o9T2qP+zToDMKqLJP69ZG6dJa6dbY0YBNLm2wkdamzVFCQwcCcigACCCCAAALpCNCApKPGOQiYKNDcIk2dbmv2XGnKdFtNS6XFS9deNXH+b84TuFa1SM57S/zycfZolJZI1ZVrH4FbU2WpU4XUUGupfx+pf9+1/50PAggggAACCPhGgAbEN6UgEAR8LOBcGWlulpYst7VgobSwSVq8RFrZLLW0Siua126AX7lKWrbcucXLXvP/trWvbVqcz/IV625eCvK15kqEc+dTWank/PeSYqmszFJpsdY0EOWl1n//707T4dw+1blWKi9b+2+85M/Hi4fQEEAAAQQQ+K4ADQgrAgEEEEAAAQQQQAABBDwToAHxjJqJEEAAAQQQQAABBBBAgAaENYAAAggggAACCCCAAAKeCdCAeEbNRAgggAACCCCAAAIIIEADwhpAAAEEEEAAAQQQQAABzwRoQDyjZiIEEEAAAQQQQAABBBCgAWENIIAAAggggAACCCCAgGcCNCCeUTMRAggggAACCCCAAAII0ICwBhBAAAEEEEAAAQQQQMAzARoQz6iZCAEEEEAAAQQQQAABBGhAWAMIIIAAAggggAACCCDgmQANiGfUTIQAAggggAACCCCAAAI0IKwBBBBAAAEEEEAAAQQQ8EyABsQzaiZCAAEEEEAAAQQQQAABGhDWAAIIIIAAAggggAACCHgmQAPiGTUTIYAAAggggAACCCCAAA0IawABBBBAAAEEEEAAAQQ8E6AB8YyaiRBAAAEEEEAAAQQQQIAGhDWAAAIIIIAAAggggAACngnQgHhGzUQIIIAAAggggAACCCBAA8IaQAABBBBAAAEEEEAAAc8EaEA8o2YiBBBAAAEEEEAAAQQQoAFhDSCAAAIIIIAAAggggIBnAjQgnlEzEQIIIIAAAggggAACCNCAsAYQQAABBBBAAAEEEEDAMwEaEM+omQgBBBBAAAEEEEAAAQRoQFgDCCCAAAIIIIAAAggg4JkADYhn1EyEAAIIIIAAAggggAACNCCsAQQQQAABBBBAAAEEEPBMgAbEM2omQgABBBBAAAEEEEAAARoQ1gACCCCAAAIIIIAAAgh4JkAD4hk1EyGAAAIIIIAAAggggAANCGsAAQQQQAABBBBAAAEEPBOgAfGMmokQQAABBBBAAAEEEECABoQ1gAACCCCAAAIIIIAAAp4J0IB4Rs1ECCCAAAIIIIAAAgggQAPCGkAAAQQQQAABBBBAAAHPBGhAPKNmIgQQQAABBBBAAAEEEKABYQ0ggAACCCCAAAIIIICAZwI0IJ5RMxECCCCAAAIIIIAAAgjQgLAGEEAAAQQQQAABBBBAwDMBGhDPqJkIAQQQQAABBBBAAAEEaEBYAwgggAACCCCAAAIIIOCZAA2IZ9RMhAACCCCAAAIIIIAAAjQgrAEEEEAAAQQQQAABBBDwTIAGxDNqJkIAAQQQQAABBBBAAAEaENYAAggggAACCCCAAAIIeCZAA+IZNRMhgAACCCCAAAIIIIAADQhrAAEEEEAAAQQQQAABBDwToAHxjJqJEEAAAQQQQAABBBBAgAaENYAAAggggAACCCCAAAKeCdCAeEbNRAgggAACCCCAAAIIIEADwhpAAAEEEEAAAQQQQAABzwRoQDyjZiIEEEAAAQQQQAABBBCgAWENIIAAAggggAACCCCAgGcCNCCeUTMRAggggAACCCCAAAII0ICwBhBAAAEEEEAAAQQQQMAzARoQz6iZCAEEEEAAAQQQQAABBGhAWAMIIIAAAggggAACCCDgmQANiGfUTIQAAggggAACCCCAAAI0IKwBBBBAAAEEEEAAAQQQ8EyABsQzaiZCAAEEEEAAAQQQQAABGhDWAAIIIIAAAggggAACCHgmsKYBSXg2HRMhgAACCCCAAAIIIIBAmAXs/wNn2BCMiMaLjgAAAABJRU5ErkJggg==\\\" ></image>\\n</svg>\\n\"\n  },\n  {\n    \"provider\": \"model_tencent_provider\",\n    \"name\": \"腾讯混元\",\n    \"icon\": \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\" standalone=\\\"no\\\"?>\\n<!DOCTYPE svg PUBLIC \\\"-//W3C//DTD SVG 1.1//EN\\\" \\\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\\\">\\n<svg version=\\\"1.1\\\" id=\\\"Layer_1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" x=\\\"0px\\\" y=\\\"0px\\\" width=\\\"100%\\\" height=\\\"100%\\\" viewBox=\\\"0 0 240 240\\\" enable-background=\\\"new 0 0 240 240\\\" xml:space=\\\"preserve\\\">  <image id=\\\"image0\\\" width=\\\"240\\\" height=\\\"240\\\" x=\\\"0\\\" y=\\\"0\\\"\\n    href=\\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPAAAADwCAYAAAA+VemSAAAKqmlDQ1BJQ0MgUHJvZmlsZQAASImVlwdQk9kWx+/3pYeElhDphN6ktwBSQmgBFKSDjZAECCXEQACxobK4gmtBRQQruiCi4FoAWWyIYmERUMG+QRYBZV0s2FB5HzAEd9+89+admTP3l5Nz//fcO/fOnA8AsiJHJEqFFQFIE2aKQ/286NExsXTcEIAAHhCALgAcboaIGRISBBCbGf9u73uQbMTuWExq/fv//9WUePwMLgBQCMLxvAxuGsJnEH/BFYkzAUAdROL62ZmiSW5FmCpGCkT4/iQnTvPIJMdPMRpM5YSHshCmAoAncTjiRABIdCROz+ImIjokT4SthTyBEGERwu5paek8hE8ibILkIDHSpD4j/judxL9pxss0OZxEGU/vZcrw3oIMUSpnxf95HP/b0lIlM2sYIU5KEvuHIqMycmb3U9IDZSyMXxA8wwLeVP4UJ0n8I2aYm8GKnWEexztQNjd1QdAMJwh82TKdTHb4DPMzfMJmWJweKlsrQcxizjBHPLuuJCVCFk/is2X6uUnhUTOcJYhcMMMZKWGBszksWVwsCZXVzxf6ec2u6yvbe1rGd/sVsGVzM5PC/WV758zWzxcyZzUzomW18fjePrM5EbJ8UaaXbC1Raogsn5/qJ4tnZIXJ5mYiF3J2bojsDJM5ASEzDFggHaQiLgZ0EIT88gYgk5+TObkRVrpohViQmJRJZyIvjE9nC7mWc+m21rb2AEy+1+nr8JY29Q4h2s3Z2AYiAG7CiYmJ5tlY4GcAziAvmyidjRl3AyCP3Pvr27kScdZ0bOotYQARKAAqUAPaQB+YAAtgCxyBK/AEPiAABINwEAOWAi5IAmlI5dlgFVgHCkAR2AZ2gTJwABwGR8EJcAo0gGZwGVwDt0AnuAceASkYAC/BKHgPxiEIwkFkiAKpQTqQIWQO2UIMyB3ygYKgUCgGioMSISEkgVZBG6AiqBgqgw5B1dAv0DnoMnQD6oIeQH3QMPQG+gyjYBJMhbVgI9gKZsBMOBAOh5fAifByOBfOh7fApXAFfByuhy/Dt+B7sBR+CY+hAEoORUPpoixQDBQLFYyKRSWgxKg1qEJUCaoCVYtqQrWh7qCkqBHUJzQWTUHT0RZoV7Q/OgLNRS9Hr0FvRpehj6Lr0a3oO+g+9Cj6G4aM0cSYY1wwbEw0JhGTjSnAlGAqMWcxVzH3MAOY91gsloY1xjph/bEx2GTsSuxm7D5sHfYStgvbjx3D4XBqOHOcGy4Yx8Fl4gpwe3DHcRdx3bgB3Ee8HF4Hb4v3xcfihfj1+BL8MfwFfDd+ED9OUCQYElwIwQQeYQVhK+EIoYlwmzBAGCcqEY2JbsRwYjJxHbGUWEu8SnxMfCsnJ6cn5yy3UE4glydXKndS7rpcn9wnkjLJjMQiLSZJSFtIVaRLpAekt2Qy2YjsSY4lZ5K3kKvJV8hPyR/lKfKW8mx5nvxa+XL5evlu+VcKBAVDBabCUoVchRKF0wq3FUYUCYpGiixFjuIaxXLFc4q9imNKFCUbpWClNKXNSseUbigNKeOUjZR9lHnK+cqHla8o91NQFH0Ki8KlbKAcoVylDFCxVGMqm5pMLaKeoHZQR1WUVexVIlVyVMpVzqtIaSiaEY1NS6VtpZ2i9dA+z9Gaw5zDn7NpTu2c7jkfVDVUPVX5qoWqdar3VD+r0dV81FLUtqs1qD1RR6ubqS9Uz1bfr35VfUSDquGqwdUo1Dil8VAT1jTTDNVcqXlYs11zTEtby09LpLVH64rWiDZN21M7WXun9gXtYR2KjruOQGenzkWdF3QVOpOeSi+lt9JHdTV1/XUluod0O3TH9Yz1IvTW69XpPdEn6jP0E/R36rfojxroGMw3WGVQY/DQkGDIMEwy3G3YZvjByNgoymijUYPRkLGqMds417jG+LEJ2cTDZLlJhcldU6wpwzTFdJ9ppxls5mCWZFZudtscNnc0F5jvM++ai5nrPFc4t2JurwXJgmmRZVFj0WdJswyyXG/ZYPnKysAq1mq7VZvVN2sH61TrI9aPbJRtAmzW2zTZvLE1s+XaltvetSPb+dqttWu0e21vbs+3329/34HiMN9ho0OLw1dHJ0exY63jsJOBU5zTXqdeBpURwtjMuO6McfZyXuvc7PzJxdEl0+WUy1+uFq4prsdch+YZz+PPOzKv303PjeN2yE3qTnePcz/oLvXQ9eB4VHg889T35HlWeg4yTZnJzOPMV17WXmKvs14fWC6s1axL3ihvP+9C7w4fZZ8InzKfp756vom+Nb6jfg5+K/0u+WP8A/23+/eytdhcdjV7NMApYHVAayApMCywLPBZkFmQOKhpPjw/YP6O+Y8XGC4QLmgIBsHs4B3BT0KMQ5aH/LoQuzBkYfnC56E2oatC28IoYcvCjoW9D/cK3xr+KMIkQhLREqkQuTiyOvJDlHdUcZQ02ip6dfStGPUYQUxjLC42MrYydmyRz6JdiwYWOywuWNyzxHhJzpIbS9WXpi49v0xhGWfZ6ThMXFTcsbgvnGBOBWcsnh2/N36Uy+Lu5r7kefJ28ob5bvxi/mCCW0JxwlCiW+KOxOEkj6SSpBEBS1AmeJ3sn3wg+UNKcEpVykRqVGpdGj4tLu2cUFmYImxN107PSe8SmYsKRNLlLst3LR8VB4orM6CMJRmNmVSkMWqXmEh+kPRluWeVZ33Mjsw+naOUI8xpX2G2YtOKwVzf3J9XoldyV7as0l21blXfaubqQ2ugNfFrWtbqr81fO5Dnl3d0HXFdyrrf1luvL17/bkPUhqZ8rfy8/P4f/H6oKZAvEBf0bnTdeOBH9I+CHzs22W3as+lbIa/wZpF1UUnRl83czTd/svmp9KeJLQlbOrY6bt2/DbtNuK1nu8f2o8VKxbnF/Tvm76jfSd9ZuPPdrmW7bpTYlxzYTdwt2S0tDSpt3GOwZ9ueL2VJZffKvcrr9mru3bT3wz7evu79nvtrD2gdKDrw+aDg4P1DfofqK4wqSg5jD2cdfn4k8kjbz4yfqyvVK4sqv1YJq6RHQ4+2VjtVVx/TPLa1Bq6R1AwfX3y884T3icZai9pDdbS6opPgpOTki1/ifuk5FXiq5TTjdO0ZwzN7z1LOFtZD9SvqRxuSGqSNMY1d5wLOtTS5Np391fLXqmbd5vLzKue3XiBeyL8wcTH34tgl0aWRy4mX+1uWtTy6En3lbuvC1o6rgVevX/O9dqWN2Xbxutv15hsuN87dZNxsuOV4q77dof3sbw6/ne1w7Ki/7XS7sdO5s6lrXteFbo/uy3e871y7y757696Ce109ET33exf3Su/z7g89SH3w+mHWw/FHeY8xjwufKD4pear5tOJ309/rpI7S833efe3Pwp496uf2v/wj448vA/nPyc9LBnUGq4dsh5qHfYc7Xyx6MfBS9HJ8pOBPpT/3vjJ5deYvz7/aR6NHB16LX0+82fxW7W3VO/t3LWMhY0/fp70f/1D4Ue3j0U+MT22foz4Pjmd/wX0p/Wr6telb4LfHE2kTEyKOmDPVCqAQhxMSAHhTBQA5BgBKJ9I/LJrup6cMmv4GmCLwn3i6554yRwBqkWGyLWJdAuAk4kZ5iDbiky1RuCeA7exkPtP7TvXpk4ZFvlgOek/Sgx1L8sA/bLqH/67uf45gUtUe/HP8F+jXBvflaC58AAAAOGVYSWZNTQAqAAAACAABh2kABAAAAAEAAAAaAAAAAAACoAIABAAAAAEAAADwoAMABAAAAAEAAADwAAAAAPYgCPEAAEAASURBVHgB7L17rKbXdd73fuecuXFmODOkLFG2JR5aVpRYikVJcWvXaURfg8ZJxRQtEOSPWED/aAqkkWqgQFs0pdKiTZECVl20aJMAjV0UaJC0thwnlmXaotJclKa2qESyFdtSRjcnpkSJFMnhXM/5+vyeZ639vt85Q4qXIcURuc987957XZ619tprfe/7vd9lVtMr7SUfgZ87/692d65d212vVrvb21tn9vf27tra3jkzTeuz62l1Zr2edleraaWFrPXYXa8l6ekk1nq1vdpai7na2Zqmy+utr6y2VutTW1uXvrq389Bqe2u6NB397EN7209+7cr+lzX/zWNb249+38mrn/3Drzry2dVq9ZWXfIBexg6y6a+0l0AEfu78+bM7087dW9P2W9f7+7sqsl3V3N0qxl3VIoVJDaoiVbIqT5rHZsIWV4WqZiaHKKHc/FVUmmUd06azO1urS9POdG3ryPTV9ZHpd69sTf/i8vZ0Znv6ivR/68j26jPntvc/+fC1nY/fuZo+/h9+5+pL2HylfWMjkEz4xvrwsrPuYt3bvkdn0Tun/fU969X6btXhrjajC+xwTCja1OHgqZBHwap6w1V9W2DBMyH6ltEhMhIMqp8YdNIWhp4Adra2plcd217fsrOz+tr66PSbF7enz1zZnh65lmcS8I9vTZ+9tL/6+InV1X/66UtHP/J3Hp0+Pv3A6lHbfuXwokVgbOSLZvFlaIiCPToduXc97b9VJXLvtNq6k0vbKh72IIXlk+tclBSoeDlFqtSkMBe4dy58URFLZEtmWdzhCMY2rQOQAcVDe67MjEQO9vGt1XT2yNZ05y06K187Mv36hZ0U896WnWm7j1+bJl2Zf/yhy+uP/9PHt3/+B89e+vjfeNuJz8apV44vVARq118o+Jcv7i+cP3/Patp+l6rknYrC2/QYxViXwC4cNsD1V6Fy4S0Kap6jrlZFffBsDD1YemLQeN+X0tleHVOkQeCpQOp+tqDjn3UoavFyVjeY7Uk6Lt5xbHv1mmM709mj29OvPbkz/dqFI7rMViEXbhcz0391eZoevbr38a9eXf+9Tz555APTj6w+MsReGdywCGSHbxjcyxfIZ9k9nV2PHHnntL9/ryJxVg9yu2I8h7qJXcFdwKqiEUDT+rzYl8VwF4Ua4VmnlYMveupUTlDbnHB1VhWw+FaaixWeOGXHdHTK0SEnA5BepSJ+48mjk+p5+pd6rfwPVMj/n87MaYf9+e0np+mrl9ePfv7S+iOnd6YP/NTrv/Dzf/Kuu1653K6IPZ/ucLSfD9rLTDeXxtO9OtP+uMribi1fRVuXtZ39jkkuWzVUvF1DFFNOaxCreLqPCqKUmsrHmNbLfrm0AkBRduFFT0eqrDorSBSzIg1H4INczZaiOGiDJ82FKFg5e+tsvNrVpTWF/Mjearr/saOjkEFZPu8A9vjeNP32hWn63Uvr6dJ69YHTW/sf+Pw7d36mDb3SP/sIHNqtZw/x8tPg8nja37p3e2v140ptvY3jq9cKRBfwobh0OVZ5+Vq1CpmC0Hs8nARdG+jmUnYUunZK/0ax+mwaoTLEVrrgKVUKLlihogvXzUzPUpeubD8R5KxrmyVLN8trYuHuV2u9Rl7x+viO4zkDc6Pr/seP6vI689jE+mb7LRXyJ5/Q21r760cv7G19YH81/cwrl9mbMXoms8ORfSZaL0MZzrbHp6337E/Te5XgvAer2NXrxVSOopLXnxQhpFF8i3h1/kOqitZoLvoFTQh1pymigMrgAuzAkKI2u17/er58cuFMDBauMdDDlC5ez6vydUeKJbRMm03Jg6H1S69dfM2x7Ymz8XG9r0z75MWd6Re+dkR3rufXyHkiMHsceK3864/prKxe/PPrrekvTlenvzf9W6vPDqFXBk8ZAe/fU3JfYUycbbdX2+9Rur+LpM17rTpfkG6+lKUiug4oaILm4qbXg7SlFFwY1403OqmtsKMFTp4g4BlXgxoCFuyMZkWoLsj4BMpGo+jSXO0Hi3zGLxx1iJvOOpnZX9TzJAYb/jGdjd90+qjuWm+j4sZl9f2PHfG411XhKol0Xcj/UoWMNcn+9J8698TP/I3vOf2RSLxyvF4EvDnXY7zcaSrce7en1XuUmvcoFjnjVFBIeg0VOxdlUdN1QZRM6syyVc2b0sKhCGhgzgUBpQq7uhRzrKb2rIWcHqqs+Jgih1VPJmhIwmdLxmnRYWxSakpH6N28hhStyXJk6CNDNXOa1mg4jtG7dIOLy+puX9Vl9V/58nG9Ts7ZGXrsbMBZnEL+yCN6vay3pZC5dWv94GPT1k9NP7z6GQu8ctiIwOEIbrBfXhNfJm9tvUfvwbxbK9/tdHUFEQrOM2okqZI7GZYaNmNZvJKJ8FyY1rVaygGMRZGZXYlNcVSSS9aFpC5JXwwL6JB//UQRRUSH/XYThes1ngxy6b+oqrFkaWC2lHMFAIrXV/GodSATu6J/+4kj6zecPLJhdHk2BrGNZF0bohN3rrm0TiHbbi6vXylkwj/aZtQG+eU1GIW7Xr9XKz+jNHTGEAUn2YjSfMZV0pqHTBI4BWuy67vOiAgA54SFsaV8Jc990jJ3PrhGkUSnC6d701xscy1TqRGPDcFeD3e2EEc25sHnqFbGajYbCrePCchy3SgylwQHO3G73i9+06mjkz6l2Yobr40HUYM2SKy6XdENh0/oRheFTIvM6vzE6+RXCtkxmaPl6cvv8MHPnb9POZ/CTfYkT1LEHquIHackaApEhWLiwcRDEFoKdu6vH9lRpNKJwVavRHZBgsXcNplw5t7nrClbMsZgs5CHrPVtOyBxt9Zj+uJgv8ccTHthE03WU1GLGYUwOC7q7UMEy1V9aWJ7tXrrmeMbRfxVveXkS2rd4BqtAhcHveDB4u2nv/PlPhvbJ6L1SiErQpuRGiH75h/83fOffrfev71PybebxNsMhWZz4iZlq8ZyZtX9muY755SxA8AJrdgiQKO+OtFbDpked0rWdsQaehRtlaimm3ZSkMiEzklv2CtsycQp4wQhtLimKoiJEsNZN2vFjw7E6LOYWS5iuFnrxg2s1KfBpum0vgZ1sIgv6j7g33zk6PQbultt6+V7OdBLqWlWwZm4z8YwysGX9aV1ItNRexn03FXeWa3uU2Hc09nLskk4F5QvA6sYUiTJkxyrjBKoJYlAel6FCbbSWJC5Yx2N+dgFvNmPVB77QqXFjWXBQ2NON/uSypkLzDJi27cUK+tEjOcFDcXBabV6dsqEGIjv9ZgHebhkGeIVrWa0IylkMW2oubxP/KZTx6y7PPztR49O/+AJinj2xTrDL6Rn27wm/jsPr6cndBYnNt30Ca/zbzl27d/5R99/9ONNezn0c2S+yVf7wfPnd5Uj79fLqnuTtS6xqg8Vg1+2dRCcnIqNcxhi4lTSJH2Kw2dd5/4il8wrHcil65GLTqPo11k7xQhWROVnFecoWl8vyzwFa1P7voSOoKjWHD5ogKBak5hc1zZCc4t95ih6RjGHoEGoBgv/UPxS2NaPulCsJ1Xd2JreoNfEBxtvM/Hw4kDEchsp4TjRmqvpY5yNH4+sVcxyHH56f0+vkV8m7yM7yB2Wb9Y+r3PX79X2ntGWV2Ipr8i2ygxvvetBtWJyF1SSRLEpSZeLLw9TI0QthUYhSozsU7FQc3PyI2WkyDDlDGKZknWFmYFjqc2N/QEPnUKK6Max7UJc+hKd3ECDN/vIDGM4o1611us2Z/DG4o0LXsUjjjoUXh+siiMDxCrMTKY36C0mCvlg+xWK+PEjAbUxix8U89wh0ChvOa31EU3JeiOjc2pneuTkau+nfu8HjvzF6wJ8ExGfOkrfBIvMhzCm92t371byk0tODXWabC4drpghJhPnCHgOa8FIwiOzINqCqzbF5ieDEJ3iGKFq81U8kTJ1YeKTHBjjYQ986HQoaJgnBsbiLO1bJnLo9DSFzcz46tpriivyC/vNb3XWCJh8l7BhrQRex1FUL0Z4iW8rx4loR/gdZ09Mp/h5kAPtVx7bcRHbvA2BeUBoMYXFpfT9+smBh69qIoLF+VhqFM+v91Y/+M18Nh5bvIjLTT/kbaFjW9N9SqX3OmVSuJzWlGNJVJKkm5M69FERzaNfnkFM76yqpCalIdFZ3EcVhjCdUFaCP8tJ3g5wiDi8SM8FikwjxN+lr7blArTbjWS4Mll53LP0+KFmufidJ4G2P0vHpud+0pBKn/6wC5uj+lo/cw/hEHB0O/yMaXzHmCJevr0UzjT9wqNH9O0mPkedYDYdIxWeJo2ehfzjR/WWkz5fTbNcTGN8+padvff/lXNf/K/+5Nu++b4BtdihLP5mP/om1db6r2vD7+xidaayodrZHj9VsjogqSWGFlcyqadIaNWFZX5jOZOXIpZNRlsV7cWTyDwm1VNEB30sOmQN1VUBLXRZlVjX89FW8RFvK6UDYk4dglUL61WLN2zgWwo1/UIv3zsOIR8z7TgVTZ2DRAFCkrd4kg96HO1gRriO//tXdHf60vxxTCmUbotdV813qPt1cSR7rZLfms5/34lvvptch69jOkY3Yf+Lnzv//p2t9QPa7d0uXtKytpsSSHZVNUAn8VN4Ti+pqs+QCLRq9ZAWTFEpquDM0kiR/JbVMWOoabHHmFHPjEt6u6VSnYDtA9UrKNjyMmvARhVm+xp1C8VXu8cB8NLDp/jFAhrXQhK0DVtBCJeKMnqR4GfeTO64M/aDuEgX5QpZYm/h9fTFi1f5wj/cQ+3fPXd1um07z1TBZsz2RTQuGXn2TKx33DpNP3rbajpml+FHx/L767s++uTOx7buX993yOBNTKiQ3MQrkOvcYdbF6s9pg9+qbMya2G3SR72T1ikwMs5nkkoBJ3KSuGRJPOOM8CCqfAyfaDk5y5ayPIla8GJbH3mS18pRgtX/jAGZPMM9gaDhhs7BFpuSnBElMtDHckWUjLMYfg0wwZD4hJQ1apaKAMn2y7ZjNHxAxoWe93dBG6149mTETmB2wy4Iu1BLCZt8FfHtZ65/Kc2HPf7Hh47xvWFrHND2Wu1CjM5h0Pwrej18/1fX02N6yylt9jUbtfqmeW1805+Bf+lz59+jXHhQ+X/32KZRBtn7OdkqjVIwTlyKrBOZCqK5kEhKNdjumCPoHk4ymLkYwKhZxLzyBUupCghIxoR+Pyr4ogKMaiOUQUCBSrMdm2euFYVXaFYRrafIkPkpVkSX4iwjS0EOtyjyVAq+lKJ5toOyZNpAFebCWIa2gY8Sd5xAEMvrY1wNfWxe3NufvniJu0+HG2fgP3P7FTFmbONbVD7K49iBIBnWXsG9XTe5f+xVK32AJOvOFhaOlKR719aR9ce+7YErfALvpm5zTG6yZeRG1f59WsB7OiE6wTrZWJy3jeSjkVrZR3ckGQxex1XCkdgWzcGpShZAbrhgLaQC1gRnUmQaqqptgWlO7Abb9rEhTVywq6XfY8kAIV8okj7rs4SYaw82++KxhnrvGAw9GmCIjxgUv2MFvQu8aVaiimjUEzLyy8DQe2w2QlkaC4v/0KZpR3eM/7Vz1z8Lw+em1j+8wOvheY0VFlMyDm/eOs31jw99/F196IO+W+vOeFvv/79f9dmb9gbXHJVe4U3Qc8k8rfYf0Cbt4m4nz2HXF8vrqlgkb+slpxYZsgCqElaOprqSoAKZM2GWPkCzLNw24N7T1lEu476F5qSvLzyUSZiN7KJrv61VhcM4TWCcclRADLrZX1P0pKUeu7WmdrP6PFuhypME+vGRsaeeN668D0wTYlKCMeLV4Q0XFPbVDElHELXdW/j64eEPeDTkX334qH88L/NZr/nxqmcsXq2IvEf8d7+875/zicRwUNPCWk/n9/e2bsq3m266S+hfOP8796y29j+mJ+47syFkR51hB8EDbSH/2Ekeo7FrUZCeL2WpTTUOJFn30EQk+Tykt6Cm7qEuoF2IVraesIKHkHDVRd+FEb3YtxGZLdvtno1Izmc2qbIU2yj3bV5rwGfGeCV+uTs8FB37s898iqukBd2h6DNy+xq7BvUiiTMeLYCMIuiqmWHbviaMKLEeeVZuxNWoxqff1Q2ta/u1BBw70LipdbyXWL4sRcBqPBvxerPm07oU/7FvyeV0dOyP5eO25qtJl9T7H/59D1zWp/RurnZTFfCHPvc79+m9wweUSue8j2xUbZ3TI7F3Qg96JXcleXZPW+YsR8mfVRZhAOQyUAxISTrSr3DQiy4ZJfv89Ey1Gow5OpoYBlk3W6ZYPXN6M5IJBExXUeGYKJCiBz8OiRqSfWq/uk9BUjwlFFiqvAgMsUhzkRkQ2sCfbVqoDvJNK/edZp/FHQbslM3RQxsmsOJHjrVEtHrNk65w17/7FK+Fsc3r4R+59eAd68arZUkOm0u79kM+n9YV+B/Xa+Jbx2tiUKMfHY3X67s+fW37Z7fu37sP7s3SbpoC/qXPfUafY169rwOrBEi+kfjKaObm1dhz9qWTNEzJWK4z19uoXZdYNlRiDMiE9AaHHZWuvBbr5O25NQ0gCk8Cxi6CishVUFMJ4I9mNsWLSA+tM6gpPLDMF52F8yiaMRgXjnhdrBgSWYfwe02QKKBcTuNk20QWLvg04pcxRYmcFmRfIqdJBPs4bIteMQQKjEhuygtfEPsr3lZ6urPw95+6Nn3rEWVA26k+czBnXNtbyOHS6R2diVXER/k6tmQtY+XF3G6u37dz/95PLtRf0sOs/yXsor9sv733c9qDe+Qm6dK55S1zQbAKMZJs/P9A9Q0gJ5eYSaSslf1S48wpNtk4IxYvAoaHHXtJXBJYeiS1PBIPlZmFC6FZu6NrE9LrJxmZjcuiiDe+mmiwXqShxc8TFazN1uDYR9ZYJrY/yMffMqepSGUlaFl+fJuZwSZAUamunAgmMZj/L6YDsAG3uRoe7IgqERn01fSdJ49N33adz0m3CD8i/9ce5nPU8Q/6rN9S9DOf2VKGz0/zbaaZWGtF0C3z0/opn0e3dn5weon/dzEv6TMwN6tObO3pLaLpnRvRJfxOWgY68zgL2TaKi+KtLSRJ8q/Uw+gEjF4KJQLaWIrdCKEAwZxkrmEVBZfapAaFk7MUIlDcFhiNh114LjDrrtZVvMZvOesLU9hGab3opvi9NHHBwgecvI7J9g0gwVi2xOIpONhp3X5qARMFVuQOC/K/fGmMxmweYG4w9K98iy05SBB5qBGtwFhW5IevXP8tpSBO03cc25++6ziX0uCV/xqVMy02eE2A3zKv1Tca7zmHb9I3MVh21XKZP76/etvW3t7HpgfWu43zUuxfsgXsO81bew8oaHcSOCeDAk7MFWLFm1xKQTCniOBoa1xXHEywfAoBHJp4wLh5oFzqhCgy3Ug1DJoge9iJLQmQ0AgmESU145TtOGVt6fnmkf1r+0wWELGUxJeS4GBiL0IeY1BqdiqHDZ+kpaXDsH/ls4PhGOEMtYPIwJATdglDInocuUDhA/NeqoMrMQOAlZh4igGaae1D2SoNFi0bWYOx0eSTWZf39YXPp2k/drYLGCH7ZGnHZ0Ovfek+q0LujbdM09tP4wFxIE5RbOdMg6fXxdt7ex9+KRfxS7KAf/n8b9+9UvEqkHdqkxTXKhrthbOzNipJU8EnibQXFIT0vGs9dlGXzqJbyDjfG5XtlCpoHtGT2H6/GBpks1IQJCpJKLuNozl+S6xQDGcl0eG6VQJjrhIaHcQ81TGCRpIUYBp73QzAig/opJF3HsWn5qtvCfVdcDFjQkwZz95bGiivY9ra8otHbIfDkX0pPkFPazsUR1wRRYbMh1n7yZUS64agh2Tpf+/S4k1bzQ82bmj9sG9oATXbyjh46IB1mG+iee+4Vcl1HLvBocdFz73GwnqJF/FLroAp3vXW1gMK/i4JkpDX0YVEpNmaDnwyqBMVBXGGXtNNtc5A7JwDSK12UKpW7p1FJ35QxIJrcdm1H953qjco0uvEjU2TEbLi4GnGWNaylOGxtUSLeJjGSDBKTxQETAMHufyBi2/CVSu3GC6brMWgdDyInFXMKW462amXJtgZNABbjrjw8JpKIrJsV22ku9iwMxglLpaP7lN9Pnrp/L9xcm86kecTkRd4CBVkDQMdso7I9oNL6a3plCpguNC8SoXhnop4Z+/ax4788pW7wX0ptZdUAX/4i//irevtrQ/ref3sRpAUyWyTk8QTXju61Nh+bxpJW2LqyAk9rNZJl9IcG6h8M1vZn7OBbSYRhST4yjvokjVkG8OExnGLnbZIzogl28lcPpQ7semswS/7ht+SStEhrqX4JtvGOmQuhdq+US/QkCceOIEr2OdBPDBnx2GqLWxYNlSZj7amsUk/N9ZufK/UPgfWaraBfYfB9r2cWT+0gVm+2ihxjXHprHUZfe1p70aDeUJZ+45bckc6a8PXehTWmIuDzHI1mprCHekfuZ0705mzDbQURfC84CztnG5YfPilVsQvmQLmzHt1b+8jCvXZxFEBJKI8tCnehDlFIjL4ijqVJRWSgKEaO6D4e+zEdTaXqKkkUm14pFCpFkJ2X+NkbviaSgvV/iBIih0Vn4Gqh89DuvYMngZL5GHNaCrEJpSepRmHDpaGWTe28GTojEHZp7Oe5HtlM5Y4MopPyERQsYuPwbay44iwRIKysGMPOJgvdvwG0xOb6LW0LcsG2/YYCgIUQ33t2tNfRiP/dhVwOb1QBgIfeXTL3OAi0S8bn5vm9TAN494q6Ycy67LfwlURTy+pIn5JFHAum/UBDf1f0omkw6l4OTlHzBVgJVzvjnjikxwEV0IUlXIkCSmC/hAOD654hoeWLS555IQDNweLebcFUqIwoUeXYXDkhya2G74VOpFtMwgoq82Tcseei2FtizzlQbopxUSBMbblo71i7eE3gheFL9ezK2HHz8JGZOqYG4awSG/Tr8SXRcAbdhggnxiXTvlGbNQcY8uVIrK2h3fDb87CBz+00Wbm/rVH1r4rPWJoW+yx/ZBguTh8bF+dDzOQRm85tZp2Tyx9gN36GRMKQdNeUkX8DS/gXDbrPh9n3gSb7XQyeDcImSkJoDdcpLHfYXvuACvuDj0CLUQGFYrhNPVGJ/EZGtx6CATCZtseEC5mjJDknpesTbkYxAKlWvlAoZjCk4RdiaUhGn9a0cL2qXE0yZyBk94EEQ0k/DaIXw5b+w1DFPM5Rr3lTRYtBaaFRbDwwFCT0JBnkYZZyKLcQuot6wNENfOzbqbxFj88kRBy2Cn56cK1p78TDQrth07ljnQXF4DG0KCxLDj8t1sm2WbJM/4jZ7f0SS18IBUiF5nkXfB6/NI5E39DC/iD5z+1e2Xv6gcUrjP+SGLFV2Ekt4mZdnsOpobZZkXYecUGJMqISs0nP5EIdELuhEqGGchUJRwkCktzwSZxGftfw8O1AtSWl1rsIIw2OIxssxO11OxQeIUsedaXwrD/KLNgSKMSRDRE4zEXQV0JVlxsVpiQ4wZwtSBGGrd/mebYtgyK/twMYFOiRZllmNzrYVJyONbkAQIsDT3WGmX1jMXzen3vAQkEsx5Wod+5emYFvHtsrZ/nsR2bSgCEhnGM8Gh2rNTMFq3TbP0E0/RH9P5wt4E19BLgCrJw9XHerfXPTg9c3G2db0Q/lv9iG6d4V9xtXq12talsIDW3SDbvpd1SWIl4doMYJ2FFFLn3wpGtiRMGbtQWmjMFvDQnm427cDHAA5d0cyuJxczSwsQd6HJ7Tkx890NS+sknj7EfmeiyTJQNb5IOntVEY41sHL3yIgQJ0vLEBK7GCLR/8K0wxESIr8KyHx0P5vUENtSNBb5xZ6DGCHh8sBtLa7Lj/bOPyPSDQo0PI252T/yu+TIV+1nyNb0X/PXeDwYG1e/XHemDLeEvB72ejGOqfaOnzf1r9Qs/XE5vxEIS4C3lBv5qddeRvS29T/yNK+JvSAH/3PkHz25tbT+gRLrTQVUSJriOnWbZyO4dQx3URFIwnWV0lQVEFAgeoiU5wx8y0bcdhGmyOdJfUxntrCo++BJCzgrd2wVr255QNKGp1zhJi15ktvh6IBI6ohr/ClIa+OikkQwajHsNljc26rZj9/HVHwwB14/2Pb6ibxmbkUr5iKjGVoli/IAUu2Ito1K2UWMoGfUM9cD3mQ7bzYIa2Q4K4PFPvR64IFV3SNVI6zEd7jTxZf9n0u7SWdguLYUNDVrzWJymergzHwVkaG1r7RtaR9l2h1P86+qhI97ad8KriA+8c4LIi9C+IQV8YuuWB5R/u07IBNNHDoRMvXa5GBSkqTAHOZHPPOMOlmhoWhu+HtY3dos2euQKDLlZFj30hSS+39YxLGkmamPamG1H1kMObVey+36bChiAXKxy0hkSubYlCdBJdOGykFkOZTV8jP3MgwoD+7DVow9bh9m/jFkTNJyhRw47UWVchQRjoxlf0PTFlBn8NZoxohBuPSkJGoO2pe+A0rO+ET9DQBpXO/bl8tN8vXDp1u7R9XRWH+7IiorDdNHKW1HiLaz4wyj+tT5vKf0RvT+MTh6FzUq9L8Q2tOB6riK+/LOgvdjtRS/gD33+d96vCNxNCAjA2MxKANFg6eCO3mKmM1q0KgI0lxWZpF/IGiBCBh1YHsTOAjYelD4iPNRMSfaXi1LCfxYiBzRgolbFaQX0nbXtI2IaU0joI+6H9EUtHZCoJD14j/pwSwLKan+dUVIWxDYDCaz3Z12mNgaUjCIS3/G7VSGhI9FaCuKHGyoxAa73IVYhaw1G1wGjJvkg7EzM0TCW5MBYNnYFt+ISOqpoPE3D0nedkHEwE0JRrqdaYRGvvQIWPRm1x9m2tT6htZru0GemIxd5xCwLtm1ZEWU9TPqBI7966UX/FtOLWsAf+sJv3acg6SdwCIYTxz1zGknDZvJo2hwfx1gH9LThliXGI4AEGAD0E1cbqmF21wLgYy7snBHnuXzLf4c5sir+WAVknLczdeMNX/EG604+zSKArPfWRy0vvuI1DPvJwLEID5r94hCx0XfMzJENZEl45jQQErgiVYzNhFUxwJ5lrYS+HS8xOtxfLmHBWgwFIiCuVMDGfOHEvOc5l4kzx4ToYT7rdB8FQPCfOF2tHx1YmHvK4R84DpwhDQpGhfiwTkxt8osGRIbr6Xv1UcvNFhtQg60nI03mOcGY3vvtf//ifZt6L+zsRSvgD33+t+5VgN43Es4bSg6SiNUqZt5gRcnTorGpyZIik4yiOYiS7WTW5rUeJO9rIESWrYKzQSAlJDrQdPUHI6pQWsVuagKBsb6cUGcro0Hxw3KWMB3XRGrbrFcPyfpgcObgpdcoISEO5hsHiPmMCkm8+FTymhaGya3LBFuCL5J71logHgwsxAFPEJBRc3iWvccsLDwGiqFM7HNAXi3xZATP37wqFduISXQQ0foM58NexqZ/vcMd+q6vTprVUNdDc8KSB6zQYjKiWWLk7W9h0N2u/5vcN7SkwPM5NOTj92KerfJ62dIvXV7dt3P/xXsk+KK0F6WAueOsheu/OJmb06cSis0lyeldvCMR2QQnMddV2l/nlLNQ47wuVUwZO6SKINvEQ7F2kXTkjRuqSdIS5NhO6SW5UapNEr92xxSKXz4gJxH7j28lYxJy+AIELVCZF40UaHbZCRZ0+WOeMFir+HagbYjgJhmYHOxj1h/efFz4Ydk82ZhvO7MXAik1zGcfwMQHWPgVG9kmhO1ArQQeCIgXkMfgxvdaC3KJV9Qlbl3LxQ76BmugZ9Dz0Uo+2NFeRWUxb8fK6kHIOI3zEmBSq3ibPijNa2L5aGz8atm2ZXHvUsloz/RE9bPHX6Q70y94AfuO8/bqAUVgVw81JwWrdfOGamM1WV3Yv7R6aP211Wf2vzT9s70vrDWeHtp7lETyA4UaJ5JOCAA9BROcNEbgEvzWlySbEZvBsoLyzHQ0pSF+MJgyl2gIlYDgpqUH32ItB4bH9i1UmTCeVPIv4GLij58QwLRi1DSOXduD5ucCcIQASw+joqgWaePVkoCD4SOjkjdQ+QiRB15pgKh98kgTGrwMGGVtmsd3GOwLc1oNRGJV0KxbAu5MaDtWijJ0haPWXYxn0t2lm1kFekCcJWVZwAYan3gcbiwtTkzTUf0Uz1tO8iIgNNZjn2sejKbhuNixce7Uev3hsw+88Hem+U9oXtB2y/aJ+2Rg10laic+Sn5wuT5/Z+9L00Su/44L9zN5D0wXR5rhKA884SuENO6+eXr26dfq+nTdO373z+uk1qzPORdiRkCzABFAHDZ0ITix2LXsAr5LN2CSZuGLaDgBpTjx45lKPlbXQWkig2LGPNh45sWWw2jwoPRH4B0js2oeSxjkA0WIcU/ZfY7tetAFvmwsllhcZfEcMPHmKvm3LEYl4Zp8sZDwck7yo+slb/1JI/eRuLbEUicp1fBERKBnDflwXXg8qJnHCDnudlo2ehHnLDeeebXstP9QxjMXkJopNGNaLRtp2yj1z+hBNOG/WWfg3LuxNVypgrCvuZQVoRDr4bVn/8+ld++uL/6XYP4HMC9Wu5/0Ns/XLX/jn7xHY+732SkKdWaf7r3xi+ui1T09P7F/u3c1+OxK4NAdn05mRg9Mbtl89vevoO6bv3flOfSXMdzEQ7colCSTs5ZEhbNaYA58EpHdyx2Qbg9ZFwIZ5i2a/vFW1HiEMdusERvIs3PgGGWssmxJIwldGaFo5LjVAvVrTNGOqJl3WUjyGKmBszP46ijrgeNl3CNAWpceaIrnAs03PTRbTS7BhROeWWEgK9fiz+HmdpT+zTkb4ryZ3DVtsXopk2Ot4zbFj0+87pQ8oP8N2US+j/9JD+l8fh3z2jRjOtMHUoA1mKH9mGqyF0oOP7088FqQhG73owo9qYSd8P3H5h275H5B4IdqwdKPBed27vbP6mBZ4jg39p9c+v/o/Lv3D6RPXvtCrlMmOlFN14ULcmo8dmoWISGz6a7bOTD909C3TDx35Lp2hz/htlfEBh4W4o08xKHGkR9xRp9MBfDXxGNmuuWNmdkQ2aUmQzkZrWsCYJSqZotkQQvbDoDEW/OGVxO1knEVeLPmN/3ldHAV8jU1rbCyll2EdDFtQnYY+a8cZ6fg/CweQNafJHoUudJNidzA1wJf41L6ZK2ycNd8CpuoQ373wYQR8GkcrepbDtx4/pv9L+JkXMFo/+aWt6dEDH8xq3N6hhYnFsFxC2HFfsDS8oieHv/mlq+7xtjEjJV0RZtoh/iPT1uW3X/qBc5/dRL0xsxfsEnrriL5dtD+de2j92PSTFz64+mcUrpoTzqutoIXiADQlW5oFhrZ4qS5d08gsjR/ae2z6Py9+dPrVy78x/enj3zf94NE3WwDuEFXSKu3RIBNhGIJzV4Uej3CN6jCvstAFQ/L5UrJY7LH+qAQ1zJihgc9u2UFQgoQQY1ceWU82L3giSM8ohswBov2TgdknI+L1Qj7weEQTD3nLaJpCLJ4FungxG4UywyQYitYAKB37C1f4ct4jh6F8o97bZqRYpvAqTuUHjmmYJ4eyB1ghG9uTnQWYXXgGB+5GP8p/9l0N0G4V4GFoyetlZwEJHtvUjRtZbz65rbNwf3kCThBbt+OltRGfVpW99bnV/lE+5PH2QbyBg0Vl3DjUD33hU/cpc3Y/cOVj0597/Gd01v2il8TC5r9sVs+5VTD+FDzmWySFH5IVzeOWqgBvIanN/vL+49NPPfnL03974W+vvrz/2CL8hFggobA7PLLYVAacPlvMkUeispKEC8ObKzicosA0YhmL5klREKoGvh/Ddhi27SF+GWt2z57b9oLGSpCk4Yc797MNRCTjZKqVS9BaRHS4BdEYkAzlQ/nIImOpj20bHQyoEQMP6tB4bQ4mRbuUEcWxgxV61sE4i1vrv1wpVgSe0TG/0jGbAqEfAWCd4V8fvXVbrvu8FuZ/PZz1micdEU13Pi3oslXybzv2q0++IB/ymP15RiH6+kL3n//EPY9v7z3wVy5+ZPrVK79RISQwi3Q2DKZDp/N+MXU77NZB7VnysOyrt26d/vOTf2K6a/tblE8kh7RHgZCz8xkmCeMEy95GOnobY8kYS5bBWo5FKkx1mDJeXNSaJKzlxUav2D2LFg5KDkAx8Xcolx4m9IBBT+tcRNc0fDBDc8aOWUvJrwKItZIZqAXpLgev0TreHBELwGxb1CgWWT8LDwsLbpnXJEssFjHCx+YBpIl9g/bdt56azhx5dheIH9V/8P3BxxaQBp/n7dVskyXZ7gjqkleL87rR/SeP7U+/8QRn4WBu4o30KSxW438FqfXu7//gpR85/ZEi3JDuhp6Becvoi6sn/vp/9sTfUvH+pkPDUgmRl6z9yllUZ02Pqx8S4vN52cUfM92ZnClOy6XE4fGX95+Y/sITPzt9+MqnbFYqjrUykj8UMhqJJlqnjnr+kIHG55hHQS4LpUElORKRbECbxNRj0IGSXeMaXXz8QIb48GUH2TJkeRoB2MgCaXkNPTNN1vAago99QNY+iw/LQowjiS6AMida0aEVjp3T2HHa4qOaZSUxWUgWPorYUzNEx4v12JgEbExs0xxHu6O5Q2DbZmsOTf8DRww9i+MJqWQ5wY4q47KlEaibj/DbWvezLiPvjT9iCX5cTuhir2TKzqy7DK+Wtb31v5194JHNn4uK8HM+3tAC/tz06H1/4cLP7v6Law+7QJ0DTgflgn6LxBlB+BSzBBFK/pIvGjtviqrdVvkGS0s0dclf6kJfPJ7cvzL9Txd+ZXrg6m9591SKzqHeoHghUPuiJBIBmr7iiDyyHEkmRuIIvPIRCsm67Ek6K4Y6tlLZzyoGTqEZUqIGtQqYUCNAMVA5lY9gVMOP8g5pDa2GPBPk8CX+MmFuf6MlcXRqEh1kCh698tk4XmYxjTvGYGph6KGDXwxrFZYFtvyDhd0KG8GKfOlFHrfB2l+d3NabsM+ynakTNsAxW0aMg6f2dgO1lmPe7OpSRDq1sjuOrabX8sVhOT+wxENv1s3YtDiiOBZtvb7r8v4R3lq6Ye2GFfAD/+pTu//rxV9975f0WpRd46/Pm36dOqjhZW9bsvvWqHlyQotlXk8A5O/y0bjKDiVBz9yj99MX//7q/N7D5rAVjr0QnUAEPz8eR6hFSvJkImmSy105wr7VTiFDs2yGLgzTOKMWL3JOTPtkhnnkfIqFHuVSsTFPYw+6DDkLMuboBWSKvuQxUnz15WdE478EiJHERxzQcUykUYscmLo9bRLgDLgEFCqSsYPHcTs9Trgt/CjfpGI79hMHli0+YcZ2pudSvOCdpebLBeYZYosC6pZ5z+jhLfkUxSYNKdp6ej2f2eSfHynM8CBv2rKMbYsOYPx47/H7H7+ndZ5vf8MK+L/72oceOH/tK/goX5d/FN5BGuVYBbkhm+UstTPO+tneAyU+2xPPhX0A98n11ekvP/GL3OSSZySwE9VnA6yRWHaw99hSLEJkP1iRmjdA45ZD1wxExXSCuiA19tk+3OxqtFGmEBetjYOWOBnXQsC2EQNIM8o6CocPW7hAEJN9Y2/gu+4MAw9bLW+XecJqAx4LqDxFNG5KTa5JLq5KUVgY3PgmlGWhYd8+oF5goYuMefvE2I0utPT4c2rn2Z99g1YWC3uDxiK8pFqWmYzneavhQ8at00jT9J23bE2chKMXXQeosDf0tO5ulhGTbdjemm7YDa0bUsB/6FP/zX1/78pv7eJsF2bttv0/XHZw83ewIEkz5wr5Mh6SYuywpg8m2gcRsoktRVwf1mvi//nCh7N/YpOMIifW9jAHCPUNI40kVRtAsuEVbP2l+KFl51D2TjEHg8aYRMg/cjoynawRwQ1XnvWR159NlTy21JpuPyCU2jyPXcnpyUPiUjOMBLFheaNQYEYTVTLmxZZm5aOLnWl/VRGUODKQRMgZGUocREh45T/2M4GOvzwEo53EEPYR0dCSDK2vM+kRf6wK/WfV8oUGlgQ4Dw37UUg91WfNFtjopDUfDMa0ptHzlhJn4ZlWtpATMUtovDwB1IZIAjry09tO/erj79PkebfnXcDHH/yzu5+99vB78crF5F3JLJQ2waz+tGX80Q4VbMnMCJEEG9pcri3RfWGXfgLcdlbTb177l9MHL39C4SOothr7oCbxmFMBccxcH5QKkJOYoiTrvQ8lKp63rPecBEXQO5peCWucBtdrcjEsVNi2ZV/wD3uY0EOapeVMsFxZ8tVv49gF2xz+SrbXIwzWCS69Eykmbd8WypJtI0GVpaGnEXpqPgrHBd6uhIHveMzBTcDWxabtikcwEiJwbZoDTz7Qz+w8u7vPMTSN38cCy6D2YfajiFiySlxobWj9aNomTuO+Ud+eKPetY1sOTeOidwCLNUvaDx20u3/++AOP7M6Wntuoq+u5aUtra3v7fRfWV85q4+IcxVk3rEzROigXF55roCRLjiXNRQlvcxZpI9nCPHfYLI9Ghca2LLPwAfs8/q+Lvz49ub6i0OIIjTTTH0mUjPI2AKd/RFxhN9M7s0h8s3O2IxlbPaho2YB6N4FUwnoKDx9ttxI4gjKqecRnzDKGiGGrt2v5QXfc1lR86QcqRjJxNiGSdWPDbB1YkyyxzvqDBpSb5cQpu/SIhxlTeVJo+XgBVGSRCUjp4qia167ecYBQvhzTs8IxXWPeiIabcRWTtaTyvfFZ6rzcpi7kC2OJw82soxt3ybPfsSddtkMT1h3ssm0fNE7g9LsfO8/7Uvp5RepHfvv9d2s/f9wpIFdduNqnLtZBZzVeE9u1+dflylLztznqWd5KIsCz1IYueUWOwO/iLVo0VpOeaKYPXvokIG4pSIYEOEFuBKjS8/464cfNLtbixUhAY0SYs4coV+sEFdMFOeZGbCn1pQOqqTlqkgEWXEAMSqZ62wsu9SI2h/hEJFJEGtAQXhpgzKfL2r/ZedQcSOzRyqNMNo95IgjNSSxZ68oNspgGSd0wLx2NjZ0iHyFE+vajz+3yGV0aZvLIPLSM44KcwXg/Sqz1NpeL4zzm1mq8Fm6d0FrWcVj4wJ708hsrPm6t9+89df8j98zoz370vAr4n1364s/hmv9UNDobOIGK0hz1LBZTB8+vOBzpLsZINJVZ6WrLc65IuKD6ZKCYHLKHHwf+WooCvqgbW2pO8KAl2UA0XcDASiBTGHNSQ0xz4UaGpBTROzOSdySxwHymGzjSzMaiVXad2Ez0D+8tEh/KnPMeSvLfVISBoneze5lBtI8Fpqn9kK8IWKh99KQgwjPQLIanan6SQD9Pl/alMFoBGcfLshi1piNkGflLjy86eMgiaM+3gA3ig6AJTBGWfW+VWc1YyCWWuFbE8q1n9PzsDvw88H221WsxzRjit6yxslYpraad7ef1ttJzLuCTD/5H776wvro7F568IS3sG153Cc0S82iT17JOC/QqPZACz9LQKGZFLHZMDc28ni90ollHnmC2XLy/eOkTBFyCTh4MgDui6gHJxyrmXcy4ZRFCoFolrKXnArNjkegnAPxvpYpWy6c2iphgDNlZa9aefZYzUp7bGJOFMGFFsfJMNlvcS5T/EpXRXm+bSbYDERZCGuMzCPFB6zScYxoa+rJdMBLHYmzIk0jjE3Gx7dX6mD41cuY53sDqxWStY20iyyauzAIehYacHqxkQyg69suultwYT9Nt+hF4/Qpt9HVM2GZbWSJ6bbswZKtly9d7Tj7w2I9b8DkcnnMB6/+bvC9J1YWTM2yfYylWXO+49Gjuw3XsvM3SVIE5nj7rSlJ0/2ntNRo08Jd3vAe/6MRiSWOcmlhN/+DypytUScJKIm8HakhaXB1hT5Ia0IlotpOT3TB/yCDfCWllktMDA7FSUdJc9MWs5AYsENhGchRBPQM0lvn8ykY1vM6s9SmMPCxSVsverCgrNmNgyBQxREKGExRu4lEQrD+NvsYDkJWg0+tAEiztZjLXaBlKFz1RsPeaY0cRfs6NbyJthmHTTwwfbO123BBXg6a1bOZL3bXvRt82rvbhNb/HDlnRkwPDhmTbT0VanxrYv++5fkLrORXw6Qff+24V0C5LPfyXLyI0L0Ujaa1rFLXTWJrOEag1Lpklpov6ulYSjqUs41HUB18HC9v2ZPPhvQvTp679noKbzSLkaknSbIT3LF6RXHlflw8yAEP0oTnJRXDKZqFippHE2VR1JDMMbVZ0lMua8qBTT3IDg0xtbnEhIZVGxMbU4346oM8Y56JExjZMtGRDEDySQVCpK/eYKP/KXcuVThyNbIPaJEjIxwIgauCYYbp9RgwshJHtB+KW5TvAz6f5p2g3vACtzYSRkMxhKd835LwRM8MuZYpe43EZve05PJq32Vvd+JGPTSTah4QAeQKndpd+y17v5Dz79pwKWCfK+7IgUigjTGc0FyTfJvLMfnJ2jXxJ2NuBU9qgLP9Y83K+ORbE4Au1fcGO/lJTC41KNT5v/amrKeD22zAknSHHBjAFyW1kpJPTO2UmfPG870lcxiDKo+x4YEHJHB5jEj1jiiobTJHbXs2DbXnr2EevT9PGsZPmLGi2UFDI0qIjE7JhmzYaLq5EhtSKqHrGLA4dPSQSikW1HmZqpeoxO6DnOMsib557x6nn0QHu1SpeXUJb+bkeLpUj6GOwjC7gFgIymq3ovY7YrIesHk1YgIW0nu7INbTlwGrRDILfWwJW6/UYi/aBdNma/vxzOQs/64idffAn3q3N2V2c61w4fXZzEbF9/Mn7lHCXbOi4DaVvUIUa2XEGRX/84eY8GyPbaeySGDGPjSGLvnnp/3kXsBMwwWXL5LS77tkAZ2jttuZkHyT2w41JErvnnaC5GYY0flRjSCWwj60QpmyLEUlRdPWBHB72o5xBFZZdaVz6JWhkRakPZMArJzg7ap1t1XpiFhcU/4KkAZElLHJbOi3CIvijERP8AYajFTyCq7kj7xiYLQF6tN0zuPPEs/vyPvoH22WjcdCjHKXrB/KEvs0yT8M3Kzdh6BySL1wEb9MvV/KprJkU7GHPjCX2zI/SPJfouf39/Wd9Fn7WBSzD9/XbReRD/x0uRrlUfBbbct0fpjTHq86TAlpgaJ3LJ4gxFt/la5lZP6NYyDFPFy0B7QvXHpVXBDASpB6JSLE4AV0ckvAcukWraASAfEgAIJiesR4ke7PpfRneWQJ2Kbtb0CkWK+rg/9EBXsliAZ59VQ+5WOoZCXdJo+Ckr9ZkD5hY1jOWbL2uKhej1x3B2aGeo42XwS6b3hW8wJ63zC5F1jKZD19AcLsRZ1+Afu+qfQqo14yFtjLHqgSKt+Qn1HE2UhwzB7tkIZRQzsLOHREbix6sWZ50mFNCdP3axby1kdd9pWd9Fn5WBczZV57vdiEse3ksh57nX69D1xPGc54LU5e8iVnhe56xBQ/ZTcHOHvXI5e6i560kfU66Iiw+0SXmRDUJX9CcFTFPUlZxS0aC5W0x62OHFhQPWQFwAHXZeidj2/YWEhizPGIeIIERHQMVJ5nlATjOu6XzsShiyo2saenJYNu6+SHZLvZiOchBHPrEi0K1bRes4+NgQBNj4LdSy4dlnyx2I86+2OQSGlsJG5RqdmC4E5nmuZ955dshGeNKNiHJPiB0hz5bmcLs348uXi0/YcRIbCAbeRs2fSFzbn96dmfhZ1XA+kLjfX2Ji/mU0FMfkaW1jiW1b+nFI+DM+1GIIB/UCVaFET3/pVAZQ2nqcuSSbZtIaJyy0s2sfX0D3EkowBF3BAyViIvvZDVJBxKd8kyBAmZpkaynmQvdRE5/kYt7hYGghTmUnosGJTUiYlTsMLIcJ9O5MfYjGTUYNtmCVVgwwRe5Fkev2ZBrvp+Umpoe88mwuFQOU4zt86wgQa8nPouOjinGyLyPtv+aY8ef92vfXvxD/n+FE1DZW6zPrtuVdgmdpciSDq/nmzLhQINPWG6r18F+ys6uFQ/82W5wEg60u5A3ZcBc/Xn4z7Q94wK+/cH/5N1yYreBNwpMZjfmTo9Ap2DkMKljOukpaSdUlsVS87dZkNDCi9WlVI+7d6Fu4IhTNsEglgdfFV9cX8FAtQqunx478GIpwu1lCwKlZqEGwMLBh5Mcy4HT1FAQXOTI+5+AxWt4xnrZKu95mBomEl1MDRpSLMCTDKfGqOlIFLJ8PxnAk0rsA20ffACx9HAqJpuNg861jaKVTATsK0rM3WAVnm1GtNgRWu/oSur1J46XxvPrWNVD12IfP1gCDlPIPGy5neu+TDY7U3SXLXMvfkmu8Tm9H4wt8G2rbJsNbTkXMbbiJzLxL75aR//R/en/55F3Zfz1j8+4gE9vHdP/aTT/4Yr/tK8pFBmr8ZBi7j+Vlzydi6xkG0N98HqBrdc6s+aS47iVDWznN7QkO+wupeuMv7D58N4TjlAXoydJ/spFbYxiy+tXu2iB5IMTet5V7zIJS/F04rIqtjYb64HyWM4hbWiNab2rkc4cEWMxlYC0jDcqwTRwjAkvLckx5mMgLmOccSc31TiIYo/CwbFcdYRcR+k5TrAtHrqm8szrDl+8MgOmsaVp300fatOK4j229dy/Ogh+t0f31voP0TA5rz/2ZhqyXr49Cz3uBKX8M8bsJnL55hIb149oTPJ/NZ32Z7cLj+dLAeU8YEOJR/nVevGzfRBVOv4vXBSs7b01P8f8jNozKuA//Kn//p4n9q/encKgmFIkpJSzU5T+wxP/ybemERj5rwal9Mc4UlC7XFuvEayps3ZLOJWFF4qo+FHctjLTQG3J7iN9YsoHB8YZC2Uai/ImJMlFIGe9gvBrTOmIoCMCXmYnucle9VKRzQ2mEwkwtISToslP65iSJw2DDgR0pSjpOEQx46edRasHNiwCsLn0lhhRAg910fkQSOxKzDCStY71aj0g4ttMw4im+Wd2KzmOSNbiWGvFxfpGsua0Oq63jL71+PO/89y2v6Szr5stMe5HDDJvJ7zYnlipZdWLDmuDbZnNQ8vQn9MHOrqwQwdPNE2y3TO+5aDbP4uVLh6W3Gr/nrMPfPmecJ/++Iy+t/Wpi195tz7ltogJbqaRjvMMmre3uJlHcabjZnSWx5ZdoIHNtG0kj1SOAIgBkA+zDsV6vRbrlXwlcHKrPvlTNsopxTGv75KQTvoVX56PFdcOcGJTHLNtOZWEFYnEhac/L9eJzIi1WNfMmqPHkDM9+FmoYDCuuahgWgoEMTiotY/xpWhAW77n2CyNgW2/AIqDiEqGP56ONppVoWlg/xDKNN6Y4yEuCkbu1BNHMjh+mrVab+v1wR+89dYNE8938rnlHWj8HUvIgHVBHGTNHSNRzWlGYmwq9G5LzXm14Z7eDjqzyDmKZgKbzWuehczLVjOMb7Hn0OIcn5H+CNyna9fP9oXG2Qf/093t1eIbR7XJuNjnMxyY/1DOLH3PFjRlpbW1OkZ+sidnmcMzP/LL82dT0EInf+1FZgvXiz/T5zOxaNI/uVWfhRubJm0XizOYkfYjl4eNKwKAkWti9fBglXdaDdsp+QwsRXJHxGtnXDKSY/3+1xKRRceCIDXfw5pVsSxIGLabnAXLZheRF1B+YVtqOtpCxmUNMi3HGtqFCs+sYyYFkQAIC7tWTPw0jfOSXL1eZ94bdekcy9P0pau8LcOaF+420z10+WWZlmMe+eHecFP0Mc6wdROS2c5tR1RGxEQCUYGXx5iLj408ih+V8gEaLT7JL52Fv/4P4H3dAj4ybd2DW+PP259Z3O1C7kJKmcDLCLoezmPpqbckOLwdtMAbNrDWdPWxL1mtfv4TuTgzbR5t1SW3cYylpYLF2PPVdNvWSQKayAaO6Bm2zyAh54MNZnAgNb3zEW4ZZr0NMdYcuy0dlHBCh/56omb6FzUPFghKfhDwpZDELH5TYPBEo8YhZC8wNC6VGc0YEjFLx4FrxSqyMAGU5MIMoFp56ZRN1uQWKy2uXrWMjB5MiBn9a/U/LtzIS2dsX5THn1MB97pwxW7hDQ8ELh7sAABAAElEQVTa6B0MEXqrmxUdi7Vs9tnqjb2EaoxTehlvlZJPKErNdmKrNiK2pZAQJtzo2+9W03RruvJ1P9jxdQtYAnrraC6MMZLdMcZ9zyPJHvsPmre9ZJvemgfn45w+I7MeSt7tgM1xRrUN6bhfPCloPrwsXTyEdsvqqM/ASUgxWQAMdaIxTo94t0pWJyZnFuQCFwVpa8oYKtupAcXVG2ieC9JPENnRRrGV0jeK7QQPQMypA7oaQ1v2YeZ0cUUBRfvq549SAW+BpGRScXYCSjC2pal1RhZnlyqLca3DXsUV9GShZCpuJ3d2Vt9xy6ly/sZ1n+f/PjnUCFYec2AkVP5FvPiWgzLHEZfttnexlbpf7sJaP8JHJIXF89XAYB45uBvYY94y4DZ2YxDtra/7ltLTFvAdD/4X96h4dudyYrQoZ+0Pf+yrqTU3lfH4wMVCxxqb83nWCw7ufMx7yZsFK9e1Zj+/G7OkHTSWRdg2vLVU09505NXKL2YU2LJpWolsALgskWT0ZtZYu8NbPaY5Ua1noyIC6P2XtEzEBluiVh9tjIz4wI9mO3G9aNIfTTryQhAo98Nc22zJ+DPsUoyaeA0oIQZSj12s5Qb2Dez/vFxDjIFX2BpkAkCwNOIffkYch1ohN8v0czfb2+s/cOo0rBvefvvygf8Q6YAFwuVHOdxrmcWymCxRY9ZTzZsYRlGaXwETdfkaODFwHIiJuHkcxJ5dEV8T7CRNkDcJn89+vZtZT1vAuif6bhv23mjE1vVD8P5jrr+Y1NH+zDzoB//sYVGX48jN5Tz0bD9I9mEgxn00hh37M2blF0izFNw3Hf0WCinJiRNZKCM3VpvRvFHFiqw2J4mdJB88RwnlUidzqvCrUKSWy8uWSW87MAaU8WuGTAoxBEnhvMUdJ/TKpCUYu6iqL1h30qSXSqSQa1orS8D/8H34hBcbVx6JAa4b2C5kbAMSVlbqrZbVW07furrRr3vtqg6f9+tfnPByTC53WqT6yJh3UKCKDYywGitzQtCPXmt65CafhWO/TIGzSA2PC3vpZ6TbVtuIrni80vozjXi9/mkL+MTWsXeNn6eRt9oH+8yxz2183Y/HTOtxwtBxCr+PczGNsyoI5FE9XKg9d/HJVa2z0btvPxp52c8yoS5tvfXYa/lv+RQihWWO38hVFxkR66fFbGB2S0cSU7ryEP2QGWoiYzyNAatOUoh7rkKhABjDYpCJKcgz5eCHqcjZDumDWp0hDVCp0K9zodGQLx9CkJ58bJ/HEwMrsLil7HQhmGD/yypPBomiWeNAhhnFiixPg4oHqjrzTn/w9JkbftOqHfjclb3psb3cwIKGTaJi33W0+y08+nnhjuohofCDhVJHpXo6mGPf11pfeNiuXUFRTXPCkonnppkCNfKxlTk0mlNsNd179oHzZ024zuEpC/h7HvzL7760f61+rK62zvmVcR/bD+YHCyYyRZXumMu//kJELsjEKX58PIhFvKDNFpDLovvY+JHkiIQ1wK7YYOd122emV23fElMFlCIpUlSZkJDMjOai1gxZMbxXoxiQ6aJhzzSeH/NWN02AdgnkMcRQTEG0nXGzSxC2aZH4FFSCKXF5o5csZIMxnDIZw18WNNItFzQ2UegSL2CTO49KZu6QY0a/sX6LjMP6qJx/y6kX7syLpU9c6stn/K9NFp2l83Ac4q5myzbLWw9hWveHsLxkCUhPMoi5L/mjRBVe2TJ/AwO96LaNlg9EfI8ejvRa1vq9+pP3Qrlee8oCvrDaf5eSTbae+Z8NyK41lAouSubcEQbLtPA5m5ekfG0blFvKtHnNySLnWUZzQY9CpWSHbWlpPK4Q6krhB295g13FJxpSdB2yUM3C78Fjx5i4aTCmIiM3J3OkmPsbRct94yycJwV5hmH/Ccp2gN4w4cKKnzlKoPz1INLCMCYJNPSHs1mPYGMbi+LFMgq0zHJ2Z87a2iBrY76U81h4w54HXhIsnZF4r/eFO/PaiA5cPi8by4mrxMLx8BJDX0pujuEj2H0G0LTsyowZo7coPUE45RtZwcgWFM8kxvElvcbC7PgOmqRat22xDdPW/lNeRl+3gHcffN/Zx/Yv3Ush0A6WzQZNMsuCsSy08UdpqTVNXvM35iUXKxyDntLMMedRCnOBo0Ilpdp2NIUMTWKzfWwFE9nbdeb93hOvg1BUhmnxikjbkga55CW5jVlJLGmDGiCTKspQXPQkt9cKdksypjHXQ3jKc3nlVvvJVBatT7d5yYykNaOTxeKOtC0reST64EEusR12jOif5e0nw8JyzGqSE3IxNEGMOHhgDcKkZStGVomMDd9+9Oh0963nXrDLZjuhwycuXZu+pstnR6SJ1WcZHPH58KOWudDC9cgN3lLdkjN/TgVoasgmQTM0qe12jNsGCjRiKJlhMPxMWxfa6u6nuoy+bgHfMZ25dxSA8sF/yz45kuIJV0eKTWfP8Xq4EfCvC1GuNY7cRyLHpcRMJZ6xrd56wQED3sa5euDGj7befV9+f+/x19kqjnRClhtELA5V9pYudIojRcVEjeS1MHVjgokguOjoreQdQgApNszeQ/CUokshGQVg48k27sGLIK7hRPTKfi+BTRZu25KU/sUza4AhEmMe8REaI1LIeBA0B0vzzMAVnycoCKF6IFUvhR5C4vH6Eyen33/qjP5/3+umFpI3rFHA5acw8cLxXeDXWkxhfHC+EN0YLmQdjIrHWH3Zkk5I+o0sRbFECYUYjkpoFooOZuJH+GMumeVaKqqFv6/L6OPXvYy+bpSfWF3RzSsh4gd/XRzdV0HGeKW5HdOhZEpT2hRUZhznUm69wrc9xpLQw7JaUWzHTTRyI42R/uAfesIoXiTGUYA++/7x02+qKsACTYvkrEUii9OJmh5RHJNshCWdwJO8kvFDLBTBK2HDooEavVuKsSaI1y6BU1Ts15jCwpYEKWp8E7yYIthnz0GPAi5It7TtjzOpMQRlHrpAYpH1xUkNyx8x4FnMvvTSIZdz8Qa/LChhLpnffPrs6nXHF/cWAH+BGl8d7MvnDrKXhON+bBoOj6U1P/0835TPbJZh5NZAnjQ/X2qYsdhc4kqgJZPcCE32G8IyY15Y1kF59lXr03+aufq3bfLA4boF/PjelXvYzfHnLRwzsPU4eKabS7O5S43lmDRwYcrnfnKYdTTCY6wkf+xy61vX1in0wrF0S6QHb+NPsn/s5BsVyz7jzbtM6MqIbNcwA8IvnGr46+3oDSLazaTXFkKhLrKb2BsCSXvE5Dn8LrgFBkMerUVfPhMXk7Uu2221whW71t4MoFgHxrBFc1ZlB5hBGi62PzYfD/y0gRDulrwmpRfM248c0yXzbdOZnfEzjdZ4IQ+/dpHf9o6P7rO6YTKhgt8ygxXnPQ0P1WzX9eURhZ+HJ5AWrfWW6dA06bJtfrRK8+hpwZ59rXm2u3zjCw6H70Yf+jLD7oP/9T16yaS7z92cLgJZtuRF8nSTzlJHK/84SxJHcgmd+Vljlt0Y9boKiLQ82KCRUuEs+RrLSJK5tIR3z8nd6V+/5dvjhzWtiiXccuP16JzoeOy4O4ygSlACQFtDwyblScHzLdeKnKuCOeh70bNEUG1HNpoiGvCZ2i/bI3Cy3eS4wkwj+UMRa0hHaHpRKBEoZLK2CItkonUwB/ZQko9c6QjN9nOw1JjD5Xecv/PkrSrc5/dzsAP0GQ746uAndfmM03Or9YiQ7UmfJbTcvIZ5tBH5goM2S8w2GIXD7hJo45doQlYbsEBi6E2R7vDETmpmgreoLEZioPSOr1Znd6aduwX1EfC6HSrgaX/r3rnAWizmcWMETSuw37LXvrS03RUzrmjWuSxCsGvFs8JYYGwsGGPYPjRBc9surDLm/JTIsKnxbdsnpj926o21K+4655eOJMFrjRRCr7CE3HmTtDImIWjAfRQmUsIlL9zaeLigSWQ0npZTzPLFK0G/IK2HeTsrHVanuVZlGYmpJwIS4CCuzeq5UlN+DdJzp4EkC3cUMV5gwxZiBxJAghG6mRHAeNbLnHZET8h3HLtlxWeaX4zXurE6H//Rk1c0aZ/aS/jtocNohQqolxZ+8ehKPMPGCz+hB6LkjTYfQpWO4uzmLat58iBkh5hhjLU8MU1Dv21DCd689S2lbdla8UX/j4SS46EClh/vbCMN1go25dzQKFkTlnxpsx7gmx5cVD9VI2sONi7Lkywzz9kF/kwqNRFENBl7NISQXWDfruL9c7d/z3Ria0eZHUGkahi9oQufhzLewAKiYJL4wSXBE++YVrHELHIFR48B9TKkYzE8wg1o5lspc8wKWdhjHjvGMAKliBgUpOWKjQatzAW7JAyIVwIGYs6U9lYghuxuOOsIsXYcsm9HNLnj2Al9GeGWb0jhso5Hddf5k5euOqLluJcFr8I15l6KAzcWpYEiwXKIIkrVSmxQg52QRWRTvvW8j5pY34egkhOEPPmMtJmeZ5a5/YEnp+JTezV72OtSolHA/zH63TZOtrsP/qVdFdHdqHqJwmLME7iLUb15TQ/XvHBwpHTEozW9+74bfD2eaWUDm/wFTxxwRXMewvM89FjhNTGjfugLC/q64L9/7m596+i4U14BkIQgXJQWbQCIcRh9RuwyD5p4zbRPoSIII7ViOduOlyl6mcSmtiB7iTAQNmYz0rMMFrCDHP/8gBnZJgQvLoppB3EAdZo7JkUANZxij1kGpVZUdbVm/AzEer2ztb1+3bGTq7efuV0/gXPqG1a8rOCDj18eyynfF3NSfSxQ9B6rR7gU6Lxp5rfMgPEg4l06kA5iP4182YlOm8VO8MLOHBR8rli7b9vID350dw++Dt44A2/vbekaW0r6R2WjPhJWk/ar+5kSuTzboJE/5nFg1mBOEYejyRJXqzCG0ht+0lM99FLI4kAJJkfLum9vV9OJ1Y7OvH9o/W07pyrLJUfxVNNAkD0ngPksHDJLuZZPLw34mmCp5eZPS9VyJFDPFVFrV4f1OI8D0uhlWrYx257YNJHL1+7xQXj80IDRHCD7lDn8RBAxqY8wOkjFyxrwCyncB18wukxe3arXtq/VpfKL/RoXR67XOPN+4Sqvfbt5KT0ZeeBlDGoPsmueaaleLRN2wJpMBpVJNfQcy8KfpSPQOpEDbVAc09K3HTgzXvQbT2uxouYWq7n3tVHXfDSVt5N+OrrTtFHA0/7+vbpfbV474bNeS6tvFzZDFwHO07ShW6MNWfyXAFkLvabWYxJZITUIAoMesWb1rOfLJ50/ffbNk4pX+VhSiyIYBUGVKWHV1PETMzR37EKcC7Hp6gtwQXHSV4UgQAm03S68UaaspXcq1enislmgZd7qo1BxkS3FrxKoEXRjtTHjzR4mFYTmJZleI8bSZRdMSRx2dFPq3JHj05kjR1e36c7yN+L1rZZ23cbvXf3DJ+ezb4TmeDDfjNAMg1TaPIq0qFKynlnNd1BaKSEa+66oDY7jaJAL/jw2+uFmNON5B9knNsNk7UDhBPHAnAJRY5doJBTa6t5qQh02Cni1tf3WpXOMN91FC0ryuzDctdycjc2dEceoPG+dQffiYYpTMo2y9KQkZpYIXmf19976xuktx15lvuMlepLdMfOylPgiuQDcN5jkM/fToMZJdLGJ/sLBOfzQKcpeRgpfM/3DI9tGxi1SZTM8+xJsa2CzxbvvddgDSVXpSXSWbQfRCYC3XUbs3FhnR1e/tLK6ZfvIdPuR4y7YF+rbQr2G59P/2qUr+tJCnm8c8AG2nHVGDaYG9RzFPnjPItPjlhy7J7jwBqcHHXrPYzWnLORB9+aVNPFvyxmJon9LbHbFWWXd9gsAsGoHSyM7CHnzdXCkRN998P1nj+5de0TDjXYwJMyVCcY/eHaOUdwesAPLepo5UF49MteXDU4fgdjE88xPaYZAYLQ/enp3+qOn73JgvOji2GeNRWOY2LV5luNgmmF+1OZADgMe8PQyFw4kJPM2lJ1dKmoc/wU8FhI/NCcgjqejJpGScaBqJ0FTG6DhhWB6L8RidSClopjY99rWujQ+Nr3m6An9GNtL6yy79H455sbVX/tqfkHUsSSKi5gsZTvWTSuxnlbfsRnbIfpyHLFsS+X7kn/APvH9lUeenB66fG1EfDaIrYNtQRvraPvdjyVKeUHTpl7a27vt0R+461FQxxn4+LVrd/fpemnOaQWhvVDyAOcjg6Yj45Z7z5CdZ85Hz3KBPeQHSul1V85KDv16rgjThBrixcAK7S3HXzX9qIqXZttWlwMtJyIF4mKtJTRPdOlwxqJDsRPeS13WAkxJxLwHul4QspVLEBt+OwcwLs+ZO3C1LrsILTgI4YrEtmS6fCbISMSIvxKCX8iCZy8YlgBYNPOQY6gDOkf00ugO3Yh6tQr3pXymxell49L5b35NP8CfGHjt82QeebVW7FHtTIE1tbEdFwW3Q7zczSFjiNL09nmbIlpCsYKMHposdlRxj5UcxXMutM/B7Zpz8gigEw341Ji0tX/lhTGOHT36TrF/HplRwBrfw0kFYz5Yw7Mx9azpdq6dQYkW+R5lMdBmHHg9z7hm7WET0RHNTweok5Bls0WWiXtu5/j0p879/uEB4s7eEmZMIldyxyFJO6bZR0sWw2XRduSH1BRi+zCotoXbqbKot6uQc3PLuixFpsRVUEoHUim5D9mLtEEv2SpSUitNgEik3gCTDdBrax5EXSZPrz1+UjejTr6kXtN64c/g8I8uXp6+pu9tsxa2KSHr0HnZMIo/A2rZ3m9TiDzRVUuQew+aQh8LjOYWfLBQnMuoJcJndoXcQqJJGnRexKYYyb/ZL+lFRkfrCWP4CQGjITh3TNGHbK5du0fDzQI+tX38nRf2+HhadNoPTPS4+yzWooO3pMGZteJ+pHXU9Ho4zZ/1QvHca5i1WraRblPx/tnbvtt3nlkvAdf9GCe99IlKnuBsnlCJWh94iDs6ipwXlojyvKj5IuDDBQcUXwTrT5jR2zeL41um7KY9bLwYgY9QHXvLZS+g4fU+IuV1lLi50sEonCjZVRYwoLG9+hadbXdP3HpTFi4L/Zhe9/66CjjxImIOqWOQ+HlXxrzloLIZo40xu5rWPVI9bvlZs0eSyD+23eNAztpP7vG9ZDYgaORSBIMfz8Vj68pQ0JmF5nklTydsS3OxxnO2pVf7uwUxn4Ev7F8TMdC+l5wEkdyC1lruQ2eY0Tw3W95cT9PJ7tVvyjPLgtBb8jJuCjIe2z9bmt5163dMFDEtZEkVGHP/8EYARk06aKLhSs5oUuYsFzkNA1AK7gJaArY2ZEz0GRAPrBwkJLweDhG3prB6j7Ava5ZvHGRMS40GxfpCEZS3M0KxE0VDr4+ttvURx7MTbwPdrI3XvQ88cZFoOuwdOq/TpBHZRK6CQ9Qq3ht6icNBlMqlgRepQ1vV2LDLAfuFXuXJVV3qx9XYGH56YI+8o0C0RGQyK6+L601GNEnDFkusd135Mu5Eu1bv1g0sZcQuZmJKRxR8AbugjTkXtsjUw2PJo7OggUBxLP/ifShxsMezlPWsKzvWj2TT6U0R70dP3zm9+fjtni9CQ1QslAJsDnB4KSf7qU0DC5suLSY8qmFfLRE0rQUEsZArcemaGAcrGrYQuo75kzynz/KiXwktUPrpFmdq5ywvDSEAooCxFrWyxlrvOHpy9d2nX3VTFy/f8f1bj3HT6noB9oq9ZMKXQIRmecdi1sv2Nb97+P0IrUJYAgQ3j9mHlp+xEUbv0Ws5+yLLfMbqeWG5QNjOPGZsdNoeqBmP9PA2x25hjw90+DXwtWuTPn3lWkbbjXKKgdkdj/xVP0QEKE9mOZHwbHN9LmpIkUNvbnmCaCtlh64xqvel6qw2RrdtH59+5DRfzhe6Vqv8bQfUB08QFK38YKtD7R5D8JwGnOrw3x6LQwGpWav88BQI5DBm3aLS+WZV3YTSPGLL4mTcfmUM0sAr47YtBrKxL9mtwkXd67RBfJb4esV7tm+45az+0+kb85+FCf0b0rhpRfE+1r9XpsXSErUcezsyIxxNKUkzRHPfETfMhuQm5bBc48dC+LHEsbjqrjoXQtNeOC/gashx2GyEkNlESwmK5EUW1MLJVMdsdrIlWGid2pnu1m3oj7hq13vbKmApc/bUX51fNddIeOOsWvxKdNNtr+ndGyVYjYZr/ZdxrDR9+IsfpPniMfPsJRJ6vXtk+g9uf0uxtLBRgJAkh2hGHUmTBEuveKGDRFUDxCwGmmdWSDwdY8OVvjUbgEk1/JBekPMkoNjXmVIyttF8zyQvOYcZrbIXuCRDHNad7DiWteEHBOlyV/m7T3/LTV+8rPmXLjyZ4k0ActQ6vRcOTvYLkoNcLOZpHcSD80g3VMsTw+vpQV5uRbYM2211tvOIz8CghE8R82gPhwnrlv8QvevRi+yMjZ3KVQTKrvjWk/Rq0qcm6y603rnYzZfoRWkMK5U0kosWuwd5zFGm17GnpuTsPgcLqchZuA7J/IUDG1Kb8px5b9P7mbTUEf1ClyCKN2tlRMIjxxMEfKuIBXehbU3RiLYVUbFIKFJ3UULj+Q1JqdcKIyuim0+RY8JAfMHavXiJLnVuU5Eo1823ZCOgD8Ea0y3bO9PvP3n7TfXWkJ2/zuGXnrgwfeYK3zRi8SMWBCdtkAhXBYiYIaCOiDLMlMiVAp2JJWDhkoe+0aKNQvtQKIZAlMyJWoCfXH4Ky8KF4U6y6htjU3uBZAEdem8xVDDOFFuNNi7oG2d3IuJLaF2fvbWFIHZjAQnD0vzCaLvlIGJtcRnOVI2kPIyBI9Dntmlhng2MWhjzNxw7M/2bp17b9WphCoriDGKQN1HaHJejWq1FlkVDHXImDMaymGS6bSi+kogvEpHG7JdkpO6FqbM/9gYK+nHKWB6ahhJSUbOelTggJToWLeP14SREcfiyxptP3X7T3mUeC9XgQyre37zMRyW9VPUJVyKzlCzOEJOc82+pEXkybKA4DxteVDMUSHB6Osy0VsnXtESHh23xEb0GTYug9xLQ8tG5pGm47Byexe0UbATtrYdISJ7c6pSWhmk2ZL93GbqAT66O7l5ctxOWGId+nToIGkDbaPaGS2KaJxvsxoDTbZaKVtMzm2k9d18b9e+d+Q7WPZMERnIfKKgy4cWKTSSCVrHwk4vsWs58S6RgCCaaots1b4KjqXknDDwgTXIxlw1vkMbCIHGM4VTpZXbfzpkZCexlZGj8ZgC13hoD7uTOkfV3nbzdr33DvXmPFO9vuHhZA+un1aI973E4G8fB8n4N1mGUAxTriVbkDnJLBWjmtz/D3LA0TbyFxI5HF4lKTlMRFEdbOpssPmTjoLlEDpZzrvKvsYf8du5Eu4AvLt5CWsJsgtpSSPZnUzLcpnXfVOabAS40dR26mTKPgoNEPxH98Olv4y0j1cQoyLH6Ljb0KYGsXcHoeDrEOnmlGRycKk5Z8feRTa8D1dNyGnmYUFOXOF/WbRu2qVHBjaVPZZfOWsKLL/FAV0U+uZodWF0VyIEISUn/3Qk+HNVr3m+G4r2sG1W/fOHC9Okr+X5vB4Y+IaJ3UBlU28yXxKYiRKR6mF2yDqTsHNPMqkRmhpNlE3tIS3nexyA1Hh/geJIbbmqhgdzcjNq+XRMrbiJjinXnUejxRFiGar/IZRP4Gtouijt3f/R/0aBNQEojUTopWx0OY99yGU6WPHS7fuDsHLaOM71cEJhGFTiLFWNpr9Whnds+prvO3y5na1mjfCIln+VyeIJlCYFngVnQbLKA84EPfjRscfcYeTWw4pJrCDRgbMN3xtfaOBuhK9soeE2LAFGoI/JG9KGMeGwky0XPMTBHRusXNrB1bHtn/V23vOqmP/M+prPWLzzxxPRlXX72fh/MwsyTVdkSAuKosT1qrelhOI5ZAmcRiS/ywKTG6Iw10SqNrUkNIWc7S7UZtb2PXq0PcMhIyyUtI08JxxtGNHE1iIwGsoOpnrPatFBbN1Rh1ZMU892Pnt/Vz+xMu3XmMUwUAuEK8DDUPnYfqfk4X1ovjc/8jMqCOx2IrlpRF6NgmFnUP3Hr6zXqpyACO48poISiNeCPseIzTzYxsEx92YXENu4McBey8S1TrvrM7WJO+EtcZrqYuxe4I29LtlO+aMy+8xkAg4716MlA68zdMct4ITI2venEuRfs/xga0XqBBxTtLzz++OJuc+Ixp3ocqEAr6HNaH3YN3Ug6vhao+DIuYks0J/PaOes0hUlLMS6vxN5IoZJZvv5lM2ns0+wTvm9iMwtNcpp47vRZSm6uOl5UHGSHbL927ZoKeLU+S03MxYcLc4vpHDepxSkWPnvIInCm6F6npyHMS2u0Fux5q85nbDjvuOX26Q8cO8vQEBUr5mp9VkwRUzgQ9aDv4mRIcA8YzDxhtvOSGvrWCTVyhE74jeG4Yo/foRLVYeD9YMwLs+XAMWi5b1zG1K6FEI+S9cCEuZR/3fFbddf55v10FYt+8NKl6R8/+eR0uRPGO1ThcAgIQsK23EQoBGRuoWTekujOFEaWMjGc2gQzXGclkzj3UR5I3HwUgNdWBqFlQJ+mh69y+R/77V90lz4xpkVyOeqS7ZRtn7DSFo1U/sQHWdJgtb/a3bltdeLuR9e5dZ/l2tLiEJUc43yPI7TgN8N9mW9aLZ8pjyyvmfNiF4ZrGJkfOvWtdZqCPBB8BtTile+NJWwVKXMFw52HB4pBIBtGJaioOCyJKdzIBATMkMpQOq/SegWnzkaHtuf2sxcAkAiSMEYDx6H2w5iAsOlr/fLjsenbVcA3a+P17v976aIK+KKXkGDOCb25Lgexn/EGqwLvONXWiLdJjXCn/lDVoONNzzQyNQtNx6BVKdaO1zZF5kAhb5yBgZWwMXwgpRoaQlIrexp6R6C9KwfsX3mxYb61QNvaWeuXKqdJpzVb07D75SgqTbGEDodCNKs+Jc5wckN7VgSz1ztTp+ntJ27X69+jTnpMszAXnHahY0xBaDLUUiCKg2MGOTqiA8GiclDBmDYKRny9yBcSEJYZA7QAkiVo5npmsXARYBEigRGiZdlNXBQbH2iiy0aEIJmzCSbaEQG94cRt1rgZD1/UWer+C1wy83pxuTzvCVFIGwNNHcSQl3kBZROhlTapS6nmkDdDufCdxwgUzCaaNQ5ZZGORf1Lr4T3guS09DWinpJF626U8m2TUbkGvpFFWmCM7LT1LZiV6Trxz55G9a2f9G8BGkUivoFC9QI1R6TGsuYW6qYYpKDHZsksHemHh5S0oNLhwbqy298OnXyui7BM4NaNXIYYGD5HWlIznFtchvogvKtohSHdWaFH3fNijCZaXwlpf2M+NLl0iD18iFvto6BONuZyuteN2HG48zaSE3/EXfFC8PkgxWOL4eMfR0zfl696cdZ+cPq6z7gjnYpS96AAlCiNKDDo0xKYY8yiEFknfVppaSkO75+obEH9aTcTrabpcWtU5GIUvXdFbr1KYsyhVgmg/dze0zZWgc0s4mUYix1i3rD2Ra5qkJoTtJwBJlq66Mzsq3jvthB2UWaN26bTXONSlJZ40k2kxFYdn2cz9loyGhWXfdLDKrNda2RiWsWn77TrznNXPvqRlmZLhPwXjB90AlCs+s7EuNwLEAzd9bLKNGGPohTUfKRjwZgry+OQ1KzoCKUPm2IaYavYD6cJADlHRi2ZngRs2ykfUrR8FT03iv+i8GS+dv3j1yvQrF/hMM2ddlqz1ebQ8JLbNSVxTBJZfKhTAItwDDxYYs43GbVsBityMMODDYAOs0BnY/ME2F6oo+vflev2buopU0HtFWTdqjcy40mf4bxoHUzYRnHetXU8ALSn5czt+O2QBtbyZZZfIMBLQ4YmTiDvYled2qNYVH2oxppUOVmvYAYK02eYbVy3zQ6fuwJqTuzcYHROtnPdGCweLNFuquvG+RDd+s0/43D0KGSdCyM5FB6SfKOh7CRBnG/wCH5h+zkI2l8otI7wqWKsYpu2LIfbGzgipAisTN1vxUrj/5OKF6Xev5bvlSUBFooOhnuDNbXMGvVPY+wDhkEgIiWaOLcIs42VBV9y9ZZ3LtYFDXoOh3Pilp875IZE0nmSm6Uv/P3XvGqt5dpX5vedUVXd1t9u4ja+YGdoIMuImaCMzcRCENow0ykhRYJSM0EiRiJRIGaQwoFEuUpRxz6d8Smwlw4fwIcbKhdwEJiMmwhrcNjZ0xiZuwwA2YFe1jdvYfavqS1V1Xc7J83uetfbe//ec9rVtt/d73v/ee61nPWvty3r///d6NFZKzMTrfWORZc0HNkyxa4veA4uFmr3Heha6PzVuOSb9YKgslufA7SjuIE5r6VeQ3nKOwD4b6LqTL9t+2sKXspU1vrVdv+mOezj7jnUkcM8c769kjquPhadJESky/VUCeuKtdaKEmSGkBU4hMw4VkilyZHT3+zUdBRKY2QVpc3oqthOzY6E2Soj8iIBeoTZ5e3KNX6wJYsRw+5kzB68+d9cG+FLtcKb9wJVn9Vnmk78cmQExDV6jMYm1CHtDCibCQnhe9mCjK4xnelqMCZQo7XgeJo6D3kTSsrcpigShlmTo1aVc1vvYV5fnv9lDWUL0XkYTskWKW3UzWUWfsbkoxk0AjUAZDDNICSO2x/fqDHygt5Esr0OBVZlQOks41N4aPrFg8kwQuyJBUbc1eBsEYr5Cq20WO1TLDo5397/sNbRJCAMtF5Z+fZ6ZnmwJ1UD1cjldUROe8fRDYy5JJelk8/rmTAiRVO3SGLCxtYquSvPAi/fMShvaAbFAl0c7k8SnfTi2EPk5NxS4iaksv/32l/6rzp8ZZ9x+J4O5yRBozZKJpZ/VnKjuTw2SmgYT9Kz0FrZwgaMX3osXu8kZLNKkcfSRBhWJV8rija2Vkugva2mi3aPX8kDVbCe4veZgm90UMQ6TfXVUZm9fRsnbCITGHHu3kOoMvHXgXvZib7ByJGTJh2A05otQQ6QGA2Zg1DW/Vm944j7ohf++8/f0K88D0ckBidqSM8Q5nOZtnLNhjgUxENcOBK+VxJOTlo3G9CGZK8HpH/8W0graLY/Vahz5o5GZAsDIS8d2MIenhjaajMm93Xl9XPKlevZ9Xi/icYn80WtcKlfi9jwr/DnRGcv2yBwxa12Ygm2JrhGrNraZRtkwZ206FiSSZh36wHVMqsVsy92ylq62CVqSEj6qb00tO6/GMLnbf64GYR4StSeu5VMS79ttg32PvUMI31lV95b3qjp8zw+WS5k6hPQYT6i8eRFPhTVClVnWeOFYmtMw8DfdeU+orViaAdaxHt7KT+ZWcRRvrek4JftnX32GVtwBc3o315rIniyRoFqTKqHkZb5NGNXZLFFfCqvGAyFlY4/ZkDBzFj1LxlsSCf7ul+AHNkjWCzeu7T72/NXdtZq3GjqjG7F7/jf6EwvdkyAr7CgT061oTurja7FpiHn6ECFrsq+e/K1pCbZT1tKWdIj8iHt+hWPrizG0DTyxk0xC9yTIGWSOdkEVGVbF4kr9/A054rLTJbTAdFbXYQpJwIsk0USgY1B0dRZWh3WzbHQmYhjZ7jR5ZLzqfO+5O9VhMPDVToee/FzOmgZkOIA3pISwFo3T16jIpDO56kZ1XZPsM7yQdmgsvle+/XYF5yjy+cjAcxSFGt7XqpFxn0FHQ1z4+XZ9ZfIbXTjTPq4fOrxw4/ndx5208wGG2Lt4ctyZLSbYGIt08MDaYtaTJ7Y5RsoxffC0WtJWLIgmrB8IJxiDKsFgv798zbZ6iQ9Mm2xukI7gM89z1aGeCaY+PrBNfDkm1YyyQFHUIoMLBhPhcm0HgQqa8qhmcOmzU9rOCTy7p7Qwxk6lHVMHWQprjWCdXBJwHhwWSZprcJJgktBSv/WuVxvXg07ARJEzrna4QsiIK4LxBBZDdGBoF5YWGj8plYzJMgxMIuDUTCjTFpvJFbrE0O0OnVGDNZl3iuzUyahytBeDHBvhF3z1ie4unX1vP/QXxYz4eh2ePrqphL25e1RnWhKXei1zeIpY42NdKD3S9PrYylg1eLVr5FqHq2zFXNaG9Iz1vMZOU1raNNSb5gWVQH9jSYRfIeFpGZqt19Z3NI/o46CrT3OVmTE1MfERqxl72LJXJDUoMZMtlD4OH/IWWbSxzfx7l3Rgtl4O62S3UTZmg9aBMuw4WqXhjoQjgbjnn2Rtnjll4O+97U4mmzHXGXM9cybBnARlbt7eTfhY2ol7DF9QcsgRCJacrUtXD5fwVBxm89AHi867IHTg8GX3JrXcjwwVQ3h86d729s+DBaFsHmTsA/nd9QubNbwXpeJsel13yjN6xZhXjW+o/5gSlf4TqsH0SowZy2rZrps1e+7WVLTKuGlLt3oa12l2Id4ejbMlyZk9BWLK6a1eoslRctYFiEqmOW3NdzXClV6zrvwTN6WYHvvS+bK+jDER9QCCT4Racu+LNIWr/TYNzNNdzwoP5i2oEY+uRx0fkXW88I4XsWpfCoEwJY3ZR9pKCLtd6BpAS6lxmH5Lu19KTMtB+I53P3T+W/TW0VkGVEbN4roSyf4FIQHyhzOHwLiqUV4AVJaNhAG74HAlAqVVJjwPHAKUPwKF3wmbDitl/3TFS3hVMFITUhd1w6ue3nc3ZyAFSNVTevzKs3cM2w2iOiTaszpbknTPqH7m+NbuGZ05nyEB5fm6+uuXBZbQxBDqrWz1Ek1Q/WEc9HshVZdZYCqyASNs5NZH9azUUN1ltru0VffbYzbC2A64K8i07dbUpJVl0bLFT8PK3hgOkq+qEtnPjNDo3Z/rixige7Ggii0bj95mjyRWNrN0+3Nku0xg2cJV3BXQ6j8RoGBcAfA2kgUOKhEQhcsaZMtSzw9cDLn2cByURJ0wz7290W/Rw+h7br/bpzA2eBKogViTgGGNQTNaVyYj6TxStooGq4pdxmyFPlzuaGLhZQbEXkmrqcgMWYZ28NKhQDzKXKDYzdgDIg58xheyjt0+iWGI+N9Fa3n05rXdk0rWx4+u7z5783kn7arHMF5W6coveQF64dfVOmnbPPxHhPC03QiyIag7dLUzjthYVeQFKysMCoNeTdtZG3kBR9UjDF3ZlrZcLL3mIBkoNQp11jWzqgRtAbZCKktXksWLP31lnvbaMzRxySn6smqykR/N1DElqLA1l3oE5CqRYVUiO4p0PQNbbV0dGt7QiNvFQA51NXCqJgFFEgvaLaOepVH6RIme9/0N/T/auZiNmhaVYOJSIjopxJuv7wlkXAhJSiHAwSLOyNV2fIPSCS2ZExSkcFCZ3ijnFk+fR3L7YRNax9ryGvHAAVCBI5PgBxDLjDniZ2hrWYhJ5fjlZ24/4Az759ef3V28eVVn2ev+tx0jXMP2D3OjDpxDrCEnAkeCJJhGJgIYW7Jlj3RFtWTgyg0MmVtqCcfuFXI6LrPyVgN3JVG29yAcLrrRmmzobbLFFmRx1w6cfYVR/tbQBryc2IcOxiyyi3rvly8weDkBjXgBJbKI8Z/W8AfYop7JivbUQHpXQGMnMt3y08eLX8TCySZY9xPEMBwyWbmIMH+zT6s2TtyVanFuSIuBL77z3LcsiVtJp6IqSYZZ2tQegMDECQ/JtIxDgtNK80WnSZQ5IRRPJyR+EIkkPJmfxIMtcYTDPveceQM7sOA0KYVHmDkVV3HDc01fLfmj688eXD+4snu5EpdiAtUruUOy9rTD1KZVDHtzseVrLx3X9Lv10LiOOhuQXmsab3djQ5VW07X12+jFmhlxN7IcZYXhAqMTrhaml+MKbT2+tlr2HWWE6Z4lacl0jRimR/SljLKKneNlHoSUwntGuK0nLKM3sbcCoh4r8fZ4pqWtKkYIwx19jvaqD3IcHFwU273QjIDX4HG0Q4vPmKpJb8i7H479y+vVppDiISBremHV+aE77p6ToMQd45IZE40ZjmvSyQC6TjpprEOisrY3/Z7kxhQn/GLDDF/65pF+Yueov6Sv5MvY2V5cBuM10QvrBmHJtISTy4R1FUA7yxYO+o/ofdV/ee3y7lN6q4Zv73z/Xa/c3b07Dw4Sl0SV9kyX2SrYpoqtAx1yR+rJa+YEPPnTmp57RgaFGhMNrhepVnMCy0V78noz2WXO9J1sS2kDlN2Uh+Gy2fbd9OJnTho1zAyn1xoE0bKOAzcaNhgYxHx0ksvnLrGTRpT+mo+CNLsOHe/0lvhiq1mzomWZ7bjmGKu2HSgbFcoViIOLeRuJJAqrfUyKSeggE4FcNH27a4VQbRJfxhLEsMFUOvcHDT/UfqhXn+8Yg1fD2pkgkxhbUzBX5Sf9xghgL3CYxoeOcq3DlbOhozRSEbMgsdLUHIrYsjFNi77oOsndtZqWOJY2jzUZ1yM3ru7ef+UpJe61so+zl+kFPPtihJ6lMXPGIU2ZczplraNuaeruZWbUY3AltGcO1e9NE/9DvJJXe8bAisQ849gHey57sVASQtZGE7SPtlLCcMX2C8XRSOJpXLOHOy4S79ZdbGDIylQsCwhOXrzaxrHYZTBx7NDnbIQtsUA5NZKJ2LNmG5Az+h4JEkrPLhjHgc/drXyUMgJgDadNydm0pV1HxzESBwIfd5W85kvDXVXhsU6Hnqh1WN9xLsnbXFB123bqsPltA69W3V079aAqUWxZJraBCYOqUY0CiztdR+PAK3oAjhjnpnFdFN1mExLAENerzfSFkSLxf1Rn2/cpcS/rRSkUcRJienecObNESlRwTlwHBW/H3BzDO8pN6c2GjdA2cEgSVH8xRp1ujobHqOQb8oFOlB0VmFgOdLlqkkb26Bo3Q+lWR1B9JrRVbbTUoHveWtyRtM+Wzxh7jrxYEZcPfnWS935dWDM7L5w8dXQ9XHx4TGXfPrfjjNKyCi77p9naqtwWeXNjrWu+S5yBL8rk3jmQGMxjsU+BWyMY1GLTuFLM3J2I0puyPpv7dKYNhIlevCoOMftEVfNUtKo4xdGbRExUrqCRqZnL7jF0513D4SXHLGyaVsIr4jpD+mrYIkurZX5x2wZsfga2N7sDaCPVjErHcca9snvwuSd0yczbEA7XA8xD0ohYP9beZ2Cs5YXKXMzTslksXw/RxetmWCtIfF4gy4wdY93adC98wGOHfMpaTk3Zx2QjZ3TNKFg1cR2u2Jni1IP0be7pr3mQLLlEY2sYeLNnHttLQ8HMmcc+ktiiU0t/f3rlOVWxTpKBzdjSmkdaeDOv6WoN1W5bxD2gjtAS7++OqOwq2J7D2Gb8mgon8CXz+RD17CcYgm2HW1jh12pk8srSbQUlLJNuk8olhnvvbeezGPZFMhYu2UxmlRfZLj6Qq9QwiVSDkx4fPRXtXTJxDJq9QWEiH0M9rLpR8aULNn5lU+PpBSoDM106urH7jac/u7uoxG1yFCM2D0sS/Z3jwy2QjSAalQ0Ruz42W7xhRUHbpZla1/L4AOvQI55TKPVk6Va4Ek97bn4Ipo9I225qVnRFItB0O8fatpOz8FRtUHtg2QpLDAsek9L0eJt/i6K3jZFt9ZzPvvk3p6xvxh7cfsQz3kow0OJgL/actZf21bHFNkeuYP2GxSBsry3ICDjqDLy77EvlHlVj8ISMvmaJm5vIXdqg+9uaJKp5K0XwPnJY/PDZZ314o+lbU2uTbjqKgTRjIpO0JDb9kNtTMPZPzMyeCnjM0pZ3ta1Bb3InpPCG9wFhcTho28cEc16McizB21gzJRTNh64+qbPuY7ur2gS2Fsp8Y/CmczCI9KZ8aRJElg3q9BN+H5EFMVsTiVXYc6R/soQLeXs0xkNTy/MS5crSURorL7FNL8eWdASJsL1sfZWdhEbXhkfa41/Zut1cttJkI++1Sl28m2odb8cWgO3VDFMbHez+VL8qMopMGK2XWXUztE3iF6KCzMxkP8Dc+6LtVm+bsWoO3A9Qlh6l4N1qj7uLul47nGdg68uqjRKVx9AB0RmT5SGVA6OsJbYTBSpiwLZGb8y9+peYkltMjTCH3kltx2kPFXnTZ8CVyXaeKfRhgakn3e7sq1qC8AYv/A5K0c0HAwV0oFeiBTEah8K5gtP8JXPwtClP6b3bX3/6M7sL17lcJorMnDteBITBElsQMOKefkqPAH1JXNUUmTXo1k8kwOgaTX/iwjePq8Z2FnQsUztb8cVWiizHtgjz2qM9kdFM2RoaqGiyUfn5pGVaDI09XtSSQRJAsaitNUKhEn9pb4+sSEfeyPiMb2RX9Cm3T/lXNMPTbFm6WlP5Qs7RdfWxb/+JptgDli7SWNGb/YkPh2Mt80zEsLqs097BRX4RxqXrUwaOSQ/YMBHy4tRmHxZNAq8hDU6U7XgA3fjrunw+UeSOD2hgMXywxfGbD1UAMDv6yCuxJa5kEqTTIHVxSSzinIThDBHU9otOlPWvTCT0nA5/PIDIBCxvNfFZ5/63KL935fHd7zz3eX3djv+X4/C8NGktywYDGpEws4Qjh7itqZCFMerafbM5NGPmepip7AR3izkwxSIPd47ha28BtYZeWCxJc0Lol2H7yBiwWuxisRynt6DaCsjiRE33Cu7FYrLVX1ALbzcFYO2brufvBazw3qV2q7vt40/1b18ojQu6tZl9P+47rkVbsWaSJnNWGcIwwuSVq5jDMFcV372yzDMjA+MHetr6VuPZM/oH422CQaYIWC8FTVlrMloydrnlUUuJt5i7gyDdIbbc4s3hdbqEhsr2k0QyBSlj+dUaJmEEC7FwwwYHmFdCZ6KwTV46TGOk0ejD4fH4nDddG4Rb/MptMqd5cKG7hOGt94kR+tNT/+LZv9qRwCEAbLjjNDPGukfDbDpGS0phCwTSRI7CNMQueQiMiIIjwuBnS9jaGEW0Mha+0YO0oQtfMB3NZjCgiWmxWuNYR7GBuAOjJ7niag8Lm5qztx1ly9uq/ebBDgcx9grKQ3BtFXUJAQsNYs76o/ppID64sUYQ6+Cgz95U3222S2S1eaAtF+29GTLuaLPNwoXFjKQ8DZlHISfFooQ48zAveV5s0Qw2EOhciHH/20PeScJlj5vUmeFRtGHqyUZrhoX2vHhfd+5czmCcWuVJCSONiOwXkySM+uUtJNnQldyQzYSTicCmsq2ZYCQZYafUsuISH5Ikvrhzv0NvJQwODWzc7Y51yXzwPz31SX1OuRYcdBUzNndH38pRxz/fiiKmRBHXWU4BewqG/94EkEyHsSa63rSOdHjqRnw0emVoRNcZeqIBx/hbp0a7VnMMr2Q9ljZoaFun7hgGaUU++wNfPhKDOpqLuXdnKM3r2jRC4Zy2bOzx1GASC6o/evaZGqZ6xeFlNKlktrfCktoLA2uATccKGschVh6F2hBJkj+P3XoyQRq70dGykkTG/ze7ddkJ3JSQU3wGSbOOZa6qjY2zdvseL24T2kQUiavORXwwIa89d1v99EymOMkbqKh6fJl1+ScS1o2cS5xrRNjJQylIVjMF6uSVygaLzkbFXHg7IBS7G/ktpP4Lmnl6Jj5z48rB//zUBT/v7Xh6acq3h2G8POP8JM5SP+cC4QDB6Tbbs+WJk95IBZege6jBVejWTcvGVBzm0EFMzdFYpLNMKTjQzdSXcyNQoAF1pfHKxpMZq8k2PexH1DEF297C7R6bh6LlTHOJKZrlKOxKUTasg1mKqg0+preNeP7ruNuwN4NBIevVwbzXKppIsvtqbiPqpSuLjLrHCnXztKZHlRDbE0iXi4cP3vfTFxnIvJOQhLHeQ5wjlLkFA9HE9mQmdzREjWK952dsI8PuNfo/t9nQ8Og8w6zRmMnnfoSzqQg0GnLJoXOkY2OEKk0FasMHToWDgPGHa9r51BXMKMITOtqDx4o/uXZp9ytP/Jm+KfQ8GtnoDBpfJjA+mu1xxa0aya/obacUGHNGDg/HpTXCtmdrgijzDbotw4dFm3fdPtmU3L9QiT7xxV74nsotYfxkAkUZ3j3I4qr1iW9GQWv2bAAJd4mzZYKh/QX5ibNMvciObXLzZYVP6Gdxu/R82Afc5s8ctZ/U5R9uCyaGrjejfLVNxjMx7Q+5fQ7gwmtQ+uA+9pb7eBWaAR9e1HLciw3q2A4GIFVeSBarXqdGdb8ZIYkMRFCvqP/vicASHT1YwJTAdNxGVj8YF4zGzHZyUTuJqXNDiUIRhoAKmkR2BxvwJDdOFYZ6JewoOhghPqS3h/6PyxdlK2wY3CbpeJAKmRWCEMhEVViWuG1Pie+GNtCxPszRrEgnPhyxidQ4NY2ymg2QFAtHcEgp0cRXS1JHZtDANbql2zp28YdmRIpLVjOubTR9RDjHZ/UCbaP9iIM7cYRYJlV53tk/7jdVIIspBh1RbNmYePyYfhqXHzpovhill3mFVH2L4iDy4iEY6HWLNkfzlI1dI64Y2CuJOFNG/JZNYPYTbjkhmuz4IlWufw92D4eAI176DiTtnNwg3r/LqWULdtif5Gm+imL32rO8gMXZEPuUxD8G7jMlGGhHUhYerOTBYA5uryDgbqxrxkCiwqeFM7N7eKBhfXwBg7McqvehZx87+N8vXyhb7LkfKXXzixc+E5tHxDJFz0uGcHOLj7T7ledwHOsjlvkZm9IK2wUJZ9A+K0e+wRW1/UmNbpYog0fallPikUozR+qpNRaLFy7hzlIIZQLJ9pdC8vbRc5A6zNPv6ikxxO4F4ilS9lA4NKYs3iCKfHTVSMyz3u0+9fwVvW2Ut/5AFq1qsBT8S+5xMb7pz2NvJ21YeJu6HRv3wXhfTO7wqz9inzrGljBqPg53F+FJAt/aPbIJ95gzCDy5nJ7JS/AwUajXe1967+nIE3gqyfsBAFvavICFEn/NrbabtQicGtuRFHbLruuCLU7QqIIrbS/onH3GZN26uPEZMj98bcZHTDw4mN/tD1/5/O5/u/wJcWGT+wxmJgM6brpSqJbqsmlJ7INsrkt8uWFwRwd+W5D0W07RBdMxeR7Mc0JuIqTRtN9VAiSTabDb3qCLfNUH1cdssO7lsa/iX4aB/eTYsxnGa2Max7bH2nVhizRr37yx5e3S2K68aV/R234f19kXgMdKDbhKxh9fzYGM8U1c+WP9dI95xxCi5rGuiHJuajy42LSfCkF+JJfQ91vHH0XuS2hdMFzk1LwtyW02Hfs3U+C4BGtqpGXXTZJ/IULb/VxaRtmy23PKV1DkloLIbNgtMlmLQv57QtB3SKnLhDzenMjtCLNKUlx6IO07/B6ARd7SnoeBgMNDgONfXX3y4Nee+oR5EyZ0JJJDrIHGTQVl47glWEEUUFGqsqT6qlQu+ZVsYTzsGQexgU5Jy/EukkbM1RKOWQEjX8jjvZmaYa7w9FgLU/yR54j1bBVgr0oMHaeUNpJVJq4JljEh6hFANkcxqeM1/RlfpBx7XNOCVuIVu92Ll7ogWHH/fy8/qY9N6oUr3ehbWrj0Y5Ejkr2+uzr4T4da59g2OnaRMVYbxR9N2USCdwsIpFrt09ZC5hLaCSzIw0YO8/Ry7N9FampJiytn6LLUwmRz0m/nPRnBeEyLj9fxLrTR4LgKrg9E0Cp3yBgXgkriHqXHD6ehyyNhR2qdOx70aIVznqkdQoJT087kigxi0TE7PnhSP2Xzvz7159br4FhyarZefaTryNOv4N0JZNkibWNPsb2kn8+JWEf9ZZFpZC7LW8njw3F20zUobMNkZgXonnmYwmAMlKYnFa7WTOuJnjIs6U0v4VqPQQ++alBtHifLxPJhPmNsn0PlRktnbHMe2qOAi0+H2wtVCtQfu/KMvx3Wc83eM3s1QiEJc1due9Q9WwMjvsbAErmO2p+Rr+MK2cSUX8IuP2rWLINCWJ6PD3wGzml2d00JXE27BKy7ZjlO+5KaPgSpjRGSElzZWdLtyRVOwJHd7g/vc+ZM0RCdqLDVYFECpjSsnbufOfbkgCs7J3092KcNXzsSyJw8KGDvhLaLHBY9Vn6V+Zcf/yO/QszzXLa7nYuwX3lOeNH4qOe8Dkb81L7bamKGHE2QxQAAQABJREFUjbHh5QWUx/2NJXsoX7R1poen4myWJWw1pw1tei0ZOCbBPM0QRJCx4Eg0LWskHEycJ4+Oi3fDKfLWz5qdA+eIjF26R9jd+Ag+Mtr7vpEEQ6tL+4mupaqL3BUP+LJ9nN+81qXz6g+L9umLwM4mzRs2wYZ3+krftnD7XjLsyzaSHle4Vt+Tu8e11lgnhrPnz/qk66zVW0n6PLR+mcMJW6FXojIU1jwJHl0PD2nk0OzpyBH7pq5hV+0Q1P4WffcVJQ9sMFVRWxfbh4zaI5c4RABdUlcnBIiYIz7WuOXkPAljfFCTsBZiAjUBVkFHk1rF8n/62B/unuhLW9Hk1ons2ERUCZtYB2p9QUt+gjPGPeOyI6JjEI8pgYs1esVDnPjlmA7tyBJP+tYb2dK2KltzYDh55oOLKc0a5rDtc4LynFZNvzG9lK2PrvXYZQNO/V5c02D46Ci8GNJ3vUDVDM/WP0jke6UIeMvoI89eckwgEOeeGNvKcG2LbCSkPYZwb21KZiO1s52a2L5q74XeONRwLtz2h32blv9YXXz4vvuUs/O0K/Pjh0MhKscA3XoH3n012dve31V3v2veStFZHarBu9pL/gonsPSVKBKpmKBkduBksqY3HZQg1W9b2oj5objEr2grGXEr2Zy3XEYMW9ikNKeEMoPdwe5+/dIndAbmE1b2xdFt96uNrOVuKZlTptzpTrzcXM8HgPTReAQ5A/cgLGufjCMcINXJffFmefXTnvhmaU8DVlynyonXN9BhWCVIma5MWbcbh/aFCphpFxLJVrIyXRZOknCfAis0VbipE1dsAoiO4wcvP6Grqv7/xSsmyGyJyJvHidbOVbPFeutgFZVmKG5G33YNkDL9ti3wiDf+A5fOftRru6MjXz6j7+tm/TrH4ftA5nlAixOOGZJXMpHML1SVrn0j132EtpHjqvGzPt+Z4kQbD1WxHPaOGwISkoObbGQzqe+2ONBz5yzMOJBXSUMGmLlTVKYrCmNlG7Pj4/c9+5nd+579S/OMTStl38R28lY+55k3qByTtLQpcQTHlNP7vH6R8jk+0MEDAXz2qcp2ZRsGWAbGnJYkLkN8SL+Pk2kPhx/76ngmQ9tEQgzNlniQsx70qCnU2RHunnIIzxYTWZiKW1W4Voppi7R9TkTzrLbFJ9AfP3c5n7YiYnaECGa8bZs9Fs6W0Ru7PCodMwYwKe57p51i14Oh1nyzB2mmdH/awWWfZac97stn8J2p0vcltLHqS2U7mSsdMpR6LozlZrjtouWqffIsj05+teErO+rzh3GvTYMSsErV1Yss49NA2WEBedQBObHXOTBM/4e3ZZmTooqwsrTonPdcVkvM48DB7vGb1w7++dMXYPItxtUjBja6Y6HmjOvJcl0o6/vSGtmWAxslirkwAzET5xF9yisWxWtcWOZxtlb/5Uj2uXW/MVPeHhpXMdrlnswkbUlNMVDHjju7BE1WpviE6B00dbTWssWYwCTiCJnBc1GnbXbf2p/tjpG6bT9+5Wl92orv+XZ8uMBP+RJwupxxRYbNYmfSspPG42zjSRf5CKs5C1D7KHMkmfuJIWOTzMV2urA9el9TzQQ+c+bBnN0ckUNsNytVt6nRj0R13nXSQ1s6y+Fk2BWOZLRvz/cYxzWrQToogRxx1zblQKKbClL1NNBmxrYSUNzgolHHoQCEr8UkqXUYmkzL13RS/HePfaQ+1gjBunFfoC/j/du0y5m0k3nK7XramTosn7j6FErpVDyU+PXi4kvdSIxI20IUdcdWpeMqtggtndq0gi2AeZhjbrGNr+gn6+zTauyYdoefhcsOWFliux6zCNkpkvfYqfu+NKco3Ia3meptOXbifvxqvqgQbOyIu/uxUV+C5su4dMYbshqn5geM4+0HAMuad42gfUyZMyEEw1n8zllqfmqXs+dPnoEfvO9+v5AVt1vGTH7JyB1CdvzdTs2+qaGMuvmo8+N2ldzq3ZFsyqtFaUNAakI4anXcRzabSkFJM0xNAxutzp7BTVX39ZWn8DSbzGjWGhgmzuP3Pv2Xuyf1q5Gwz2n0gIuqNfZZKNp1RrUVtrkFLXvH29LwpTfPYO6L57rek3z0+acTA2NzC/fdUk3TkskZbfoMzPcCti5W07Lji5fZmw6kyfyKqbgHSayau/WRFsjzPTevN6145jIMsmrA1jsp7cSiNmM6pTTXwNl+7hZMPq1PWnHpPOZERtixkWKfmByfBfLVGAgYu/03LrL4LC7G5USmhhvsGnP06NqOuufEVwE1xsjCCxrc4e7o4X4BC8k8A9M7On53h7GeWZNP8ljJO/rYeOhx1YlOnZA5I9OLbcaetjFzMZzExcYZeXNWnpuBmVkngwmyIGfWqCYc/uXBYCrsCbTD8HNmT+iOV5sPfutpfdIKWvPRimXCpd2flTUAskJUayRyLq2boRffl9wic8JPdvPGU/j+Qj/JMwu+WtutnNm9IUcoo2H82BBzrstj+E7yJ1rk0+PCWTxBtby5ZnyJsBgW30xz38HU8s0w9lrBgqOIz4Kut2AnTInWBMHkaf1D8o88q6sadQbOcYnLT4Gaq2PqOPEVf203YpGJw7GpMJ6x8ASTdnyKFx4wruaYOltA2678hbx5qSnz+S+9+iAHTZXDw4d3fuqYbsdjUydi5DhpukJKUGGMyNXoxXbiF5JZkLE3c2bZSSioLaUSUQ9OHY06XTyuRPQbmnYxKCk9U7ynVLbhsUGFkSAsEVg3HMvuty5/8uAqLyA5tiEXEB85OuuXPnKCD4I2c6GeOdGmIENHCVY9HCPTX/RdH+w+f+M5vaX03O5V5/SLnWUXLQyZ7xzpq8SluSIwaZr2Xb4sacue68RlEnOPCB1roxNodEVT+mnfluGK+7aY46iwqurR0G3sFtFxemanqtbJW6MMHUnvIXUu3bi++72n9UML5p4gx6JuJOKvvRnywslq3445YId5vaT03jEqstjHiiNlrLwFcYqHno+gB8oWaD1axeUdrc1ydOZQJ9lZtmfg3e43UEGcO7R9nzJPJbk07vOyeMpgwjY6bHxP7G7bCbAAyRncjpLktRNkPjPT6EF3RDF3JgTjS2mQm0LALaCxoWHt/vzaEwcPPfdZo8h8MwroNjUa/7llXAi7T51b0GqbB3etoaZXL2JZro+HcBYwNpfTjfros5/bs8WasnKGPTJU0sFVGMsZvU2iq455msk4qBe7cGwlE6fWab4WjnCfzsnu4D5L0C3reurTip13oXeVpezwUwwuV/LyARnizi4M1NtNNqy9TTNB7tC3rOZitfP45c/nCdzGuPhjN/AhsV/m0j4d8MRlPjOebgcSWeJkbij5DHTae5fQPA/W3tcTZPY2l7/6G0mqSDqXqIXY3qGMLGFmCHGLrdR7XH7EQywdCNUQuBhel78l4nQqPbxo/ZhUKvqm4IRmRbCa2zljtdeMLTtXw+u7nvwTCey5FGo7ItUQm0zeCZi7IyHhKOmnRYQtTbttctnMZop1cM2lGn/iNl4cl25e0X8FeLL4FpwZ+tC+6MefNeapyEa87bF8ILeucI47fGEHPx9UYh0vbTF8VZQbefEl8pOf8ALLkPvePrNsbRXpyWPi9BJZqf6SyJ+6/tzu9555bHfTv08Wa/uxQduWDydkxVFzhUV2cWx7XJtYaxtuecMNvtcxXAuP4+wYyo/jqhg2MUYmPj3/fcvFZqHePwMjex+JljVdzqxjimf4TH3uWYaaClEUphJ2n4svNTjkHqd6ajrkJeEQSGzQFim85MbLbJyZkWJTCj218QRWVz8+p5cQOasD64lVjf74oece1fNfPgE1tyi+/XwVhO4+S6pJMQqmCs9Nt6MdGNmFU0p4rIA30qktVMmdNLRl9qdXHtN3VPU9YdnGBdh+4SuU0SDv0mjVkKhMSXy1zBowG1xjYrtaz3RuaWHsQHbicXOJppHtMw97Ky4cNdXAXNLvXdbStY6nrd3x7tNK3j/Uc96b4/ltcwiv+JqXuKatdN4ekiEc8U+MxTW6tiMLYpcltrx5xBJc4oR12tHBVn8IHVfh9mJ0LPpoAvZrOZHAh8eHuoxuF4ES/vZew64EXc+sOXPL3nnRPNRd+sEBGcFSulYrcxWxR2Vb5Z1npBMweh+Ho/QIPrNfs4K/8Kd20AWZCf3PLv/FGoXavblIXLWbo1EKy5iE5cBLUogsxGpHu29gGazPyFW3vXUaTbB5RfrDTz9qfKRbxORkCsqvrYNGPz4U0pDi7+7kSFzIKc2WVmTdNq8RSIJsHWPz+Kxp3WRspuBjPWylXHdMY7MLu7dfxweYP9f7vB99dl61sB36Hithy0G2StsWp3W147G1uMcwNyi+1pjddkIi37fb9su9uQfPkswtc3yJgY9PbZ7/4uNEAj/45h97UCH6lyprCOBUcKn7SNo2LXmH4tiRUaYuw64PgoiDE5/OxEJnsF3bg5K1GZBrEFrhFjU+Hjh2kkRi/QLiPw3WWXqRTuvd7vf1iavHdfbFV98mVxtRp21MJSOiTkwP15iVpe0i8xQOL9Ix4LbphLYkz9ma6TPPX979hS6lDc2hrYwOSzP16Nr30q/ngkVk2/YBKnPJoOS/ktBydHWjT29KaLekWxZJ2LigS2p087V1+nPcc/dMK1iyN2K1amj/qyuXdh+/qreKBg5pYrCsN1bFhar9xGbZ9SjAqW6zTvh8hKHl02blsh22NvY+3viacRHjypVOfMq/YlD70oeVm+DW0lm4ynYHR7t3jzMoebPelxDiEjfznqEQWmTknXPPNW6QUw70HwsgHmdBJyrTpbOShEwtehVabqgeiSygZqbvQRWo7cqK8duuXQPDkX3sDn7/uc+04ahP31yRJppE1DiG5USWsyQBPtIGOdCSUdouWHoqPpuXDbaWdq2P/+nHBJ47yi92mNFczRdWnmWGvX1G3izDDw3sK54VFcaVxWAgLh1X94OcF9ZBbzwOX5FOD5NxsnarazAs3Vy+sbu8TZBfuXVz9/tPf353UR/SIB5KbIRVY2OrebbMwoqlMBtck8Bnm+IVqNcNX9jEDn1hLJu6bOf43WJwEhytDU86lt19cPLyGfypCSy6B03l/El4WxdT5j0gIvSdqMkfzrbBJazZ7ufA17TXnEQyJhHBuTKXk059/QpkCIL1cARV6USpGoIXKJnUjZJLcon/7Pkndn927QnIzDcXptHAyt7BjZ4AHVli2WjgaztGUL3Iis+y6Kwvm7xCXTbDVpfSxzd377t0cXddmxWxuUxV2EQ0PIHYlsKpSnTph2xipy4MnhM7xDC4WOY4fbQ0GORuycaxskLFk+fwA0FDpe3LbkQZOYjeRbS7PK2fIXromc/vnqh/1RoMNnm/nlc2YSSJ8piNZcm8a9QuTHDelxsbwzBb7LJXW7bGFu7GuhZB8N7sFUfHFY7Ehqy5Bs/xswe7X7di73BqAp85d523ky5Bw5A7zz18JeVIzE7Q/ty0seB1TxyLu8jDIfXU+2xbmwSQNAySGl/zjWnZOM+ZiPZQyWs/Uioi38svpzQo94p96wv9eqTh8rm2l0Ao1FMVWfpYu6/LSpuuWoG5gSCuU+3QGsLowQSnyqX7zYOQNs+93cZHCHbP6Rcwf+/pTxthHUh0Jgl+yOEYZ+Sp6wg6iuHXPOEyXUihc2vYOZb2MlkM9MHRy6ZvpakxFESG0nOvW3tpP4kh/GnnLI+EeeR+4flndu+//Fe6msvPwCZJyt9wJCzgKr2r02Xdcm+9/Suu9hFT+pG1neOQsvW2M2aLa57EzUg4c8Vu2kpcsubBj211uH5w8zfp75dTE9hvJ+0OH+4zauZdLJ2woh1JHBfixVUXux0YfrnDHMIkW6O/dFOULKDIkOuP0RnDPPSkU+tundReSx3Qu03iYgeP2LjTVSHgbtPH7yx8x/eD+rZRCpZFjg0BE5uUfRy4wRltrIB3P5IRB+FVVMYUbuKxa/b2Rx+GuWnBc3/sxrO7Dz1D3Ku/9ibZEgcjjoZjbjDPEt99DDaX4WDiYT0WX7+6a18TF/uwTWlfXk9JcHLgANt7cY/g4jdxTE5sr+trgL/39Od2f/LcU9426/bohKx9k1EscxL6/YRkQ7Hpps+OdoTjxr6d8LbLUOYOg6uGV7PYPMFgJwkYC9SvGEvscRGDfqfm3Q/7o87NMOvtJ7GmfHd08/hXD88c/kQGYQ+lnW2G2j2G5eQsWa1rYrKlppjzYxtI9tTNpVMroA06hYxxSU68oRcLrlVg03K2M0t4urtwGGesDm2W/se5dMaerkqz0mY8lFx+pV/eapxYFUatsHR/MFoeXnRsZNUhSkXsjEiaLumlT3vLrp5sHrmWLzu8+e43WB9b5gJ8zZAcr6wZRdh4twAdaCTzmEj6snMgiFm44MdieQzDiZXNmB1h4mKf/haMXavfa+imhJ6T4GZ86X9O/zTuo8896Z9/9Vg3UVX8jjbj8iM9lMTBQW00Xgb5bS8oWw6UkrAaEyQYGNJrmaRuZsXso8ZUEoliiXWva5iIJTyGsG61hSU91ouwp14+Y3vqGRjFbefP83aSL6Nxl3Cz6O7LATcc2ZlrGSrG3kTgEhjBJcCMKX2eA3fppAPfg1HtEaOjzRitMxWDRJ1JiQ627tcrz+2g5KOr/u8/12exSJngWNMPd47pt3b6bczQ2K57/YgwUZO340SngXgs2DV2+l+l3cbkWC/YPFlnYki6wFBnvJCVYst8+qU10C2u+8RImypRbPvBAUGbMnElKMtmSI0uSOpp3NbNpstInXU/pOe6H9KHM2gHCy5XDLQQJg2nnWXeStEPjGLNrkx83kNpmid2vXNRTP5p17LGlQ/v1cj6rI7N9DF9W17cDkgUcwzHl37/Tf/mu/B+WnnBBH4wP9nxq5kOuRgJSsKGKnXcV6hS0KdUWLLr9n7NJTQJmeSD12Cxy+uSvLChY9NyT4G32+gtRV0BGF/tMlkqPrTxsWuP17Rls0Sd9sIscbYGTuxfrOhLWqwVgDXREdNkmxYeBxoH3QiDi3XLjIPFeuHc7S7oa4cP6v8y8e2lbWmL5i0SMwUZL9M/FrO0fXz7WPGOyRZ4cqjN/MDfb0ENsnCZY2PTPqijbb4IWn+0++S1y7t/cenTu7/SBzT6aYW9OSZsGxsu7EmCdQMkedhbZs92NCa4Xo/Yta34lPxsx9qSNs6eDUX44hfq3BOPO1DYSo2Kd9oUrmK1bxPgz3a8HvWC5QUT2Ba3Dn6jf9O5w9rWbZ6Qp67lsJCYqiqRyVH63J+s58DCaP4JWDXF+pmASfKcmcEECjDTQosy5XQsmpzpjuPHn+eNfmAFrPbs05JuuFg0BF/33rRBT644op/7Yl2y+J7yGQvU8O/fGrHKkX1ez4nf89Rf7J7tf/Fin0QwfY84i3vqwpo4+rkqoC7NEYu26/GDCjq40WcpK5ETb1tOXLDraFam4B7X1zo/ePmz+mdjT9Sn0dp+svrDMIqiE7ATvD0mIUdk7EgnY2rkFMnGPZIp6762gxMZuPBZqLQlwh6bE3fmAjtjikNtSsvSw5YSrImPjt8V2enHsT1PV+92P/rQh3mV4BVTf5oJsna+1XuMDpPAHdKkkvy//GtHO/02tHUZDgsRSBKaRF5M3Iy/6E3KFJWXHr2mM2f0fWNwx//95z908JGrf1Vs8FG67t4X7oPPI+nErS1pPStTNn1M2dqa7RHBEKUxuhUrPrqcOziz+6G7X7+79/w9LSLC0U6jeRa5BzFnsBFtuOWYdifkUzX8DpEXX4yZsCWqgSh36V/VJfKf6eri09f45QxK5MOneEbb2smT5/dKAvva4mDKdpr45t7fZ51G2KRkPbsH0RfG4GPO68avbZtpxhLMkF/4/Tfd/53dO61eT5Wn6ZUEB+/IAHGiOx7shXZkDDxnVpJmbZcNlprM7QSh0wtZ9c296Dj6uWuv88YmCwIXnuEjufUjduYP1ASaVXReP3uZB6IG8zG9gNXxLI/lGhps3Dm2xt3Rj7YQhutgMuGrydmAW3MF3cdVAyalpa49PWqNIEM8GbsFOjc+L/0h/RgB9+de6F+0eLbaonw7aGJIP8zNn3moEAvTuPDETrLB0zboo0XnnjGLfCDCyXPbj+sTZw/qcvmRa0/bJpbzea778PltvXgIpo95cSVzhyxXFj2G7EoSvCXhyAXglGuH+WFjwNwvLKZS+IyMfICwiR0jj3ixkcwzwhnZd2jSbnxRHZ/ZHb4NN1+ovOCr0G102/nDt19//vgfd5/amUEc8sR6nCxjNEMVXMu73u0+e+Nw9/rb9Ia7RRy4xo6Zpl6urBheEDg5DTHek2SZVXnBS/7AEasqHmnyeC2iA159vsInmiTtuOy0UHEm28UHsvmIz6agSKJFaA6E9FOLEUVIurLdiEQM4TGZMe3VsVtcPPE2LGJH1G0x2xf1CjXfI/7eu14zzsYD5wddiMOQkRCkorJIB8cM38QF3zNCHW0Qs2+EVWXPJjXaGnFL2aY0LT7Qc/ij3QU9z/2kPgaZr/7VLLWZWBLrmFLTmG5giOYkzjIu5yvLwuyghI5xh7TdbtKhZh8tuLawCo0anl/qZX6Di2X4e606TmppOq6qs/YHu5sHR+8H8YXKFz0D+8WsAz0X9gAISRPExKuVjRv6hBk9ktlPb7XH2Bwa7Gev97Ig8+g1ylr8UOPKutJLza6YO2EyoEp8zEW1LSMyE6n+1HV+ME63MQD33A+XR2d8aQghNuNYEnGEV0c58bgK2x5Xji0PPcrW37SjFfagypMqh57DKdbHfj78IX3g47ee+JgS46nChG/ltPvBMPkTw+q749zGFPt9HXZ1r/npOWDOrXHND6tf3f1/z3xu98+f/KT+Q8IT+rQZP/MaW9uA811nUdusz9NBRO6oitN2ddblG2Scqb194fUfCFsMX+yP+HVDe4f9Q8KBpMRi9iM90e+tGfXGLj5akWRGFg74c5UBQp+1fudD991/kfYXKl/0DIzx4dHuHUcHB/9OBrENo8kTTvc2UyGhbBQf0+bEhkJ9qqduHmqi6rd5R1IKJ6WwWpIU+pLQ9RnYPR1aT7+K325isUlgmalZj/PqQ8irz12ylPZlEXYpsrU7fMAz5ei3/WLBp278jUdiy9T3AJqj/bX32Ycblj7aE3HoLw9I4aA9QqqY2wfWttPxWV1Kk8h/ceXx3Xff+arNGTmoPoY3EWWePBY7BtMRdXTg25PVRrTfRhvj+IStkLnUf0KJ+4mrl5zA8QOHWoWJPfzxM7ypQbu2Aq3MVvtgvj3Xq22MwkE7Os+nGcKBtNhV9wr3eBEJIfNogsa59xqmLqXNVrWE0GxKbAtm9YU88alhilvvMvSLHCqKL4KS+kcfevi9cvATQb6QmeREodLBrDWRpbR0t7vn7PHuH3378haIVJpYDZVZrgyKoRMxEwEmTgZTTW67YNJmFMEjw+wf/eVv+9tHc+NEjgUtL2wIqm9FHV7QagFlnOsRZWIuqau0F0O8g9TdA3A7qC22ZR2r7QZkNIpvenjZmdt236VEfsNtL9/dpfZa1pFF3jyl6a7jWxFRvJB9aznbPqrfvP60fqzvRs1v62Cb9pL6L9pour3FMf4usd/vQ4xmkXuvpL/a7/uBt1OufWRbSt7LYwzayR9sPwBsdavdahO6cKj9kQ/e91NvCs8XPn5JZ2Ao9FLROw6OD39i7CvzzseiZTylWQaguOokYt36+RE+jcUHOs73B4NkNs9LSmIRw81Ee97VUdvukuRFqQq7WqicjGzBZEsjKuyvHN04fvzmc54pkxT3nFiwg8es60S3l3jt5SXMsMV/2mFpL8QRK5x3m3V3MHVsHpCRYxT+9KOJNGNDYjvGaHX7pLe2c0b+6DOP7j66++zu1efu2n3b+ZfvXnP2ZbtXnDu/N4awJoqKSlRjfBVMZqCP2FCC4sfpP6vPKvM/j6lvroMGpQVZo+u2GdShnwTDv3seX+M8OtZq4SmrgbO9MD4diM+24yAGx6R42Rz2Ej+OweOY3tyqcwrw3lMLqZo9Qz0n4YWPQk/O7DYc019Hd+Zw93ZgX0ox35cCBPOWh/7wgiK8d8UTbg9xlRPqNvStNj0hZPz3X3t99713mqXOshkgGMZKNZxgIkkm3CzWk3SU9in9kHcb/Z9efWz3X//V7xoJNmVtzTa69OpYE2/5gGWcK89qEzk8w8CsLN4cU+m24zIO+9VyyzQ1W36h7ADrLisidpF4mnbnDs/sXn32rt095+7YveLs+d1dh7fv7tI/X+etqVlWu0hv6HnbDb1yfFn/vZGEvapvStG+rPekb+iFKRfF0pYl2PStNaBR23rMgHj0hGtO25iZ4mfMYw6bA4/dFk4YRkyJVdmWxBUauKqsCKza3mp10p945EnfInAV/cYWro0AzMGF373vp75ztfxC7S/5DAzJt5y59auXb579x03IwPLIOCXdop6PQcugNwELpIm6eO2sEljvJ6l3eKivD+qhbTN/5jIUiJQ8njqN0aidZJWRbSWk0GEh8Og2wk9d58velBd8TI/ax8TtIwu/zjZuKaLPkDIbElS/pQbZG4NK1LIZ8yA7CxlGR2Vmui69GdLHX1rtKT5bikliHf4M37fDOjbUN/gd6utP+z437IET+3YlMeHedeZ2f5jiZiVmkndN0o6446taxstwHT39RnsFCdvCxEWXAiZxqiU9L2ExLsqQVwu98ebhQAm296r3RHs2pcGBDrS4a4GmL5jMXsfiligesBk0klWn1tzBmz/oaBlLeGKrjX+4e9tk+eKtLyuB9Z7623e3735BXv3BjgTR4bcz9XsgS3CIGsm2oW2YDo8+j40aHqwXSPOnZS2jVuEBOYOWTDUwm3kerGve0ItD7xVLhhFg3j6aCw8IbcqIyd30xhFHVZqL7tzsYYURm5QxhPgpio4nrsuOAWHkwVGHIcNI23ZWhKg9xcvqeXEk08yRCGsMeX5RmHI0GWFLjxBI7Bu7vMjIjwlkvD3qEeQS1RJ6DWHrqYSyGH4EaAwxmt3hSuqVbWx0NYysqS2JA12vQccwJdEkXnyta+jgfeiIjHAc2WCm96mC6IlvYbJXyyPUENSgDY3Hk+jARBgO1CmKPet+8f0/+Le+pBev2vKwG19K/eD9911SbO/o8D0iB6loCbqGlTr95GHp/B8LafeAIv/ktTO7a3yn2Ap/hDJtqTUwMrbH6nlH6XVtf5okALC5dgMz2U17czyit5AAYdHLZT5Zr/0wRbOVS+MdZDq36VskhpU5dhzZXNHASLHN8Nh2Uw5h84an7GzTkrbb9oNsf0uce/46DpC5xyZsHLdv2QDrsaPtm60q3o57yzR5xrjrQxjNQY3NsINv7+Z4V1z7XO3c5q0jWfP2UdVbrvyMLw/t3PqtpsbgJ3FwdSFpxULbfLaa8bK21rlmb+bBhB2VfcrenO3AFowFcnJ49Lai+JKrLyuBYT1/7ebb5UpvpHbYDk0a1ch0H0Oj4a0LFj3HtNGsus9er1A0Yk1YyIVg8jw5Gi9N3aMzNxzWpiFNJbZnC55wOTDpDvL/jng8MC/VukBw9a3bWcTuxRG9SNJCWjzF2/KEGc5YzHa4pr/YrMyFNYna5g7jlqv8O/ZowrUeF7sac7T4WHEdVctW7iD9CSjHUhvcJug6UTuBiHmdv21CeDzEUhGkVT5a5ljbT/M3cuFzPPuxqq89ASPHvkWiXj2IDG0nfCGHXOOyfeF7TJOvd3JvTixVvFPZit6OEqCve3axYZHvdPb92+8qwZdcfdkJzFn4njNH7yAMssU3ks4uE14Px79QWfIajcNvvY2yXvpiNi+WFMs8a7JpGaofK0lO7i526bNs5qmFppgPABZbJgMtMh8xZK0ReQOZj8XMchg/jOYS0cKKs1JZn4KyqLhot02sS+vqpKZ9oelxpt2a+M9xy5heoyeivbxAJExEjZ9JyS1JEouVqbmCsgYb32disVLTSnPFyhX3KoelE96+NzyVMBXR1tfKn1h6TTohva62rR/M54FF/PhZudRJvx54uucErZgTZ/vJCJqDOpLWp59scIZAWSWJ2z1i8X6O4Fi/u/i2ofsyGl92AsP9W3/zB94m7xfJLYeluGeytvcMIztEu8Rjwx3tunv3sIMOlMCbp+N15pTGGctgm0PmcIVYbTvP0zqigc5FvwPSv0apvnDWXDnmw9cOxgzYm84ywpy3IsLat+53r5Gr3ENCAG8F6qr8xPNkMPQU/vkb1GWhynFyLN5tXMGFjyMFP13WVvsvBsdWetPEblpHF6ttuzGeR3gGl4lm3zpCD8uMnX4/AFTMCwZc5jR89DM3xV8jDKtksjX3wtE6+9mX7/XtT5y+nTgj9wOLPWQs2Ne9pPafbejsUPjElJL9WftUduxF6S++9ys4+8L4FSUwhq++7egB6sxuhdiJ6Vqq0W/cWneboR7oq4WH+mID4XiwWQUg6s+fn2XA+svskNSVuOscVSYKlv9GqCBkoD9j+U2psYmYQPtjEdTKwV4R1zKmb+SU0Bq2bpdZLcrQlY8VH91A2A/WK3v3w5qej+LDMwEnXpodSziaCVjanSCNywayP9sW0tzFX2PqHjzcmfrIbD34W++5HTzTX9sYt+jD0ricKY0FUzg/R7VXNJVEpWceEhF1t8KKruNZNaNNgjqBJFl4Yi0+DdbtoUufK4fBIcToDxxWwYCjZMvSzty7jk778is7+8L7FSfw//3D3/fO3U7/U9ijZKSEuH+XaF8mHNuAf5SILu3Y/cGz5yyrBEXovi5p1ObPyZlZkOboSB8v8aQZBpiFsB1iA6mNCd38WVb0udkwaEtaI0OTgHIjQLdhQ9plcqkVs8E4MFIQC/pYTwbsmzGtPqKZt3B1H4vCwVvtyYOue2kFLVmCKH3zBW9Wx1ncG+aW2XRoJkPpK55Vzq+AdL+j6v5a238hM1lT26NA0rg+I/sMW/LWDZzHO206wZiHzQNExW37es47uPqMvIxt6NakRi9XHTUt7uyZ7HhGoSKB/i689wf/rXdF8OUfv+IEtqtbtx5I4q6OZ5gJGR1hZwjUDtt9d6p/oF+X8GW05tRJOC6LgxIbA+ZUWkmaM7CSOqRN5jrYnHVz+Q1nzs5zYpt5K2FRKYlZRy8Y/e1tlYQpktjGuttYslop6mWF1V1xbR9ZH21rw/avjrkaEdYZZ/N0vdiVv2mZVjGoWvuJ09PheMMTLLhZ0LRtUDoDMW/cubH507JR5DnjgostqsKvdfk2Xz0IrDi3SbbCrQzjebZjSBzGLZ7sm21EjGwRYoZPt7aPDZLcwBjX/ebDVrfEpBaw5UaPeyXy8XedPf93JfiKy1eVwA/92Pc8eNfh8a93OKkTXnZYwiTk7m9qcsp3qQX65NVz+mdeSrecaT148OlX8il5Ww+pJpbX0WyvY/Y1jSHxjEnhFcJhtAb0ZLeopxpUJrpbCWbaRt590L3gaNis1PPoFqtZ0mhkx4K3XHUPZdp2KzV2PDjZbrB1tMUFX/G2Bjt8p59299vDlEZDP6Xs7Bfq9NdjW0z+KaFF8Thtm7nqcZvH8SYJw4ENrZn4jdsfm+WF9jqIC0zscwxmOToO9StRx5hKLmNzTJaKudZ5rncQ7YX6RBtOHhw8nhmDhO/8le+7/2FcfaXlq0pgnD63O/olZVD+c7K3H1tw776XqCNp93Gy/PDTtzH3bBXuuKDf9ZBFwmbGl0pVXWtBkPgRpDYKiW6qnsLwd6+cmCeLAFdFkGVRx1xeEHv1kqTFsbnSTn8eZys4+ku88VHHiZ2tZs9mWH3Ak0intFqOORzYRzqY3HcciybCfYxQY/bKbMTaDFv+4ZX5KmyzxqJ71P0AmIDxNf2t+rCOM+VqV16CqAeDkTjNT+zx692wrnfLqwbl57e13o7JsmjMU3H6N7zdTqKOM3c9ANmr9CnHuzsODp46Or7+T0rwFVdfdQI/9JbvufiK23bvcL55x9euJ1bfmaG6O9k8Y5LhOvKcd9L+4OXzNuQsq4wTQ5N0QnunCpwsN5jDmBx1VEhsn6mF7BxHfNdhv9qdRYC9fIgRdxxLas7GSWbXpe2Escm0MZ0ZWgZXc0wtLWsWf8bRj0kjXCNKaa4Bsjibi2bLgxv9QTD1QU++/VZsHdXCW1aOUxai89zTX3xPq+kvCG1w2+4n2IwmuDq2n+LuRO9kwv/gcwRJ1OZwHMVh3Eio5p9xZA+1ZZ0xsV144wun7Sf17As9bMBlJog7sdjfsb6E944H7/vpi+i/mvJVJzDO/583f/cD+hDnR0YSd8K6BpHkXPVzWaPtJL56dKhL6bNMglLEZ1EIxEDGwDMSmWktYmnqstvgOsCx9mnfechX6DKxqbM861SDQzpr2pJ4YUrqTdGLJ/2yaOC67PNs+0JVhJaPaNVj9PAMKhDcKBzTbul+zzBw4hxoxyykBKFGQ5ks3W/+ab31YJzjjW2WpplitR7bevU2uAlGMWQ25xms7V0T+6m3WJnfPKBzS2s9Zryt36+N5JL6FF/owtTc6lXckXOsJMU/OvPMPbL4u/CbP/h3HvAcfpWHFyWBieHw6NYvUWeKWNncOzHRtWy/BsPN+aac++ClOwJfjuL1dokINK4swiV3OszcSOZMcCwKAyCTi8AL0Ho0IZpHJCZ1ncOK29PWok2cvdlnWNJvMm9ZL3L5WdoORkBbjDgT4TbO2O5FEjvssa1pOmFn0/2Y1n7aU7IySDriih/jDAluPWYkNR4PbnIj7VeSexxt269cW44/+8S2E3224z8JCC43cCRR+Ubue+FabvRMQNuv2GKApeONv3jBByU9MNPfjMW2emw++kkDXoTDi5bAD/3Ydz+oMeh7jDzt7GGyc7x7JCu5lJoXHSRf7pbVgP74udv1HeHadSVrRvHBwK2Iq7YHOdH1sp4jWsdl9FLoHL9KX5k7dZINTOR9BBe/5bEsI5NOjM2VutA+e07P4en+tIgEH+Ep68GZfvmpzTRtwtrWkYdnasqXnwfGy+rJ2jHxMPV95VmZG/HFcGJ2YBMXlu4nwrDFl4+MsW/EbJLYdMJaXzEPrG3Cg6xbbY8kCYY2SQpmcx+cWlbWr27BYN+Shb9lHXfVwfKgMR8Q/GCnw50HZ16US2ciorxoCQzZ87cOH9Az1ws5n1byKL/6zJqck3yTWOD277vdB3IWFrJ4aiaN9OUyz5Fj2gimuHaOFF42H+jUJfYB33ml+FE4mPTVrjVEW/diWXCAx1IOg0jaDvGIJZ1hg/3AFa+xsajjihLafjIUx62+RbaP7/XYHvoBJv0gMp3L+Dx58NXd0S16R1S25a9GZ+Q6FlDpl8cEuTAEgbY5TtrMxPXTCFn3WXhi1aoXp2oixDcTcyYOZ1lcgcf65M3vAZcubT5+GVxipJ2z9bSeiZnxStM21N2uUWJXuIv/0cv/2gPqvGjlRU3gh+9/4yXth/+AhB1JO0Jlp9Tdqdd9AGn35Tb1By7d6bNwBh+MkpmpqY9ZZnnRZFO6Ve3wzceJttntXnX2TgGZ0JSebGovWC+AISVbFr6sXM3YWookNvahMEzjiwa3BlMs7FHNZmp/PHo3V3MXiqG5SO9Y1TF1Zg9V2NCz0brvZm2uMsGwOBJdMLFZ7aINb2PoRdLa1a6xxtSc4s7+3Jj8Fkdp8vBKKru+h2edF0ng5aZxjvbCHW3Z9Bq0jS3z70eLZfgmMvz2LeNSr64KMu/pBxMfPIgMG+wrrpIf/427zv/MT7/xPn0d7sUrL2oCE9ZDP/bGBzVAX0p3Yp480e8nrKfMo+okvnrrUG8p3eEr4pw9nbDevn1WrjO93Pm94TErSlxWQBPII4WaxUzFz8gwyda7lTa6lufxxPbiaIbGNYr+yoJ8lWnZMJ6sbptwz07dRbLnb4yguPc21oig5LHuGKf3RJd4JmZGvOGBi9irjudtlFMWzj62Zx487RMeSveZI/9FbgwCo8tm9FuLQWGIiVvVJotkibkTCseg1Te+EqwSEU7O7pOjzqzNDa64JwY+Sh0bW7hol0RGjvOjowd+5V/7O1/Ve752u3d40RMY/g/9+Bt/UanzkQxRK6eSxOSHr8guZLmPM7VzjVXmjkXOwhp6nXGRpcwppIWBTADm0pq2ZeVD2X2YUIS797Z7TN8ctuWwJxVdFTXUgZ8Su5PtAhuRNhjQZevnxbG2L/gCaVRRZHbiQZCxicJWIFdmq5EiiK/Q5tgs0faxo6BfVrbtfuqFxzSyYh50a1x6LUEenVs1X2CNO9FHIWTfAdo+POsxGkmy0saFc5so5cn+4HW81MXb9cRBBS48ww94Pw9GXXrztOX0a07BqbNH3HIfPvMcHF/4wA//vQfcf5EPX5MEJsYzN45+RtE/RS51kmoONFKtAgN2u0fDyvQdGW3+a8OZ3R/oLNylnsdWorZUaCcunEUu+zprA9IDQL+otdu95raX1XSjymSn5liyCm5vKbxAWaTg2r5x+1LT+SCEOYOwHw+3JiGOa9FPxoQk0a26GEVSckR1qTj8yS+39px2ZG5nqjf6xthnxWk+2qIKN5zhpY5NvHR/8pi+UHs24jRTxdnakkbXlo0ZCScPLTNmnjETUZ1dRbrFRbsf5wZj3rL3SJW0kungXsbW/tDRnn6aW1dhT0n7or3qbCfL4WuWwA/d/8aL+kLfP5m+vBPUXWvcc19l2/Z7nnyZKZCqKC9Ho1+YYmJLGp0nb8iWLXF0rB9ru82X0T3B1Cmp5/K0HG0vuJryZAwLPCBpBDWEZTVtW++NYnskYTdrcTauPA3/jY1V7LypzHJKH75FJyKXGaFsCtO4RsRX6Y1JGy/ch97tMMbbPtMWDX/j3BI3tbeAm81lqbHYUKbX7iFR4hBf8TRmrX0JjY8TmJxJzQxHc9mTg6kWOIrqupJKNNjklojAbeI5/taDMw88dN/PXkT/tShfswQm2A//+Le/XZNWz4eRdJ5Rdxt5l8j7EpscfPL62d0fPHOnJzAbv7Gsx0jczLZUI8MnDGnmnyfHsvmOc1xGRwRsLkLTRLeVF45N4I1Qll74tEEwgmmHPGUrW3vx6bEBrTMozUZRpwS7tuMv2AIFvUxv5k0sxOrYYZybF2sKUm7pp96XDS08vmclY2eawbTliW7yt7b8EBu+FffAONalPzSFLXeNz/g0LtsF00ezaG77I4/tPeyNqrqftlRMSPsFKsbsW/vo2pgaSyH0U2Lv+Gf3/Xv632Jfu/I1TWDC1k+OPnCnngNkmdlVJ8+4nbDgMx8kGj3wB7v3PH63XpHmPzjorSPn4HxVuRI2QNvbEGMVv8wlQRIdW6Tfd8frqDLNEW36YeCY+3pc7byweFaw2TzhjN1sG2cPLVMHO/hVw59SPO5IyiRkIoTJDdV+Owyth01tYlpu0195qyp8fYywGBCO0uztK1Hk6ITpOZDPLtM/EnrbOlx19DwI0zw17mhX74mumcIa/QZVPCRefMvOnIXd+GnOhQc9EWPjdrME20hLzZUHRLTI5PfCf/qq73nA3a/h4WuewLy19PzNs2/VTn0qg/N81LyQjaxc6pxQ1XeZif7kjbO7DzzFpXQY5mW0zJngFyyeZhGGU1g37r39lRu7dTFCFU6Ok93LqX5LC+PFbZS0XszUWC+awYa04049ce2fYWFr+24gW8ZrHrh8i+V8QGgjjV6jdlywFYc9VqwIQVNmK+1wr9pVIjnctos1nYk4aQeqvcQumB5X/JcMJsdoE/NO21iHrY/NCEslbo2xX6hqfupxA1O3Hk1foWzkG1zO9FMvS+l1oL5wcHjrJ1/st4wY5X75micwDh+6//UXj28d/EwnahIqSTXbeVNo9reh/q4S+MotnYJ1umaeciauzBTUZ+fM/tbQOq2Oi1/c0hn4NfpSw7naHKWSnhYLQkndEh5dI3U1DpFGk6VsVZImmg5rcnQrNvu9KUXTvbTXPppZgtt/68pSJqyLn02k38xRpbfyd9zo0W5LWZsbXRA+ItPd0kUfTOxor/eg42GytceyQeGyImZ7tgC1n7R8dEw5U07f6ftFKPTGtCM4pt6MXF4bF97xQNEPGFK+4cydv/jQfT930aF+jQ9flwRmDB++//UPvvLw5gMzQUngTuKMkmkjjU9iDnZ8yeE9j7/ciQtac2jjPhtr4v0IQCKjX5mtQ5bLb9S77+UyWtAk2rJwtYBeLG8yw3VoyVpHyjH3BSUe95qv+8XT+LA7ZGvSd6yzX0nnixUA5qK2h4lTi3F3hAYEBtQFU9pgeqq9aYkTHcqJCM792Exu0xV22w4GruFpYQx25aG9ssf/KnFQjqVjNYtjntZbOxAjEo/VyLLpT3c1qrEDU7Zb9iUOEpbxwadb8Si84wd+/b5/990WfB0OX7cEZiy//WN/7W0a99vzvPX0RM1U7I3cQr0v/NTLd5+4cns97U3yCenrYpKTtgp6pjZnZTK0CjqaAL7vjtdm/mMmqSzyB4vbYLM8vUQtoabgJbrZsmLYVUYF56haP7m7hWb4K6z7HY5l5a/aGeli55iaKXKOfR/RaiY0H7mj98ykjjwck8GRFE+k4Zrt5sYb0nEkVscbbOuiD+9Eq9WxjLGcYkfsGy/NkzoWi8z+p86+JesPcqBJXH3GlQQb/Phtq34unXrlL8zxyw7OvuPDb/65B+D+epWvawIzqA/f/7pfVB69l+kapTuew2Rf5o42mVkyGbz786+QjOTNaUltn7RdFw9quC3LqgxXbmhN7n/5d4GoZdsEMGT4YXEsMDb42NGe/QKVBL5wwtE3fLtv2sa0tvugpofuRaJeb27V9tD9AIevRBbO6aH7FUfFiGnHWazyIyvG7hI7dJF0P5KWAk1b+hGXJB0r1vy5AqnGKOkjSSzhCiY462zVtiUZcULWqLTmMfIgZF+vNBNMjzOszd0Ji52KcZXcmxjwcLx71ZnzH3nwh//9Xwz463f8uicwQ7t5dE7Ph3cXvMo+KS6J6rGz+t4B7nHoS+tHr92+e88Tr7Bcc6o8diIbsTXJxGtxOOG6A5a7JvzgZWduP85ZmIXKIrAQKarLfQx1bJUafWvsUBk0teFbtd1WTYIY39TRnWYTJHq1iB471aPvbnSJyarRTMNWi0fszeCjO7QGvzoknv1Qg6XEP+0tY+QD1XY2Edpci/XQtx1ASvfjrb1MeVAT2dzBF615sGlc84R9ytNXYnKWlXHG2yhws22rBRMd5/CjT37u4MbX7MMaHsQLHL4hCfzw/fdcunV89FZN1oVM0RodmXPynikP7nefvHv35I0zbAk2ll/UaoZO6K4jV/p7A823n+T74Efu+uttNlyO5RIx/PERKc668IDS2CxkdBNRthj4Mab1EwG9Nwy1YPBNrvJZMmimP3Uyemvb2ogRo9Bq9w37xsVL+NYj8i7YebyZN88f/YkJyzzGcu0HXRE4rmiHzxErtoUrD91r1g2XMXBVKW7PJTzEWa5WO3x0ibx7IyIJ0Ohmgu0Zd+iUssvcXtidPfrJh+/7uUuT7evX+oYkMMN7WK9Mk8RqXhjZo12p/bIpU4Im96tHZ3bv/Mxr1M0ZFYM+yxaIBVQO19mZy+wq3VCty+jv9iezxoJ50VgmShYyre5LKkwvHtKUYDm23WarYAOwEtloy2Jnj8UbvrDQDiO2rSkPwqeEozxIpFb5swyc/1bcYE1cw0swNmCCYDMXnLlSUSMy10QQrmZPn2NuIFKqP8aBXeGKE+6UcA69uUo1/C0+OlbrJg6PLkXXY0mMHeEpdV1ex7pjmTg43Ts+uqB/evzWh+/7jy/azzfg8A1LYMZKEv/Anc/pM9M7/SheSp6+ssq5ZxIrzDmXu0ev3rb7zb+6Zy6dklTrr3tAJLQKJC5jb9TltFC6jL5tx3NhFoMSyz62rPsT4ZYI4yt20WaRadcSp9l9bGgvZ2S6llakcPa9IzLCts2f2nYVOzxMWUe9bUkq+y5tHczsTRuwzdQ1kskRLkkGb2zWYyyav3jGOGCQzPbCjNgRwZvaGDtrHnW4nFpi6bfOkK03zMxVUvoTk/bK07qB6lgdDzHN58V3Hp598uDWwVsffss3LnmJ8xuawATwzh9548P6j8A6E/cvWyKt1cxuYBWWeSYDpdcivv+pb9l94rnznGk53fIpLa+tL299BqavbGG9q4hq4KH9my/7DmnWZY9TOFsa09mjlYDUGouLLGUiV+ZFi426OZaNZZJuYoV/2sXnyWP765+lMa94wh8C+2syd6IPBh9hCbsf/STZe32gOIMMr+eJ2H0PzymeaxCxiY9mocZ9dEm45om4EIPDz1fdwyb3bnU/DJIu80kb/pi0/yARwpFedJYxLi6Z0bb90dGTV5+/8ZPf6OQl1m94AhPEh+5/jZJYl9PHSuLxUDwTlQxMFlIzj8wkK3Ow+7XPvlr/cdBfF/TZ1wsEKYW10jeRam9EhtiCLNf3nX/d7vvveH3AhUDTj7aIemljYa3jIAy0vtfijg2y2IEKS5hWHs7GQ9oclgRlOzaRN1J4wjWPpq9DuKSrMYa9YoTXPNUHYzdBhZ12P/fb+mjO9merOitFVvymj4995rXfPA7Cyymb9WmGg5OsYsZ2ln2m9teI6jPG5qStEsu03dvwtzy4ZgtOD2jHR08e3rr5kw//6M+/6N/tnb6+9NZLIoEJ10m8u6Uz8W585BJ5zblaXmHXTHFPMx+zfOenX8sS+RVmzrgsEOgcVKmDnJNqn6UFMgTY33vlfV7UsIa9+bNperP00hdmLLxIHGisYgMzjFPWrZaaxXbleXA0clt7I+PTYlubvX2EJexT1lG0pO1mP4k5o2oNEiapo5itSMLMkRJeN/2g5FYdYGzW9o/FwuNxSUI9TNN2n0FbhwxJUN1OD8PoJwKziUorDjxuAYdt8UcSabPZ7nj35NEtvWD1o7/4kkheRvGSSWCC6STWGVZJzNZZ78tEb+QH+nDHHbvffuwezsC+dxJ7gSBmkTbPkRHO8gM6A3//+dfX0rc8S4cxS9kLuWq7bQcKtZfcaJ2Z8O+b6pTJ1OhVvvIMrz57CNW1DaQ9hbPZw+koKu59ydpfIiHekAiQmEccZgon1vMs3YhYuFckHv+IM1aD3vygWxJ/oOIlmmZlyY0svjG3lq42zRjexBl9GPbZCydeR6ADrbYur/o54rNPHit5//gllLyM6iWVwAREEt/aHbxJSXwhCYx0TeSRKZFnpkng3Ycv81FLcjgvXqmtvi6vMdeScPadnLRToPjZV/0wdoLV4tVGCR5ESi8tvSx0HY1vSYHZBuaTvPgmE/Z9qTrtgguK47CDarnZP9zmdw80DaMabUHZtb51jXZ/5akzaPsLRzM7qhKlDW5yRxVZtTtOwRo7LaaXqUNLSW2sONIXym26pR86bCiNTS+Syd766blxsvMeiQW4srrw3WfvecklL1G+5BKYoPQ+8cVbu91bNZ0Xxoz2mlCPh+NqV4K/+3Ov2n3m+dsRdhHayaw6WZwkVm8sVKB6LnzAc+FeZqS9wNknHcBc/nayYiNry0bE1skGme7xM31Mu/C3l4mY/k/4g8+c0cQmI44fbJsxbQuW0TZuapfWEm94Gh2WNUY0PCxNHO3mUu1Yo42doSOSRrbNvqeJLk3FFi/I0oqH2Q9voyb7CZzjk51qqLRNLtw6Onjr/3Lf33/JXDZnDnJ8SSYwoZHEmr+3+kxcL1hlS3bmdZ3FYGmu3Dqz+x8//bqDp26c85lYIj3r9WKs+QrU68PZWm0nM4Cf/dY3obLSS2+t4TGI1kcdQPpGu8tW1j1qSrjYFSOZSzyZyl9Z1OOObTcc2KlsPCybLwndXMHF/2oThkxOs4txj8d2yOpmx45vStoa3TrZxjJe42PpnvhSwmEfEgQ3j20XVB8nT+zEVTGnDs9q28ztc/ZlW1uJiGKT2O44PIeB5W4AABJNSURBVPdJkvdjb/nFi+3xpVa/ZBOYicqZ+PhNr779xkfWictEM9l5dVpJLnVW4cnr53a//MgbdvpVS66l2U1+cctLX5fWEmdLjX4W7Afu/Da9rXRvLSIeZdUbLRYSmWmz0C0B32XKSmL7tMNJbNClLo15saWYo/wvklVrlMGbFhJZx6g0Ye0jiI63x9g614ot+pbSFaH/kOUGaovTGVi4zdODimPaRIDfeYcF+fYY9rYsImMiQ0+J1RJJxWoNbZfTeIRwvJNP54vju87c/t4nzt384Zdy8jIkL1MG99I+/sT7H/tvL9+87R92lATey9Ky1BnSG84/v/sH3/GXu/OHekadM222pBaL58ZZ07RtJzKW8PM3n939wiP/145/BD4nZ7aYsrXXp5vIpmYPtWd1Epez1pQT0z7n6ZK26bpnYe2rnb8oN8wrbn900U1EjcqCkrII1dyOOb3M9QRZCr4Wb9hIRrtzzZR7p/LGdl2DcbWVbXtzjSrQMgQ1I0NovURH7/j4v/6ffd2/mFBhfVnVS/oMvI7kwR9/tb7FdPSAJt1Ln/VnwnXnErsvs1Ho/pmrt+9++eK3S50Xtfosw56gzdLNzWUTu3vt2bvnpXTvMmviEaRbuKZ4x01dM/VjfUCxKkuM5p3hEJOp6pVrOPlzFabwGCXVaUzoKFWLd4NynEE0ptm6H+s9u/IW1ujmWbN8jaoadpN2PiUV5nFcYIlRAv56Lns51e9pNqXHVjFUXJFjvpCWDonvnkghVA8fhQmi7Y+Pv/XMbQ98syQvY/+mSWCCffgn73nbG85f5edqLyRpJdysWy839cHuM9fO737tL19LnjpZg9ZDQATqrtZOaG+hf/sVP6APd3wb+lrmbI8cS8pmwBxXqrw50qxjAmvLdlVWkLhkU/EBgSC3OBMHV5GUmapmws/0lZ50ERWq9BUzungb6AVX2JJs/cVTI9zrB4pymKgasfLDRH++8t7cRjePYZJUrNuPSYahj4aOONtz19E2tiOhb39SpxX8nYfnnjw4vvXW33vzLzyA5TdLYft905Ufeu9T9x4cn/kdJfEbv3jwB7s3v+Ly7me/7bNJNhmw15zEMVYO+fStHslt4fHnbjxz8J888n8ul9IoWPo5ZVyCrSWcktmB2UodHMcwrHYrSxCrpP3ZooJDz7Z7QdwS1xaTcIZs8G1G4c7AlOq0/pyNjMd0cyo3PNgnYQrrGNcxLBzoIFdhTvPY4AfYmsBgrTcqrWYeIjcW3o5N8gV7vDs8fvjG0e5nLr7lv7i4tX3p976pzsA9nby49d+88RNv0mcl3i5ZLXVrWRpfIHuLI/2Xl75l908f+Q5e2KKbF7W8KzCly3Usm4Uurd3Ba8/dvfsPX/1vDEE2H6rF3XrWQCNO7l3ctwUyGKJLHVn42mbqW9L+2HvNv/J0u3m23Ehz65iohw2xEvOqdK+tpu0qMYczoG1hUBu6uply6TcSRFDr56wj4egXwBxTMS0f1ez5tCNh53I5ovLcEeAncvt0N76nNzHqH23/D6/522/9ZkxeRudl8Di/SQ8/9f4n/uHjN27/rxT+PesQ9h/x2TdvuOPa7ufv/dTujjNsnl7gOjOomzMoeZKHahL7Vx774MG7n/qjhfrkuQhlJnJ/Ogtr8T5i7W/tth7UQ+1w0yZRKlbH1eqtXaLalw2vbVQMkW95e1RhMlCHRtKa7ZZPydqqtqqtTZhXZHiSnM05/U/ksNyImn0KI2Hy9mTHu6ded/b8Ax/4kV96B1zfrGWO6pt1BIqbS+rd8bnf0dOrN64L5SExQm/+DPCV527sfv6Nj+xeedsNP5CTpEvCkr0kx1hxvRp9/J9/+t0Hn3z+8b3Nl6lzIpFRmw2Cr9IvckfgbJxay4wOvvvUvR0jU28zlok3bnb37PaZFlYIa27GA4KHEnmF6kgSw8m499mDW3yMeYBmBrkiJvfUbz2t8r15Kc7TEVOalo/H+hrpRy7feu7vXnzL2y6272/Weo7wm3UES9w/9J5n3rY7OORsXOM6fXivPHd99w++81O7b1UyZweTsDnr5uwGKc+9srsfu/Wsk1jPi1F80QQxyDhhtcGK5qSddDPC/Vb6ILgoXJHbCKQRNHFPjv04t/bRrnHCOaUrT/gbm3plm9gpXWVtucrS9tzUI8i0PTE6h+bx/f/tXUtsXFcZPvfOeMaJHxk7DwMJyRiaRVHajFWpsMDEZQFh0xSJTVaJuqIgitoNESyaVCxAQiXuqiyDgBVqbakopTHFaUURLVKNQU2iPjxNnMR5OJ5xJx57PJ7L/zqvOxM36kuJfW+ic8/5z/d//3/+e/459zVjJ1rsq+WktiSztaj79V5FuzO9w3994Ed3xSMiPY7V9mZkq4Hupr7Db0wXJiq9z/NqHPdcDzeC0+gVdXD7ZXVf9zwmWdRo2Gth0KKXQDj5+HTuvaVZdWR6FL66WDOk7qTzEwAh2pauuW2pG5HLZIReSjGj7WMnRI8yGOxgt15BGWBK1wJ6ZJncmltHVWhrkdwAsjmiO9iEz+j3xe15fvJnpJhaXU88Yr/YLJWubRaDRFNxPND74tfatzz64sBj447qXV/Vw7zrB+IO4O9Tb+WOnf/KU6XlzE9BzmPEUk8WAeOn+vf6rqnv9l2DRU5mqMD16ovJTX0wIyYXLio4ncaEd81J3Z1Gbh27dTuup+UIsW4yIffR/COB1XW0pMf2EY+MkyltX9yC9Yst+m2wAgRySdEUO/LBULvMRtjsm4lD3B7oOGrx8fl+oS4iHAWhIwkOWh9nqGOrJ5UdvtFWPVYcOFrSltfKvjkKa2VkMI7CyWo+alOvQLUfh4WHE09H+QDD0Pk/PGaaU/u3XaXrYuwL+C1MHRucDlKP1KnyOfXbmVdocjORhmkLuHc3PdUsjmu2jWjzoUDWtA71EFlLHe0WIRiL85c25IF+agsnd5BUV2Xv2kOR29aEjtySigcuBrUZSzspXEYWoQSR4Bzy0U54RGSZnMsIUWnZZ2xhJYi6UpmJ7ZnOJ18a+Mm4dK25nURszY3LG9C3T5ePwg/hPb5QT5s71Xp10UC8qfXj/im1OVPT15Ow+PCqzEmhr5Phj62Vz1ISa13cmwTEukxh22+malOfj3VadGSctsPJB80eOkRRjopBry3JoX0JoY1Y3lz+uN8uP6LdNtSxqYk4QB7Calg9lt26TTF0OZ1B3dpT5rOsUIM7zFvTG4b//fWfHUOba3mz417Lo4Sx4WqsUuqpKAgPQZPnip4sztj3912F1fgKSejGCa7GcH2MAk5SnlVj8+eiZy6PSfxgeplImgpx3HriESNhuNZCj0jZSebxMbfUM6wOArmAiv1cncf1GceMcWAmW7IE89jhWsUGoxwsEFhdrnFkrQ3XV0SsfjMP9cJoZyY3sqej78nn7j1YRMla3/yIrvXRwvgO/+Nq4Vyt8/mFeirP01lmJ42dw3FPR0Ud3DFNqzGtMiTmx01cpcdM6r3F68GRCyPwttaSRE7CaXZcsRNVYDLpBQZCjcN+K+UWtI3IxflYbUPvsddurfVIyllioHF92zZOsDuSrFrR4lACWP7PdVNKH+5k81ZdULJWDIIqpkcAps0w+CmW1HjYUE8XB4+Oa831sG+O13oYNYyx8HL1cBTCI6dI5aEZi0NAd6lxJd635bpEBCGc7LQyi8aVZXzE9EIwA4+YLAnUpEGp7iRJbOKRaU/PWHOksWQhCMgsAiXcapZaVLzP6qD6ajjmdxEowS3OGW8jL8ua1lcmkNLXkxaNm/X47AfA+vMWnAFU1BFmins2bD36570//L1HuE4a9piskwHHh0mJrCCRgyAPfbF4BPD21oJ6dGcRVmN8ZgzzByZQKF81xmtknGP4fPiXl06qdxevEQXlAuD06Se1SZvp/cmKHSzhqcptglNPvC0u4o4UNJvIRVFLWdu/RmaIIChJRKLrDpXLo/0UE7JjsI9zkOQnFPzf08GG1pYOjgTpeBJpGHTUGWZKOzLdw2MPPHFMI9fjniOyHkceG3PrRNbhiei6GFfj+GuYcscLUikKfnf1NTUyNwnMZqI5VhyZZLSe9DZxEa6lWtXRwz5o8hkAy+2K5OKYpxVbs4w59YeB5jMrnrjhe9XalmtV1JpHI2PnGPHIZSSgYmvMpdvGHiXuzrbe4z/fPPjsQ/0Da+6xkI7b7e51hG4Xv+ZxhZdrcGqt4NQ6yGNS8oBxF9Fjpv3bZtSDPeYPSVA3THbzIsip8pngT7NvKDyltrowNYmJ6YgN5i6LWIYtXUM9bMUTm+W2xJqwsC4TGqnmIAEhrQVjjT4RGOH2osRNYsbTQzjyjTWavTZ6dOfLZbR1g9Ek7D21LMqtBRF8T7u4Ob3hxC96h4aTxDWBcyJnZUkNIoAr8sa24PGb9VQBmjCbcEJhSilI5Jr6/hcvqT3dZchwO9F0wsMpdfTMzFgwuXCJ8FwwjkonabCvOVGsmk5CR5vxrRIEQSbrXQ1ONPae69qC7z9KfT1Ck7+gzZ9CJhK+rrQ01omLj3PsA9Zz2bHNw4CfQ1LReD7VM/z6N46Map+TvY0AHy3bTmqxCPzgtbmhd6odh2DyHpIuihlOsN1wtxpvdH0V9jy3YbpBYun6yNx/1MiNCXWlrldjZGiezsILevZwxFGMQanFMJuUKEan4ghJEuzhjfXdEuU+r/QaTkZ4JUM8PeQgF4gP0bi5lkSJO6xuzMeNYSbaluoYKS6Vn50Z/NW4wJNdiwj4EW0BSEQcAXyOvJJKDYV0wyvKw8Sk2QrPL9Q9HTfVvs1X5b1qwuM8ptjiXeo/XP9X8Lf5s2ZyU4bLTDcHAJNXtOLJZDBEykliZbGa8OCHAV0rkzvaG5eZ9bBks5bHT2fWMRhRQDN0heGoWXZXBx1wQKZlZaIX9Wd7SpX64vB0+/xwaeD4ur++xch91Gaj+FHIpN9EoHBqeQhW2kMb09EB+CnbHHRQVuGp9f4+vEa+QSKdGtCI3l+6Fjx98S90bWwnOlLyIaCSsgLRKIceECJWmigRNPVi4bWRwyQa9XLhapEEiNkqtuI13RaMJhWc7tXMxKD9ZiEgfVRLv1GHXnULIFHDE3AXYXRm8DfjQpHsbjMC8UjfploC0xHY/+r84cpK5uF6I3ik2kjTnMRE3t1ZUd+BG17w+EnuhOEuiE6V3w7+OPsmJDJ8C8qb6PZQmFNpyEbODezDb0XxD6i0eiMJ/WEGZuVEdqRARKf3rk2QOVYRTJvm0S2LEQlxaXsoEw10Vgy7zNyP/hMOEaXt2d6JnemuE0/kvjWa3JTiuH6cMn5sPg5HogMRwG9A/frC7kcqK+mHV6JwCG5+wcocBXh6/WDvDbzhBX9fp85nzzCFx+CUeqx8Rk1WL1Jq2gMRr0kbdn5SYNjjEsY2M7gSW6cDB03Lwn1YSh6SDcRZDLZ4ow8aBNIGCFKHwr3BxpRwpRGUetIdI/e2955+tXR+tPRQcoqsI/dJ9hzeT8KQ6LaMwJ6Ty0NhoA50Zhv7KsvpAoLu6y4H928qczKn6iCBP8wGL3/gza6x+TOUNDZV+NB4SQIie8BszeqwK5hszQmok9LXQw2SUPaxVstk9SwLh15xNQeZpz5K6+5U+0SbCk/PrVRHrw8eH6fupPhUI+AezU+VOCGzETjy1uX8S7NbC13B8oH5RnYvnMoWdnfCytwzG3ypvap2wNteFfjpnn9++H5wChL5f/D4ySxslDg6sZDTP2R61WuVdBprNbjmluylrJ5ghm5+eVY0GvfaK5YhP9RICH/JQG0Ks8XLtcroN7t2TSSrLEf2sy71kfis7ST8TgQKL0Q51Vkv5NL1obnl7P27OhcK29qqefwSxY4NVZVOX4FnyBfV65Up9V9I5puNRdHmw0Wlu/pxFjsWJLWMxNGLfwB4bedjgI0Qg6NNyUrtKCjm2jZMlOqL45DDk6qenkhOi03AP7cKH5vPzVxi6FYR4KRWhUag9uaCpXx/98LeL2cXcluzi4XplSm1pC7AL4JMq6naLL577aQXMkqKxaTc4x5iJ0FRCxKfLleJgXDwcjekI67E8K8jzKpUGBRTKpiYq1c/2NHeNdUddE2ercwmyYrBvQM29+jeAe4kLrSKAJ6CvzizJT+49UbuCxtru8JgpuftxfM7G+Fs/oPanKo15nOXlpZzUQAvlITpfL1RVwuR/e0u5Oxr6ybqK7UPi5ic27Ob1M16rRiEqpSK0iX4smQ5m04V01Gq3JVuLxbSfcXnBh4rklJS3LER+D9/nAUCxXt8uAAAAABJRU5ErkJggg==\\\" ></image>\\n</svg>\\n\"\n  },\n  {\n    \"provider\": \"model_tencent_cloud_provider\",\n    \"name\": \"腾讯云\",\n    \"icon\": \"<?xml version=\\\"1.0\\\" standalone=\\\"no\\\"?>\\n<!DOCTYPE svg PUBLIC \\\"-//W3C//DTD SVG 1.1//EN\\\" \\\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\\\">\\n<svg t=\\\"1739343874438\\\" class=\\\"icon\\\" viewBox=\\\"0 0 1402 1024\\\" version=\\\"1.1\\\"\\n    xmlns=\\\"http://www.w3.org/2000/svg\\\" p-id=\\\"1514\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\"\\n    width=\\\"100%\\\" height=\\\"100%\\\">\\n    <path\\n        d=\\\"M1215.521013 836.730667c-22.304 22.698667-66.906667 56.746667-144.96 56.746666h-479.493333c144.96-141.872 267.621333-261.045333 278.773333-272.394666 11.146667-11.349333 39.024-39.722667 66.901334-62.421334 55.754667-51.077333 100.362667-56.746667 139.386666-56.746666 55.76 0 100.362667 22.693333 139.392 56.746666 78.053333 73.770667 78.053333 204.293333 0 278.069334z m94.778667-368.869334c-55.754667-62.421333-139.386667-102.149333-228.592-102.149333-78.058667 0-144.96 28.373333-206.293333 73.776-22.304 22.698667-55.754667 45.397333-83.626667 79.445333-22.309333 22.698667-501.797333 499.392-501.797333 499.392C317.86768 1024 351.32368 1024 379.19568 1024h607.728c44.602667 0 78.053333 0 111.509333-5.674667 72.48-5.674667 144.96-34.048 206.293334-90.8 128.234667-124.848 128.234667-334.816 5.573333-459.664z\\\"\\n        fill=\\\"#00A3FF\\\" p-id=\\\"1515\\\"></path>\\n    <path\\n        d=\\\"M517.921013 434.405333c-61.674667-45.792-123.349333-68.693333-196.234666-68.693333-89.706667 0-173.797333 40.069333-229.866667 103.04-123.344 131.658667-123.344 337.728 5.605333 469.386667 56.069333 51.52 112.133333 80.138667 179.413334 85.861333l128.949333-125.930667H332.902347c-72.885333-5.728-117.738667-28.624-145.770667-57.242666-78.490667-80.138667-78.490667-206.074667-5.605333-286.213334 39.242667-40.069333 84.096-57.242667 140.16-57.242666 33.642667 0 84.101333 5.722667 134.56 57.242666 22.426667 22.896 84.096 68.693333 106.522666 91.589334h5.605334l84.101333-85.866667v-5.722667c-39.248-40.069333-100.922667-91.589333-134.56-120.208\\\"\\n        fill=\\\"#00C8DC\\\" p-id=\\\"1516\\\"></path>\\n    <path\\n        d=\\\"M1111.62768 286.944C1049.17968 118.154667 884.55568 0 697.233013 0c-221.386667 0-397.354667 163.162667-431.413333 365.712 17.029333 0 34.058667-5.621333 56.762667-5.621333 22.709333 0 51.093333 5.621333 73.797333 5.621333 28.378667-140.656 153.264-241.930667 300.853333-241.930667 124.885333 0 232.736 73.141333 283.824 180.042667 0 0 5.68 5.626667 5.68 0 39.733333-5.626667 85.146667-16.88 124.88-16.88 0 5.626667 0 5.626667 0 0\\\"\\n        fill=\\\"#006EFF\\\" p-id=\\\"1517\\\"></path>\\n</svg>\"\n  },\n  {\n    \"provider\": \"model_aws_bedrock_provider\",\n    \"name\": \"Amazon Bedrock\",\n    \"icon\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" version=\\\"2.0\\\" focusable=\\\"false\\\" aria-hidden=\\\"true\\\" class=\\\"globalNav-1216 globalNav-1213\\\" data-testid=\\\"awsc-logo\\\" viewBox=\\\"0 0 29 17\\\"><path class=\\\"globalNav-1214\\\" d=\\\"M8.38 6.17a2.6 2.6 0 00.11.83c.08.232.18.456.3.67a.4.4 0 01.07.21.36.36 0 01-.18.28l-.59.39a.43.43 0 01-.24.08.38.38 0 01-.28-.13 2.38 2.38 0 01-.34-.43c-.09-.16-.18-.34-.28-.55a3.44 3.44 0 01-2.74 1.29 2.54 2.54 0 01-1.86-.67 2.36 2.36 0 01-.68-1.79 2.43 2.43 0 01.84-1.92 3.43 3.43 0 012.29-.72 6.75 6.75 0 011 .07c.35.05.7.12 1.07.2V3.3a2.06 2.06 0 00-.44-1.49 2.12 2.12 0 00-1.52-.43 4.4 4.4 0 00-1 .12 6.85 6.85 0 00-1 .32l-.33.12h-.14c-.14 0-.2-.1-.2-.29v-.46A.62.62 0 012.3.87a.78.78 0 01.27-.2A6 6 0 013.74.25 5.7 5.7 0 015.19.07a3.37 3.37 0 012.44.76 3 3 0 01.77 2.29l-.02 3.05zM4.6 7.59a3 3 0 001-.17 2 2 0 00.88-.6 1.36 1.36 0 00.32-.59 3.18 3.18 0 00.09-.81V5A7.52 7.52 0 006 4.87h-.88a2.13 2.13 0 00-1.38.37 1.3 1.3 0 00-.46 1.08 1.3 1.3 0 00.34 1c.278.216.63.313.98.27zm7.49 1a.56.56 0 01-.36-.09.73.73 0 01-.2-.37L9.35.93a1.39 1.39 0 01-.08-.38c0-.15.07-.23.22-.23h.92a.56.56 0 01.36.09.74.74 0 01.19.37L12.53 7 14 .79a.61.61 0 01.18-.37.59.59 0 01.37-.09h.75a.62.62 0 01.38.09.74.74 0 01.18.37L17.31 7 18.92.76a.74.74 0 01.19-.37.56.56 0 01.36-.09h.87a.21.21 0 01.23.23 1 1 0 010 .15s0 .13-.06.23l-2.26 7.2a.74.74 0 01-.19.37.6.6 0 01-.36.09h-.8a.53.53 0 01-.37-.1.64.64 0 01-.18-.37l-1.45-6-1.44 6a.64.64 0 01-.18.37.55.55 0 01-.37.1l-.82.02zm12 .24a6.29 6.29 0 01-1.44-.16 4.21 4.21 0 01-1.07-.37.69.69 0 01-.29-.26.66.66 0 01-.06-.27V7.3c0-.19.07-.29.21-.29a.57.57 0 01.18 0l.23.1c.32.143.656.25 1 .32.365.08.737.12 1.11.12a2.47 2.47 0 001.36-.31 1 1 0 00.48-.88.88.88 0 00-.25-.65 2.29 2.29 0 00-.94-.49l-1.35-.43a2.83 2.83 0 01-1.49-.94 2.24 2.24 0 01-.47-1.36 2 2 0 01.25-1c.167-.3.395-.563.67-.77a3 3 0 011-.48A4.1 4.1 0 0124.4.08a4.4 4.4 0 01.62 0l.61.1.53.15.39.16c.105.062.2.14.28.23a.57.57 0 01.08.31v.44c0 .2-.07.3-.21.3a.92.92 0 01-.36-.12 4.35 4.35 0 00-1.8-.36 2.51 2.51 0 00-1.24.26.92.92 0 00-.44.84c0 .249.1.488.28.66.295.236.635.41 1 .51l1.32.42a2.88 2.88 0 011.44.9 2.1 2.1 0 01.43 1.31 2.38 2.38 0 01-.24 1.08 2.34 2.34 0 01-.68.82 3 3 0 01-1 .53 4.59 4.59 0 01-1.35.22l.03-.01z\\\"></path><path class=\\\"globalNav-1215\\\" d=\\\"M25.82 13.43a20.07 20.07 0 01-11.35 3.47A20.54 20.54 0 01.61 11.62c-.29-.26 0-.62.32-.42a27.81 27.81 0 0013.86 3.68 27.54 27.54 0 0010.58-2.16c.52-.22.96.34.45.71z\\\"></path><path class=\\\"globalNav-1215\\\" d=\\\"M27.1 12c-.4-.51-2.6-.24-3.59-.12-.3 0-.34-.23-.07-.42 1.75-1.23 4.63-.88 5-.46.37.42-.09 3.3-1.74 4.68-.25.21-.49.09-.38-.18.34-.95 1.17-3.02.78-3.5z\\\"></path></svg>\"\n  },\n  {\n    \"provider\": \"model_local_provider\",\n    \"name\": \"本地模型\",\n    \"icon\": \"<svg width=\\\"100%\\\" height=\\\"100%\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\">\\n<path d=\\\"M1 8.72778C1 8.36759 1.36935 8.12556 1.6996 8.26935L10.5885 12.1395C10.7709 12.2189 10.8889 12.3989 10.8889 12.5979V22.1723C10.8889 22.5484 10.4892 22.7899 10.1562 22.6148L1.37249 17.9978C1.26117 17.9368 1.16822 17.8484 1.10289 17.7416C1.03756 17.6347 1.00212 17.5131 1.0001 17.3888L1 17.3776V8.72778ZM22.2978 8.25648C22.6283 8.11032 23 8.35235 23 8.71377V17.3776C23 17.5038 22.9655 17.6278 22.9 17.7368C22.8346 17.8457 22.7405 17.9358 22.6275 17.9978L13.8437 22.6148C13.5108 22.7899 13.1111 22.5484 13.1111 22.1723L13.1111 12.6442C13.1111 12.4463 13.2279 12.267 13.4089 12.1869L22.2978 8.25648ZM11.6399 2.09198C11.7498 2.03168 11.8738 2 12 2C12.1262 2 12.2502 2.03168 12.3601 2.09198L20.8462 5.86125C21.2444 6.03811 21.2416 6.60423 20.8417 6.77714L12.1984 10.5141C12.0718 10.5689 11.9282 10.5689 11.8016 10.5141L3.15831 6.77714C2.75838 6.60422 2.75558 6.03811 3.15378 5.86125L11.6399 2.09198Z\\\" fill=\\\"#14C0FF\\\"/>\\n</svg>\\n\"\n  },\n  {\n    \"provider\": \"model_xinference_provider\",\n    \"name\": \"Xorbits Inference\",\n    \"icon\": \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\" standalone=\\\"no\\\"?>\\n<!DOCTYPE svg PUBLIC \\\"-//W3C//DTD SVG 1.1//EN\\\" \\\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\\\">\\n<svg version=\\\"1.1\\\" id=\\\"Layer_1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" x=\\\"0px\\\" y=\\\"0px\\\" width=\\\"100%\\\" height=\\\"100%\\\" viewBox=\\\"0 0 48 48\\\" enable-background=\\\"new 0 0 48 48\\\" xml:space=\\\"preserve\\\">  <image id=\\\"image0\\\" width=\\\"48\\\" height=\\\"48\\\" x=\\\"0\\\" y=\\\"0\\\"\\n    href=\\\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAALRklEQVR4nNRZCVRTVxq+92UjIQlLEggJimGTQGVRBLVWQQGtVafitNVWavWUtjMdO1Qd67E9tlNtq9MRp1NrVUDFVo9Yi9O6YCluLAXcClYERJA1hhAQCJA9d87LAiF7tHM8859z4d333v3v99373f/+7wYD/ycWQ2Hx/uIbn/qkcTySrfCJfEYU+c4dPpHu9aSxuGVkSADZ/OSsodi/qbL58z940njcMjaRSrsgXHl0aNpG1Bv7rpRHojNtvUd01SETE5A9MG8fQxukGNaKHw4jse53RW20CIqX7+moFUUBBEYCQAB839+wT6QeGrT1rl0CXEJC8GTSyhd4pIQkDil2CsRQAIQQgxAAiAEAIdBACEQ92ppGqab+lkh1tUqkqC7tVtdKHgf8MnZY/C5BylE/Ai0cIAQQgiPZ4uo99t6HljcCCc9OmUPZ9rEvSbgUQoAZwYLR/+bXVveQTgH661pGis7fGTpV0DJ84YYWKF0CToYEmB02b9NqvynbAYJEfOShDoDC3sbsV5vObHBKAANkMI+St3UyOX0rhIBgBdIGgX5tc3mz4uypLvW1q72qOy0AgAEA9T69OBThBA8CA4kVtbcfKGqHHYHnUxiM41HPHY5l8NJx4PqiA/gEDCf8mh/SIO/tttd2VEKpHnnbQkjpH+i7t1NMBPq1nVWlss3r7o6cum7H75BU1dDlfNwBmO0dKDwy5blCDokWgSMGOmgaUXBKcvdrR+BHCQiJq5NCiOlboBlIWwUn0ago3PXTwJ83q3QyjSsAHdl6wfTlH4XMOAggkYmMkgEYAgBBgHRg6LP2yn8486EnMJ2yZade744IAACalWf2nH6YsfFxgTMwAmFPzIJPl3HDNxnkgvSjg3Bp6iWEQKHk7t4GeW+PUwI8bHYkHQtMMJeJLeloobL7Qv/6zY8LPtbLj7s/Lu1bIZ0136R1XC4m+RipyD5rrfzcFX9EHmHObEuZ2JJOs+LsMZmuy+FidGYr+KGJu6emnaZhJI4eOA4XgwYSYEw+Rd0thxpH+qQuEfAmBAeby8Tq2vi/VfnzxccBv1mY+MpmYeJ+iICnPr7jwPW6R8YZMBFByi/bb+521S+RiFEMW7SD6IMXqbqh8VGAs0lk0peJqf9cFBD8zmiItKV7nWE2zotbdpU97Gx1mQAAQO4s+uBFhQZH3AUf4eXDOjz72e8iGKxkYFycoyNtqXsMAKVOI3qv/son7vSBDehaOl0hQCOwfdxxHO3lM/F0Wnp5hLdvMg4WH2nDbBolY6pjhjr+/Iiobsd9+YBbA4X16eprxm1UwPbmxSEJI1x1muIfEFyyML2UQ6VGGIAivS9kAo6DHkcEAaVW2Zn9W8UBcz8JASs4Tgm0q8uqIDTIyBK0+b2J1JS5roCPYXMEh9IWXyTTPIIMQM1AY0ivIGR+z3id3Xzzsy6NalziJJHVo2eDNyxwSECOuuRSbc15Z5tYOC31eTLGcPgJOt3PX3B+SXo5nUIOQhYSGZ1VM8mY6oNaZcdXTTV5lv5ah2qlNCp74ryJmfPsEsD/1Cm/Pe5sLyASyIHhtMV2v0m9KRRi/oKFJzxIBN44ydjSvcXzvJbfvpBZjL7Jihp35i+O3PplAF3oZ5dAg+K7H7VA0W21G4PxHT7tsz7LHoHdc+d8FED3jDfX9TjdQ3M5jdUVSNN7oOlWjj2/I5p+VVP3pR/fSszPJ2Jkq/SfgP/RgBENncBFAZT4NOucH43WPUmcUA1CdR2K8jvmTjKihHPfjZ+aq0+lITQjbtwFgfl6Gv/80L26Dwvbmi7ZI4DboLpHtDA8azed4iP7TVxcaTUDuF0d2vWVCg222lvMpjLf//1cvkeCwNSOR6fTtj0z6xDCAGZXLvjixYyL11jHn8s0qqadNZX/dgQet7bea/UKzeD9eSFvbI/mLoyxSUCm61JUyD7JAuPWArJO7ADwejnwyDEagU3G22UlxK5jUkkC/F1kWpyWYRIbI2YuqS03yt6WqlUqZwSUQAnaB2quQwg83phx4IQ3jU+1IoDbjaE9P7QpLn5tIIDsRiVPSuCM5RNy9hEgGeTV3s7pkctrzcOiVZi0qOPPL4jacr9pqv/ZGXiTDSmlHXhbT7JP+JKIsbVoFRb/05vxTr+2pcxWKDWvhzFS12QITuy41yfv+8PxwuSGvr5ye2HSMoz2KEca15VfshsQbJrhEEHfd1Lo6g2+NB7dJgGlrl9zsvuFFRqgfGA3uTM4BCGMlPdWBRfsahpQ9i858WNKeXt7rmUEQth43QMMDLx1pWS5aGTYrdScRmJyTQRIBAprfvgbq20SwE2qbhCdk2x8BUKkc5YjhXulrH8t7OTeAQVQLf3+XGZ2RfVLAEMDo+vALJQiiNRZpZfTL3Z01Lk1+gAAAWfaVPN9ak7Iy6uAKYzaMomqppVGZA8GUuMXOjtaYXkI4oU+aXH3Bi8X/9RWf/1ic+ux6AC/IC7dUziWXAGQV1v75uc3bha6Cz6aMz9sbuiabeb9UskM3s3Oc/vtEsDt3nBxFY3IQnxafLJdAsZrJpk7eQZ3bQYBEnt+EV+uOHTrdkFDX19JBMuHz6ZRg3Nv3c7aVFqxz13wuGXOyv2K5cl/ymLg4IPBuzVWO5stWxSw6/1E1pvbHREw/z+g7rpe1PbhxhuS41fw9tO5ftxrYon4UcCvjst+PXlyZo6pLwwb26Oq2k994XAGTNY0VFzmRWB38z2n4XLCnBGgEpm8aM7S12L9liUBDPT+2l1Tq0Uq5A5wT4yBrZqWvSkpbO3u0T4t+tUiZY9LM2CyBFbmc4sCPz1CIlB9LQlgZs6BxT2FdrDjdu+5gqaekjP1/ZevDijFcnt9eHlwPWcJXl6aEpK5ieUZGGtvkPDSNVB/xS0CuPGpcbw/CvYf4dIi57ty/Gjj/FQ9pJY2SpX3G/vlnSIIwQiEgEglMfz4XsJIFo0XDTBIsmyLmfkDYwSKXSawZmZEaqAXY8K289cOEiAZe3HS3i2xfiu2QghIDgk4JYjce9/s+kb72QMurQHccjOScxdGTfzr7FD+xAuNbT9VdX9/UaYWXwn3TlpEJJDoj0YAuQzW1r2qppP7XPqRb2mMYHoQh5mM5zCzgv3XFq9bWpkczo+qlhws/VftM9Gtg1ePjv9+sLh2VICN9+20Nd/IAASqKtG5My7NQO6a5Bw23SPMlBQxPcjcF+NC1wZ40dGZupqSSvHhk1JFy4Ug5tSnqCQm3+7ojRtF5KLEbD+r7So5cObWFwVOZ2BJ3KTECJ7PorEPc0M2iTBAolD0p1Na/L2bkuPln1RHJx6tz1wgHqkvsZl6jCaE9jNdu8UsmZQpeu7uK31bf07rdAbyXk/KYzM8Qg1zZzClVifdeLJi+Y6imnwtGgvvOqAFoqHbzeWdOd+Ih+vPMigcGosWFA4xQHRJ38bQC2zcM10/lD+49fHZxYu6ZfclwNZPTONGf+qkmXmZyb/oz+2R4fyyVSor+9PhKxnX2yRtTrUHAAigC1kJ/JdWTuOlr/SnB8+AGLLelFwLwyOXmvJ3flv9/o5h1cDoR5BDAqUfPV8cwfVJ1R/CIqT9rqrl7+8eq9iu1Gjd2lVNNoEZ4x/OnpUcyp75dChr+kyWZ2AU/pXlgICmT955s6b1XOH5uzkHO/sbrH4vsEtgybSg2XlvJZfh4JUqXd/WE9dWHSqtL3oU4PaMQWYT/RiCSb60wAlMCpvFpLKpEABd93CLTK6Wdbb21jb0jXS5fSart5+3Lv5Bkvcaat+7qmOukCf8PYH/z21WhH+k5PBqTfWO9MopE1j8J43HbSvcklZQsCH1azaDQn7SWNy21Dh+1IZlMa8+aRyu2n8DAAD//2mMQDVCqcaMAAAAAElFTkSuQmCC\\\" ></image>\\n</svg>\\n\"\n  },\n  {\n    \"provider\": \"model_vllm_provider\",\n    \"name\": \"vLLM\",\n    \"icon\": \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\" standalone=\\\"no\\\"?>\\n<!DOCTYPE svg PUBLIC \\\"-//W3C//DTD SVG 1.1//EN\\\" \\\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\\\">\\n<svg version=\\\"1.1\\\" id=\\\"Layer_1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\" xmlns:xlink=\\\"http://www.w3.org/1999/xlink\\\" x=\\\"0px\\\" y=\\\"0px\\\" width=\\\"100%\\\" height=\\\"100%\\\" viewBox=\\\"0 0 1938 650\\\" enable-background=\\\"new 0 0 1938 650\\\" xml:space=\\\"preserve\\\">  <image id=\\\"image0\\\" width=\\\"100%\\\" height=\\\"100%\\\" x=\\\"0\\\" y=\\\"0\\\"\\n    href=\\\"data:image/png;base64,/9j/4QAiRXhpZgAATU0AKgAAAAgAAQESAAMAAAABAAEAAAAAAAD/4QlhaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA2LjAuMCI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIiB0aWZmOk9yaWVudGF0aW9uPSIxIi8+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0idyI/PgD/4gIoSUNDX1BST0ZJTEUAAQEAAAIYYXBwbAQAAABtbnRyUkdCIFhZWiAH5gABAAEAAAAAAABhY3NwQVBQTAAAAABBUFBMAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGzs/aOOOIVHw220vU962hgvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApkZXNjAAAA/AAAADBjcHJ0AAABLAAAAFB3dHB0AAABfAAAABRyWFlaAAABkAAAABRnWFlaAAABpAAAABRiWFlaAAABuAAAABRyVFJDAAABzAAAACBjaGFkAAAB7AAAACxiVFJDAAABzAAAACBnVFJDAAABzAAAACBtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABQAAAAcAEQAaQBzAHAAbABhAHkAIABQADNtbHVjAAAAAAAAAAEAAAAMZW5VUwAAADQAAAAcAEMAbwBwAHkAcgBpAGcAaAB0ACAAQQBwAHAAbABlACAASQBuAGMALgAsACAAMgAwADIAMlhZWiAAAAAAAAD21QABAAAAANMsWFlaIAAAAAAAAIPfAAA9v////7tYWVogAAAAAAAASr8AALE3AAAKuVhZWiAAAAAAAAAoOAAAEQsAAMi5cGFyYQAAAAAAAwAAAAJmZgAA8qcAAA1ZAAAT0AAACltzZjMyAAAAAAABDEIAAAXe///zJgAAB5MAAP2Q///7ov///aMAAAPcAADAbv/bAIQAAgICAgICAwICAwQDAwMEBQQEBAQFBwUFBQUFBwgHBwcHBwcICAgICAgICAoKCgoKCgsLCwsLDQ0NDQ0NDQ0NDQECAgIDAwMGAwMGDQkHCQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0N/90ABAB6/8AAEQgCigeSAwEiAAIRAQMRAf/EAaIAAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKCxAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6AQADAQEBAQEBAQEBAAAAAAAAAQIDBAUGBwgJCgsRAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA//Q/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKK8e+K3x7+FHwR/swfE3Wzo51gT/YgLS6uvN+y+X5v/AB7Qy7dvmp97Gc8dDjxpv2+P2Vx08Xyn6aTqP/yMKAPsaivjX/hvz9ln/oa5/wDwVX//AMj0n/Dfn7LP/Q1z/wDgqv8A/wCR6APsuivjYft9/ssf9DZMP+4Vf/8AyPS/8N9fsr/9DdL/AOCrUP8A5GoA+yKK+Of+G+P2V/8Aob5f/BTqP/yNSj9vf9lb/ocJB/3CdR/+RaAPsWivjz/hvb9lX/ocn/8ABTqX/wAi0v8Aw3r+yp/0Ob/+CnUv/kWgD7CorD8M+I9G8YeHdN8VeHZ/tWl6vaxXtnPsaPzIJ1Do2x1V1ypHDAEelblABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRSEhQWPQCgBaK+PX/b1/ZUXp4zdvppOpf1tRUB/b7/AGWB08WzH6aVqH/yPQB9k0V8af8ADfn7LP8A0NU//gqv/wD5Hpf+G/P2Wf8Aoa5//BVf/wDyPQB9lUV8bj9vr9lj/obZh/3CtQ/+R6X/AIb5/ZX/AOhul/8ABVqH/wAjUAfY9FfHX/DfH7K3/Q4Sf+CnUf8A5Gpf+G9/2Vf+hxk/8FOpf/ItAH2JRXx5/wAN7fsq/wDQ5P8A+CnUv/kWvRvhj+098EfjF4hk8KfDrxCdU1SK1e9aA2N5bYgiZEZt88EacF1GM556UAe+0UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFeb/FD4ufD34M6Db+JviRqv8AZGm3V2ljDN9mnud1w6PIqbLeOVh8kTHJAXjGelAHpFFfHjft7fsqjp4xc/TSdS/+RRUR/b6/ZYH/ADNsx/7hWof/ACPQB9kUV8a/8N+fss/9DXP/AOCq/wD/AJHpP+G/P2Wf+hqn/wDBVf8A/wAj0AfZdFfGv/Dff7LP/Q1z/wDgqv8A/wCR6X/hvv8AZY/6Gyb/AMFWof8AyPQB9k0V8cf8N8/sr/8AQ3S/+CrUP/kal/4b4/ZX/wChvl/8FOo//I1AH2NRXx1/w3x+yt/0OEn/AIKdR/8Akal/4b3/AGVf+hxk/wDBTqP/AMi0AfYlFfHn/De37Kv/AEOT/wDgp1L/AORaX/hvX9lX/oc3/wDBTqX/AMi0AfYVFfJmlftx/svazqdppFj4zH2i+nitofN06/hj8yVgi7pJLZY0XJGWYhVHJIFfWdABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRWH4m8R6N4P8O6l4q8Q3AtNM0i1lvbuYgt5cMCl3IVQSxwOFUEk8AZr5J/4eA/su/wDQyXX/AIK7z/4zQB9pUV8Xj/goB+y5/wBDNdf+Cu9/+M07/hv/APZb/wChnuf/AAVX3/xigD7Oor4z/wCG/f2Wv+hpuP8AwVX3/wAYpf8Ahvz9ln/oap//AAVX/wD8j0AfZdFfGv8Aw33+yz/0Nk3/AIKr/wD+R6d/w31+yx/0Ns3/AIKtQ/8AkagD7Ior44/4b5/ZX/6G6X/wVah/8jUv/DfH7K3/AEN8n/gp1H/5GoA+xqK+Ov8Ahvf9lb/ocJP/AAU6j/8AItO/4b2/ZV/6HF//AAU6l/8AItAH2HRXx5/w3t+yr/0OT/8Agp1L/wCRaX/hvX9lT/oc3/8ABTqX/wAi0AfYVFeafC74vfD34z6Fc+JPhvqh1bTrS7axmlNtPbbZ0RJCm24jjY4SRTkDHOO1el0AFFFFAH//0f38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPyB/4Kp/6/4ZD/AGNd/nYV+Rtfrj/wVT/4+Phn/ua7/Owr8jqACiiigAooooAKKKKACiiigD+oL9mr/k3z4cf9ixpX/pMle3V4h+zT/wAm9/Dj/sWNL/8ASdK9voAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKhuOIJP8Acb+VTVBc/wDHvL/uN/KgD+RaiiigAooooAKKKKACiiigAr9Cv+CaP/Jweof9ixff+lFpX561+hP/AATR/wCThL//ALFi+/8ASi0oA/eeiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr83/wDgp5/yQzw8P+pstf8A0hva/SCvze/4Kef8kN8O/wDY2W3/AKQ3tAH4YUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV/SN+xx8Zj8aPglpWpalP52vaHjR9X3HMjz26r5c7Z5PnwlHZunmbwPu1/NzX2/+wR8Zf8AhV3xrtvDupzeXofjUR6Vcgn5I7zd/oUuAOokYxdgFlJPSgD+hGiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKpajqNjpGn3WrapPHa2dlDJcXE8pCxxQxKWd2J4CqoJJ7AUAfmR/wUt+MY0PwfpPwY0mbF54iZdS1UKeV0+2f9whGOk1wm4EEEeQQeGr8U69d+O3xSvfjN8V/EPxCut6w6jdFbGF+DBYwjy7aPAJAYRKpfHBcse9eRUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAfuZ/wTC/5Ij4j/AOxquP8A0is6/SSvzb/4Jhf8kR8R/wDY1T/+kVnX6SUAFFFFAH//0v38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPyA/4Kpf8AHz8M/wDrnrn87GvyOr9cP+CqX/H18M/+ueufzsa/I+gAooooAKKKKACiiigAooooA/qB/Zp/5N7+HH/Ys6X/AOk6V7fXh/7NH/Jvfw5/7FnS/wD0nSvcKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACoLr/j2l/65t/Kp6r3X/HrN/1zb+VAH8i9FFFABRRRQAUUUUAFFFFABX6E/wDBNL/k4S//AOxZvv8A0otK/Pav0I/4Jpf8nCX3/Ys33/pRa0AfvRRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV+b3/AAU8/wCSG+Hf+xrtv/SK9r9Ia/N3/gp7/wAkO8Of9jXbf+kV7QB+GNFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFOVmRg6EqykEEcEEdMU2igD+mX9lb4xr8b/AIL6L4supRJrFov9mayO4v7VVDucBQPOQpMAowok29q+i6/A3/gnn8Zh8PPi+fAmrT+Xo3jhY7MbiAsWpxZNo3/bTc0GB1Z0zwtfvlQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFfnn/wUV+MZ8CfCaH4d6RP5ereNnaCXYSGj0u32m4OQePOYpDgjDI0g7V+hZKqpZiAAMkngACv5mv2qPjC3xt+NOueLbWUyaPbMNM0Ydhp9oSEcZCkec5ebBGVMm3tQB860UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAfuZ/wTB/5Ij4j/7Gqf8A9IrOv0kr82/+CYP/ACRHxH/2NU//AKRWdfpJQAUUUUAf/9P9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD8f8A/gql/wAfXwz/AOueufzsa/I+v1w/4Kpf8ffw0/6565/Oxr8j6ACiiigAooooAKKKKACiiigD+oD9mf8A5N6+HP8A2LOmf+k6V7hXh37M/wDyb18Of+xZ0z/0nSvcaACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACq93/x6zf9c2/lViq93/x6Tf8AXNv5UAfyL0UUUAFFFFABRRRQAUUUUAFfoR/wTT/5OEvf+xZvv/R9rX571+g//BNP/k4W9/7Fm+/9H2tAH700UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFfm7/wAFPf8Akh/hz/sa7f8A9Ir2v0ir83P+Cnv/ACQ/w5/2Ndv/AOkV5QB+GVFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAE9rdXNjcw3tlK8FxbussUsZKvG6EFWUjkFSAQR0r+nj9nL4u2vxu+EOg+O1ZPt8sP2XVYkwPJ1G3wk42gnarnEiA8+W61/MBX6P/8ABOL40f8ACG/Eq6+Fes3GzSfGKhrPe2Ei1W3UlMZIVftEQMZwCzOsSigD90aKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPir9u/wCMY+FnwPvdH02cR654y36PZgEb0tnX/TJgOuFhPl5HKvKh7V/PJX2B+278ZP8Ahbvxx1KPTZ/N0Hwtu0bTdrZjcwMftM4wxU+bNkK643RJH6V8f0AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH7mf8Ewf+SI+I/8Asap//SKzr9JK/Nv/AIJg/wDJEfEf/Y1T/wDpFZ1+klABRRRQB//U/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/H7/gql/wAffw0/6565/Oxr8kK/W/8A4Kpf8fnw0/6563/Oxr8kKACiiigAooooAKKKKACiiigD+n/9mb/k3r4c/wDYtaZ/6ISvca8N/Zl/5N5+HP8A2LWmf+iEr3KgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqtef8ek/wD1zb+VWarXn/HnP/1zf+VAH8jFFFFABRRRQAUUUUAFFFFABX6D/wDBNP8A5OFvP+xavv8A0fa1+fFfoN/wTU/5OFvP+xavv/R9rQB+9VFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5uf8FPf+SIeHP+xqt/8A0ivK/SOvzc/4Ke/8kQ8Of9jVb/8ApFeUAfhlRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVf0rVNQ0PVLPWtIuHtL7T54rq1niOHimhYPG6nsVYAj6VQooA/qX+BvxS0/4zfCzw/8Q7HYj6lagXkCdLe9h/d3EWDzhZVOzPJTa3evWa/E7/gmp8Zf+Ef8Zan8GdXmxY+JA2oaWG+6mo20f71Bx/y3t0zknGYVA5av2xoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr5j/a7+MX/ClvghrOv2E3k63qg/sjRypIZbu6Vh5qkA4MESvKM8bkVe4r6cr8Bf8AgoT8Yh8RPjKfBWlzCTR/AySaeNpBV9RkKm8boD8hVIMHo0TEfeoA+CaKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD9y/+CYP/JEvEg/6mqf/ANIrOv0lr82v+CYP/JE/En/Y0z/+kVnX6S0AFFFFAH//1f38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPx+/4Kpf8AH58NP+uWt/zsa/JCv1u/4Kpf8fvw0/65a3/Oxr8kaACiiigAooooAKKKKACiiigD+n79mX/k3n4c/wDYtab/AOiFr3KvDP2ZP+Tefhz/ANi3pv8A6IWvc6ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACq17/AMec/wD1yf8AlVmqt7/x5T/9cn/9BoA/kZooooAKKKKACiiigAooooAK/Qb/AIJqf8nDXn/YtX3/AKPta/Pmv0F/4Jq/8nDXf/Yt33/o62oA/euiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr82/+Cnv/JEPDn/Y1W//AKRXlfpJX5t/8FPf+SI+G/8Asarf/wBIrygD8M6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigDa8OeINW8J+INN8UaDMbbUtIu4b20lAB2TW7h0ODwQGUcHgjiv6k/hP8RdJ+LPw50D4iaLhbfWrNJ2iB3eROvyTwk4GTDKrRk4wduRxX8qVfq9/wTO+MwsNY1j4H6zPiHUt+r6KGPS5iQC6hXud8SrKoGAPKc9WoA/ZKiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA8S/aI+LVr8E/hDr/j12T7dbwfZ9LifB87ULj5LddpK7lVv3jgc+WjEdK/mDurq5vrma9vZXnuLh2lllkYs7u5yzMTySTyTX6Uf8FJvjGfE/wAQdO+EWkTk6d4UQXWoBSdsmp3SAqCPunyLcgKR0aWRT0r80KACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/cv/gmD/yRPxJ/2NM//pFZ1+ktfm1/wTB/5In4k/7Gmf8A9IrOv0loAKKKKAP/1v38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPx9/4Kpf8AH58NP+uWt/zsa/JGv1u/4Kpf8fvw0/65a3/Oxr8kaACiiigAooooAKKKKACiiigD+nz9mP8A5N4+HP8A2Lenf+iVr3SvC/2Yv+TePhz/ANi3p3/ola90oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKq3v8Ax5z/APXJ/wCVWqq3v/HlP/1yf/0GgD+RmiiigAooooAKKKKACiiigAr9BP8Agmr/AMnDXX/Yt33/AKOtq/Puv0E/4Jrf8nD3X/Yt33/o62oA/eyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr82/+Cnv/JEfDf8A2NVv/wCkV5X6SV+bf/BT3/kiPhv/ALGq3/8ASK8oA/DOiiigAooooAKKKKACiiigD6Y/ZN+HOjfFn4tf8K8135bXWdF1WES7QxgmFszQzKOMtFIquB3xjpXgfibw5q3hDxHqfhTXofs+paPdzWN3FkEJNbuY3AI4IyvBHBHI4r6+/wCCe/8Ayc7oX/Xhqf8A6TPXs/8AwUs+Df8AYPjLSvjNpEOLLxIq6dqhXomo20f7lzk/8trdNoCgAeQSeWoA/L6iiigAooooAKKKKACuo8E+L9a+H/i7R/Gvh2TytR0S8hvbcnO0tEwOxwCMo4+V17qSOlcvRQB/V98PfHGi/ErwRonjzw8+/T9bs4ruIZBaMuPnifbkb4nBjcDoykdq7KvyO/4JnfGfzINY+But3HMW/WND3t/AcC7t1yex2zIijvKx6V+uNABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXnXxa+IulfCX4b+IPiJrADQaJZvOkRO3z52wkEIIBwZZmSMHHG7PQV6LX46/8FM/jGLrUNE+CGjTgx2W3WdZCEf651K2kDYOQVjLSspGCHiYdKAPyw8Qa7qnijXdR8S65ObnUdVupr27mIAMk9w5kkbAwBlieBwO1ZFFFABRRRQAUUUUAFFFd78Lvh9q/xU+IOg/D3Qhi71u8S28zbuEMX3ppiOMrDErSMB2XigD3rTfgqui/sh638bdZhxea9rVlpukbgPksLeVhPKDnjzp02YIBAhyMhq+SK/eT9vHw3pHg79km08J6BD9n03R77R7G0izkrDbhkQE9zgck8k81+DdABRRRQAUUUUAFFFFABRRRQB+5f/BMH/kifiT/ALGmf/0is6/SWvzZ/wCCYP8AyRTxJ/2NM3/pFZ1+k1ABRRRQB//X/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/H3/gql/wAfnw0/65a3/Oxr8ka/W7/gql/x+fDT/rlrf87GvyRoAKKKKACiiigAooooAKKKKAP6e/2Yv+TePh1/2Lenf+iVr3WvCf2Yf+Td/h1/2Lmn/wDola92oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKq3v8Ax5z/APXJ/wCVWqq3v/HnP/1yf+VAH8jNFFFABRRRQAUUUUAFFFFABX6B/wDBNb/k4e5/7Fu+/wDR1tX5+V+gX/BNf/k4e4/7Fy//APRttQB+91FFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5t/8FPf+SI+G/wDsarf/ANIryv0kr82/+Cnv/JEfDf8A2NVv/wCkV5QB+GdFFFABRRRQAUUUUAFFFFAH21/wT3/5Od0L/rw1P/0mev27+OnwssPjP8KvEHw8vNiS6jak2Uz9IL6H95bSZAJCrKq78clNy96/ET/gnv8A8nO6F/14an/6TPX9C1AH8jup6Zf6LqV3o+q272l7YTyW1zbyjbJFNCxR0YdmVgQR2xVGv0U/4KMfBkeCPilb/EzR7fy9J8aIWudi4SLVbcATfdUKvnpslGSWd/NPavzroAKKKKACiiigAooooA7n4aePdZ+F3j7QviBoB/03Q7yO5VN21ZYx8ssLEchJoy0bY/hY1/Ur4R8UaP438L6T4w8PS+fpus2cN7av0JinQMoYfwsM4Zf4SMdq/kxr9n/+CaHxl/tbw3q3wT1ibNzohfVdHDd7KdwLiIcYAincOOcnzjjhaAP1RooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigDmfGfi3RvAfhLV/GniCTytO0SymvbgjG4pChbagOMu2NqL3YgCv5ZviD431r4k+Ntb8d+IX3X+t3kt3KASyxhz8kSbsny4k2xoOyKB2r9bv+CmPxi/snw1o/wU0ibFxrZXVtXC9rK3ci2iPGCJZ0L8EFfIHZq/GCgAooooAKKKKACiiigAr9hP8Agmb8GDBaaz8cdat8Nc79H0Tev/LJSDdzrkYOXCwqynjbKp61+UngnwfrXj/xfo/gnw7F5uo61eQ2VuDnarSsF3vgEhEHzOcfKoJ6Cv6lvh94I0X4beCdE8B+HU2afodnFaRHaqtJsHzSvtAHmSvl3IHLMTQB8g/8FGf+Tbbn/sM6d/N6/n+r+gH/AIKM/wDJttz/ANhnTv5vX8/1ABRRRQAUUUUAFFFFABRRRQB+5X/BMH/kiniX/saZv/SK0r9Jq/Nn/gmD/wAkU8S/9jTN/wCkVpX6TUAFFFFAH//Q/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/H3/gql/wAfvw1/65a3/Oyr8ka/W7/gql/x+/DX/rlrf87KvyRoAKKKKACiiigAooooAKKKKAP6ev2YP+Td/h1/2Lmn/wDopa92rwj9l/8A5N3+HX/Yu6f/AOilr3egAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqrff8eVx/1yf/0GrVVb7/jyuP8Ark//AKDQB/IzRRRQAUUUUAFFFFABRRRQAV+gX/BNf/k4i4/7Fy//APRttX5+1+gP/BNj/k4if/sXb/8A9G29AH730UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFfm3/wU9/5Ij4b/AOxqt/8A0ivK/SSvzb/4Ke/8kS8N/wDY1Qf+kV5QB+GdFFFABRRRQAUUUUAFFFFAH21/wT3/AOTndC/68NT/APSZ6/oWr+en/gnv/wAnO6F/14an/wCkz1/QtQB4D+038H4vjf8ABrXfBcUatqiR/b9HZsDZqNqC0QBPCiUboWbskhr+ZCSKSGRoZkMckZKsjDBUjggjsR6V/XbX8/H7f3wb/wCFZ/GqbxTpkPl6L44EmqQ4+6l+CBex8knJdlm6ADztoHy0AfC1FFFABRRRQAUUUUAFemfBz4lan8IPiZ4f+IulAu+j3ayTQqQPPtXBjuIckEDzIWZQcfKSCOQK8zooA/rb0LW9L8S6Jp/iPQ51utO1S1hvLSdeFlguEDxsM4OGUg1q1+Zv/BNn4zDxP4C1D4P6xPu1HwoxutODH5pNLuX+ZR3P2ediCegWWNRwK/TKgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKztX1XTtB0q91zWJ0tLDTreW6up5DhIoIELyOx7BVBJ9hWjX5y/8ABRz4yf8ACGfC+1+GGjz7NV8ZOftWxsPFpVsQZM7WBXz5NkYyCrxiVe1AH49/Gr4m6h8Yfij4h+ImoBk/ta7ZraFsZgs4gI7aLjjMcKqrEdWBPevLaKKACiiigAooooAKKK2PD+g6r4p17TvDOhQG51HVrqGytIQQDJPcOI41ycAZYgZPAoA/UP8A4JnfBr+0Na1j436zBmHSw+kaLuA/4+pUBupl7gxwssSnoRK46rX7K15z8I/hxpXwj+G3h/4daOQ8Gi2aQvKAV8+4bLzzYJOPNmZnxnAzgcAV6NQB8If8FGf+Tbbn/sM6d/N6/n+r+gH/AIKM/wDJttz/ANhnTv5vX8/1ABRRRQAUUUUAFFFFABRRRQB+5P8AwTB/5Ir4lH/U0zf+kVpX6T1+bH/BMH/kiviX/saJv/SK0r9J6ACiiigD/9H9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD8fP+CqX/AB+/DX/rlrf87KvySr9bf+CqX/H78Nf+uWt/zsq/JKgAooooAKKKKACiiigAooooA/p4/Ze/5N2+HX/Yu2H/AKKFe8V4P+y9/wAm7fDr/sXbD/0UK94oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKqX3/Hjcf8AXJ//AEGrdVL7/jxuP+uT/wDoNAH8jVFFFABRRRQAUUUUAFFFFABX6Af8E2P+TiJv+xdv/wD0bb1+f9foB/wTZ/5OJm/7F2//APRlvQB++FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5tf8FPf+SJeG/wDsaoP/AEivK/SWvza/4Ke/8kS8N/8AY1Qf+kV5QB+GlFFFABRRRQAUUUUAFFFFAH21/wAE9/8Ak53Qv+vDU/8A0mev6Fq/np/4J7/8nO6F/wBeGp/+kz1/QtQAV8s/tifBo/Gj4IavpOnQedrui/8AE40gKMu9xbK2+BQOSZ4S8ar03lCfu19TUUAfyH0V9efts/Bn/hT/AMb9S/sy38nQPE27WdM2LiOPzmP2i3XCqi+TNu2ov3Ymjz1r5DoAKKKKACiiigAooooA9j+AXxXvfgr8WNA+IFsXa2srgRajBH1nsJ/kuI9uVBbYdyZ4Eiqe1f1A6dqFjq2n22q6ZPHdWd5DHcW88TBo5YZVDI6EcFWUggjtX8jdfu//AME6fjR/wnPwsn+Gms3G/V/BTLHb72y8ulTkmDGTk+QwaIgAKkflDvQB+iNFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFADJJI4o2llYIiAszMcBQOpJ7AV/Mh+038XpPjb8Zte8aRSM+lrJ9g0dTkBNOtSViIDAFfNO6Zl7NIRX7H/ALffxiHwy+CVx4a02YR6142Mmk24BG5LLaDeyYIII8thD2IMwYdK/nyoAKKKKACiiigAooooAK/Tb/gmv8Gj4k8daj8YdXgzp/hZTZaaWHyyancph2HY/Z7duQRwZkYfdr81dO0++1a/ttK0yCS6vLyaO3t4Il3SSyysEREUclmYgADvX9QXwD+FFj8FfhP4f+H1qEa4sbcSahNH0nv5/nuHzhSV3krHkZEaqvagD2KiiigD4Q/4KM/8m23P/YZ07+b1/P8AV/QD/wAFGf8Ak225/wCwzp383r+f6gAooooAKKKKACiiigAooooA/cn/AIJg/wDJFfEv/Y0zf+kVpX6T1+bH/BMH/kiviX/saJv/AEitK/SegAooooA//9L9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKK8V+IPx38DeAfNs2n/tXVUyv2KzIYowzxLJ9yPBGCOXH9zFd2X5bisdVVDCU3KXZf1ovwOTGY7D4Sn7XEzUY+f9fke1V4D4//AGifA3gvzLHTpP7d1NOPItHHkow7ST4Kj6IHIIwQK+MfiB8c/HPj/wA2znuf7N0t8j7DZkojJ6Sv96TjqDhOMhRXjdfsvD3hNFWrZvK/9yP6v9F95+XZ34kN3pZZG395/ov8/uPtr4Q/Ejx38WfilFLq959m0rSrae8On2gMdsxIWFA/VpCGcON5IBXKgV9rV8Ifs13Z8OaJrWsR24kuNQmit42fhVS3Uk4A5OTJzyPuivrDwdeajq1zd6jfys4QLFGvSMZ5OFHGQAPevgOPnho5vPDYSCjTppRSSsu7/F2+R9lwaq8ssjXxMm5zblr9y+Vkjv6KKK+LPqgooooAKKKKACiiigAooooA/Hz/AIKpf8fvw1/65a3/ADsq/JKv1t/4Kpf8fvw1/wCuWt/zsq/JKgAooooAKKKKACiiigAooooA/p3/AGXf+Tdvh1/2Lth/6LFe814L+y5/ybr8Ov8AsXrH/wBFiveqACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACql9/x43H/XJ/8A0GrdVL7/AI8bj/rk/wD6DQB/I1RRRQAUUUUAFFFFABRRRQAV9/8A/BNn/k4mX/sXb/8A9GW9fAFff3/BNr/k4qT/ALF6/wD/AEZBQB++VFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5tf8ABT3/AJIl4b/7GqD/ANIryv0lr82v+Cnv/JEvDf8A2NUH/pFeUAfhpRRRQAUUUUAFFFFABRRRQB9tf8E9/wDk53Qv+vDU/wD0mev6Fq/np/4J7/8AJzuhf9eGp/8ApM9f0LUAFFFFAHxZ+3b8Gh8Vfghe6tpsAk13wdv1iyKgb3t0X/TIQeuGhHmBRyzxIK/nir+u8gEYIyDxiv5of2r/AIOf8KS+NeteF7OLytFviNU0bH3RY3RYrGOWOIJFeHnk+XuxyKAPm6iiigAooooAKKKKACvfP2ZvjBN8EPjHoXjZ3ZdLMn2HWEXJ36dckLL8o5YxELMq93jUV4HRQB/XXDNDcQpcW7rJFKodHQhlZWGQQRwQR0xUtfB3/BPz4y/8LJ+DSeDtVm8zWvA5j05933pNOYH7E/QD5UVoMDJxECfvV940AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUV8q/tk/GM/Br4HavqWnTmHXdc/4k2kFCVdJ7lW8yZSuCpghDurdBIEB60AfjV+2f8AGQfGP44ard6bOJtB8PZ0bSijAxyR2zHzZ12kqwmmLMrDGYvLB6V8nUUUAFFFFABRRRQAUUU+OOSaRYYVLu5CqqjJJPAAA70AfoX/AME6vgyPHXxVm+JGsW+/SPBSrLBvUFJdUnBEAAIwfIUNLwQUcRHvX7xV8/fsw/B6L4IfBnQ/Bk0apqskf9oawy4+bUboAyglSQwhULCrDqsYNfQNABRRRQB8If8ABRn/AJNtuf8AsM6d/N6/n+r+gH/goz/ybbc/9hnTv5vX8/1ABRRRQAUUUUAFFFFABRRRQB+5H/BMD/ki3iX/ALGib/0jtK/SivzW/wCCYH/JF/E3/Y0S/wDpHaV+lNABRRRQB//T/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoorzzxx8UvBfw9gLeIb5Rcld0dlBiS6kHbEYxtBxwzlV966MJg6+JqqjhoOUn0SMMRiaOHpurXkoxXV6I9Dryrx98ZPA3w8V4NVvPtOoqOLC0xJP2xv5CxjBB+cjI+6DXxp8QP2lfGXirzLDw5nw/px4/cNm7kH+1MMbOnSMLjoWYV84MzMxZjknkk9Sa/YOHfCapO1bN5cq/kjv83svRX9Ufmed+I9OF6WWRu/5nt8l/nb0PePiD+0L448beZY2Un9h6W+V+zWjnzHU9pZuGb0IUIpHVTXg1FFftGW5VhMBS9hg6ahHy/Xv8z8rx2Y4nGVPa4qbk/62Wy+QUUVteHNLOta9p+lAZFzcRxtjshPzH8FzXXXrQo0pVZ7RV/kjmo0pVakaUN3ZI+zfAGlf2N4O0uyK7XMAmkHffN85H4Zx+FfT/g2z+yaDCxGGnLSn8eF/8dArxSCFpZI7eFfmcqiKPU8AV9H28CW1vFbR8JEiov0UYFfxdjsVLE4ipiZ7ybf3n9UYTDxw9CFCG0Ul9ysTUUUVynQFFFFABRRRQAUUUUAFFFFAH4+f8FUv+P34a/8AXLW/52VfklX62/8ABVL/AI/fhr/1y1v+dlX5JUAFFFFABRRRQAUUUUAFFFFAH9Ov7Lf/ACbr8O/+xesf/RYr3uvBP2Wv+TdPh3/2L9l/6LFe90AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFVL7/AI8bj/rk/wD6DVuql9/x43H/AFyf/wBBoA/kaooooAKKKKACiiigAooooAK+/f8Agm3/AMnFSf8AYvX/AP6Mgr4Cr79/4Jt/8nFP/wBi/f8A/ocFAH750UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFfm1/wU9/5Il4b/AOxqg/8ASK8r9Ja/Nr/gp7/yRLw3/wBjVB/6RXlAH4aUUUUAFFFFABRRRQAUUUUAfbX/AAT3/wCTndC/68NT/wDSZ6/oWr+en/gnv/yc7oX/AF4an/6TPX9C1ABRRRQAV8A/8FDPg2fiF8Hh460mDzNY8Ds96doy0mmS7RdrgYH7sKk2T91Y3A+9X39Ve7tLW/tJrC+hS4triNoZoZFDJJG42srKeCpBwR0xQB/IvRXtn7Q/wkuvgl8Xdf8AAUiv9ht5/tGlyvk+dp1x89u24hdzKv7tyBjzEYDpXidABRRRQAUUUUAFFFFAH0v+yV8Zf+FJfGrR/Ed9N5Wiaif7K1nP3Vsrpl/eng8QSKkpwMlUKjrX9LAIIyOlfyIV/Q9+wn8Zh8V/glZ6Tqc/ma94O8vSL0McvJbov+hzkdcPEvlknlnic0AfaVFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV/Px+3/8Yv8AhZXxrm8K6ZN5mi+B1k0uED7r35IN7J90EEOqwY5H7ncPvV+x/wC0r8XYPgl8HNe8brIi6kIfsWkRttPmajcgpDhW4YR8yuveONq/mLllluJXnndpJJGLu7nczM3JJJ6k0AR0UUUAFFFFABRRRQAV90fsA/Bv/hZnxph8U6nDv0TwOItUmz9178kiyj4IIw6tN0I/c7SPmr4Xr+kz9j34NH4LfBHSNH1CDydd1n/ib6wGGHS5uVXZCwPIMEQSNh03hiOtAH1JRRRQAUUUUAfCH/BRn/k225/7DOnfzev5/q/oB/4KM/8AJttz/wBhnTv5vX8/1ABRRRQAUUUUAFFFFABRRRQB+5H/AATA/wCSLeJv+xol/wDSO0r9KK/Nb/gmB/yRfxN/2NEv/pHaV+lNABRRRQB//9T9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoorm/E/i/wANeDbD+0vE2oQ2EH8PmH55CO0aLl3PsoNaUaNSrNUqUbyeyS/JGdWrClBzqNJL5I6SuM8Y/EDwl4Ds/tnibUI7UsCYoB888uP7ka/MeeM42juRXx/8QP2qtW1DzNO+H9t/Z0HT7dcqr3LDj7kfMcfcc7zjptNfJ2oajf6reSahqlzLd3UxzJNO5kkY9OWbJPFfrHDvhTisRatmkvZx/lXxf5R/F+SPznO/EXD0L0svXPLv9n/N/gj6a+IP7UPiXXvN07wXEdEsWyv2hsNeyLyOoysOR/dywI4evl+4uLi8nkuruV5ppWLySSMWd2PUknkk1DRX7blGRYHLKXssFTUV+L9Xuz8nzLN8Xj6ntMVO/wCS9Fsgooor1zzQooooAK9j+CWlfbfFcmouuU0+3ZgfSSX5F/8AHd35V45X1b8DtK+yeGrjVGXD39wQp9Y4RtH/AI8Wr4jxDzH6pkVa287QXz3/APJbn1nBOB+s5vSvtH3vu2/Gx9I+ELP7XrtvkZWDMze2z7v/AI9ivcq81+HtnhLu/YDkrCh78fMw/wDQa9Kr+Vj+iAooooAKKKKACiiigAooooAKKKKAPx9/4Kpf8fvw1/65a3/Oyr8ka/W7/gql/wAfvw1/65a3/Oyr8kaACiiigAooooAKKKKACiiigD+nT9ln/k3T4d/9i/Zf+gCvfK8C/ZY/5N0+Hf8A2L9n/wCgV77QAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVVvv+PK4/65P/6DVqqt9/x5XH/XJ/8A0GgD+RmiiigAooooAKKKKACiiigAr78/4Juf8nFt/wBi/f8A/ocFfAdfff8AwTc/5OLb/sX7/wD9DgoA/fWiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr82/+Cnv/JEfDf8A2NUH/pFeV+klfm3/AMFPf+SI+G/+xqt//SK8oA/DOiiigAooooAKKKKACiiigD7a/wCCe/8Ayc7oX/Xhqf8A6TPX9C1fz0/8E9/+TndC/wCvDU//AEmev6FqACiiigAooooA/NT/AIKR/BoeKvh5Y/FvR7cNqXhJhb35RRuk0u5cDJwNzfZ5yCB0VJJGPSvw6r+tvXNF0vxJot/4d1uBbrTtUtZrO7gf7ssE6GORDjHDKSOK/lv+MXw11P4QfEzxB8OtUJd9Gu2jhmYAefauBJbzYBIHmQsjYz8pOOooA80ooooAKKKKACiiigAr63/Ys+M4+Dfxt02XU7jydA8R7dH1Xc2I41mYeRcHJVV8mbaWY/diMmOtfJFFAH9eFFfJv7GPxn/4XN8EtMutSuPO1/w9jR9W3NmSSSBR5M7ZJY+fDtZmOAZA4HSvrKgAooooAKKKKACiiigAooooAKKKKACiiigAoorzL4yfErTfhB8MfEPxF1MK66PZtJBC2QJ7qQiO3h+UEgSTMik4+UHPQUAfj7/wUf8AjIfF/wAS7P4V6RPu0vwfHvvAh+SXVLpQWzhireRDtQcAo7SrX5vVpazrGpeIdYvtf1mdrrUNSuZby6nf70s87l5HOOMsxJrNoAKKKKACiiigAooooA+wf2Ivgx/wt743adLqdv52geFtusaluXMcjQsPs1ucqynzZsFkbG6JJMdK/ovr4z/YZ+DI+EvwRsb/AFK38rXvF2zWL/coEkcMi/6JAeAwEcJ3FTyskjivsygAooooAKKKKAPhD/goz/ybbc/9hnTv5vX8/wBX9AP/AAUZ/wCTbbn/ALDOnfzev5/qACiiigAooooAKKKKACiiigD9x/8AgmB/yRfxN/2M8v8A6R2lfpTX5rf8EwP+SMeJv+xnl/8ASO1r9KaACiiigD//1f38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooqhqWqabo1lJqOrXUNlawjLzTusca9hlmwPYVUIOTUYrUUpKKu9EX6xtd8Q6H4Y059V8QXsNhaR9ZJmCgnGdqjqzEDhVBJ7CvlP4gftV6dZ+Zp3w9tftsv3f7QulKQL7xxcO/1bYAR0YV8aeJPFXiLxfqB1TxLfzX9weA0p+VAf4UQYVF/2VAHtX6dw74XY/GWq4/8AdQ7fafy6fP7j4DO/EDB4W9PB/vJ/+Sr59flp5n1r8Qf2rHbzdN+HVrtHK/2ldpz3GYoTx6EGT6FK+P8AWtd1jxFfyaprt5NfXcn3pZ3LtjsBnoo7KMAdhWVRX7jknDOXZTDlwVOz77yfz/RWXkfkmbZ9jcxlzYqenRLRL0QUUUV754wUUUUAFFFFABRRRQAV97+FNK/sTw3pulkYaC3QOP8ApoRl/wDx4mvjLwRpX9teLNL08jKPcK7j/pnF87D/AL5U197Wlu13dQ2qcGaRYwfTccV+G+MOY64fAR85P8l+p+ueGOB0rYx+UV+b/Q9u8KWn2PQbVcDdKvmnHffyP/HcCuipqIsaLGgwqgAAdAB0p1fiJ+sBRRRQAUUUUAFFFFABRRRQAUUUUAfj7/wVS/4/Php/1y1v+djX5I1+t3/BVL/j8+Gn/XLW/wCdjX5I0AFFFFABRRRQAUUUUAFFFFAH9OX7K/8Aybn8O/8AsAWf/oFe/V4B+yt/ybn8O/8AsAWn/oNe/wBABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABVW9/wCPKf8A65P/ACq1VW9/485/+uT/AMqAP5GaKKKACiiigAooooAKKKKACvvr/gm7/wAnGH/sAX//AKFDXwLX3z/wTe/5OM/7gGof+hQ0AfvvRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV+bf/BT3/kiPhv/ALGq3/8ASK8r9JK/Nv8A4Ke/8kR8N/8AY1W//pFeUAfhnRRRQAUUUUAFFFFABRRRQB9tf8E9/wDk53Qv+vDU/wD0mev6Fq/np/4J7/8AJzuhf9eGp/8ApM9f0LUAFFFFABRRRQAV+VP/AAUx+Df9p+H9H+Nujw5n0cppOsFR/wAuczk2sp5wBFOxjPGT5y9lr9Vq5jxp4S0bx74S1fwX4hi83Tdas5rK4UY3BJlK7kJBw6feQ4+VgCOlAH8m1Fdl8QvA+tfDXxvrfgLxCmy/0O8ltJTtKrIEPySoGAPlyptdDjlGBrjaACiiigAooooAKKKKAPtf9g/4zf8ACqvjZaaLqc/l6F4z8vSLwE4SO5Lf6FMR/syt5eTwqSse1f0M1/IgCVIZTgjoR2r+l39k/wCMg+N3wV0bxPeS+ZrVgP7K1n1+3WqqDIeFH7+MpN8owN+3+GgD6SooooAKKKKACiiigAooooAKKKKACiiigAr8Zv8Agpl8YhqWv6N8EtImBg0cLq+sBSD/AKXMhW1iPGQY4GaQjoRMnda/Wvx7400X4deC9a8c+IX2afodlLeTYKhnES5WNNxAMkjYRBnliBX8s3jfxhrPxA8Yax428QyeZqOt3k17ORnarStkImSSI0GFRc/KoA7UActRRRQAUUUUAFFFFABX0z+yP8G/+F2fGzRvD19D5uiaYf7W1kEfK1nasv7ojIyJ5CkRxyFcsPu18zV+/wD/AME+/g3/AMK3+DKeMdUh8vWfHBj1F93DR6cgIsk6kfMrNNkYOJQD92gD7wAxwKWiigAooooAKKKKAPhD/goz/wAm23P/AGGdO/m9fz/V/QD/AMFGf+Tbbn/sM6d/N6/n+oAKKKKACiiigAooooAKKKKAP3H/AOCYH/JGPE3/AGM8v/pHa1+lNfmr/wAEwP8AkjPif/sZ5f8A0jta/SqgAooooA//1v38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKY7pEjSSMERASzE4AA7n0AoAfVe6u7WwtpLy9mjt7eFS8ksrBI0UdSzHAAFfOHxB/aZ8IeGBJp/hYDX9QXK74m22cZ95R/rMccR5B6bhXxJ43+JnjL4g3Pm+I79ngVt0VnF+7tYuuNsY4JGcbmy2OM1+icPeG2ZZjariF7Kn5rV+kf87eR8RnfHWAwN6dH95Pstl6v/K/yPsX4g/tSeHtE8zTvA8I1m8XK/apMpZoRxxjDy4x22r3DGvinxd468V+Ob37d4n1CW8Kk+XEflhizxiONcIvAGcDJ75rkqK/dMg4QyzKI3w0Pf8A5nrL/geisfkWc8TY/MnavK0f5Vov+D8wooor6g+fCiiigAooooAKKKKACiiigAooooA90+BWlfaNbvtXcfLZwCJf9+Y9voqkfjX2l4JtPtGuJIfu26NJ0/4CP55/Cvnf4MaV9g8HLdsMPqEzze+xf3aj6fKSPrX1h8PrTZZXN6f+WsgjHHaMZ4/76/Sv5S4/zH63nteS2j7q/wC3dH+Nz+i+DMD9VyilF7yXN9+34WPQqKKK+MPqQooooAKKKKACiiigAooooAKKKKAPx9/4Kpf8fnw0/wCuWt/zsa/JGv1u/wCCqX/H78NP+uWt/wA7GvyRoAKKKKACiiigAooooAKKKKAP6cf2Vv8Ak3L4d/8AYBtP/Qa9/r5//ZV/5Ny+Hn/YBtP/AEGvoCgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqre/8ec//XJ/5Vaqre/8eU//AFyf+VAH8jNFFFABRRRQAUUUUAFFFFABX3z/AME3v+Tjf+4DqH/oUNfA1ffH/BN//k40f9gHUP8A0KGgD9+KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvzb/wCCnv8AyRHw3/2NVv8A+kV5X6SV+bf/AAU9/wCSI+G/+xqt/wD0ivKAPwzooooAKKKKACiiigAooooA+2v+Ce//ACc7oX/Xhqf/AKTPX9C1fz0/8E9/+TndC/68NT/9Jnr+hagAooooAKKKKACiiigD8fP+CmXwZEN1ovxw0W3AW42aNrexR/rFBa0nbA7qGhZmOBtiUV+SVf1ZfFL4faR8Vfh7r3w91zAtNbs3tvM27jDLw0MyjjLQyqkijplRX8tPibw5q3g/xHqfhTXofs+paPdzWN3FnISa3cxuARwRleCOCORxQBh0UUUAFFFFABRRRQAV97f8E+PjMPhx8Yl8FarN5ei+OBHYHdwsepRk/Y36E/OzNBgYyZFJ4Wvgmpreee0njurWRoZoXWSOSMlWR1OVZSOQQRwR0oA/rqorwj9mv4v2/wAb/g9oXjguh1IxfYtXjTA8rUbYBZvlHCiTiVF7Rute70AFFFFABRRRQAUUUUAFFFFABRRWVrut6X4a0TUPEWtzra6dpdrNeXc7fdigt0LyOcc4VVJ4oA/LT/gpn8YzY6RonwR0ecrLqW3WNZCEj/RomK2kLY4IeVWkK9VMUZ6Gvxvr0j4vfEfU/i58SvEHxF1YFJdavGljhJB8i2QCO3hyAAfKhVEzgZ256mvN6ACiiigAooooAKKKKAPeP2a/hDP8bvjFoPgco500y/bdXkTI8vTrbDTfMv3TJxCjdnkWv6c4IIbWCO2to1ihiVUjjQBVRFGAqgcAAcADpX51/wDBOX4Mf8IV8MLn4n6xb7NW8ZsPsu9cPFpVuSIsZUFfPk3SHBKvGIj2r9GaACiiigAooooAKKKKAPhD/goz/wAm23P/AGGdO/m9fz/V/QD/AMFGf+Tbbn/sM6d/N6/n+oAKKKKACiiigAooooAKKKKAP3G/4Jgf8kZ8T/8AYzy/+kdrX6VV+av/AATA/wCSM+J/+xnk/wDSO1r9KqACiiigD//X/fyiuQPj7wYvXWLX8Hz/ACqI/EPwUP8AmL2/6/4V4r4jylb4qn/4HH/M7lleM6UZf+Av/I7SiuIPxH8ED/mLQ/k3/wATTT8SvAw/5i0X/fD/APxNQ+KMmX/MXT/8Dj/mV/ZOO/58y/8AAX/kdzRXB/8ACzfAv/QVj/79yf8AxFJ/ws7wJ/0FU/79S/8AxFT/AK15J/0GUv8AwZD/ADH/AGPj/wDnxL/wF/5He0VwH/C0PAn/AEFV/wC/Mv8A8RSf8LR8B/8AQVX/AL8zf/G6X+tmR/8AQZS/8GQ/zH/Y2P8A+fE//AX/AJHoFFef/wDC0vAf/QUX/vzN/wDG6T/hafgL/oKD/vxN/wDG6X+tuR/9BtL/AMGQ/wAw/sXMP+fE/wDwF/5HoNFeff8AC0/AX/QUH/fib/43R/wtPwF/0FB/34m/+N0f63ZF/wBBtL/wZD/MP7FzD/nxP/wF/wCR6DRXn3/C0/AX/QUH/fib/wCN0f8AC0/AX/QUH/fib/43R/rdkX/QbS/8GQ/zD+xcw/58T/8AAX/keg0V59/wtPwF/wBBQf8Afib/AON0f8LT8Bf9BQf9+Jv/AI3R/rdkX/QbS/8ABkP8w/sXMP8AnxP/AMBf+R6DRXn3/C0/AX/QUH/fib/43R/wtPwF/wBBQf8Afib/AON0f63ZF/0G0v8AwZD/ADD+xcw/58T/APAX/keg0V59/wALT8Bf9BQf9+Jv/jdH/C0/AX/QUH/fib/43R/rdkX/AEG0v/BkP8w/sXMP+fE//AX/AJHoNFeff8LT8Bf9BQf9+Jv/AI3XW6RrOm69ZLqOkzefbsSofay8rweGAP6V2YLPssxlT2WExEJy7RlFu3omY18uxVCPPWpSivNNL8jUooor1TjCiiigAooooAKKKKACiiigDlPG/im38E+E9T8U3Mfmrp8G9Ys7fMkJCRpnBxuchc4OPSvzJ8efF/xx8Q3aLWb3ybDOVsLXMVsMYxuXJMhyMguWwemOlfXX7WHiH+z/AARp/h+JysmrXoZ17NBajcw/CRoj+FfntX9A+FfD2GWA/tKtTTm5Plb6Jaadtb6o/GPEPOq/1v6hSnaCSul1b7/K2gUUUV+vn5kFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFPjjeWRYohudyFUDuTwBTK7r4a6V/a/jTTIGXMcMn2l/QCAbx+BYAVx5jjI4TC1MVPaEW/uR1YHCvE4inh47yaX36H2XounJpGkWWlx/dtII4frsUAn8a+kfD1p9h0W0tyCD5YdgeoZ/mI/DOK8Q0u0+36jbWeCRLIqtj+7/F+Qr6Jr+L6tSVSbqT3Z/U9OnGEFCOy0CiiisywooooAKKKKACiiigAooooAKKKKAPx9/4Kpf8fnw0/wCuWt/zsa/JGv1u/wCCqX/H58NP+uWt/wA7GvyRoAKKKKACiiigAooooAKKKKAP6b/2U/8Ak3H4ef8AYCtf5V9A18+/sp/8m4/Dz/sBWv8AKvoKgAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqre/8AHnP/ANcn/lVqqt7/AMeU/wD1yf8AlQB/IzRRRQAUUUUAFFFFABRRRQAV97/8E4P+Tjl/7AWofzir4Ir72/4Jwf8AJxy/9gLUP5xUAfv1RRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV+bf/BT3/kiPhv/ALGq3/8ASK8r9JK/Nv8A4Ke/8kR8N/8AY1W//pFeUAfhnRRRQAUUUUAFFFFABRRRQB9tf8E9/wDk53Qv+vDU/wD0mev6Fq/np/4J7/8AJzuhf9eGp/8ApM9f0LUAFFFFABRRRQAUUUUAFfij/wAFLPg3/YPjHSvjPpEOLLxGq6dqhUcJqNtH+5c8/wDLa3TaAAAPIJPLV+11eSfHT4W2Hxm+FXiD4eXmxJdStSbKZ+kF9D+8tpMgZCrKq78clNy96AP5aqKvanpl/oupXej6tbvaXthPJbXNvKNskU0LFHRh2ZWBBHbFUaACiiigAooooAKKKKAP0X/4JzfGj/hCPifcfDDWbjZpHjNVFrvbCRarbgmLGSFXz490RwCzuIh2r92a/kd0zUr/AEbUbTV9Kne1vbGeO5tp4jtkimhYOjqR0ZWAIPbFf1B/Ab4q2Pxp+FHh/wCIVpsSe/tgl/BH0t7+H93cRgZJCiQEpnkxlT3oA9fooooAKKKKACiiigAooooAK/NX/gpH8Yx4U+HVh8JdInC6l4tcT3wQjfFpdqwODghl8+cKqnGGSORa/SS4uILS3kurqRIYIUaSSRyFREQZLEngAAcnsK/mG/aN+Ldx8bfjBr/jsu50+Wb7LpMT5HladbfJANpJ2Fx+9dRx5jtQB4dRRRQAUUUUAFFFFABXrXwM+Ft/8Zvip4f+HllvSPUroG8mTrb2MI8y4kyRgFYlOzPBfavevJa/a/8A4JqfBoaB4N1P4z6vDi98SFtO0skcpp1s/wC+cYP/AC3uEwQRwIFI4agD9MtL0zT9E0y00bSbdLSxsIIrW2giG2OKGFQkaKOyqoAA9BV+iigAooooAKKKKACiiigD4Q/4KM/8m23P/YZ07+b1/P8AV/QD/wAFGf8Ak225/wCwzp383r+f6gAooooAKKKKACiiigAooooA/cX/AIJgf8ka8T/9jPJ/6R2tfpXX5qf8EwP+SNeJ/wDsZ5P/AEjta/SugAooooA//9D9FPFvh+Xwxr91pD5McbboXP8AHC3KHoAeODjjII7VzdfUnxl8Mf2lo8fiC1TNxp3EuBy1ux56DPyNz2ABY18t1/EXHvDTyTOKmFiv3b96H+F9Plt8j974dzRY/Axqv4lo/Vf57hRRRXxh7oUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX0/8K/Eej6d4RitL2fypFmlONjHgn/ZBr5gr1Hwr/yB0/33/nX6/wCCf/JQS/69y/OJ8Vx5/wAi1f4l+TPrK0ure+t0urVt8Ug+VsEZxx0IBqzXO+FP+Res/wDdb/0I10Vf1ifjgUUUUAFFFFABRRRQAUUUhIAyeAKAPzp/ao8Qf2n8RItFjcmLRrKONk7Caf8AesR9UMY/Cvmaup8b6+fFPi/WPEOWKX97NLFu6iIsfLX/AICmB+FctX9j8PZd9RyyhhLaxir+vX8T+X87xv1vH1cR0bdvTZfhYKKKK9k8sKKKKACiiigAooooAKKKKACiiigAooooAK+hfgNpW651PW3X/VolrGf9473/AC2rXz1X2f8ACbSv7L8E2ZZdsl4Xun/4GcJ/44Fr878T8x+rZHKkt6jUflu/wVvmfb+H+B9vm0aj2gm/0X5/gfQHgOz8/V2uiPltoyQfRn+UfpmvYa4bwFZ+TpUl2y4a4k4PqkfA/XdXc1/MZ++hRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH4+/8FUv+P34af9ctb/nY1+SNfrd/wVS/4/Php/1y1v8AnY1+SNABRRRQAUUUUAFFFFABRRRQB/Tb+yl/ybh8PP8AsBW38q+g6+e/2Uf+TcPh5/2A7b+Rr6EoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKq3v/HlP/1yf/0GrVVb3/jzn/65P/KgD+RmiiigAooooAKKKKACiiigAr71/wCCcP8Ayccn/YC1D+cVfBVfen/BOL/k4+P/ALAeof8AtKgD9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvzb/AOCnv/JEfDf/AGNVv/6RXlfpJX5t/wDBT3/kiPhv/sarf/0ivKAPwzooooAKKKKACiiigAooooA+2v8Agnv/AMnO6F/14an/AOkz1/QtX89P/BPf/k53Qv8Arw1P/wBJnr+hagAooooAKKKKACiiigAooooA/CT/AIKMfBkeCPijb/EzR7fy9J8ZoWudi4SLVbcATfdUBfPj2yDJLO/mntX51V/Tn+018H4vjf8ABvXfBUcatqix/b9HdsDZqNqC0QBPCiUboWbskjV/MhLFJBI8MyNHJGxVkYbWVl4IIPQj0oAjooooAKKKKACiiigAr9N/+Ca/xl/4RrxzqPwd1ifbp/ihTe6aGPyx6nbJ86jsPPt15J7woo5NfmRWv4f13VPC+u6d4l0Oc2uo6VdQ3tpMoBMc9u4eNsHg4ZRweKAP62aK83+EPxI0r4u/DXw/8RNIASHWbRZZIQSfIuUzHPDkgZ8qZWTOBnGRwRXpFABRRRQAUUUUAFFFFAHwb/wUF+MX/CuPgu/g7S5vL1nxy0mmpt4ZNOjAN6/QjDKyQYODiUkfdr8Aa+of2wPjGPjR8b9Z1rT5xNoekH+x9HKkFGtbVm3TKRwRPKXkU9djKv8ADXy9QAUUUUAFFFFABRRRQB6B8Kvh5q/xX+ImgfDzRMrc63eJbmQLuEEI+eaYjIysMStIR3C4Ff1K+GvD2k+EfDumeFdBh+z6bpFpBY2kWc7ILdBGgyeSQqjJPJr8s/8Agmb8GTa6frPxx1q3w97v0fRN64/cRsDdzrkYIaRViVhyPLlXoa/WegAooooAKKKKACiiigAooooA+EP+CjP/ACbbc/8AYZ07+b1/P9X9AP8AwUZ/5Ntuf+wzp383r+f6gAooooAKKKKACiiigAooooA/cX/gmB/yRrxP/wBjNJ/6R2tfpXX5p/8ABL//AJI34n/7GaT/ANI7Wv0soAKKKKAP/9H9954IbmCS2uEEkUqFHRhlWVhggj0Ir4b8WeH5vDGvXWkSZKRNmFz/ABwtyh6AZxwccZBHavumvFvjN4Y/tHR4/EFqmbjTvllwOWt2PsM/I3PoAWNflHi7wx/aWUfXKK/eUdf+3ftL5b/I+x4LzX6rjfYTfuz0+fT/ACPlyiiiv5GP2cKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr1Hwr/yB0/33/nXl1eo+Ff+QOn++/8AOv1/wT/5KCX/AF7l+cT4rjz/AJFq/wAS/Jn0z4U/5F6z/wB1v/QjXRVzvhT/AJF6z/3W/wDQjXRV/WJ+OBRRRQAUUUUAFFFFABXm/wAXtf8A+EZ+GniDVVJWQWbW8RXgrLc4hQj/AHWcH8K9Ir5G/a41/wCy+GdF8NxkhtQu3uXwf+Wdqm3aR6FpQR/u19Dwnl/13N8PhraOSv6LV/gjxOI8b9UyytXW6jZer0X4nwRRRRX9fn8zBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQBasbSW/vLexg/1lxKkSf7zkKP51+gtnaxWNnBZQDEdvGkSD0VAFH6Cvjv4SaV/afja0dhmOyV7pv+ADav5Oy19yaDafbtYtLYgFTIGYHoVT5iPyFfgHi/mPPjKOCjtCN/m9PyX4n7N4Z4HkwtXFP7TsvRf8P+B7hpFn/Z+mW1ngKY41DAdN3Vv1rRoor8eP04KKKKACiiigAooooAKKKKACiiigAooooA/H7/gql/x+fDT/AK5a3/Oxr8kK/W//AIKpf8fnw0/6563/ADsa/JCgAooooAKKKKACiiigAooooA/pr/ZP/wCTb/h5/wBgO3/rX0LXzz+yd/ybf8Pf+wJb/wBa+hqACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACq15/wAec/8A1yf+VWarXn/HnOP+mb/yoA/kYooooAKKKKACiiigAooooAK+8/8AgnH/AMnHxf8AYD1D/wBp18GV95/8E4/+TkIf+wJqH/tOgD9/aKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvzc/4Ke/8AJEPDn/Y1W/8A6RXlfpHX5uf8FPf+SIeHP+xqt/8A0ivKAPwyooooAKKKKACiiigAooooA+2v+Ce//Jzuhf8AXhqf/pM9f0LV/PT/AME9/wDk53Qv+vDU/wD0mev6FqACiiigAooooAKKKKACiiigAr+fn9v/AODf/CtPjTL4q0yHy9F8cCTVIsD5Uv1IF7HySSS7LN0AHnbVGFr+gavlv9sP4NH40fBHV9I06Dztd0b/AIm+jhRl3uLZW3QqByTPCXjVem8oT92gD+bOiiigAooooAKKKKACiiigD9Vv+CZ/xm/szX9X+CGsTYt9YD6to249LyFALmFeMnzIEEgHAXyW7tX7NV/Jn4O8V6z4F8V6R4y8PS+TqWi3kN7bN/D5kLBgrAYyjY2svQqSOlf1LfDjx3ovxO8CaH4+8Ptmx1uzjukXIZomYYkhYjjfDIGjfHRlIoA7aiiigAooooAK+RP22PjJ/wAKg+B2pnTZ/J13xNnRtM2tiSPz1P2idcMrL5MG7a6/dlaPPWvruv56f28/jEfij8b7vQ9Nm8zRPBYfR7QAnY90rf6bKAR1MqiLI4ZIVI60AfEtFFFABRRRQAUUUUAFdZ4E8Ga18Q/GWjeB/Dsfmajrd5DZwZBKoZGALvtBIjjXLucfKqk9q5Ov1i/4Jm/BoXmqaz8b9ZgzFp+7R9F3gf8AHxIoN3MueQUiZYlYcESSDqtAH6weAvBei/DnwZovgXw7H5enaJZxWcGQoZxGuGkfaADJI2Xc45Yk111FFABRRRQAUUUUAFFFFABRRRQB8If8FGf+Tbbn/sM6d/N6/n+r+gH/AIKM/wDJttz/ANhnTv5vX8/1ABRRRQAUUUUAFFFFABRRRQB+4n/BL/8A5I34o/7GaT/0jtq/SyvzT/4Jf/8AJG/FH/YzSf8ApHa1+llABRRRQB//0v38qGeCG6gktrhBJFKhjdGGQysMEEehFTUUpRUlytaDTtqj4V8V6BN4Z1660iXJSJswuf44m5Q9AOnBxxkEdq52vqL4zeGP7R0iPxDapm40/wCWXA5a3Y+wz8jc+gBY18u1/EXHnDTyTOKmEiv3b96H+F7L5bfI/e+Hc0WPwMKz+JaP1X+e4UUUV8ae4FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXqPhX/kDp/vv/ADry6vUfCv8AyB0/33/nX6/4J/8AJQS/69y/OJ8Vx5/yLV/iX5M+mfCn/IvWf+63/oRroq53wp/yL1n/ALrf+hGuir+sT8cCiiigAooooAKKKKACvzW/ad17+1/ifNYJ/q9HtILQYOQXYecx9j+82n/dr9JZpYreJ55mCRxqXdjwFVRkk+wFfjd4l1mTxF4h1PX5htfUbua6K/3fNctt+gzgV+t+EOX+0zCrjGtIRt85f8BM/NvEvG+zwVPDL7Tv8o/8FoxKKKK/oU/FAooooAKKKKACiiigAooooAKKKKACiiigAooooA+lfgNpWyz1PWnH+skS2jPsg3N+e5fyr7C+H9p5l/cXhxiCMIPrIeo/BSPxrwf4b6V/ZHgvTLdhh5YvtD+uZvn5+gIH4V9QeB7T7PoazHrcSNJ06AfKB/47n8a/kTjHMfruc4iutuay9I+6vyP6W4YwX1TK6FHra/zep2NFFFfNHvBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH4/f8ABVL/AI+/hp/1z1z+djX5IV+t/wDwVS/4+/hp/wBc9c/nY1+SFABRRRQAUUUUAFFFFABRRRQB/TV+yb/ybd8Pf+wJB/Wvoavnj9kz/k274e/9gWD+Zr6HoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKrXn/HpP8A9c2/lVmq15/x6Tf9c2/lQB/IxRRRQAUUUUAFFFFABRRRQAV95f8ABOT/AJOQg/7Amof+06+Da+8f+Ccv/JyNv/2BdR/lHQB+/wDRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV+bn/BT3/kiHhz/sarf/ANIryv0jr83P+Cnv/JD/AA5/2NVv/wCkV5QB+GVFFFABRRRQAUUUUAFFFFAH21/wT3/5Od0L/rw1P/0mev6Fq/np/wCCe/8Ayc7oX/Xhqf8A6TPX9C1ABRRRQAUUUUAFFFFABRRRQAUUUUAfzmfttfBn/hT/AMb9SOmW/k6B4n3azpm1cRx+cx+0W64VUHkzZ2ov3Ymj9a+Qq/oe/bs+DQ+K3wQvdV02ASa74O36xZFQN726L/pkAPXDwjeFHLPEgr+eGgAooooAKKKKACiiigAr9d/+CZ3xowdY+But3H9/WND3t9Bd265b/dmRFX/nsxr8iK7X4c+Ota+GXjrQ/H3h5tt/od5HdRrkqsqrxJE5XnZLGWjcDqrEUAf1d0VzPgzxZo3jzwnpHjPw9L52m61Zw3tsxxuCTKGCsBna6/dZf4WBHaumoAKKKKAPnn9qT4wJ8Efgtrni+2lEerzoNN0YcZOoXQKxsAQVPkqGmKkYKxkd6/mXZmdi7kszHJJ6k1+hv/BRf4yDxz8VoPhxpE4k0nwVG0U+wgpJqlwFM/3Tg+SgSLBAKOJR3r88KACiiigAooooAKKKKANrw54f1bxZ4g03wvoMH2nUtXu4LG0hBA3z3DiNFycAAsRyeBX9Sfwn+HWk/Cb4c6B8O9Fw1volmkDSgbfPnOXnmIycGaVmcjOBuwOK/I7/AIJq/Br/AISHxtqfxk1eHNj4ZVrDTCw+V9SuY/3rDnH7i3bBBHWZSOVr9tKACiiigAooooAKKKKACiiigAooooA+EP8Agoz/AMm23P8A2GdO/m9fz/V/QD/wUZ/5Ntuf+wzp383r+f6gAooooAKKKKACiiigAooooA/cT/gl/wD8kc8Uf9jNJ/6R21fpZX5pf8Ev/wDkjnij/sZn/wDSO2r9LaACiiigD//T/fyiiigCG4ghuoJLW4QSRTIY3RhkMrDBBHoRXw14q0Cbwzr11o8uSsT5hc/xxNyjdAOnBxwCCO1fddeK/Gbwx/aGkR+IbVMz6f8ALNgctbsfYZ+RufQAsa/J/F3hj+0so+uUV+8oa+sftL5b/I+x4LzX6rjPYTfuz0+fT/I+XaKKK/kc/ZwooooAKKKKACiiigAooooAKKKKACiiigAooooAK9R8K/8AIHT/AH3/AJ15dXqPhX/kDp/vv/Ov1/wT/wCSgl/17l+cT4rjz/kWr/EvyZ9M+FP+Res/91v/AEI10Vc74U/5F6z/AN1v/QjXRV/WJ+OBRRRQAUUUUAFFFFAHkvxy18eHfhZr10pAlurf7DECcEm7IibHuqMzfhX5U19zftd+IPL03QfC0bKTPNLfzL/EohXy4vwbe/8A3zXwzX9LeFeXfV8l9u1rUk38l7q/Jn4P4h4322aexW0El83r+q+4KKKK/Sj4MKKKKACiiigAooooAKKKKACiiigAooooAK1NE019Y1iy0pOt1PHFx2DEAn8BWXXrnwX0r7f4wF6y5TT4Hl9t7fu1H5MSPpXkZ9mH1HLq2L/li7ettPxselk+C+t46lhv5ml8uv4H13FEFCQQrwAERVH4ACvo+ythZ2cFopyIY1jz67RivEfC1p9s120Qg7Y38047eX8w/DIAr3iv42P6iSCiiigAooooAKKKKACiiigAooooAKKKKACiiigD8f8A/gqkP9K+Gf8A1z1z+djX5H1+uH/BVL/j5+Gf/XPXP52NfkfQAUUUUAFFFFABRRRQAUUUUAf0z/sl/wDJtvw9/wCwLD/Nq+ia+dv2Sv8Ak234e/8AYFh/m1fRNABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABVe6GbWYf9M2/lViq91/x6y/8AXNv5UAfyL0UUUAFFFFABRRRQAUUUUAFfeH/BOb/k5G2/7Auo/wAkr4Pr7v8A+Cc3/JyVt/2BdR/klAH9ANFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5u/8FPR/xY7w7/2Ndt/6RXtfpFX5u/8ABT3/AJId4d/7Gu2/9Ir2gD8MaKKKACiiigAooooAKKKKAPtr/gnv/wAnO6F/14an/wCkz1/QtX89P/BPf/k53Qv+vDU//SZ6/oWoAKKKKACiiigAooooAKKKKACiiigBCARgjj0r+aL9rD4Of8KS+NeteGLKHytFviNV0bH3RY3RYrGOWOIJFeHnkiMN3Ff0vV8Bf8FC/g3/AMLC+Dw8c6VB5mseBme9+UZaTTJQou1wMD92FSbJ+6sbAfeoA/AyiiigAooooAKKKKACiiigD9l/+CZ/xm/tLQtX+CGsTZn0nfq2jBj1tJnAuoV4wBHMwkA6nzX7LX6s1/Kt8IviPqvwj+JOgfETRwXm0W7WWSEEL59uwMc8OSCAJYWZM44zkcgV/Uj4e17SvFOg6d4m0KcXOm6taQ3tpMAQJILhBJG2DgjKkcEZFAGxXkvxz+KNj8GvhV4h+Id5seTTbUiyhfpPezYjto8DB2tKy7schAx7V61X4q/8FLvjF/bfi7SfgvpE2bTw8q6lqoXo2oXMf7iMjHWG3bcCDg+fg8rQB+Y+p6lf6zqN1q+qzvdXt9PJc3M8p3SSzSsXd2PdmYkk1RoooAKKKKACiiigAq9pmm3+s6jaaPpVu91e308dtbQRLuklmlYIiKB1ZmIAFUa/RT/gnP8ABj/hN/ijcfEzWLffpHgtVa23rlJdVnBEOMqQfITdKcEMj+Ue9AH6/fAj4V2PwX+FPh/4e2mx5tPtg19MnSe+m/eXEgOASpkJCZ5EYVe1evUUUAFFFFABRRRQAUUUUAFFFFABRRRQB8If8FGf+Tbbn/sM6d/N6/n+r+gH/goz/wAm23P/AGGdO/m9fz/UAFFFFABRRRQAUUUUAFFFFAH7g/8ABL7/AJJB4p/7GRv/AEkt6/S+vzP/AOCX3/JIPFP/AGMjf+klvX6YUAFFFFAH/9T9/KKKKACobiCG6gktbhBJFMjRujDhlYYIPsRU1FKUVJcrWg07ao+FPFWgTeGdeutHlyVifMTn+OJuUboB04OOAQR2rnq+ofjN4Y/tDSI/ENqmZ9P+SbA5a3Y+wz8jc+gBY18vV/EPHfDTyTOKmEiv3b96H+F7L5fD8j974ezRY/BRrP4lo/Vf1cKKKK+OPcCiiigAooooAKKKKACiiigAooooAKKKKACvUfCv/IHT/ff+deXV6j4V/wCQOn++/wDOv1/wT/5KCX/XuX5xPiuPP+Rav8S/Jn0z4U/5F6z/AN1v/QjXRVzvhT/kXrP/AHW/9CNdFX9Yn44FFFFABRRRQAUUVUv7620ywudSvG8u3tIXnlb+7HGpZj+AFOMXJqMRNpK72PzQ/aO8Qf278VNQijdXh0qKHT4yv/TMb5AfcSu4/CvCa0dX1O51rVr3WbzBnv7iW5lx03zMXb9TWdX9m5PgFgsDRwi+xFL7kfy1meMeKxdTEv7Tb/yCiiivSOEKKKKACiiigAooooAKKKKACiiigAooooAK+pvgXpX2bQb3VnXDXk4jU/8ATOEf/FMR+FfLNfeHgrSv7F8KaZpxXa8durSD0kk+d/8Ax4mvy7xYzH2OUxwq3qSX3R1/PlP0Lw4wPtcxliHtCP4vT8rnuHw9s8y3V+wPyqsKHtzy35YWvUK5fwfZ/ZNBgJXa05Mzf8C4X/x0Cuor+cD9yCiiigAooooAKKKKACiiigAooooAKKKKACiiigD8gP8Agql/x8/DP/rnrn87GvyOr9cf+Cqf/Hz8M/8Arnrn87CvyOoAKKKKACiiigAooooAKKKKAP6Zv2Sf+TbPh7/2Bov/AEJq+iq+dP2SP+TbPh9/2B4v/Qmr6LoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKguv+PaX/rm38qnqC6/49pf+ubfyoA/kWooooAKKKKACiiigAooooAK+7v8AgnP/AMnJWv8A2BtR/wDQUr4Rr7t/4J0f8nJ2n/YH1H/0FaAP6A6KKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvze/4Ke/8kO8O/8AY123/pFe1+kNfm9/wU8/5Ib4d/7Gu2/9Ir2gD8MKKKKACiiigAooooAKKKKAPtr/AIJ7/wDJzuhf9eGp/wDpM9f0LV/PT/wT3/5Od0L/AK8NT/8ASZ6/oWoAKKKKACiiigAooooAKKKKACiiigAqvd2lrf2s1jexJPb3EbRSxSKGSSNxtZWU8FSOCOmKsUUAfy+ftEfCS6+CXxd1/wABOr/YYJ/tGlyvk+dp1x89u24hdxVf3bkDHmIwHSvEq/cb/gpH8Gh4r+Hdj8WtHg3al4ScQX5RRuk0u5YDJwNzfZ5irAdFSSRj0r8OaACiiigAooooAKKKKACv22/4JrfGb/hI/BGpfBvWJ91/4YJvtMDEbn0y5f8AeIOMnyLhuST0mRRwtfiTXrvwJ+Kl/wDBf4q+H/iFZ73h0+5C30CdZ7GYeXcR4yAWMZJTPAcK3agD+lv4lePNH+F/gLXfH+vH/QtDspLpk3BTK6jEUKk8b5ZCsaf7TCv5Z/FfibV/GnibVfF2vy+fqWs3k19dOBgGWdy7bR/CoJwqjgDAHAr9Tf8Ago/8eNP1jSvDXwn8H36XVlqMEHiLUZ7d90csEq5sI8qcFWBMxUj/AJ4sK/JKgAooooAKKKKACiiigCSGGWeVIIEaSSRgiIgyzMeAAB1J6ACv6cP2Zvg/D8EPg3oXgl41XVDH9u1h1wd+o3IDSjK8MIgFhVu6RrX48/8ABP34Nf8ACyvjPH4u1SHfovgcR6lJkfLJqDEiyj6gja6tNnBH7kKRhq/oCoAKKKKACiiigAooooAKKKKACiiigAooooA+EP8Agoz/AMm23P8A2GdO/m9fz/V/QD/wUZ/5Ntuf+wzp383r+f6gAooooAKKKKACiiigAooooA/cD/gl9/ySHxV/2Mjf+klvX6YV+Z//AAS+/wCSQ+Kv+xkb/wBJLev0woAKKKKAP//V/fyiiigAooooAhuLeG6gktbhBJFMjRujDhlYYIPsRXw14q0Cbwzr11o8uSsL5ic/xxNyjdAOnXHAII7V9114p8ZvDH9oaTF4itUzPp/yTYHLW7H6fwNz6AFjX5P4u8Mf2llH1yiv3lDX1j9pfLf5eZ9jwXmv1XGewm/dnp8+n+R8vUUUV/I5+zhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXqPhX/kDp/vv/ADry6vUfCv8AyB0/33/nX6/4J/8AJQS/69y/OJ8Vx5/yLV/iX5M+mfCn/IvWf+63/oRroq53wp/yL1n/ALrf+hGuir+sT8cCiiigAooooAK8U/aD8Q/8I98KtX2SeXNqQTTouPveef3i/wDfkPXtdfEn7XniH95oHhSJ+gl1CePtz+6hP6SivquCcu+uZ3h6TWifM/SOv6WPneK8b9VymtUW9rL56HxVRRRX9bH82hRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB0XhHSv7b8TabpZXck1wnmD/pmvzP/AOOg199wQvcTR28Q+eVlRR7scCvk74G6V9q8R3WqMMpY2+1faSY4H/joavtTwdZ/a9dhJAKwBpiD/s8L+TEV/OXizmPts1hhVtTj+Mtfy5T9x8N8F7LLpYh/bf4LT87ntcEMdvDHbxDCRKEUegUYFS0UV+WH6GFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH5Af8FU/+Pn4Z/wC5rn87CvyOr9cf+Cqf/Hz8M/8Ac1z+dhX5HUAFFFFABRRRQAUUUUAFFFFAH9Mn7I3/ACbX8Pv+wPH/AOhNX0ZXzl+yN/ybX8Pv+wPH/wChtX0bQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVBc/8AHtL/ALjfyqeoLn/j2l/3G/lQB/ItRRRQAUUUUAFFFFABRRRQAV92f8E6P+TlLP8A7A+o/wDoC18J191/8E6f+TlLL/sD6j/6AtAH9AtFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5vf8FPP+SG+Hf8Asa7b/wBIr2v0hr83v+Cnn/JDfDv/AGNdt/6RXtAH4YUUUUAFFFFABRRRQAUUUUAfbX/BPf8A5Od0L/rw1P8A9Jnr+hav56f+Ce//ACc7oX/Xhqf/AKTPX9C1ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAGXrei6Z4j0W/wDD2tW63WnanazWd3A/3ZYJ0MciHGOGUkcV/Lf8Y/hrqXwg+JviD4daoS76PdtHDMwA8+1cCS3mwCQPMhZGIz8pO3qK/qkr8qv+CmPwb/tTw9o/xs0eHM+jFNJ1gqOtnO5NtKecARTsYzxk+cvZaAPxkooooAKKKKACiiigAooooAlmnnuGElxI0rBEjBcliEjUIi89lUBQOgAAHFRUUUAFFFFABRRRQAUUV9jfsO/Bj/hbnxusLrU7fzdA8J7NY1HcuY5JIm/0W3PBU+ZMAxRuGijkFAH7Gfsh/Bv/AIUr8EtH0K/h8nXNVH9r6wGGHW7ulXEJB6GCIJEQONysR1r6eoooAKKKKACiiigAooooAKKKKACiiigAooooA+EP+CjP/Jttz/2GdO/m9fz/AFf0A/8ABRn/AJNtuf8AsM6d/N6/n+oAKKKKACiiigAooooAKKKKAP2//wCCX3/JIvFX/Yxn/wBJLev0xr8zv+CX3/JIvFX/AGMZ/wDSS3r9MaACiiigD//W/fyiiigAooooAKhuLeG6t5LW4QSRTI0bo3RkYYIPsRU1FKUVJcrWg07ao+FPFWgTeGdeutHlyVhfMTn+OJuUboB0644BBHauer6h+M/hj+0NJi8RWqZn0/5JsDlrdj9P4G59ACxr5er+IeO+GnkmcVMJFfu370P8L2+74fkfvfD2aLH4KNZ/EtH6r+rhRRRXxx7gUUUUAFFFFABRRRQAUUUUAFFFFABXqPhX/kDp/vv/ADry6vUfCv8AyB0/33/nX6/4J/8AJQS/69y/OJ8Vx5/yLV/iX5M+mfCn/IvWf+63/oRroq53wp/yL1n/ALrf+hGuir+sT8cCiiigAooooAK/LT4/eIP+Eh+Kmsujl4dPddPiB/h+zDbIB7ebvP41+m+t6rb6Fot/rd1nydPtZrqQDrshQucfgK/Gu8u7i/u5767cyT3EjSyOerO5yx/Emv2Twfy7mxNfGtfClFfPV/dZfefl/ibjeXD0cIurv92i/P8AAr0UUV++H42FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFKqliFUZJ4AFAH1t8E9K+xeEmv2GH1C4dwf+mcfyKPzDfnX1l8PbTEN3fED5mWFfUbRk/gcj8q8V8PaYui6FYaUP+XW3jjb3YKNx/E19JeF7T7HoVpHxl0808Y/1nzD8gQPwr+N+IMw+vZlXxfSUnb02X4WP6hyXBfU8BSw38sV9/X8Tfooorxz0wooooAKKKKACiiigAooooAKKKKACiiigAooooA/ID/gqn/x8fDP/c13+dhX5HV+uP8AwVT/AOPj4Z/7mu/zsK/I6gAooooAKKKKACiiigAooooA/pi/ZE/5Nq+H3/YIT/0N6+jq+cP2Q/8Ak2r4ff8AYJX/ANGPX0fQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVBc/wDHvL/uN/Kp6guf+PaX/cb+VAH8i1FFFABRRRQAUUUUAFFFFABX3V/wTq/5OUsf+wRqP/oC18K190/8E6/+TlbD/sE6j/6LFAH9A9FFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5vf8FPP+SG+Hf+xstv8A0hva/SGvze/4Kef8kN8O/wDY123/AKQ3tAH4YUUUUAFFFFABRRRQAUUUUAfbX/BPf/k53Qv+vDU//SZ6/oWr+en/AIJ7/wDJzuhf9eGp/wDpM9f0LUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFcz4z8JaN488J6v4L8QxebputWc1lcqMbhHMpXchIO11+8hx8rAEdK6aigD+UL4h+Bta+GnjjW/AXiFNt/od5JaSHaVWQIfklQMAfLlTa6HurA1xlfrd/wUy+DQhudF+OGi2+Fn2aNrexf41Ba0nbC91DQszHjbEor8kaACiiigAooooAKKKKACiiigAooooAKKKKACv6K/2HvgyPhF8EdPudSt/K17xXs1jUdy4kjSVR9lgPyhh5UOCUb7sjyCvxz/ZC+DX/AAur426Pod/B5uh6Sf7X1gMAUa1tWXEJBxkTylIiBzsZmH3a/pRoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPhD/goz/ybbc/9hnTv5vX8/wBX9AP/AAUZ/wCTbbn/ALDOnfzev5/qACiiigAooooAKKKKACiiigD9v/8Agl9/ySLxV/2MZ/8ASS3r9Ma/M3/gl9/ySPxX/wBjGf8A0kgr9MqACiiigD//1/38ooooAKKKKACiiigCG4t4bu3ktblBJDMjRujdGVhgg+xFfDXinQJvDOvXejy5KwvmJz/HE3KN0A6dccA5HavuuvFPjP4Y/tDSYvEVqmZ9P+SbA5a3Y/T+BufQAsa/J/F3hj+0so+u0V+8oa+sftL5b/LzPseC81+q4z2E37s9Pn0/yPl6iiiv5HP2cKKKKACiiigAooooAKKKKACiiigAr1Hwr/yB0/33/nXl1eo+Ff8AkDp/vv8Azr9f8E/+Sgl/17l+cT4rjz/kWr/EvyZ9M+FP+Res/wDdb/0I10Vc74U/5F6z/wB1v/QjXRV/WJ+OBRRRQAUUUUAeB/tJ6/8A2H8LL23RmWXVp4bBCvGAx8x8+xjjZfxr8y6+xP2utf8AN1nQvDEZIFrbSXsoB+UmdvLTj1URN+DV8d1/T3hll/1bI4Ta1qNy/RfgkfgHH2N9vm8oLaCUf1f4u3yCiiiv0I+KCiiigAooooAKKKKACiiigAooooAKKKKACuz+Hulf2x4y0u0IyizCZ/TbCN+D7Hbj8a4yvffgPpXmalqOsuvEES26H/alO5sfQIPzr5zi7MfqOT4jELflsvV+6vzPd4awP1vM6NDpe79Fq/wR9S2Nqb29gtF486RUyOwY4z+FfRoAUBVGAOAK8a8DWn2jWxOc7baNn9sn5QPyJ/KvZq/kI/pcKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD8gP8Agqn/AMfPwz/3Nd/nYV+R1frj/wAFU/8Aj4+Gf+5rv87CvyOoAKKKKACiiigAooooAKKKKAP6YP2Qv+Tafh//ANglf/Rj19IV82/sgf8AJtHw/wD+wUP/AEY9fSVABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUFz/wAe0v8AuN/Kp6guf+PeX/cb+VAH8i1FFFABRRRQAUUUUAFFFFABX3R/wTr/AOTltP8A+wTqP/osV8L19z/8E7f+TltO/wCwVqX/AKKFAH9BNFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5vf8ABTz/AJIb4d/7Gu2/9Ir2v0hr83v+Cnn/ACQ3w7/2Nlt/6Q3tAH4YUUUUAFFFFABRRRQAUUUUAfbX/BPf/k53Qv8Arw1P/wBJnr+hav56f+Ce/wDyc7oX/Xhqf/pM9f0LUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAHA/FH4faP8Vfh7r3w91wAWmt2b23mbdxhl4aGZRxloZVWRR0yor+WrxP4b1bwd4j1Twnr8P2fUtHu5rG7izkLNbuUcAjgjI4I4I5HFf1p1+KX/BSz4N/2F4x0r4z6RDiz8RqunaqVHCahbR/uXPP/AC2t02gAADyCTy1AH5eUUUUAFFFFABRRRQAUUUUAFFFFABRRXvv7Mvwfm+N/xk0LwVJGzaWsn2/WHXI2adakNKMryvmnbCrDo8i0AfsP/wAE/vg1/wAK0+C8XizU4fL1rxwY9TlyPmj09QRZR9SMFGaboD+92n7tfdlRxRRQRJBAixxxqEREAVVVRgAAcAAdBUlABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB8If8FGf+Tbbn/sM6d/N6/n+r+gH/goz/ybbc/9hnTv5vX8/wBQAUUUUAFFFFABRRRQAUUUUAft9/wS+/5JJ4r/AOxjP/pJBX6ZV+Zn/BL7/kkniv8A7GP/ANtIK/TOgAooooA//9D9/KKKKACiiigAooooAKhuLeG7t5LW5QSQzI0bo3RlYYIPsRU1FKUVJcsloNO2qPhTxToE3hnXbrR5clYXzE5/jiblG6AdOuOAcjtXPV9Q/Gfwx/aGkxeIrVMz6f8AJNgctbsfp/A3PoAWNfL1fxDx3w08kziphIr92/eh/he33fD8j974ezRY/BRrP4lo/Vf1cKKKK+OPcCiiigAooooAKKKKACiiigAr1Hwr/wAgdP8Aff8AnXl1eo+Ff+QOn++/86/X/BP/AJKCX/XuX5xPiuPP+Rav8S/Jn0z4U/5F6z/3W/8AQjXRVzvhT/kXrP8A3W/9CNdFX9Yn44FFFFABRRXP+K9bTw14Z1XxA4DDTrOe5Ck4DNEhZV/4EQAK0o0pVJxpwWr0RFSpGnBzlsj8w/jZr3/CR/FHxBer/q4Lo2cYzkbbQCHI9mKFvxryunO7yO0khLMxySepJptf2jgMJHC4anhYbQio/crH8r4zEyxFedeW8m394UUUV1nMFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFfY/wf0r+zfBVvMy4kvpHuW+h+Rf/HVB/Gvj62t5bu4itYBmSZ1jQerMcAV+gum2UWmafbadB/q7WGOFfpGoUfyr8g8Xsx9ngaOCj9t3+UV/m19x+meGeB58XVxT2irL5/8AAR7P4As/K0+e8YEGeQKPTbGO34kj8K76srQ7P7BpFpakbWSMFh6O3zN+prVr+fj9oCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/IH/AIKp/wDHx8Mv9zXf52FfkbX65f8ABVP/AI+Phl/ua7/Owr8jaACiiigAooooAKKKKACiiigD+l79kD/k2j4f/wDYKH/o16+kq+bP2Pv+TZ/h/wD9gv8A9qyV9J0AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFQ3H/HvJ/uN/KpqhuP8Aj3l/3G/lQB/IrRRRQAUUUUAFFFFABRRRQAV9zf8ABO3/AJOX07/sFal/6Kr4Zr7l/wCCd3/Jy+mf9gvUv/RVAH9BdFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5v/8ABTz/AJIZ4d/7Gy2/9Ib2v0gr83/+Cnn/ACQzw7/2Nlt/6Q3tAH4X0UUUAFFFFABRRRQAUUUUAfbX/BPf/k53Qv8Arw1P/wBJnr+hav56f+Ce/wDyc7oX/Xhqf/pM9f0LUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXkvxz+Fth8ZvhV4g+Hl5sSTUrUmymfpBew4ktpOBkKsqruxyU3L3r1qigD+R3VNM1DRdSu9G1a3e0vrCeS1ubeUbXimhYo6MOzKwII9qo1+i3/BRn4M/8IT8Ubb4m6Pb7NJ8ZoWudi4SLVLcAS/dUBfPj2yDJLO/mntX500AFFFFABRRRQAUUUUAFFFFABX7u/8ABOj4Mf8ACD/C2f4l6xb7NX8aMr2+9cPFpVuSIQMqCvnvulODtePyj2r8gPgP8Kr740fFbw/8PbTekF/ch7+ZOsFhD+8uJAcEBhGpCZ4LlV71/ULpunWGj6da6RpUEdrZWMMdtbQRLtjihiUIiKBwFVQAB2AoAu0UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAHwh/wUZ/5Ntuf+wzp383r+f6v6Af+CjP/Jttz/2GdO/m9fz/AFABRRRQAUUUUAFFFFABRRRQB+3v/BL3/kkviv8A7GL/ANtIK/TOvzL/AOCXv/JJfFn/AGMX/tpBX6aUAFFFFAH/0f38ooooAKKKKACiiigAooooAhuLeG7t5LW5QSQzI0bo3RlYYIPsRXwz4p0Cbwzrt1o8uSsL5ic/xxNyjdAOnXHAOR2r7srxT4z+GP7Q0mLxFapmfT/kmwOWt2P0/gbn0ALGvyfxd4Y/tLKPrtFfvKGvrH7S+W/y8z7HgvNfquM9hN+7PT59P8j5eooor+Rz9nCiiigAooooAKKKKACiiigAr1Hwr/yB0/33/nXl1eo+Ff8AkDp/vv8Azr9f8E/+Sgl/17l+cT4rjz/kWr/EvyZ9M+FP+Res/wDdb/0I10Vc74U/5F6z/wB1v/QjXRV/WJ+OBRRRQAV84/tQ6/8A2T8M20tCPM1i7htsZwwjjPnMw9sxqp/3q+jq+Af2tdf+2eLNJ8ORspTTLNp3x1Et03Kn6JEhH+9X2fh/l31vPaEWtI+9/wCA6r8bHy3GeN+rZRVa3kuVfPT8rnybRRRX9Wn86BRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB6J8K9K/tXxtYBlzHaFrp/byh8v/j+2vufRbP7fqtraEbleRdw/wBheW/8dBr5i+AulYTVNbcdSlrGfp87j/0CvsXwDZ+bqc14wBW3jwPZpOBj/gINfzL4o5j9Yzt0VtTio/q/zt8j988PsD7DKVUe8238tl+R67RRRX5yfcBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAfkD/wAFU/8Aj4+Gf+5rv87CvyNr9cv+Cqf/AB8fDL/c13+dhX5G0AFFFFABRRRQAUUUUAFFFFAH9Ln7Hv8AybP8P/8AsF/+1ZK+lK+af2O/+TZvAH/YMP8A6Okr6WoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKhuP+PeX/cb+VTVDcf8AHvJ/uN/KgD+RWiiigAooooAKKKKACiiigAr7k/4J3/8AJzGl/wDYL1L/ANE18N19x/8ABPD/AJOZ0r/sGal/6JoA/oNooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAK/N//gp5/wAkM8O/9jZbf+kN7X6QV+b/APwU8/5IZ4d/7Gy2/wDSG9oA/C+iiigAooooAKKKKACiiigD7a/4J7/8nO6F/wBeGp/+kz1/QtX89P8AwT3/AOTndC/68NT/APSZ6/oWoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigDwL9pr4QQ/G74N674KjjVtUWP7do7tgbNRtQWhAJ4USjdCzdkkav5kJYpIJHhmRo5I2KsjDaysvBBB6Eelf12V/Pz+3/APBv/hWnxpl8V6XD5ei+OBJqcWB8sd+pAvY+pPLss3QD99tH3aAPhWiiigAooooAKKKKACiivRPhN8OtV+LPxG0D4d6NlbjWrxIGlA3eRAvzzzEZGRDCrORnnbgUAfrj/wAE1fg0PDvgjU/jJq8G2/8AEzNYaYWAymm2z/vGHcefcLggjpChHDV+ndY3h3QNK8KaBpvhjQoBbabpNpBY2kIJPlwW6CONcnk4VRyeTWzQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAHwh/wUZ/5Ntuf+wzp383r+f6v6Af+CjP/ACbbc/8AYZ07+b1/P9QAUUUUAFFFFABRRRQAUUUUAft5/wAEvf8Akk3iz/sYv/bSCv00r8y/+CXv/JJvFn/Yxf8AtpBX6aUAFFFFAH//0v38ooooAKKKKACiiigAooooAKhuLeG7t5LW5QSQzI0bo3RlYYIPsRU1FKUVJcsloNNp3R8J+KdAm8M67daPLkrC+YnP8cTco3QDp1xwDkdq5+vqH4z+GP7Q0mLxFapmfT/kmwOWt2P0/gbn0ALGvl6v4h474aeSZxUwkV+7fvQ/wvb7vh+R++cPZosfgoVn8S0fqv6uFFFFfHHthRRRQAUUUUAFFFFABXqPhX/kDp/vv/OvLq9R8K/8gdP99/51+v8Agn/yUEv+vcvzifFcef8AItX+Jfkz6Z8Kf8i9Z/7rf+hGuirnfCn/ACL1n/ut/wChGuir+sT8cCiiigAr8kfiv4g/4Sf4ja/rCsrxSXrxQsv3Wht8QxkfVEBr9P8Ax94gHhXwVrXiAOEksrKV4S3Tzyu2IfjIVFfj9X7b4PZd72IxzXaC/N/+2n5P4nY33aODXnJ/kv1Ciiiv3M/IwooooAKKKKACiiigAooooAKKKKACiiigAooq/pVhJqmp2mmRffupo4R7b2C/pUVKkacHOWiRcIOclCO7Psn4X6V/ZPgnTkYYe5Q3T+/nHcv/AI5tFfUfgS0EGjG5IG65kZs99qfKB+BBrxeCGO3hjt4V2pGqoijsFGAPyr6Q061FjYW9nx+5jVDjjJA5P4mv4wzPGyxmLq4qW82397P6mwGFjhsNTw8dopL7kXaKKK4TrCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/IH/AIKp/wDHx8Mv9zXf52FfkbX65f8ABVP/AF/wy/3Nd/nYV+RtABRRRQAUUUUAFFFFABRRRQB/Sz+x1/ybN4A/7Bjf+jpK+l6+Z/2Of+TZfAH/AGDW/wDR8tfTFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUNx/x7yf7jfyqaoZ/9RJ/uH+VAH8itFFFABRRRQAUUUUAFFFFABX3D/wTx/5OZ0n/ALBmpf8Aog18PV9wf8E8v+TmtI/7Bupf+iDQB/QhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAV+b/wDwU8/5IZ4d/wCxstv/AEhva/SCvzf/AOCnn/JC/D3/AGNlr/6Q3tAH4X0UUUAFFFFABRRRQAUUUUAfbX/BPf8A5Od0L/rw1P8A9Jnr+hav56f+Ce//ACc7oX/Xhqf/AKTPX9C1ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFfLf7YXwb/4XR8EdX0fT4PO1zRh/a+jhRl3ubVW3QqB1M8ReNR03lSfu19SUUAfyH0V9ffttfBn/AIU/8b9ROmW/k6B4n3axpm1cRx+cx+0W64VVHlTZ2ov3Ymj9a+QaACiiigAooooAK/Y//gmb8GjZaTrPxv1mDEuob9H0XeMf6PEwN1MvYh5VWJSOR5cg6Gvye8A+Cta+I3jTRfAvh2PfqGt3kVnDkEqnmH5pH2gkRxrl3OOFUntX9S/gbwbovw98HaN4H8Ox+Vp2iWcNlACAGZYlALvtABdzlnOOWJNAHV0UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB8If8FGf+Tbbn/sM6d/N6/n+r+gH/goz/ybbc/9hnTv5vX8/wBQAUUUUAFFFFABRRRQAUUUUAft3/wS9/5JP4s/7GL/ANtYa/TWvzK/4Je/8kn8Wf8AYw/+2sNfprQAUUUUAf/T/fyiiigAooooAKKKKACiiigAooooAhuLeG7t5LW5QSQzI0bo3RlYYIPsRXwz4p0Cbwzrt1o8uSsL5ic/xxNyjdAOnXHAOR2r7srxT4z+GP7Q0mLxFapmfT/kmwOWt2P0/gbn0ALGvyfxd4Y/tLKPrtFfvKGvrH7S+W/y8z7HgvNvquM9hP4Z6fPp/kfL1FFFfyOfs4UUUUAFFFFABRRRQAV6j4V/5A6f77/zry6vUfCv/IHT/ff+dfr/AIJ/8lBL/r3L84nxXHn/ACLV/iX5M+mfCn/IvWf+63/oRroq53wp/wAi9Z/7rf8AoRroq/rE/HAooooA+YP2rPEP9m+ALbQo3Cy6xeoGT+9BbDzG/KTyq/O6vqH9q3xD/aXjy00GKTdFo9ku9P7s9yfMb84xFXy9X9UeHWXfVMio3Ws7y+/b/wAlSP5444xv1jN6ltoWivlv+Nwooor7g+SCiiigAooooAKKKKACiiigAooooAKKKKACvVPg5pX9o+M4bhlzHYRSXB9N2Ni/q2R9K8rr6f8AgRpXk6TqGsOuDczLAn+5CMnHsS2Pwr47j3MfqeRV5reS5V/29p+Vz6fg3BfWc3oxe0fe/wDAdV+Nj6V8N2n2zXLSHssgkPGeI/m/XGK98ryv4e2m65ur45/dosS8cfOcn8to/OvVK/lA/o0KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/IH/gqn/r/hl/ua7/ADsK/I2v1y/4Kp/6/wCGX+5rv87CvyNoAKKKKACiiigAooooAKKKKAP6V/2OP+TZfAH/AGDX/wDR8tfTNfMv7G3/ACbJ4A/7Bz/+lEtfTVABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUM/+ok/3D/Kpqhn/ANRJ/uH+VAH8itFFFABRRRQAUUUUAFFFFABX3B/wTy/5Oa0f/sG6l/6INfD9fb3/AATz/wCTm9G/7B2p/wDpO1AH9CVFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5v/APBTz/khfh7/ALGy1/8ASG9r9IK/N/8A4Kef8kL8Pf8AY2Wv/pDe0AfhfRRRQAUUUUAFFFFABRRRQB9tf8E9/wDk53Qv+vDU/wD0mev6Fq/np/4J7/8AJzuhf9eGp/8ApM9f0LUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAfN37S37N+gftI+FNO0HUtQbRr7Sbz7VZ6jHbrcPGjrsmh2F4/klG0nDD5kU9sV8T/wDDq7S/+ikT/wDgmX/5Mr9aaKAPyW/4dXaX/wBFIn/8Ey//ACZR/wAOrtL/AOikT/8AgmX/AOTK/WmigD8lv+HV2l/9FIn/APBMv/yZR/w6u0v/AKKRP/4Jl/8Akyv1pooA+Fv2cf2HPDfwA8cy+PpvEMniW/WzktLJZbFbVbRpsCSVcSykuYwYxjbhWYc54+6aKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPhD/goz/ybbc/9hnTv5vX8/1f0A/8FGf+Tbbn/sM6d/N6/n+oAKKKKACiiigAooooAKKKKAP26/4Je/8AJKPFv/Ywj/0lhr9Nq/Mn/gl7/wAkp8W/9jCP/SWGv02oAKKKKAP/1P38ooooAKKKKACiiigAooooAKKKKACobi3hu7eS1uUEkMyNG6N0ZWGCD7EVNRSlFSXLJaDTtqj4U8U6BN4Z1260eXJWF8xOf44m5RugHTrjgHI7Vz1fUPxn8Mf2hpMXiK1TM+n/ACTYHLW7H6fwNz6AFjXy9X8Q8d8NPJM4qYSK/dv3of4Xt93w/I/e+Hs0WPwUaz+JaP1X9XCiiivjj3AooooAKKKKACvUfCv/ACB0/wB9/wCdeXV6j4V/5A6f77/zr9f8E/8AkoJf9e5fnE+K48/5Fq/xL8mfTPhT/kXrP/db/wBCNdFXO+FP+Res/wDdb/0I10Vf1ifjgUUV5/8AFTxB/wAIt8O9f1pXMckVk8cLL1Waf9zER9Hda6MHhp4ivDD095NJfPQxxNeNCjKtPaKb+SR+YHxB8Q/8JV431vxArmSO8vZWhY9fIU7YR+EYUfhXHUUV/aOGw8KFKNCn8MUkvRKyP5Wr1pVakqs927hRRRW5kFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFfdPgLSv7G8IaXYldr+Qssg/wBuX5yPwLY/CvjLw1pZ1rxBp+lYytzcRo/+5n5vyXNff8MTSukEK5ZyEVR6ngCvxPxhzG0MPgI+cn8tF+p+r+GOB96ti30tFfm/0PafBdn9l0KJyCGuGaUg+/yjHttUGusqC2gS1torWP7kKLGv0UYFT1+Fn66FFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAfkF/wVT/1/wAMv9zXf52FfkZX65/8FU/9d8Mv93Xf52FfkZQAUUUUAFFFFABRRRQAUUUUAf0q/sa/8mx+Af8AsHyf+lEtfTdfMf7Gn/JsfgH/ALB8n/pRLX05QAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAVFP/qJP9w/yqWop/8AUSf7p/lQB/IpRRRQAUUUUAFFFFABRRRQAV9u/wDBPT/k5zRf+wfqf/pO1fEVfbn/AAT1/wCTnNF/7B+p/wDpO1AH9ClFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5wf8FPP+SF+Hv8AsbLX/wBIb6v0fr84P+Cnn/JC/D3/AGNlr/6Q31AH4XUUUUAFFFFABRRRQAUUUUAfbX/BPf8A5Od0L/rw1P8A9Jnr+hav56f+Ce//ACc7oX/Xhqf/AKTPX9C1ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB8If8ABRn/AJNtuf8AsM6d/N6/n+r+gH/goz/ybbc/9hnTv5vX8/1ABRRRQAUUUUAFFFFABRRRQB+3X/BLz/klPi3/ALGEf+ksNfptX5kf8EvP+SVeLf8AsYB/6Sw1+m9ABRRRQB//1f38ooooAKKKKACiiigAooooAKKKKACiiigCG4t4bu3ktblBJDMjRujdGVhgg+xFfDXinQJvDOu3Wjy5KwvmJz/HE3KN0A6dccA5HavuuvFPjP4Y/tDSYvEVqmZ9P+SbA5a3Y/T+BufQAsa/J/F3hj+0so+uUV+8oa+sftL5b/LzPseC81+q4z2E37s9Pn0/yPl6iiiv5HP2cKKKKACiiigAr1Hwr/yB0/33/nXl1eo+Ff8AkDp/vv8Azr9f8E/+Sgl/17l+cT4rjz/kWr/EvyZ9M+FP+Res/wDdb/0I10Vc74U/5F6z/wB1v/QjXRV/WJ+OBXyf+1p4g+xeENK8Oxsyvql4Znx0MNovKn/gciEf7tfWFfm/+1Hr/wDavxK/spCfL0azhtyufl82UeczD/gLop/3a+88Nsu+tZ7SbWlNOX3aL8Wj4/jrG/V8omlvO0V+v4JnzfRRRX9SH89hRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB7L8EdK+2eKpdSYfJp9uxB9JJfkH/ju6vtjwjZ/bNdtwVykOZm9tn3f/AB7FfOPwP0r7J4Yn1Nhhr+4O0+scPyj/AMe3V9bfD2z+W7v2A/hhQ/T5mH/oNfyx4i5j9bz2rbaFoL5b/jc/ofgfA/Vsop33l7337fhY9Looor4Y+tCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPyC/4Kp/674Zf7uu/zsK/Iyv1z/wCCqf8Arvhl/u67/Owr8jKACiiigAooooAKKKKACiiigD+lP9jI5/Zi8A/9eEv/AKUzV9O18wfsYf8AJsPgH/rwm/8ASmavp+gAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAqKf/AFEn+6f5VLUU/wDqJP8AdP8AKgD+RSiiigAooooAKKKKACiiigAr7b/4J7HH7Tuh/wDXhqf/AKTPXxJX2z/wT3/5Oe0L/rw1P/0legD+heiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAr84P+Cnn/ACQvw9/2Nlr/AOkN9X6P1+cH/BTz/khfh7/sbLX/ANIb6gD8LqKKKACiiigAooooAKKKKAPtr/gnv/yc7oX/AF4an/6TPX9C1fz0/wDBPf8A5Od0L/rw1P8A9Jnr+hagAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA+EP+CjP/Jttz/2GdO/m9fz/V/QD/wUZ/5Ntuf+wzp383r+f6gAooooAKKKKACiiigAooooA/bn/gl5/wAkq8Xf9jAP/SWGv03r8x/+CXn/ACSvxd/2MC/+ksVfpxQAUUUUAf/W/fyiiigAooooAKKKKACiiigAooooAKKKKACobi3hureS1uUEkUyNG6MOGVhgg+xFTUUpRUlyyWg07ao+FPFWgTeGdeutHlyVhfMTn+OJuUboB0644ByO1c9X1D8ZvDH9oaTF4htUzPp/yTYHLW7H2H8Dc+gBY18vV/EPHfDTyTOKmEiv3b96H+F7fd8PyP3vh7NFj8FGs/iWj9V/Vwooor449wKKKKACvUfCv/IHT/ff+deXV6j4V/5A6f77/wA6/X/BP/koJf8AXuX5xPiuPP8AkWr/ABL8mfTPhT/kXrP/AHW/9CNdFXO+FP8AkXrP/db/ANCNdFX9Yn44NZlRS7kKqjJJ6ACvxy8W643ibxRq3iBsj+0Lye4VWOSqSOSq/wDAVwB9K/T740a9/wAI58L/ABBfr/rJLQ2keDg77siEEe6h934V+UFfu3g9l1qWIxzW7UV8tX+a+4/IPE7G3qUcIuicv0X5MKKKK/aj8qCiiigAooooAKKKKACiiigAooooAKKKKACiius8DaV/bPi3S7AjKGdZJB/0zi+dh+IXFc2MxMMNh54ip8MU38krm+Fw8q9aFCG8mkvnofZvhbSv7E8OadpeMNb28auP+mhGX/8AHia+lPCdn9j0G2UgBpV84477+R/47gV4laW7Xd1DaocGaRYwfTcQK+jkRY0WNBtVQAAOgA6V/F2IryrVZVqm8nf7z+qKFGNKnGlDaKSXoh1FFFYmoUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB+QX/BVP/XfDL/d13+dhX5GV+uf/AAVT/wBd8Mv93Xv/AGwr8jKACiiigAooooAKKKKACiiigD+lD9jD/k2HwD/14z/+lU1fUFfL37F3/JsHgL/rxn/9Kpq+oaACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACop/9RJ/un+VS1FN/qX/3T/KgD+RSiiigAooooAKKKKACiiigAr7Z/wCCfH/Jz2g/9eOp/wDpK9fE1fa//BPn/k5/QP8Arx1P/wBJZKAP6GaKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACvzg/4Kef8kL8Pf8AY2Wv/pDfV+j9fnB/wU8/5IV4e/7Gy1/9Ib6gD8LqKKKACiiigAooooAKKKKAPtr/AIJ7/wDJzuhf9eGp/wDpM9f0LV/PT/wT3/5Od0L/AK8NT/8ASZ6/oWoAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPhD/goz/wAm23P/AGGdO/m9fz/V/QD/AMFGf+Tbbn/sM6d/N6/n+oAKKKKACiiigAooooAKKKKAP24/4Jef8kr8Xf8AYwL/AOksVfpxX5jf8EvP+SWeLv8AsYF/9JYq/TmgAooooA//1/38ooooAKKKKACiiigAooooAKKKKACiiigAooooAhuLeG6gktbhBJFMjRujDhlYYIPsRXw14q0Cbwzr11o8uSsT5ic/xxNyjdAOnBxwCCO1fddeK/Gbwx/aGkR+IbVMz6f8k2By1ux9hn5G59ACxr8n8XeGP7Syj65RX7yhr6x+0vlv8vM+x4LzX6rjPYTfuz0+fT/I+XaKKK/kc/ZwooooAK9R8K/8gdP99/515dXqPhX/AJA6f77/AM6/X/BP/koJf9e5fnE+K48/5Fq/xL8mfTPhT/kXrP8A3W/9CNdFXO+FP+Res/8Adb/0I10Vf1ifjh8fftda/wCRoeheGYyM3dzJeyYPIW3TYoI9GMp/75r4Qr379pbX/wC2/ild2qENFpFvBYoVPBIHmv8AiHkKn/drwGv6w4Dy/wCp5Hh4NayXM/8At7VfhZH848YY36zm9aS2T5V/27p+YUUUV9gfMhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABXu3wJ0rz9av9XcfLaQLCn+/Me30VCPxrwmvsD4NaV/Z/g2O6YYe/mkn99o/dr+Hy5H1r4HxKzH6rkVSK3m1Ffm/wAEz7LgPA/WM3hJ7QTl92i/Fo+hvBNp9o11JD0t0aTp/wABH/oWfwr2qvPfh9abLK5vT/y1kEY47RjPH/fX6V6FX8un9BBRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB+QX/AAVT/wBd8Mv93Xv/AGwr8jK/XP8A4Kp/674Zf7uvf+2FfkZQAUUUUAFFFFABRRRQAUUUUAf0nfsW/wDJr/gL/ryuP/SuevqKvlv9iv8A5Nf8Bf8AXnc/+lc9fUlABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABUU3+pf/AHT/ACqWopv9S/8Aun+VAH8ilFFFABRRRQAUUUUAFFFFABX2t/wT6/5Of8P/APXlqf8A6SSV8U19q/8ABPv/AJOg8O/9eeqf+kktAH9DdFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABX5wf8ABTz/AJIV4e/7G21/9Ib6v0fr84P+Cnn/ACQrw9/2Ntr/AOkN9QB+F1FFFABRRRQAUUUUAFFFFAH21/wT3/5Od0L/AK8NT/8ASZ6/oWr+en/gnv8A8nO6F/14an/6TPX9C1ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB8If8FGf+Tbbn/sM6d/N6/n+r+gH/goz/wAm23P/AGGdO/m9fz/UAFFFFABRRRQAUUUUAFFFFAH7b/8ABLz/AJJZ4u/7D6/+ksVfpzX5jf8ABLz/AJJZ4u/7D6/+ksVfpzQAUUUUAf/Q/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAqGeCG6gktbhBJFKhjdGGVZWGCCPQipqKUoqS5WtBp21R8K+KtAm8M69daPLkrE+YXP8AHE3KN0A6cHHAII7VztfUfxm8Mf2jpEfiC1TNxp3yy4HLW7H2Gfkbn0ALGvlyv4i484aeSZxUwkV+7fvQ/wAL2Xy2+R+98PZosfgo1n8S0fqv89wooor409wK9R8K/wDIHT/ff+deXV6j4V/5A6f77/zr9f8ABP8A5KCX/XuX5xPiuPP+Rav8S/Jn0z4U/wCRes/91v8A0I1tXVzBZWs15dOI4beNpZHPAVEGST7ACsXwp/yL1n/ut/6Ea8/+PfiD/hHvhXrcqMqzX0S6fED/ABfaiEcD3EW8j6V/X+WYKWLxdLCx+20vvdj8Rx+KjhsNUxEtopv7kfmNrurT69reoa5cgCXULqa6cDoGmcuQPYZrKoor+z6dOMIqEFZLRH8sTm5ycpbsKKKKskKKKKACiiigAooooAKKKKACiiigAooooAfFE80qQxLud2CqB3J4Ar9A9H0+PSdKs9Li+7aQRwj32KBn8cV8a/DPSv7W8a6bEwzHBJ9pf2EI3D/x4AV916Xafb9RtrPBIlkVWx/d/i/IV+DeMGY81ehgY/ZTk/novyf3n7F4Y4Hlo1sW+rUV8tX+a+49v8PWn2HRbS3IIPlh2B6hn+Yj8M4raoor8YP1IKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPyC/4Kp/674Zf7uvf+2FfkZX65/wDBVP8A13wy/wB3Xv8A2wr8jKACiiigAooooAKKKKACiiigD+kv9ir/AJNe8Bf9edz/AOlk9fUtfnv+yZ+0H8EfCH7PPg3w34n8a6PpmqWNtcJcWlzcrHLEzXUzAMp6ZUgj2NfRf/DU37On/RRPD/8A4GJQB77RXgf/AA1L+zp/0UTw/wD+BqUv/DUv7On/AEUTw/8A+BsdAHvdFeC/8NR/s6/9FE8Pf+B0f+NL/wANRfs6/wDRRPD3/gdH/jQB7zRXg3/DUP7O3/RRfDv/AIHxf40v/DUH7O3/AEUXw7/4Hxf40Ae8UV4R/wANP/s7/wDRRfDn/gwi/wAaX/hp79nf/oovhz/wYQ//ABVAHu1FeFf8NO/s8f8ARRfDf/gxh/8AiqX/AIac/Z4/6KL4b/8ABjD/APFUAe6UV4Z/w03+zz/0Ubw3/wCDKD/4qj/hpr9nn/oo3hr/AMGUH/xVAHudFeG/8NM/s9f9FG8Nf+DKD/4ql/4aY/Z6/wCijeGf/BnB/wDF0Ae40V4f/wANL/s9f9FG8M/+DO3/APi6X/hpb9nv/oo3hn/waW//AMXQB7fRXiH/AA0r+z3/ANFG8Mf+DS3/APi6X/hpX9nz/oo/hj/wa23/AMXQB7dRXiX/AA0p+z5/0Ufwx/4Nbb/4ul/4aT/Z8/6KP4X/APBrbf8AxdAHtlFeJ/8ADSf7Pv8A0Ufwv/4Nbb/4uj/hpL9n3/oo/hf/AMG1t/8AHKAPbKK8U/4aR/Z9/wCij+Fv/Bta/wDxyj/hpH9n3/oo/hb/AMG1r/8AHKAPa6K8V/4aP/Z+/wCij+Fv/Bva/wDxyl/4aP8A2fv+ij+Ff/Bva/8AxygD2mivFv8Aho/9n7/oo/hX/wAG9p/8cpf+Gjv2f/8AopHhX/wcWn/xygD2iivF/wDho79n/wD6KR4V/wDBxaf/AByl/wCGjf2f/wDopHhX/wAHFp/8coA9norxj/hoz9n/AP6KR4U/8HFp/wDHaP8Ahoz4Af8ARSPCn/g5tP8A47QB7PRXjP8Aw0X8AP8AopHhT/wc2f8A8do/4aL+AH/RSPCn/g5s/wD47QB7NRXF+EviP8PvHxul8DeJdJ8QGxEf2kaXew3fkCXds3+Uzbd2xtucZwcdK7SgAooooAKKKKACiiigAqKb/Uv/ALp/lUtRTf6l/wDdP8qAP5FKKKKACiiigAooooAKKKKACvtT/gn5/wAnQ+HP+vPVP/SOWviuvq/9ijxh4X8CftDaD4j8Yanb6RpcFtqKSXd0/lxI0lrIqAt0GWIA96AP6QaK8C/4an/Zz/6KJoH/AIGJR/w1N+zp/wBFE8P/APgYlAHvtFeB/wDDUv7On/RRPD//AIGpSj9qT9nX/oonh7/wNj/xoA97orwX/hqP9nX/AKKJ4e/8Do/8aX/hqL9nb/oonh3/AMDov8aAPeaK8H/4ah/Z2/6KL4d/8D4v8aX/AIag/Z3/AOii+Hf/AAPi/wAaAPd6K8J/4ae/Z3/6KL4c/wDBhD/8VS/8NO/s8f8ARRfDn/gwh/8AiqAPdaK8K/4ac/Z4/wCii+G//BjD/wDFUv8Aw03+zz/0UXw3/wCDGD/4qgD3SivDP+Gm/wBnn/oo3hr/AMGUH/xVL/w01+zz/wBFG8Nf+DKD/wCKoA9yorw7/hpn9nr/AKKN4a/8GcH/AMVR/wANMfs9f9FG8M/+DO3/APi6APcaK8P/AOGl/wBnv/oo3hn/AMGdv/8AF0v/AA0t+z3/ANFG8Mf+DS3/APi6APb6K8R/4aV/Z8/6KN4Y/wDBpbf/ABdH/DSn7Pn/AEUfwx/4Nbb/AOLoA9uorxL/AIaT/Z8/6KP4X/8ABrbf/F0v/DSX7Pv/AEUfwv8A+Da2/wDjlAHtlFeJ/wDDSf7Pv/RR/C//AINbb/4uj/hpL9n3/oo/hf8A8G1r/wDHKAPbKK8U/wCGkf2ff+ij+Fv/AAbWv/xylH7SH7P3/RR/C3/g3tf/AI5QB7VRXi3/AA0f+z9/0Ufwt/4N7X/45R/w0f8As/f9FH8K/wDg3tP/AI5QB7TRXi3/AA0d+z//ANFH8K/+Di0/+OUv/DR37P8A/wBFI8K/+Di0/wDjlAHtFFeL/wDDRv7P/wD0Ujwr/wCDi0/+OUv/AA0b+z//ANFI8K/+Di0/+OUAez0V4x/w0Z8AP+ikeFP/AAc2n/x2l/4aM+AH/RSPCn/g5tP/AI7QB7NRXjP/AA0X8AP+ikeFP/BzZ/8Ax2l/4aL+AH/RSPCn/g5s/wD47QB7LRSAhgGUggjgjpiloAKKKKACiiigAooooAK/OD/gp5/yQrw9/wBjba/+kN9X6P1+cH/BTz/khXh7/sbbX/0hvqAPwuooooAKKKKACiiigAooooA+2v8Agnv/AMnO6F/14an/AOkz1/QtX89P/BPf/k53Qv8Arw1P/wBJnr+hagAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA+EP+CjP/ACbbc/8AYZ07+b1/P9X9AP8AwUZ/5Ntuf+wzp383r+f6gAooooAKKKKACiiigAooooA/bb/gl5/yS3xf/wBh9P8A0lir9Oq/MX/gl3/yS7xf/wBh9P8A0lir9OqACiiigD//0f38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAIp4IbmCS2uEEkUqGN0YZVlYYII9CK+G/Fnh+bwxr11pEmSkTZhc/xwtyh6AdODjjII7V9014v8ZfDH9paPH4gtUzcadxLgctbt16DJ2HnsACxr8o8XOGP7Syj65RX7yjr6x+0vlv8AI+w4Lzb6rjPYT+Cenz6f5Hy3RRRX8jH7QFeo+Ff+QOn++/8AOvLq9R8K/wDIHT/ff+dfr/gn/wAlBL/r3L84nxXHn/ItX+Jfkz6Z8Kf8i9Z/7rf+hGvk79rzxBiDQPCkTj5nl1CdO42jyoT9DulH4V9Y+FP+Res/91v/AEI1+b37Q3iH/hIPipqojk8yDTBHp0XGNvkD94v4TM9f3t4X5d9YzuNVrSmnL/21fnf5H8y+IGN9hlLprebUf1/Sx4lRRRX9Nn4EFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH0N8BtKzNqetuv3FS1jP+987/yWvsnwHZ+fq7XTD5baMkH0d/lH/jua8B+FGlf2V4IstwxJebrp/wDtp93/AMcC19TeArPyNKku2GGuZOD6onA/XdX8lcb5j9dzvEVVsnyr0jp+lz+keE8D9VymjT6tX+/X/gHc0UUV8ofRhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB+QX/BVP/XfDL/d17/2wr8jK/XP/gqn/rfhl/u69/7YV+RlABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB+t3/BK3/j8+JY/wCmWifzva/YKvx7/wCCV3/H/wDEkf8ATHRf/Qr2v2EoAKKKKACiiigAooooAKim/wBS/wDun+VS1FN/qX/3T/KgD+RSiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/rmsv+POD/AK5J/IVaqnp/NhbH/pjH/wCgirlABRRRQAUUUUAFFFFABX5wf8FPP+SFeHv+xttf/SG+r9H6/OD/AIKef8kK8Pf9jba/+kN9QB+F1FFFABRRRQAUUUUAFFFFAH21/wAE9/8Ak53Qv+vDU/8A0mev6Fq/np/4J7/8nO6F/wBeGp/+kz1/QtQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAfCH/BRn/k225/7DOnfzev5/q/oB/wCCjP8Aybbc/wDYZ07+b1/P9QAUUUUAFFFFABRRRQAUUUUAftr/AMEu/wDkl/i//sPp/wCksdfp3X5if8Eu/wDkl/jD/sPJ/wCk0dfp3QAUUUUAf//S/fyiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKimhhuYXt7hFkilUo6MMqysMEEehFS0UnFNcr2BO2qPhfxb4el8Ma/daRJkxxtugc/xwtyh6DJxwccZBHaubr6n+Mfhj+09FTXrVM3Om/6zA5a3br0H8B59AN1fLFfxHx7w08kziphYr92/eh/hfT5bfI/e+Hc1WPwUar+JaP1X+e4V6j4V/wCQOn++/wDOvLq9R8K/8gdP99/519Z4J/8AJQS/69y/OJ4/Hn/ItX+Jfkz6K0nUrbRvBX9r3p229jaz3Mp9I4dzN+gr8jNRv7nVdQutUvW33F5NJPK3rJKxZj+Zr9D/AIweIf7A+BU0cbmObVCmnxEd/NkZpB9DCjivzkr/AEw8IMu5MHWxrXxNRXpFf8H8D+NvEzG82JpYVfZV/v8A+AvxCiiiv2E/MQooooAKKKKACiiigAooooAKKKKACiiigAq3YWcuoX1vYQf6y5lSFP8AechRVSvTfhFpX9p+NrWRhmOxR7lv+Ajav5Mw/KvNzjHrBYGri39iLf3LQ7sswbxWLpYZfaaX9fI+w7S2isrWGzgGI4I1iQeioAB+gr6P0mz+waZbWeADFGoYDpux83614foFp9u1m0tiAVMgZgehVPmI/IV9A1/GUpOT5mf1LGKSstgooopDCiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD8g/wDgqn/rfhj/ALuvf+4+vyLr9dP+Cqf+t+GP+7r3/uPr8i6ACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD9af+CVx/4mfxIH/TDRv/Qryv2Hr8dv+CV5/wCJt8Rh/wBO2j/+hXdfsTQAUUUUAFFFFABRRRQAVHL/AKp/90/yqSo5f9U/+6f5UAfyJ0UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH9cOlnOmWh9YIv/QRV+s7RznSbI/9O0X/AKAK0aACiiigAooooAKKKKACvzh/4Kef8kJ8Pf8AY22v/pDfV+j1fnD/AMFPP+SE+Hv+xttf/SG+oA/C2iiigAooooAKKKKACiiigD7a/wCCe/8Ayc7oX/Xhqf8A6TPX9C1fz0/8E9/+TndC/wCvDU//AEmev6FqACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD4Q/4KM/8AJttz/wBhnTv5vX8/1f0A/wDBRn/k225/7DOnfzev5/qACiiigAooooAKKKKACiiigD9tf+CXf/JMPGH/AGHk/wDSaOv07r8w/wDgl3/yTHxh/wBh6P8A9Jo6/TygAooooA//0/38ooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigCOWKKeJ4JkDxyKUdGGVZSMEEehFfD3jDw7L4X8QXWktnylO+3Y/xQt9w9Bkj7pxxkGvuWvH/jD4Y/tbQhrdqmbnTMs+By1ufvf98fe9AN1flfi1wv/aeUPFUV+9o+8vOP2l92vysfX8G5t9UxvsZv3J6fPp/kfKdeo+Ff+QOn++/868ur1Hwr/wAgdP8Aff8AnX5F4J/8lBL/AK9y/OJ9px5/yLV/iX5M80/aP8RCTTfCvhSFz+4gnv50/hJkcxxfioST8Gr5VrvfiZrJ1vxpqM4bdHbuLSL0CwDYcexYE/jXBV/rXwdl31HJsPQtry3frLX8L2P4H4nx31vNK1ZbXsvRaL8gooor6Y8AKKKKACiiigAooooAKKKKACiiigAooooAK+l/gNpXl2Opa045mkS2jPtGNzY+u4flXzRX3D8OdK/sfwZpdsww8kP2h/XdN8/P0BA/CvzPxUzH6vk31db1JJfJa/okfe+HeB9tmntntBN/N6L9fuPd/h/aeZfXF4cYhjCD6yHt+C/rXrFcf4ItPs+hrKetxI0nTGAPlH/oOfxrsK/ms/dwooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPyD/4Kp/634Y/7uvf+4+vyLr9dP+Cqf+t+GP8Au69/7j6/IugAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/WP/glgf+J38RV/6ddJ/R7mv2Nr8bv+CWJ/4qD4hL/056X+klxX7I0AFFFFABRRRQAUUUUAFRy/6p/90/yqSo5f9U/+6f5UAfyJ0UUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAH9behHOiaefW1g/wDQBWrWN4dOfD+mH1s7f/0WtbNABRRRQAUUUUAFFFFABX5w/wDBTz/khPh7/sbbX/0hvq/R6vzh/wCCnn/JCfD3/Y22v/pDfUAfhbRRRQAUUUUAFFFFABRRRQB9tf8ABPf/AJOd0L/rw1P/ANJnr+hav56f+Ce//Jzuhf8AXhqf/pM9f0LUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAHwh/wUZ/5Ntuf+wzp383r+f6v6Af8Agoz/AMm23P8A2GdO/m9fz/UAFFFFABRRRQAUUUUAFFFFAH7af8Eu/wDkmPjD/sPR/wDpNHX6eV+YX/BLv/kmXjD/ALDsf/pNHX6e0AFFFFAH/9T9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKY8aSo0UihkYFWUjIIPGMelPopNJqzBHxB428Nv4W8RXOmAHyD+9tie8L/d/wC+cFT7itHT9RTSPCFzqj9LSOeXHrsBIH49K95+LPhb+3fD51G2TN3pm6VcdWh/5aL+AAYfTA618gePNV/s/wCG32RTh7+5EHHXap3t+HygfjX474c8EPBeJKy2nH91Vi3H/C5Ruv8At3VeiXc+x4u4jVThKWMk/fp6P1Sdvv0PmaSR5ZGlkO5nJZie5NMoor/UJK2iP4bCiiimIKKKKACiiigAooooAKKKKACiiigAooooA1dC01tY1qx0pP8Al6njiOOysQCfwFfoFFFgJBCvoqKo/AACvkb4K6V9u8X/AG5h8mnwPLntvf8AdqPyJP4V9teFrT7ZrtqhB2xt5rY7eXyPwyAK/njxczH2uY0sHHanH8Zf8BI/bPDXA+zwM8S/tu3yj/wbnt9nbLZ2kFopyIY1jB9doxVmiivyY/SAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAPyD/4Kp/634Y/7uvf+4+vyLr9dP+Cqf+s+GP8Au69/7j6/IugAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooA/Vv/glkf+Km+IC/9OOm/pJPX7K1+M3/AASzP/FV+Pl9dO08/lLLX7M0AFFFFABRRRQAUUUUAFRy/wCqf/dP8qkqOX/VN/un+VAH8idFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB/Wn4XO7wzpB9bG2/wDRa1u1z3hE7vCmit66dan/AMhLXQ0AFFFFABRRRQAUUUUAFfnD/wAFPP8AkhPh/wD7G21/9Ib6v0er84f+Cnf/ACQnw/8A9jbaf+kN9QB+FtFFFABRRRQAUUUUAFFFFAH21/wT3/5Od0L/AK8NT/8ASZ6/oWr+en/gnv8A8nO6F/14an/6TPX9C1ABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQB8If8FGf+Tbbn/sM6d/N6/n+r+gH/goz/wAm23P/AGGdO/m9fz/UAFFFFABRRRQAUUUUAFFFFAH7Z/8ABLv/AJJl4xH/AFHY/wD0mSv09r8wf+CXX/JM/GP/AGHYv/SZK/T6gAooooA//9X9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKAE9q/PD9pPw1faDrOm6fY2sv9ksk9xbuqkoJJX+aLjvGAMf7JFfohUU0MVxC9vMoeORSrKehBr1uHsxpZXmtLNnSU501JLpbmVnr93ToeZneXzzDL55eqnLGVr6fyu600Pxd+x3n/PCT/vg/wCFH2O8/wCeEn/fB/wr9MNS8O6pa30tvBazzRocI6RswZcDHIGOnWqP9i6z/wA+Fz/35f8Awr9W/wCIyT/6BF/4H/8Aan53/wAQuh/0E/8Akv8A9sfm79jvP+eEn/fB/wAKPsd5/wA8JP8Avg/4V+kX9i6z/wA+Fz/35f8Awo/sXWf+fC5/78v/AIUf8Rkn/wBAi/8AA/8A7UP+IXQ/6Cf/ACX/AO2Pzd+x3n/PCT/vg/4UfY7z/nhJ/wB8H/Cv0i/sTWf+fC5/78v/AIUf2JrP/Phc/wDflv8ACj/iMk/+gRf+B/8A2of8Quh/0E/+S/8A2x+bv2O8/wCeEn/fB/wo+x3n/PCT/vg/4V+kX9iaz/z4XP8A35b/AAo/sTWf+fC5/wC/Lf4Uf8Rkn/0CL/wP/wC1D/iF0P8AoJ/8l/8Atj83fsd5/wA8JP8Avg/4UfY7z/nhJ/3wf8K/SL+xNZ/58Ln/AL8t/hR/Yms/8+Fz/wB+W/wo/wCIyT/6BF/4H/8Aah/xC6H/AEE/+S//AGx+bv2O8/54Sf8AfB/wo+x3n/PCT/vg/wCFfpF/Yms/8+Fz/wB+W/wo/sTWf+fC5/78t/hR/wARkn/0CL/wP/7UP+IXQ/6Cf/Jf/tj83fsd5/zwk/74P+FH2O8/54Sf98H/AAr9Iv7E1n/nwuf+/Lf4Uf2JrP8Az4XP/flv8KP+IyT/AOgRf+B//ah/xC6H/QT/AOS//bH5u/Y7z/nhJ/3wf8KPsd5/zwk/74P+FfpF/Yms/wDPhc/9+W/wo/sTWf8Anwuf+/Lf4Uf8Rkn/ANAi/wDA/wD7UP8AiF0P+gn/AMl/+2Pn74HaM9loN5qc8ZSS8uAi5GD5cI4/8eZvyr63+HtnmS6v2H3QsKHtz8zfyWuJ/sTWf+fC5/78t/hXsPhPT307RYo5kMcshaSRTwQScDjt8oHFflOeZtPM8fUx1RWcnt2VrJfJI/Rsoy6GAwdPCQd1FHSUUUV5J6IUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFAHzL+0T+y74Q/aRPh9vFWq6lpn/CPC8EH9nmEeZ9t8ndv82N/u+QuMY6mvmf/h198I/+hr8R/naf/I9fplRQB+Zn/Dr74Sf9DX4j/O0/+MUn/Dr34Tf9DZ4i/wDJT/4xX6aUUAfmV/w69+E//Q2eIv8AyV/+MUn/AA69+FHbxb4h/K1/+M1+m1FAH5kf8OvPhT28W+Ifytf/AIzSf8OvPhX/ANDd4g/75tf/AI1X6cUUAfmN/wAOvPhZ/wBDdr//AHza/wDxqk/4dd/C7/ob9f8A++LX/wCNV+nVFAH5if8ADrv4X9vF+vf98W3/AMbpP+HXfwx/6HDXv+/dt/8AG6/TyigD8wf+HXXwy/6HHXf+/dt/8RSf8Ouvhp/0OOuf9+rb/wCIr9P6KAPy/wD+HXXw2/6HLXP+/Nt/8RSf8Oufhv8A9Dnrf/fm3/8Aia/UGigD8vf+HXPw5/6HPW/+/Fv/APE03/h1z8Ov+h01r/vxb/4V+olFAH5df8OuPh5/0Ous/wDgPb/4Un/Drj4ff9DrrP8A4D29fqNRQB+W/wDw648AdvG2sf8AgNBSf8Ot/AP/AEO+r/8AgLBX6k0UAflr/wAOt/Af/Q76v/4Cwf40xv8Aglt4H/g8c6qPrZwH/wBmFfqbRQB+V3/DrXwZ28d6n/4Aw/8AxdN/4da+Du3jzUv/AABh/wDjlfqnRQB+Vf8Aw608IdvHupf+AEP/AMcpv/DrTwn28faj/wCC+L/47X6rUUAflP8A8Os/Cvbx/qH/AILov/jtJ/w6z8L/APQ/3/8A4Lov/j1fqzRQB+Un/DrLwz2+IF9/4LY//j9N/wCHWXhzt8Qb3/wWR/8Ax+v1dooA/KD/AIdY+Hf+ihXv/grj/wDkik/4dY6B2+Id5/4Ko/8A5Ir9YKKAPyd/4dYaF/0US7/8FKf/ACTTf+HWGidviLdf+ClP/kqv1kooA/Jn/h1fo/b4jXP/AIKE/wDkqm/8Or9J7fEe4/8ABOn/AMl1+tFFAH5K/wDDq7TO3xIn/wDBMv8A8mUn/Dq7Tu3xJm/8Eq//ACZX610UAfkl/wAOrbDt8Spf/BIv/wAm0z/h1badviXJ/wCCMf8AydX64UUAfkd/w6stu3xMf/wRD/5Opn/DqyHt8TW/8EI/+T6/XSigD8iv+HVcfb4nH/wQf/fCm/8ADqsdvif/AOW//wDfCv13ooA+Mf2Wf2RB+zTq+var/wAJX/wkf9t21vb7P7O+w+T5DM2c/aZ92d2MYXGK+zqKKACiiigAooooAKKKKACmsNylfUYp1FAH5nf8OvvhF/0NXiP87T/5HpP+HX3wj/6GvxH+dp/8Yr9MqKAPzM/4de/CX/oa/EX/AJKf/GKT/h178Jv+hs8Rf+Sv/wAYr9NKKAPzK/4de/Cft4s8Q/8Akr/8ZpP+HXnwp/6G3xD+Vr/8Zr9NqKAPzI/4defCrt4u8Qfla/8Axmk/4defCz/obvEH/fNr/wDGq/TiigD8xf8Ah158Lf8Aob9f/wC+LX/41R/w67+F3/Q36/8A98Wv/wAar9OqKAPzD/4dd/DD/ocNe/7923/xuk/4dd/DH/ocNd/7923/AMbr9PaKAPzB/wCHXXwz/wChx13/AL9W3/xFJ/w66+Gv/Q5a5/36tv8A4iv0/ooA/L7/AIddfDb/AKHLXP8Avzbf/E0n/Drn4cf9Dnrf/fm3/wDia/UKigD8vP8Ah1z8Oe3jTWv+/Fv/APE0n/Drn4d/9DprX/gPb/4V+olFAH5c/wDDrj4e/wDQ66z/AOA9v/hSf8OuPh//ANDtrH/gNb1+o9FAH5b/APDrfwD/ANDtrH/gLBTf+HW/gLt431f/AMBYP8a/UqigD8tP+HW/gTt441b/AMBIP8ai/wCHWvgrt461TH/XlD/8XX6oUUAflb/w618G/wDQ+an/AOAMP/xym/8ADrTwf/0Pmpf+AEP/AMcr9VKKAPyq/wCHWnhHt491H/wXxf8Ax2m/8Os/Cn/Q/ah/4L4v/jtfqvRQB+U3/DrPwt/0P+of+C6L/wCPUn/DrPwx2+IF/wD+C2L/AOPV+rVFAH5R/wDDrLw12+IN9/4LI/8A4/Tf+HWXh3/ooV7/AOCuP/5Ir9XqKAPyg/4dY+H+3xDvP/BXH/8AJFN/4dY6D2+Id3/4Ko//AJJr9YaKAPyc/wCHWGh9viJdf+ClP/kmm/8ADrDRv+ii3P8A4KE/+Sq/WWigD8mP+HV+kdviNcf+CdP/AJLpv/Dq/S+3xHuP/BMv/wAl1+tNFAH5Kf8ADq7Tf+ikT/8AglX/AOTKT/h1dp/b4kzf+CVf/k2v1sooA/JE/wDBK2y7fEqX/wAEi/8AybTf+HVlr/0UuT/wRj/5Or9cKKAPyN/4dWW/b4mP/wCCIf8AyfTf+HVcX/RTW/8ABCP/AJPr9dKKAPyJ/wCHVadvicf/AAQf/fCm/wDDqr/qp/8A5b//AN8a/XiigDN0fTxpOkWOlB/NFlbRW+/G3d5SBM45xnHStKiigAooooAKKKKACiiigArwz4//AAF8N/tDeD7LwZ4o1C+021sdSj1NJbAxiQyRQzQhT5iOu3bMTwM5Ar3OigD8y2/4Je/CX+HxZ4iH1+yH/wBoCoW/4JefC3+Hxfr4+q2p/wDaQr9OqKAPy/b/AIJdfDb+Dxlrg+sNsf8A2UVXb/glx4A/g8bawPrbQH/Cv1IooA/K5/8Aglr4LP8Aq/HeqD62UJ/9nFVm/wCCWfhc/wCr+IF+PrpsR/8Aawr9WqKAPybf/glhop/1fxFul+ukof8A26FVH/4JW2Z/1fxLkX66GD/K9FfrfRQB+ev7Pf7Bo+A/xPsfiP8A8Jx/bn2K3uYBZf2T9k3faIzHnzftc2Nuc42c+1foVRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAeEftGfBMfH/wCG0vw9/tn+wfMvLa7+1/ZftmPs5Py+V5sPXPXdx6V+f3/Dqr/qqH/lv/8A3xr9eKKAPyH/AOHVX/VUP/Lf/wDvjR/w6q/6qh/5b/8A98a/XiigD8h/+HVX/VUP/Lf/APvjR/w6q/6qh/5b/wD98a/XiigD8h/+HVX/AFVD/wAt/wD++NH/AA6q/wCqof8Alv8A/wB8a/XiigD8h/8Ah1V/1VD/AMt//wC+NH/Dqr/qqH/lv/8A3xr9eKKAPmD9l39m8fs2eGNY8Of8JD/wkX9rX63vnfYvsPlbYlj2bPPn3dM5yPTFfT9FFABRRRQB/9b9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9f9/KKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigD/9k=\\\" ></image>\\n</svg>\\n\"\n  },\n  {\n    \"provider\": \"aliyun_bai_lian_model_provider\",\n    \"name\": \"阿里云百炼\",\n    \"icon\": \"<svg id=\\\"图层_1\\\" data-name=\\\"图层 1\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\" viewBox=\\\"0 0 50 50\\\"><defs><style>.cls-1{fill:#6d49f6;}.cls-2{fill:#6be8d2;}.cls-3{fill:#9b8ee8;}.cls-4{fill:#0d22d2;}.cls-5{fill:#2c53dc;}.cls-6{fill:#5eccc9;}.cls-7{fill:#fff;}</style></defs><title>【icon】阿里百炼大模型</title><path class=\\\"cls-1\\\" d=\\\"M35.463,6.454,25,12.5,14.186,18.737,4.032,12.882a2.33,2.33,0,0,1,.851-.851L23.829,1.089a2.358,2.358,0,0,1,2.342,0Z\\\"/><polygon class=\\\"cls-2\\\" points=\\\"35.825 31.232 35.825 31.243 25 37.491 35.814 31.232 35.825 31.232\\\"/><polygon class=\\\"cls-2\\\" points=\\\"35.825 31.232 35.825 31.243 25 37.491 35.814 31.232 35.825 31.232\\\"/><polygon class=\\\"cls-2\\\" points=\\\"35.825 31.232 35.825 31.243 25 37.491 35.814 31.232 35.825 31.232\\\"/><polygon class=\\\"cls-2\\\" points=\\\"35.825 31.232 35.825 31.243 25 37.491 35.814 31.232 35.825 31.232\\\"/><polygon class=\\\"cls-2\\\" points=\\\"35.825 31.232 35.825 31.243 25 37.491 35.814 31.232 35.825 31.232\\\"/><path class=\\\"cls-3\\\" d=\\\"M45.117,12.031,35.463,6.454,25,12.5,14.186,18.737v.01L25,25h.011l10.814-6.248,10.143-5.865A2.33,2.33,0,0,0,45.117,12.031Z\\\"/><path class=\\\"cls-4\\\" d=\\\"M4.032,12.882a2.254,2.254,0,0,0-.32,1.171V35.926a2.319,2.319,0,0,0,.32,1.182l10.143-5.865v-12.5h.011v-.01Z\\\"/><polygon class=\\\"cls-5\\\" points=\\\"25 24.995 14.175 31.243 14.175 18.747 14.186 18.747 25 24.995\\\"/><path class=\\\"cls-6\\\" d=\\\"M45.968,37.1a2.278,2.278,0,0,1-.851.862L26.171,48.9a2.358,2.358,0,0,1-2.342,0l-9.3-5.375,10.462-6.035H25l10.825-6.248Z\\\"/><path class=\\\"cls-2\\\" d=\\\"M46.288,25.2V35.926a2.251,2.251,0,0,1-.32,1.171L35.825,31.243v-.011Z\\\"/><path class=\\\"cls-7\\\" d=\\\"M46.288,14.053V25.2L35.825,31.232V18.747l10.143-5.865A2.254,2.254,0,0,1,46.288,14.053Z\\\"/><polygon class=\\\"cls-7\\\" points=\\\"35.825 18.747 35.825 31.232 35.814 31.232 25.011 24.995 35.825 18.747\\\"/><path class=\\\"cls-2\\\" d=\\\"M35.814,31.232,25.011,25H25L14.175,31.243,4.032,37.108a2.33,2.33,0,0,0,.851.851l9.644,5.567,10.462-6.035H25l10.825-6.248v-.011Z\\\"/></svg>\"\n  },\n  {\n    \"provider\": \"model_anthropic_provider\",\n    \"name\": \"Anthropic\",\n    \"icon\": \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" shape-rendering=\\\"geometricPrecision\\\" text-rendering=\\\"geometricPrecision\\\" image-rendering=\\\"optimizeQuality\\\" fill-rule=\\\"evenodd\\\" clip-rule=\\\"evenodd\\\" viewBox=\\\"0 0 512 512\\\"><rect fill=\\\"#CC9B7A\\\" width=\\\"512\\\" height=\\\"512\\\" rx=\\\"104.187\\\" ry=\\\"105.042\\\"/><path fill=\\\"#1F1F1E\\\" fill-rule=\\\"nonzero\\\" d=\\\"M318.663 149.787h-43.368l78.952 212.423 43.368.004-78.952-212.427zm-125.326 0l-78.952 212.427h44.255l15.932-44.608 82.846-.004 16.107 44.612h44.255l-79.126-212.427h-45.317zm-4.251 128.341l26.91-74.701 27.083 74.701h-53.993z\\\"/></svg>\"\n  },\n  {\n    \"provider\": \"model_siliconCloud_provider\",\n    \"name\": \"SILICONFLOW\",\n    \"icon\": \"<svg width=\\\"100%\\\" height=\\\"100%\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\">\\n<g clip-path=\\\"url(#clip0_10835_2)\\\">\\n<path fill-rule=\\\"evenodd\\\" clip-rule=\\\"evenodd\\\" d=\\\"M20.4785 0H18.7463C13.203 0 10.0078 3.54162 10.0078 8.96931V9.9C9.36946 9.71693 8.70867 9.62403 8.04462 9.624C4.11139 9.62377 0.92308 12.8123 0.92308 16.7453C0.92308 20.6785 4.11139 23.8668 8.04462 23.8668C11.9779 23.8668 15.1662 20.6785 15.1662 16.7453C15.1662 16.2669 15.1179 15.7899 15.0224 15.321H20.4785C21.915 15.3157 23.0769 14.1496 23.0769 12.7131C23.0769 11.2763 21.915 10.1102 20.4785 10.1049H15.0508V8.73831C15.0508 6.65954 16.5519 5.15838 18.7463 5.15838H20.4785C21.8878 5.13692 23.0192 3.98862 23.0192 2.57908C23.0192 1.16977 21.8878 0.0212308 20.4785 0ZM10.1619 16.7453C10.1619 16.1838 9.93885 15.6452 9.54178 15.2481C9.14471 14.8511 8.60616 14.628 8.04462 14.628C7.48307 14.628 6.94453 14.8511 6.54746 15.2481C6.15038 15.6452 5.92731 16.1838 5.92731 16.7453C5.92731 17.3069 6.15038 17.8454 6.54746 18.2425C6.94453 18.6395 7.48307 18.8626 8.04462 18.8626C8.60616 18.8626 9.14471 18.6395 9.54178 18.2425C9.93885 17.8454 10.1619 17.3069 10.1619 16.7453Z\\\" fill=\\\"#8358F6\\\"/>\\n</g>\\n</svg>\\n\"\n  },\n  {\n    \"provider\": \"model_regolo_provider\",\n    \"name\": \"Regolo\",\n    \"icon\": \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\" standalone=\\\"no\\\"?>\\n<svg\\n   id=\\\"Livello_2\\\"\\n   data-name=\\\"Livello 2\\\"\\n   viewBox=\\\"0 0 104.4 104.38\\\"\\n   version=\\\"1.1\\\"\\n   sodipodi:docname=\\\"Regolo_logo_positive.svg\\\"\\n   width=\\\"100%\\\" height=\\\"100%\\\"\\n   inkscape:version=\\\"1.4 (e7c3feb100, 2024-10-09)\\\"\\n   xmlns:inkscape=\\\"http://www.inkscape.org/namespaces/inkscape\\\"\\n   xmlns:sodipodi=\\\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\\\"\\n   xmlns=\\\"http://www.w3.org/2000/svg\\\"\\n   xmlns:svg=\\\"http://www.w3.org/2000/svg\\\">\\n  <sodipodi:namedview\\n     id=\\\"namedview13\\\"\\n     pagecolor=\\\"#ffffff\\\"\\n     bordercolor=\\\"#666666\\\"\\n     borderopacity=\\\"1.0\\\"\\n     inkscape:showpageshadow=\\\"2\\\"\\n     inkscape:pageopacity=\\\"0.0\\\"\\n     inkscape:pagecheckerboard=\\\"0\\\"\\n     inkscape:deskcolor=\\\"#d1d1d1\\\"\\n     inkscape:zoom=\\\"2.1335227\\\"\\n     inkscape:cx=\\\"119.05193\\\"\\n     inkscape:cy=\\\"48.511318\\\"\\n     inkscape:window-width=\\\"1920\\\"\\n     inkscape:window-height=\\\"1025\\\"\\n     inkscape:window-x=\\\"0\\\"\\n     inkscape:window-y=\\\"0\\\"\\n     inkscape:window-maximized=\\\"1\\\"\\n     inkscape:current-layer=\\\"g13\\\" />\\n  <defs\\n     id=\\\"defs1\\\">\\n    <style\\n       id=\\\"style1\\\">\\n      .cls-1 {\\n        fill: #303030;\\n      }\\n\\n      .cls-2 {\\n        fill: #59e389;\\n      }\\n    </style>\\n  </defs>\\n  <g\\n     id=\\\"Grafica\\\"\\n     transform=\\\"translate(0,-40.87)\\\">\\n    <g\\n       id=\\\"g13\\\">\\n      <path\\n         class=\\\"cls-1\\\"\\n         d=\\\"m 104.39,105.96 v 36.18 c 0,0.32 -0.05,0.62 -0.14,0.91 -0.39,1.27 -1.58,2.2 -2.99,2.2 H 65.08 c -1.73,0 -3.13,-1.41 -3.13,-3.13 V 113.4 c 0,-0.15 0,-0.29 0,-0.44 v -7 c 0,-1.73 1.4,-3.13 3.13,-3.13 h 36.19 c 1.5,0 2.77,1.07 3.06,2.5 0.05,0.21 0.07,0.41 0.07,0.63 z\\\"\\n         id=\\\"path1\\\" />\\n      <path\\n         class=\\\"cls-1\\\"\\n         d=\\\"m 104.39,105.96 v 36.18 c 0,0.32 -0.05,0.62 -0.14,0.91 -0.39,1.27 -1.58,2.2 -2.99,2.2 H 65.08 c -1.73,0 -3.13,-1.41 -3.13,-3.13 V 113.4 c 0,-0.15 0,-0.29 0,-0.44 v -7 c 0,-1.73 1.4,-3.13 3.13,-3.13 h 36.19 c 1.5,0 2.77,1.07 3.06,2.5 0.05,0.21 0.07,0.41 0.07,0.63 z\\\"\\n         id=\\\"path2\\\" />\\n      <path\\n         class=\\\"cls-2\\\"\\n         d=\\\"M 101.27,40.88 H 65.09 c -1.73,0 -3.13,1.4 -3.13,3.13 v 28.71 c 0,4.71 -1.88,9.23 -5.2,12.56 L 44.42,97.61 c -3.32,3.33 -7.85,5.2 -12.55,5.2 H 18.98 c -2.21,0 -3.99,-1.79 -3.99,-3.99 V 87.29 c 0,-2.21 1.79,-3.99 3.99,-3.99 h 20.34 c 1.41,0 2.59,-0.93 2.99,-2.2 0.09,-0.29 0.14,-0.59 0.14,-0.91 V 44 c 0,-0.22 -0.02,-0.42 -0.07,-0.63 -0.29,-1.43 -1.56,-2.5 -3.06,-2.5 H 3.13 C 1.4,40.87 0,42.27 0,44 v 7 c 0,0.15 0,0.29 0,0.44 v 28.72 c 0,1.72 1.41,3.13 3.13,3.13 h 3.16 c 2.21,0 3.99,1.79 3.99,3.99 v 11.53 c 0,2.21 -1.79,3.99 -3.99,3.99 H 3.15 c -1.73,0 -3.13,1.4 -3.13,3.13 v 36.19 c 0,1.72 1.41,3.13 3.13,3.13 h 36.19 c 1.73,0 3.13,-1.41 3.13,-3.13 V 113.4 c 0,-4.7 1.87,-9.23 5.2,-12.55 L 60,88.51 c 3.33,-3.32 7.85,-5.2 12.56,-5.2 h 28.71 c 1.73,0 3.13,-1.4 3.13,-3.13 V 44 c 0,-1.73 -1.4,-3.13 -3.13,-3.13 z\\\"\\n         id=\\\"path3\\\" />\\n    </g>\\n  </g>\\n</svg>\\n\"\n  }\n]"
  },
  {
    "path": "ui/src/components/dynamics-form/items/radio/Radio.vue",
    "content": "<template>\n  <el-radio-group v-bind=\"$attrs\">\n    <el-radio v-for=\"(item, index) in option_list\" :key=\"index\" :label=\"item[valueField]\">\n      <div v-html=\"label(item)\"></div>\n    </el-radio>\n  </el-radio-group>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport _ from 'lodash'\n\nconst props = defineProps<{\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n}>()\n\nconst textField = computed(() => {\n  return props.formField.text_field ? props.formField.text_field : 'key'\n})\n\nconst valueField = computed(() => {\n  return props.formField.value_field ? props.formField.value_field : 'value'\n})\n\nconst option_list = computed(() => {\n  return props.formField.option_list ? props.formField.option_list : []\n})\n\nconst label = (option: any) => {\n  return option[textField.value]\n}\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/radio/RadioButton.vue",
    "content": "<template>\n  <el-radio-group v-bind=\"$attrs\">\n    <el-radio-button v-for=\"(item, index) in option_list\" :key=\"index\" :label=\"item[valueField]\">\n      <div v-html=\"label(item)\"></div>\n    </el-radio-button>\n  </el-radio-group>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport _ from 'lodash'\n\nconst props = defineProps<{\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n}>()\n\nconst textField = computed(() => {\n  return props.formField.text_field ? props.formField.text_field : 'key'\n})\n\nconst valueField = computed(() => {\n  return props.formField.value_field ? props.formField.value_field : 'value'\n})\n\nconst option_list = computed(() => {\n  return props.formField.option_list ? props.formField.option_list : []\n})\n\nconst label = (option: any) => {\n  return option[textField.value]\n}\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/radio/RadioCard.vue",
    "content": "<template>\n  <div class=\"radio-card\" :style=\"radioContentStyle\">\n    <el-row :gutter=\"12\" class=\"w-full\">\n      <template v-for=\"(item, index) in option_list\" :key=\"index\">\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"12\" :xl=\"12\">\n          <el-card\n            :key=\"item.value\"\n            class=\"item break-all\"\n            shadow=\"never\"\n            style=\"--el-card-padding: 12px 16px\"\n            :class=\"[\n              inputDisabled ? 'is-disabled' : '',\n              modelValue == item[valueField] ? 'active' : '',\n            ]\"\n            @click=\"inputDisabled ? () => {} : selected(item[valueField])\"\n            :innerHTML=\"item[textField] ? item[textField] : '\\u200D'\"\n          >\n          </el-card>\n        </el-col>\n      </template>\n    </el-row>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { computed, ref, inject } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { useFormDisabled, formItemContextKey } from 'element-plus'\n\nconst inputDisabled = useFormDisabled()\n\nconst props = defineProps<{\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n  // 选中的值\n  modelValue?: any\n  disabled?: boolean\n}>()\nconst elFormItem = inject(formItemContextKey, void 0)\nconst selected = (activeValue: string | number) => {\n  emit('update:modelValue', activeValue)\n  if (elFormItem?.validate) {\n    elFormItem.validate('change')\n  }\n}\nconst emit = defineEmits(['update:modelValue', 'change'])\nconst width = ref<number>()\nconst radioContentStyle = computed(() => {\n  if (width.value) {\n    if (width.value < 350) {\n      return { '--maxkb-radio-card-width': '316px' }\n    } else if (width.value > 770) {\n      return { '--maxkb-radio-card-width': '378px' }\n    } else {\n      return { '--maxkb-radio-card-width': '100%' }\n    }\n  }\n  return {}\n})\n\nconst textField = computed(() => {\n  return props.formField.text_field ? props.formField.text_field : 'key'\n})\n\nconst valueField = computed(() => {\n  return props.formField.value_field ? props.formField.value_field : 'value'\n})\n\nconst option_list = computed(() => {\n  return props.formField.option_list ? props.formField.option_list : []\n})\n</script>\n<style lang=\"scss\" scoped>\n.radio-card {\n  display: flex;\n  flex-wrap: wrap;\n  justify-content: flex-start;\n  width: 100%;\n\n  .is-disabled {\n    border: 1px solid var(--el-card-border-color);\n    background-color: var(--el-fill-color-light);\n    color: var(--el-text-color-placeholder);\n    cursor: not-allowed;\n    &:hover {\n      cursor: not-allowed;\n    }\n  }\n  .active {\n    border: 1px solid var(--el-color-primary);\n    color: var(--el-color-primary);\n  }\n  .item {\n    cursor: pointer;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    width: var(--maxkb-radio-card-width, 100%);\n    margin: 4px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/radio/RadioRow.vue",
    "content": "<template>\n  <div class=\"radio-row\">\n    <div\n      v-for=\"item in option_list\"\n      :key=\"item.value\"\n      class=\"item\"\n      :class=\"[inputDisabled ? 'is-disabled' : '', modelValue == item[valueField] ? 'active' : '']\"\n      @click=\"selected(item[valueField])\"\n    >\n      {{ item[textField] }}\n    </div>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { computed, inject } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { useFormDisabled, formItemContextKey } from 'element-plus'\nconst inputDisabled = useFormDisabled()\nconst props = defineProps<{\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n  // 选中的值\n  modelValue?: any\n}>()\nconst elFormItem = inject(formItemContextKey, void 0)\nconst selected = (activeValue: string | number) => {\n  emit('update:modelValue', activeValue)\n  if (elFormItem?.validate) {\n    elFormItem.validate('change')\n  }\n}\nconst emit = defineEmits(['update:modelValue'])\n\nconst textField = computed(() => {\n  return props.formField.text_field ? props.formField.text_field : 'key'\n})\n\nconst valueField = computed(() => {\n  return props.formField.value_field ? props.formField.value_field : 'value'\n})\n\nconst option_list = computed(() => {\n  return props.formField.option_list ? props.formField.option_list : []\n})\n</script>\n<style lang=\"scss\" scoped>\n.radio-row {\n  // height: 32px;\n  display: inline-flex;\n  border: 1px solid #bbbfc4;\n  border-radius: 4px;\n  font-weight: 400;\n  font-size: 14px;\n  color: var(--el-text-color-primary);\n  padding: 3px 4px;\n  box-sizing: border-box;\n  white-space: nowrap;\n  .is-disabled {\n    border: 1px solid var(--el-card-border-color);\n    background-color: var(--el-fill-color-light);\n    color: var(--el-text-color-placeholder);\n    cursor: not-allowed;\n    &:hover {\n      cursor: not-allowed;\n    }\n  }\n  .active {\n    border-radius: 4px;\n    background: var(--el-color-primary-light-9);\n    color: var(--el-color-primary);\n  }\n  .item {\n    cursor: pointer;\n    margin: 0px 2px;\n    padding: 2px 8px;\n    height: 20px;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    &:last-child {\n      margin: 0 4px 0 2px;\n    }\n    &:first-child {\n      margin: 0 2px 0 4px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/select/MultiSelect.vue",
    "content": "<template>\n  <el-select\n    class=\"m-2\"\n    multiple\n    filterable\n    allow-create\n    clearable\n    default-first-option\n    :reserve-keyword=\"false\"\n    v-bind=\"$attrs\"\n    v-model=\"_modelValue\"\n  >\n    <el-option\n      v-for=\"(item, index) in option_list\"\n      :key=\"index\"\n      :label=\"label(item)\"\n      :value=\"item[valueField]\"\n    >\n    </el-option>\n  </el-select>\n</template>\n<script setup lang=\"ts\">\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { computed, ref } from 'vue'\nimport _ from 'lodash'\nconst rowTemp = ref<any>()\n\nconst props = defineProps<{\n  modelValue?: Array<any>\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n}>()\n\nconst emit = defineEmits(['update:modelValue', 'change'])\n\nconst _modelValue = computed({\n  get() {\n    if (props.modelValue) {\n      return props.modelValue\n    }\n    return []\n  },\n  set($event) {\n    emit('update:modelValue', $event)\n  },\n})\nconst textField = computed(() => {\n  return props.formField.text_field ? props.formField.text_field : 'key'\n})\n\nconst valueField = computed(() => {\n  return props.formField.value_field ? props.formField.value_field : 'value'\n})\n\nconst option_list = computed(() => {\n  return props.formField.option_list ? props.formField.option_list : []\n})\n\nconst label = (option: any) => {\n  return option[textField.value]\n}\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/select/SingleSelect.vue",
    "content": "<template>\n  <el-select\n    filterable\n    :teleported=\"true\"\n    popper-class=\"dynamics-single-select\"\n    clearable\n    v-bind=\"$attrs\"\n    v-model=\"_modelValue\"\n  >\n    <el-option\n      v-for=\"(item, index) in option_list\"\n      :key=\"index\"\n      teleported\n      :label=\"label(item)\"\n      :value=\"item[valueField]\"\n    >\n    </el-option>\n  </el-select>\n</template>\n<script setup lang=\"ts\">\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { computed, ref, useAttrs } from 'vue'\nimport _ from 'lodash'\nconst attrs = useAttrs() as any\n\nconst props = defineProps<{\n  modelValue?: string\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n}>()\n\nconst emit = defineEmits(['update:modelValue', 'change'])\n\nconst _modelValue = computed({\n  get() {\n    return props.modelValue\n  },\n  set(value) {\n    emit('update:modelValue', value)\n    emit('change', props.formField)\n  },\n})\nconst textField = computed(() => {\n  return props.formField.text_field ? props.formField.text_field : 'key'\n})\n\nconst valueField = computed(() => {\n  return props.formField.value_field ? props.formField.value_field : 'value'\n})\n\nconst option_list = computed(() => {\n  return props.formField.option_list ? props.formField.option_list : []\n})\n\nconst label = (option: any) => {\n  //置空\n  if (props.modelValue && option_list.value && !attrs['allow-create']) {\n    const oldItem = option_list.value.find((item) => item[valueField.value] === props.modelValue)\n    if (!oldItem) {\n      emit('update:modelValue', undefined)\n    }\n  }\n\n  return option[textField.value]\n}\n</script>\n<style lang=\"scss\">\n.dynamics-single-select {\n  .el-select-dropdown {\n    max-width: 1px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/slider/Slider.vue",
    "content": "<template>\n  <el-slider v-bind=\"$attrs\" show-input :show-input-controls=\"false\" class=\"custom-slider\" />\n</template>\n<script setup lang=\"ts\"></script>\n<style lang=\"scss\" scoped>\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/switch/SwitchInput.vue",
    "content": "<template>\n    <el-switch v-bind=\"$attrs\" />\n  </template>\n  <script setup lang=\"ts\"></script>\n  <style lang=\"scss\" scoped>\n  </style>\n  "
  },
  {
    "path": "ui/src/components/dynamics-form/items/table/ProgressTableItem.vue",
    "content": "<template>\n  <div class=\"progress-table-item\">\n    <el-popover\n      placement=\"top-start\"\n      :title=\"row[text_field]\"\n      :width=\"200\"\n      trigger=\"hover\"\n      :persistent=\"false\"\n    >\n      <template #reference>\n        <el-progress v-bind=\"$attrs\" :percentage=\"row[value_field]\"></el-progress\n      ></template>\n      <div>\n        <el-row v-for=\"(item, index) in view_card\" :key=\"index\">\n          <el-col :span=\"6\">{{ item.title }}</el-col>\n          <el-col :span=\"18\"> <span class=\"value\" :innerHTML=\"value_html(item)\"> </span></el-col>\n        </el-row>\n      </div>\n    </el-popover>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nconst props = defineProps<{\n  /**\n   *表单渲染Item column\n   */\n  column: any\n  /**\n   * 这一行数据\n   */\n  row: any\n}>()\nconst rowRef = ref<any>()\n\nfunction evalF(text: string, row: any) {\n  rowRef.value = row\n  return eval(text)\n}\nconst props_info = computed(() => {\n  return props.column.props_info ? props.column.props_info : {}\n})\nconst text_field = computed(() => {\n  return props.column.text_field ? props.column.text_field : 'key'\n})\nconst value_field = computed(() => {\n  return props.column.value_field ? props.column.value_field : 'value'\n})\n\nconst value_html = (view_card_item: any) => {\n  if (view_card_item.type === 'eval') {\n    return evalF(view_card_item.value_field, props.row)\n  } else {\n    return props.row[view_card_item.value_field]\n  }\n}\n\nconst view_card = computed(() => {\n  return props_info.value.view_card ? props_info.value.view_card : []\n})\n</script>\n<style lang=\"scss\" scoped>\n@mixin valueScss() {\n  color: var(--el-text-color-primary);\n  font-weight: 500;\n  font-size: 12px;\n  line-height: 22px;\n  height: 22px;\n}\n.progress-table-item {\n  .value {\n    float: right;\n    @include valueScss;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/table/TableCheckbox.vue",
    "content": "<template>\n  <div class=\"table-checkbox\">\n    <div class=\"header\">\n      <div class=\"title\">{{ title }}</div>\n\n      <el-input\n        v-model=\"filterText\"\n        :validate-event=\"false\"\n        :placeholder=\"$t('common.searchBar.placeholder')\"\n        class=\"input-with-select\"\n        style=\"--el-color-danger: #c0c4cc\"\n        clearable\n      >\n        <template #prepend>\n          <el-button :icon=\"Search\" />\n        </template>\n      </el-input>\n    </div>\n\n    <el-table\n      ref=\"multipleTableRef\"\n      :data=\"tableData\"\n      highlight-current-row\n      style=\"width: 100%; height: 100%; --el-bg-color: #f5f6f7\"\n      @selection-change=\"handleSelectionChange\"\n    >\n      <el-table-column type=\"selection\" width=\"55\" />\n      <el-table-column\n        v-for=\"(column, index) in tableColumns\"\n        :key=\"index\"\n        v-bind=\"column\"\n        :label=\"column.label\"\n      >\n        <template #default=\"scope\">\n          <template v-if=\"column.type === 'component'\">\n            <TableColumn :column=\"column\" :row=\"scope.row\"></TableColumn>\n          </template>\n          <template v-else-if=\"column.type === 'eval'\">\n            <span v-html=\"evalF(column.property, scope.row)\"></span\n          ></template>\n          <template v-else>\n            <span>{{ scope.row[column.property] }}</span></template\n          >\n        </template>\n      </el-table-column>\n    </el-table>\n    <div class=\"msg\" v-show=\"props.modelValue\">\n      {{ activeMsg }}\n      <span class=\"active\">\n        {{ activeText }}\n      </span>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { computed, ref, watch } from 'vue'\nimport { Search } from '@element-plus/icons-vue'\nimport type { ElTable } from 'element-plus'\n\nimport _ from 'lodash'\nimport TableColumn from '@/components/dynamics-form/items/table/TableColumn.vue'\nconst filterText = ref<string>('')\nconst props = defineProps<{\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n  // 选中的值\n  modelValue?: Array<any>\n}>()\nconst rowTemp = ref<any>()\nconst evalF: (text: string, row: any) => string = (text: string, row: any) => {\n  rowTemp.value = row\n  return eval(text)\n}\nconst emit = defineEmits(['update:modelValue', 'change'])\n\nconst multipleTableRef = ref<InstanceType<typeof ElTable>>()\n\nconst _data = computed({\n  get() {\n    return props.modelValue\n  },\n  set(value) {\n    emit('update:modelValue', value)\n    emit('change', props.formField)\n  }\n})\nconst handleSelectionChange = (val: any[]) => {\n  _data.value = val.map((row) => row[valueField.value])\n}\n\nconst propsInfo = computed(() => {\n  return props.formField.props_info ? props.formField.props_info : {}\n})\n\nconst activeMsg = computed(() => {\n  return propsInfo.value.active_msg ? propsInfo.value.active_msg : ''\n})\nconst title = computed(() => {\n  return propsInfo.value.title ? propsInfo.value.title : ''\n})\nconst tableColumns = computed(() => {\n  return propsInfo.value.table_columns ? propsInfo.value.table_columns : []\n})\n\nconst option_list = computed(() => {\n  return props.formField.option_list ? props.formField.option_list : []\n})\n\nconst textField = computed(() => {\n  return props.formField.text_field ? props.formField.text_field : 'key'\n})\n\nconst valueField = computed(() => {\n  return props.formField.value_field ? props.formField.value_field : 'value'\n})\n\nconst tableData = computed(() => {\n  if (option_list.value) {\n    if (filterText.value) {\n      return option_list.value.filter((item: any) =>\n        tableColumns.value.some((c: any) => {\n          let v = ''\n          if (c.type === 'eval') {\n            v = evalF(c.property, item)\n          } else if (c.type === 'component') {\n            return false\n          } else {\n            v = item[c.property]\n          }\n          return typeof v == 'string' ? v.indexOf(filterText.value) >= 0 : false\n        })\n      )\n    } else {\n      return option_list.value.filter((item: any) => item[valueField.value])\n    }\n  }\n  return []\n})\n\n/**\n * 监听表格数据，设置默认值\n */\nwatch(\n  () => tableData.value,\n  () => {\n    if (tableData.value && tableData.value.length > 0) {\n      const defaultItem = _.head(tableData.value)\n      let defaultItemValue = _.get(defaultItem, valueField.value)\n      if (props.modelValue) {\n        const row = option_list.value.find((f: any) => f[valueField.value] === props.modelValue)\n        if (row) {\n          defaultItemValue = row[valueField.value]\n        }\n      }\n      emit('update:modelValue', defaultItemValue)\n    } else {\n      emit('update:modelValue', undefined)\n    }\n    emit('change', props.formField)\n  }\n)\n\nconst activeText = computed(() => {\n  if (props.modelValue) {\n    const rows = option_list.value.filter((f: any) =>\n      props.modelValue?.includes(f[valueField.value])\n    )\n    if (rows) {\n      if (rows.length > 3) {\n        return (\n          rows\n            .map((row) => row[textField.value])\n            .splice(0, 3)\n            .join(',') + '...'\n        )\n      } else {\n        return rows.map((row) => row[textField.value]).join(',')\n      }\n    }\n  }\n  return props.modelValue\n})\n</script>\n<style lang=\"scss\" scoped>\n.table-checkbox {\n  .header {\n    display: flex;\n    justify-content: space-between;\n    margin-bottom: 16px;\n    .title {\n      color: var(--el-text-color-primary);\n      font-weight: 400;\n      font-size: 14px;\n      line-height: 22px;\n    }\n    .input-with-select {\n      width: 45%;\n    }\n  }\n  .msg {\n    margin-top: 12px;\n    color: rgba(100, 106, 115, 1);\n    .active {\n      margin-left: 3px;\n      color: var(--el-color-primary);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/table/TableColumn.vue",
    "content": "<template>\n  <component :is=\"column.property\" v-bind=\"attrs\" :column=\"column\" :row=\"row\"></component>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport _ from 'lodash'\nconst props = defineProps<{\n  /**\n   *表单渲染Item column\n   */\n  column: any\n  /**\n   * 这一行数据\n   */\n  row: any\n}>()\n\nconst attrs = computed(() => {\n  return props.column.attrs ? props.column.attrs : {}\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/table/TableRadio.vue",
    "content": "<template>\n  <div class=\"table-radio\">\n    <div class=\"header\">\n      <div class=\"title\">{{ title }}</div>\n\n      <el-input\n        v-model=\"filterText\"\n        :validate-event=\"false\"\n        :placeholder=\"$t('common.searchBar.placeholder')\"\n        class=\"input-with-select\"\n        style=\"--el-color-danger: #c0c4cc\"\n        clearable\n      >\n        <template #prepend>\n          <el-button :icon=\"Search\" />\n        </template>\n      </el-input>\n    </div>\n\n    <el-table\n      ref=\"singleTableRef\"\n      :data=\"tableData\"\n      highlight-current-row\n      style=\"width: 100%; height: 100%; --el-bg-color: #f5f6f7\"\n      @current-change=\"_data = $event[valueField]\"\n    >\n      <el-table-column width=\"50px\">\n        <template #default=\"scope\">\n          <input type=\"radio\" :checked=\"_data === scope.row[valueField]\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        v-for=\"(column, index) in tableColumns\"\n        v-bind=\"column\"\n        :label=\"column.label\"\n        :key=\"index\"\n      >\n        <template #default=\"scope\">\n          <template v-if=\"column.type === 'component'\">\n            <TableColumn :column=\"column\" :row=\"scope.row\"></TableColumn>\n          </template>\n          <template v-else-if=\"column.type === 'eval'\">\n            <span v-html=\"evalF(column.property, scope.row)\"></span\n          ></template>\n          <template v-else>\n            <span>{{ scope.row[column.property] }}</span></template\n          >\n        </template>\n      </el-table-column>\n    </el-table>\n    <div class=\"msg\" v-show=\"props.modelValue\">\n      {{ activeMsg }}\n      <span class=\"active\">\n        {{ activeText }}\n      </span>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { computed, ref, watch } from 'vue'\nimport { Search } from '@element-plus/icons-vue'\nimport type { ElTable } from 'element-plus'\n\nimport _ from 'lodash'\nimport TableColumn from '@/components/dynamics-form/items/table/TableColumn.vue'\nconst filterText = ref<string>('')\nconst props = defineProps<{\n  formValue?: any\n  formfieldList?: Array<FormField>\n  field: string\n  otherParams: any\n  formField: FormField\n  view?: boolean\n  // 选中的值\n  modelValue?: any\n}>()\nconst rowTemp = ref<any>()\nconst evalF = (text: string, row: any) => {\n  rowTemp.value = row\n  return eval(text)\n}\nconst emit = defineEmits(['update:modelValue', 'change'])\n\nconst singleTableRef = ref<InstanceType<typeof ElTable>>()\n\nconst _data = computed({\n  get() {\n    return props.modelValue\n  },\n  set(value) {\n    emit('update:modelValue', value)\n    emit('change', props.formField)\n  }\n})\n\nconst propsInfo = computed(() => {\n  return props.formField.props_info ? props.formField.props_info : {}\n})\n\nconst activeMsg = computed(() => {\n  return propsInfo.value.active_msg ? propsInfo.value.active_msg : ''\n})\nconst title = computed(() => {\n  return propsInfo.value.title ? propsInfo.value.title : ''\n})\nconst tableColumns = computed(() => {\n  return propsInfo.value.table_columns ? propsInfo.value.table_columns : []\n})\n\nconst option_list = computed(() => {\n  return props.formField.option_list ? props.formField.option_list : []\n})\n\nconst textField = computed(() => {\n  return props.formField.text_field ? props.formField.text_field : 'key'\n})\n\nconst valueField = computed(() => {\n  return props.formField.value_field ? props.formField.value_field : 'value'\n})\n\nconst tableData = computed(() => {\n  if (option_list.value) {\n    if (filterText.value) {\n      return option_list.value.filter((item: any) =>\n        tableColumns.value.some((c: any) => {\n          let v = ''\n          if (c.type === 'eval') {\n            v = evalF(c.property, item)\n          } else if (c.type === 'component') {\n            return false\n          } else {\n            v = item[c.property]\n          }\n          return typeof v == 'string' ? v.indexOf(filterText.value) >= 0 : false\n        })\n      )\n    } else {\n      return option_list.value.filter((item: any) => item[valueField.value])\n    }\n  }\n  return []\n})\n\n/**\n * 监听表格数据，设置默认值\n */\nwatch(\n  () => tableData.value,\n  () => {\n    if (tableData.value && tableData.value.length > 0) {\n      const defaultItem = _.head(tableData.value)\n      let defaultItemValue = _.get(defaultItem, valueField.value)\n      if (props.modelValue) {\n        const row = option_list.value.find((f: any) => f[valueField.value] === props.modelValue)\n        if (row) {\n          defaultItemValue = row[valueField.value]\n        }\n      }\n      emit('update:modelValue', defaultItemValue)\n    } else {\n      emit('update:modelValue', undefined)\n    }\n    emit('change', props.formField)\n  }\n)\n\nconst activeText = computed(() => {\n  if (props.modelValue) {\n    const row = option_list.value.find((f: any) => f[valueField.value] === props.modelValue)\n    return row[textField.value]\n  }\n  return props.modelValue\n})\n</script>\n<style lang=\"scss\" scoped>\n.table-radio {\n  .header {\n    display: flex;\n    justify-content: space-between;\n    margin-bottom: 16px;\n    .title {\n      color: var(--el-text-color-primary);\n      font-weight: 400;\n      font-size: 14px;\n      line-height: 22px;\n    }\n    .input-with-select {\n      width: 45%;\n    }\n  }\n  .msg {\n    margin-top: 12px;\n    color: rgba(100, 106, 115, 1);\n    .active {\n      margin-left: 3px;\n      color: var(--el-color-primary);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/tree/Tree.vue",
    "content": "<template>\n  <div v-loading=\"loading\" class=\"w-full\">\n    <div class=\"card-never border-r-6 mb-16\">\n      <el-checkbox\n        v-model=\"allCheck\"\n        :label=\"$t('common.allCheck')\"\n        size=\"large\"\n        class=\"ml-24\"\n        @change=\"handleAllCheckChange\"\n      />\n    </div>\n    <div style=\"height: calc(100vh - 450px)\">\n      <el-scrollbar>\n        <el-tree\n          :data=\"option_list\"\n          @check-change=\"change\"\n          v-loading=\"loading\"\n          style=\"width: 100%\"\n          :props=\"propsData\"\n          :load=\"loadNode\"\n          :lazy=\"attrs.lazy\"\n          show-checkbox\n          :node-key=\"valueField\"\n          ref=\"treeRef\"\n        >\n          <template #default=\"{ node, data }\">\n            <div class=\"flex align-center lighter\">\n              <img :src=\"data.icon\" alt=\"\" height=\"20\" v-if=\"data.icon\" />\n              <img\n                src=\"@/assets/fileType/file-icon.svg\"\n                alt=\"\"\n                height=\"20\"\n                v-else-if=\"data.type === 'folder'\"\n              />\n              <img\n                src=\"@/assets/fileType/docx-icon.svg\"\n                alt=\"\"\n                height=\"22\"\n                v-else-if=\"data.type === 'docx' || data.name.endsWith('.docx')\"\n              />\n              <img\n                src=\"@/assets/fileType/xlsx-icon.svg\"\n                alt=\"\"\n                height=\"22\"\n                v-else-if=\"data.type === 'sheet' || data.name.endsWith('.xlsx')\"\n              />\n              <img\n                src=\"@/assets/fileType/xls-icon.svg\"\n                alt=\"\"\n                height=\"22\"\n                v-else-if=\"data.name.endsWith('xls')\"\n              />\n              <img\n                src=\"@/assets/fileType/csv-icon.svg\"\n                alt=\"\"\n                height=\"22\"\n                v-else-if=\"data.name.endsWith('csv')\"\n              />\n              <img\n                src=\"@/assets/fileType/pdf-icon.svg\"\n                alt=\"\"\n                height=\"22\"\n                v-else-if=\"data.name.endsWith('.pdf')\"\n              />\n              <img\n                src=\"@/assets/fileType/html-icon.svg\"\n                alt=\"\"\n                height=\"22\"\n                v-else-if=\"data.name.endsWith('.html')\"\n              />\n              <img\n                src=\"@/assets/fileType/txt-icon.svg\"\n                alt=\"\"\n                height=\"22\"\n                v-else-if=\"data.name.endsWith('.txt')\"\n              />\n              <img\n                src=\"@/assets/fileType/zip-icon.svg\"\n                alt=\"\"\n                height=\"22\"\n                v-else-if=\"data.name.endsWith('.zip')\"\n              />\n              <img\n                src=\"@/assets/fileType/md-icon.svg\"\n                alt=\"\"\n                height=\"22\"\n                v-else-if=\"data.name.endsWith('.md')\"\n              />\n\n              <span class=\"ml-4\">{{ node.label }}</span>\n            </div>\n          </template>\n        </el-tree></el-scrollbar\n      >\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, useAttrs, nextTick, inject } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { get, post, put, del } from '@/request/index'\nimport { cloneDeep } from 'lodash'\nimport { formItemContextKey } from 'element-plus'\nimport type { LoadFunction } from 'element-plus'\nconst get_extra = inject('get_extra') as any\nconst elFormItem = inject(formItemContextKey, void 0)\nconst request = {\n  get,\n  post,\n  put,\n  del,\n}\n\nconst allCheck = ref<boolean>(false)\n\nconst handleAllCheckChange = (checked: boolean) => {\n  if (checked) {\n    const nodes = Object.values(treeRef.value?.store.nodesMap || {}) as any[]\n    nodes.forEach((node) => {\n      if (!node.disabled) {\n        treeRef.value?.setChecked(node.data, true, false)\n      }\n    })\n  } else {\n    treeRef.value?.setCheckedKeys([])\n  }\n}\ninterface Tree {\n  label: string\n  leaf?: boolean\n  type: string\n  value: string\n  disabled: boolean\n  icon?: string\n}\nconst textField = computed(() => {\n  return props.formField.text_field ? props.formField.text_field : 'label'\n})\n\nconst valueField = computed(() => {\n  return props.formField.value_field ? props.formField.value_field : 'value'\n})\nconst childrenField = computed(() => {\n  return props.formField.childrenField ? props.formField.childrenField : 'children'\n})\nconst option_list = computed(() => {\n  return props.formField.option_list ? props.formField.option_list : []\n})\nconst propsData = computed(() => {\n  return {\n    label: textField,\n    children: childrenField,\n    isLeaf: (data: any) => data.leaf,\n    disabled: (data: any) => data.disabled,\n  }\n})\n\nconst attrs = useAttrs() as any\nconst treeRef = ref<any>(null)\nconst request_call = new Function(\n  'request',\n  'extra',\n  'return  request.post(extra.url,extra.body,{},extra.loading).then(extra.then);',\n)\nfunction renderTemplate(template: string, data: any) {\n  return template.replace(/\\$\\{(\\w+)\\}/g, (match, key) => {\n    return data[key] !== undefined ? data[key] : match\n  })\n}\n\nconst loadNode: LoadFunction = (node, resolve)=> {\n  request_call(request, {\n    url: renderTemplate(\n      '/workspace/${current_workspace_id}/knowledge/${current_knowledge_id}/datasource/tool/${current_tool_id}/' +\n        attrs.fetch_list_function,\n      { ...props.otherParams, ...(get_extra ? get_extra() : {}) },\n    ),\n    body: { current_node: node.level == 0 ? undefined : node.data },\n    then: (res: any) => {\n      resolve(res.data)\n      res.data.forEach((childNode: any) => {\n        if (childNode.is_exist) {\n          treeRef.value?.setChecked(childNode.token, true, false)\n        }\n      })\n    },\n    loading: loading,\n  })\n}\nconst props = withDefaults(\n  defineProps<{ modelValue?: any; formField: FormField; otherParams: any }>(),\n  {\n    modelValue: () => [],\n  },\n)\n\nconst emit = defineEmits(['update:modelValue', 'change'])\n\nconst model_value = computed({\n  get: () => {\n    if (!props.modelValue) {\n      emit('update:modelValue', [])\n    }\n    return props.modelValue\n  },\n  set: (v: Array<any>) => {\n    emit('update:modelValue', v)\n  },\n})\nconst change = () => {\n  model_value.value = cloneDeep(treeRef.value?.getCheckedNodes() || [])\n  nextTick(() => {\n    if (elFormItem?.validate) {\n      elFormItem.validate('change')\n    }\n  })\n}\n\nconst loading = ref<boolean>(false)\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/upload/LocalFileUpload.vue",
    "content": "<template>\n  <div v-loading=\"loading\" class=\"w-full\">\n    <el-upload\n      ref=\"UploadRef\"\n      :webkitdirectory=\"false\"\n      class=\"w-full\"\n      drag\n      multiple\n      v-bind:file-list=\"modelValue\"\n      action=\"#\"\n      :auto-upload=\"false\"\n      :show-file-list=\"false\"\n      :accept=\"accept\"\n      :limit=\"file_count_limit\"\n      :on-exceed=\"onExceed\"\n      :on-change=\"fileHandleChange\"\n      @click.prevent=\"handlePreview(false)\"\n    >\n      <img src=\"@/assets/upload-icon.svg\" alt=\"\" />\n      <div class=\"el-upload__text\">\n        <p>\n          {{ $t('views.document.upload.uploadMessage') }}\n          <em class=\"hover\" @click.prevent=\"handlePreview(false)\">\n            {{ $t('views.document.upload.selectFile') }}\n          </em>\n          <em class=\"hover ml-4\" @click.prevent=\"handlePreview(true)\">\n            {{ $t('views.document.upload.selectFiles') }}\n          </em>\n        </p>\n        <div class=\"upload__decoration\">\n          <p>\n            {{ $t('views.document.tip.fileLimitCountTip1') }} {{ file_count_limit }}\n            {{ $t('views.document.tip.fileLimitCountTip2') }},\n            {{ $t('views.document.tip.fileLimitSizeTip1') }} {{ file_size_limit }} MB\n          </p>\n          <p>{{ $t('views.document.upload.formats') }}{{ formats }}</p>\n        </div>\n      </div>\n    </el-upload>\n    <el-row :gutter=\"8\" v-if=\"modelValue?.length\" class=\"mt-16\">\n      <template v-for=\"(item, index) in modelValue\" :key=\"index\">\n        <el-col :span=\"12\" class=\"mb-8\">\n          <el-card shadow=\"never\" style=\"--el-card-padding: 8px 12px; line-height: normal\">\n            <div class=\"flex-between\">\n              <div class=\"flex\">\n                <img :src=\"getImgUrl(item && item?.name)\" alt=\"\" width=\"40\" />\n                <div class=\"ml-8\">\n                  <p class=\"ellipsis-1\" :title=\"item && item?.name\">{{ item && item?.name }}</p>\n                  <el-text type=\"info\" size=\"small\">{{\n                    filesize(item && item?.size) || '0K'\n                  }}</el-text>\n                </div>\n              </div>\n              <el-button text @click=\"deleteFile(index)\">\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </div>\n          </el-card>\n        </el-col>\n      </template>\n    </el-row>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, useAttrs, nextTick, inject, ref } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { MsgError } from '@/utils/message'\nimport type { UploadFiles } from 'element-plus'\nimport { filesize, getImgUrl, fileType } from '@/utils/common'\nimport { t } from '@/locales'\nconst upload = inject('upload') as any\nconst attrs = useAttrs() as any\nconst props = withDefaults(defineProps<{ modelValue?: any; formField: FormField }>(), {\n  modelValue: () => [],\n})\nconst onExceed = () => {\n  MsgError(\n    t('views.document.tip.fileLimitCountTip1') +\n      file_count_limit.value +\n      t('views.document.tip.fileLimitCountTip2'),\n  )\n}\nconst emit = defineEmits(['update:modelValue'])\nconst fileArray = ref<any>([])\nconst loading = ref<boolean>(false)\nconst UploadRef = ref()\n// 上传on-change事件\nconst fileHandleChange = (file: any, fileList: UploadFiles) => {\n  //1、判断文件大小是否合法，文件限制不能大于100M\n  const isLimit = file?.size / 1024 / 1024 < file_size_limit.value\n  if (!isLimit) {\n    MsgError(t('views.document.tip.fileLimitSizeTip1') + file_size_limit.value + 'MB')\n    fileList.splice(-1, 1) //移除当前超出大小的文件\n    return false\n  }\n  if (!file_type_list.value.includes(fileType(file.name).toLocaleUpperCase())) {\n    if (file?.name !== '.DS_Store') {\n      MsgError(t('views.document.upload.errorMessage2'))\n    }\n    fileList.splice(-1, 1)\n    return false\n  }\n\n  if (file?.size === 0) {\n    MsgError(t('views.document.upload.errorMessage3'))\n    fileList.splice(-1, 1)\n    return false\n  }\n  upload(file.raw, loading).then((ok: any) => {\n    const split_path = ok.data.split('/')\n    const file_id = split_path[split_path.length - 1]\n    fileArray.value?.push({ name: file.name, file_id, size: file.size })\n    emit('update:modelValue', fileArray.value)\n  })\n}\nfunction deleteFile(index: number) {\n  props.modelValue.splice(index, 1)\n}\n\nconst handlePreview = (bool: boolean) => {\n  let inputDom: any = null\n  nextTick(() => {\n    if (document.querySelector('.el-upload__input') != null) {\n      inputDom = document.querySelector('.el-upload__input')\n      inputDom.webkitdirectory = bool\n    }\n  })\n}\nconst accept = computed(() => {\n  return (attrs.file_type_list || []).map((item: any) => '.' + item.toLowerCase()).join(',')\n})\nconst file_type_list = computed(() => {\n  return attrs.file_type_list.map((item: any) => item.toUpperCase()) || []\n})\nconst formats = computed(() => {\n  return file_type_list.value.join('、')\n})\nconst file_size_limit = computed(() => {\n  return attrs.file_size_limit || 50\n})\nconst file_count_limit = computed(() => {\n  return attrs.file_count_limit || 100\n})\n</script>\n<style lang=\"scss\" scoped>\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/items/upload/UploadInput.vue",
    "content": "<template>\n  <el-upload\n    style=\"width: 100%\"\n    v-loading=\"loading\"\n    action=\"#\"\n    v-bind=\"$attrs\"\n    :auto-upload=\"false\"\n    :on-change=\"(file: any, fileList: any) => uploadFile(file, fileList)\"\n    v-model:file-list=\"model_value\"\n    multiple\n    :show-file-list=\"false\"\n  >\n    <el-button type=\"primary\">{{ $t('chat.uploadFile.label') }}</el-button>\n  </el-upload>\n  <el-space wrap class=\"w-full media-file-width upload_content mt-16\">\n    <template v-for=\"(file, index) in model_value\" :key=\"index\">\n      <el-card style=\"--el-card-padding: 0\" shadow=\"never\">\n        <div\n          class=\"flex-between\"\n          :class=\"[inputDisabled ? 'is-disabled' : '']\"\n          style=\"padding: 0 8px 0 8px\"\n        >\n          <div class=\"flex align-center\" style=\"width: 70%\">\n            <img :src=\"getImgUrl(file && file?.name)\" alt=\"\" width=\"24\" class=\"mr-4\" />\n            <span class=\"ellipsis-1\" :title=\"file.name\">\n              {{ file.name }}\n            </span>\n          </div>\n          <div class=\"flex align-center\">\n            <div class=\"ellipsis-1\" :title=\"formatSize(file.size)\">{{ formatSize(file.size) }}</div>\n\n            <el-button link class=\"ml-8\" @click=\"deleteFile(file)\" v-if=\"!inputDisabled\">\n              <AppIcon iconName=\"app-delete\"></AppIcon>\n            </el-button>\n          </div>\n        </div>\n      </el-card>\n    </template>\n  </el-space>\n</template>\n<script setup lang=\"ts\">\nimport { computed, inject, ref, useAttrs } from 'vue'\nimport { ElMessage } from 'element-plus'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { getImgUrl } from '@/utils/common'\nimport { t } from '@/locales'\nimport { useFormDisabled } from 'element-plus'\nconst inputDisabled = useFormDisabled()\nconst attrs = useAttrs() as any\nconst upload = inject('upload') as any\nconst props = withDefaults(defineProps<{ modelValue?: any; formField: FormField }>(), {\n  modelValue: () => [],\n})\nconst emit = defineEmits(['update:modelValue'])\nfunction formatSize(sizeInBytes: number) {\n  const units = ['B', 'KB', 'MB', 'GB', 'TB']\n  let size = sizeInBytes\n  let unitIndex = 0\n\n  while (size >= 1024 && unitIndex < units.length - 1) {\n    size /= 1024\n    unitIndex++\n  }\n\n  return size.toFixed(2) + ' ' + units[unitIndex]\n}\n\nconst deleteFile = (file: any) => {\n  if (inputDisabled.value) {\n    return\n  }\n  fileArray.value = fileArray.value.filter((f: any) => f.uid != file.uid)\n  emit('update:modelValue', fileArray.value)\n}\n\nconst model_value = computed({\n  get: () => {\n    if (!props.modelValue) {\n      emit('update:modelValue', [])\n    }\n    return props.modelValue\n  },\n  set: (v: Array<any>) => {\n    emit('update:modelValue', v)\n  },\n})\nconst fileArray = ref<any>([])\n\nconst loading = ref<boolean>(false)\n\nconst uploadFile = async (file: any, fileList: Array<any>) => {\n  fileList.splice(fileList.indexOf(file), 1)\n  if (fileArray.value.find((f: any) => f.name === file.name)) {\n    ElMessage.warning(t('chat.uploadFile.fileRepeat'))\n\n    return\n  }\n  const max_file_size = (props.formField as any).max_file_size\n  if (file.size / 1024 / 1024 > max_file_size) {\n    ElMessage.warning(t('chat.uploadFile.sizeLimit') + max_file_size + 'MB')\n    return\n  }\n\n  if (fileList.length > attrs.limit) {\n    ElMessage.warning(\n      t('chat.uploadFile.limitMessage1') + attrs.limit + t('chat.uploadFile.limitMessage2'),\n    )\n    return\n  }\n  upload(file.raw, loading).then((ok: any) => {\n    const split_path = ok.data.split('/')\n    const file_id = split_path[split_path.length - 1]\n    fileArray.value?.push({ name: file.name, file_id, size: file.size })\n    emit('update:modelValue', fileArray.value)\n  })\n}\n</script>\n<style lang=\"scss\" scoped>\n.upload_content {\n  .is-disabled {\n    background-color: var(--el-fill-color-light);\n    color: var(--el-text-color-placeholder);\n    cursor: not-allowed;\n    &:hover {\n      cursor: not-allowed;\n    }\n  }\n  &.media-file-width {\n    :deep(.el-space__item) {\n      width: calc(50% - 4px) !important;\n    }\n  }\n}\n@media only screen and (max-width: 768px) {\n  .upload_content {\n    &.media-file-width {\n      :deep(.el-space__item) {\n        min-width: 100% !important;\n      }\n    }\n  }\n}\n.debug-ai-chat {\n  .upload_content {\n    &.media-file-width {\n      :deep(.el-space__item) {\n        min-width: 100% !important;\n      }\n    }\n  }\n}\n.execution-details {\n  .upload_content {\n    &.media-file-width {\n      :deep(.el-space__item) {\n        min-width: 100% !important;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/dynamics-form/type.ts",
    "content": "import type { Dict } from '@/api/type/common'\n\ninterface ViewCardItem {\n  /**\n   * 类型\n   */\n  type: 'eval' | 'default'\n  /**\n   * 标题\n   */\n  title: string\n  /**\n   * 值 根据类型不一样 取值也不一样 default= row[value_field] eval `${parseFloat(row.number).toLocaleString(\"zh-CN\",{style: \"decimal\",maximumFractionDigits:1})}%&nbsp;&nbsp;&nbsp;`\n   */\n  value_field: string\n}\n\ninterface TableColumn {\n  /**\n   * 字段|组件名称|可计算的模板字符串\n   */\n  property: string\n  /**\n   *表头\n   */\n  label: string\n  /**\n   * 表数据字段\n   */\n  value_field?: string\n\n  attrs?: Attrs\n  /**\n   * 类型\n   */\n  type: 'eval' | 'component' | 'default'\n\n  props_info?: PropsInfo\n}\ninterface ColorItem {\n  /**\n   * 颜色#f56c6c\n   */\n  color: string\n  /**\n   * 进度\n   */\n  percentage: number\n}\ninterface Attrs {\n  /**\n   * 提示语\n   */\n  placeholder?: string\n  /**\n   * 标签的长度，例如 '50px'。 作为 Form 直接子元素的 form-item 会继承该值。 可以使用 auto。\n   */\n  labelWidth?: string\n  /**\n   * 表单域标签的后缀\n   */\n  labelSuffix?: string\n  /**\n   * 星号的位置。\n   */\n  requireAsteriskPosition?: 'left' | 'right'\n\n  color?: Array<ColorItem>\n\n  [propName: string]: any\n}\ninterface PropsInfo {\n  /**\n   * 表格选择的card\n   */\n  view_card?: Array<ViewCardItem>\n  /**\n   * 表格选择\n   */\n  table_columns?: Array<TableColumn>\n  /**\n   * 选中 message\n   */\n  active_msg?: string\n\n  /**\n   * 组件样式\n   */\n  style?: Dict<any>\n\n  /**\n   * el-form-item 样式\n   */\n  item_style?: Dict<any>\n  /**\n   * 表单校验 这个和element校验一样\n   */\n  rules?: Dict<any>\n  /**\n   * 默认 不为空校验提示\n   */\n  err_msg?: string\n  /**\n   *tabs的时候使用\n   */\n  tabs_label?: string\n\n  [propName: string]: any\n}\n\ninterface FormField {\n  field: string\n  /**\n   * 输入框类型\n   */\n  input_type: string\n  /**\n   * 提示\n   */\n  label?: string | any\n  /**\n   * 是否 必填\n   */\n  required?: boolean\n  /**\n   * 默认值\n   */\n  default_value?: any\n  /**\n   * 是否显示默认值\n   */\n  show_default_value?: boolean\n  /**\n   *  {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才显示\n   */\n  relation_show_field_dict?: Dict<Array<any>>\n  /**\n   * {field:field_value_list} 表示在 field有值 ,并且值在field_value_list中才 执行函数获取 数据\n   */\n  relation_trigger_field_dict?: Dict<any>\n  /**\n   * 执行器类型  OPTION_LIST请求Option_list数据 CHILD_FORMS请求子表单\n   */\n  trigger_type?: 'OPTION_LIST' | 'CHILD_FORMS'\n  /**\n   * 前端attr数据\n   */\n  attrs?: Attrs\n  /**\n   * 其他额外信息\n   */\n  props_info?: PropsInfo\n  /**\n   * 下拉选字段field\n   */\n  text_field?: string\n  /**\n   * 下拉选 value\n   */\n  value_field?: string\n  /**\n   * 下拉选数据\n   */\n  option_list?: Array<any>\n  /**\n   * 供应商\n   */\n  provider?: string\n  /**\n   * 执行函数\n   */\n  method?: string\n\n  children?: Array<FormField>\n  required_asterisk?: boolean\n  [propName: string]: any\n}\nexport type { FormField }\n"
  },
  {
    "path": "ui/src/components/execution-detail-card/index.vue",
    "content": "<template>\n  <el-card class=\"mb-8\" shadow=\"never\" style=\"--el-card-padding: 12px 16px\">\n    <div class=\"flex-between cursor\" @click=\"data['show'] = !data['show']\">\n      <div class=\"flex align-center\">\n        <el-icon class=\"mr-8 arrow-icon\" :class=\"data['show'] ? 'rotate-90' : ''\">\n          <CaretRight />\n        </el-icon>\n        <component\n          :is=\"iconComponent(`${data.type}-icon`)\"\n          class=\"mr-8\"\n          :size=\"24\"\n          :item=\"data.info\"\n        />\n        <h4>{{ data.name }}</h4>\n      </div>\n      <div class=\"flex align-center\">\n        <span\n          class=\"mr-16 color-secondary\"\n          v-if=\"\n            data.type === WorkflowType.Question ||\n            data.type === WorkflowType.AiChat ||\n            data.type === WorkflowType.ImageUnderstandNode ||\n            data.type === WorkflowType.ImageGenerateNode ||\n            data.type === WorkflowType.Application ||\n            data.type == WorkflowType.IntentNode ||\n            data.type === WorkflowType.VideoUnderstandNode\n          \"\n          >{{ data?.message_tokens + data?.answer_tokens }} tokens</span\n        >\n        <span class=\"mr-16 color-secondary\" v-if=\"data.status != 202\"\n          >{{ data?.run_time?.toFixed(2) || 0.0 }} s</span\n        >\n        <el-icon class=\"color-success\" :size=\"16\" v-if=\"data.status === 200\">\n          <CircleCheck />\n        </el-icon>\n        <el-icon class=\"is-loading\" :size=\"16\" v-else-if=\"data.status === 202\">\n          <Loading />\n        </el-icon>\n        <el-icon class=\"color-danger\" :size=\"16\" v-else>\n          <CircleClose />\n        </el-icon>\n      </div>\n    </div>\n    <el-collapse-transition>\n      <div class=\"mt-12\" v-if=\"data['show']\">\n        <template v-if=\"data.status === 200 || data.type == WorkflowType.LoopNode\">\n          <!-- 开始 -->\n          <template\n            v-if=\"data.type === WorkflowType.Start || data.type === WorkflowType.Application\"\n          >\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.inputParam') }}\n              </h5>\n\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\"> {{ $t('chat.paragraphSource.question') }}:</span>\n\n                  {{ data.question || '-' }}\n                </div>\n\n                <div v-for=\"(f, i) in data.global_fields\" :key=\"i\" class=\"mb-8\">\n                  <span class=\"color-secondary\">{{ f.label }}:</span> {{ f.value }}\n                </div>\n                <div v-if=\"data.document_list?.length > 0\">\n                  <p class=\"mb-8 color-secondary\">{{ $t('common.fileUpload.document') }}:</p>\n\n                  <el-space wrap>\n                    <template v-for=\"(f, i) in data.document_list\" :key=\"i\">\n                      <el-card shadow=\"never\" style=\"--el-card-padding: 8px\" class=\"file cursor\">\n                        <div class=\"flex align-center\">\n                          <img :src=\"getImgUrl(f && f?.name)\" alt=\"\" width=\"24\" />\n                          <div class=\"ml-4 ellipsis\" :title=\"f && f?.name\">\n                            {{ f && f?.name }}\n                          </div>\n                        </div>\n                      </el-card>\n                    </template>\n                  </el-space>\n                </div>\n                <div v-if=\"data.image_list?.length > 0\">\n                  <p class=\"mb-8 color-secondary\">{{ $t('common.fileUpload.image') }}:</p>\n\n                  <el-space wrap>\n                    <template v-for=\"(f, i) in data.image_list\" :key=\"i\">\n                      <el-image\n                        :src=\"f.url\"\n                        alt=\"\"\n                        fit=\"cover\"\n                        style=\"width: 40px; height: 40px; display: block\"\n                        class=\"border-r-6\"\n                      />\n                    </template>\n                  </el-space>\n                </div>\n                <div v-if=\"data.audio_list?.length > 0\">\n                  <p class=\"mb-8 color-secondary\">{{ $t('chat.executionDetails.audioFile') }}:</p>\n\n                  <el-space wrap>\n                    <template v-for=\"(f, i) in data.audio_list\" :key=\"i\">\n                      <audio\n                        :src=\"f.url\"\n                        controls\n                        style=\"width: 300px; height: 43px\"\n                        class=\"border-r-6\"\n                      />\n                    </template>\n                  </el-space>\n                </div>\n                <div v-if=\"data.video_list?.length > 0\">\n                  <p class=\"mb-8 color-secondary\">{{ $t('common.fileUpload.image') }}:</p>\n\n                  <el-space wrap>\n                    <template v-for=\"(f, i) in data.video_list\" :key=\"i\">\n                      <video\n                        :src=\"f.url\"\n                        style=\"width: 170px; display: block\"\n                        controls\n                        autoplay\n                        class=\"border-r-6\"\n                      />\n                    </template>\n                  </el-space>\n                </div>\n                <div v-if=\"data.other_list?.length > 0\">\n                  <p class=\"mb-8 color-secondary\">{{ $t('common.fileUpload.document') }}:</p>\n\n                  <el-space wrap>\n                    <template v-for=\"(f, i) in data.other_list\" :key=\"i\">\n                      <el-card shadow=\"never\" style=\"--el-card-padding: 8px\" class=\"file cursor\">\n                        <div class=\"flex align-center\">\n                          <img :src=\"getImgUrl(f && f?.name)\" alt=\"\" width=\"24\" />\n                          <div class=\"ml-4 ellipsis\" :title=\"f && f?.name\">\n                            {{ f && f?.name }}\n                          </div>\n                        </div>\n                      </el-card>\n                    </template>\n                  </el-space>\n                </div>\n              </div>\n            </div>\n          </template>\n          <!-- 知识库检索 -->\n          <template v-if=\"data.type == WorkflowType.SearchKnowledge\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.searchContent') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">{{ data.question || '-' }}</div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.searchResult') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <template v-if=\"data.paragraph_list?.length > 0\">\n                  <template\n                    v-for=\"(paragraph, paragraphIndex) in arraySort(\n                      data.paragraph_list,\n                      'similarity',\n                      true,\n                    )\"\n                    :key=\"paragraphIndex\"\n                  >\n                    <ParagraphCard\n                      :data=\"paragraph\"\n                      :content=\"paragraph.content\"\n                      :index=\"paragraphIndex\"\n                    />\n                  </template>\n                </template>\n                <template v-else> -</template>\n              </div>\n            </div>\n          </template>\n          <!-- 判断器 -->\n          <template v-if=\"data.type == WorkflowType.Condition\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.conditionResult') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                {{ data.branch_name || '-' }}\n              </div>\n            </div>\n          </template>\n          <!-- AI 对话 -->\n          <template v-if=\"data.type == WorkflowType.AiChat\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('views.application.form.roleSettings.label') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                {{ data.system || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\" v-if=\"!isKnowLedge\">\n              <h5 class=\"p-8-12\">{{ $t('chat.history') }}</h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <template v-if=\"data.history_message?.length > 0\">\n                  <p\n                    class=\"mt-4 mb-4\"\n                    v-for=\"(history, historyIndex) in data.history_message\"\n                    :key=\"historyIndex\"\n                  >\n                    <span class=\"color-secondary mr-4\">{{ history.role }}:</span\n                    ><span>{{ history.content }}</span>\n                  </p>\n                </template>\n                <template v-else> -</template>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{\n                  isKnowLedge\n                    ? $t('views.application.form.prompt.label')\n                    : $t('chat.executionDetails.currentChat')\n                }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{ data.question || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('workflow.nodes.aiChatNode.think') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{ data.reasoning_content || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.answer') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <MdRenderer v-if=\"data.answer\" :source=\"data.answer\" noImgZoomIn></MdRenderer>\n\n                <template v-else> -</template>\n              </div>\n            </div>\n          </template>\n          <!-- 问题优化 / 意图识别-->\n          <template\n            v-if=\"\n              data.type == WorkflowType.Question ||\n              data.type == WorkflowType.Application ||\n              data.type == WorkflowType.IntentNode\n            \"\n          >\n            <div class=\"card-never border-r-6\" v-if=\"data.type !== WorkflowType.Application\">\n              <h5 class=\"p-8-12\">\n                {{ $t('views.application.form.roleSettings.label') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                {{ data.system || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\" v-if=\"data.type !== WorkflowType.Application\">\n              <h5 class=\"p-8-12\">{{ $t('chat.history') }}</h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <template v-if=\"data.history_message?.length > 0\">\n                  <p\n                    class=\"mt-4 mb-4\"\n                    v-for=\"(history, historyIndex) in data.history_message\"\n                    :key=\"historyIndex\"\n                  >\n                    <span class=\"color-secondary mr-4\">{{ history.role }}:</span\n                    ><span>{{ history.content }}</span>\n                  </p>\n                </template>\n                <template v-else> -</template>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\" v-if=\"data.type !== WorkflowType.Application\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.currentChat') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{ data.question || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{\n                  data.type == WorkflowType.Application\n                    ? $t('common.param.outputParam')\n                    : $t('chat.executionDetails.answer')\n                }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <MdPreview\n                  v-if=\"data.answer\"\n                  ref=\"editorRef\"\n                  editorId=\"preview-only\"\n                  :modelValue=\"data.answer\"\n                  style=\"background: none\"\n                  noImgZoomIn\n                />\n                <template v-else> -</template>\n              </div>\n            </div>\n          </template>\n\n          <!-- 指定回复 -->\n          <template v-if=\"data.type === WorkflowType.Reply\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.replyContent') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <el-scrollbar height=\"150\">\n                  <MdPreview\n                    v-if=\"data.answer\"\n                    ref=\"editorRef\"\n                    editorId=\"preview-only\"\n                    :modelValue=\"data.answer\"\n                    style=\"background: none\"\n                    noImgZoomIn\n                  />\n                  <template v-else> -</template>\n                </el-scrollbar>\n              </div>\n            </div>\n          </template>\n\n          <!-- 文档内容提取 -->\n          <template v-if=\"data.type === WorkflowType.DocumentExtractNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12 flex align-center\">\n                <span class=\"mr-4\"> {{ $t('common.param.outputParam') }}</span>\n\n                <el-tooltip\n                  effect=\"dark\"\n                  :content=\"$t('chat.executionDetails.paramOutputTooltip')\"\n                  placement=\"right\"\n                >\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                </el-tooltip>\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <el-scrollbar height=\"200\">\n                  <el-card\n                    shadow=\"never\"\n                    style=\"--el-card-padding: 8px\"\n                    v-for=\"(file_content, index) in data.content\"\n                    :key=\"index\"\n                    class=\"mb-8\"\n                  >\n                    <MdPreview\n                      v-if=\"file_content\"\n                      ref=\"editorRef\"\n                      editorId=\"preview-only\"\n                      :modelValue=\"file_content\"\n                      style=\"background: none\"\n                      noImgZoomIn\n                    />\n\n                    <template v-else> -</template>\n                  </el-card>\n                </el-scrollbar>\n              </div>\n            </div>\n          </template>\n          <template v-if=\"data.type === WorkflowType.SpeechToTextNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.inputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div class=\"mb-8\">\n                  <div v-if=\"data.audio_list?.length > 0\">\n                    <p class=\"mb-8 color-secondary\">{{ $t('chat.executionDetails.audioFile') }}:</p>\n\n                    <el-space wrap>\n                      <template v-for=\"(f, i) in data.audio_list\" :key=\"i\">\n                        <audio\n                          :src=\"f.url\"\n                          controls\n                          style=\"width: 300px; height: 43px\"\n                          class=\"border-r-6\"\n                        />\n                      </template>\n                    </el-space>\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <el-card\n                  shadow=\"never\"\n                  style=\"--el-card-padding: 8px\"\n                  v-for=\"(file_content, index) in data.content\"\n                  :key=\"index\"\n                  class=\"mb-8\"\n                >\n                  <MdPreview\n                    v-if=\"file_content\"\n                    ref=\"editorRef\"\n                    editorId=\"preview-only\"\n                    :modelValue=\"file_content\"\n                    style=\"background: none\"\n                    noImgZoomIn\n                  />\n                  <template v-else> -</template>\n                </el-card>\n              </div>\n            </div>\n          </template>\n\n          <template v-if=\"data.type === WorkflowType.TextToSpeechNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.inputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div class=\"p-8-12 border-t-dashed lighter\">\n                  <p class=\"mb-8 color-secondary\">{{ $t('chat.executionDetails.textContent') }}:</p>\n                  <div v-if=\"data.content\">\n                    <MdPreview\n                      ref=\"editorRef\"\n                      editorId=\"preview-only\"\n                      :modelValue=\"data.content\"\n                      style=\"background: none\"\n                      noImgZoomIn\n                    />\n                  </div>\n                </div>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <p class=\"mb-8 color-secondary\">{{ $t('chat.executionDetails.audioFile') }}:</p>\n                <div v-if=\"data.answer\" v-html=\"data.answer\"></div>\n              </div>\n            </div>\n          </template>\n\n          <!-- 工具库 -->\n          <template\n            v-if=\"data.type === WorkflowType.ToolLib || data.type === WorkflowType.ToolLibCustom\"\n          >\n            <div class=\"card-never border-r-6 mt-8\" v-if=\"data.index != 0\">\n              <h5 class=\"p-8-12\">{{ $t('chat.executionDetails.input') }}</h5>\n              <div class=\"p-8-12 border-t-dashed lighter break-all\">\n                {{ data.params || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">{{ $t('chat.executionDetails.output') }}</h5>\n              <div class=\"p-8-12 border-t-dashed lighter break-all\">\n                {{ data.result || '-' }}\n              </div>\n            </div>\n          </template>\n          <!-- 多路召回 -->\n          <template v-if=\"data.type == WorkflowType.RerankerNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.searchContent') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">{{ data.question || '-' }}</div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.rerankerContent') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <template v-if=\"data.document_list?.length > 0\">\n                  <template\n                    v-for=\"(paragraph, paragraphIndex) in data.document_list\"\n                    :key=\"paragraphIndex\"\n                  >\n                    <ParagraphCard\n                      :data=\"paragraph.metadata\"\n                      :content=\"paragraph.page_content\"\n                      :index=\"paragraphIndex\"\n                    />\n                  </template>\n                </template>\n                <template v-else> -</template>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.rerankerResult') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <template v-if=\"data.result_list?.length > 0\">\n                  <template\n                    v-for=\"(paragraph, paragraphIndex) in data.result_list\"\n                    :key=\"paragraphIndex\"\n                  >\n                    <ParagraphCard\n                      :data=\"paragraph.metadata\"\n                      :content=\"paragraph.page_content\"\n                      :index=\"paragraphIndex\"\n                      :score=\"paragraph.metadata?.relevance_score\"\n                    />\n                  </template>\n                </template>\n                <template v-else> -</template>\n              </div>\n            </div>\n          </template>\n\n          <!-- 表单收集 -->\n          <template v-if=\"data.type === WorkflowType.FormNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam')\n                }}<span style=\"color: #f54a45\">{{\n                  data.is_submit ? '' : `(${$t('chat.executionDetails.noSubmit')})`\n                }}</span>\n              </h5>\n\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <DynamicsForm\n                  :disabled=\"true\"\n                  label-position=\"top\"\n                  require-asterisk-position=\"right\"\n                  ref=\"dynamicsFormRef\"\n                  :render_data=\"data.form_field_list\"\n                  label-suffix=\":\"\n                  v-model=\"data.form_data\"\n                  :model=\"data.form_data\"\n                ></DynamicsForm>\n              </div>\n            </div>\n          </template>\n          <!-- 图片理解 -->\n          <template v-if=\"data.type == WorkflowType.ImageUnderstandNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('views.application.form.roleSettings.label') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                {{ data.system || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\" v-if=\"!isKnowLedge\">\n              <h5 class=\"p-8-12\">{{ $t('chat.history') }}</h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <template v-if=\"data.history_message?.length > 0\">\n                  <p\n                    class=\"mt-4 mb-4\"\n                    v-for=\"(history, historyIndex) in data.history_message\"\n                    :key=\"historyIndex\"\n                  >\n                    <span class=\"color-secondary mr-4\">{{ history.role }}:</span>\n\n                    <span v-if=\"Array.isArray(history.content)\">\n                      <template v-for=\"(h, i) in history.content\" :key=\"i\">\n                        <el-image\n                          v-if=\"h.type === 'image_url'\"\n                          :src=\"h.image_url.url\"\n                          alt=\"\"\n                          fit=\"cover\"\n                          style=\"width: 40px; height: 40px; display: inline-block\"\n                          class=\"border-r-6 mr-8\"\n                        />\n\n                        <span v-else>{{ h.text }}<br /></span>\n                      </template>\n                    </span>\n\n                    <span v-else>{{ history.content }}</span>\n                  </p>\n                </template>\n                <template v-else> -</template>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{\n                  isKnowLedge\n                    ? $t('views.application.form.prompt.label')\n                    : $t('chat.executionDetails.currentChat')\n                }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                <div v-if=\"data.image_list?.length > 0\">\n                  <el-space wrap>\n                    <template v-for=\"(f, i) in data.image_list\" :key=\"i\">\n                      <el-image\n                        :src=\"f.url || f.file_id ? `./oss/file/${f.file_id}` : ''\"\n                        alt=\"\"\n                        fit=\"cover\"\n                        style=\"width: 40px; height: 40px; display: block\"\n                        class=\"border-r-6\"\n                      />\n                    </template>\n                  </el-space>\n                </div>\n                <div>\n                  {{ data.question || '-' }}\n                </div>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.answer') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <MdPreview\n                  v-if=\"data.answer\"\n                  ref=\"editorRef\"\n                  editorId=\"preview-only\"\n                  :modelValue=\"data.answer\"\n                  style=\"background: none\"\n                  noImgZoomIn\n                />\n                <template v-else> -</template>\n              </div>\n            </div>\n          </template>\n          <!-- 视频理解 -->\n          <template v-if=\"data.type == WorkflowType.VideoUnderstandNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('views.application.form.roleSettings.label') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                {{ data.system || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\" v-if=\"!isKnowLedge\">\n              <h5 class=\"p-8-12\">{{ $t('chat.history') }}</h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <template v-if=\"data.history_message?.length > 0\">\n                  <p\n                    class=\"mt-4 mb-4\"\n                    v-for=\"(history, historyIndex) in data.history_message\"\n                    :key=\"historyIndex\"\n                  >\n                    <span class=\"color-secondary mr-4\">{{ history.role }}:</span>\n\n                    <span v-if=\"Array.isArray(history.content)\">\n                      <template v-for=\"(h, i) in history.content\" :key=\"i\">\n                        <video\n                          v-if=\"h.type === 'video_url'\"\n                          :src=\"h.video_url.url\"\n                          style=\"width: 40px; height: 40px; display: inline-block\"\n                          class=\"border-r-6 mr-8\"\n                        />\n\n                        <span v-else>{{ h.text }}<br /></span>\n                      </template>\n                    </span>\n\n                    <span v-else>{{ history.content }}</span>\n                  </p>\n                </template>\n                <template v-else> -</template>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{\n                  isKnowLedge\n                    ? $t('views.application.form.prompt.label')\n                    : $t('chat.executionDetails.currentChat')\n                }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                <div v-if=\"data.video_list?.length > 0\">\n                  <el-space wrap>\n                    <template v-for=\"(f, i) in data.video_list\" :key=\"i\">\n                      <video\n                        :src=\"f.url\"\n                        style=\"width: 100px; display: block\"\n                        class=\"border-r-6\"\n                        autoplay\n                        controls\n                      />\n                    </template>\n                  </el-space>\n                </div>\n                <div>\n                  {{ data.question || '-' }}\n                </div>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.answer') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <MdPreview\n                  v-if=\"data.answer\"\n                  ref=\"editorRef\"\n                  editorId=\"preview-only\"\n                  :modelValue=\"data.answer\"\n                  style=\"background: none\"\n                  noImgZoomIn\n                />\n                <template v-else> -</template>\n              </div>\n            </div>\n          </template>\n          <!-- 图片生成 -->\n          <template v-if=\"data.type == WorkflowType.ImageGenerateNode\">\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.currentChat') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{ data.question || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('workflow.nodes.imageGenerateNode.negative_prompt.label') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{ data.negative_prompt || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.answer') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <MdPreview\n                  v-if=\"data.answer\"\n                  ref=\"editorRef\"\n                  editorId=\"preview-only\"\n                  :modelValue=\"data.answer\"\n                  style=\"background: none\"\n                  noImgZoomIn\n                />\n                <template v-else> -</template>\n              </div>\n            </div>\n          </template>\n          <template v-if=\"data.type == WorkflowType.TextToVideoGenerateNode\">\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.currentChat') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{ data.question || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('workflow.nodes.imageGenerateNode.negative_prompt.label') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{ data.negative_prompt || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.answer') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <MdPreview\n                  v-if=\"data.answer\"\n                  ref=\"editorRef\"\n                  editorId=\"preview-only\"\n                  :modelValue=\"data.answer\"\n                  style=\"background: none\"\n                  noImgZoomIn\n                />\n                <template v-else> -</template>\n              </div>\n            </div>\n          </template>\n\n          <template v-if=\"data.type == WorkflowType.ImageToVideoGenerateNode\">\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.currentChat') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{ data.question || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('workflow.nodes.imageGenerateNode.negative_prompt.label') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{ data.negative_prompt || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('workflow.nodes.imageToVideoGenerate.first_frame.label') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                <div v-if=\"typeof data.first_frame_url === 'string'\">\n                  <el-image\n                    :src=\"data.first_frame_url\"\n                    alt=\"\"\n                    fit=\"cover\"\n                    style=\"width: 40px; height: 40px; display: block\"\n                    class=\"border-r-6\"\n                  />\n                </div>\n                <div v-else-if=\"Array.isArray(data.first_frame_url)\">\n                  <el-space wrap>\n                    <template v-for=\"(f, i) in data.first_frame_url\" :key=\"i\">\n                      <el-image\n                        :src=\"f.url\"\n                        alt=\"\"\n                        fit=\"cover\"\n                        style=\"width: 40px; height: 40px; display: block\"\n                        class=\"border-r-6\"\n                      />\n                    </template>\n                  </el-space>\n                </div>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('workflow.nodes.imageToVideoGenerate.last_frame.label') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                <div v-if=\"typeof data.last_frame_url === 'string'\">\n                  <el-image\n                    :src=\"data.last_frame_url\"\n                    alt=\"\"\n                    fit=\"cover\"\n                    style=\"width: 40px; height: 40px; display: block\"\n                    class=\"border-r-6\"\n                  />\n                </div>\n                <div v-else-if=\"Array.isArray(data.last_frame_url)\">\n                  <el-space wrap>\n                    <template v-for=\"(f, i) in data.last_frame_url\" :key=\"i\">\n                      <el-image\n                        :src=\"f.url\"\n                        alt=\"\"\n                        fit=\"cover\"\n                        style=\"width: 40px; height: 40px; display: block\"\n                        class=\"border-r-6\"\n                      />\n                    </template>\n                  </el-space>\n                </div>\n                <div v-else>-</div>\n              </div>\n            </div>\n\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.answer') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <MdPreview\n                  v-if=\"data.answer\"\n                  ref=\"editorRef\"\n                  editorId=\"preview-only\"\n                  :modelValue=\"data.answer\"\n                  style=\"background: none\"\n                  noImgZoomIn\n                />\n                <template v-else> -</template>\n              </div>\n            </div>\n          </template>\n          <!-- 变量赋值 -->\n          <template v-if=\"data.type === WorkflowType.VariableAssignNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.inputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div v-for=\"(f, i) in data.result_list\" :key=\"i\" class=\"mb-8\">\n                  <span class=\"color-secondary\">{{ f.name }}:</span> {{ f.input_value }}\n                </div>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div v-for=\"(f, i) in data.result_list\" :key=\"i\" class=\"mb-8\">\n                  <span class=\"color-secondary\">{{ f.name }}:</span> {{ f.output_value }}\n                </div>\n              </div>\n            </div>\n          </template>\n\n          <!-- 变量拆分 -->\n          <template\n            v-if=\"\n              data.type === WorkflowType.VariableSplittingNode ||\n              data.type == WorkflowType.ParameterExtractionNode\n            \"\n          >\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.inputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{ data.request || '-' }}\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div v-for=\"(f, i) in data.result\" :key=\"i\" class=\"mb-8\">\n                  <span class=\"color-secondary\">{{ i }}:</span> {{ f }}\n                </div>\n              </div>\n            </div>\n          </template>\n          <!-- 变量聚合 -->\n          <template v-if=\"data.type === WorkflowType.VariableAggregationNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('workflow.nodes.variableAggregationNode.Strategy') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter pre-wrap\">\n                {{\n                  data.strategy === 'variable_to_json'\n                    ? t('workflow.nodes.variableAggregationNode.placeholder1')\n                    : t('workflow.nodes.variableAggregationNode.placeholder')\n                }}\n              </div>\n            </div>\n            <div\n              class=\"card-never border-r-6 mt-8\"\n              v-for=\"(group, groupI) in data.group_list\"\n              :key=\"groupI\"\n            >\n              <h5 class=\"p-8-12\">\n                {{ group.label + ' ' + $t('common.param.inputParam') }}\n              </h5>\n              <el-scrollbar height=\"200\">\n                <div class=\"p-8-12 border-t-dashed lighter\">\n                  <div v-for=\"(f, i) in group.variable_list\" :key=\"i\" class=\"mb-8\">\n                    <span class=\"color-secondary\">{{ `${f.node_name}.${f.field}` }}:</span>\n                    {{ f.value }}\n                  </div>\n                </div>\n              </el-scrollbar>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n              <el-scrollbar height=\"200\">\n                <div class=\"p-8-12 border-t-dashed lighter\">\n                  <div v-for=\"(f, i) in data.result\" :key=\"i\" class=\"mb-8\">\n                    <span class=\"color-secondary\">{{ i }}:</span> {{ f }}\n                  </div>\n                </div>\n              </el-scrollbar>\n            </div>\n          </template>\n          <!-- MCP 节点 -->\n          <template v-if=\"data.type === WorkflowType.McpNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('views.tool.title') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\"> {{ $t('views.tool.title') }}: </span>\n                  {{ data.mcp_tool }}\n                </div>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('workflow.nodes.mcpNode.toolParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div v-for=\"(value, name) in data.tool_params\" :key=\"name\" class=\"mb-8\">\n                  <span class=\"color-secondary\">{{ name }}:</span> {{ value }}\n                </div>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter break-all\">\n                <div v-for=\"(f, i) in data.result\" :key=\"i\" class=\"mb-8\">\n                  <span class=\"color-secondary\">result:</span> {{ f }}\n                </div>\n              </div>\n            </div>\n          </template>\n          <!-- 循环 节点 -->\n          <div class=\"card-never border-r-6\" v-if=\"data.type === WorkflowType.LoopNode\">\n            <h5 class=\"p-8-12\">\n              {{ $t('workflow.nodes.loopNode.loopSetting') }}\n            </h5>\n\n            <div class=\"p-8-12 border-t-dashed lighter\">\n              <div class=\"mb-8\">\n                <span class=\"color-secondary\">\n                  {{ $t('workflow.nodes.loopNode.loopType.label') }}:</span\n                >\n                {{ data.loop_type || '-' }}\n              </div>\n              <div>\n                <span class=\"color-secondary\">\n                  {{ $t('workflow.nodes.loopNode.loopArray.label') }}:</span\n                >\n                {{\n                  data.loop_type === 'NUMBER'\n                    ? data.number\n                    : Object.keys(data.loop_node_data) || '-'\n                }}\n              </div>\n            </div>\n            <h5 class=\"p-8-12\">\n              {{ $t('workflow.nodes.loopNode.loopDetail') }}\n            </h5>\n            <div class=\"p-8-12 border-t-dashed lighter\">\n              <template v-if=\"data.type === WorkflowType.LoopNode\">\n                <el-radio-group v-model=\"currentLoopNode\" class=\"app-radio-button-group mb-8\">\n                  <template v-for=\"(loop, loopIndex) in data.loop_node_data\" :key=\"loopIndex\">\n                    <el-radio-button :label=\"loopIndex\" :value=\"loopIndex\" />\n                  </template>\n                </el-radio-group>\n                <template\n                  v-for=\"(cLoop, cIndex) in Object.values(\n                    data.loop_node_data?.[currentLoopNode] || [],\n                  ).sort((x: any, y: any) => (x.index || 0) - (y.index || 0))\"\n                  :key=\"cIndex\"\n                >\n                  <ExecutionDetailCard :data=\"cLoop\" :type=\"type\"></ExecutionDetailCard>\n                </template>\n              </template>\n            </div>\n          </div>\n          <!-- 循环开始 节点-->\n          <template v-if=\"data.type === WorkflowType.LoopStartNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.inputParam') }}\n              </h5>\n\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\">\n                    {{ $t('workflow.nodes.loopStartNode.loopItem') }}:</span\n                  >\n\n                  {{ data.current_item }}\n                </div>\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\">\n                    {{ $t('workflow.nodes.loopStartNode.loopIndex') }}:</span\n                  >\n\n                  {{ data.current_index }}\n                </div>\n              </div>\n            </div>\n          </template>\n          <!-- 循环跳过 节点-->\n          <template v-if=\"data.type === WorkflowType.LoopContinueNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\">\n                    {{ $t('workflow.nodes.loopContinueNode.isContinue') }}:</span\n                  >\n\n                  {{ data.is_continue }}\n                </div>\n              </div>\n            </div>\n          </template>\n          <!-- 循环退出 节点-->\n          <template v-if=\"data.type === WorkflowType.LoopBreakNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\">\n                    {{ $t('workflow.nodes.loopBreakNode.isBreak') }}:</span\n                  >\n\n                  {{ data.is_break }}\n                </div>\n              </div>\n            </div>\n          </template>\n          <!-- 文档检索 -->\n          <template v-if=\"data.type === WorkflowType.SearchDocument\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12 flex align-center\">\n                <span class=\"mr-4\"> {{ $t('common.param.outputParam') }}</span>\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\"> knowledge_list:</span>\n                  {{ data.knowledge_items?.map((v: any) => v.name).join(',') }}\n                </div>\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\"> document_list:</span>\n                  {{ data.document_items?.map((v: any) => v.name).join(',') }}\n                </div>\n              </div>\n            </div>\n          </template>\n          <!-- 文本文件 -->\n          <template v-if=\"data.type === WorkflowType.DataSourceLocalNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div class=\"mb-8\">\n                  {{ data.file_list || '-' }}\n                </div>\n              </div>\n            </div>\n          </template>\n          <!-- 文档分段 -->\n          <template v-if=\"data.type === WorkflowType.DocumentSplitNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.inputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\"\n                    >{{ $t('chat.executionDetails.paragraphRules') }}:</span\n                  >\n                  {{ data.split_strategy }}\n                </div>\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\"\n                    >{{ $t('workflow.nodes.documentSplitNode.chunk_length.label') }}:</span\n                  >\n                  {{ data.chunk_size }}\n                </div>\n                {{ data.size }}\n                <div class=\"mb-8\">\n                  <span class=\"color-secondary\">{{ $t('common.inputContent') }}:</span>\n                  {{ data.document_list?.map((v: any) => v.name).join(',') }}\n                </div>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}（{{\n                  $t('chat.executionDetails.documentSplitTip')\n                }}）\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <el-tabs v-model=\"currentParagraph\" class=\"paragraph-tabs\">\n                  <template v-for=\"(item, index) in data.paragraph_list\" :key=\"index\">\n                    <el-tab-pane :label=\"item.name\" :name=\"index\">\n                      <template #label>\n                        <div class=\"flex-center\">\n                          <span class=\"ml-4\">{{ item?.name }}</span>\n                        </div>\n                      </template>\n\n                      <template v-for=\"(paragraph, pId) in item?.paragraphs\" :key=\"pId\">\n                        <ParagraphCard :data=\"paragraph\" :content=\"paragraph.content\" :index=\"pId\">\n                          <template #footer>\n                            <span class=\"color-secondary\">\n                              {{ $t('common.character') }}：{{ paragraph.content.length }}</span\n                            >\n                          </template>\n                        </ParagraphCard>\n                      </template>\n                    </el-tab-pane>\n                  </template>\n                </el-tabs>\n              </div>\n            </div>\n          </template>\n          <!-- 知识库写入 -->\n          <template v-if=\"data.type === WorkflowType.KnowledgeWriteNode\">\n            <div class=\"card-never border-r-6 mt-8\">\n              <h5 class=\"p-8-12\">\n                {{ $t('chat.executionDetails.writeContent') }}（{{\n                  $t('chat.executionDetails.documentSplitTip')\n                }}）\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <el-tabs v-model=\"currentWriteContent\" class=\"paragraph-tabs\">\n                  <template v-for=\"(item, index) in data.write_content\" :key=\"index\">\n                    <el-tab-pane :label=\"item.name\" :name=\"index\">\n                      <template #label>\n                        <div class=\"flex-center\">\n                          <span class=\"ml-4\">{{ item?.name }}</span>\n                        </div>\n                      </template>\n\n                      <template v-for=\"(paragraph, pId) in item?.paragraphs\" :key=\"pId\">\n                        <ParagraphCard :data=\"paragraph\" :content=\"paragraph.content\" :index=\"pId\">\n                          <template #footer>\n                            <span class=\"color-secondary\">\n                              {{ $t('common.character') }}：{{ paragraph.content.length }}</span\n                            >\n                          </template>\n                        </ParagraphCard>\n                      </template>\n                    </el-tab-pane>\n                  </template>\n                </el-tabs>\n              </div>\n            </div>\n          </template>\n          <!-- Web站点 -->\n          <template v-if=\"data.type === WorkflowType.DataSourceWebNode\">\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.inputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <p class=\"mb-8 color-secondary\">\n                  {{ $t('views.document.form.selector.label') }}: {{ data.input_params.selector }}\n                </p>\n                <p class=\"mb-8 color-secondary\">\n                  {{ $t('views.document.form.source_url.label') }}:\n                  {{ data.input_params.source_url }}\n                </p>\n              </div>\n            </div>\n            <div class=\"card-never border-r-6\">\n              <h5 class=\"p-8-12\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n              <div class=\"p-8-12 border-t-dashed lighter\">\n                <el-scrollbar height=\"200\">\n                  <el-card\n                    shadow=\"never\"\n                    style=\"--el-card-padding: 8px\"\n                    v-for=\"(file_content, index) in data.output_params\"\n                    :key=\"index\"\n                    class=\"mb-8\"\n                  >\n                    <h4>{{ file_content.name }}</h4>\n                    <MdPreview\n                      v-if=\"file_content\"\n                      ref=\"editorRef\"\n                      editorId=\"preview-only\"\n                      :modelValue=\"file_content.content\"\n                      style=\"background: none\"\n                      noImgZoomIn\n                    />\n                    <template v-else> -</template>\n                  </el-card>\n                </el-scrollbar>\n              </div>\n            </div>\n          </template>\n          <slot></slot>\n        </template>\n        <template v-else>\n          <div class=\"card-never border-r-6\">\n            <h5 class=\"p-8-12\">{{ $t('chat.executionDetails.errMessage') }}</h5>\n            <div class=\"p-8-12 border-t-dashed lighter\">{{ data.err_message || '-' }}</div>\n          </div>\n        </template>\n      </div>\n    </el-collapse-transition>\n  </el-card>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, type PropType } from 'vue'\nimport ParagraphCard from '@/components/ai-chat/component/knowledge-source-component/ParagraphCard.vue'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport { iconComponent } from '@/workflow/icons/utils'\nimport { WorkflowType } from '@/enums/application'\nimport { getImgUrl } from '@/utils/common'\nimport { arraySort } from '@/utils/array'\nimport ExecutionDetailCard from '@/components/execution-detail-card/index.vue'\nimport MdRenderer from '@/components/markdown/MdRenderer.vue'\nimport { t } from '@/locales'\n\nconst props = defineProps({\n  data: {\n    type: Object as PropType<any>,\n    default: null,\n  },\n  type: {\n    type: String as PropType<'application' | 'knowledge'>,\n    default: 'application',\n  },\n})\nconst isKnowLedge = computed(() => props.type === 'knowledge')\nconst currentLoopNode = ref(0)\nconst currentParagraph = ref(0)\nconst currentWriteContent = ref(0)\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/folder-breadcrumb/index.vue",
    "content": "<template>\n  <h2 v-if=\"breadcrumbData?.length === 1\" class=\"ellipsis\" :title=\"breadcrumbData[0]?.name\">\n    {{ i18n_name(breadcrumbData[0]?.name) }}\n  </h2>\n  <el-breadcrumb separator-icon=\"ArrowRight\" style=\"line-height: normal\" class=\"mt-4\" v-else>\n    <template v-if=\"breadcrumbData?.length > 3\">\n      <el-breadcrumb-item>\n        <el-button link @click=\"handleClick(breadcrumbData[0])\" :title=\"breadcrumbData[0].name\">\n          <span class=\"ellipsis\"> {{ breadcrumbData[0].name }}</span>\n        </el-button>\n      </el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <el-button link @click=\"handleClick(breadcrumbData[breadcrumbData.length - 2])\">\n          <el-icon><MoreFilled /></el-icon>\n        </el-button>\n      </el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <h5 class=\"ml-4 ellipsis\" :title=\"breadcrumbData[breadcrumbData.length - 1].name\">\n          {{ breadcrumbData[breadcrumbData.length - 1].name }}\n        </h5>\n      </el-breadcrumb-item>\n    </template>\n    <template v-else>\n      <el-breadcrumb-item v-for=\"(item, index) in breadcrumbData\" :key=\"index\">\n        <h5 class=\"ml-4 ellipsis\" v-if=\"index === breadcrumbData.length - 1\" :title=\"item.name\">\n          {{ item.name }}\n        </h5>\n\n        <el-button v-else link @click=\"handleClick(item)\" :title=\"item.name\">\n          <span class=\"ellipsis\"> {{ item.name }}</span>\n        </el-button>\n      </el-breadcrumb-item>\n    </template>\n  </el-breadcrumb>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { TreeToFlatten } from '@/utils/array'\ndefineOptions({ name: 'FolderBreadcrumb' })\nimport useStore from '@/stores'\nimport {i18n_name} from \"@/utils/common.ts\";\nconst { folder, user } = useStore()\n\nconst props = defineProps({\n  folderList: {\n    type: Array,\n    default: () => [],\n  },\n})\n\nconst breadcrumbData = computed(() => {\n  return folder.currentFolder?.id && getBreadcrumbData()\n})\n\nconst emit = defineEmits(['click'])\n\nfunction getBreadcrumbData() {\n  const targetId = folder.currentFolder?.id\n  const list = TreeToFlatten(props.folderList)\n  if (!folder.currentFolder) return [] // 如果没有 id，返回空数组\n  const breadcrumbList: any[] = []\n  let currentId: string | null = targetId\n  while (currentId) {\n    const currentNode = list.find((item: any) => item.id === currentId)\n    if (!currentNode) break // 如果找不到节点，终止循环\n    breadcrumbList.unshift(currentNode) // 添加到面包屑\n    currentId = currentNode.parent_id // 继续查找父级\n  }\n  return breadcrumbList\n}\n\nfunction handleClick(item: any) {\n  emit('click', item)\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/folder-tree/CreateFolderDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"title\"\n    v-model=\"dialogVisible\"\n    width=\"720\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form\n      ref=\"FolderFormRef\"\n      :rules=\"rules\"\n      :model=\"folderForm\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-form-item :label=\"$t('common.name')\" prop=\"name\">\n        <el-input\n          v-model=\"folderForm.name\"\n          :placeholder=\"$t('components.folder.folderNamePlaceholder')\"\n          maxlength=\"64\"\n          show-word-limit\n          @blur=\"folderForm.name = folderForm.name.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('common.desc')\" prop=\"desc\">\n        <el-input\n          v-model=\"folderForm.desc\"\n          type=\"textarea\"\n          :placeholder=\"$t('common.descPlaceholder')\"\n          maxlength=\"128\"\n          show-word-limit\n          :autosize=\"{ minRows: 3 }\"\n          @blur=\"folderForm.desc = folderForm.desc.trim()\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\" :loading=\"loading\">\n          {{ $t('common.cancel') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle\" :disabled=\"loading\" :loading=\"loading\">\n          {{ isEdit ? $t('common.confirm') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive } from 'vue'\nimport folderApi from '@/api/workspace/folder'\nimport { MsgSuccess, MsgAlert } from '@/utils/message'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nconst { user, tool, knowledge, folder } = useStore()\nconst emit = defineEmits(['refresh'])\n\nconst props = defineProps({\n  title: {\n    type: String,\n    default: t('components.folder.addFolder'),\n  },\n})\n\nconst FolderFormRef = ref()\n\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\nconst sourceType = ref<any>('')\nconst isEdit = ref<boolean>(false)\nconst editId = ref<string>('')\n\nconst folderForm = ref<any>({\n  name: '',\n  desc: '',\n  parent_id: '',\n})\n\nconst rules = reactive({\n  name: [\n    {\n      required: true,\n      message: t('components.folder.folderNamePlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    sourceType.value = ''\n    folderForm.value = {\n      name: '',\n      desc: '',\n      parent_id: '',\n    }\n    isEdit.value = false\n    FolderFormRef.value.resetFields()\n  }\n})\n\nconst open = (source: string, id: string, data?: any) => {\n  sourceType.value = source\n  if (data) {\n    //  编辑当前id\n    editId.value = data.id\n    folderForm.value.name = data.name\n    folderForm.value.desc = data.desc\n    folderForm.value.parent_id = data.parent_id\n    isEdit.value = true\n  } else {\n    //  给当前id添加子id\n    folderForm.value.parent_id = id\n  }\n  dialogVisible.value = true\n}\n\nconst submitHandle = async () => {\n  await FolderFormRef.value.validate((valid: any) => {\n    if (valid) {\n      if (isEdit.value) {\n        folderApi\n          .putFolder(editId.value, sourceType.value, folderForm.value, loading)\n          .then((res) => {\n            MsgSuccess(t('common.editSuccess'))\n            emit('refresh')\n            dialogVisible.value = false\n          })\n      } else {\n        folderApi.postFolder(sourceType.value, folderForm.value, loading)\n          .then((res) => {\n            return user.profile().then(() => {\n              return res\n            })\n          })\n          .then((res) => {\n          MsgSuccess(t('common.createSuccess'))\n          folder.setCurrentFolder(res.data)\n          folder.asyncGetFolder(sourceType.value, {}, 'workspace',loading)\n          clearData()\n          emit('refresh')\n          dialogVisible.value = false\n        })\n      }\n    }\n  })\n}\n\nfunction clearData() {\n  tool.setToolList([])\n  knowledge.setKnowledgeList([])\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/folder-tree/MoveToDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('common.moveTo')\"\n    v-model=\"dialogVisible\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    align-center\n  >\n    <folder-tree\n      ref=\"treeRef\"\n      :source=\"source\"\n      :data=\"folderList\"\n      :default-expanded-keys=\"[currentNodeKey]\"\n      :canOperation=\"false\"\n      class=\"move-to-dialog-tree\"\n      @handleNodeClick=\"folderClickHandle\"\n    />\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\" :loading=\"loading\">\n          {{ $t('common.cancel') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle\" :loading=\"loading\">\n          {{ $t('common.confirm') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive } from 'vue'\nimport folderApi from '@/api/workspace/folder'\nimport { MsgError, MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nimport { SourceTypeEnum } from '@/enums/common'\nimport KnowledgeApi from '@/api/knowledge/knowledge'\nimport ApplicationApi from '@/api/application/application'\nimport ToolApi from '@/api/tool/tool'\nconst { folder } = useStore()\nconst emit = defineEmits(['refresh'])\n\nconst props = defineProps({\n  source: {\n    type: String,\n    default: '',\n  },\n})\n\nconst treeRef = ref()\nconst loading = ref(false)\nconst dialogVisible = ref(false)\nconst folderList = ref<any[]>([])\nconst detail = ref<any>({})\nconst selectForderId = ref<any>('')\nconst currentNodeKey = ref<string>('')\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    detail.value = {}\n    selectForderId.value = ''\n    folderList.value = []\n    currentNodeKey.value = ''\n    treeRef.value?.clearCurrentKey()\n  }\n})\n\nconst isFolder = ref<boolean>(false)\n\nconst open = (data: any, is_folder?: any) => {\n  detail.value = data\n  isFolder.value = is_folder\n  getFolder()\n  dialogVisible.value = true\n}\n\nfunction getFolder() {\n  const params = {}\n  folder.asyncGetFolder(props.source, params, 'workspace', loading).then((res: any) => {\n    folderList.value = res.data\n    if (folderList.value?.length > 0) {\n      currentNodeKey.value = folderList.value[0]?.id\n    } else {\n      currentNodeKey.value = ''\n    }\n  })\n}\n\nfunction folderClickHandle(item: any) {\n  selectForderId.value = item.id\n}\n\nconst submitHandle = async () => {\n  if (selectForderId.value) {\n    const obj = {\n      ...detail.value,\n      folder_id: selectForderId.value,\n    }\n    if (isFolder.value) {\n      const folder_obj = {\n        ...detail.value,\n        parent_id: selectForderId.value,\n      }\n      folderApi\n        .putFolder(detail.value.id, detail.value.folder_type, folder_obj, loading)\n        .then(() => {\n          MsgSuccess(t('common.saveSuccess'))\n          emit('refresh')\n          dialogVisible.value = false\n        })\n    } else if (props.source === SourceTypeEnum.KNOWLEDGE) {\n      if (detail.value.type === 2) {\n        KnowledgeApi.putLarkKnowledge(detail.value.id, obj, loading).then(() => {\n          MsgSuccess(t('common.saveSuccess'))\n          emit('refresh', detail.value)\n          dialogVisible.value = false\n        })\n      } else {\n        KnowledgeApi.putKnowledge(detail.value.id, obj, loading).then(() => {\n          MsgSuccess(t('common.saveSuccess'))\n          emit('refresh', detail.value)\n          dialogVisible.value = false\n        })\n      }\n    } else if (props.source === SourceTypeEnum.TOOL) {\n      ToolApi.putTool(detail.value.id, obj, loading).then(() => {\n        MsgSuccess(t('common.saveSuccess'))\n        emit('refresh', detail.value)\n        dialogVisible.value = false\n      })\n    } else if (props.source === SourceTypeEnum.APPLICATION) {\n      ApplicationApi.moveApplication(detail.value.id, obj.folder_id, loading).then((res) => {\n        MsgSuccess(t('common.saveSuccess'))\n        emit('refresh', detail.value)\n        dialogVisible.value = false\n      })\n    }\n  } else {\n    MsgError(t('components.folder.requiredMessage'))\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped>\n.move-to-dialog-tree {\n  :deep(.el-input) {\n    padding: 0 !important;\n    margin-bottom: 8px;\n  }\n  :deep(.el-scrollbar) {\n    border: 1px solid var(--el-border-color-light);\n    border-radius: 6px;\n  }\n  :deep(.el-tree) {\n    height: calc(100vh - 320px) !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/folder-tree/constant.ts",
    "content": "import { t } from '@/locales'\n\nexport const SORT_TYPES = {\n  CREATE_TIME_ASC: 'createTime-asc',\n  CREATE_TIME_DESC: 'createTime-desc',\n  NAME_ASC: 'name-asc',\n  NAME_DESC: 'name-desc',\n  CUSTOM: 'custom',\n} as const\n\nexport type SortType = (typeof SORT_TYPES)[keyof typeof SORT_TYPES]\n\nexport const SORT_MENU_CONFIG = [\n  {\n    title: 'time',\n    items: [\n      { label: t('components.folder.ascTime'), value: SORT_TYPES.CREATE_TIME_ASC },\n      { label: t('components.folder.descTime'), value: SORT_TYPES.CREATE_TIME_DESC },\n    ],\n  },\n  {\n    title: 'name',\n    items: [\n      { label: t('components.folder.ascName'), value: SORT_TYPES.NAME_ASC },\n      { label: t('components.folder.descName'), value: SORT_TYPES.NAME_DESC },\n    ],\n  },\n  {\n    items: [{ label: t('components.folder.custom'), value: SORT_TYPES.CUSTOM }],\n  },\n]\n"
  },
  {
    "path": "ui/src/components/folder-tree/index.vue",
    "content": "<template>\n  <div class=\"folder-tree\">\n    <div class=\"flex ml-4 p-8 pb-0\">\n      <el-input\n        v-model=\"filterText\"\n        :placeholder=\"$t('common.search')\"\n        prefix-icon=\"Search\"\n        clearable\n      />\n      <el-dropdown trigger=\"click\" :teleported=\"false\" @command=\"switchSortMethod\">\n        <el-button class=\"ml-4\">\n          <AppIcon :iconName=\"sortIconName\"></AppIcon>\n        </el-button>\n        <template #dropdown>\n          <el-dropdown-menu style=\"width: 220px\">\n            <template v-for=\"(group, index) in SORT_MENU_CONFIG\" :key=\"index\">\n              <el-dropdown-item\n                v-for=\"obj in group.items\"\n                :key=\"obj.value\"\n                :command=\"obj.value\"\n                :class=\"`${currentSort === obj.value ? 'active' : ''} flex-between`\"\n              >\n                <span>\n                  {{ obj.label }}\n                </span>\n\n                <el-icon v-if=\"currentSort === obj.value\" class=\"ml-4\">\n                  <Check />\n                </el-icon>\n              </el-dropdown-item>\n              <el-divider class=\"mb-4 mt-4\" v-if=\"index < SORT_MENU_CONFIG.length - 1\"></el-divider>\n            </template>\n          </el-dropdown-menu>\n        </template>\n      </el-dropdown>\n    </div>\n    <div class=\"p-8 pb-0\" v-if=\"showShared && hasPermission(EditionConst.IS_EE, 'OR')\">\n      <div class=\"border-b\">\n        <div\n          @click=\"handleSharedNodeClick\"\n          class=\"shared-button flex cursor border-r-6\"\n          :class=\"currentNodeKey === 'share' && 'active'\"\n        >\n          <AppIcon\n            iconName=\"app-shared-active\"\n            style=\"font-size: 18px\"\n            class=\"color-primary\"\n          ></AppIcon>\n          <span class=\"ml-8\">{{ shareTitle }}</span>\n        </div>\n      </div>\n    </div>\n\n    <el-scrollbar>\n      <el-tree\n        class=\"folder-tree__main p-8\"\n        :class=\"\n          showShared && hasPermission(EditionConst.IS_EE, 'OR')\n            ? 'tree-height-shared'\n            : 'tree-height '\n        \"\n        :style=\"treeStyle\"\n        ref=\"treeRef\"\n        :data=\"sortedData\"\n        :props=\"defaultProps\"\n        @node-click=\"handleNodeClick\"\n        :filter-node-method=\"filterNode\"\n        :default-expanded-keys=\"[currentNodeKey]\"\n        :current-node-key=\"currentNodeKey\"\n        highlight-current\n        :draggable=\"draggable\"\n        :allow-drop=\"allowDrop\"\n        :allow-drag=\"allowDrag\"\n        @node-drop=\"handleDrop\"\n        node-key=\"id\"\n        v-loading=\"loading\"\n        v-bind=\"$attrs\"\n      >\n        <template #default=\"{ node, data }\">\n          <div\n            @mouseenter.stop=\"handleMouseEnter(data)\"\n            class=\"flex align-center w-full custom-tree-node\"\n          >\n            <AppIcon iconName=\"app-folder\" style=\"font-size: 20px\"></AppIcon>\n            <span class=\"tree-label ml-8\" :title=\"node.label\">{{ i18n_name(node.label) }}</span>\n\n            <div\n              v-if=\"canOperation && MoreFilledPermission(node, data)\"\n              @click.stop\n              v-show=\"hoverNodeId === data.id\"\n              @mouseenter.stop=\"handleMouseEnter(data)\"\n              @mouseleave.stop=\"handleMouseleave\"\n              class=\"mr-8 tree-operation-button\"\n            >\n              <el-dropdown trigger=\"click\" :teleported=\"false\">\n                <el-button text class=\"w-full\" v-if=\"MoreFilledPermission(node, data)\">\n                  <AppIcon iconName=\"app-more\"></AppIcon>\n                </el-button>\n                <template #dropdown>\n                  <el-dropdown-menu>\n                    <el-dropdown-item\n                      @click.stop=\"openCreateFolder(data)\"\n                      v-if=\"permissionPrecise.folderCreate(data.id)\"\n                    >\n                      <AppIcon iconName=\"app-add-folder\" class=\"color-secondary\"></AppIcon>\n                      {{ $t('components.folder.addChildFolder') }}\n                    </el-dropdown-item>\n                    <el-dropdown-item\n                      @click.stop=\"openEditFolder(data)\"\n                      v-if=\"permissionPrecise.folderEdit(data.id)\"\n                    >\n                      <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n                      {{ $t('common.edit') }}\n                    </el-dropdown-item>\n                    <el-dropdown-item\n                      @click.stop=\"openMoveToDialog(data)\"\n                      v-if=\"node.level !== 1 && permissionPrecise.folderEdit(data.id)\"\n                    >\n                      <AppIcon iconName=\"app-migrate\" class=\"color-secondary\"></AppIcon>\n                      {{ $t('common.moveTo') }}\n                    </el-dropdown-item>\n                    <el-dropdown-item\n                      @click.stop=\"openAuthorization(data)\"\n                      v-if=\"permissionPrecise.folderAuth(data.id)\"\n                    >\n                      <AppIcon\n                        iconName=\"app-resource-authorization\"\n                        class=\"color-secondary\"\n                      ></AppIcon>\n                      {{ $t('views.system.resourceAuthorization.title') }}\n                    </el-dropdown-item>\n                    <el-dropdown-item\n                      divided\n                      @click.stop=\"deleteFolder(data)\"\n                      :disabled=\"!data.parent_id\"\n                      v-if=\"permissionPrecise.folderDelete(data.id)\"\n                    >\n                      <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                      {{ $t('common.delete') }}\n                    </el-dropdown-item>\n                  </el-dropdown-menu>\n                </template>\n              </el-dropdown>\n            </div>\n          </div>\n        </template>\n      </el-tree>\n    </el-scrollbar>\n\n    <CreateFolderDialog ref=\"CreateFolderDialogRef\" @refresh=\"refreshFolder\" :title=\"title\" />\n    <MoveToDialog ref=\"MoveToDialogRef\" :source=\"props.source\" @refresh=\"emit('refreshTree')\" />\n    <ResourceAuthorizationDrawer\n      :type=\"`${props.source}_FOLDER`\"\n      :is-folder=\"true\"\n      :is-root-folder=\"!currentNode?.parent_id\"\n      ref=\"ResourceAuthorizationDrawerRef\"\n    />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, onMounted, onUnmounted, ref, watch } from 'vue'\nimport { onBeforeRouteLeave } from 'vue-router'\nimport type { TreeInstance } from 'element-plus'\nimport CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'\nimport ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'\nimport { t } from '@/locales'\nimport MoveToDialog from '@/components/folder-tree/MoveToDialog.vue'\nimport { i18n_name } from '@/utils/common'\nimport { SORT_MENU_CONFIG, SORT_TYPES, type SortType } from '@/components/folder-tree/constant'\nimport { debounce } from 'lodash-es'\nimport { encode, mid, rebalance } from '@/utils/folder'\nimport folderApi from '@/api/workspace/folder'\nimport { EditionConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport useStore from '@/stores'\nimport { TreeToFlatten } from '@/utils/array'\nimport { MsgConfirm, MsgError, MsgSuccess } from '@/utils/message'\nimport permissionMap from '@/permission'\nimport bus from '@/bus'\nconst { folder, user } = useStore()\n\ndefineOptions({ name: 'FolderTree' })\nconst props = defineProps({\n  data: {\n    type: Array,\n    default: () => [],\n  },\n  currentNodeKey: {\n    type: String,\n    default: 'default',\n  },\n  source: {\n    type: String,\n    default: 'APPLICATION',\n  },\n  showShared: {\n    type: Boolean,\n    default: false,\n  },\n  shareTitle: {\n    type: String,\n    default: '',\n  },\n  canOperation: {\n    type: Boolean,\n    default: true,\n  },\n  treeStyle: {\n    type: Object,\n    default: () => ({}),\n  },\n  draggable: {\n    type: Boolean,\n    default: false,\n  },\n})\nconst resourceType = computed(() => {\n  if (props.source === 'APPLICATION') {\n    return 'application'\n  } else if (props.source === 'KNOWLEDGE') {\n    return 'knowledge'\n  } else if (props.source === 'MODEL') {\n    return 'model'\n  } else if (props.source === 'TOOL') {\n    return 'tool'\n  } else {\n    return 'application'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap[resourceType.value!]['workspace']\n})\n\nconst MoreFilledPermission = (node: any, data: any) => {\n  return (\n    permissionPrecise.value.folderCreate(data.id) ||\n    permissionPrecise.value.folderEdit(data.id) ||\n    permissionPrecise.value.folderDelete(data.id) ||\n    permissionPrecise.value.folderAuth(data.id)\n  )\n}\n\nconst MoveToDialogRef = ref()\nfunction openMoveToDialog(data: any) {\n  const obj = {\n    id: data.id,\n    folder_type: props.source,\n  }\n  MoveToDialogRef.value.open(obj, true)\n}\n\nconst CUSTOM_STORAGE_KEY = `${user.userInfo?.id}-${user.getWorkspaceId()}-${props.source}-folder-custom-positions`\nconst FOLDER_SORT_TYPE = `${user.userInfo?.id}-${user.getWorkspaceId()}-${props.source}-folder-sort-type`\n\nconst dataWithOrder = computed(() => {\n  if (currentSort.value !== SORT_TYPES.CUSTOM || !props.data?.length) {\n    return props.data\n  }\n\n  const rootNode: any = props.data[0]\n\n  return [\n    {\n      ...rootNode,\n      children: rootNode.children ? addOrderToTree(rootNode.children, rootNode.id) : [],\n    },\n  ]\n})\n\nfunction addOrderToTree(nodes: any, parentId: string): Node[] {\n  if (!nodes || nodes.length === 0) {\n    return nodes\n  }\n\n  const positions = getPositions(parentId)\n  let needSave = false\n\n  nodes.forEach((node: any) => {\n    if (positions[node.id] === undefined) {\n      const existingPositions: any = Object.values(positions)\n      const maxPos = existingPositions.length > 0 ? Math.max(...existingPositions) : 0\n\n      positions[node.id] = maxPos + encode(1, 0)\n      needSave = true\n    }\n  })\n\n  if (needSave) {\n    savePositionsInit(parentId, positions)\n  }\n  return nodes.map((node: any) => ({\n    ...node,\n    order: positions[node.id] ?? Infinity,\n    children:\n      node.children && node.children.length > 0\n        ? addOrderToTree(node.children, node.id)\n        : node.children,\n  }))\n}\n\nconst sortIconName = computed(() => {\n  const sort = currentSort.value\n  if (sort.endsWith('asc')) {\n    return 'app-folder-asc'\n  }\n  if (sort.endsWith('desc')) {\n    return 'app-folder-desc'\n  }\n  return 'app-folder-custom'\n})\n\nconst currentSort = ref<SortType>(SORT_TYPES.CREATE_TIME_DESC)\nconst sortedData = computed(() => {\n  const treeData = dataWithOrder.value\n\n  const sortMethods = {\n    [SORT_TYPES.CREATE_TIME_ASC]: (a: any, b: any) =>\n      new Date(a.create_time).getTime() - new Date(b.create_time).getTime(),\n    [SORT_TYPES.CREATE_TIME_DESC]: (a: any, b: any) =>\n      new Date(b.create_time).getTime() - new Date(a.create_time).getTime(),\n    [SORT_TYPES.NAME_ASC]: (a: any, b: any) => a.name.localeCompare(b.name),\n    [SORT_TYPES.NAME_DESC]: (a: any, b: any) => b.name.localeCompare(a.name),\n    [SORT_TYPES.CUSTOM]: (a: any, b: any) => a.order - b.order,\n  }\n\n  const compareFn = sortMethods[currentSort.value]\n  if (!treeData || !compareFn) {\n    return treeData\n  }\n\n  return sortTreeData(treeData, compareFn)\n})\n\n// 获取指定父节点的位置数据\nfunction getPositions(parentId: string) {\n  try {\n    const data = localStorage.getItem(CUSTOM_STORAGE_KEY)\n    const allNodesData = data ? JSON.parse(data) : {}\n    return allNodesData[parentId] || {}\n  } catch (error) {\n    MsgError(error as string)\n    return {}\n  }\n}\n\nfunction doSave(parentId: string, positions: Record<string, number>) {\n  try {\n    const data = localStorage.getItem(CUSTOM_STORAGE_KEY)\n    const allNodesData = data ? JSON.parse(data) : {}\n    allNodesData[parentId] = positions\n    localStorage.setItem(CUSTOM_STORAGE_KEY, JSON.stringify(allNodesData))\n  } catch (error) {\n    MsgError(error as string)\n  }\n}\n\nconst savePositions = debounce(doSave, 300)\n\nfunction savePositionsInit(parentId: string, positions: Record<string, number>) {\n  doSave(parentId, positions)\n}\n\nfunction collectAllPositions(parentId: string, children: any[]) {\n  const allPositions: Record<string, Record<string, number>> = {}\n  if (!children || children.length === 0) {\n    return allPositions\n  }\n\n  const positions: Record<string, number> = {}\n  children.forEach((child, index) => {\n    positions[child.id] = encode(index + 1, 0)\n\n    if (child.children && child.children.length > 0) {\n      const childPositions = collectAllPositions(child.id, child.children)\n      Object.assign(allPositions, childPositions)\n    }\n  })\n  allPositions[parentId] = positions\n  return allPositions\n}\n\nfunction initAllPositions(parentId: string, children: any[]) {\n  const allPositions = collectAllPositions(parentId, children)\n  localStorage.setItem(CUSTOM_STORAGE_KEY, JSON.stringify(allPositions))\n}\n\n// 对原始数据递归排序\nfunction sortTreeData(nodes: any[], compareFn: (a: any, b: any) => number): any[] {\n  if (!compareFn || nodes.length === 0 || !nodes) {\n    return nodes\n  }\n  const sortedNodes = [...nodes].sort(compareFn)\n  return sortedNodes.map((node) => ({\n    ...node,\n    children:\n      node.children && node.children.length > 0\n        ? sortTreeData(node.children, compareFn)\n        : node.children,\n  }))\n}\n\nfunction switchSortMethod(method: SortType) {\n  currentSort.value = method\n  localStorage.setItem(FOLDER_SORT_TYPE, method)\n\n  if (method === SORT_TYPES.CUSTOM) {\n    const rootNode: any = props.data?.[0]\n    if (rootNode) {\n      const folderPositions = getPositions(rootNode.id)\n      if (Object.keys(folderPositions).length === 0) {\n        if (rootNode.children?.length > 0) {\n          initAllPositions(rootNode.id, rootNode.children)\n        }\n      }\n    }\n  }\n}\n\nconst allowDrag = (node: any) => {\n  return permissionPrecise.value.folderEdit(node.data.id)\n}\n\nconst allowDrop = (draggingNode: any, dropNode: any, type: string) => {\n  const dropData = dropNode.data\n  if (type === 'inner') {\n    return permissionPrecise.value.folderEdit(dropData.id)\n  } else if ((type === 'prev' || type === 'next') && currentSort.value === SORT_TYPES.CUSTOM) {\n    if (!dropData.parent_id) {\n      return false\n    } else {\n      return permissionPrecise.value.folderEdit(dropData.parent_id)\n    }\n  }\n  return false\n}\n\nconst handleDrop = (draggingNode: any, dropNode: any, dropType: string, ev: DragEvent) => {\n  const dragData = draggingNode.data\n  const dropData = dropNode.data\n\n  const oldParentId = dragData.parent_id\n  let newParentId: string\n  if (dropType === 'inner') {\n    newParentId = dropData.id\n  } else if (dropType === 'prev' || dropType === 'next') {\n    newParentId = dropData.parent_id\n  } else {\n    newParentId = dropData.parent_id\n  }\n\n  const isCrossNode: boolean = oldParentId !== newParentId\n\n  if (isCrossNode) {\n    const obj = {\n      ...dragData,\n      parent_id: newParentId,\n    }\n    folderApi\n      .putFolder(dragData.id, props.source, obj, loading)\n      .then(() => {\n        emit('refreshTree')\n\n        MsgSuccess(t('common.saveSuccess'))\n      })\n      .catch(() => {\n        emit('refreshTree')\n      })\n  } else {\n    // 同级拖拽，直接放置\n    sortAfterDrop(dragData, dropData, dropType, newParentId)\n  }\n}\n\nfunction sortAfterDrop(\n  draggingNodeData: any,\n  dropNodeData: any,\n  dropType: string,\n  newParentId: string,\n) {\n  const sortMethod = localStorage.getItem(FOLDER_SORT_TYPE)\n  currentSort.value = sortMethod as SortType\n\n  if (sortMethod === SORT_TYPES.CUSTOM) {\n    const positions = getPositions(newParentId)\n    let prevPos: number\n    let nextPos: number\n    if (dropType === 'inner') {\n      const childrenPositions: number[] = Object.values(positions)\n      if (childrenPositions.length === 0) {\n        positions[draggingNodeData.id] = encode(1, 0)\n        savePositions(newParentId, positions)\n        return\n      }\n      // 放到最后\n      const maxPos = Math.max(...childrenPositions)\n      positions[draggingNodeData.id] = maxPos + encode(1, 0)\n      savePositions(newParentId, positions)\n    } else if (dropType === 'before') {\n      const { dropPos, sortedNodes, dropIndex } = getSortContext(positions, dropNodeData.id)\n      const prevNode: any[] = sortedNodes[dropIndex - 1]\n\n      prevPos = prevNode ? prevNode[1] : 0\n      nextPos = dropPos\n\n      const newPos = mid(prevPos, nextPos)\n\n      if (newPos === null) {\n        // rebalance\n        rebalanceAndInsert(newParentId, draggingNodeData.id, dropNodeData.id, 'before')\n        return\n      }\n      positions[draggingNodeData.id] = newPos\n      savePositions(newParentId, positions)\n    } else if (dropType === 'after') {\n      const { dropPos, sortedNodes, dropIndex } = getSortContext(positions, dropNodeData.id)\n      const nextNode: any[] = sortedNodes[dropIndex + 1]\n\n      prevPos = dropPos\n      nextPos = nextNode ? nextNode[1] : Infinity\n\n      if (nextPos === Infinity) {\n        positions[draggingNodeData.id] = prevPos + encode(1, 0)\n      } else {\n        const newPos = mid(prevPos, nextPos)\n\n        if (newPos === null) {\n          rebalanceAndInsert(newParentId, draggingNodeData.id, dropNodeData.id, 'after')\n          return\n        }\n        positions[draggingNodeData.id] = newPos\n      }\n\n      savePositions(newParentId, positions)\n    }\n  } else {\n    emit('refreshTree')\n  }\n}\n\nfunction getSortContext(positions: Record<string, number>, nodeId: string) {\n  const dropPos = positions[nodeId]\n  const sortedNodes = Object.entries(positions).sort((a: any[], b: any[]) => a[1] - b[1])\n  const dropIndex = sortedNodes.findIndex(([id]) => id === nodeId)\n\n  return { dropPos, sortedNodes, dropIndex }\n}\n\nfunction rebalanceAndInsert(\n  parentId: string,\n  dragNodeId: string,\n  dropNodeId: string,\n  position: 'before' | 'after',\n) {\n  const positions = getPositions(parentId)\n  const sortedIds = Object.entries(positions)\n    .sort((a: any[], b: any[]) => a[1] - b[1])\n    .map(([id]) => id)\n\n  const dragIndex = sortedIds.indexOf(dragNodeId)\n  if (dragIndex > -1) {\n    sortedIds.splice(dragIndex, 1)\n  }\n  const dropIndex = sortedIds.indexOf(dropNodeId)\n  if (position === 'before') {\n    sortedIds.splice(dropIndex, 0, dragNodeId)\n  } else {\n    sortedIds.splice(dropIndex + 1, 0, dragNodeId)\n  }\n\n  const tempPositions: Record<string, number> = {}\n  sortedIds.forEach((id, index) => {\n    tempPositions[id] = index\n  })\n\n  const newPositions = rebalance(tempPositions)\n  savePositionsInit(parentId, newPositions)\n  // rebalance finish\n}\n\nonBeforeRouteLeave((to, from) => {\n  folder.setCurrentFolder({})\n})\n\nfunction loadSortPreference() {\n  const savedSort = localStorage.getItem(FOLDER_SORT_TYPE)\n  if (savedSort) {\n    currentSort.value = savedSort as SortType\n  }\n}\n\nonMounted(() => {\n  bus.on('select_node', (id: string) => {\n    treeRef.value?.setCurrentKey(id)\n    hoverNodeId.value = id\n  })\n  loadSortPreference()\n})\ninterface Tree {\n  name: string\n  children?: Tree[]\n  id?: string\n  show?: boolean\n  parent_id?: string\n}\n\nconst defaultProps = {\n  children: 'children',\n  label: 'name',\n}\n\nconst emit = defineEmits(['handleNodeClick', 'refreshTree'])\n\nconst treeRef = ref<TreeInstance>()\nconst filterText = ref('')\nconst hoverNodeId = ref<string | undefined>('')\nconst title = ref('')\nconst loading = ref(false)\n\nwatch(filterText, (val) => {\n  treeRef.value!.filter(val)\n})\nconst filterNode = (value: string, data: Tree) => {\n  if (!value) return true\n  return data.name.toLowerCase().includes(value.toLowerCase())\n}\n\nlet time: any\n\nfunction handleMouseEnter(data: Tree) {\n  clearTimeout(time)\n  hoverNodeId.value = data.id\n}\nfunction handleMouseleave() {\n  time = setTimeout(() => {\n    clearTimeout(time)\n    document.body.click()\n  }, 300)\n}\n\nconst handleNodeClick = (data: Tree) => {\n  emit('handleNodeClick', data)\n}\n\nconst handleSharedNodeClick = () => {\n  treeRef.value?.setCurrentKey(undefined)\n  emit('handleNodeClick', { id: 'share', name: props.shareTitle })\n}\n\nfunction deleteFolder(row: Tree) {\n  MsgConfirm(\n    `${t('common.deleteConfirm')}：${row.name}`,\n    t('components.folder.deleteConfirmMessage'),\n    {\n      confirmButtonText: t('common.delete'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      folderApi.delFolder(row.id as string, props.source, loading).then(() => {\n        treeRef.value?.setCurrentKey(row.parent_id || 'default')\n        const prevFolder = TreeToFlatten(props.data).find((item: any) => item.id === row.parent_id)\n        folder.setCurrentFolder(prevFolder)\n\n        if (currentSort.value === SORT_TYPES.CUSTOM) {\n          const parentId = row.parent_id || 'default'\n          const positions = getPositions(parentId)\n\n          if (positions[row.id as string] !== undefined) {\n            delete positions[row.id as string]\n            savePositionsInit(parentId, positions)\n          }\n        }\n        emit('refreshTree')\n      })\n    })\n    .catch(() => {})\n}\n\nconst CreateFolderDialogRef = ref()\nfunction openCreateFolder(row: Tree) {\n  title.value = t('components.folder.addChildFolder')\n  CreateFolderDialogRef.value.open(props.source, row.id)\n}\nfunction openEditFolder(row: Tree) {\n  title.value = t('components.folder.editFolder')\n  CreateFolderDialogRef.value.open(props.source, row.id, row)\n}\n\nconst currentNode = ref<Tree | null>(null)\nconst ResourceAuthorizationDrawerRef = ref()\nfunction openAuthorization(data: any) {\n  currentNode.value = data\n  ResourceAuthorizationDrawerRef.value.open(data.id, data)\n}\n\nfunction refreshFolder() {\n  emit('refreshTree')\n}\n\nfunction clearCurrentKey() {\n  treeRef.value?.setCurrentKey(undefined)\n}\ndefineExpose({\n  clearCurrentKey,\n})\nonUnmounted(() => {\n  treeRef.value?.setCurrentKey(undefined)\n})\n</script>\n<style lang=\"scss\" scoped>\n.folder-tree {\n  .shared-button {\n    padding: 10px 8px;\n    font-weight: 400;\n    font-size: 14px;\n    margin-bottom: 4px;\n    &.active {\n      background: var(--el-color-primary-light-9);\n      border-radius: var(--app-border-radius-small);\n      color: var(--el-color-primary);\n      font-weight: 500;\n      &:hover {\n        background: var(--el-color-primary-light-9);\n      }\n    }\n    &:hover {\n      border-radius: var(--app-border-radius-small);\n      background: rgba(var(--el-text-color-primary-rgb), 0.1);\n    }\n    &.is-active {\n      &:hover {\n        color: var(--el-color-primary);\n        background: var(--el-color-primary-light-9);\n      }\n    }\n  }\n  .tree-height-shared {\n    padding-top: 4px;\n    height: calc(100vh - 220px);\n  }\n  .tree-height {\n    padding-top: 4px;\n    height: calc(100vh - 180px);\n  }\n  :deep(.folder-tree__main) {\n    .el-tree-node.is-dragging {\n      opacity: 0.5;\n    }\n    .el-tree-node.is-drop-inner > .el-tree-node__content {\n      background-color: var(--el-color-primary-light-9);\n      border: 2px dashed var(--el-color-primary);\n      border-radius: 4px;\n    }\n    .custom-tree-node {\n      box-sizing: content-box;\n      width: calc(100% - 27px);\n    }\n    .tree-label {\n      width: 100%;\n      overflow: hidden;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n    .el-tree-node__content {\n      position: relative;\n    }\n    .el-tree-node__children {\n      overflow: inherit !important;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/generate-related-dialog/index.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.document.generateQuestion.title')\"\n    v-model=\"dialogVisible\"\n    width=\"650\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    @click.stop\n  >\n    <div class=\"content-height\">\n      <el-form\n        ref=\"FormRef\"\n        :model=\"form\"\n        :rules=\"rules\"\n        label-position=\"top\"\n        hide-required-asterisk\n      >\n        <div class=\"update-info flex border-r-6 mb-16 p-8-12\">\n          <div class=\"mt-4\">\n            <AppIcon iconName=\"app-warning-colorful\" style=\"font-size: 16px\"></AppIcon>\n          </div>\n          <div class=\"ml-12 lighter\">\n            <p>{{ $t('views.document.generateQuestion.tip1', { data: '{data}' }) }}</p>\n            <p>\n              {{ $t('views.document.generateQuestion.tip2')+ '<question></question>' +\n              $t('views.document.generateQuestion.tip3') }}\n            </p>\n            <p>{{ $t('views.document.generateQuestion.tip4') }}</p>\n          </div>\n        </div>\n        <el-form-item prop=\"model_id\">\n          <template #label>\n            <div class=\"flex-between\">\n              <div>\n                <span\n                  >{{ $t('views.application.form.aiModel.label') }}\n                  <span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button\n                type=\"primary\"\n                link\n                @click=\"openAIParamSettingDialog\"\n                :disabled=\"!form.model_id\"\n              >\n                <AppIcon iconName=\"app-setting\" class=\"mr-4\"></AppIcon>\n                {{ $t('common.paramSetting') }}\n              </el-button>\n            </div>\n          </template>\n          <ModelSelect\n            v-model=\"form.model_id\"\n            :placeholder=\"$t('views.application.form.aiModel.placeholder')\"\n            :options=\"modelOptions\"\n            showFooter\n            :model-type=\"'LLM'\"\n            @change=\"modelChange\"\n          ></ModelSelect>\n        </el-form-item>\n        <el-form-item prop=\"prompt\">\n          <template #label>\n            <div class=\"flex-between\">\n              <div>\n                <span>{{ $t('common.prompt.label') }} <span class=\"color-danger\">*</span></span>\n              </div>\n            </div>\n          </template>\n          <el-input\n            v-model=\"form.prompt\"\n            :placeholder=\"$t('common.prompt.placeholder')\"\n            :rows=\"7\"\n            type=\"textarea\"\n          />\n        </el-form-item>\n        <el-form-item\n          v-if=\"['document', 'knowledge'].includes(apiSubmitType)\"\n          :label=\"$t('components.selectParagraph.title')\"\n          prop=\"state\"\n        >\n          <el-radio-group v-model=\"state\" class=\"radio-block\">\n            <el-radio value=\"error\" size=\"large\">{{\n              $t('components.selectParagraph.error')\n            }}</el-radio>\n            <el-radio value=\"all\" size=\"large\">{{ $t('components.selectParagraph.all') }}</el-radio>\n          </el-radio-group>\n        </el-form-item>\n      </el-form>\n    </div>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle(FormRef)\" :disabled=\"!model || loading\">\n          {{ $t('common.confirm') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n  <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshForm\" />\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport { groupBy } from 'lodash'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport type { FormInstance } from 'element-plus'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\n\nconst props = defineProps<{\n  apiType: 'systemShare' | 'workspace' | 'systemManage' | 'workspaceShare'\n}>()\n\nconst route = useRoute()\nconst {\n  params: { id, documentId }, // id为knowledgeID\n} = route as any\n\nconst { model, prompt, user } = useStore()\n\nconst emit = defineEmits(['refresh'])\n\nconst loading = ref<boolean>(false)\n\nconst dialogVisible = ref<boolean>(false)\nconst modelOptions = ref<any>(null)\nconst idList = ref<string[]>([])\nconst apiSubmitType = ref('') // 文档document或段落paragraph\nconst state = ref<'all' | 'error'>('error')\nconst stateMap = {\n  all: ['0', '1', '2', '3', '4', '5', 'n'],\n  error: ['0', '1', '3', '4', '5', 'n'],\n}\nconst FormRef = ref()\nconst currentKnowledge = ref<any>(null)\nconst userId = user.userInfo?.id as string\nconst form = ref(prompt.get(userId))\nconst rules = reactive({\n  model_id: [\n    {\n      required: true,\n      message: t('views.application.form.aiModel.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n  prompt: [\n    {\n      required: true,\n      message: t('common.prompt.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = prompt.get(userId)\n    FormRef.value?.clearValidate()\n  }\n})\nconst AIModeParamSettingDialogRef = ref()\nconst openAIParamSettingDialog = () => {\n  if (form.value.model_id) {\n    AIModeParamSettingDialogRef.value?.open(\n      form.value.model_id,\n      id,\n      form.value.model_params_setting,\n    )\n  }\n}\n\nfunction modelChange() {\n  if (form.value.model_id) {\n    AIModeParamSettingDialogRef.value?.reset_default(form.value.model_id, id)\n  } else {\n    refreshForm({})\n  }\n}\n\nfunction refreshForm(data: any) {\n  form.value.model_params_setting = data\n}\n\nconst open = (ids: string[], type: string, _knowledge?: any) => {\n  currentKnowledge.value = _knowledge\n  getModelFn()\n  idList.value = ids\n  apiSubmitType.value = type\n  dialogVisible.value = true\n}\n\nconst submitHandle = async (formEl: FormInstance) => {\n  if (!formEl) {\n    return\n  }\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      // 保存提示词\n      prompt.save(user.userInfo?.id as string, form.value)\n      if (apiSubmitType.value === 'paragraph') {\n        const data = {\n          ...form.value,\n          paragraph_id_list: idList.value,\n        }\n        loadSharedApi({ type: 'paragraph', systemType: props.apiType })\n          .putBatchGenerateRelated(id, documentId, data, loading)\n          .then(() => {\n            MsgSuccess(t('views.document.generateQuestion.successMessage'))\n            emit('refresh')\n            dialogVisible.value = false\n          })\n      } else if (apiSubmitType.value === 'document') {\n        const data = {\n          ...form.value,\n          document_id_list: idList.value,\n          state_list: stateMap[state.value],\n        }\n        loadSharedApi({ type: 'document', systemType: props.apiType })\n          .putBatchGenerateRelated(id, data, loading)\n          .then(() => {\n            MsgSuccess(t('views.document.generateQuestion.successMessage'))\n            emit('refresh')\n            dialogVisible.value = false\n          })\n      } else if (apiSubmitType.value === 'knowledge') {\n        const data = {\n          ...form.value,\n          state_list: stateMap[state.value],\n        }\n        loadSharedApi({ type: 'knowledge', systemType: props.apiType })\n          .putGenerateRelated(id ? id : currentKnowledge.value?.id, data, loading)\n          .then(() => {\n            MsgSuccess(t('views.document.generateQuestion.successMessage'))\n            dialogVisible.value = false\n          })\n      }\n    }\n  })\n}\n\nfunction getModelFn() {\n  loading.value = true\n  const obj =\n    props.apiType === 'systemManage'\n      ? {\n          model_type: 'LLM',\n          workspace_id: currentKnowledge.value?.workspace_id,\n        }\n      : {\n          model_type: 'LLM',\n        }\n  loadSharedApi({ type: 'model', systemType: props.apiType })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n      loading.value = false\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\ndefineExpose({ open, dialogVisible })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/index.ts",
    "content": "import { type App } from 'vue'\nimport LogoFull from './logo/LogoFull.vue'\nimport LogoIcon from './logo/LogoIcon.vue'\nimport SendIcon from './logo/SendIcon.vue'\nimport dynamicsForm from './dynamics-form'\nimport AppIcon from './app-icon/AppIcon.vue'\nimport LayoutContainer from './layout-container/index.vue'\nimport ContentContainer from './layout-container/ContentContainer.vue'\nimport CardBox from './card-box/index.vue'\nimport FolderTree from './folder-tree/index.vue'\nimport CommonList from './common-list/index.vue'\nimport BackButton from './back-button/index.vue'\nimport AppTable from './app-table/index.vue'\nimport CodemirrorEditor from './codemirror-editor/index.vue'\nimport InfiniteScroll from './infinite-scroll/index.vue'\nimport ModelSelect from './model-select/index.vue'\nimport ReadWrite from './read-write/index.vue'\nimport AutoTooltip from './auto-tooltip/index.vue'\nimport MdEditor from './markdown/MdEditor.vue'\nimport MdPreview from './markdown/MdPreview.vue'\nimport MdEditorMagnify from './markdown/MdEditorMagnify.vue'\nimport TagEllipsis from './tag-ellipsis/index.vue'\nimport CardCheckbox from './card-checkbox/index.vue'\nimport AiChat from './ai-chat/index.vue'\nimport KnowledgeIcon from './app-icon/KnowledgeIcon.vue'\nimport ToolIcon from './app-icon/ToolIcon.vue'\nimport TriggerIcon from './app-icon/TriggerIcon.vue'\nimport TagGroup from './tag-group/index.vue'\nimport WorkspaceDropdown from './workspace-dropdown/index.vue'\nimport FolderBreadcrumb from './folder-breadcrumb/index.vue'\nexport default {\n  install(app: App) {\n    app.component('LogoFull', LogoFull)\n    app.component('LogoIcon', LogoIcon)\n    app.component('SendIcon', SendIcon)\n    app.use(dynamicsForm)\n    app.component('AppIcon', AppIcon)\n    app.component('LayoutContainer', LayoutContainer)\n    app.component('ContentContainer', ContentContainer)\n    app.component('CardBox', CardBox)\n    app.component('FolderTree', FolderTree)\n    app.component('CommonList', CommonList)\n    app.component('BackButton', BackButton)\n    app.component('AppTable', AppTable)\n    app.component('CodemirrorEditor', CodemirrorEditor)\n    app.component('InfiniteScroll', InfiniteScroll)\n    app.component('ModelSelect', ModelSelect)\n    app.component('ReadWrite', ReadWrite)\n    app.component('AutoTooltip', AutoTooltip)\n    app.component('MdPreview', MdPreview)\n    app.component('MdEditor', MdEditor)\n    app.component('MdEditorMagnify', MdEditorMagnify)\n    app.component('TagEllipsis', TagEllipsis)\n    app.component('CardCheckbox', CardCheckbox)\n    app.component('AiChat', AiChat)\n    app.component('KnowledgeIcon', KnowledgeIcon)\n    app.component('ToolIcon', ToolIcon)\n    app.component('TriggerIcon', TriggerIcon)\n    app.component('TagGroup', TagGroup)\n    app.component('WorkspaceDropdown', WorkspaceDropdown)\n    app.component('FolderBreadcrumb', FolderBreadcrumb)\n  },\n}\n"
  },
  {
    "path": "ui/src/components/infinite-scroll/index.vue",
    "content": "<template>\n  <div v-infinite-scroll=\"loadData\" :infinite-scroll-disabled=\"disabledScroll\">\n    <slot />\n  </div>\n  <div style=\"padding: 0 10px 16px\" class=\"text-center lighter color-secondary\">\n    <el-text class=\"text-with-lines\" type=\"info\" v-if=\"size > 0 && loading\">\n      {{ $t('components.loading') }}...</el-text\n    >\n    <el-text class=\"text-with-lines\" v-if=\"noMore\" type=\"info\">\n      {{ $t('components.noMore') }}</el-text\n    >\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\n\ndefineOptions({ name: 'InfiniteScroll' })\nconst props = defineProps({\n  /**\n   * 对象数量\n   */\n  size: {\n    type: Number,\n    default: 0,\n  },\n  /**\n   * 总数\n   */\n  total: {\n    type: Number,\n    default: 0,\n  },\n  /**\n   * 总数\n   */\n  page_size: {\n    type: Number,\n    default: 0,\n  },\n  current_page: {\n    type: Number,\n    default: 0,\n  },\n  loading: Boolean,\n})\n\nconst emit = defineEmits(['update:current_page', 'load'])\nconst current = ref(props.current_page)\n\nwatch(\n  () => props.current_page,\n  (val) => {\n    if (val === 1) {\n      current.value = 1\n    }\n  },\n)\n\nconst noMore = computed(\n  () =>\n    props.size > 0 && props.size === props.total && props.total > props.page_size && !props.loading,\n)\nconst disabledScroll = computed(() => props.size > 0 && (props.loading || noMore.value))\nfunction loadData() {\n  if (props.total > props.page_size) {\n    current.value += 1\n    emit('update:current_page', current.value)\n    emit('load')\n  }\n}\n</script>\n<style lang=\"scss\" scoped>\n.text-with-lines {\n  position: relative;\n  display: inline-block;\n}\n\n.text-with-lines::before,\n.text-with-lines::after {\n  content: '';\n  position: absolute;\n  width: 80px;\n  border-bottom: 1px solid var(--el-border-color);\n  top: 10px;\n}\n\n.text-with-lines::before {\n  left: -88px; /* 左侧线条位置 */\n}\n\n.text-with-lines::after {\n  right: -82px; /* 右侧线条位置 */\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/layout-container/ContentContainer.vue",
    "content": "<template>\n  <div class=\"content-container\">\n    <div class=\"content-container__header flex align-center w-full\" v-if=\"slots.header || header\">\n      <slot name=\"backButton\">\n        <back-button :to=\"backTo\" v-if=\"showBack\"></back-button>\n      </slot>\n      <div class=\"flex-between w-full\">\n        <slot name=\"header\">\n          <h4>{{ header }}</h4>\n        </slot>\n        <slot name=\"search\"> </slot>\n      </div>\n    </div>\n\n    <div class=\"content-container__main\">\n      <el-scrollbar class=\"p-16\" style=\"padding-right: 0;\">\n        <slot></slot>\n      </el-scrollbar>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue'\ndefineOptions({ name: 'ContentContainer' })\nconst slots = useSlots()\nconst props = defineProps({\n  header: String || null,\n  backTo: String,\n})\nconst showBack = computed(() => {\n  const { backTo } = props\n  return backTo\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.content-container {\n  transition: 0.3s;\n  .content-container__header {\n    box-sizing: border-box;\n    padding: calc(var(--app-base-px) * 2) calc(var(--app-base-px) * 2) 0;\n    flex-wrap: wrap;\n  }\n  .content-container__main {\n    box-sizing: border-box;\n    min-width: 447px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/layout-container/index.vue",
    "content": "<template>\n  <div class=\"layout-container flex h-full\">\n    <div\n      :class=\"`layout-container__left border-r ${isCollapse ? 'hidden' : ''}`\"\n      :style=\"{ width: isCollapse ? 0 : `${leftWidth}px` }\"\n      v-if=\"showLeft\"\n    >\n      <div class=\"layout-container__left_content\">\n        <slot name=\"left\"></slot>\n      </div>\n      <el-tooltip\n        :content=\"isCollapse ? $t('common.expand') : $t('common.collapse')\"\n        placement=\"right\"\n      >\n        <el-button\n          v-if=\"props.showCollapse\"\n          class=\"collapse\"\n          size=\"small\"\n          circle\n          @click=\"isCollapse = !isCollapse\"\n          :icon=\"isCollapse ? 'ArrowRightBold' : 'ArrowLeftBold'\"\n        />\n      </el-tooltip>\n      <div\n        v-if=\"props.resizable\"\n        class=\"splitter-bar-line\"\n        :class=\"isResizing ? 'hover' : ''\"\n        @mousedown=\"onSplitterMouseDown\"\n      ></div>\n    </div>\n    <div class=\"layout-container__right\">\n      <slot></slot>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { onUnmounted, ref } from 'vue'\ndefineOptions({ name: 'LayoutContainer' })\n\nconst props = defineProps({\n  showCollapse: Boolean,\n  resizable: Boolean,\n  minLeftWidth: {\n    type: Number,\n    default: 240,\n  },\n  maxLeftWidth: {\n    type: Number,\n    default: 400,\n  },\n  showLeft: {\n    type: Boolean,\n    default: true,\n  },\n})\n\nconst isCollapse = ref(false)\nconst leftWidth = ref(props.minLeftWidth)\nconst isResizing = ref(false)\n\nconst onSplitterMouseDown = (e: MouseEvent) => {\n  if (!props.resizable) return\n  e.preventDefault()\n  isResizing.value = true\n  document.body.style.userSelect = 'none'\n  const startX = e.clientX\n  const startWidth = leftWidth.value\n  const onMouseMove = (moveEvent: MouseEvent) => {\n    if (!isResizing.value) return\n    const deltaX = moveEvent.clientX - startX\n    let newWidth = startWidth + deltaX\n\n    // 限制宽度在最小和最大值之间\n    newWidth = Math.max(props.minLeftWidth, Math.min(props.maxLeftWidth, newWidth))\n    leftWidth.value = newWidth\n  }\n\n  const onMouseUp = () => {\n    isResizing.value = false\n    document.body.style.userSelect = ''\n    document.removeEventListener('mousemove', onMouseMove)\n    document.removeEventListener('mouseup', onMouseUp)\n  }\n  document.addEventListener('mousemove', onMouseMove)\n  document.addEventListener('mouseup', onMouseUp)\n}\n\nonUnmounted(() => {\n  document.removeEventListener('mousemove', () => {})\n  document.removeEventListener('mouseup', () => {})\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.layout-container {\n  &__left {\n    position: relative;\n    box-sizing: border-box;\n    // transition: width 0.28s;\n    width: var(--sidebar-width);\n    .splitter-bar-line {\n      z-index: 1;\n      position: absolute;\n      top: 0;\n      right: 0;\n      cursor: col-resize;\n      width: 4px;\n      height: 100%;\n      &.hover:after {\n        width: 1px;\n        height: 100%;\n        content: '';\n        z-index: 2;\n        position: absolute;\n        right: -1px;\n        top: 0;\n        background: var(--el-color-primary);\n      }\n    }\n\n    .collapse {\n      position: absolute;\n      top: 36px;\n      right: -12px;\n      box-shadow: 0px 5px 10px 0px rgba(var(--el-text-color-primary-rgb), 0.1);\n      z-index: 2;\n    }\n\n    .layout-container__left_content {\n      width: 100%;\n      // height: 100%;\n    }\n\n    &.hidden {\n      width: 0;\n      min-width: 0;\n\n      .layout-container__left_content {\n        visibility: hidden;\n      }\n\n      .collapse {\n        right: -18px;\n      }\n    }\n  }\n\n  &__right {\n    flex: 1;\n    overflow: hidden;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/loading/DownloadLoading.vue",
    "content": "<template>\n  <div class=\"loading-container loader\">\n    <div class=\"download-loading\">\n      <div></div>\n      <div></div>\n      <div></div>\n      <div></div>\n      <div></div>\n      <div></div>\n      <div></div>\n      <div></div>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\"></script>\n<style lang=\"scss\" scoped>\n.loading-container {\n  display: -webkit-flex; /*safari弹性布局*/\n  justify-content: center;\n  display: flex;\n  align-items: center;\n}\n\n@-webkit-keyframes loader {\n  0% {\n    opacity: 0.3;\n  }\n  80% {\n    opacity: 1;\n  }\n  100% {\n    opacity: 1;\n  }\n}\n.download-loading {\n  position: relative;\n}\n.download-loading div {\n  width: 5px;\n  height: 12px;\n  background: var(--el-color-info);\n  position: absolute;\n  border-radius: 2px;\n  margin: 0 auto;\n}\n.download-loading div:nth-child(1) {\n  top: -20px;\n  left: 0;\n  -webkit-animation: loader 1s -0.8s infinite ease-in-out;\n}\n.download-loading div:nth-child(2) {\n  top: -13px;\n  left: 13px;\n  -webkit-transform: rotate(45deg);\n  -webkit-animation: loader 1s -0.6s infinite ease-in-out;\n}\n.download-loading div:nth-child(3) {\n  top: 0px;\n  left: 20px;\n  -webkit-transform: rotate(90deg);\n  -webkit-animation: loader 1s -0.5s infinite ease-in-out;\n}\n.download-loading div:nth-child(4) {\n  top: 13px;\n  left: 13px;\n  -webkit-transform: rotate(-45deg);\n  -webkit-animation: loader 1s -0.4s infinite ease-in-out;\n}\n.download-loading div:nth-child(5) {\n  top: 20px;\n  left: 0px;\n  -webkit-transform: rotate(0deg);\n  -webkit-animation: loader 1s -0.3s infinite ease-in-out;\n}\n.download-loading div:nth-child(6) {\n  top: 13px;\n  left: -13px;\n  -webkit-transform: rotate(45deg);\n  -webkit-animation: loader 1s -0.2s infinite ease-in-out;\n}\n.download-loading div:nth-child(7) {\n  top: 0px;\n  left: -20px;\n  -webkit-transform: rotate(90deg);\n  -webkit-animation: loader 1s -0.1s infinite ease-in-out;\n}\n.download-loading div:nth-child(8) {\n  top: -13px;\n  left: -13px;\n  -webkit-transform: rotate(-45deg);\n  -webkit-animation: loader 1s 0s infinite ease-in-out;\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/logo/LogoFull.vue",
    "content": "<template>\n  <img v-if=\"theme.themeInfo?.loginLogo\" :src=\"fileURL\" alt=\"\" height=\"45px\" class=\"mr-8\" />\n  <template v-else>\n    <svg\n      v-if=\"!isDefaultTheme\"\n      viewBox=\"0 0 122 36\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      :height=\"height\"\n      :class=\"!isDefaultTheme ? 'custom-logo-color' : ''\"\n    >\n      <g clip-path=\"url(#clip0_5682_1471)\" fill-rule=\"evenodd\">\n        <path\n          d=\"M75.3094 19.0805V27.05H71.8274L71.8109 26.2436C70.5933 26.8762 69.4033 27.1925 68.2412 27.1923H67.8972C66.7033 27.1923 65.7546 26.7337 65.051 25.8166C64.5855 25.1007 64.342 24.2631 64.3513 23.4092V23.3143C64.3513 21.7489 64.9008 20.7092 65.9997 20.1953C66.4505 19.8949 67.5929 19.7447 69.4271 19.7447H71.3008V19.3058C71.3008 18.4045 71.1703 17.8867 70.9094 17.7523C70.6249 17.5388 70.1228 17.4321 69.4033 17.4321H65.6678L65.7312 14.2396L70.032 14.1233C72.5857 14.1233 74.1669 14.7558 74.7758 16.0208C75.1315 16.756 75.3094 17.7759 75.3094 19.0805ZM68.6032 22.3901C68.4844 22.5315 68.3597 22.9902 68.3597 23.3143C68.3597 24.0259 68.7234 24.3817 69.4508 24.3817C69.8855 24.3817 70.5022 24.2038 71.3008 23.848V22.1112C71.3008 22.1112 69.0361 21.875 68.6032 22.3901Z\"\n          fill=\"currentColor\"\n        />\n        <path\n          d=\"M56.6308 27.3317L55.4162 15.1815L52.0028 27.3317H48.028L44.6928 15.1815L43.4348 27.3317L37.9399 27.2849L40.6207 9.35034H47.6212L50.0211 17.8845L52.4444 9.35034H59.281L62.0087 27.3317H56.6308Z\"\n          fill=\"currentColor\"\n        />\n        <path\n          d=\"M85.3943 26.9654L83.5118 23.1105L81.6291 26.9654H77.2017L80.831 20.5778L77.3644 14.6084H81.6525L83.5118 18.2543L85.313 14.6084H89.6009L86.1576 20.5778L89.7637 26.9654H85.3943Z\"\n          fill=\"currentColor\"\n        />\n        <path\n          d=\"M101.114 26.9656C101.029 26.8419 96.8966 20.1336 96.8966 20.1336L95.5396 22.2226V26.9656H90.9727V9.11621H95.5396V16.3526L99.7128 9.17639H104.477L99.514 17.1079L105.855 26.9656H101.114Z\"\n          fill=\"currentColor\"\n        />\n        <path\n          d=\"M121.036 22.145C121.036 24.745 119.74 27.1282 115.097 27.1282H107.032V9.02689L113.203 8.90869C113.203 8.90869 116.858 8.74751 118.706 10.153C120.068 11.1885 120.515 13.0021 120.384 14.5197C120.254 16.0373 119.553 17.0129 118.405 17.727C119.979 18.354 121.036 19.5451 121.036 22.145ZM114.228 16.3325C115.766 16.3325 116.62 15.5559 116.62 14.45C116.62 13.2735 115.833 12.5837 114.228 12.5837L111.576 12.5906V16.3325H114.228ZM114.365 23.5374C116.497 23.5374 117.022 22.393 117.022 21.6316C117.022 20.4308 116.17 19.563 114.752 19.563H111.576V23.5374H114.365Z\"\n          fill=\"currentColor\"\n        />\n        <path\n          d=\"M17.4213 26.7354H12.8296L11.1277 28.4372C11.028 28.5369 10.9601 28.6639 10.9326 28.8022C10.9051 28.9405 10.9193 29.0838 10.9732 29.2141C11.0272 29.3443 11.1185 29.4557 11.2358 29.534C11.353 29.6123 11.4908 29.6541 11.6318 29.6541H18.6192C18.7602 29.6541 18.898 29.6123 19.0153 29.534C19.1325 29.4557 19.2239 29.3443 19.2778 29.2141C19.3318 29.0838 19.3459 28.9405 19.3184 28.8022C19.2909 28.6639 19.223 28.5369 19.1233 28.4372L17.4213 26.7354Z\"\n          fill=\"currentColor\"\n        />\n        <path\n          d=\"M30.04 13.3823H29.1348V19.7499H30.04C30.1305 19.7499 30.2201 19.732 30.3037 19.6974C30.3873 19.6628 30.4633 19.612 30.5273 19.548C30.5913 19.484 30.642 19.4081 30.6767 19.3244C30.7113 19.2408 30.7291 19.1512 30.7291 19.0607V14.0715C30.7291 13.8887 30.6565 13.7134 30.5273 13.5842C30.398 13.4549 30.2227 13.3823 30.04 13.3823Z\"\n          fill=\"currentColor\"\n        />\n        <path\n          d=\"M1.92296 13.3823H1.01776C0.834985 13.3823 0.659698 13.4549 0.530458 13.5842C0.401219 13.7134 0.328613 13.8887 0.328613 14.0715V19.0607C0.328611 19.1512 0.346435 19.2408 0.381067 19.3244C0.415699 19.4081 0.466461 19.484 0.530455 19.548C0.594448 19.612 0.670419 19.6628 0.754031 19.6974C0.837643 19.732 0.927258 19.7499 1.01776 19.7499H1.92296V13.3823Z\"\n          fill=\"currentColor\"\n        />\n        <path\n          d=\"M19.0238 14.2251C18.682 14.2251 18.3541 14.3609 18.1124 14.6026C17.8707 14.8443 17.7349 15.1722 17.7349 15.514V16.4382C17.7349 16.7801 17.8707 17.108 18.1124 17.3497C18.3541 17.5914 18.682 17.7272 19.0239 17.7272C19.3657 17.7272 19.6936 17.5914 19.9353 17.3497C20.1771 17.108 20.3129 16.7801 20.3129 16.4382V15.5141C20.3129 15.3448 20.2796 15.1772 20.2148 15.0208C20.15 14.8644 20.055 14.7223 19.9353 14.6026C19.8156 14.4829 19.6735 14.388 19.5171 14.3232C19.3607 14.2584 19.1931 14.2251 19.0238 14.2251Z\"\n          fill=\"currentColor\"\n        />\n        <path\n          d=\"M12.3012 14.2251C11.9593 14.2251 11.6315 14.3609 11.3897 14.6026C11.148 14.8443 11.0122 15.1722 11.0122 15.514V16.4382C11.0122 16.7801 11.148 17.108 11.3897 17.3497C11.6315 17.5914 11.9593 17.7272 12.3012 17.7272C12.6431 17.7272 12.9709 17.5914 13.2127 17.3497C13.4544 17.108 13.5902 16.7801 13.5902 16.4382V15.5141C13.5902 15.3448 13.5569 15.1772 13.4921 15.0208C13.4273 14.8644 13.3324 14.7223 13.2127 14.6026C13.093 14.4829 12.9509 14.388 12.7945 14.3232C12.6381 14.2584 12.4704 14.2251 12.3012 14.2251Z\"\n          fill=\"currentColor\"\n        />\n        <path\n          d=\"M23.3607 6.91333H7.69709C6.3139 6.91489 4.98782 7.46505 4.00976 8.44311C3.0317 9.42117 2.48154 10.7473 2.47998 12.1304V20.9612C2.48154 22.3444 3.03169 23.6705 4.00975 24.6486C4.98781 25.6266 6.3139 26.1768 7.69709 26.1784H23.3607C24.7439 26.1768 26.07 25.6267 27.0481 24.6486C28.0262 23.6705 28.5764 22.3444 28.5779 20.9612V12.1304C28.5763 10.7472 28.0262 9.42115 27.0481 8.44309C26.07 7.46503 24.7439 6.91487 23.3607 6.91333ZM23.7988 20.9085C23.7988 21.1577 23.6998 21.3968 23.5235 21.573C23.3473 21.7492 23.1083 21.8482 22.859 21.8482H15.2189C14.0629 21.8482 12.9263 22.1453 11.9181 22.711L9.355 24.1492V21.8483H8.19882C7.94958 21.8483 7.71055 21.7493 7.53432 21.573C7.35808 21.3968 7.25907 21.1578 7.25906 20.9085V11.547C7.25907 11.2978 7.35808 11.0588 7.53432 10.8825C7.71056 10.7063 7.94958 10.6073 8.19882 10.6073H22.859C23.1082 10.6073 23.3472 10.7063 23.5235 10.8825C23.6997 11.0588 23.7987 11.2978 23.7987 11.5471L23.7988 20.9085Z\"\n          fill=\"currentColor\"\n        />\n      </g>\n    </svg>\n    <img v-else src=\"@/assets/logo/MaxKB-logo.svg\" :height=\"height\" />\n  </template>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted } from 'vue'\nimport useStore from '@/stores'\ndefineOptions({ name: 'LogoFull' })\n\ndefineProps({\n  height: {\n    type: String,\n    default: '36px',\n  },\n})\nconst { theme } = useStore()\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\n\nconst fileURL = computed(() => {\n  if (theme.themeInfo) {\n    if (typeof theme.themeInfo?.loginLogo === 'string') {\n      return theme.themeInfo?.loginLogo\n    } else {\n      return URL.createObjectURL(theme.themeInfo?.loginLogo)\n    }\n  } else {\n    return ''\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.custom-logo-color {\n  path {\n    fill: var(--el-color-primary);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/logo/LogoIcon.vue",
    "content": "<template>\n  <svg\n    v-if=\"!isDefaultTheme\"\n    :class=\"!isDefaultTheme ? 'custom-logo-color' : ''\"\n    :height=\"height\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    viewBox=\"0 0 232.4409 232.4409\"\n  >\n    <title>MaxKB</title>\n    <path\n      class=\"cls-1\"\n      d=\"M128.4532,177H98.7785L87.78,187.9985a4.6069,4.6069,0,0,0,3.2576,7.8644h45.1569a4.6069,4.6069,0,0,0,3.2575-7.8644Z\"\n    />\n    <path\n      class=\"cls-1\"\n      d=\"M210.0008,90.7042h-5.85v41.1511h5.85a4.4537,4.4537,0,0,0,4.4537-4.4537V95.1579A4.4537,4.4537,0,0,0,210.0008,90.7042Z\"\n    />\n    <path\n      class=\"cls-1\"\n      d=\"M28.29,90.7042H22.44a4.4538,4.4538,0,0,0-4.4538,4.4537v32.2437a4.4538,4.4538,0,0,0,4.4538,4.4537h5.85Z\"\n    />\n    <path\n      class=\"cls-1\"\n      d=\"M138.8087,96.1512a8.33,8.33,0,0,0-8.33,8.33v5.9727a8.33,8.33,0,1,0,16.6607,0v-5.9727A8.33,8.33,0,0,0,138.8087,96.1512Z\"\n    />\n    <path\n      class=\"cls-1\"\n      d=\"M95.3622,96.1512a8.33,8.33,0,0,0-8.33,8.33v5.9727a8.33,8.33,0,1,0,16.6607,0v-5.9727A8.33,8.33,0,0,0,95.3622,96.1512Z\"\n    />\n    <path\n      class=\"cls-1\"\n      d=\"M166.8344,48.8968H65.6064A33.7544,33.7544,0,0,0,31.89,82.6131v57.07A33.7548,33.7548,0,0,0,65.6064,173.4h101.228a33.7549,33.7549,0,0,0,33.7168-33.7168v-57.07A33.7545,33.7545,0,0,0,166.8344,48.8968Zm2.831,90.4457a6.0733,6.0733,0,0,1-6.0732,6.0733H114.2168a43.5922,43.5922,0,0,0-21.3313,5.5757l-16.5647,9.2946v-14.87h-7.472a6.0733,6.0733,0,0,1-6.0733-6.0733v-60.5a6.0733,6.0733,0,0,1,6.0733-6.0733h94.7434a6.0733,6.0733,0,0,1,6.0732,6.0733Z\"\n    />\n  </svg>\n  <img v-else src=\"@/assets/logo/logo.png\" :height=\"height\" />\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted } from 'vue'\nimport useStore from '@/stores'\ndefineOptions({ name: 'LogoIcon' })\n\ndefineProps({\n  height: {\n    type: String,\n    default: '36px'\n  }\n})\nconst { theme } = useStore()\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\n</script>\n<style lang=\"scss\" scoped>\n.custom-logo-color {\n  path {\n    fill: var(--el-color-primary);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/logo/SendIcon.vue",
    "content": "<template>\n  <svg\n    v-if=\"!isDefaultTheme\"\n    :class=\"!isDefaultTheme ? 'custom-logo-color' : ''\"\n    width=\"24\"\n    height=\"24\"\n    fill=\"none\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M20.1716 1.68834C20.6753 1.53273 21.0458 2.16193 20.6652 2.52691L12.2658 10.5836C11.0058 11.7921 9.32754 12.4668 7.5817 12.4668C5.68044 12.4668 3.8669 11.667 2.58487 10.263L1.45879 9.02985C1.33225 8.90313 1.24137 8.74527 1.19534 8.5722C1.14931 8.39913 1.14974 8.21698 1.19661 8.04413C1.24347 7.87129 1.3351 7.71386 1.46225 7.58775C1.5894 7.46164 1.74757 7.3713 1.92079 7.32585L20.1716 1.68834Z\"\n      fill=\"currentColor\"\n    />\n    <path\n      d=\"M12 16.1851C12 14.2766 12.7377 12.4419 14.0588 11.0646L21.4664 3.34177C21.8268 2.96601 22.4499 3.32266 22.3084 3.82374L17.143 22.1182C17.0971 22.291 17.0064 22.4487 16.8801 22.5754C16.7538 22.7021 16.5964 22.7932 16.4237 22.8397C16.251 22.8862 16.0691 22.8864 15.8964 22.8402C15.7236 22.794 15.566 22.7031 15.4395 22.5767L14.4439 21.6791C12.8881 20.2764 12 18.2799 12 16.1851Z\"\n      fill=\"currentColor\"\n    />\n  </svg>\n\n  <img v-else src=\"@/assets/chat/icon_send_colorful.svg\" />\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted } from 'vue'\nimport useStore from '@/stores'\ndefineOptions({ name: 'SendIcon' })\n\ndefineProps({\n  height: {\n    type: String,\n    default: '36px'\n  }\n})\nconst { theme } = useStore()\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\n</script>\n<style lang=\"scss\" scoped>\n.custom-logo-color {\n  path {\n    fill: var(--el-color-primary);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/markdown/EchartsRander.vue",
    "content": "<template>\n  <div class=\"charts-container\">\n    <div ref=\"chartsRef\" :style=\"style\" v-resize=\"changeChartSize\"></div>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { onMounted, nextTick, watch, onBeforeUnmount, ref } from 'vue'\nimport * as echarts from 'echarts'\nconst tmp = ref()\nconst props = defineProps<{ option: string }>()\nconst chartsRef = ref()\n\nconst style = ref({\n  height: '220px',\n  width: '100%',\n})\n\nfunction initChart() {\n  if (chartsRef.value) {\n    let myChart = echarts?.getInstanceByDom(chartsRef.value)\n    if (myChart === null || myChart === undefined) {\n      myChart = echarts.init(chartsRef.value)\n    }\n    const option = JSON.parse(props.option)\n    if (option.actionType === 'EVAL') {\n      myChart.setOption(evalParseOption(option), true)\n    } else {\n      myChart.setOption(jsonParseOption(option), true)\n    }\n  }\n}\nfunction jsonParseOption(option: any) {\n  if (option.style) {\n    style.value = option.style\n  }\n\n  if (option.option) {\n    // 渲染数据\n    return option.option\n  }\n  return option\n}\nfunction evalParseOption(option_json: any) {\n  if (option_json.style) {\n    style.value = option_json.style\n  }\n  const option = {}\n  tmp.value = echarts\n  eval(option_json.option)\n  return option\n}\n\nfunction changeChartSize() {\n  echarts?.getInstanceByDom(chartsRef.value)?.resize()\n}\n\nwatch(\n  () => props.option,\n  (val) => {\n    if (val) {\n      nextTick(() => {\n        initChart()\n      })\n    }\n  },\n)\n\nonMounted(() => {\n  nextTick(() => {\n    initChart()\n  })\n})\n\nonBeforeUnmount(() => {\n  echarts.getInstanceByDom(chartsRef.value)?.dispose()\n})\n</script>\n<style lang=\"scss\" scoped>\n.charts-container {\n  overflow-x: auto;\n}\n.charts-container::-webkit-scrollbar-track-piece {\n  background-color: rgba(0, 0, 0, 0);\n  border-left: 1px solid rgba(0, 0, 0, 0);\n}\n\n.charts-container::-webkit-scrollbar {\n  width: 5px;\n  height: 5px;\n  -webkit-border-radius: 5px;\n  -moz-border-radius: 5px;\n  border-radius: 5px;\n}\n\n.charts-container::-webkit-scrollbar-thumb {\n  background-color: rgba(0, 0, 0, 0.5);\n\n  background-clip: padding-box;\n\n  -webkit-border-radius: 5px;\n\n  -moz-border-radius: 5px;\n\n  border-radius: 5px;\n\n  min-height: 28px;\n}\n\n.charts-container::-webkit-scrollbar-thumb:hover {\n  background-color: rgba(0, 0, 0, 0.5);\n\n  -webkit-border-radius: 5px;\n\n  -moz-border-radius: 5px;\n\n  border-radius: 5px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/markdown/FormRander.vue",
    "content": "<template>\n  <div>\n    <DynamicsForm\n      :disabled=\"is_submit || disabled\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      ref=\"dynamicsFormRef\"\n      :render_data=\"form_field_list\"\n      label-suffix=\":\"\n      v-model=\"form_data\"\n      :model=\"form_data\"\n    ></DynamicsForm>\n    <el-button\n      :type=\"is_submit ? 'info' : 'primary'\"\n      :disabled=\"is_submit || disabled\"\n      @click=\"submit\"\n      >{{$t('common.submit')}}</el-button\n    >\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nconst props = withDefaults(\n  defineProps<{\n    form_setting: string\n    disabled?: boolean\n    sendMessage?: (question: string, type: 'old' | 'new', other_params_data?: any) => void\n    child_node?: any\n    chat_record_id?: string\n    runtime_node_id?: string\n  }>(),\n  {\n    disabled: false\n  }\n)\nconst form_setting_data = computed(() => {\n  if (props.form_setting) {\n    return JSON.parse(props.form_setting)\n  } else {\n    return {}\n  }\n})\nconst _submit = ref<boolean>(false)\n/**\n * 表单字段列表\n */\nconst form_field_list = computed(() => {\n  if (form_setting_data.value.form_field_list) {\n    return form_setting_data.value.form_field_list\n  }\n  return []\n})\nconst is_submit = computed(() => {\n  if (_submit.value) {\n    return true\n  }\n  if (form_setting_data.value.is_submit) {\n    return form_setting_data.value.is_submit\n  } else {\n    return false\n  }\n})\nconst _form_data = ref<any>({})\nconst form_data = computed({\n  get: () => {\n    if (form_setting_data.value.is_submit) {\n      return form_setting_data.value.form_data\n    } else {\n      return _form_data.value\n    }\n  },\n  set: (v) => {\n    _form_data.value = v\n  }\n})\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst submit = () => {\n  dynamicsFormRef.value?.validate().then(() => {\n    _submit.value = true\n    if (props.sendMessage) {\n      props.sendMessage('', 'old', {\n        child_node: props.child_node,\n        runtime_node_id: props.runtime_node_id,\n        chat_record_id: props.chat_record_id,\n        node_data: form_data.value\n      })\n    }\n  })\n}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/markdown/HtmlRander.vue",
    "content": "<template>\n  <div ref=\"htmlRef\" :innerHTML=\"source\"></div>\n</template>\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nconst htmlRef = ref<HTMLElement>()\nconst props = withDefaults(\n  defineProps<{\n    source?: string\n    script_exec?: boolean\n  }>(),\n  {\n    source: '',\n    script_exec: true\n  }\n)\nonMounted(() => {\n  if (htmlRef.value && props.script_exec) {\n    const range = document.createRange()\n    range.selectNode(htmlRef.value)\n    const scripts = htmlRef.value.getElementsByTagName('script')\n    if (scripts) {\n      const documentFragment = range.createContextualFragment(\n        [...scripts]\n          .map((item: HTMLElement) => {\n            htmlRef.value?.removeChild(item)\n            return item.outerHTML\n          })\n          .join('\\n')\n      )\n      htmlRef.value.appendChild(documentFragment)\n    }\n  }\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/markdown/IframeRender.vue",
    "content": "<template>\n  <div class=\"iframe-wrapper\">\n    <iframe\n      v-show=\"visible\"\n      ref=\"iframeRef\"\n      class=\"iframe\"\n      :srcdoc=\"finalSource\"\n      @load=\"resize\"\n      sandbox=\"allow-scripts allow-same-origin\"\n    />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\nconst resize = async () => {\n  await nextTick()\n\n  const iframe = iframeRef.value\n  if (!iframe) return\n\n  const doc = iframe.contentDocument\n  if (!doc) return\n\n  const contentHeight = doc.documentElement.scrollHeight || doc.body.scrollHeight\n\n  const viewportHeight = window.innerHeight\n\n  const finalHeight = Math.min(contentHeight, viewportHeight)\n\n  iframe.style.height = finalHeight + 'px'\n\n  iframe.style.overflow = contentHeight > viewportHeight ? 'auto' : 'hidden'\n}\nconst props = withDefaults(\n  defineProps<{\n    source?: string\n    script_exec?: boolean\n    visible?: boolean\n  }>(),\n  {\n    source: '',\n    script_exec: true,\n    visible: true,\n  },\n)\n\nconst iframeRef = ref<HTMLIFrameElement>()\n\n// 如果不允许执行 script，就过滤掉\nconst finalSource = computed(() => {\n  if (props.script_exec) return props.source\n\n  return props.source.replace(/<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/gi, '')\n})\n\n// 如果 source 改变才刷新 iframe\nwatch(\n  () => props.source,\n  () => {\n    if (iframeRef.value) {\n      iframeRef.value.srcdoc = finalSource.value\n    }\n  },\n)\n</script>\n\n<style scoped>\n.iframe-wrapper {\n  width: 100%;\n  height: 100%;\n}\n.iframe {\n  width: 100%;\n  height: 100%;\n  border: none;\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/markdown/MdEditor.vue",
    "content": "<template>\n  <MdEditor :language=\"language\" noIconfont noPrettier v-bind=\"$attrs\">\n    <template #defFooters>\n      <slot name=\"defFooters\"> </slot>\n    </template>\n  </MdEditor>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { MdEditor, config } from 'md-editor-v3'\nimport { getBrowserLang } from '@/locales/index'\nimport './assets/markdown-iconfont.js'\n// 引入公共库中的语言配置\nimport ZH_TW from '@vavt/cm-extension/dist/locale/zh-TW'\ndefineOptions({ name: 'MdEditor' })\nconst language = computed(() => localStorage.getItem('MaxKB-locale') || getBrowserLang() || '')\nconfig({\n  editorConfig: {\n    languageUserDefined: {\n      'zh-Hant': ZH_TW,\n    },\n  },\n})\n</script>\n"
  },
  {
    "path": "ui/src/components/markdown/MdEditorMagnify.vue",
    "content": "<template>\n  <MdEditor\n    v-bind=\"$attrs\"\n    v-model=\"data\"\n    :preview=\"false\"\n    :toolbars=\"[]\"\n    class=\"magnify-md-editor\"\n    :footers=\"footers\"\n  >\n    <template #defFooters>\n      <el-button text type=\"info\" @click=\"openDialog\">\n        <AppIcon class=\"color-secondary\" iconName=\"app-magnify\" style=\"font-size: 16px\"></AppIcon>\n      </el-button>\n    </template>\n  </MdEditor>\n  <!-- 回复内容弹出层 -->\n  <el-dialog v-model=\"dialogVisible\" :title=\"title\" append-to-body align-center>\n    <MdEditor v-model=\"cloneContent\" :preview=\"false\" :toolbars=\"[]\" :footers=\"[]\"></MdEditor>\n    <template #footer>\n      <div class=\"dialog-footer mt-24\">\n        <el-button type=\"primary\" @click=\"submitDialog\"> {{ $t('common.confirm') }}</el-button>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\ndefineOptions({ name: 'MdEditorMagnify' })\nconst props = defineProps<{\n  title: string\n  modelValue: any\n}>()\nconst emit = defineEmits(['update:modelValue', 'submitDialog'])\nconst data = computed({\n  set: (value) => {\n    emit('update:modelValue', value)\n  },\n  get: () => {\n    return props.modelValue\n  }\n})\nconst dialogVisible = ref(false)\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    emit('submitDialog', cloneContent.value)\n  }\n})\n\nconst cloneContent = ref('')\nconst footers: any = [null, '=', 0]\nfunction openDialog() {\n  cloneContent.value = props.modelValue\n  dialogVisible.value = true\n}\nfunction submitDialog() {\n  emit('submitDialog', cloneContent.value)\n  dialogVisible.value = false\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.magnify-md-editor {\n  :deep(.md-editor-footer) {\n    border: none !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/markdown/MdPreview.vue",
    "content": "<template>\n  <MdPreview :language=\"language\" noIconfont noPrettier :codeFoldable=\"false\" v-bind=\"$attrs\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { MdPreview, config } from 'md-editor-v3'\nimport { getBrowserLang } from '@/locales/index'\nimport useStore from '@/stores'\n// 引入公共库中的语言配置\nimport ZH_TW from '@vavt/cm-extension/dist/locale/zh-TW'\ndefineOptions({ name: 'MdPreview' })\n\nconst emit = defineEmits(['clickPreview'])\n\nconst { user } = useStore()\nconst language = computed(() => user.getLanguage() || getBrowserLang() || '')\nconfig({\n  editorConfig: {\n    languageUserDefined: {\n      'zh-Hant': ZH_TW,\n    },\n  },\n})\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(audio) {\n  width: 300px;\n  height: 43px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/markdown/MdRenderer.vue",
    "content": "<template>\n  <div>\n    <!-- 推理过程 -->\n    <ReasoningRander v-if=\"reasoning_content?.trim()\" :content=\"reasoning_content\" />\n\n    <template v-for=\"(item, index) in mdViewList\" :key=\"index\">\n      <!-- 动态组件 -->\n      <component\n        v-if=\"componentMap[item.type]\"\n        :is=\"componentMap[item.type]\"\n        v-bind=\"getComponentProps(item)\"\n      />\n\n      <!-- 快捷问题 -->\n      <div\n        v-else-if=\"item.type === 'question'\"\n        class=\"problem-button mt-4 mb-4\"\n        :class=\"sendMessage && type !== 'log' ? 'cursor' : 'disabled'\"\n        @click=\"handleQuestionClick(item.content)\"\n      >\n        <el-space :size=\"8\" alignment=\"flex-start\">\n          <AppIcon iconName=\"app-edit\" class=\"color-primary\" style=\"margin-top: 3px\" />\n          {{ item.content }}\n        </el-space>\n      </div>\n\n      <!-- Markdown -->\n      <MdPreview v-else editorId=\"preview-only\" :modelValue=\"item.content\" class=\"maxkb-md\" />\n    </template>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { config } from 'md-editor-v3'\nimport HtmlRander from './HtmlRander.vue'\nimport EchartsRander from './EchartsRander.vue'\nimport FormRander from './FormRander.vue'\nimport ReasoningRander from './ReasoningRander.vue'\nimport IframeRender from './IframeRender.vue'\nimport ToolCallsRender from './tool-calls-render/index.vue'\nconfig({\n  markdownItConfig(md) {\n    md.renderer.rules.image = (tokens, idx, options) => {\n      tokens[idx].attrSet('style', 'display:inline-block;min-height:33px;padding:0;margin:0')\n      tokens[idx].attrSet('onerror', 'this.src=\"/load_error.png\"')\n      return md.renderer.renderToken(tokens, idx, options)\n    }\n\n    md.renderer.rules.link_open = (tokens, idx, options) => {\n      tokens[idx].attrSet('target', '_blank')\n      return md.renderer.renderToken(tokens, idx, options)\n    }\n  },\n})\n\nconst props = withDefaults(\n  defineProps<{\n    source?: string\n    reasoning_content?: string\n    sendMessage?: (question: string, type: 'old' | 'new', other_params_data?: any) => void\n    child_node?: any\n    chat_record_id?: string\n    runtime_node_id?: string\n    disabled?: boolean\n    type?: 'log' | 'ai-chat' | 'debug-ai-chat' | 'share'\n  }>(),\n  {\n    source: '',\n    disabled: false,\n  },\n)\n\ntype RenderNode = {\n  type: string\n  content: string\n}\n\ninterface TagPlugin {\n  tag: string\n  type: string\n  nested?: boolean\n  transform?: (content: string) => any\n}\n\nconst TAG_PLUGINS: TagPlugin[] = [\n  { tag: 'quick_question', type: 'question' },\n  { tag: 'html_rander', type: 'html_rander' },\n  { tag: 'iframe_render', type: 'iframe_render' },\n  { tag: 'tool_calls_render', type: 'tool_calls_render' },\n  {\n    tag: 'echarts_rander',\n    type: 'echarts_rander',\n    transform: (c) => {\n      try {\n        return JSON.parse(c)\n      } catch {\n        return c\n      }\n    },\n  },\n  { tag: 'form_rander', type: 'form_rander', nested: true },\n]\n\nfunction parseByPlugin(source: string, plugin: TagPlugin): RenderNode[] {\n  const startTag = `<${plugin.tag}>`\n  const endTag = `</${plugin.tag}>`\n\n  if (!source.includes(startTag)) {\n    return [{ type: 'md', content: source }]\n  }\n\n  const result: RenderNode[] = []\n  let cursor = 0\n\n  while (cursor < source.length) {\n    const start = source.indexOf(startTag, cursor)\n\n    if (start === -1) {\n      result.push({\n        type: 'md',\n        content: source.slice(cursor),\n      })\n      break\n    }\n\n    if (start > cursor) {\n      result.push({\n        type: 'md',\n        content: source.slice(cursor, start),\n      })\n    }\n\n    let end = source.indexOf(endTag, start)\n    if (end === -1) break\n\n    // 处理嵌套\n    if (plugin.nested) {\n      let depth = 1\n      let tempIndex = start + startTag.length\n\n      while (depth > 0) {\n        const nextStart = source.indexOf(startTag, tempIndex)\n        const nextEnd = source.indexOf(endTag, tempIndex)\n\n        if (nextStart !== -1 && nextStart < nextEnd) {\n          depth++\n          tempIndex = nextStart + startTag.length\n        } else {\n          depth--\n          tempIndex = nextEnd + endTag.length\n          end = nextEnd\n        }\n      }\n    }\n\n    let content = source.slice(start + startTag.length, end)\n\n    if (plugin.transform) {\n      content = plugin.transform(content)\n    }\n\n    result.push({\n      type: plugin.type,\n      content,\n    })\n\n    cursor = end + endTag.length\n  }\n\n  return result\n}\n\nfunction parseContent(source: string): RenderNode[] {\n  let nodes: RenderNode[] = [{ type: 'md', content: source }]\n\n  TAG_PLUGINS.forEach((plugin) => {\n    nodes = nodes.flatMap((node) => {\n      if (node.type !== 'md') return node\n      return parseByPlugin(node.content, plugin)\n    })\n  })\n\n  return nodes\n}\n\nconst mdViewList = computed(() => {\n  return parseContent(props.source || '')\n})\n\nconst componentMap: Record<string, any> = {\n  html_rander: HtmlRander,\n  echarts_rander: EchartsRander,\n  form_rander: FormRander,\n  iframe_render: IframeRender,\n  tool_calls_render: ToolCallsRender,\n}\n\nfunction getComponentProps(item: RenderNode) {\n  switch (item.type) {\n    case 'form_rander':\n      return {\n        chat_record_id: props.chat_record_id,\n        runtime_node_id: props.runtime_node_id,\n        child_node: props.child_node,\n        disabled: props.disabled,\n        sendMessage: props.sendMessage,\n        form_setting: item.content,\n      }\n\n    case 'echarts_rander':\n      return { option: item.content }\n    case 'html_rander':\n      return { source: item.content }\n    case 'iframe_render':\n      return { source: item.content }\n    case 'tool_calls_render':\n      return { content: item.content }\n\n    default:\n      return {}\n  }\n}\n\nfunction handleQuestionClick(content: string) {\n  if (!props.sendMessage) return\n  if (props.type === 'log') return\n  props.sendMessage(content, 'new')\n}\n</script>\n\n<style scoped lang=\"scss\">\n.problem-button {\n  width: 100%;\n  border-radius: 8px;\n  background: var(--app-layout-bg-color);\n  padding: 12px;\n  box-sizing: border-box;\n  word-break: break-all;\n\n  &:hover {\n    background: var(--el-color-primary-light-9);\n  }\n\n  &.disabled:hover {\n    background: var(--app-layout-bg-color);\n  }\n}\n\n</style>\n"
  },
  {
    "path": "ui/src/components/markdown/ReasoningRander.vue",
    "content": "<template>\n  <el-card shadow=\"never\" class=\"reasoning mt-8\" style=\"--el-card-padding: 12px\">\n    <div class=\"flex-between cursor\" @click=\"showThink = !showThink\">\n      <div class=\"flex align-center\" style=\"line-height: 20px\">\n        <img src=\"@/assets/chat/icon_reasoning.svg\" alt=\"\" />\n        <span class=\"ml-4\">{{ $t('workflow.nodes.aiChatNode.think') }}</span>\n      </div>\n      <div>\n        <el-icon class=\"arrow-icon\" :class=\"showThink ? 'rotate-180' : ''\">\n          <ArrowDown />\n        </el-icon>\n      </div>\n    </div>\n    <el-collapse-transition>\n      <div v-show=\"showThink\">\n        <MdPreview\n          ref=\"editorRef\"\n          editorId=\"preview-only\"\n          :modelValue=\"content\"\n          class=\"reasoning-md\"\n        />\n      </div>\n    </el-collapse-transition>\n  </el-card>\n</template>\n<script lang=\"ts\" setup>\nimport { ref } from 'vue'\nconst props = defineProps<{ content?: string }>()\nconst showThink = ref<boolean>(true)\n</script>\n<style lang=\"scss\" scoped>\n.reasoning {\n  .reasoning-md {\n    --md-color: var(--app-input-color-placeholder) !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/markdown/assets/markdown-iconfont.js",
    "content": "window._iconfont_svg_string_2605852='<svg><symbol id=\"md-editor-icon-col-resize\" viewBox=\"0 0 1024 1024\"><path d=\"M661.63378906 895.64257813L661.63378906 128.35742187c0-21.09375 17.13867188-38.23242188 38.23242188-38.23242187 21.09375 0 38.23242188 17.13867188 38.23242187 38.23242188l0 766.62597656c0 21.09375-17.13867188 38.23242188-38.23242187 38.23242187-21.09375 0.65917969-38.23242188-16.47949219-38.23242188-37.57324218zM474.42675781 895.64257813L474.42675781 128.35742187c0-21.09375 17.13867188-38.23242188 38.23242188-38.23242187C534.41210938 90.125 551.55078125 107.26367187 551.55078125 128.35742187l0 766.62597657c0 21.09375-17.13867188 38.23242188-38.23242188 38.23242187-21.75292969 0.65917969-38.89160156-16.47949219-38.89160156-37.57324219zM287.87890625 895.64257813L287.87890625 128.35742187C287.87890625 107.26367187 305.01757812 90.125 326.11132812 90.125c21.09375 0 38.23242188 17.13867188 38.23242188 38.23242188l1e-8 766.62597656c0 21.09375-17.13867188 38.23242188-38.23242188 38.23242187-21.09375 0.65917969-38.23242188-16.47949219-38.23242188-37.57324218z\"  ></path></symbol><symbol id=\"md-editor-icon-jump\" viewBox=\"0 0 1024 1024\"><path d=\"M280.25714112 790.09143067a46.39492035 46.39492035 0 0 1-46.34857179-46.34857179V280.25714112a46.39492035 46.39492035 0 0 1 46.34857179-46.34857179h208.56857299v46.34857179H280.25714112v463.48571776h463.48571776V535.17428589h46.34857179V743.74285888a46.39492035 46.39492035 0 0 1-46.34857179 46.34857179H280.25714112zM558.34857177 233.90856933v46.34857179h152.62584688L512 479.23155975 544.76844025 512 743.74285888 313.02558135V465.65142823h46.34857179V257.08285523a23.17428589 23.17428589 0 0 0-23.1742859-23.1742859H558.34857177z\" fill=\"#000000\" fill-opacity=\".9\" ></path></symbol><symbol id=\"md-editor-icon-copy\" viewBox=\"0 0 1024 1024\"><path d=\"M717.2421875 679.5265625a31.89375 31.89375 0 0 1 0-63.7875 30.8390625 30.8390625 0 0 0 30.8390625-30.8390625V308.4875a30.8390625 30.8390625 0 0 0-30.8390625-30.8390625H440.8296875a30.8390625 30.8390625 0 0 0-30.8390625 30.8390625 31.89375 31.89375 0 1 1-63.7875 0 94.8375 94.8375 0 0 1 94.6265625-94.6265625h276.4125a94.8375 94.8375 0 0 1 94.6265625 94.6265625v276.4125a94.8375 94.8375 0 0 1-94.6265625 94.6265625z\"  ></path><path d=\"M575.8296875 817.775H311.103125a99.815625 99.815625 0 0 1-99.9421875-99.9421875v-265.78125a99.815625 99.815625 0 0 1 99.9421875-99.984375h264.7265625a99.815625 99.815625 0 0 1 99.9421875 99.9421875v265.78125c1.0546875 55.3078125-44.634375 99.984375-99.9421875 99.984375zM311.103125 415.8546875a35.859375 35.859375 0 0 0-36.1546875 36.1546875v265.78125a35.859375 35.859375 0 0 0 36.1546875 36.1546875h264.7265625a35.859375 35.859375 0 0 0 36.1546875-36.1546875v-265.78125a35.859375 35.859375 0 0 0-36.1546875-36.1546875z\"  ></path></symbol><symbol id=\"md-editor-icon-task\" viewBox=\"0 0 1024 1024\"><path d=\"M672.32 672v68.8A59.52 59.52 0 0 1 612.48 800H283.52A59.52 59.52 0 0 1 224 740.48V411.52A59.52 59.52 0 0 1 283.52 352H352V283.52A59.52 59.52 0 0 1 411.52 224h329.28a59.52 59.52 0 0 1 59.52 59.52v329.28A59.52 59.52 0 0 1 740.48 672h-68.16z m0-64H736V288H416v64h196.8a59.52 59.52 0 0 1 59.52 59.52V608zM288 736h320V416H288v320z m128-64a32 32 0 0 1-22.72-9.28l-64-64a32 32 0 0 1 45.12-45.12l41.28 41.28 105.28-105.28a32 32 0 0 1 45.12 45.12l-128 128A32 32 0 0 1 416 672z\"  ></path></symbol><symbol id=\"md-editor-icon-formula\" viewBox=\"0 0 1024 1024\"><path d=\"M539.21819878 548.59683227L575.84284019 512l-36.62464141-36.59683227L361.69158172 298.02254868l374.1024971-0.20161629v-51.77367211l-447.53949164 0.24333001v51.7736721L502.59355736 512 288.20592117 726.20687676v51.50253295l447.53949166 0.24333001v-51.77367211l-374.1024971-0.20161629 177.57528304-177.38061905\"  ></path></symbol><symbol id=\"md-editor-icon-mermaid\" viewBox=\"0 0 1024 1024\"><path d=\"M812.40545039 771.45579978L724.78666362 771.45579978c-13.82543327 0-25.03357536-11.18650107-25.03357537-24.98201882L699.75308825 671.52263255c0-13.79742724 11.2081421-24.98201881 25.03357538-24.98201881l25.03357535 0L749.82023898 534.11611888c0-13.79870023-11.20686909-24.9832918-25.03357536-24.9832918L524.51615118 509.13282707l0 137.40778668 25.03484838-1e-8c13.82543327 0 25.03357536 11.18459157 25.03357536 24.98201882l1e-8 74.95178492c0 13.79551773-11.2081421 24.98201881-25.03357538 24.9820188L461.93284926 771.45643628c-13.82670626 0-25.03357536-11.18650107-25.03357536-24.9820188L436.8992739 671.52263255c0-13.79742724 11.20686909-24.98201881 25.03357537-24.98201881l25.03357537 0 0-137.40778666L299.21333638 509.13282707c-13.82543327 0-25.03357536 11.18459157-25.03357536 24.98329181l0 112.42449486 25.03357536 1e-8c13.82543327 0 25.03357536 11.18459157 25.03357537 24.9820188l0 74.95178493c0 13.79551773-11.2081421 24.98201881-25.03357537 24.98201879L211.59454961 771.45643628c-13.82543327 0-25.03357536-11.18650107-25.03357538-24.9820188L186.56097423 671.52263255c0-13.79742724 11.2081421-24.98201881 25.03357538-24.98201881l25.03357536 0L236.62812497 521.62351822c0-27.59549098 22.41692069-49.96658362 50.06906023-49.9665836L486.96642464 471.65693462 486.96642464 334.25105744l-25.03357538 0c-13.82670626 0-25.03357536-11.18650107-25.03357536-24.98456481L436.8992739 234.3172537c0-13.79870023 11.20686909-24.9832918 25.03357537-24.98329179l87.61878679 0c13.82543327 0 25.03357536 11.18459157 25.03357536 24.9832918l0 74.94860241c0 13.79870023-11.2081421 24.98456481-25.03357536 24.98456482l-25.03484837 0 0 137.40651368 212.78793662 0c27.65023005 0 50.06715074 22.37109264 50.06715072 49.96658361l0 124.91645902 25.03357537 0c13.82543327 0 25.03357536 11.18459157 25.03357537 24.9820188l0 74.95178491C837.43902577 760.26993521 826.23024717 771.45579978 812.40545039 771.45579978z\"  ></path></symbol><symbol id=\"md-editor-icon-layout-one\" viewBox=\"0 0 1024 1024\"><path d=\"M729.2589302 200.59553315A65.17767906 65.17767906 0 0 1 794.43660927 265.77321222v492.45357557a65.17767906 65.17767906 0 0 1-65.17767907 65.17767906H251.28928375A21.72589302 21.72589302 0 0 1 229.56339073 801.67857383V222.32142617A21.72589302 21.72589302 0 0 1 251.28928375 200.59553315z m-325.88839531 43.45178604h-130.35535811v535.90536162h130.35535811v-535.90536162z m325.88839531 0H446.82232094v535.90536162H729.2589302a21.72589302 21.72589302 0 0 0 21.62450575-19.64020728L750.98482322 758.22678778V265.77321222A21.72589302 21.72589302 0 0 0 729.2589302 244.04731919z m-57.93571448 217.2589302a21.72589302 21.72589302 0 0 1 2.08568572 43.35039877L671.32321572 504.75803544H526.48392845a21.72589302 21.72589302 0 0 1-2.08568572-43.35039809L526.48392845 461.30624939h144.83928727z m0-130.35535812a21.72589302 21.72589302 0 0 1 2.08568572 43.35039876L671.32321572 374.40267732H526.48392845a21.72589302 21.72589302 0 0 1-2.08568572-43.3503981L526.48392845 330.95089127h144.83928727z\"  ></path></symbol><symbol id=\"md-editor-icon-delete\" viewBox=\"0 0 1024 1024\"><path d=\"M515.79524194 179.05679987c-68.09641701 0-123.36437311 53.98477055-125.71620103 121.76005169H231.33065634c-17.63803047 0-32.06945491 14.32483177-32.0694549 32.07013384 0 17.63870941 14.32483177 32.07081277 32.07013384 32.07081277h35.38401145v358.75967145c0 67.34755264 45.64678017 122.40164448 102.09132918 122.40164447h287.56256215c56.33795634 0 102.09132917-54.73295599 102.09132917-122.40164447V365.17166244h32.07013384c17.63870941 0 32.07013383-14.32551071 32.07013383-32.07081278 0-17.63870941-14.32483177-32.07013383-32.07013383-32.07013384H641.29689977C639.26552878 233.04224935 583.89098001 179.0574788 515.79524194 179.0574788z m-65.63731748 121.76005169c2.35182792-34.74241867 30.68103455-61.68116815 65.74391014-61.68116814s63.39208222 27.04534214 65.42345322 61.68116814H450.15792446z m-81.45852015 485.33065058c-19.99053732 0-42.01244563-25.65624285-42.01244564-62.43003252V365.17166244h371.58813236V724.03792655c0 36.66719701-22.02190831 62.43071146-42.01244565 62.43071145H368.69940431v-0.32113586z m0 0\"  ></path><path d=\"M411.7804923 709.07218092c14.64460976 0 26.72488523-14.6452887 26.72488522-33.03218354V487.57330692c0-18.38689484-11.97300385-33.03286248-26.72488522-33.03286248-14.64596763 0-26.72556416 14.64596763-26.72556416 33.03286248v188.46669046c0 18.27962325 11.7591396 33.03218354 26.72556416 33.03218355z m96.74540162 1e-8c14.6452887 0 26.72488523-14.6452887 26.72488522-33.03218355V487.57330692c0-18.38689484-11.97300385-33.03286248-26.72488522-33.03286248-14.6452887 0-26.72556416 14.64596763-26.72556415 33.03286248v188.46669046c0 18.27962325 12.08027546 33.03218354 26.72556415 33.03218355z m101.76951438 0c14.6452887 0 26.72556416-14.6452887 26.72556415-33.03218355V487.57330692c0-18.38689484-11.97300385-33.03286248-26.72556415-33.03286248-14.6452887 0-26.72488523 14.64596763-26.72488522 33.03286248v188.46669046c0 18.27962325 11.651868 33.03218354 26.72488522 33.03218355z m0 0\"  ></path></symbol><symbol id=\"md-editor-icon-upload\" viewBox=\"0 0 1024 1024\"><path d=\"M737 371.375a196.875 196.875 0 0 1 0 393.75h-28.125a28.125 28.125 0 0 1 0-56.25h28.125a140.625 140.625 0 0 0 0-281.25 129.375 129.375 0 0 0-24.46875 2.25 28.125 28.125 0 0 1-33.46875-24.1875 168.75 168.75 0 0 0-334.125 0 28.125 28.125 0 0 1-33.46875 24.1875A129.375 129.375 0 0 0 287 427.625a140.625 140.625 0 0 0 0 281.25h28.125a28.125 28.125 0 0 1 0 56.25H287a196.875 196.875 0 0 1 0-393.75h7.3125a225 225 0 0 1 435.375 0z\" fill=\"#808080\" ></path><path d=\"M540.125 849.5a28.125 28.125 0 0 1-56.25 0v-253.125a28.125 28.125 0 0 1 56.25 0z\" fill=\"#808080\" ></path><path d=\"M434.65625 727.4375a28.125 28.125 0 1 1-42.1875-37.125l98.4375-112.5a28.125 28.125 0 0 1 42.1875 0l98.4375 112.5a28.125 28.125 0 0 1-42.1875 37.125L512 639.125z\" fill=\"#808080\" ></path></symbol><symbol id=\"md-editor-icon-close\" viewBox=\"0 0 1024 1024\"><path d=\"M249.6376475 722.01411781l203.49176437-203.49176531-203.49176437-203.52 52.17882281-52.17882281 203.52 203.52 203.52-203.52 52.17882375 52.20705844-203.49176437 203.49176437 203.46352875 203.49176531-52.17882282 52.17882375-203.52-203.49176531-203.49176531 203.49176531-52.17882281-52.17882375z\"  ></path></symbol><symbol id=\"md-editor-icon-prettier\" viewBox=\"0 0 1024 1024\"><path d=\"M211.4140625 248.98730469h601.171875c-7.11914063 0-12.65625-5.53710938-12.65625-12.65625v551.33789062c0-7.11914063 5.53710938-12.65625 12.65625-12.65625H211.4140625c7.11914063 0 12.65625 5.53710938 12.65625 12.65625V236.7265625c0 6.328125-5.14160156 12.26074219-12.65625 12.26074219z m-25.3125 528.79394531c0 20.17089844 16.21582031 36.38671875 36.38671875 36.38671875h579.41894531c20.17089844 0 36.38671875-16.21582031 36.38671875-36.38671875V246.21875c0-20.17089844-16.21582031-36.38671875-36.38671875-36.38671875H222.48828125c-20.17089844 0-36.38671875 16.21582031-36.38671875 36.38671875v531.5625z\"  ></path><path d=\"M614.43652344 542.05859375v-34.8046875l-64.86328125 43.50585938 64.86328125 43.50585937V559.4609375zM722.80566406 464.14355469v101.64550781h-108.36914062v-29.26757813H693.93359375V464.14355469zM309.89550781 421.03320312h249.9609375v43.50585938H309.89550781zM309.89550781 532.96191406h192.21679688v43.50585938H309.89550781zM309.89550781 309.5h394.71679688v43.50585938H309.89550781zM309.89550781 644.890625h394.71679688v43.50585938H309.89550781z\"  ></path></symbol><symbol id=\"md-editor-icon-revoke\" viewBox=\"0 0 1024 1024\"><path d=\"M709.05928138 754.60218462a13.58411482 13.58411482 0 0 1-13.54609472-14.38797265c2.07482258-34.75056609-2.28665003-83.43829236-34.85919514-118.0150511-36.83625182-39.10660745-102.99159607-52.54407248-194.82151398-39.50310477v107.38565752a13.57868292 13.57868292 0 0 1-23.17609681 9.6028445L239.10105777 496.12923514a13.56782041 13.56782041 0 0 1 0-19.20025774l203.55532297-203.5553236a13.56782041 13.56782041 0 0 1 23.1760968 9.60284512V378.16849919c95.28976679 0.92878193 166.90374165 15.58832844 218.73085904 44.73361354 61.22899822 34.43554022 95.41469088 90.29824307 101.60113914 166.03470614 4.31802124 52.87539214-36.97746971 128.96490117-67.17103018 161.34191262a13.54609409 13.54609409 0 0 1-9.93416417 4.32345313zM529.40787157 550.67752126c67.13844069 0 117.94987342 17.79350638 151.01668239 52.89711782 27.24426985 28.91716382 38.15066794 65.03646055 41.47473019 97.83169628 21.83995374-34.84290073 39.61716572-80.67910352 37.20559159-110.25890729-10.3578197-126.80317462-106.65784052-185.46851705-303.04905007-185.46851704l-3.76401095 0.40736049H452.25379398c-3.5956355 0-7.03918933-1.81954375-9.58655009-4.36147324-2.55822391-2.54736075-3.99213284-6.40913824-3.99213283-10.01563625V315.7608715L267.90416064 486.5318219 438.67511105 657.30277231V569.68767723c0-7.4737074 6.03979847-11.52015477 13.51350526-11.52015477h0.96137076c1.72177723 0 3.07421407-1.01025402 4.18766588-0.98852772l1.67289397-0.49426448 0.59203037-0.27157324c24.80553815-3.63365561 48.31838587-5.73563577 69.80529428-5.73563576z\"  ></path></symbol><symbol id=\"md-editor-icon-next\" viewBox=\"0 0 1024 1024\"><path d=\"M316.87975457 753.70055965c-3.68797013 0-7.31076321-1.04827413-9.93416417-3.86720876-33.80548974-36.24422082-68.66468488-112.71936467-64.71600404-161.11379136 14.88223651-182.26937949 180.49871902-208.76953731 310.5010318-210.50217706V282.97649892a13.57868292 13.57868292 0 0 1 23.18152742-9.60284512l203.55532361 203.5553236a13.56782041 13.56782041 0 0 1 0 19.20025774L575.91757748 699.68455812a13.57868292 13.57868292 0 0 1-23.18152807-9.6028445V582.02255299c-88.61991785-11.81345433-152.26592144 1.5642643-187.86379674 39.3618869-31.77411855 33.72944891-36.40716502 81.98808899-34.53330737 117.15144685 0.14664978 0.80385784 0.22812188 1.18949265 0.22812188 2.03680246 0 7.49543309-6.07238731 13.12787107-13.56238851 13.12787045h-0.1249241z m250.04873446-347.61428813c-0.1629442 0-0.33675155-0.39649733-0.505127-0.39106607-195.03877333 0.20096431-286.64600125 57.31833747-297.12331318 185.65861881-2.41157412 29.57980313 14.28477487 74.9380365 34.71797726 109.17804369 3.99756411-39.78010991 17.91299859-73.11849276 41.0836635-97.71763533 32.5562507-34.54960117 82.71047472-52.10412315 149.0667839-52.10412253 21.42716198 0 44.91285214 1.83040627 69.79986238 5.47492505l1.34700558-0.03802073a13.58954608 13.58954608 0 0 1 14.58350569 13.54609471v87.61509509l170.77095041-170.77095104L579.8934159 315.7608715v70.52224875c0.71695447 1.6457364 1.92274152 3.45441698 1.92274153 5.3608641 0.02172568 4.53527996-1.92274152 13.03010455-12.78568825 13.61127177v0.8310154h-2.10198015z\"  ></path></symbol><symbol id=\"md-editor-icon-sup\" viewBox=\"0 0 1024 1024\"><path d=\"M401.58302857 293.51116466L244.06698906 726.81901058h69.54765466l33.27904506-96.88337376h164.35444469L544.83150545 726.81901058h69.24332776L456.55879369 293.51116466H401.58302857zM365.92307279 571.68388177l62.70924952-189.30925308h1.18150456l62.70924951 189.30925308H365.92307279zM699.96663427 601.50792108L619.40950529 726.81901058h161.11425794zM658.8466953 365.13540311a186.98204713 186.98204713 0 0 0-41.10203733 44.7897637c-10.83045846 16.72007966-16.23673687 34.12041952-16.23673689 52.20101956h161.11425793v-44.80766529h-88.77395612c3.29389149-6.03283387 12.74592797-15.198445 28.3561094-27.53263651 21.91153908-17.25712718 36.71614922-31.30987079 44.39592885-42.12242766 7.67977963-10.81255689 11.51071865-23.3615674 11.51071865-37.61122844 0-19.99606957-6.64148775-36.08959379-19.94236482-48.2984742S707.00195686 243.47623678 684.53546867 243.47623678c-43.28603063 0-69.86988319 19.17259671-79.73365609 57.53569167l53.43622887 8.62856359c3.29389149-14.24966103 10.95576954-21.37449155 23.0214373-21.37449155 6.03283387 0 11.02737588 1.9154695 15.00152758 5.76431011 3.97415169 3.83093903 5.96122755 8.89708735 5.96122754 15.19844501 0 8.21682714-3.07907248 16.30834322-9.25511903 24.2566466-6.17604655 7.9483034-17.54355253 18.4923365-34.12041954 31.6500009z\"  ></path></symbol><symbol id=\"md-editor-icon-italic\" viewBox=\"0 0 1024 1024\"><path d=\"M664.76018531 257.6733865h-171.85520848a19.09502317 19.09502317 0 0 0 0 38.19004632h57.47601974l-137.44597674 420.09050958H359.23981469a19.09502317 19.09502317 0 0 0 0 38.19004633h171.85520848a19.09502317 19.09502317 0 0 0 0-38.19004633h-55.92932285l137.56054686-420.09050958H664.76018531a19.09502317 19.09502317 0 0 0 0-38.19004632z\"  ></path></symbol><symbol id=\"md-editor-icon-sub\" viewBox=\"0 0 1024 1024\"><path d=\"M762.62217901 279.2794052h-161.11425793l80.55712896 125.31108951zM401.58302856 275.60958042L244.06698906 708.91742636h69.54765466l33.27904506-96.88337377h164.35444469L544.83150545 708.91742636h69.24332775L456.55879368 275.60958042H401.58302856zM365.92307279 553.78229757l62.70924952-189.30925308h1.18150456l62.70924951 189.30925308H365.92307279zM691.7498071 721.66335433c3.29389149-6.03283387 12.74592797-15.198445 28.3561094-27.53263652 21.91153908-17.25712718 36.71614922-31.3098708 44.39592885-42.12242766 7.67977963-10.81255688 11.51071865-23.3615674 11.51071865-37.61122844 0-19.99606957-6.64148775-36.08959379-19.9423648-48.29847422S724.90354107 547.80316842 702.43705288 547.80316842c-43.28603063 0-69.86988319 19.17259671-79.7336561 57.53569169l53.43622888 8.62856357c3.29389149-14.24966104 10.95576954-21.37449155 23.02143731-21.37449154 6.03283387 0 11.02737588 1.9154695 15.00152756 5.76431011 3.97415169 3.83093903 5.96122755 8.89708735 5.96122754 15.19844501 0 8.21682714-3.07907248 16.30834322-9.25511902 24.25664661-6.15814497 7.9483034-17.52565094 18.4923365-34.12041952 31.65000089a186.98204712 186.98204712 0 0 0-41.10203736 44.78976371c-10.83045846 16.72007966-16.23673688 34.12041952-16.23673688 52.20101956h161.11425793v-44.80766529h-88.77395611z\"  ></path></symbol><symbol id=\"md-editor-icon-preview\" viewBox=\"0 0 1024 1024\"><path d=\"M512 790.09143067C337.63667297 790.09143067 164.38571167 638.02176667 164.38571167 512S337.63667297 233.90856933 512 233.90856933c174.50237274 0 347.61428833 151.79157258 347.61428833 278.09143067s-173.11191561 278.09143067-347.61428833 278.09143067z m0-46.34857179c150.16937257 0 301.26571655-132.51056672 301.26571655-231.74285888S662.16937257 280.25714112 512 280.25714112C362.01602173 280.25714112 210.73428345 412.99945068 210.73428345 512s151.28173828 231.74285888 301.26571655 231.74285888z m0-92.69714355a139.04571533 139.04571533 0 1 1 0-278.09143066 139.04571533 139.04571533 0 0 1 0 278.09143066z m0-46.34857178a92.69714356 92.69714356 0 1 0 0-185.3942871 92.69714356 92.69714356 0 0 0 0 185.3942871z\"  ></path></symbol><symbol id=\"md-editor-icon-coding\" viewBox=\"0 0 1024 1024\"><path d=\"M729.27590356 801.6842314H294.72409644a72.40832783 72.40832783 0 0 1-72.40832784-72.40832784V294.72409644A72.40832783 72.40832783 0 0 1 294.72409644 222.3157686h434.55180712a72.40832783 72.40832783 0 0 1 72.40832784 72.40832784v434.55180712a72.40832783 72.40832783 0 0 1-72.40832784 72.40832784z m36.61152442-510.26993897a34.82932226 34.82932226 0 0 0-36.25508398-33.30172041H294.36765601a34.82932226 34.82932226 0 0 0-36.25508399 33.30172041v39.92132842h507.77485596z m0 75.7690519H258.11257202v361.83795892a36.50968429 36.50968429 0 0 0 36.25508398 36.86612473h435.26468799a36.50968429 36.50968429 0 0 0 36.25508399-36.86612473zM646.7854035 661.39946122a15.73429909 15.73429909 0 0 1-22.04838675 0 15.27601854 15.27601854 0 0 1 0-21.6919463l80.25001735-65.48319944-80.25001735-65.43227937a15.27601854 15.27601854 0 0 1 0-21.74286638 15.83613921 15.83613921 0 0 1 22.04838675 0l92.5726723 75.56537166a17.51650124 17.51650124 0 0 1 0 23.27046824z m-156.78287018 46.33725622c-4.27728519 7.28156883-11.15149353 8.50365032-21.13182564 5.60120679-8.14720987-2.3932429-10.18401235-13.5956565-5.65212685-20.92814539l77.29665375-259.69231501s13.64657656-9.82757192 21.08090558-5.60120679a15.27601854 15.27601854 0 0 1 5.65212686 20.87722532z m-90.79047013-46.33725622a15.68337903 15.68337903 0 0 1-21.99746669 0L284.6419242 585.88500963a17.51650124 17.51650124 0 0 1 0-23.27046824L377.2145965 487.04916973a15.78521916 15.78521916 0 0 1 21.99746669 0 15.27601854 15.27601854 0 0 1 0 21.74286638L319.0129659 574.22431548l80.19909729 65.48319944a15.27601854 15.27601854 0 0 1 0 21.6919463z\"  ></path></symbol><symbol id=\"md-editor-icon-underline\" viewBox=\"0 0 1024 1024\"><path d=\"M737.94928742 744.79869207H286.05071258c-3.18646431 0-5.79357148 2.46226788-5.79357146 5.50389289v44.03114319c0 3.04162503 2.60710716 5.5038929 5.79357147 5.5038929h451.89857483c3.18646431 0 5.79357148-2.46226788 5.79357146-5.5038929v-44.03114319c0-3.04162503-2.60710716-5.5038929-5.79357146-5.50389289z m-225.94928742-55.038929c50.25923252 0 97.47684002-19.62572336 133.10730458-55.18376827C680.73776912 599.0179499 700.29107285 551.72792276 700.29107285 501.46869024V275.51940282c0-4.77969646-3.91066074-8.69035719-8.69035722-8.69035721h-43.45178604c-4.77969646 0-8.69035719 3.91066074-8.6903572 8.69035721v225.94928742c0 70.2470541-57.21151829 127.45857239-127.45857239 127.45857238s-127.45857239-57.21151829-127.45857239-127.45857238V275.51940282c0-4.77969646-3.91066074-8.69035719-8.6903572-8.69035721h-43.45178605c-4.77969646 0-8.69035719 3.91066074-8.69035721 8.69035721v225.94928742c0 50.25923252 19.62572336 97.47684002 55.18376827 133.10730457C414.45074033 670.20645936 461.74076748 689.75976308 512 689.75976308z\"  ></path></symbol><symbol id=\"md-editor-icon-fangda\" viewBox=\"0 0 1024 1024\"><path d=\"M482.03564834 541.96435166a23.17428589 23.17428589 0 0 1 1.92346573 30.59005737l-1.92346573 2.17838287L336.22304153 720.568573l105.72109223 0.02317427a23.17428589 23.17428589 0 0 1 23.01206588 20.46289445l0.16222001 2.71139145a23.17428589 23.17428589 0 0 1-20.48606872 23.01206589l-2.68821717 0.16222H280.25714112l-1.73807145-0.06952286-2.91996002-0.39396285-2.57234574-0.69522858-2.57234572-1.01966859-2.27108002-1.20506286-2.22473145-1.55267715-2.08568573-1.85394287a23.40602875 23.40602875 0 0 1-2.24790573-2.59552002l-1.6453743-2.54917146-1.25141143-2.64186859-0.81110002-2.43330001-0.69522858-3.47614289L257.08285523 743.74285888v-162.31269836a23.17428589 23.17428589 0 0 1 46.18635177-2.68821716l0.16222 2.68821716-0.02317427 106.34679795 145.85895537-145.81260681a23.17428589 23.17428589 0 0 1 32.76844024 0zM582.05586624 257.08285523H743.74285888l1.73807145 0.06952285 2.91996002 0.39396286 2.57234574 0.69522859 2.57234572 1.01966857 2.27108002 1.20506287 2.22473145 1.55267715 2.08568573 1.85394287c0.83427429 0.81110001 1.57585145 1.69172287 2.24790573 2.59552002l1.6453743 2.54917145 1.25141143 2.64186859 0.81110002 2.43330001 0.69522858 3.47614289L766.91714477 280.25714112v162.31269836a23.17428589 23.17428589 0 0 1-46.18635177 2.68821716L720.568573 442.56983948v-106.36997224l-145.83578111 145.8357811a23.17428589 23.17428589 0 0 1-34.69190596-30.59005737l1.92346573-2.17838287L687.75378418 303.431427h-105.67474366a23.17428589 23.17428589 0 0 1-23.03524017-20.46289443L558.88158035 280.25714112a23.17428589 23.17428589 0 0 1 20.48606872-23.0120659L582.05586624 257.08285523z\"  ></path></symbol><symbol id=\"md-editor-icon-suoxiao\" viewBox=\"0 0 1024 1024\"><path d=\"M303.96443558 535.17428589H465.65142823l1.73807144 0.06952285 2.91996001 0.39396287 2.57234574 0.69522858 2.57234573 1.01966857 2.27108002 1.20506288 2.22473145 1.55267715 2.08568572 1.85394287c0.83427429 0.81110001 1.57585145 1.69172287 2.24790574 2.59552002l1.64537429 2.54917143 1.25141144 2.6418686 0.81110001 2.43330002 0.69522859 3.47614288L488.82571411 558.34857177v162.31269837a23.17428589 23.17428589 0 0 1-46.18635178 2.68821717L442.47714234 720.66127014v-106.36997223l-145.83578111 145.83578109a23.17428589 23.17428589 0 0 1-34.69190596-30.59005737l1.92346573-2.17838286L409.66235353 581.52285766h-105.67474366a23.17428589 23.17428589 0 0 1-23.0352402-20.46289443L280.79014969 558.34857177a23.17428589 23.17428589 0 0 1 20.48606873-23.01206588L303.96443558 535.17428589zM760.12707901 263.87292099a23.17428589 23.17428589 0 0 1 1.92346572 30.59005738l-1.92346572 2.17838286L614.3144722 442.47714234l105.72109222 0.02317428a23.17428589 23.17428589 0 0 1 23.01206589 20.46289444l0.16222 2.71139145a23.17428589 23.17428589 0 0 1-20.48606873 23.01206588l-2.68821716 0.16222002-163.42506409-0.06952287-2.91996001-0.39396286-2.57234574-0.69522858-2.57234573-1.01966857-2.27108002-1.20506287-2.22473145-1.55267716-2.08568572-1.85394287a23.40602875 23.40602875 0 0 1-2.24790574-2.59552002l-1.64537429-2.54917145-1.25141144-2.64186859-0.81110001-2.43330001-0.69522857-3.47614289L535.17428589 465.65142823V303.33872986a23.17428589 23.17428589 0 0 1 46.18635178-2.68821717l0.16221999 2.68821717v106.32362365l145.83578111-145.78943252a23.17428589 23.17428589 0 0 1 32.76844023 0z\"  ></path></symbol><symbol id=\"md-editor-icon-baocun\" viewBox=\"0 0 1024 1024\"><path d=\"M695.19287847 245.2663952H328.80712153c-46.06674339 0-83.54072633 37.47398295-83.54072633 83.54072634v366.38575693c0 46.06674339 37.47398295 83.54072633 83.54072634 83.54072633h366.38575693c46.06674339 0 83.54072633-37.47398295 83.54072633-83.54072634V328.80712153c0-46.06674339-37.47398295-83.54072633-83.54072634-83.54072633z m-274.13292629 47.7375579h181.88009564v155.62443878c0 18.85633538-15.33569047 34.2516978-34.25169781 34.25169782H455.25197803c-18.85633538 0-34.2516978-15.33569047-34.2516978-34.25169782V293.0039531zM730.9960469 695.19287847c0 19.7514146-16.05175385 35.80316842-35.80316844 35.80316843H328.80712153c-19.7514146 0-35.80316842-16.05175385-35.80316843-35.80316844V328.80712153c0-19.7514146 16.05175385-35.80316842 35.80316844-35.80316843h44.51527274v155.62443878c0 45.17166417 36.75791959 81.9892557 81.98925571 81.9892557h113.43637198c45.17166417 0 81.9892557-36.75791959 81.9892557-81.9892557V293.0039531H695.19287847c19.7514146 0 35.80316842 16.05175385 35.80316843 35.80316844v366.38575693z\"  ></path><path d=\"M549.17562323 441.05005455c13.18750038 0 23.86877897-10.68127858 23.86877894-23.86877894v-55.97228664c0-13.18750038-10.68127858-23.86877897-23.86877894-23.86877898s-23.86877897 10.68127858-23.86877896 23.86877898v55.97228664c0 13.12782842 10.68127858 23.86877897 23.86877896 23.86877894z\"  ></path></symbol><symbol id=\"md-editor-icon-bold\" viewBox=\"0 0 1024 1024\"><path d=\"M337.99948597 263.41195693h174.00051403c21.78711731 0 38.3868257 0 59.06235508 6.22489056 18.82288355 5.6320438 32.7547819 12.44978111 49.72501888 24.82545665 12.52388741 9.18912433 23.04691639 22.30585777 31.05034721 37.27523849 8.522172 16.00686163 12.44978111 34.16279285 12.44978184 55.94990944 0 27.9379023-9.3373362 52.83746453-24.82545665 68.39969128-15.56222675 18.67467168-37.27523777 31.0503472-62.17480072 40.38768268 15.56222675 0 27.9379023 6.22489056 43.50012832 12.44978184 12.44978111 6.22489056 23.78797502 13.78368649 34.16279285 25.27009153 18.23003678 20.23089449 18.52646053 25.49240898 24.45492736 39.9430478 7.18826662 17.63719003 6.66952544 31.0503472 6.66952544 46.61257398 0 21.78711731-3.11244563 43.50012832-12.44978112 59.06235508-9.3373362 15.56222675-21.78711731 31.0503472-37.27523776 43.50012832-15.56222675 12.44978111-34.16279285 21.78711731-55.94990944 27.9379023-24.82545666 6.22489056-46.61257397 9.3373362-71.43803062 9.3373362H337.99948597V263.41195693z m68.32558498 205.05086195h93.22514794c12.44978111 0 24.82545666 0 37.27523777-3.11244563 12.44978111-3.11244563 21.19427055-6.81773731 27.93790229-12.44978112 9.55965365-8.00343082 14.00600393-13.11673417 18.67467167-21.78711731 6.96594917-12.9685223 6.22489056-21.78711731 6.22489056-34.16279284 0-18.67467168-6.81773731-36.68239102-18.67467167-49.7250189-18.00771935-19.86036517-31.34677095-25.19598596-59.06235509-24.82545664H406.32507095v146.06261244z m0 233.13697468h99.45003849c9.3373362 0 21.78711731 0 34.16279286-3.11244563 12.44978111-3.11244563 21.12016426-5.18740892 34.16279213-12.44978111 14.1542158-7.85521895 21.63890544-14.1542158 27.9379023-24.82545665 5.7061501-9.63375922 12.44978111-24.82545666 12.44978112-43.50012834 0-27.9379023-5.40972637-49.87323076-26.7522088-65.8800924-21.416588-16.00686163-47.79826748-24.2326099-81.96105961-24.23260988H406.32507095v174.00051401z m0 0\"  ></path></symbol><symbol id=\"md-editor-icon-strike-through\" viewBox=\"0 0 1024 1024\"><path d=\"M821.59397555 532.97906546V490.95304114c0-1.15418807-0.33946708-2.03680247-1.56154857-2.03680247H552.46447575l-0.33946708-0.06789342c-7.26459548-1.42576173-14.66497778-2.85152347-22.0653601-4.20939176-11.47398726-2.10469589-15.75127245-2.91941687-21.59010619-4.07360495-36.05140373-7.06091523-57.98097699-14.05393705-75.76905189-24.30584283-25.73160455-15.00444487-38.22399303-35.44036299-38.22399305-62.46194242 0-26.95368604 11.13452018-49.42640662 32.11358562-64.97399882 20.43591813-15.1402317 49.42640662-23.08376134 83.71258155-23.08376133 39.24239426 0 69.6586445 10.38769261 90.36613628 30.89150413 10.59137286 10.45558601 18.39911565 23.28744158 23.08376133 38.15609962 1.08629466 3.32677737 2.10469589 7.73984939 3.12309713 12.76396216 0.33946708 1.6973354 1.83312222 2.91941687 3.59835103 2.91941687h50.92006176c1.96890906 0 3.66624444-1.56154857 3.66624445-3.53045762v-0.54314732c-0.67893415-4.61675227-0.88261441-8.21510331-1.35786832-10.7950531-4.95621935-29.73731607-19.01015639-55.67260087-40.668156-75.22590458-30.34835681-27.7005136-75.22590458-42.36549139-129.67642395-42.3654914-49.83376713 0-94.64342148 12.42449508-126.21385978 34.96510909-17.51650124 12.62817532-30.95939755 28.1078741-39.92132841 46.09962925-9.09771771 18.46700907-13.78236339 39.85343501-13.7823634 63.48034367 0 20.02855763 3.80203127 37.00191157 11.67766752 51.938463 5.56726009 10.52347943 13.10342924 19.82487738 23.08376132 28.44734118l6.9251284 5.97462057H204.57861375c-1.22208149 0-2.85152347 0.95050781-2.85152344 2.10469588V533.04695886c0 1.22208149 1.62944197 2.03680247 2.85152345 2.03680247h303.27988789l0.33946706 0.13578683c0.88261441 0.20368026 1.76522881 0.40736049 2.5799498 0.54314733 0.54314733 0.13578683 1.01840124 0.20368026 1.56154856 0.33946708 22.40482718 4.48096543 35.10089591 7.40038231 46.84645683 10.72715968 16.49810002 4.68464569 29.05838191 9.57297162 39.3781811 15.34391195 26.27475187 14.80076462 39.03871403 36.11929715 39.03871402 65.17767906 0 25.73160455-11.27030701 48.74747246-31.77411854 64.77031856-21.86167985 17.10914076-54.11105231 26.20685845-93.35344657 26.20685845-30.95939755 0-57.43782967-6.04251399-78.7563622-17.92386173-20.97906546-11.74556092-35.57614982-28.71891485-43.31599921-50.44480786-0.61104074-1.62944197-1.22208149-3.9378181-1.96890907-6.72144816-0.40736049-1.56154857-1.90101564-2.91941687-3.5304576-2.91941687h-55.74049428c-2.03680247 0-3.8699247 2.03680247-3.8699247 4.07360495v0.54314733c0 1.49365514 0.33946708 2.78363004 0.47525392 3.66624444 4.41307203 33.19988027 20.63959838 60.42513997 48.13643172 80.79316466 32.31726588 23.89848233 78.07742804 36.52665764 132.12058694 36.52665765 58.11676383 0 106.8642363-13.64657655 140.74305072-39.3781811 16.97335391-12.83185556 30.07678315-28.65102142 38.90292718-47.05013708 8.89403746-18.6027959 13.44289631-39.64975477 13.4428963-62.52983584 0-21.72589302-3.9378181-39.78554159-12.08502797-55.3331338-3.8699247-7.53616914-8.82614404-14.52919095-14.73287122-20.8432786l-5.36357984-5.77094033h152.96386556c1.35786832 0.06789342 1.6973354-0.88261441 1.69733538-2.03680248z\"  ></path></symbol><symbol id=\"md-editor-icon-link\" viewBox=\"0 0 1024 1024\"><path d=\"M457.68526766 662.72338284l-48.06853829 46.71067021a67.07869491 67.07869491 0 0 1-95.05078242-95.0507824l123.29444312-123.56601632a66.80712103 66.80712103 0 0 1 92.06347167-2.44416299l3.25888393 2.71573683a27.15736647 27.15736647 0 0 0 38.0203126-38.83503419 74.68275705 74.68275705 0 0 0-4.88832593-5.70304691 121.12185338 121.12185338 0 0 0-165.38836062 5.97462079l-124.92388445 123.56601634a121.66500113 121.66500113 0 0 0 171.90612853 171.90612853L494.89085924 702.10156414A27.15736647 27.15736647 0 0 0 457.68526766 662.72338284zM747.99751273 276.00248727a121.93657437 121.93657437 0 0 0-171.90612853 0L529.10914076 321.89843586A27.15736647 27.15736647 0 0 0 566.31473234 361.27661716l46.98224407-46.71067021a67.07869491 67.07869491 0 0 1 95.05078177 95.0507824l-123.2944431 123.56601632a66.80712103 66.80712103 0 0 1-92.06347169 2.44416299l-3.25888393-2.71573683a27.15736647 27.15736647 0 0 0-38.02031258 38.83503419 74.68275705 74.68275705 0 0 0 6.24619402 5.70304691 121.39342726 121.39342726 0 0 0 165.38836061-5.97462079l123.56601699-123.56601634a121.93657437 121.93657437 0 0 0 1.08629423-171.90612853z\"  ></path></symbol><symbol id=\"md-editor-icon-ordered-list\" viewBox=\"0 0 1024 1024\"><path d=\"M409.96096997 282.85972205h366.38575693v48.33427738H409.96096997zM409.96096997 486.9377821h366.38575693v47.73755791H409.96096997zM409.96096997 691.01584214h366.38575693v47.14083844H409.96096997zM297.77770889 278.68268574v104.42590792h26.25565684V231.54184731h-20.407806l-0.47737559 1.19343893c-4.89309968 15.21634658-18.91600732 27.74745553-41.7106912 37.35463906l-1.25311091 0.41770364v26.85237632l2.98359738-1.13376699c14.14225153-5.72850695 25.0622179-11.69570169 34.60972948-17.54355253zM350.88574206 563.31787476H283.57578541c7.0412898-8.95079212 17.84191227-18.67731953 32.22285159-29.35859814 26.79270438-17.2451928 39.14479748-35.92251232 36.75791959-55.31589522-1.96917426-27.32975191-18.25961589-41.88970707-48.45362128-43.32183378-26.13631296 0-44.03789716 12.47143702-53.22737705 37.17562321l-0.65639144 1.84983038 26.4346727 11.51668583 0.59671947-2.32720594c3.99802047-14.6196271 11.87471753-21.95927664 24.10746675-22.37698027 15.87273801 0 23.27205949 6.02686669 23.27205948 19.21436705 0.83540725 10.44259079-8.77177627 22.85435586-28.70220669 36.87726348-20.82550964 15.09700268-36.87726348 32.64055522-47.7375579 52.39196982l-0.23868779 16.94683305H350.88574206v-23.27205948zM328.80712153 710.70758479c13.36651621-6.56391422 20.10944627-16.70814527 20.10944627-30.31334927-1.01442311-27.80712749-17.2451928-42.24773874-48.45362129-42.72511433-26.25565685 0.53704752-42.96380213 14.02290766-49.7664041 40.21889253l-0.53704753 2.02884622 28.04581529 6.62358616 0.41770361-2.08851817c2.68523764-13.66487594 10.08455913-20.70616575 22.5559961-21.541573 12.5907809 0.41770364 18.67731953 5.72850695 19.15469512 16.70814528C319.43862579 693.40272004 310.84586537 699.96663427 294.19739205 699.96663427h-13.7245479v25.06221789h19.27403902c16.29044163 0.59671947 24.82353011 6.80260199 26.13631294 19.63207069 0.17901585 7.99604095-1.73048647 13.66487594-5.60916306 17.60322446-3.99802047 3.99802047-10.44259079 5.72850695-19.03535122 5.72850697h-1.43212672c-14.38093932-0.59671947-22.31730832-7.87669705-24.04779481-23.27205949l-0.29835973-2.14819011-28.04581527 6.62358617 0.41770363 1.96917426c5.31080332 26.73303243 23.27205949 40.57692423 53.4063929 41.0542998 33.41629054-1.43212673 51.31787476-17.00650501 53.28704902-46.42477508-0.41770364-15.39536242-9.070136-27.210408-25.71860932-35.08710505z\"  ></path></symbol><symbol id=\"md-editor-icon-unordered-list\" viewBox=\"0 0 1024 1024\"><path d=\"M270.51669908 316.14107442m-42.36549141 1e-8a42.36549139 42.36549139 0 1 0 84.7309828 0 42.36549139 42.36549139 0 1 0-84.7309828 0Z\"  ></path><path d=\"M798.43634618 287.18914938l-433.25558604 0.22812188-0.02607106 53.75203192 433.30121039-0.22812188-0.0195533-53.75203191z\"  ></path><path d=\"M270.51669908 512m-42.36549141 0a42.36549139 42.36549139 0 1 0 84.7309828 0 42.36549139 42.36549139 0 1 0-84.7309828 0Z\"  ></path><path d=\"M365.18076014 483.27619684l-0.02607106 53.75203192 433.30121039-0.22812188-0.0195533-53.75203191-433.25558603 0.22812187z\"  ></path><path d=\"M270.51669908 707.85892558m-42.36549141-1e-8a42.36549139 42.36549139 0 1 0 84.7309828 0 42.36549139 42.36549139 0 1 0-84.7309828 0Z\"  ></path><path d=\"M365.18076014 679.78038143l-0.02607106 53.75203194 433.30121039-0.22812188-0.0195533-53.75203193-433.25558603 0.22812187z\"  ></path></symbol><symbol id=\"md-editor-icon-code-row\" viewBox=\"0 0 1024 1024\"><path d=\"M616.98155188 365.94904204c-8.48396123-8.48464016-8.48396123-22.24052512 0-30.72516528 8.48464016-8.48396123 22.24052512-8.48396123 30.72516527 0L809.01332562 496.53048523c8.48464016 8.48464016 8.48464016 22.24052512 1e-8 30.72516527L647.70671715 688.56225897c-8.48464016 8.48464016-22.24052512 8.48464016-30.72516529 0-8.48396123-8.48464016-8.48396123-22.24052512 0-30.72516526L759.08518666 515.73413788a5.43147326 5.43147326 0 0 0 0-7.68146107L616.98155188 365.94904204zM264.30241473 515.73413788L406.40537059 657.83709371c8.48464016 8.48464016 8.48464016 22.24052512-1e-8 30.72516526s-22.24052512 8.48464016-30.72516528 0L214.37359684 527.25565051c-8.48396123-8.48464016-8.48396123-22.24052512 0-30.72516529l161.30660846-161.30660846c8.48464016-8.48396123 22.24052512-8.48396123 30.72516529 0 8.48464016 8.48464016 8.48464016 22.24052512 0 30.72516528L264.30241473 508.05267681a5.43147326 5.43147326 0 0 0 0 7.68146106zM558.43909632 310.49098437c11.69124619 2.69944222 18.98096223 14.36556783 16.28152003 26.05681399L491.33188639 697.74484345c-2.69876327 11.69124619-14.3648889 18.98096223-26.05613506 16.28152001-11.69124619-2.69876327-18.98096223-14.3648889-16.28219896-26.05613507L532.38296127 326.77318332c2.69876327-11.69124619 14.3648889-18.98096223 26.05613505-16.28219895z\"  ></path></symbol><symbol id=\"md-editor-icon-quote\" viewBox=\"0 0 1024 1024\"><path d=\"M478.2078125 305.82124999l0 81.2025c0 11.0109375-8.926875 19.9378125-19.9378125 19.93781251-39.2934375 0-60.665625 40.2975-63.6271875 119.8378125l63.6271875 0c11.0109375 0 19.9378125 8.9353125 19.9378125 19.9378125l0 171.45c0 11.0109375-8.926875 19.9378125-19.9378125 19.9378125L288.625625 738.125c-11.019375 0-19.9378125-8.9353125-19.9378125-19.9378125l0-171.45c0-38.1290625 3.8390625-73.119375 11.4075-104.0090625 7.7625-31.674375 19.67625001-59.36624999 35.40375-82.31625 16.183125-23.5828125 36.4246875-42.08625001 60.1678125-54.97875 23.9034375-12.9684375 51.6965625-19.5496875 82.6115625-19.5496875C469.2809375 285.8834375 478.2078125 294.8103125 478.2078125 305.82124999zM735.374375 406.9615625c11.0109375 0 19.9378125-8.9353125 19.9378125-19.93781249l0-81.2025c0-11.0109375-8.926875-19.9378125-19.9378125-19.93781251-30.9065625 0-58.6996875 6.58125001-82.60312499 19.5496875-23.743125 12.8925-43.993125 31.3959375-60.17625001 54.97875-15.7275 22.95-27.64124999 50.641875-35.40375001 82.3246875-7.56 30.898125-11.3990625 65.8884375-11.39906249 104.000625l0 171.45c0 11.0109375 8.926875 19.9378125 19.9378125 19.9378125l169.6359375 0c11.0109375 0 19.9378125-8.9353125 19.93781251-19.9378125l0-171.45c0-11.0109375-8.926875-19.9378125-19.93781251-19.9378125L672.65 526.799375C675.569375 447.2590625 696.629375 406.9615625 735.374375 406.9615625z\"  ></path></symbol><symbol id=\"md-editor-icon-title\" viewBox=\"0 0 1024 1024\"><path d=\"M314.24609375 264.80761693h81.01318385v206.32324271h250.65307617V264.80761693h81.01318309v494.38476563h-81.01318309v-218.81469727H395.2592776V759.19238256H314.24609375z\"  ></path></symbol><symbol id=\"md-editor-icon-github\" viewBox=\"0 0 1024 1024\"><path d=\"M620.62946531 837.8883953a27.15736647 27.15736647 0 0 1-27.15736649-27.15736647v-105.09900749a64.36295809 64.36295809 0 0 0-17.92386174-51.87056937 27.15736647 27.15736647 0 0 1-5.97462079-27.15736649 27.15736647 27.15736647 0 0 1 22.54061444-17.65228787c78.21321488-8.96193087 150.72338284-34.76142862 150.72338282-162.94419766a121.39342726 121.39342726 0 0 0-33.13198727-83.10154081 27.15736647 27.15736647 0 0 1-5.97462017-27.15736648A108.62946531 108.62946531 0 0 0 707.53303719 267.58370352a228.66502383 228.66502383 0 0 0-71.69544698 35.0330025 27.15736647 27.15736647 0 0 1-22.26904054 3.80203107 333.49245808 333.49245808 0 0 0-175.97973347 0 27.15736647 27.15736647 0 0 1-22.26903993-3.80203107A217.25893001 217.25893001 0 0 0 343.62432931 267.58370352a108.62946531 108.62946531 0 0 0 3.80203106 64.90610521 27.15736647 27.15736647 0 0 1-5.97462079 27.15736647A119.76398527 119.76398527 0 0 0 308.31975294 445.19287897c0 127.09647417 72.51016795 153.71069291 150.9949567 162.94419765a27.15736647 27.15736647 0 0 1 22.26903992 17.92386173 27.15736647 27.15736647 0 0 1-5.97462016 27.15736648 63.54823709 63.54823709 0 0 0-17.92386174 50.51270128V810.73102883a27.15736647 27.15736647 0 0 1-54.31473297 0v-47.52539057A133.88581595 133.88581595 0 0 1 262.96695146 702.10156414c-10.59137286-13.57868292-19.82487759-24.98477676-29.05838233-27.15736648a27.15736647 27.15736647 0 1 1 13.0355358-54.31473235 112.43149638 112.43149638 0 0 1 58.65991117 47.25381732c22.5406138 28.78680846 41.00762327 52.14214327 97.76651859 40.46447552v-2.71573681a115.14723321 115.14723321 0 0 1 8.14720989-50.24112739c-76.5837729-17.38071462-157.51272461-64.36295809-157.512724-209.92644112a173.26399663 173.26399663 0 0 1 37.47716547-108.62946531 162.94419766 162.94419766 0 0 1 7.87563601-106.72844947A27.15736647 27.15736647 0 0 1 316.73853669 213.26897117C326.51518855 211.36795532 363.44920627 205.93648228 435.4162271 251.28928375a396.49754741 396.49754741 0 0 1 180.32491228 0C687.70815959 205.93648228 724.64217794 211.36795532 734.41882979 213.26897117a27.15736647 27.15736647 0 0 1 17.38071463 15.47969877 162.94419766 162.94419766 0 0 1 7.87563601 106.72844948 174.07871762 174.07871762 0 0 1 37.47716547 108.62946468c0 154.25384065-93.69291365 196.8909053-157.24115074 210.74116271a117.59139619 117.59139619 0 0 1 7.87563601 52.95686425V810.73102883a27.15736647 27.15736647 0 0 1-27.15736586 27.15736647z\"  ></path></symbol><symbol id=\"md-editor-icon-fullscreen-exit\" viewBox=\"0 0 1024 1024\"><path d=\"M663.00853518 634.20814825H736.04827177a20.36802471 20.36802471 0 1 0 0-40.73604942H613.84012354a20.36802471 20.36802471 0 0 0-20.36802471 20.36802471v122.20814823a20.36802471 20.36802471 0 1 0 40.7360494 0v-73.0397366l87.42974607 87.42974606a20.36802471 20.36802471 0 1 0 28.79020292-28.81057095L663.00853518 634.20814825zM389.79185175 663.00853518V736.04827177a20.36802471 20.36802471 0 1 0 40.73604942 0V613.84012354a20.36802471 20.36802471 0 0 0-20.36802471-20.36802471H287.95172823a20.36802471 20.36802471 0 1 0 0 40.7360494h73.0397366l-87.42974606 87.42974607a20.36802471 20.36802471 0 1 0 28.81057095 28.79020292L389.79185175 663.00853518zM663.00853518 389.79185175H736.04827177a20.36802471 20.36802471 0 1 1 0 40.73604942H613.84012354a20.36802471 20.36802471 0 0 1-20.36802471-20.36802471V287.95172823a20.36802471 20.36802471 0 1 1 40.7360494 0v73.0397366l87.42974607-87.42974606a20.36802471 20.36802471 0 1 1 28.79020292 28.81057095L663.00853518 389.79185175zM389.79185175 360.99146482V287.95172823a20.36802471 20.36802471 0 1 1 40.73604942 0v122.20814823a20.36802471 20.36802471 0 0 1-20.36802471 20.36802471H287.95172823a20.36802471 20.36802471 0 1 1 0-40.7360494h73.0397366l-87.42974606-87.42974607a20.36802471 20.36802471 0 1 1 28.81057095-28.79020293L389.79185175 360.99146482z\"  ></path></symbol><symbol id=\"md-editor-icon-fullscreen\" viewBox=\"0 0 1024 1024\"><path d=\"M585.86803606 585.86803606a20.36802471 20.36802471 0 0 1 28.78680846 0l101.02540254 100.97108801V627.41880646l0.19010178-2.77005136A20.36802471 20.36802471 0 0 1 756.41629648 627.41880646v115.41880708l-0.21725872 2.44416298a13.57868292 13.57868292 0 0 1-13.36142422 11.13451996h-115.41880708l-2.77005136-0.19010177a20.36802471 20.36802471 0 0 1-17.59797334-20.17792294l0.19010177-2.77005135a20.36802471 20.36802471 0 0 1 20.17792293-17.59797336h59.47463215l-101.02540255-101.02540254-1.98248732-2.28121876a20.36802471 20.36802471 0 0 1 1.98248732-26.5055897z m-147.73607212 0a20.36802471 20.36802471 0 0 1 1.98248732 26.5055897l-1.98248732 2.28121876-101.02540255 101.02540254h59.47463215a20.36802471 20.36802471 0 0 1 20.17792293 17.59797336l0.19010177 2.77005135a20.36802471 20.36802471 0 0 1-17.59797334 20.17792294L396.58119354 756.41629648H281.16238646a13.57868292 13.57868292 0 0 1-13.36142422-11.13451996L267.58370352 742.83761354v-115.41880708a20.36802471 20.36802471 0 0 1 40.54594764-2.77005136l0.19010178 2.77005136v59.42031763l101.02540253-100.97108803a20.36802471 20.36802471 0 0 1 28.78680847 0zM742.83761354 267.58370352a13.57868292 13.57868292 0 0 1 13.36142422 11.13451996L756.41629648 281.16238646v115.41880708a20.36802471 20.36802471 0 0 1-40.54594764 2.77005136L715.68024706 396.58119354V337.16087593L614.65484453 438.13196394a20.36802471 20.36802471 0 0 1-30.76929579-26.5055897l1.98248732-2.28121876 101.02540255-101.02540254h-59.47463215a20.36802471 20.36802471 0 0 1-20.17792294-17.59797336L607.05078176 287.95172823a20.36802471 20.36802471 0 0 1 17.59797334-20.17792294L627.41880646 267.58370352h115.41880708zM396.58119354 267.58370352l2.77005136 0.19010177a20.36802471 20.36802471 0 0 1 17.59797334 20.17792294l-0.19010177 2.77005135a20.36802471 20.36802471 0 0 1-20.17792293 17.59797336H337.1065614l101.02540254 101.02540253 1.98248732 2.28121877a20.36802471 20.36802471 0 0 1-30.76929578 26.5055897L308.31975294 337.16087593V396.58119354l-0.19010178 2.77005136A20.36802471 20.36802471 0 0 1 267.58370352 396.58119354V281.16238646l0.21725872-2.44416298A13.57868292 13.57868292 0 0 1 281.16238646 267.58370352h115.41880708z\"  ></path></symbol><symbol id=\"md-editor-icon-code\" viewBox=\"0 0 1024 1024\"><path d=\"M286.78396126 504.04289189A27.04873681 27.04873681 0 0 0 294.74107001 484.84263353V349.05580234a27.15736647 27.15736647 0 0 1 27.15736585-27.15736648 27.15736647 27.15736647 0 0 0 0-54.31473234C276.98015246 267.58370352 240.42633703 304.13751833 240.42633703 349.05580234v124.54368154l-19.20025774 19.20025837a27.15736647 27.15736647 0 0 0 0 38.4005155l19.20025774 19.20025837V674.94419766c0 44.91828402 36.55381479 81.47209883 81.47209883 81.47209882a27.15736647 27.15736647 0 0 0 0-54.31473234 27.15736647 27.15736647 0 0 1-27.15736585-27.15736648v-135.78683119a27.04873681 27.04873681 0 0 0-7.95710875-19.20025836L278.82685316 512l7.9571081-7.95710811zM808.6670694 501.59872892a27.18452343 27.18452343 0 0 0-5.89314869-8.79898667L783.57366297 473.59948388V349.05580234c0-44.91828402-36.55381479-81.47209883-81.47209883-81.47209882a27.15736647 27.15736647 0 1 0 0 54.31473234 27.15736647 27.15736647 0 0 1 27.15736585 27.15736648v135.78683119a27.10305134 27.10305134 0 0 0 7.95710875 19.20025836l7.9571081 7.95710811-7.9571081 7.95710811A27.04873681 27.04873681 0 0 0 729.25892999 539.15736647v135.78683119a27.15736647 27.15736647 0 0 1-27.15736585 27.15736648 27.15736647 27.15736647 0 1 0 0 54.31473233c44.91828402 0 81.47209883-36.55381479 81.47209883-81.47209881v-124.54368154l19.20025774-19.20025837a27.18452343 27.18452343 0 0 0 5.89314869-29.60152883zM612.40078333 329.6111283a27.211681 27.211681 0 0 0-44.26650723 9.39644832l-162.835568 325.88839529a27.13020891 27.13020891 0 0 0 15.20812492 35.27741881 27.32031068 27.32031068 0 0 0 35.35889088-15.12665281l162.80841104-325.88839531a27.07589439 27.07589439 0 0 0-6.24619466-29.5472143z\"  ></path></symbol><symbol id=\"md-editor-icon-catalog\" viewBox=\"0 0 1024 1024\"><path d=\"M683.13318788 654.61099014c0 9.12710341-7.98621522 17.11331865-17.11331864 17.11331864H380.79788896c-9.12710341 0-17.11331865-7.98621522-17.11331863-17.11331864s7.98621522-17.11331865 17.11331863-17.11331864h285.22198028c9.12710341 0 17.11331865 7.98621522 17.11331864 17.11331864zM683.13318788 515.42266388c0 9.12710341-7.98621522 17.11331865-17.11331864 17.11331864H380.79788896c-9.12710341 0-17.11331865-7.98621522-17.11331863-17.11331864s7.98621522-17.11331865 17.11331863-17.11331864h285.22198028c9.12710341 0 17.11331865 6.84532775 17.11331864 17.11331863zM683.13318788 375.09344941c0 9.12710341-7.98621522 17.11331865-17.11331864 17.11331864H380.79788896c-9.12710341 0-17.11331865-7.98621522-17.11331863-17.11331865S371.67078554 357.98013076 380.79788896 357.98013076h285.22198028c9.12710341 0 17.11331865 7.98621522 17.11331864 17.11331864z\"  ></path><path d=\"M723.06426543 859.97081603H322.61260528c-59.32617188 0-107.24346465-47.91729278-107.24346466-107.24346466V294.09040684c0-59.32617188 47.91729278-107.24346465 107.24346466-107.24346395H723.06426543c59.32617188 0 107.24346465 47.91729278 107.24346466 107.24346392v457.49605637c1.1408882 60.46706009-46.77640459 108.38435213-107.24346466 108.38435285zM322.61260528 221.07358017c-39.93107754 0-73.01682665 33.08574982-73.01682737 73.01682664v457.49605637c0 39.93107754 33.08574982 73.01682665 73.01682737 73.01682737H723.06426543c39.93107754 0 73.01682665-33.08574982 73.01682666-73.01682737V294.09040684C797.22198028 254.15932999 764.13623047 221.07358017 723.06426543 221.07358017H322.61260528z\"  ></path></symbol><symbol id=\"md-editor-icon-table\" viewBox=\"0 0 1024 1024\"><path d=\"M836.44000244 553.03908539V391.93144989c0-0.20856857 0.11587143-0.37078857 0.11587143-0.55618286s-0.11587143-0.37078857-0.11587143-0.55618286V263.77764893a23.17428589 23.17428589 0 0 0-23.17428589-23.17428589H210.73428345a23.17428589 23.17428589 0 0 0-23.17428589 23.17428589v127.04143524c0 0.20856857-0.11587143 0.37078857-0.11587143 0.55618285s0.11587143 0.34761428 0.11587143 0.55618287v161.1076355c0 0.20856857-0.11587143 0.37078857-0.11587143 0.55618286s0.11587143 0.34761428 0.11587143 0.55618286V750.43765258a23.17428589 23.17428589 0 0 0 23.17428589 23.17428589h602.5314331a23.17428589 23.17428589 0 0 0 23.17428589-23.17428589V554.15145111c0-0.20856857 0.11587143-0.37078857 0.11587143-0.55618285s-0.11587143-0.37078857-0.11587143-0.55618287z m-405.55000305-22.61810302v-115.87142946h150.63285827v115.87142946H430.88999939z m150.63285827 46.34857177V727.2633667H430.88999939V576.76955414h150.63285827z m-347.61428831-162.22000123h150.63285826v115.87142946H233.90856933v-115.87142946z m393.9628601 1e-8h162.22000122v115.87142945H627.87142945v-115.87142945zM790.09143067 286.95193482v81.24904633H233.90856933V286.95193482h556.18286132zM233.90856933 576.76955414h150.63285828V727.2633667H233.90856933V576.76955414zM627.87142945 727.2633667V576.76955414h162.2200012V727.2633667H627.87142945z\"  ></path></symbol><symbol id=\"md-editor-icon-image\" viewBox=\"0 0 1024 1024\"><path d=\"M731.17724609 241.57153319H293.64672852c-41.85791017 0-75.80566406 33.94775391-75.80566407 75.80566408v358.42895507c-0.98876953 3.95507813-0.98876953 7.74536134 0 11.53564455v19.44580077c0 41.85791017 33.94775391 75.80566406 75.80566407 75.80566406h437.53051757c41.85791017 0 75.80566406-33.94775391 75.80566406-75.80566406V317.37719727c0-41.85791017-33.94775391-75.80566406-75.80566406-75.80566408z m-437.53051757 46.14257814h437.53051757c16.31469727 0 29.66308594 13.34838867 29.66308595 29.66308594v287.89672851c-17.13867188-23.23608398-37.90283203-48.61450195-59.49096681-68.71948242-12.85400391-12.0300293-28.83911133-16.97387695-46.30737304-14.50195313-27.52075195 3.95507813-57.67822266 26.86157227-92.12036133 69.87304688-8.23974609 10.21728516-15.65551758 20.43457031-21.75292969 29.16870117-30.32226563-41.85791017-89.97802734-119.14672852-140.57006836-154.74243163-14.99633789-10.546875-31.80541991-13.01879883-48.94409179-7.41577149-18.62182617 6.09741211-36.74926758 21.91772461-55.20629883 48.28491211-10.71166992 15.16113281-21.58813477 33.94775391-32.62939452 56.03027343V317.37719727c0.16479492-16.47949219 13.34838867-29.66308594 29.82788085-29.66308594z\"  ></path><path d=\"M610.87695313 398.95068359a51.91040039 51.91040039 0 1 0 103.82080078 0 51.91040039 51.91040039 0 1 0-103.82080078 0z\"  ></path></symbol></svg>',function(l){var a=(a=document.getElementsByTagName(\"script\"))[a.length-1],c=a.getAttribute(\"data-injectcss\"),a=a.getAttribute(\"data-disable-injectsvg\");if(!a){var o,t,i,e,h,d=function(a,c){c.parentNode.insertBefore(a,c)};if(c&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write(\"<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>\")}catch(a){console&&console.log(a)}}o=function(){var a,c=document.createElement(\"div\");c.innerHTML=l._iconfont_svg_string_2605852,(c=c.getElementsByTagName(\"svg\")[0])&&(c.setAttribute(\"aria-hidden\",\"true\"),c.style.position=\"absolute\",c.style.width=0,c.style.height=0,c.style.overflow=\"hidden\",c=c,(a=document.body).firstChild?d(c,a.firstChild):a.appendChild(c))},document.addEventListener?~[\"complete\",\"loaded\",\"interactive\"].indexOf(document.readyState)?setTimeout(o,0):(t=function(){document.removeEventListener(\"DOMContentLoaded\",t,!1),o()},document.addEventListener(\"DOMContentLoaded\",t,!1)):document.attachEvent&&(i=o,e=l.document,h=!1,v(),e.onreadystatechange=function(){\"complete\"==e.readyState&&(e.onreadystatechange=null,m())})}function m(){h||(h=!0,i())}function v(){try{e.documentElement.doScroll(\"left\")}catch(a){return void setTimeout(v,50)}m()}}(window);"
  },
  {
    "path": "ui/src/components/markdown/tool-calls-render/content/index.vue",
    "content": "<template>\n  <component :is=\"kw[content.type]\" :content=\"content\"></component>\n</template>\n<script setup lang=\"ts\">\nimport SimpleToolCalls from './simple-tool-calls/index.vue'\nimport { type ToolCalls } from '@/components/markdown/tool-calls-render/index'\ndefineProps<{\n  content: ToolCalls\n}>()\nconst kw: any = {\n  'simple-tool-calls': SimpleToolCalls,\n}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/markdown/tool-calls-render/content/simple-tool-calls/index.vue",
    "content": "<template>\n  <div>\n    <p class=\"mt-8 mb-8\">{{ $t('common.param.inputParam') }}：</p>\n    <span class=\"color-secondary\">{{ content.content.input }}</span>\n    <p class=\"mt-8 mb-8\">{{ $t('common.param.outputParam') }}：</p>\n    <span class=\"color-secondary\"><pre>{{ content.content.output }}</pre></span>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { type ToolCalls } from '@/components/markdown/tool-calls-render/index'\ndefineProps<{\n  content: ToolCalls\n}>()\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/markdown/tool-calls-render/index.ts",
    "content": "export interface ToolCalls {\n  type: string\n  icon?: string\n  title: string\n  content: any\n}\n"
  },
  {
    "path": "ui/src/components/markdown/tool-calls-render/index.vue",
    "content": "<template>\n  <el-card shadow=\"never\" class=\"layout-bg mt-8\" style=\"--el-card-padding: 8px 12px\">\n    <div class=\"flex-between cursor\" @click=\"showContent = !showContent\">\n      <div class=\"flex align-center\" style=\"line-height: 20px\">\n        <el-avatar\n          v-if=\"toolCallsContent.icon\"\n          shape=\"square\"\n          :size=\"24\"\n          style=\"background: none\"\n          class=\"mr-4\"\n        >\n          <img :src=\"toolCallsContent.icon\" alt=\"\" />\n        </el-avatar>\n        <ToolIcon v-else :size=\"24\" class=\"mr-4\" />\n        <span class=\"ml-4\">{{ toolCallsContent.title || '-' }}</span>\n      </div>\n      <div>\n        <el-icon class=\"arrow-icon\" :class=\"showContent ? 'rotate-180' : ''\">\n          <ArrowDown />\n        </el-icon>\n      </div>\n    </div>\n    <el-collapse-transition>\n      <div v-show=\"showContent\">\n        <Content :content=\"toolCallsContent\"></Content>\n      </div>\n    </el-collapse-transition>\n  </el-card>\n</template>\n<script lang=\"ts\" setup>\nimport Content from './content/index.vue'\nimport { ref, computed } from 'vue'\nimport { type ToolCalls } from './index'\nimport defaultIcon from '@/assets/workflow/icon_robot.svg'\nconst props = defineProps<{ content?: string }>()\n\nconst toolCallsContent = computed<ToolCalls>(() => {\n  try {\n    return JSON.parse(props.content ? props.content : '{}')\n  } catch (error) {\n    return { type: 'simple-tool-calls', icon: '', title: '', content: {} }\n  }\n})\n\nconst showContent = ref<boolean>(false)\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/model-select/index.vue",
    "content": "<template>\n  <div class=\"w-full\">\n    <el-select\n      v-model=\"modelValue\"\n      popper-class=\"select-model\"\n      :clearable=\"true\"\n      filterable\n      v-bind=\"$attrs\"\n    >\n      <el-option-group\n        v-for=\"(value, label) in options\"\n        :key=\"value\"\n        :label=\"relatedObject(providerOptions, label, 'provider')?.name\"\n      >\n        <el-option\n          v-for=\"item in value.filter((v: any) => v.status === 'SUCCESS')\"\n          :key=\"item.id\"\n          :label=\"item.name\"\n          :value=\"item.id\"\n          class=\"flex-between\"\n        >\n          <div class=\"flex\">\n            <span\n              v-html=\"relatedObject(providerOptions, label, 'provider')?.icon\"\n              class=\"model-icon mr-8\"\n            ></span>\n            <span>{{ item.name }}</span>\n\n            <el-tag\n              v-if=\"item.type === 'share'\"\n              type=\"info\"\n              class=\"info-tag ml-8\"\n              style=\"margin-top: 7px\"\n            >\n              {{ t('views.shared.title') }}\n            </el-tag>\n          </div>\n          <el-icon class=\"check-icon\" v-if=\"item.id === modelValue\">\n            <Check />\n          </el-icon>\n        </el-option>\n        <!-- 不可用 -->\n        <el-option\n          v-for=\"item in value.filter((v: any) => v.status !== 'SUCCESS')\"\n          :key=\"item.id\"\n          :label=\"item.name\"\n          :value=\"item.id\"\n          class=\"flex-between\"\n          disabled\n        >\n          <div class=\"flex\">\n            <span\n              v-html=\"relatedObject(providerOptions, label, 'provider')?.icon\"\n              class=\"model-icon mr-8\"\n            ></span>\n            <span>{{ item.name }}</span>\n            <span class=\"color-danger\">{{ $t('common.unavailable') }}</span>\n          </div>\n          <el-icon class=\"check-icon\" v-if=\"item.id === modelValue\">\n            <Check />\n          </el-icon>\n        </el-option>\n      </el-option-group>\n\n      <template #tag v-if=\"$slots.tag\">\n        <slot name=\"tag\"></slot>\n      </template>\n\n      <template #footer v-if=\"showFooter\">\n        <slot name=\"footer\">\n          <div class=\"w-full text-left cursor\" @click=\"openCreateModel(undefined, props.modelType)\">\n            <el-button type=\"primary\" link v-if=\"permissionPrecise.create()\">\n              <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n              {{ $t('views.application.operation.addModel') }}\n            </el-button>\n          </div>\n        </slot>\n      </template>\n    </el-select>\n    <!-- 添加模板 -->\n    <CreateModelDialog\n      v-if=\"showFooter\"\n      ref=\"createModelRef\"\n      @submit=\"submitModel\"\n      @change=\"openCreateModel($event)\"\n    ></CreateModelDialog>\n    <SelectProviderDialog\n      v-if=\"showFooter\"\n      ref=\"selectProviderRef\"\n      @change=\"(provider, modelType) => openCreateModel(provider, modelType)\"\n    />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, onMounted } from 'vue'\nimport type { Provider } from '@/api/type/model'\nimport { relatedObject } from '@/utils/array'\nimport CreateModelDialog from '@/views/model/component/CreateModelDialog.vue'\nimport SelectProviderDialog from '@/views/model/component/SelectProviderDialog.vue'\n\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nimport permissionMap from '@/permission'\n\ndefineOptions({ name: 'ModelSelect' })\nconst props = defineProps<{\n  modelValue: any\n  options: any\n  showFooter?: false\n  modelType?: ''\n}>()\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['model']['workspace']\n})\n\nconst emit = defineEmits(['update:modelValue', 'change', 'submitModel'])\nconst modelValue = computed({\n  set: (item) => {\n    emit('change', item)\n    emit('update:modelValue', item)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\nconst { model } = useStore()\n\nconst createModelRef = ref<InstanceType<typeof CreateModelDialog>>()\nconst selectProviderRef = ref<InstanceType<typeof SelectProviderDialog>>()\nconst providerOptions = ref<Array<Provider>>([])\nconst loading = ref(false)\n\nfunction getProvider() {\n  loading.value = true\n  model\n    .asyncGetProvider()\n    .then((res: any) => {\n      providerOptions.value = res?.data\n      loading.value = false\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nconst openCreateModel = (provider?: Provider, model_type?: string) => {\n  if (provider && provider.provider) {\n    createModelRef.value?.open(provider, model_type)\n  } else {\n    selectProviderRef.value?.open(model_type)\n  }\n}\n\nfunction submitModel() {\n  emit('submitModel')\n}\n\nonMounted(() => {\n  getProvider()\n})\n</script>\n<style lang=\"scss\" scoped>\n// AI模型选择：添加模型hover样式\n.select-model {\n  .el-select-dropdown__footer {\n    &:hover {\n      background-color: var(--el-fill-color-light);\n    }\n  }\n\n  .model-icon {\n    width: 18px;\n  }\n\n  .check-icon {\n    position: absolute;\n    right: 10px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/pdf-export/index.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    :title=\"$t('chat.preview')\"\n    style=\"overflow: auto\"\n    width=\"60%\"\n    :before-close=\"close\"\n    destroy-on-close\n    align-center\n  >\n    <div\n      v-loading=\"loading\"\n      style=\"\n        max-height: calc(100vh - 200px);\n        overflow-y: auto;\n        display: flex;\n        justify-content: center;\n      \"\n    >\n      <div ref=\"svgContainerRef\"></div>\n    </div>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button :loading=\"loading\" @click=\"exportPDF\">{{ $t('chat.exportPDF') }}</el-button>\n        <el-button\n          :loading=\"loading\"\n          type=\"primary\"\n          @click=\"\n            () => {\n              loading = true\n              exportJepg()\n            }\n          \"\n        >\n          {{ $t('chat.exportImg') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport * as htmlToImage from 'html-to-image'\nimport { ref, nextTick } from 'vue'\nimport { jsPDF } from 'jspdf'\n\nconst loading = ref<boolean>(false)\nconst svgContainerRef = ref()\nconst dialogVisible = ref<boolean>(false)\n\n// 保存原始元素引用，用于导出\nconst originalElement = ref<HTMLElement | null>(null)\n\nconst open = (element: HTMLElement | null) => {\n  dialogVisible.value = true\n  loading.value = true\n  if (!element) {\n    loading.value = false\n    return\n  }\n\n  // 保存原始元素引用\n  originalElement.value = element\n\n  nextTick(() => {\n    htmlToImage\n      .toCanvas(element, {\n        pixelRatio: window.devicePixelRatio || 1,\n        quality: 1,\n        skipFonts: false,\n        backgroundColor: '#ffffff',\n      })\n      .then((canvas) => {\n        // 清空之前的内容\n        svgContainerRef.value.innerHTML = ''\n        canvas.style.width = '100%'\n        canvas.style.height = 'auto'\n        svgContainerRef.value.appendChild(canvas)\n      })\n      .finally(() => {\n        loading.value = false\n      })\n      .catch((e) => {\n        console.error(e)\n        loading.value = false\n      })\n  })\n}\n\nconst exportPDF = () => {\n  loading.value = true\n  setTimeout(() => {\n    nextTick(async () => {\n      try {\n        const targetEl = originalElement.value\n        if (!targetEl) return\n        const canvas = await htmlToImage.toCanvas(targetEl, {\n          pixelRatio: 2,\n          quality: 1,\n          skipFonts: false,\n          backgroundColor: '#ffffff',\n        })\n        generatePDF(canvas)\n      } catch (e) {\n        console.error('PDF export error:', e)\n      } finally {\n        loading.value = false\n      }\n    })\n  })\n}\n\nconst generatePDF = (canvas: HTMLCanvasElement) => {\n  const doc = new jsPDF('p', 'mm', 'a4')\n  const imgData = canvas.toDataURL('image/jpeg', 1)\n  const pageWidth = doc.internal.pageSize.getWidth()\n  const pageHeight = doc.internal.pageSize.getHeight()\n  const imgWidth = pageWidth\n  const imgHeight = (canvas.height * imgWidth) / canvas.width\n\n  doc.addImage(imgData, 'JPEG', 0, 0, imgWidth, imgHeight)\n\n  let heightLeft = imgHeight - pageHeight\n  while (heightLeft > 0) {\n    const position = -(imgHeight - heightLeft)\n    doc.addPage()\n    doc.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight)\n    heightLeft -= pageHeight\n  }\n\n  doc.save('导出文档.pdf')\n}\n\nconst exportJepg = () => {\n  loading.value = true\n  setTimeout(() => {\n    nextTick(async () => {\n      try {\n        const targetEl = originalElement.value\n        if (!targetEl) return\n        const canvas = await htmlToImage.toCanvas(targetEl, {\n          pixelRatio: window.devicePixelRatio || 1,\n          quality: 1,\n          skipFonts: false,\n          backgroundColor: '#ffffff',\n        })\n        downloadJpeg(canvas)\n      } catch (e) {\n        console.error('JPEG export error:', e)\n      } finally {\n        loading.value = false\n      }\n    })\n  }, 1)\n}\n\nconst downloadJpeg = (canvas: HTMLCanvasElement) => {\n  const newCanvas = document.createElement('canvas')\n  newCanvas.width = canvas.width\n  newCanvas.height = canvas.height\n  const ctx = newCanvas.getContext('2d')!\n  ctx.fillStyle = '#ffffff'\n  ctx.fillRect(0, 0, newCanvas.width, newCanvas.height)\n  ctx.drawImage(canvas, 0, 0)\n\n  const imgData = newCanvas.toDataURL('image/jpeg', 1)\n  const link = document.createElement('a')\n  link.download = 'webpage-screenshot.jpeg'\n  link.href = imgData\n  document.body.appendChild(link)\n  link.click()\n  document.body.removeChild(link)\n}\n\nconst close = () => {\n  dialogVisible.value = false\n  originalElement.value = null\n  // 清空预览内容\n  if (svgContainerRef.value) {\n    svgContainerRef.value.innerHTML = ''\n  }\n}\n\ndefineExpose({ open, close })\n</script>\n"
  },
  {
    "path": "ui/src/components/read-write/index.vue",
    "content": "<template>\n  <div class=\"cursor w-full\">\n    <slot name=\"read\">\n      <div class=\"flex align-center\" v-if=\"!isEdit\" @dblclick=\"dblclick\">\n        <auto-tooltip :content=\"data\">\n          {{ data }}\n        </auto-tooltip>\n\n        <el-button\n          v-if=\"trigger === 'default' && showEditIcon\"\n          class=\"ml-4\"\n          @click.stop=\"editNameHandle\"\n          text\n        >\n          <AppIcon iconName=\"app-edit\"></AppIcon>\n        </el-button>\n      </div>\n    </slot>\n    <slot>\n      <div class=\"flex align-center\" @click.stop v-if=\"isEdit\">\n        <div class=\"w-full\">\n          <el-input\n            ref=\"inputRef\"\n            v-model=\"writeValue\"\n            :placeholder=\"$t('common.inputPlaceholder')\"\n            autofocus\n            :maxlength=\"maxlength || '-'\"\n            :show-word-limit=\"maxlength ? true : false\"\n            @blur=\"isEdit = false\"\n            @keyup.enter=\"submit\"\n            clearable\n          ></el-input>\n        </div>\n\n        <span class=\"ml-4\">\n          <el-button type=\"primary\" text @mousedown=\"submit\" :disabled=\"loading\">\n            <el-icon><Select /></el-icon>\n          </el-button>\n        </span>\n        <span>\n          <el-button text @click.stop=\"isEdit = false\" :disabled=\"loading\">\n            <el-icon><CloseBold /></el-icon>\n          </el-button>\n        </span>\n      </div>\n    </slot>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, onMounted, nextTick } from 'vue'\ndefineOptions({ name: 'ReadWrite' })\nconst props = defineProps({\n  data: {\n    type: String,\n    default: '',\n  },\n  showEditIcon: {\n    type: Boolean,\n    default: false,\n  },\n  maxlength: {\n    type: Number,\n    default: () => 0,\n  },\n  trigger: {\n    type: String,\n    default: 'default',\n    validator: (value: string) => ['default', 'dblclick', 'manual'].includes(value),\n  },\n  write: {\n    type: Boolean,\n    default: false,\n  },\n})\nconst emit = defineEmits(['change', 'close'])\nconst inputRef = ref()\nconst isEdit = ref(false)\nconst writeValue = ref('')\nconst loading = ref(false)\n\nwatch(isEdit, (bool) => {\n  if (!bool) {\n    writeValue.value = ''\n    emit('close')\n  } else {\n    setTimeout(() => {\n      nextTick(() => {\n        inputRef.value?.focus()\n      })\n    }, 200)\n  }\n})\n\nwatch(\n  () => props.write,\n  (bool) => {\n    if (bool && props.trigger === 'manual') {\n      editNameHandle()\n    } else {\n      isEdit.value = false\n    }\n  },\n)\n\nfunction dblclick() {\n  if (props.trigger === 'dblclick') {\n    editNameHandle()\n  }\n}\n\nfunction submit() {\n  loading.value = true\n  emit('change', writeValue.value)\n  setTimeout(() => {\n    isEdit.value = false\n    loading.value = false\n  }, 200)\n}\nfunction editNameHandle() {\n  writeValue.value = props.data\n  isEdit.value = true\n}\n\nonMounted(() => {})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/resource-authorization-drawer/index.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"drawerVisible\"\n    :title=\"$t('views.system.resourceAuthorization.title')\"\n    size=\"850\"\n    :append-to-body=\"true\"\n  >\n    <div class=\"flex-between mb-16\">\n      <el-button\n        type=\"primary\"\n        :disabled=\"multipleSelection.length === 0\"\n        @click=\"openMulConfigureDialog\"\n        >{{ $t('views.system.resourceAuthorization.setting.configure') }}</el-button\n      >\n\n      <div class=\"flex-between complex-search\">\n        <el-select\n          class=\"complex-search__left\"\n          v-model=\"searchType\"\n          style=\"width: 100px\"\n          @change=\"search_type_change\"\n        >\n          <el-option :label=\"$t('views.userManage.userForm.nick_name.label')\" value=\"nick_name\" />\n          <el-option :label=\"$t('views.login.loginForm.username.label')\" value=\"username\" />\n          <el-option :label=\"$t('views.model.modelForm.permissionType.label')\" value=\"permission\" />\n          <el-option\n            v-if=\"hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')\"\n            :label=\"$t('views.role.member.role')\"\n            value=\"role\"\n          />\n        </el-select>\n        <el-input\n          v-if=\"searchType === 'nick_name'\"\n          v-model=\"searchForm.nick_name\"\n          @change=\"searchHandle\"\n          :placeholder=\"$t('common.search')\"\n          style=\"width: 220px\"\n          clearable\n        />\n        <el-input\n          v-if=\"searchType === 'username'\"\n          v-model=\"searchForm.username\"\n          @change=\"searchHandle\"\n          :placeholder=\"$t('common.search')\"\n          style=\"width: 220px\"\n          clearable\n        />\n        <el-input\n          v-if=\"searchType === 'role'\"\n          v-model=\"searchForm.role\"\n          @change=\"searchHandle\"\n          :placeholder=\"$t('common.search')\"\n          style=\"width: 220px\"\n          clearable\n        />\n        <el-select\n          v-else-if=\"searchType === 'permission'\"\n          v-model=\"searchForm.permission\"\n          @change=\"searchHandle\"\n          filterable\n          clearable\n          multiple\n          :reserve-keyword=\"false\"\n          collapse-tags\n          collapse-tags-tooltip\n          style=\"width: 220px\"\n        >\n          <template v-for=\"(item, index) in permissionOptions\" :key=\"index\">\n            <el-option :label=\"item.label\" :value=\"item.value\" />\n          </template>\n        </el-select>\n      </div>\n    </div>\n\n    <app-table\n      ref=\"multipleTableRef\"\n      class=\"mt-16\"\n      :data=\"permissionData\"\n      :pagination-config=\"paginationConfig\"\n      @sizeChange=\"handleSizeChange\"\n      @changePage=\"getPermissionList\"\n      @selection-change=\"handleSelectionChange\"\n      :maxTableHeight=\"200\"\n      :row-key=\"(row: any) => row.id\"\n      v-loading=\"loading\"\n    >\n      <el-table-column type=\"selection\" width=\"55\" :reserve-selection=\"true\" />\n      <el-table-column\n        prop=\"nick_name\"\n        :label=\"$t('views.userManage.userForm.nick_name.label')\"\n        min-width=\"120\"\n        show-overflow-tooltip\n      />\n      <el-table-column\n        prop=\"username\"\n        min-width=\"120\"\n        show-overflow-tooltip\n        :label=\"$t('views.login.loginForm.username.label')\"\n      />\n      <el-table-column\n        v-if=\"hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')\"\n        prop=\"role_name\"\n        :label=\"$t('views.role.member.role')\"\n        width=\"160\"\n      >\n        <template #default=\"{ row }\">\n          <TagGroup class=\"cursor\" :tags=\"row.role_name\" />\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"340\">\n        <template #default=\"{ row }\">\n          <el-radio-group\n            v-model=\"row.permission\"\n            @change=\"(val: any) => permissionsHandle(val, row)\"\n          >\n            <template v-for=\"(item, index) in getFolderPermissionOptions()\" :key=\"index\">\n              <el-radio :value=\"item.value\" class=\"mr-16\">{{ item.label }}</el-radio>\n            </template>\n          </el-radio-group>\n        </template>\n      </el-table-column>\n    </app-table>\n    <!-- 单个资源授权提示框 -->\n    <el-dialog\n      v-model=\"singleSelectDialogVisible\"\n      :title=\"$t('views.system.resourceAuthorization.setting.effectiveResource')\"\n      destroy-on-close\n      @close=\"closeSingleSelectDialog\"\n      width=\"500px\"\n    >\n      <el-radio-group v-model=\"authAllChildren\" class=\"radio-block\">\n        <el-radio :value=\"false\">\n          <p class=\"color-text-primary lighter\">\n            {{ $t('views.system.resourceAuthorization.setting.currentOnly') }}\n          </p>\n        </el-radio>\n        <el-radio :value=\"true\">\n          <p class=\"color-text-primary lighter\">\n            {{ $t('views.system.resourceAuthorization.setting.includeAll') }}\n          </p>\n        </el-radio>\n      </el-radio-group>\n      <template #footer>\n        <div class=\"dialog-footer mt-24\">\n          <el-button @click=\"closeSingleSelectDialog\">{{ $t('common.cancel') }}</el-button>\n          <el-button type=\"primary\" @click=\"confirmSinglePermission\">{{\n            $t('common.confirm')\n          }}</el-button>\n        </div>\n      </template>\n    </el-dialog>\n\n    <!-- 批量配置 弹出层 -->\n    <el-dialog\n      v-model=\"dialogVisible\"\n      :title=\"$t('views.system.resourceAuthorization.setting.configure')\"\n      destroy-on-close\n      @close=\"closeDialog\"\n      width=\"500px\"\n    >\n      <el-radio-group v-model=\"radioPermission\" class=\"radio-block\">\n        <template v-for=\"(item, index) in getFolderPermissionOptions()\" :key=\"index\">\n          <el-radio :value=\"item.value\" class=\"mr-16\">\n            <p class=\"color-text-primary lighter\">{{ item.label }}</p>\n            <el-text class=\"color-secondary lighter\">{{ item.desc }}</el-text>\n          </el-radio>\n        </template>\n      </el-radio-group>\n      <!-- 如果是文件夹，显示子资源选项 -->\n      <div v-if=\"isFolder\" class=\"mt-16\">\n        <el-divider />\n        <div class=\"color-text-primary mb-8\">\n          {{ $t('views.system.resourceAuthorization.setting.effectiveResource') }}\n        </div>\n        <el-radio-group v-model=\"batchAuthAllChildren\" class=\"radio-block\">\n          <el-radio :value=\"false\">\n            <p class=\"color-text-primary lighter\">\n              {{ $t('views.system.resourceAuthorization.setting.currentOnly') }}\n            </p>\n          </el-radio>\n          <el-radio :value=\"true\">\n            <p class=\"color-text-primary lighter\">\n              {{ $t('views.system.resourceAuthorization.setting.includeAll') }}\n            </p>\n          </el-radio>\n        </el-radio-group>\n      </div>\n      <template #footer>\n        <div class=\"dialog-footer mt-24\">\n          <el-button @click=\"closeDialog\"> {{ $t('common.cancel') }}</el-button>\n          <el-button type=\"primary\" @click=\"submitDialog\"> {{ $t('common.confirm') }}</el-button>\n        </div>\n      </template>\n    </el-dialog>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, watch, computed, reactive } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { getPermissionOptions } from '@/views/system/resource-authorization/constant'\nimport AuthorizationApi from '@/api/system/resource-authorization'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { t } from '@/locales'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst route = useRoute()\nimport useStore from '@/stores'\nimport { hasPermission } from '@/utils/permission/index'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\n\nconst { user } = useStore()\nconst props = defineProps<{\n  type: string\n  isFolder?: boolean\n  isRootFolder?: boolean\n}>()\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst folderType = computed(() => {\n  if (route.path.includes('application')) {\n    return 'application'\n  } else if (route.path.includes('knowledge')) {\n    return 'knowledge'\n  } else if (route.path.includes('tool')) {\n    return 'tool'\n  } else {\n    return 'application'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap[folderType.value!]['workspace']\n})\n\n// 取出文件夹id\nfunction getAllFolderIds(data: any) {\n  if (!data) return []\n  return [data.id, ...(data.children?.flatMap((child: any) => getAllFolderIds(child)) || [])]\n}\n\nconst RESOURCE_PERMISSION_MAP = {\n  application:\n    PermissionConst.APPLICATION_RESOURCE_AUTHORIZATION.getWorkspacePermissionWorkspaceManageRole,\n  knowledge:\n    PermissionConst.KNOWLEDGE_RESOURCE_AUTHORIZATION.getWorkspacePermissionWorkspaceManageRole,\n  tool: PermissionConst.TOOL_RESOURCE_AUTHORIZATION.getWorkspacePermissionWorkspaceManageRole,\n}\n\nconst resourceAuthorizationOfManager = computed(() => {\n  return RESOURCE_PERMISSION_MAP[folderType.value]\n})\n\n// 过滤没有Manage权限的文件夹ID\nfunction filterHasPermissionFolderIds(folderIds: string[]) {\n  if (\n    hasPermission(\n      [RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, resourceAuthorizationOfManager.value],\n      'OR',\n    )\n  ) {\n    return folderIds\n  } else {\n    return folderIds.filter((id) => permissionPrecise.value.folderManage(id))\n  }\n}\n\nfunction confirmSinglePermission() {\n  if (!pendingPermissionChange.value) return\n  const { val, row } = pendingPermissionChange.value\n  let folderIds: string[] = []\n  if (authAllChildren.value && folderData.value) {\n    const allFolderIds = getAllFolderIds(folderData.value)\n    folderIds = filterHasPermissionFolderIds(allFolderIds)\n  }\n  const obj = [\n    {\n      user_id: row.id,\n      permission: val,\n      include_children: authAllChildren.value,\n      ...(folderIds.length > 0 && { folder_ids: folderIds }),\n    },\n  ]\n  submitPermissions(obj)\n  singleSelectDialogVisible.value = false\n  authAllChildren.value = false\n  pendingPermissionChange.value = null\n  getPermissionList()\n}\n\nconst permissionOptionMap = computed(() => {\n  return {\n    rootFolder: getPermissionOptions(true, true),\n    folder: getPermissionOptions(false, false),\n  }\n})\n\nconst getFolderPermissionOptions = () => {\n  if (props.isRootFolder) {\n    return permissionOptionMap.value.rootFolder\n  }\n  if (props.isFolder) {\n    return permissionOptionMap.value.folder\n  }\n  return getPermissionOptions(false, false)\n}\n\nconst permissionOptions = computed(() => {\n  return getPermissionOptions()\n})\nconst drawerVisible = ref(false)\nconst multipleTableRef = ref()\n\nwatch(drawerVisible, (bool) => {\n  if (!bool) {\n    targetId.value = ''\n    searchType.value = 'nick_name'\n    searchForm.value = { nick_name: '', username: '', permission: undefined }\n    permissionData.value = []\n    paginationConfig.current_page = 1\n    paginationConfig.total = 0\n    multipleSelection.value = []\n    multipleTableRef.value?.clearSelection()\n  }\n})\n\nconst loading = ref(false)\nconst targetId = ref('')\nconst folderData = ref<any>(null)\nconst permissionData = ref<any[]>([])\nconst searchType = ref('nick_name')\nconst searchForm = ref<any>({\n  nick_name: '',\n  username: '',\n  role: '',\n  permission: undefined,\n})\n\nconst search_type_change = () => {\n  searchForm.value = { nick_name: '', username: '', role: '', permission: undefined }\n}\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getPermissionList()\n}\nfunction searchHandle() {\n  paginationConfig.current_page = 1\n  getPermissionList()\n}\n\nconst multipleSelection = ref<any[]>([])\n\nconst handleSelectionChange = (val: any[]) => {\n  multipleSelection.value = val\n}\n\nconst dialogVisible = ref(false)\nconst singleSelectDialogVisible = ref(false)\nconst pendingPermissionChange = ref<{ val: any; row: any } | null>(null)\nconst radioPermission = ref('')\nconst authAllChildren = ref(false)\nfunction openMulConfigureDialog() {\n  if (multipleSelection.value.length === 0) {\n    return\n  }\n  dialogVisible.value = true\n}\n\nconst batchAuthAllChildren = ref(false)\nfunction submitDialog() {\n  if (multipleSelection.value.length === 0 || !radioPermission.value) {\n    return\n  }\n  let folderIds: string[] = []\n  if (props.isFolder && batchAuthAllChildren.value && folderData.value) {\n    const allFolderIds = getAllFolderIds(folderData.value)\n    folderIds = filterHasPermissionFolderIds(allFolderIds)\n  }\n\n  const obj = multipleSelection.value.map((item) => ({\n    user_id: item.id,\n    permission: radioPermission.value,\n    include_children: batchAuthAllChildren.value,\n    ...(folderIds.length > 0 && { folder_ids: folderIds }),\n  }))\n  submitPermissions(obj)\n  closeDialog()\n}\n\nfunction closeSingleSelectDialog() {\n  singleSelectDialogVisible.value = false\n  authAllChildren.value = false\n  pendingPermissionChange.value = null\n  getPermissionList()\n}\n\nfunction closeDialog() {\n  dialogVisible.value = false\n  radioPermission.value = ''\n  batchAuthAllChildren.value = false\n  multipleSelection.value = []\n  multipleTableRef.value?.clearSelection()\n}\n\nfunction permissionsHandle(val: any, row: any) {\n  if (props.isFolder) {\n    singleSelectDialogVisible.value = true\n    pendingPermissionChange.value = { val, row }\n    return\n  }\n  const obj = [\n    {\n      user_id: row.id,\n      permission: val,\n    },\n  ]\n  submitPermissions(obj)\n}\n\nfunction submitPermissions(obj: any) {\n  const workspaceId = user.getWorkspaceId() || 'default'\n  loadSharedApi({ type: 'resourceAuthorization', systemType: apiType.value })\n    .putResourceAuthorization(workspaceId, targetId.value, props.type, obj, loading)\n    .then(() => {\n      MsgSuccess(t('common.submitSuccess'))\n      getPermissionList()\n    })\n}\nconst getPermissionList = () => {\n  const workspaceId = user.getWorkspaceId() || 'default'\n  const params: any = {}\n  if (searchForm.value[searchType.value]) {\n    params[searchType.value] = searchForm.value[searchType.value]\n  }\n  loadSharedApi({ type: 'resourceAuthorization', systemType: apiType.value })\n    .getResourceAuthorization(\n      workspaceId,\n      targetId.value,\n      props.type,\n      paginationConfig,\n      params,\n      loading,\n    )\n    .then((res: any) => {\n      permissionData.value =\n        res.data.records.map((item: any) => {\n          if (props.isRootFolder && item.permission === 'NOT_AUTH') {\n            return { ...item, permission: 'VIEW' }\n          }\n          return item\n        }) || []\n      paginationConfig.total = res.data.total || 0\n    })\n}\n\nconst open = (id: string, folder_data?: any) => {\n  targetId.value = id\n  folderData.value = folder_data\n  drawerVisible.value = true\n  getPermissionList()\n}\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/resource_mapping/index.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"visible\"\n    :title=\"$t('views.system.resourceMapping.title')\"\n    size=\"60%\"\n    :append-to-body=\"true\"\n  >\n    <div class=\"lighter mb-12\">\n      {{ currentSourceName }}\n    </div>\n    <div class=\"flex align-center mb-16\">\n      <KnowledgeIcon\n        v-if=\"currentSourceType === 'KNOWLEDGE'\"\n        class=\"mr-12\"\n        :size=\"24\"\n        :type=\"currentSource.type\"\n      />\n      <el-avatar\n        v-else-if=\"currentSourceType === 'TOOL' && isAppIcon(currentSource?.icon)\"\n        shape=\"square\"\n        :size=\"24\"\n        style=\"background: none\"\n        class=\"mr-12\"\n      >\n        <img :src=\"resetUrl(currentSource?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n      </el-avatar>\n      <ToolIcon\n        v-else-if=\"currentSourceType === 'TOOL'\"\n        class=\"mr-12\"\n        :size=\"24\"\n        :type=\"currentSource.tool_type\"\n      />\n\n      <span\n        v-else-if=\"currentSourceType === 'MODEL'\"\n        style=\"height: 24px; width: 24px\"\n        :innerHTML=\"getProviderIcon(currentSource)\"\n        class=\"mr-12\"\n      ></span>\n      {{ currentSource.name }}\n    </div>\n    <div class=\"lighter mb-12\">\n      {{ $t('views.system.resourceMapping.sub_title') }}\n    </div>\n    <div class=\"flex-between mb-16\">\n      <div class=\"flex-between complex-search\">\n        <el-select class=\"complex-search__left\" v-model=\"searchType\" style=\"width: 100px\">\n          <el-option :label=\"$t('common.name')\" value=\"resource_name\" />\n          <el-option :label=\"$t('common.creator')\" value=\"user_name\" />\n          <el-option :label=\"$t('common.type')\" value=\"source_type\" />\n        </el-select>\n        <el-input\n          v-if=\"searchType === 'resource_name'\"\n          v-model=\"query.resource_name\"\n          :placeholder=\"$t('common.search')\"\n          style=\"width: 220px\"\n          clearable\n          @keyup.enter=\"pageResourceMapping()\"\n        />\n        <el-input\n          v-if=\"searchType === 'user_name'\"\n          v-model=\"query.user_name\"\n          :placeholder=\"$t('common.search')\"\n          style=\"width: 220px\"\n          clearable\n          @keyup.enter=\"pageResourceMapping()\"\n        />\n        <el-select\n          v-else-if=\"searchType === 'source_type'\"\n          v-model=\"query.source_type\"\n          @change=\"pageResourceMapping()\"\n          filterable\n          clearable\n          multiple\n          :reserve-keyword=\"false\"\n          collapse-tags\n          collapse-tags-tooltip\n          style=\"width: 220px\"\n          :placeholder=\"$t('common.search')\"\n        >\n          <el-option :label=\"$t('views.application.title')\" value=\"APPLICATION\" />\n          <el-option :label=\"$t('views.knowledge.title')\" value=\"KNOWLEDGE\" />\n        </el-select>\n      </div>\n    </div>\n\n    <app-table\n      ref=\"multipleTableRef\"\n      class=\"mt-16\"\n      :data=\"tableData\"\n      :pagination-config=\"paginationConfig\"\n      @sizeChange=\"handleSizeChange\"\n      @changePage=\"pageResourceMapping\"\n      :maxTableHeight=\"200\"\n      :row-key=\"(row: any) => row.id\"\n      v-loading=\"loading\"\n    >\n      <el-table-column prop=\"name\" :label=\"$t('common.name')\" min-width=\"130\" show-overflow-tooltip>\n        <template #default=\"{ row }\">\n          <el-button link @click=\"toSetting(row)\">\n            <div class=\"flex align-center\">\n              <KnowledgeIcon\n                v-if=\"row.source_type === 'KNOWLEDGE'\"\n                class=\"mr-8\"\n                :size=\"22\"\n                :type=\"row.icon\"\n              />\n              <el-avatar\n                v-else-if=\"row.source_type === 'APPLICATION' && isAppIcon(row?.icon)\"\n                shape=\"square\"\n                :size=\"22\"\n                style=\"background: none\"\n                class=\"mr-8\"\n              >\n                <img :src=\"resetUrl(row?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n              </el-avatar>\n\n              <span>{{ row.name }}</span>\n            </div>\n          </el-button>\n        </template>\n      </el-table-column>\n      <el-table-column\n        prop=\"desc\"\n        min-width=\"120\"\n        show-overflow-tooltip\n        :label=\"$t('common.desc')\"\n      />\n      <el-table-column\n        prop=\"source_type\"\n        min-width=\"120\"\n        show-overflow-tooltip\n        :label=\"$t('common.type')\"\n      >\n        <template #default=\"{ row }\">\n          {{\n            row.source_type === 'APPLICATION'\n              ? $t('views.application.title')\n              : $t('views.knowledge.title')\n          }}\n        </template>\n      </el-table-column>\n      <el-table-column\n        prop=\"workspace_name\"\n        min-width=\"120\"\n        show-overflow-tooltip\n        :label=\"$t('views.workspace.title')\"\n        v-if=\"showWorkspace\"\n      >\n        <template #header>\n          <div>\n            <span>{{ $t('views.workspace.title') }}</span>\n            <el-popover\n              :width=\"200\"\n              trigger=\"click\"\n              :visible=\"workspaceVisible\"\n              :persistent=\"false\"\n            >\n              <template #reference>\n                <el-button\n                  style=\"margin-top: -2px\"\n                  :type=\"workspaceArr && workspaceArr.length > 0 ? 'primary' : ''\"\n                  link\n                  @click=\"workspaceVisible = !workspaceVisible\"\n                >\n                  <el-icon>\n                    <Filter />\n                  </el-icon>\n                </el-button>\n              </template>\n              <div class=\"filter\">\n                <div class=\"form-item mb-16 ml-4\">\n                  <div @click.stop>\n                    <el-input\n                      v-model=\"filterText\"\n                      :placeholder=\"$t('common.search')\"\n                      prefix-icon=\"Search\"\n                      clearable\n                    />\n                    <el-scrollbar height=\"300\" v-if=\"filterData.length\">\n                      <el-checkbox-group\n                        v-model=\"workspaceArr\"\n                        style=\"display: flex; flex-direction: column\"\n                      >\n                        <el-checkbox\n                          v-for=\"item in filterData\"\n                          :key=\"item.value\"\n                          :label=\"item.label\"\n                          :value=\"item.value\"\n                        />\n                      </el-checkbox-group>\n                    </el-scrollbar>\n                    <el-empty v-else :description=\"$t('common.noData')\" />\n                  </div>\n                </div>\n              </div>\n              <div class=\"text-right\">\n                <el-button size=\"small\" @click=\"filterWorkspaceChange('clear')\"\n                  >{{ $t('common.clear') }}\n                </el-button>\n                <el-button type=\"primary\" @click=\"filterWorkspaceChange\" size=\"small\"\n                  >{{ $t('common.confirm') }}\n                </el-button>\n              </div>\n            </el-popover>\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column\n        prop=\"username\"\n        min-width=\"120\"\n        show-overflow-tooltip\n        :label=\"$t('common.creator')\"\n      />\n    </app-table>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, computed, onMounted, watch } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport useStore from '@/stores'\nimport { t } from '@/locales'\nimport type { Provider } from '@/api/type/model'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\nimport permissionMap from '@/permission'\nimport { MsgError } from '@/utils/message'\n\nconst route = useRoute()\nconst router = useRouter()\nconst { model, user } = useStore()\nconst searchType = ref<string>('resource_name')\nconst query = ref<any>({\n  resource_name: '',\n  user_name: '',\n  source_type: '',\n})\nconst loading = ref<boolean>(false)\nconst tableData = ref<Array<any>>()\nconst visible = ref<boolean>(false)\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst showWorkspace = computed(() => (user.isPE() || user.isEE()) && route.path.includes('shared'))\n\nconst currentSourceName = computed(() => {\n  if (currentSourceType.value === 'TOOL') {\n    return t('views.tool.title')\n  } else if (currentSourceType.value === 'MODEL') {\n    return t('views.model.title')\n  } else {\n    return t('views.knowledge.title')\n  }\n})\n\nconst pageResourceMapping = () => {\n  const workspaceId = user.getWorkspaceId() || 'default'\n  const params: any = {}\n  if (query.value[searchType.value]) {\n    params[searchType.value] = query.value[searchType.value]\n  }\n  if (workspaceArr.value.length > 0) {\n    params.workspace_ids = JSON.stringify(workspaceArr.value)\n  }\n  loadSharedApi({ type: 'resourceMapping', systemType: apiType.value })\n    .getResourceMapping(\n      workspaceId,\n      currentSourceType.value,\n      currentSourceId.value,\n      paginationConfig,\n      params,\n      loading,\n    )\n    .then((res: any) => {\n      tableData.value = res.data.records || []\n      paginationConfig.total = res.data.total || 0\n    })\n}\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  pageResourceMapping()\n}\n\nconst currentSourceType = ref<string>()\nconst currentSourceId = ref<string>()\nconst currentSource = ref<any>()\nconst open = (source: string, data: any) => {\n  visible.value = true\n  currentSourceType.value = source\n  currentSourceId.value = data.id\n  currentSource.value = data\n  pageResourceMapping()\n  if (currentSourceType.value === 'MODEL') {\n    getProvider()\n  }\n  getWorkspaceList()\n}\nconst close = () => {\n  visible.value = false\n  paginationConfig.current_page = 1\n}\n\nconst getProviderIcon = computed(() => {\n  return (row: any) => {\n    return provider_list.value.find((p) => p.provider === row.provider)?.icon\n  }\n})\n\nconst provider_list = ref<Array<Provider>>([])\n\nfunction getProvider() {\n  model.asyncGetProvider().then((res: any) => {\n    provider_list.value = res?.data\n  })\n}\n\nconst workspaceOptions = ref<any[]>([])\nconst workspaceVisible = ref(false)\nconst workspaceArr = ref<any[]>([])\n\nconst filterText = ref('')\nconst filterData = ref<any[]>([])\n\nfunction filterWorkspaceChange(val: string) {\n  if (val === 'clear') {\n    workspaceArr.value = []\n  }\n  filterText.value = ''\n  pageResourceMapping()\n  workspaceVisible.value = false\n}\n\nasync function getWorkspaceList() {\n  if (user.isEE() && showWorkspace.value) {\n    const res = await loadPermissionApi('workspace').getSystemWorkspaceList(loading)\n    workspaceOptions.value = res.data.map((item: any) => ({\n      label: item.name,\n      value: item.id,\n    }))\n  }\n}\n\nconst hasResourceWorkspacePermission = (row: any) => {\n  return permissionMap[row.source_type.toLowerCase() as 'application' | 'knowledge'][\n    'workspace'\n  ].jump_read(row.source_id)\n}\n\nconst hasResourceSystemManagePermission = (row: any) => {\n  return permissionMap[row.source_type.toLowerCase() as 'application' | 'knowledge'][\n    'systemManage'\n  ].jump_read()\n}\nconst hasResourceSharedPermission = () => {\n  return permissionMap['knowledge']['systemShare'].jump_read()\n}\n\nfunction hasJumpPermission(from: string, row: any) {\n  if (row.source_type === 'KNOWLEDGE') {\n    if (from === 'shared') {\n      if (row.workspace_id === 'None') {\n        return hasResourceSharedPermission()\n      } else {\n        return hasResourceSystemManagePermission(row)\n      }\n    } else if (from === 'resource-management') {\n      return hasResourceSystemManagePermission(row)\n    } else if (from === 'workspace') {\n      return hasResourceWorkspacePermission(row)\n    }\n  }\n\n  if (row.source_type === 'APPLICATION') {\n    if (['shared', 'resource-management'].includes(from)) {\n      return hasResourceSystemManagePermission(row)\n    } else if (from === 'workspace') {\n      return hasResourceWorkspacePermission(row)\n    }\n  }\n  return false\n}\n\nfunction toSetting(row: any) {\n  let from = ''\n  if (route.path.includes('resource-management')) {\n    from = 'resource-management'\n  } else if (route.path.includes('shared')) {\n    from = 'shared'\n  } else {\n    from = 'workspace'\n  }\n  if (row.source_type === 'KNOWLEDGE') {\n    if (!hasJumpPermission(from, row)) {\n      MsgError(t('common.noTargetPermission'))\n      return\n    }\n    const knowledge_from =\n      from === 'workspace'\n        ? row.folder_id\n        : from === 'shared'\n          ? row.workspace_id === 'None'\n            ? 'shared'\n            : 'resource-management'\n          : from\n    const newUrl = router.resolve({\n      path: `/knowledge/${row.source_id}/${knowledge_from}/${row.type}/document`,\n    }).href\n    window.open(newUrl)\n  } else if (row.source_type === 'APPLICATION') {\n    if (!hasJumpPermission(from, row)) {\n      MsgError(t('common.noTargetPermission'))\n      return\n    }\n    if (row.type === 'WORK_FLOW') {\n      const newUrl = router.resolve({\n        path: `/application/${from === 'shared' ? 'resource-management' : from}/${row.source_id}/workflow`,\n      }).href\n      window.open(newUrl)\n    } else {\n      const newUrl = router.resolve({\n        path: `/application/${from === 'shared' ? 'resource-management' : from}/${row.source_id}/SIMPLE/setting`,\n      }).href\n      window.open(newUrl)\n    }\n  }\n}\n\nwatch(\n  [() => workspaceOptions.value, () => filterText.value],\n  () => {\n    if (!filterText.value.length) {\n      filterData.value = workspaceOptions.value\n    }\n    filterData.value = workspaceOptions.value.filter((v: any) =>\n      v.label.toLowerCase().includes(filterText.value.toLowerCase()),\n    )\n  },\n  { immediate: true },\n)\n\ndefineExpose({\n  open,\n  close,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/select-knowledge-document/index.vue",
    "content": "<template>\n  <el-form\n    ref=\"formRef\"\n    :model=\"form\"\n    label-position=\"top\"\n    require-asterisk-position=\"right\"\n    :rules=\"rules\"\n    @submit.prevent\n  >\n    <el-form-item :label=\"$t('views.chatLog.selectKnowledge')\" prop=\"knowledge_id\">\n      <el-tree-select\n        :key=\"treeKey\"\n        v-model=\"form.knowledge_id\"\n        :props=\"defaultProps\"\n        node-key=\"id\"\n        lazy\n        :load=\"loadTree\"\n        :placeholder=\"$t('views.chatLog.selectKnowledgePlaceholder')\"\n        @change=\"changeKnowledge\"\n      >\n        <template #default=\"{ data }\">\n          <div class=\"flex align-center\">\n            <KnowledgeIcon\n              class=\"mr-12\"\n              :size=\"20\"\n              v-if=\"data.resource_type !== 'folder'\"\n              :type=\"data.type\"\n            />\n            <el-avatar v-else class=\"mr-12\" shape=\"square\" :size=\"20\" style=\"background: none\">\n              <img\n                src=\"@/assets/knowledge/icon_file-folder_colorful.svg\"\n                style=\"width: 100%\"\n                alt=\"\"\n              />\n            </el-avatar>\n\n            {{ data.name }}\n          </div>\n        </template>\n      </el-tree-select>\n    </el-form-item>\n    <el-form-item :label=\"$t('views.chatLog.saveToDocument')\" prop=\"document_id\">\n      <el-select\n        v-model=\"form.document_id\"\n        filterable\n        :placeholder=\"$t('views.chatLog.documentPlaceholder')\"\n        :loading=\"optionLoading\"\n        @changeDocument=\"changeDocument\"\n      >\n        <el-option v-for=\"item in documentList\" :key=\"item.id\" :label=\"item.name\" :value=\"item.id\">\n          {{ item.name }}\n        </el-option>\n      </el-select>\n    </el-form-item>\n  </el-form>\n</template>\n<script lang=\"ts\" setup>\nimport { ref, onUnmounted, reactive, watch } from 'vue'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport useStore from '@/stores'\nimport { t } from '@/locales'\nconst props = withDefaults(defineProps<{\n  data?: any,\n  postKnowledgeHandler?: (knowledge_list:Array<any>) => Array<any>\n  apiType: 'systemShare' | 'workspace' | 'systemManage'\n  isApplication?: boolean,\n  workspaceId?: string,\n}>(), {\n  postKnowledgeHandler: (k_l: Array<any>) => k_l,\n  data: () => null\n})\n\nconst { user } = useStore()\n\nconst emit = defineEmits(['changeKnowledge', 'changeDocument'])\nconst formRef = ref()\nconst form = ref<any>({\n  knowledge_id: '',\n  document_id: '',\n})\nconst treeKey = ref(0)\nconst rules = reactive<FormRules>({\n  knowledge_id: [\n    { required: true, message: t('views.chatLog.selectKnowledgePlaceholder'), trigger: 'change' },\n  ],\n  document_id: [\n    { required: true, message: t('views.chatLog.documentPlaceholder'), trigger: 'change' },\n  ],\n})\n\nconst defaultProps = {\n  children: 'children',\n  label: 'name',\n  isLeaf: (data: any) =>\n    data.resource_type ? data.resource_type !== 'folder' : data.workspace_id === 'None',\n  disabled: (data: any, node: any) => {\n    return data.resource_type === 'folder' && node?.isLeaf\n  },\n}\n\nconst loadTree = async (node: any, resolve: any) => {\n  if (node.isLeaf) return resolve([])\n  const folder_id = node.level === 0\n    ? (props.workspaceId || user.getWorkspaceId())\n    : node.data.id\n  const obj =\n   props.apiType === 'systemManage'\n      ? {\n          workspace_id: props.workspaceId,\n          folder_id:  folder_id,\n        }\n      : {\n          folder_id: folder_id,\n        }\n  await loadSharedApi({ type: 'knowledge', systemType: props.apiType })\n    .getKnowledgeList(obj, optionLoading)\n    .then((ok: any)=>ok.data)\n    .then(props.postKnowledgeHandler)\n    .then((res: any) => {\n      resolve(res)\n    })\n}\n\nconst documentList = ref<any[]>([])\nconst optionLoading = ref(false)\n\nfunction changeKnowledge(id: string) {\n  form.value.document_id = ''\n  getDocument(id)\n  emit('changeKnowledge', id)\n}\n\nfunction changeDocument(document_id: string) {\n  emit('changeKnowledge', document_id)\n}\n\nfunction getDocument(id: string) {\n  loadSharedApi({ type: 'document', systemType: props.apiType })\n    .getDocumentList(id, optionLoading)\n    .then((res: any) => {\n      documentList.value = res.data\n      if (props.isApplication) {\n        if (localStorage.getItem(id + 'chat_document_id')) {\n          form.value.document_id = localStorage.getItem(id + 'chat_document_id') as string\n        }\n        if (!documentList.value.find((v) => v.id === form.value.document_id)) {\n          form.value.document_id = ''\n        }\n      }\n    })\n}\n\nwatch(\n  () => props.data,\n  (value: any) => {\n    if (value && JSON.stringify(value) !== '{}') {\n      form.value.knowledge_id = value.knowledge_id\n      form.value.document_id = value.document_id\n    }\n  },\n  {\n    immediate: true,\n  },\n)\n\nwatch(\n  () => props.workspaceId,\n  (value: any) => {\n    treeKey.value++\n  },\n  {\n    immediate: true,\n  },\n)\n\n/*\n  表单校验\n*/\nfunction validate() {\n  if (!formRef.value) return\n  return formRef.value.validate((valid: any) => {\n    return valid\n  })\n}\n\nfunction clearValidate() {\n  form.value = {\n    knowledge_id: '',\n    document_id: '',\n  }\n  formRef.value?.clearValidate()\n}\n\nonUnmounted(() => {\n  clearValidate()\n})\n\ndefineExpose({\n  validate,\n  form,\n  clearValidate,\n})\n</script>\n"
  },
  {
    "path": "ui/src/components/tag-ellipsis/index.vue",
    "content": "<template>\n  <el-tag class=\"tag-ellipsis flex-between mb-8 w-full\" effect=\"plain\" v-bind=\"$attrs\">\n    <slot></slot>\n  </el-tag>\n</template>\n<script setup lang=\"ts\">\ndefineOptions({ name: 'TagEllipsis' })\n</script>\n<style lang=\"scss\" scoped>\n/* tag超出省略号 */\n.tag-ellipsis {\n  border: 1px solid var(--el-border-color);\n  color: var(--app-text-color);\n  border-radius: 4px;\n  height: 30px;\n  line-height: 30px;\n  padding: 0 9px;\n  box-sizing: border-box;\n\n  :deep(.el-tag__content) {\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/tag-group/index.vue",
    "content": "<template>\n  <div class=\"tag-group\" v-if=\"props.tags.length\">\n    <el-tag :size=\"props.size\" class=\"default-tag tag-ellipsis\" :title=\"props.tags[0]\">\n      {{ i18n_name(props.tags[0]) }}\n    </el-tag>\n    <el-popover\n      placement=\"bottom\"\n      :disabled=\"tooltipDisabled\"\n      :popper-style=\"{ width: 'auto', maxWidth: '300px' }\"\n      :persistent=\"false\"\n    >\n      <template #reference>\n        <el-tag :size=\"props.size\" class=\"info-tag ml-4 cursor\" v-if=\"props.tags?.length > 1\">\n          +{{ props.tags?.length - 1 }}\n        </el-tag>\n      </template>\n      <el-space>\n        <el-tag\n          :size=\"props.size\"\n          v-for=\"item in props.tags.slice(1)\"\n          :key=\"item\"\n          class=\"default-tag mr-4\"\n        >\n          {{ item }}\n        </el-tag>\n      </el-space>\n    </el-popover>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { i18n_name } from '@/utils/common'\n\nconst props = defineProps<{\n  tags: string[]\n  size?: 'large' | 'default' | 'small'\n  tooltipDisabled?: boolean\n}>()\n</script>\n\n<style lang=\"scss\" scoped>\n.tag-group {\n  /* tag超出省略号 */\n  .tag-ellipsis {\n    box-sizing: border-box;\n    max-width: 130px;\n    :deep(.el-tag__content) {\n      overflow: hidden;\n      white-space: nowrap;\n      text-overflow: ellipsis;\n    }\n  }\n}\n.el-popper.is-customized {\n  background: #ffffff;\n  box-shadow: 0px 4px 8px 0px rgba(var(--el-text-color-primary-rgb), 0.1);\n}\n\n.el-popper.is-customized .el-popper__arrow::before {\n  background: #ffffff;\n  right: 0;\n}\n</style>\n"
  },
  {
    "path": "ui/src/components/workflow-dropdown-menu/application/NodeContent.vue",
    "content": "<template>\n  <el-input\n    v-model.trim=\"filterText\"\n    :placeholder=\"$t('common.search')\"\n    prefix-icon=\"Search\"\n    clearable\n    style=\"padding: 12px 12px 0 12px\"\n  />\n  <div class=\"list flex-wrap\">\n    <template v-if=\"filterList.length\">\n      <el-popover\n        v-for=\"item in filterList\"\n        :key=\"item.id\"\n        placement=\"right\"\n        :width=\"280\"\n        :show-after=\"500\"\n        :persistent=\"false\"\n      >\n        <template #reference>\n          <div\n            class=\"list-item flex align-center border border-r-6 p-8-12 cursor\"\n            style=\"width: calc(50% - 6px)\"\n            @click.stop=\"emit('clickNodes', item)\"\n            @mousedown.stop=\"emit('onmousedown', item)\"\n          >\n            <el-avatar\n              v-if=\"isAppIcon(item?.icon)\"\n              shape=\"square\"\n              :size=\"20\"\n              style=\"background: none\"\n            >\n              <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n            </el-avatar>\n            <el-avatar\n              v-else\n              class=\"avatar-green\"\n              shape=\"square\"\n              :size=\"20\"\n              style=\"--el-avatar-border-radius: 6px\"\n            >\n              <img src=\"@/assets/tool/icon_tool.svg\" style=\"width: 58%\" alt=\"\" />\n            </el-avatar>\n            <span class=\"ml-8 ellipsis\" :title=\"item.name\">{{ item.name }}</span>\n          </div>\n        </template>\n\n        <template #default>\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <el-avatar\n                v-if=\"isAppIcon(item?.icon)\"\n                shape=\"square\"\n                :size=\"20\"\n                style=\"background: none\"\n              >\n                <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n              </el-avatar>\n              <el-avatar\n                v-else\n                class=\"avatar-green\"\n                shape=\"square\"\n                :size=\"20\"\n                style=\"--el-avatar-border-radius: 6px\"\n              >\n                <img src=\"@/assets/tool/icon_tool.svg\" style=\"width: 58%\" alt=\"\" />\n              </el-avatar>\n              <span class=\"font-medium ml-8 break-all\" :title=\"item.name\">{{ item.name }}</span>\n            </div>\n            <div v-if=\"item.type\" class=\"status-tag\" style=\"margin-left: auto\">\n              <el-tag size=\"small\" class=\"warning-tag\" v-if=\"isWorkFlow(item.type)\">\n                {{ $t('views.application.senior') }}\n              </el-tag>\n              <el-tag size=\"small\" class=\"blue-tag\" v-else>\n                {{ $t('views.application.simple') }}\n              </el-tag>\n            </div>\n          </div>\n          <el-text type=\"info\" size=\"small\" class=\"mt-4\">{{ item.desc }}</el-text>\n        </template>\n      </el-popover>\n    </template>\n    <el-empty v-else :description=\"$t('common.noData')\" />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { watch, ref } from 'vue'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport { isWorkFlow } from '@/utils/application'\n\nconst props = defineProps<{\n  list: any[]\n}>()\n\nconst emit = defineEmits<{\n  (e: 'clickNodes', item: any): void\n  (e: 'onmousedown', item: any): void\n}>()\n\nconst filterText = ref('')\nconst filterList = ref<any[]>([])\n\nfunction filter(list: any[], filterText: string) {\n  if (!filterText.length) {\n    return list\n  }\n  return list.filter((v: any) => v.name.toLowerCase().includes(filterText.toLowerCase()))\n}\n\nwatch([() => filterText.value, () => props.list], () => {\n  filterList.value = filter(props.list, filterText.value)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/workflow-dropdown-menu/application/index.vue",
    "content": "<template>\n  <div\n    v-show=\"show\"\n    class=\"workflow-dropdown-menu border border-r-6 white-bg\"\n    :style=\"{ width: activeName === 'base' ? '400px' : '640px' }\"\n  >\n    <el-tabs v-model=\"activeName\" class=\"workflow-dropdown-tabs\" @tab-change=\"handleClick\">\n      <div\n        v-show=\"activeName === 'base'\"\n        style=\"display: flex; width: 100%; justify-content: center\"\n        class=\"mb-12 mt-12\"\n      >\n        <el-input\n          v-model=\"search_text\"\n          class=\"mr-12 ml-12\"\n          :placeholder=\"$t('common.searchBar.placeholder')\"\n        >\n          <template #suffix>\n            <el-icon class=\"el-input__icon\">\n              <search />\n            </el-icon>\n          </template>\n        </el-input>\n      </div>\n\n      <el-tab-pane :label=\"$t('workflow.baseComponent')\" name=\"base\">\n        <el-scrollbar height=\"400\">\n          <div v-if=\"filter_menu_nodes.length > 0\">\n            <template v-for=\"(node, index) in filter_menu_nodes\" :key=\"index\">\n              <el-text type=\"info\" size=\"small\" class=\"color-secondary ml-12\">{{\n                node.label\n              }}</el-text>\n              <div class=\"flex-wrap\" style=\"gap: 12px; padding: 12px\">\n                <template v-for=\"(item, index) in node.list\" :key=\"index\">\n                  <el-popover placement=\"right\" :width=\"280\" :show-after=\"500\" :persistent=\"false\">\n                    <template #reference>\n                      <div\n                        class=\"list-item flex align-center border border-r-6 p-8-12 cursor\"\n                        style=\"width: calc(50% - 6px)\"\n                        @click.stop=\"clickNodes(item)\"\n                        @mousedown.stop=\"onmousedown(item)\"\n                      >\n                        <component\n                          :is=\"iconComponent(`${item.type}-icon`)\"\n                          class=\"mr-8\"\n                          :size=\"20\"\n                          style=\"--el-avatar-border-radius:6px\"\n                        />\n                        <div class=\"lighter\">{{ item.label }}</div>\n                      </div>\n                    </template>\n                    <template #default>\n                      <div class=\"flex align-center mb-8\">\n                        <component\n                          :is=\"iconComponent(`${item.type}-icon`)\"\n                          class=\"mr-8\"\n                          :size=\"32\"\n                        />\n                        <div class=\"lighter color-text-primary\">{{ item.label }}</div>\n                      </div>\n                      <el-text type=\"info\" size=\"small\" class=\"color-secondary lighter\">{{\n                        item.text\n                      }}</el-text>\n                    </template>\n                  </el-popover>\n                </template>\n              </div>\n            </template>\n          </div>\n          <div v-else class=\"ml-16 mt-8\">\n            <el-text type=\"info\">{{ $t('workflow.tip.noData') }}</el-text>\n          </div>\n        </el-scrollbar>\n      </el-tab-pane>\n      <!-- 工具 -->\n      <el-tab-pane :label=\"$t('views.tool.title')\" name=\"tool\">\n        <LayoutContainer>\n          <template #left>\n            <folder-tree\n              :source=\"SourceTypeEnum.TOOL\"\n              :data=\"toolTreeData\"\n              :currentNodeKey=\"folder.currentFolder?.id\"\n              @handleNodeClick=\"folderClickHandle\"\n              :shareTitle=\"$t('views.shared.shared_tool')\"\n              :showShared=\"permissionPrecise['is_share']()\"\n              :canOperation=\"false\"\n              :treeStyle=\"{ height: '400px' }\"\n            />\n          </template>\n          <el-scrollbar height=\"450\">\n            <NodeContent\n              :list=\"toolList\"\n              @clickNodes=\"\n                (val: any) =>\n                  clickNodes(\n                    val.tool_type === 'WORKFLOW' ? toolWorkflowLibNode : toolLibNode,\n                    val,\n                    'tool',\n                  )\n              \"\n              @onmousedown=\"\n                (val: any) =>\n                  onmousedown(\n                    val.tool_type === 'WORKFLOW' ? toolWorkflowLibNode : toolLibNode,\n                    val,\n                    'tool',\n                  )\n              \"\n            />\n          </el-scrollbar>\n        </LayoutContainer>\n      </el-tab-pane>\n      <!-- 应用 -->\n      <el-tab-pane :label=\"$t('views.application.title')\" name=\"application\">\n        <LayoutContainer>\n          <template #left>\n            <folder-tree\n              :source=\"SourceTypeEnum.APPLICATION\"\n              :data=\"applicationTreeData\"\n              :currentNodeKey=\"folder.currentFolder?.id\"\n              @handleNodeClick=\"folderClickHandle\"\n              :canOperation=\"false\"\n              :treeStyle=\"{ height: '400px' }\"\n            />\n          </template>\n          <el-scrollbar height=\"450\">\n            <NodeContent\n              :list=\"applicationList\"\n              @clickNodes=\"(val: any) => clickNodes(applicationNode, val, 'application')\"\n              @onmousedown=\"(val: any) => onmousedown(applicationNode, val, 'application')\"\n            />\n          </el-scrollbar>\n        </LayoutContainer>\n      </el-tab-pane>\n    </el-tabs>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed, inject } from 'vue'\nimport {\n  getMenuNodes,\n  toolLibNode,\n  applicationNode,\n  toolWorkflowLibNode,\n} from '@/workflow/common/data'\nimport { iconComponent } from '@/workflow/icons/utils'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { isWorkFlow } from '@/utils/application'\nimport useStore from '@/stores'\nimport NodeContent from './NodeContent.vue'\nimport { SourceTypeEnum } from '@/enums/common'\nimport permissionMap from '@/permission'\nimport { useRoute } from 'vue-router'\nimport { WorkflowMode } from '@/enums/application'\nconst workflowModel = inject('workflowMode') as WorkflowMode\nconst route = useRoute()\nconst { user, folder } = useStore()\n\nconst menuNodes = getMenuNodes(workflowModel || WorkflowMode.Application)\nconst search_text = ref<string>('')\nconst props = defineProps({\n  show: {\n    type: Boolean,\n    default: false,\n  },\n  id: {\n    type: String,\n    default: '',\n  },\n  workflowRef: Object,\n})\n\nconst emit = defineEmits(['clickNodes', 'onmousedown'])\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst loading = ref(false)\nconst activeName = ref('base')\n\nconst filter_menu_nodes = computed(() => {\n  if (!search_text.value) return menuNodes || []\n  const searchTerm = search_text.value.toLowerCase()\n\n  return (menuNodes || []).reduce((result: any[], item) => {\n    const filteredList = item.list.filter((listItem) =>\n      listItem.label.toLowerCase().includes(searchTerm),\n    )\n\n    if (filteredList.length) {\n      result.push({ ...item, list: filteredList })\n    }\n\n    return result\n  }, [])\n})\nfunction clickNodes(item: any, data?: any, type?: string) {\n  if (data) {\n    item['properties']['stepName'] = data.name\n    if (type == 'tool') {\n      item['properties']['node_data'] = {\n        ...data,\n        tool_lib_id: data.id,\n        input_field_list: data.input_field_list.map((field: any) => ({\n          ...field,\n          value: field.source == 'reference' ? [] : '',\n        })),\n      }\n    }\n    if (type == 'application') {\n      item['properties']['node_data'] = {\n        name: data.name,\n        icon: data.icon,\n        application_id: data.id,\n      }\n    }\n  }\n  props.workflowRef?.addNode(item)\n\n  emit('clickNodes', item)\n}\n\nfunction onmousedown(item: any, data?: any, type?: string) {\n  if (data) {\n    item['properties']['stepName'] = data.name\n    if (type == 'tool') {\n      item['properties']['node_data'] = {\n        ...data,\n        tool_lib_id: data.id,\n        input_field_list: data.input_field_list.map((field: any) => ({\n          ...field,\n          value: field.source == 'reference' ? [] : '',\n        })),\n      }\n    }\n    if (type == 'application') {\n      if (isWorkFlow(data.type)) {\n        const nodeData = data.work_flow.nodes[0].properties.node_data\n        const fileUploadSetting = nodeData.file_upload_setting\n        item['properties']['node_data'] = {\n          name: data.name,\n          icon: data.icon,\n          application_id: data.id,\n        }\n      } else {\n        item['properties']['node_data'] = {\n          name: data.name,\n          icon: data.icon,\n          application_id: data.id,\n        }\n      }\n    }\n  }\n  props.workflowRef?.onmousedown(item)\n  emit('onmousedown', item)\n}\n\nconst toolTreeData = ref<any[]>([])\nconst toolList = ref<any[]>([])\n\nasync function getToolFolder() {\n  const res: any = await folder.asyncGetFolder(\n    SourceTypeEnum.TOOL,\n    { source_id: props.id },\n    apiType.value,\n    loading,\n  )\n  toolTreeData.value = res.data\n  folder.setCurrentFolder(res.data?.[0] || {})\n}\n\nasync function getToolList() {\n  const baseType = activeName.value == 'DATA_SOURCE_TOOL' ? 'DATA_SOURCE' : 'CUSTOM'\n\n  const res = await loadSharedApi({\n    type: 'tool',\n    isShared: folder.currentFolder?.id === 'share',\n    systemType: apiType.value,\n  }).getToolList({\n    folder_id: folder.currentFolder?.id || user.getWorkspaceId(),\n    tool_type_list: [baseType, 'WORKFLOW'],\n  })\n  toolList.value = res.data?.tools || res.data || []\n  toolList.value = toolList.value?.filter((item: any) => item.is_active)\n}\n\nconst applicationTreeData = ref<any[]>([])\nconst applicationList = ref<any[]>([])\n\nfunction getApplicationFolder() {\n  folder\n    .asyncGetFolder(SourceTypeEnum.APPLICATION, { source_id: props.id }, apiType.value, loading)\n    .then((res: any) => {\n      applicationTreeData.value = res.data\n      folder.setCurrentFolder(res.data?.[0] || {})\n    })\n}\n\nasync function getApplicationList() {\n  const res = await loadSharedApi({\n    type: 'application',\n    systemType: apiType.value,\n  }).getAllApplication({\n    folder_id: folder.currentFolder?.id || user.getWorkspaceId(),\n  })\n  applicationList.value = res.data.filter(\n    (item: any) => item.resource_type === 'application' && item.id !== props.id && item.is_publish,\n  )\n}\n\nfunction folderClickHandle(row: any) {\n  folder.setCurrentFolder(row)\n  if (activeName.value === 'tool') {\n    getToolList()\n  } else {\n    getApplicationList()\n  }\n}\n\nasync function handleClick(val: string) {\n  if (val === 'tool') {\n    await getToolFolder()\n    getToolList()\n  } else if (val === 'application') {\n    getApplicationFolder()\n    getApplicationList()\n  }\n}\n\nonMounted(() => {})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/workflow-dropdown-menu/index.scss",
    "content": ".workflow-dropdown-menu {\n  -moz-user-select: none; /* Firefox */\n  -webkit-user-select: none; /* WebKit内核 */\n  -ms-user-select: none; /* IE10及以后 */\n  -khtml-user-select: none; /* 早期浏览器 */\n  -o-user-select: none; /* Opera */\n  user-select: none; /* CSS3属性 */\n  position: absolute;\n  top: 49px;\n  right: 16px;\n  z-index: 99;\n  width: 600px;\n  box-shadow: 0px 4px 8px 0px rgba(var(--el-text-color-primary-rgb), 0.1);\n  padding-bottom: 8px;\n\n  .title {\n    padding: 12px 12px 4px;\n  }\n  .workflow-dropdown-item {\n    &:hover {\n      background: rgba(var(--el-text-color-primary-rgb), 0.1);\n    }\n  }\n\n  .list-item {\n    box-sizing: border-box;\n    &:hover {\n      border-color: var(--el-color-primary);\n    }\n  }\n\n  .el-tabs__header {\n    margin-bottom: 0;\n  }\n  .list {\n    cursor: default;\n    padding: 12px;\n    gap: 12px;\n    box-sizing: border-box;\n\n    .el-empty {\n      margin: 0 auto;\n    }\n  }\n}\n"
  },
  {
    "path": "ui/src/components/workflow-dropdown-menu/index.vue",
    "content": "<template>\n  <component :is=\"kw[workflow_mode]\" :show=\"show\" :id=\"id\" :workflow-ref=\"workflowRef\"></component>\n</template>\n<script setup lang=\"ts\">\nimport { inject } from 'vue'\nimport { WorkflowMode } from '@/enums/application'\nimport ApplicationDropdownMenu from '@/components/workflow-dropdown-menu/application/index.vue'\nimport KnowledgeDropdownMenu from '@/components/workflow-dropdown-menu/knowledge/index.vue'\nimport KnowledgeDropdownInnerMenu from '@/components/workflow-dropdown-menu/knowledge-inner/index.vue'\nimport ToolDropdownMenu from '@/components/workflow-dropdown-menu/tool/index.vue'\nconst workflow_mode: WorkflowMode = inject('workflowMode') || WorkflowMode.Application\nconst props = defineProps({\n  show: {\n    type: Boolean,\n    default: false,\n  },\n  id: {\n    type: String,\n    default: '',\n  },\n  workflowRef: Object,\n  inner: {\n    type: Boolean,\n    default: false,\n  },\n})\nconst kw: any = {\n  [WorkflowMode.Application]: ApplicationDropdownMenu,\n  [WorkflowMode.ApplicationLoop]: ApplicationDropdownMenu,\n  [WorkflowMode.Knowledge]: props.inner ? KnowledgeDropdownInnerMenu : KnowledgeDropdownMenu,\n  [WorkflowMode.KnowledgeLoop]: props.inner ? KnowledgeDropdownInnerMenu : KnowledgeDropdownMenu,\n  [WorkflowMode.Tool]: ToolDropdownMenu,\n  [WorkflowMode.ToolLoop]: ToolDropdownMenu,\n}\n</script>\n<style lang=\"scss\">\n@use './index.scss';\n</style>\n"
  },
  {
    "path": "ui/src/components/workflow-dropdown-menu/knowledge/NodeContent.vue",
    "content": "<template>\n  <el-input\n    v-model.trim=\"filterText\"\n    :placeholder=\"$t('common.search')\"\n    prefix-icon=\"Search\"\n    clearable\n    style=\"padding: 12px 12px 0 12px\"\n  />\n  <div class=\"list flex-wrap\">\n    <template v-if=\"filterList.length\">\n      <el-popover\n        v-for=\"item in filterList\"\n        :key=\"item.id\"\n        placement=\"right\"\n        :width=\"280\"\n        :show-after=\"500\"\n        :persistent=\"false\"\n      >\n        <template #reference>\n          <div\n            class=\"list-item flex align-center border border-r-6 p-8-12 cursor\"\n            style=\"width: calc(50% - 6px)\"\n            @click.stop=\"emit('clickNodes', item)\"\n            @mousedown.stop=\"emit('onmousedown', item)\"\n          >\n            <el-avatar\n              v-if=\"isAppIcon(item?.icon)\"\n              shape=\"square\"\n              :size=\"20\"\n              style=\"background: none\"\n            >\n              <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n            </el-avatar>\n            <ToolIcon v-else :size=\"20\" :type=\"item?.tool_type\" />\n            <span class=\"ml-8 ellipsis\" :title=\"item.name\">{{ item.name }}</span>\n          </div>\n        </template>\n\n        <template #default>\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <el-avatar\n                v-if=\"isAppIcon(item?.icon)\"\n                shape=\"square\"\n                :size=\"20\"\n                style=\"background: none\"\n              >\n                <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n              </el-avatar>\n              <ToolIcon v-else :size=\"20\" :type=\"item?.tool_type\" />\n              <span class=\"font-medium ml-8 break-all\" :title=\"item.name\">{{ item.name }}</span>\n            </div>\n          </div>\n          <el-text type=\"info\" size=\"small\" class=\"mt-4\">{{ item.desc }}</el-text>\n        </template>\n      </el-popover>\n    </template>\n    <el-empty v-else :description=\"$t('common.noData')\" />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { watch, ref } from 'vue'\nimport { isAppIcon, resetUrl } from '@/utils/common'\n\nconst props = defineProps<{\n  list: any[]\n}>()\n\nconst emit = defineEmits<{\n  (e: 'clickNodes', item: any): void\n  (e: 'onmousedown', item: any): void\n}>()\n\nconst filterText = ref('')\nconst filterList = ref<any[]>([])\n\nfunction filter(list: any[], filterText: string) {\n  if (!filterText.length) {\n    return list\n  }\n  return list.filter((v: any) => v.name.toLowerCase().includes(filterText.toLowerCase()))\n}\n\nwatch([() => filterText.value, () => props.list], () => {\n  filterList.value = filter(props.list, filterText.value)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/workflow-dropdown-menu/knowledge/index.vue",
    "content": "<template>\n  <div\n    v-show=\"show\"\n    class=\"workflow-dropdown-menu border border-r-6 white-bg\"\n    :style=\"{ width: activeName === 'base' || route.path.includes('shared') ? '400px' : '640px' }\"\n  >\n    <el-tabs v-model=\"activeName\" class=\"workflow-dropdown-tabs\" @tab-change=\"handleClick\">\n      <div\n        v-show=\"activeName === 'base'\"\n        style=\"display: flex; width: 100%; justify-content: center\"\n        class=\"mb-12 mt-12\"\n      >\n        <el-input\n          v-model=\"search_text\"\n          class=\"mr-12 ml-12\"\n          :placeholder=\"$t('common.searchBar.placeholder')\"\n        >\n          <template #suffix>\n            <el-icon class=\"el-input__icon\">\n              <search />\n            </el-icon>\n          </template>\n        </el-input>\n      </div>\n\n      <el-tab-pane :label=\"$t('workflow.baseComponent')\" name=\"base\">\n        <el-scrollbar height=\"400\">\n          <div v-if=\"filter_menu_nodes.length > 0\">\n            <template v-for=\"(node, index) in filter_menu_nodes\" :key=\"index\">\n              <el-text type=\"info\" size=\"small\" class=\"color-secondary ml-12\">{{\n                node.label\n              }}</el-text>\n              <div class=\"flex-wrap\" style=\"gap: 12px; padding: 12px\">\n                <template v-for=\"(item, index) in node.list\" :key=\"index\">\n                  <el-popover placement=\"right\" :width=\"280\" :show-after=\"500\" :persistent=\"false\">\n                    <template #reference>\n                      <div\n                        class=\"list-item flex align-center border border-r-6 p-8-12 cursor\"\n                        style=\"width: calc(50% - 6px)\"\n                        @click.stop=\"clickNodes(item)\"\n                        @mousedown.stop=\"onmousedown(item)\"\n                      >\n                        <component\n                          :is=\"iconComponent(`${item.type}-icon`)\"\n                          class=\"mr-8\"\n                          :size=\"20\"\n                        />\n                        <div class=\"lighter\">{{ item.label }}</div>\n                      </div>\n                    </template>\n                    <template #default>\n                      <div class=\"flex align-center mb-8\">\n                        <component\n                          :is=\"iconComponent(`${item.type}-icon`)\"\n                          class=\"mr-8\"\n                          :size=\"32\"\n                        />\n                        <div class=\"lighter color-text-primary\">{{ item.label }}</div>\n                      </div>\n                      <el-text type=\"info\" size=\"small\" class=\"color-secondary lighter\">{{\n                        item.text\n                      }}</el-text>\n                    </template>\n                  </el-popover>\n                </template>\n              </div>\n            </template>\n          </div>\n          <div v-else class=\"ml-16 mt-8\">\n            <el-text type=\"info\">{{ $t('workflow.tip.noData') }}</el-text>\n          </div>\n        </el-scrollbar>\n      </el-tab-pane>\n      <!-- 数据源 -->\n      <el-tab-pane :label=\"$t('views.tool.dataSource.title')\" name=\"DATA_SOURCE_TOOL\">\n        <LayoutContainer :showLeft=\"!route.path.includes('shared')\">\n          <template #left>\n            <folder-tree\n              :source=\"SourceTypeEnum.TOOL\"\n              :data=\"toolTreeData\"\n              :currentNodeKey=\"folder.currentFolder?.id\"\n              @handleNodeClick=\"folderClickHandle\"\n              :shareTitle=\"$t('views.shared.shared_tool')\"\n              :showShared=\"permissionPrecise['is_share']()\"\n              :canOperation=\"false\"\n              :treeStyle=\"{ height: '400px' }\"\n            />\n          </template>\n          <el-scrollbar height=\"450\">\n            <NodeContent\n              :list=\"toolList\"\n              @clickNodes=\"(val: any) => clickNodes(toolLibNode, val)\"\n              @onmousedown=\"(val: any) => onmousedown(toolLibNode, val)\"\n            />\n          </el-scrollbar>\n        </LayoutContainer>\n      </el-tab-pane>\n      <!-- 工具 -->\n      <el-tab-pane :label=\"$t('views.tool.title')\" name=\"CUSTOM_TOOL\">\n        <LayoutContainer :showLeft=\"!route.path.includes('shared')\">\n          <template #left>\n            <folder-tree\n              :source=\"SourceTypeEnum.TOOL\"\n              :data=\"toolTreeData\"\n              :currentNodeKey=\"folder.currentFolder?.id\"\n              @handleNodeClick=\"folderClickHandle\"\n              :shareTitle=\"$t('views.shared.shared_tool')\"\n              :showShared=\"permissionPrecise['is_share']()\"\n              :canOperation=\"false\"\n              :treeStyle=\"{ height: '400px' }\"\n            />\n          </template>\n          <el-scrollbar height=\"450\">\n            <NodeContent\n              :list=\"toolList\"\n              @clickNodes=\"\n                (val: any) =>\n                  clickNodes(val.tool_type === 'WORKFLOW' ? toolWorkflowLibNode : toolLibNode, val)\n              \"\n              @onmousedown=\"\n                (val: any) =>\n                  onmousedown(val.tool_type === 'WORKFLOW' ? toolWorkflowLibNode : toolLibNode, val)\n              \"\n            />\n          </el-scrollbar>\n        </LayoutContainer>\n      </el-tab-pane>\n    </el-tabs>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed, inject } from 'vue'\nimport { getMenuNodes, toolLibNode, toolWorkflowLibNode } from '@/workflow/common/data'\nimport { iconComponent } from '@/workflow/icons/utils'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport useStore from '@/stores'\nimport NodeContent from './NodeContent.vue'\nimport { SourceTypeEnum } from '@/enums/common'\nimport permissionMap from '@/permission'\nimport { useRoute } from 'vue-router'\nimport { WorkflowKind, WorkflowMode } from '@/enums/application'\nconst workflowModel = inject('workflowMode') as WorkflowMode\nconst route = useRoute()\nconst { user, folder } = useStore()\n\nconst menuNodes = getMenuNodes(workflowModel || WorkflowMode.Application)\nconst search_text = ref<string>('')\nconst props = defineProps({\n  show: {\n    type: Boolean,\n    default: false,\n  },\n  id: {\n    type: String,\n    default: '',\n  },\n  workflowRef: Object,\n})\n\nconst emit = defineEmits(['clickNodes', 'onmousedown'])\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst loading = ref(false)\nconst activeName = ref('base')\n\nconst filter_menu_nodes = computed(() => {\n  if (!search_text.value) return menuNodes || []\n  const searchTerm = search_text.value.toLowerCase()\n\n  return (menuNodes || []).reduce((result: any[], item) => {\n    const filteredList = item.list.filter((listItem) =>\n      listItem.label.toLowerCase().includes(searchTerm),\n    )\n\n    if (filteredList.length) {\n      result.push({ ...item, list: filteredList })\n    }\n\n    return result\n  }, [])\n})\nfunction clickNodes(item: any, data?: any) {\n  if (data) {\n    item['properties']['stepName'] = data.name\n\n    if (data.tool_type == 'DATA_SOURCE') {\n      item['properties'].kind = WorkflowKind.DataSource\n    }\n    item['properties']['node_data'] = {\n      ...data,\n      tool_lib_id: data.id,\n      input_field_list: data.input_field_list.map((field: any) => ({\n        ...field,\n        value: field.source == 'reference' ? [] : '',\n      })),\n    }\n  }\n  item['properties']['condition'] = 'OR'\n  props.workflowRef?.addNode(item)\n  emit('clickNodes', item)\n}\n\nfunction onmousedown(item: any, data?: any) {\n  if (data) {\n    item['properties']['stepName'] = data.name\n    if (data.tool_type == 'DATA_SOURCE') {\n      item['properties'].kind = WorkflowKind.DataSource\n    }\n    item['properties']['node_data'] = {\n      ...data,\n      tool_lib_id: data.id,\n      input_field_list: data.input_field_list.map((field: any) => ({\n        ...field,\n        value: field.source == 'reference' ? [] : '',\n      })),\n    }\n  }\n  item['properties']['condition'] = 'OR'\n  props.workflowRef?.onmousedown(item)\n  emit('onmousedown', item)\n}\n\nconst toolTreeData = ref<any[]>([])\nconst toolList = ref<any[]>([])\n\nasync function getToolFolder() {\n  const res: any = await folder.asyncGetFolder(\n    SourceTypeEnum.TOOL,\n    { source_id: props.id },\n    apiType.value,\n    loading,\n  )\n  toolTreeData.value = res.data\n  folder.setCurrentFolder(res.data?.[0] || {})\n}\n\nasync function getToolList() {\n  const baseType = activeName.value == 'DATA_SOURCE_TOOL' ? 'DATA_SOURCE' : 'CUSTOM'\n\n  const res = await loadSharedApi({\n    type: 'tool',\n    isShared: folder.currentFolder?.id === 'share',\n    systemType: apiType.value,\n  }).getToolList({\n    folder_id: folder.currentFolder?.id || user.getWorkspaceId(),\n    tool_type_list: [baseType, 'WORKFLOW'],\n  })\n  toolList.value = res.data?.tools || res.data || []\n  toolList.value = toolList.value?.filter((item: any) => item.is_active)\n}\n\nfunction folderClickHandle(row: any) {\n  folder.setCurrentFolder(row)\n  if (['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'].includes(activeName.value)) {\n    getToolList()\n  }\n}\n\nasync function handleClick(val: string) {\n  if (['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'].includes(val)) {\n    if (!route.path.includes('shared')) {\n      await getToolFolder()\n    }\n    getToolList()\n  }\n}\n\nonMounted(() => {})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/workflow-dropdown-menu/knowledge-inner/NodeContent.vue",
    "content": "<template>\n  <el-input\n    v-model.trim=\"filterText\"\n    :placeholder=\"$t('common.search')\"\n    prefix-icon=\"Search\"\n    clearable\n    style=\"padding: 12px 12px 0 12px\"\n  />\n  <div class=\"list flex-wrap\">\n    <template v-if=\"filterList.length\">\n      <el-popover\n        v-for=\"item in filterList\"\n        :key=\"item.id\"\n        placement=\"right\"\n        :width=\"280\"\n        :show-after=\"500\"\n        :persistent=\"false\"\n      >\n        <template #reference>\n          <div\n            class=\"list-item flex align-center border border-r-6 p-8-12 cursor\"\n            style=\"width: calc(50% - 6px)\"\n            @click.stop=\"emit('clickNodes', item)\"\n            @mousedown.stop=\"emit('onmousedown', item)\"\n          >\n            <el-avatar\n              v-if=\"isAppIcon(item?.icon)\"\n              shape=\"square\"\n              :size=\"20\"\n              style=\"background: none\"\n            >\n              <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n            </el-avatar>\n            <ToolIcon v-else :size=\"20\" :type=\"item?.tool_type\" />\n            <span class=\"ml-8 ellipsis\" :title=\"item.name\">{{ item.name }}</span>\n          </div>\n        </template>\n\n        <template #default>\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <el-avatar\n                v-if=\"isAppIcon(item?.icon)\"\n                shape=\"square\"\n                :size=\"20\"\n                style=\"background: none\"\n              >\n                <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n              </el-avatar>\n              <ToolIcon v-else :size=\"20\" :type=\"item?.tool_type\" />\n              <span class=\"font-medium ml-8 break-all\" :title=\"item.name\">{{ item.name }}</span>\n            </div>\n          </div>\n          <el-text type=\"info\" size=\"small\" class=\"mt-4\">{{ item.desc }}</el-text>\n        </template>\n      </el-popover>\n    </template>\n    <el-empty v-else :description=\"$t('common.noData')\" />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { watch, ref } from 'vue'\nimport { isAppIcon, resetUrl } from '@/utils/common'\n\nconst props = defineProps<{\n  list: any[]\n}>()\n\nconst emit = defineEmits<{\n  (e: 'clickNodes', item: any): void\n  (e: 'onmousedown', item: any): void\n}>()\n\nconst filterText = ref('')\nconst filterList = ref<any[]>([])\n\nfunction filter(list: any[], filterText: string) {\n  if (!filterText.length) {\n    return list\n  }\n  return list.filter((v: any) => v.name.toLowerCase().includes(filterText.toLowerCase()))\n}\n\nwatch([() => filterText.value, () => props.list], () => {\n  filterList.value = filter(props.list, filterText.value)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/workflow-dropdown-menu/knowledge-inner/index.vue",
    "content": "<template>\n  <div\n    v-show=\"show\"\n    class=\"workflow-dropdown-menu border border-r-6 white-bg\"\n    :style=\"{ width: activeName === 'base' || route.path.includes('shared') ? '400px' : '640px' }\"\n  >\n    <el-tabs v-model=\"activeName\" class=\"workflow-dropdown-tabs\" @tab-change=\"handleClick\">\n      <div\n        v-show=\"activeName === 'base'\"\n        style=\"display: flex; width: 100%; justify-content: center\"\n        class=\"mb-12 mt-12\"\n      >\n        <el-input\n          v-model=\"search_text\"\n          class=\"mr-12 ml-12\"\n          :placeholder=\"$t('common.searchBar.placeholder')\"\n        >\n          <template #suffix>\n            <el-icon class=\"el-input__icon\">\n              <search />\n            </el-icon>\n          </template>\n        </el-input>\n      </div>\n\n      <el-tab-pane :label=\"$t('workflow.baseComponent')\" name=\"base\">\n        <el-scrollbar height=\"400\">\n          <div v-if=\"filter_menu_nodes.length > 0\">\n            <template v-for=\"(node, index) in filter_menu_nodes\" :key=\"index\">\n              <el-text type=\"info\" size=\"small\" class=\"color-secondary ml-12\">{{\n                node.label\n              }}</el-text>\n              <div class=\"flex-wrap\" style=\"gap: 12px; padding: 12px\">\n                <template v-for=\"(item, index) in node.list\" :key=\"index\">\n                  <el-popover placement=\"right\" :width=\"280\" :show-after=\"500\" :persistent=\"false\">\n                    <template #reference>\n                      <div\n                        class=\"list-item flex align-center border border-r-6 p-8-12 cursor\"\n                        style=\"width: calc(50% - 6px)\"\n                        @click.stop=\"clickNodes(item)\"\n                        @mousedown.stop=\"onmousedown(item)\"\n                      >\n                        <component\n                          :is=\"iconComponent(`${item.type}-icon`)\"\n                          class=\"mr-8\"\n                          :size=\"20\"\n                        />\n                        <div class=\"lighter\">{{ item.label }}</div>\n                      </div>\n                    </template>\n                    <template #default>\n                      <div class=\"flex align-center mb-8\">\n                        <component\n                          :is=\"iconComponent(`${item.type}-icon`)\"\n                          class=\"mr-8\"\n                          :size=\"32\"\n                        />\n                        <div class=\"lighter color-text-primary\">{{ item.label }}</div>\n                      </div>\n                      <el-text type=\"info\" size=\"small\" class=\"color-secondary lighter\">{{\n                        item.text\n                      }}</el-text>\n                    </template>\n                  </el-popover>\n                </template>\n              </div>\n            </template>\n          </div>\n          <div v-else class=\"ml-16 mt-8\">\n            <el-text type=\"info\">{{ $t('workflow.tip.noData') }}</el-text>\n          </div>\n        </el-scrollbar>\n      </el-tab-pane>\n      <!-- 工具 -->\n      <el-tab-pane :label=\"$t('views.tool.title')\" name=\"CUSTOM_TOOL\">\n        <LayoutContainer :showLeft=\"!route.path.includes('shared')\">\n          <template #left>\n            <folder-tree\n              :source=\"SourceTypeEnum.TOOL\"\n              :data=\"toolTreeData\"\n              :currentNodeKey=\"folder.currentFolder?.id\"\n              @handleNodeClick=\"folderClickHandle\"\n              :shareTitle=\"$t('views.shared.shared_tool')\"\n              :showShared=\"permissionPrecise['is_share']()\"\n              :canOperation=\"false\"\n              :treeStyle=\"{ height: '400px' }\"\n            />\n          </template>\n          <el-scrollbar height=\"450\">\n            <NodeContent\n              :list=\"toolList\"\n              @clickNodes=\"\n                (val: any) =>\n                  clickNodes(val.tool_type === 'WORKFLOW' ? toolWorkflowLibNode : toolLibNode, val)\n              \"\n              @onmousedown=\"\n                (val: any) =>\n                  onmousedown(val.tool_type === 'WORKFLOW' ? toolWorkflowLibNode : toolLibNode, val)\n              \"\n            />\n          </el-scrollbar>\n        </LayoutContainer>\n      </el-tab-pane>\n    </el-tabs>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed, inject } from 'vue'\nimport { getMenuNodes, toolLibNode, toolWorkflowLibNode } from '@/workflow/common/data'\nimport { iconComponent } from '@/workflow/icons/utils'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport useStore from '@/stores'\nimport NodeContent from './NodeContent.vue'\nimport { SourceTypeEnum } from '@/enums/common'\nimport permissionMap from '@/permission'\nimport { useRoute } from 'vue-router'\nimport { WorkflowKind, WorkflowMode } from '@/enums/application'\nconst workflowModel = inject('workflowMode') as WorkflowMode\nconst route = useRoute()\nconst { user, folder } = useStore()\n\nconst menuNodes = getMenuNodes(workflowModel || WorkflowMode.Application)?.filter(\n  (item, index) => index > 0,\n)\nconst search_text = ref<string>('')\nconst props = defineProps({\n  show: {\n    type: Boolean,\n    default: false,\n  },\n  id: {\n    type: String,\n    default: '',\n  },\n  workflowRef: Object,\n})\n\nconst emit = defineEmits(['clickNodes', 'onmousedown'])\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst loading = ref(false)\nconst activeName = ref('base')\n\nconst filter_menu_nodes = computed(() => {\n  if (!search_text.value) return menuNodes || []\n  const searchTerm = search_text.value.toLowerCase()\n\n  return (menuNodes || []).reduce((result: any[], item) => {\n    const filteredList = item.list.filter((listItem) =>\n      listItem.label.toLowerCase().includes(searchTerm),\n    )\n\n    if (filteredList.length) {\n      result.push({ ...item, list: filteredList })\n    }\n\n    return result\n  }, [])\n})\nfunction clickNodes(item: any, data?: any) {\n  if (data) {\n    item['properties']['stepName'] = data.name\n\n    if (data.tool_type == 'DATA_SOURCE') {\n      item['properties'].kind = WorkflowKind.DataSource\n    }\n    item['properties']['node_data'] = {\n      ...data,\n      tool_lib_id: data.id,\n      input_field_list: data.input_field_list.map((field: any) => ({\n        ...field,\n        value: field.source == 'reference' ? [] : '',\n      })),\n    }\n  }\n  item['properties']['condition'] = 'OR'\n  props.workflowRef?.addNode(item)\n\n  emit('clickNodes', item)\n}\n\nfunction onmousedown(item: any, data?: any) {\n  if (data) {\n    item['properties']['stepName'] = data.name\n    if (data.tool_type == 'DATA_SOURCE') {\n      item['properties'].kind = WorkflowKind.DataSource\n    }\n\n    item['properties']['node_data'] = {\n      ...data,\n      tool_lib_id: data.id,\n      input_field_list: data.input_field_list.map((field: any) => ({\n        ...field,\n        value: field.source == 'reference' ? [] : '',\n      })),\n    }\n  }\n  item['properties']['condition'] = 'OR'\n  props.workflowRef?.onmousedown(item)\n  emit('onmousedown', item)\n}\n\nconst toolTreeData = ref<any[]>([])\nconst toolList = ref<any[]>([])\n\nasync function getToolFolder() {\n  const res: any = await folder.asyncGetFolder(\n    SourceTypeEnum.TOOL,\n    { source_id: props.id },\n    apiType.value,\n    loading,\n  )\n  toolTreeData.value = res.data\n  folder.setCurrentFolder(res.data?.[0] || {})\n}\n\nasync function getToolList() {\n  const baseType = activeName.value == 'DATA_SOURCE_TOOL' ? 'DATA_SOURCE' : 'CUSTOM'\n\n  const res = await loadSharedApi({\n    type: 'tool',\n    isShared: folder.currentFolder?.id === 'share',\n    systemType: apiType.value,\n  }).getToolList({\n    folder_id: folder.currentFolder?.id || user.getWorkspaceId(),\n    tool_type_list: [baseType, 'WORKFLOW'],\n  })\n  toolList.value = res.data?.tools || res.data || []\n  toolList.value = toolList.value?.filter((item: any) => item.is_active)\n}\n\nfunction folderClickHandle(row: any) {\n  folder.setCurrentFolder(row)\n  if (['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'].includes(activeName.value)) {\n    getToolList()\n  }\n}\n\nasync function handleClick(val: string) {\n  if (['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'].includes(val)) {\n    if (!route.path.includes('shared')) {\n      await getToolFolder()\n    }\n    getToolList()\n  }\n}\n\nonMounted(() => {})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/workflow-dropdown-menu/tool/NodeContent.vue",
    "content": "<template>\n  <el-input\n    v-model.trim=\"filterText\"\n    :placeholder=\"$t('common.search')\"\n    prefix-icon=\"Search\"\n    clearable\n    style=\"padding: 12px 12px 0 12px\"\n  />\n  <div class=\"list flex-wrap\">\n    <template v-if=\"filterList.length\">\n      <el-popover\n        v-for=\"item in filterList\"\n        :key=\"item.id\"\n        placement=\"right\"\n        :width=\"280\"\n        :show-after=\"500\"\n        :persistent=\"false\"\n      >\n        <template #reference>\n          <div\n            class=\"list-item flex align-center border border-r-6 p-8-12 cursor\"\n            style=\"width: calc(50% - 6px)\"\n            @click.stop=\"emit('clickNodes', item)\"\n            @mousedown.stop=\"emit('onmousedown', item)\"\n          >\n            <el-avatar\n              v-if=\"isAppIcon(item?.icon)\"\n              shape=\"square\"\n              :size=\"20\"\n              style=\"background: none\"\n            >\n              <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n            </el-avatar>\n            <ToolIcon v-else :size=\"20\" :type=\"item?.tool_type\" />\n            <span class=\"ml-8 ellipsis\" :title=\"item.name\">{{ item.name }}</span>\n          </div>\n        </template>\n\n        <template #default>\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <el-avatar\n                v-if=\"isAppIcon(item?.icon)\"\n                shape=\"square\"\n                :size=\"20\"\n                style=\"background: none\"\n              >\n                <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n              </el-avatar>\n              <ToolIcon v-else :size=\"20\" :type=\"item?.tool_type\" />\n              <span class=\"font-medium ml-8 break-all\" :title=\"item.name\">{{ item.name }}</span>\n            </div>\n          </div>\n          <el-text type=\"info\" size=\"small\" class=\"mt-4\">{{ item.desc }}</el-text>\n        </template>\n      </el-popover>\n    </template>\n    <el-empty v-else :description=\"$t('common.noData')\" />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { watch, ref } from 'vue'\nimport { isAppIcon, resetUrl } from '@/utils/common'\n\nconst props = defineProps<{\n  list: any[]\n}>()\n\nconst emit = defineEmits<{\n  (e: 'clickNodes', item: any): void\n  (e: 'onmousedown', item: any): void\n}>()\n\nconst filterText = ref('')\nconst filterList = ref<any[]>([])\n\nfunction filter(list: any[], filterText: string) {\n  if (!filterText.length) {\n    return list\n  }\n  return list.filter((v: any) => v.name.toLowerCase().includes(filterText.toLowerCase()))\n}\n\nwatch([() => filterText.value, () => props.list], () => {\n  filterList.value = filter(props.list, filterText.value)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/workflow-dropdown-menu/tool/index.vue",
    "content": "<template>\n  <div\n    v-show=\"show\"\n    class=\"workflow-dropdown-menu border border-r-6 white-bg\"\n    :style=\"{ width: activeName === 'base' || route.path.includes('shared') ? '400px' : '640px' }\"\n  >\n    <el-tabs v-model=\"activeName\" class=\"workflow-dropdown-tabs\" @tab-change=\"handleClick\">\n      <div\n        v-show=\"activeName === 'base'\"\n        style=\"display: flex; width: 100%; justify-content: center\"\n        class=\"mb-12 mt-12\"\n      >\n        <el-input\n          v-model=\"search_text\"\n          class=\"mr-12 ml-12\"\n          :placeholder=\"$t('common.searchBar.placeholder')\"\n        >\n          <template #suffix>\n            <el-icon class=\"el-input__icon\">\n              <search />\n            </el-icon>\n          </template>\n        </el-input>\n      </div>\n\n      <el-tab-pane :label=\"$t('workflow.baseComponent')\" name=\"base\">\n        <el-scrollbar height=\"400\">\n          <div v-if=\"filter_menu_nodes.length > 0\">\n            <template v-for=\"(node, index) in filter_menu_nodes\" :key=\"index\">\n              <el-text type=\"info\" size=\"small\" class=\"color-secondary ml-12\">{{\n                node.label\n              }}</el-text>\n              <div class=\"flex-wrap\" style=\"gap: 12px; padding: 12px\">\n                <template v-for=\"(item, index) in node.list\" :key=\"index\">\n                  <el-popover placement=\"right\" :width=\"280\" :show-after=\"500\" :persistent=\"false\">\n                    <template #reference>\n                      <div\n                        class=\"list-item flex align-center border border-r-6 p-8-12 cursor\"\n                        style=\"width: calc(50% - 6px)\"\n                        @click.stop=\"clickNodes(item)\"\n                        @mousedown.stop=\"onmousedown(item)\"\n                      >\n                        <component\n                          :is=\"iconComponent(`${item.type}-icon`)\"\n                          class=\"mr-8\"\n                          :size=\"20\"\n                        />\n                        <div class=\"lighter\">{{ item.label }}</div>\n                      </div>\n                    </template>\n                    <template #default>\n                      <div class=\"flex align-center mb-8\">\n                        <component\n                          :is=\"iconComponent(`${item.type}-icon`)\"\n                          class=\"mr-8\"\n                          :size=\"32\"\n                        />\n                        <div class=\"lighter color-text-primary\">{{ item.label }}</div>\n                      </div>\n                      <el-text type=\"info\" size=\"small\" class=\"color-secondary lighter\">{{\n                        item.text\n                      }}</el-text>\n                    </template>\n                  </el-popover>\n                </template>\n              </div>\n            </template>\n          </div>\n          <div v-else class=\"ml-16 mt-8\">\n            <el-text type=\"info\">{{ $t('workflow.tip.noData') }}</el-text>\n          </div>\n        </el-scrollbar>\n      </el-tab-pane>\n      <!-- 工具 -->\n      <el-tab-pane :label=\"$t('views.tool.title')\" name=\"CUSTOM_TOOL\">\n        <LayoutContainer :showLeft=\"!route.path.includes('shared')\">\n          <template #left>\n            <folder-tree\n              :source=\"SourceTypeEnum.TOOL\"\n              :data=\"toolTreeData\"\n              :currentNodeKey=\"folder.currentFolder?.id\"\n              @handleNodeClick=\"folderClickHandle\"\n              :shareTitle=\"$t('views.shared.shared_tool')\"\n              :showShared=\"permissionPrecise['is_share']()\"\n              :canOperation=\"false\"\n              :treeStyle=\"{ height: '400px' }\"\n            />\n          </template>\n          <el-scrollbar height=\"450\">\n            <NodeContent\n              :list=\"toolList\"\n              @clickNodes=\"\n                (val: any) =>\n                  clickNodes(val.tool_type === 'WORKFLOW' ? toolWorkflowLibNode : toolLibNode, val)\n              \"\n              @onmousedown=\"\n                (val: any) =>\n                  onmousedown(val.tool_type === 'WORKFLOW' ? toolWorkflowLibNode : toolLibNode, val)\n              \"\n            />\n          </el-scrollbar>\n        </LayoutContainer>\n      </el-tab-pane>\n    </el-tabs>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed, inject } from 'vue'\nimport { getMenuNodes, toolLibNode, toolWorkflowLibNode } from '@/workflow/common/data'\nimport { iconComponent } from '@/workflow/icons/utils'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport useStore from '@/stores'\nimport NodeContent from './NodeContent.vue'\nimport { SourceTypeEnum } from '@/enums/common'\nimport permissionMap from '@/permission'\nimport { useRoute } from 'vue-router'\nimport { WorkflowKind, WorkflowMode } from '@/enums/application'\nconst workflowModel = inject('workflowMode') as WorkflowMode\nconst route = useRoute()\nconst { user, folder } = useStore()\n\nconst menuNodes = getMenuNodes(workflowModel || WorkflowMode.Application)\nconst search_text = ref<string>('')\nconst props = defineProps({\n  show: {\n    type: Boolean,\n    default: false,\n  },\n  id: {\n    type: String,\n    default: '',\n  },\n  workflowRef: Object,\n})\n\nconst emit = defineEmits(['clickNodes', 'onmousedown'])\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst loading = ref(false)\nconst activeName = ref('base')\n\nconst filter_menu_nodes = computed(() => {\n  if (!search_text.value) return menuNodes || []\n  const searchTerm = search_text.value.toLowerCase()\n\n  return (menuNodes || []).reduce((result: any[], item) => {\n    const filteredList = item.list.filter((listItem) =>\n      listItem.label.toLowerCase().includes(searchTerm),\n    )\n\n    if (filteredList.length) {\n      result.push({ ...item, list: filteredList })\n    }\n\n    return result\n  }, [])\n})\nfunction clickNodes(item: any, data?: any) {\n  if (data) {\n    item['properties']['stepName'] = data.name\n\n    if (data.tool_type == 'DATA_SOURCE') {\n      item['properties'].kind = WorkflowKind.DataSource\n    }\n    item['properties']['node_data'] = {\n      ...data,\n      tool_lib_id: data.id,\n      input_field_list: data.input_field_list.map((field: any) => ({\n        ...field,\n        value: field.source == 'reference' ? [] : '',\n      })),\n    }\n  }\n  item['properties']['condition'] = 'OR'\n  props.workflowRef?.addNode(item)\n\n  emit('clickNodes', item)\n}\n\nfunction onmousedown(item: any, data?: any) {\n  if (data) {\n    item['properties']['stepName'] = data.name\n    if (data.tool_type == 'DATA_SOURCE') {\n      item['properties'].kind = WorkflowKind.DataSource\n    }\n\n    item['properties']['node_data'] = {\n      ...data,\n      tool_lib_id: data.id,\n      input_field_list: data.input_field_list.map((field: any) => ({\n        ...field,\n        value: field.source == 'reference' ? [] : '',\n      })),\n    }\n  }\n  item['properties']['condition'] = 'OR'\n  props.workflowRef?.onmousedown(item)\n  emit('onmousedown', item)\n}\n\nconst toolTreeData = ref<any[]>([])\nconst toolList = ref<any[]>([])\n\nasync function getToolFolder() {\n  const res: any = await folder.asyncGetFolder(\n    SourceTypeEnum.TOOL,\n    { source_id: props.id },\n    apiType.value,\n    loading,\n  )\n  toolTreeData.value = res.data\n  folder.setCurrentFolder(res.data?.[0] || {})\n}\n\nasync function getToolList() {\n  const baseType = activeName.value == 'DATA_SOURCE_TOOL' ? 'DATA_SOURCE' : 'CUSTOM'\n  const res = await loadSharedApi({\n    type: 'tool',\n    isShared: folder.currentFolder?.id === 'share',\n    systemType: apiType.value,\n  }).getToolList({\n    folder_id: folder.currentFolder?.id || user.getWorkspaceId(),\n    tool_type_list: [baseType, 'WORKFLOW'],\n  })\n  toolList.value = res.data?.tools || res.data || []\n  toolList.value = toolList.value?.filter((item: any) => item.is_active)\n}\n\nfunction folderClickHandle(row: any) {\n  folder.setCurrentFolder(row)\n  if (['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'].includes(activeName.value)) {\n    getToolList()\n  }\n}\n\nasync function handleClick(val: string) {\n  if (['DATA_SOURCE_TOOL', 'CUSTOM_TOOL'].includes(val)) {\n    if (!route.path.includes('shared')) {\n      await getToolFolder()\n    }\n    getToolList()\n  }\n}\n\nonMounted(() => {})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/components/workspace-dropdown/index.vue",
    "content": "<template>\n  <el-dropdown\n    trigger=\"click\"\n    placement=\"bottom-start\"\n    class=\"workspace-dropdown\"\n    popper-class=\"workspace-dropdown-popper\"\n  >\n    <el-button text style=\"font-size: 14px\" class=\"workspace-dropdown__button\">\n      <AppIcon iconName=\"app-workspace\" style=\"font-size: 18px\"></AppIcon>\n      <span class=\"ellipsis\" style=\"max-width: 155px\" :title=\"currentWorkspace?.name\">\n        {{ i18n_name(currentWorkspace?.name) }}\n      </span>\n      <el-icon class=\"el-icon--right\">\n        <CaretBottom />\n      </el-icon>\n    </el-button>\n    <template #dropdown>\n      <div class=\"w-full p-8\" style=\"box-sizing: border-box\">\n        <el-input\n          v-model=\"filterText\"\n          :placeholder=\"$t('common.search')\"\n          prefix-icon=\"Search\"\n          clearable\n        />\n      </div>\n      <el-scrollbar max-height=\"300\">\n        <el-dropdown-menu v-loading=\"loading\">\n          <el-dropdown-item\n            v-for=\"item in filterData\"\n            :key=\"item.id\"\n            :class=\"`${item.id === currentWorkspace?.id ? 'active' : ''} flex-between`\"\n            @click=\"changeWorkspace(item)\"\n          >\n            <div class=\"flex align-center\" style=\"overflow: hidden\">\n              <AppIcon class=\"mr-8\" iconName=\"app-workspace\" style=\"font-size: 16px\"></AppIcon>\n              <span class=\"ellipsis\" style=\"flex: 1\" :title=\"item.name\">\n                {{ item.name }}\n              </span>\n              <TagGroup v-if=\"item.role_name\" class=\"ml-8\" size=\"small\" :tags=\"item.role_name\" />\n            </div>\n            <el-icon\n              v-show=\"item.id === currentWorkspace?.id\"\n              class=\"ml-8\"\n              style=\"font-size: 16px; margin-right: 0\"\n            >\n              <Check />\n            </el-icon>\n          </el-dropdown-item>\n        </el-dropdown-menu>\n      </el-scrollbar>\n      <div class=\"no-data color-info\" v-if=\"!filterData.length\">{{ $t('common.noData') }}</div>\n    </template>\n  </el-dropdown>\n</template>\n\n<script setup lang=\"ts\">\nimport { watch, ref } from 'vue'\nimport { i18n_name } from '@/utils/common'\nimport type { WorkspaceItem } from '@/api/type/workspace'\nimport useStore from '@/stores'\nconst props = defineProps({\n  data: {\n    type: Array<any>,\n    default: () => [],\n  },\n  currentWorkspace: {\n    type: Object,\n    default: () => {},\n  },\n})\n\nconst { folder } = useStore()\nconst loading = ref(false)\nconst emit = defineEmits(['changeWorkspace'])\nfunction changeWorkspace(item: WorkspaceItem) {\n  folder.setCurrentFolder({})\n  emit('changeWorkspace', item)\n}\n\nconst filterText = ref('')\nconst filterData = ref<any[]>([])\n\nwatch(\n  [() => props.data, () => filterText.value],\n  () => {\n    if (!filterText.value.length) {\n      filterData.value = props.data\n    }\n    filterData.value = props.data.filter((v: any) =>\n      v.name.toLowerCase().includes(filterText.value.toLowerCase()),\n    )\n  },\n  { immediate: true },\n)\n</script>\n<style lang=\"scss\" scoped>\n.workspace-dropdown {\n  &__button {\n    font-size: 14px;\n    padding: 0 12px !important;\n    max-height: 32px;\n    color: var(--el-text-color-primary) !important;\n  }\n}\n\n.no-data {\n  text-align: center;\n  padding-bottom: 16px;\n  font-size: 14px;\n}\n</style>\n<style lang=\"scss\">\n.workspace-dropdown-popper {\n  width: 340px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/directives/clickoutside.ts",
    "content": "import type { App } from 'vue'\nimport { ClickOutside as vClickOutside } from 'element-plus'\nexport default {\n  install: (app: App) => {\n    app.directive('click-outside', vClickOutside)\n  }\n}\n"
  },
  {
    "path": "ui/src/directives/hasPermission.ts",
    "content": "import type { App } from 'vue'\nimport { hasPermission } from '@/utils/permission'\nconst display = async (el: any, binding: any) => {\n  const has = hasPermission(\n    binding.value?.permission || binding.value,\n    binding.value?.compare || 'OR',\n  )\n  if (!has) {\n    el.style.display = 'none'\n  } else {\n    delete el.style.display\n  }\n}\n\nexport default {\n  install: (app: App) => {\n    app.directive('hasPermission', {\n      async created(el: any, binding: any) {\n        display(el, binding)\n      },\n      async beforeUpdate(el: any, binding: any) {\n        display(el, binding)\n      },\n    })\n  },\n}\n"
  },
  {
    "path": "ui/src/directives/index.ts",
    "content": "import type { App } from 'vue'\n\nconst directives = import.meta.glob('./*.ts', { eager: true })\nconst install = (app: App) => {\n  Object.keys(directives)\n    .filter((key: string) => {\n      return !key.endsWith('index.ts')\n    })\n    .forEach((key: string) => {\n      const directive: any = directives[key]\n      app.use(directive.default)\n    })\n}\nexport default { install }\n"
  },
  {
    "path": "ui/src/directives/infiniteScrollUp.ts",
    "content": "import { nextTick } from 'vue'\n\nimport { throttle } from 'lodash-unified'\nimport { getScrollContainer } from 'element-plus/es/utils/index'\nimport type { App } from 'vue'\nexport const SCOPE = 'InfiniteScrollUP'\nexport const CHECK_INTERVAL = 50\nexport const DEFAULT_DELAY = 200\nexport const DEFAULT_DISTANCE = 0\n\nconst attributes = {\n  delay: {\n    type: Number,\n    default: DEFAULT_DELAY\n  },\n  distance: {\n    type: Number,\n    default: DEFAULT_DISTANCE\n  },\n  disabled: {\n    type: Boolean,\n    default: false\n  },\n  immediate: {\n    type: Boolean,\n    default: true\n  }\n}\n\ntype Attrs = typeof attributes\ntype ScrollOptions = { [K in keyof Attrs]: Attrs[K]['default'] }\ntype InfiniteScrollCallback = () => void\ntype InfiniteScrollEl = HTMLElement & {\n  [SCOPE]: {\n    container: HTMLElement | Window\n    containerEl: HTMLElement\n    instance: any\n    delay: number // export for test\n    lastScrollTop: number\n    cb: InfiniteScrollCallback\n    onScroll: () => void\n    observer?: MutationObserver\n  }\n}\n\nconst getScrollOptions = (el: HTMLElement, instance: any): ScrollOptions => {\n  return Object.entries(attributes).reduce((acm: any, [name, option]) => {\n    const { type, default: defaultValue } = option\n    const attrVal: any = el.getAttribute(`infinite-scroll-up-${name}`)\n    let value = instance[attrVal] ?? attrVal ?? defaultValue\n    value = value === 'false' ? false : value\n    value = type(value)\n    acm[name] = Number.isNaN(value) ? defaultValue : value\n    return acm\n  }, {} as ScrollOptions)\n}\n\nconst destroyObserver = (el: InfiniteScrollEl) => {\n  const { observer } = el[SCOPE]\n\n  if (observer) {\n    observer.disconnect()\n    delete el[SCOPE].observer\n  }\n}\n\nconst handleScroll = (el: InfiniteScrollEl, cb: InfiniteScrollCallback) => {\n  const { container, containerEl, instance, observer, lastScrollTop } = el[SCOPE]\n  const { disabled } = getScrollOptions(el, instance)\n  const { scrollTop } = containerEl\n\n  el[SCOPE].lastScrollTop = scrollTop\n\n  // trigger only if full check has done and not disabled and scroll down\n\n  if (observer || disabled || scrollTop > 0) return\n\n  if (scrollTop == 0) {\n    cb.call(instance)\n  }\n}\n\nfunction checkFull(el: InfiniteScrollEl, cb: InfiniteScrollCallback) {\n  const { containerEl, instance } = el[SCOPE]\n  const { disabled } = getScrollOptions(el, instance)\n\n  if (disabled || containerEl.clientHeight == 0) return\n\n  if (containerEl.scrollTop <= 0) {\n    cb.call(instance)\n  } else {\n    destroyObserver(el)\n  }\n}\n\nconst InfiniteScroll = {\n  async mounted(el: any, binding: any) {\n    const { instance, value: cb } = binding\n\n    // ensure parentNode mounted\n    await nextTick()\n\n    const { delay, immediate } = getScrollOptions(el, instance)\n    const container = getScrollContainer(el, true)\n    const containerEl = container === window ? document.documentElement : (container as HTMLElement)\n    const onScroll = throttle(handleScroll.bind(null, el, cb), delay)\n\n    if (!container) return\n\n    el[SCOPE] = {\n      instance,\n      container,\n      containerEl,\n      delay,\n      cb,\n      onScroll,\n      lastScrollTop: containerEl.scrollTop\n    }\n\n    if (immediate) {\n      const observer = new MutationObserver(throttle(checkFull.bind(null, el, cb), CHECK_INTERVAL))\n      el[SCOPE].observer = observer\n      observer.observe(el, { childList: true, subtree: true })\n      checkFull(el, cb)\n    }\n\n    container.addEventListener('scroll', onScroll)\n  },\n  unmounted(el: any) {\n    if (!el[SCOPE]) return\n    const { container, onScroll } = el[SCOPE]\n\n    container?.removeEventListener('scroll', onScroll)\n    destroyObserver(el)\n  },\n  async updated(el: any) {\n    if (!el[SCOPE]) {\n      await nextTick()\n    } else {\n      const { containerEl, cb, observer } = el[SCOPE]\n      if (containerEl.clientHeight && observer) {\n        checkFull(el, cb)\n      }\n    }\n  }\n}\nexport default {\n  install: (app: App) => {\n    app.directive('infinite-scroll-up', InfiniteScroll)\n  }\n}\n"
  },
  {
    "path": "ui/src/directives/resize.ts",
    "content": "import type { App } from 'vue'\nexport default {\n  install: (app: App) => {\n    app.directive('resize', {\n      created(el: any, binding: any) {\n        // 记录长宽\n        let width = ''\n        let height = ''\n        function getSize() {\n          const style = (document.defaultView as any).getComputedStyle(el)\n          // 如果当前长宽和历史长宽不同\n          if (width !== style.width || height !== style.height) {\n            // binding.value在这里就是下面的resizeChart函数\n\n            binding.value({\n              width: parseFloat(style.width),\n              height: parseFloat(style.height)\n            })\n          }\n          width = style.width\n          height = style.height\n        }\n\n        ;(el as any).__vueDomResize__ = setInterval(getSize, 500)\n      },\n      unmounted(el: any, binding: any) {\n        clearInterval((el as any).__vueDomResize__)\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "ui/src/enums/application.ts",
    "content": "export enum SearchMode {\n  embedding = 'views.application.dialog.vectorSearch',\n  keywords = 'views.application.dialog.fullTextSearch',\n  blend = 'views.application.dialog.hybridSearch',\n}\n\nexport enum WorkflowType {\n  Base = 'base-node',\n  KnowledgeBase = 'knowledge-base-node',\n  Start = 'start-node',\n  AiChat = 'ai-chat-node',\n  SearchKnowledge = 'search-knowledge-node',\n  SearchDocument = 'search-document-node',\n  Question = 'question-node',\n  Condition = 'condition-node',\n  Reply = 'reply-node',\n  ToolLib = 'tool-lib-node',\n  ToolWorkflowLib = 'tool-workflow-lib-node',\n  ToolLibCustom = 'tool-node',\n  RerankerNode = 'reranker-node',\n  Application = 'application-node',\n  DocumentExtractNode = 'document-extract-node',\n  DocumentSplitNode = 'document-split-node',\n  ImageUnderstandNode = 'image-understand-node',\n  VariableAssignNode = 'variable-assign-node',\n  FormNode = 'form-node',\n  TextToSpeechNode = 'text-to-speech-node',\n  SpeechToTextNode = 'speech-to-text-node',\n  ImageGenerateNode = 'image-generate-node',\n  McpNode = 'mcp-node',\n  IntentNode = 'intent-node',\n  TextToVideoGenerateNode = 'text-to-video-node',\n  ImageToVideoGenerateNode = 'image-to-video-node',\n  LoopNode = 'loop-node',\n  LoopBodyNode = 'loop-body-node',\n  LoopStartNode = 'loop-start-node',\n  LoopContinueNode = 'loop-continue-node',\n  LoopBreakNode = 'loop-break-node',\n  VariableSplittingNode = 'variable-splitting-node',\n  VariableAggregationNode = 'variable-aggregation-node',\n  VideoUnderstandNode = 'video-understand-node',\n  ParameterExtractionNode = 'parameter-extraction-node',\n  DataSourceLocalNode = 'data-source-local-node',\n  DataSourceWebNode = 'data-source-web-node',\n  KnowledgeWriteNode = 'knowledge-write-node',\n  ToolStartNode = 'tool-start-node',\n  ToolBaseNode = 'tool-base-node',\n}\nexport enum WorkflowKind {\n  DataSource = 'data-source',\n}\nexport enum WorkflowMode {\n  // 应用工作流\n  Application = 'application',\n  // 应用工作流循环\n  ApplicationLoop = 'application-loop',\n  // 知识库工作流\n  Knowledge = 'knowledge',\n  // 工具\n  Tool = 'tool',\n  // 工具循环体\n  ToolLoop = 'tool-loop',\n  // 知识库工作流循环体\n  KnowledgeLoop = 'knowledge-loop',\n}\n"
  },
  {
    "path": "ui/src/enums/common.ts",
    "content": "export enum DeviceType {\n  Mobile = 'Mobile',\n  Desktop = 'Desktop',\n}\n\nexport enum ValidType {\n  Application = 'application',\n  Knowledge = 'knowledge',\n  User = 'user',\n}\n\nexport enum ValidCount {\n  Application = 5,\n  Knowledge = 50,\n  User = 2,\n}\n\nexport enum SourceTypeEnum {\n  KNOWLEDGE = 'KNOWLEDGE',\n  APPLICATION = 'APPLICATION',\n  TOOL = 'TOOL',\n  MODEL = 'MODEL',\n}\n"
  },
  {
    "path": "ui/src/enums/document.ts",
    "content": "export enum hitHandlingMethod {\n  optimization = 'views.document.hitHandlingMethod.optimization',\n  directly_return = 'views.document.hitHandlingMethod.directly_return'\n}\n"
  },
  {
    "path": "ui/src/enums/model.ts",
    "content": "export enum PermissionType {\n  PRIVATE = 'common.private',\n  PUBLIC = 'common.public'\n}\nexport enum PermissionDesc {\n  PRIVATE = 'views.model.modelForm.permissionType.privateDesc',\n  PUBLIC = 'views.model.modelForm.permissionType.publicDesc',\n}\n\nexport enum modelType {\n  EMBEDDING = 'views.model.modelType.EMBEDDING',\n  LLM = 'views.model.modelType.LLM',\n  STT = 'views.model.modelType.STT',\n  TTS = 'views.model.modelType.TTS',\n  IMAGE = 'views.model.modelType.IMAGE',\n  TTI = 'views.model.modelType.TTI',\n  RERANKER = 'views.model.modelType.RERANKER',\n  TTV = 'views.model.modelType.TTV',\n  ITV = 'views.model.modelType.ITV',\n}\n"
  },
  {
    "path": "ui/src/enums/system.ts",
    "content": "export enum AuthorizationEnum {\n  MANAGE = 'MANAGE',\n  VIEW = 'VIEW',\n  ROLE = 'ROLE',\n  NOT_AUTH = 'NOT_AUTH',\n}\n\nexport enum RoleTypeEnum {\n  ADMIN = 'ADMIN',\n  USER = 'USER',\n  WORKSPACE_MANAGE = 'WORKSPACE_MANAGE',\n}\n"
  },
  {
    "path": "ui/src/enums/tool.ts",
    "content": "export enum ToolType {\n  CUSTOM = 'common.custom',\n  INTERNAL = 'views.tool.toolStore.internal',\n}\n"
  },
  {
    "path": "ui/src/enums/trigger.ts",
    "content": "export enum TriggerType {\n  SCHEDULED = 'views.trigger.type.scheduled',\n  EVENT = 'views.trigger.type.event',\n}\n\n\n"
  },
  {
    "path": "ui/src/layout/app-main/index.vue",
    "content": "<template>\n  <router-view v-slot=\"{ Component }\">\n    <transition appear name=\"fade-transform\" mode=\"out-in\">\n      <keep-alive :include=\"cachedViews\">\n        <component :is=\"Component\" :key=\"route.path\" />\n      </keep-alive>\n    </transition>\n  </router-view>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onBeforeUpdate } from 'vue'\nimport { useRoute } from 'vue-router'\n\nconst route = useRoute()\n\nconst cachedViews: any = ref([])\nonBeforeUpdate(() => {\n  const { name, meta } = route\n  if (name && !cachedViews.value.includes(name)) {\n    cachedViews.value.push(name)\n  }\n})\n</script>\n"
  },
  {
    "path": "ui/src/layout/components/breadcrumb/index.vue",
    "content": "<template>\n  <div class=\"breadcrumb ml-4 mt-4 mb-12 flex align-center\">\n    <back-button @click=\"toBack\"></back-button>\n    <div class=\"flex align-center\">\n      <el-avatar\n        v-if=\"isApplication\"\n        shape=\"square\"\n        :size=\"24\"\n        style=\"background: none\"\n        class=\"mr-8\"\n      >\n        <img :src=\"resetUrl(current?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n      </el-avatar>\n      <!-- <LogoIcon\n        v-else-if=\"isApplication\"\n        height=\"28px\"\n        style=\"width: 28px; height: 28px; display: block\"\n        class=\"mr-8\"\n      /> -->\n      <KnowledgeIcon v-else-if=\"isKnowledge\" :type=\"current?.type\" class=\"mr-8\" :size=\"24\" />\n\n      <div class=\"ellipsis\" :title=\"current?.name\">{{ current?.name }}</div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed } from 'vue'\nimport { onBeforeRouteLeave, useRouter, useRoute } from 'vue-router'\nimport { resetUrl } from '@/utils/common'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport useStore from '@/stores'\nimport permissionMap from '@/permission'\nconst { common, folder, user } = useStore()\nconst route = useRoute()\nconst router = useRouter()\n\nconst {\n  meta: { activeMenu },\n  params: { id, folderId },\n  query: { isShared },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst folderType = computed(() => {\n  if (route.path.includes('application')) {\n    return 'application'\n  }\n  if (route.path.includes('knowledge')) {\n    return 'knowledge'\n  } else {\n    return 'application'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap[folderType.value!]['workspace']\n})\n\nconst shareDisabled = computed(() => {\n  return folderId === 'share' || isShared === 'true'\n})\n\nonBeforeRouteLeave((to, from) => {\n  common.saveBreadcrumb(null)\n})\n\nconst loading = ref(false)\n\nconst current = ref<any>(null)\n\nconst isApplication = computed(() => {\n  return activeMenu.includes('application')\n})\nconst isKnowledge = computed(() => {\n  return activeMenu.includes('knowledge')\n})\n\nconst toBackPath = computed(() => {\n  if (route.path.includes('shared')) {\n    return '/system/shared' + activeMenu\n  } else if (route.path.includes('resource-management')) {\n    return '/system/resource-management' + activeMenu\n  } else {\n    return activeMenu\n  }\n})\n\nfunction getKnowledgeDetail() {\n  loading.value = true\n  loadSharedApi({ type: 'knowledge', isShared: shareDisabled.value, systemType: apiType.value })\n    .getKnowledgeDetail(id)\n    .then((res: any) => {\n      current.value = res.data\n      loading.value = false\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nfunction getApplicationDetail() {\n  loading.value = true\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getApplicationDetail(id)\n    .then((res: any) => {\n      current.value = res.data\n      loading.value = false\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nfunction toBack() {\n  if (isKnowledge.value) {\n    folder.setCurrentFolder({\n      id: permissionPrecise.value.folderRead(folderId) ? folderId : user.getWorkspaceId(),\n    })\n  } else if (isApplication.value) {\n    folder.setCurrentFolder({\n      id: permissionPrecise.value.folderRead(current.value.folder)\n        ? current.value.folder\n        : user.getWorkspaceId(),\n    })\n  }\n  router.push({ path: toBackPath.value })\n}\n\nonMounted(() => {\n  if (isKnowledge.value) {\n    getKnowledgeDetail()\n  } else if (isApplication.value) {\n    getApplicationDetail()\n  }\n})\n</script>\n\n<style scoped lang=\"scss\">\n:deep(.dropdown-active) {\n  background-color: var(--el-dropdown-menuItem-hover-fill);\n  .el-dropdown-menu__item {\n    color: var(--el-dropdown-menuItem-hover-color);\n  }\n}\n.breadcrumb {\n  .breadcrumb-hover {\n    padding: 4px;\n    border-radius: 4px;\n    &:hover {\n      background: var(--el-color-primary-light-9);\n      color: var(--el-color-primary);\n    }\n  }\n  &__footer {\n    &:hover {\n      background-color: rgba(var(--el-text-color-primary-rgb), 0.1);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/components/sidebar/SidebarItem.vue",
    "content": "<template>\n  <div v-if=\"(!menu.meta || !menu.meta.hidden) && showMenu()\" class=\"sidebar-item\">\n    <el-sub-menu\n      v-if=\"menu?.children && menu?.children.length > 0\"\n      :index=\"menu.path\"\n      popper-class=\"sidebar-container-popper\"\n    >\n      <template #title>\n        <AppIcon v-if=\"menu.meta && menu.meta.icon\" :iconName=\"menuIcon\" class=\"sidebar-icon\" />\n\n        <span>{{ $t(menu.meta?.title as string) }}</span>\n      </template>\n      <sidebar-item\n        v-hasPermission=\"child.meta?.permission\"\n        v-for=\"(child, index) in menu?.children\"\n        :key=\"index\"\n        :menu=\"child\"\n        :activeMenu=\"activeMenu\"\n      >\n      </sidebar-item>\n    </el-sub-menu>\n    <el-menu-item\n      v-else\n      ref=\"subMenu\"\n      :index=\"menu.path\"\n      popper-class=\"sidebar-popper\"\n      @click=\"clickHandle(menu)\"\n    >\n      <template #title>\n        <AppIcon v-if=\"menu.meta && menu.meta.icon\" :iconName=\"menuIcon\" class=\"sidebar-icon\" />\n        <span v-if=\"menu.meta && menu.meta.title\">{{ $t(menu.meta?.title as string) }}</span>\n      </template>\n    </el-menu-item>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRouter, useRoute, type RouteRecordRaw } from 'vue-router'\nimport { isWorkFlow } from '@/utils/application'\nconst props = defineProps<{\n  menu: RouteRecordRaw\n  activeMenu: any\n}>()\n\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  params: { id, type, from, folderId },\n} = route as any\n\nfunction showMenu() {\n  if (isWorkFlow(type)) {\n    return props.menu.name !== 'AppHitTest'\n  } else {\n    return true\n  }\n}\n\nfunction clickHandle(item?: any) {\n  if (isWorkFlow(type) && item?.name === 'AppSetting') {\n    router.push({ path: `/application/${from}/${id}/workflow` })\n  } else if (type === '4' && item?.name === 'knowledgeWorkflowSetting') {\n    router.push({ path: `/knowledge/${id}/${folderId}/workflow` })\n  }\n}\nconst menuIcon = computed(() => {\n  if (props.activeMenu === props.menu.path) {\n    return props.menu.meta?.iconActive || props.menu?.meta?.icon\n  } else {\n    return props.menu?.meta?.icon\n  }\n})\n</script>\n\n<style scoped lang=\"scss\">\n.sidebar-item {\n  .sidebar-icon {\n    font-size: 20px;\n    margin-top: -2px;\n  }\n  .el-menu-item {\n    padding: 13px 12px 13px 8px !important;\n    font-weight: 500;\n    border-radius: 4px;\n    &:hover {\n      background: rgba(var(--el-text-color-primary-rgb), 0.1);\n      color: var(--el-menu-text-color);\n    }\n  }\n  :deep(.el-sub-menu__title) {\n    padding: 13px 12px 13px 10px !important;\n    &:hover {\n      background: none;\n      color: var(--el-color-primary);\n    }\n  }\n  .el-sub-menu {\n    .el-menu-item {\n      padding-left: 43px !important;\n    }\n  }\n  .el-menu-item.is-active {\n    color: var(--el-color-primary);\n    background: var(--el-color-primary-light-9);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/components/sidebar/index.vue",
    "content": "<template>\n  <div class=\"sidebar p-8\">\n    <div v-if=\"showBreadcrumb\">\n      <AppBreadcrumb />\n    </div>\n    <el-scrollbar wrap-class=\"scrollbar-wrapper\">\n      <el-menu :default-active=\"activeMenu\" router>\n        <sidebar-item\n          v-hasPermission=\"menu.meta?.permission\"\n          v-for=\"(menu, index) in subMenuList\"\n          :key=\"index\"\n          :menu=\"menu\"\n          :activeMenu=\"activeMenu\"\n        >\n        </sidebar-item>\n      </el-menu>\n    </el-scrollbar>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport { getChildRouteListByPathAndName } from '@/router/index'\nimport SidebarItem from './SidebarItem.vue'\nimport AppBreadcrumb from './../breadcrumb/index.vue'\n\nconst route = useRoute()\n\nconst showBreadcrumb = computed(() => {\n  const { meta } = route as any\n  return meta?.breadcrumb\n})\n\nconst subMenuList = computed(() => {\n  const { meta } = route\n  return getChildRouteListByPathAndName(meta.parentPath, meta.parentName)\n})\n\nconst activeMenu = computed(() => {\n  const { path, meta } = route\n  return meta.active || path\n})\n</script>\n\n<style lang=\"scss\">\n.sidebar {\n  .el-menu {\n    height: 100%;\n    border: none;\n    background: none;\n    max-height: calc(100vh - 100px);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/hooks/useResize.ts",
    "content": "import { nextTick, onBeforeMount, onMounted, onBeforeUnmount } from 'vue'\nimport useStore from '@/stores'\nimport { DeviceType } from '@/enums/common'\n/** 参考 Bootstrap 的响应式设计 WIDTH = 768 */\nconst WIDTH = 768\n\n/** 根据大小变化重新布局 */\nexport default () => {\n  const { common } = useStore()\n  const _isMobile = () => {\n    const rect = document.body?.getBoundingClientRect()\n    return rect.width - 1 < WIDTH\n  }\n\n  // const _resizeHandler = () => {\n  //   if (!document.hidden) {\n  //     const isMobile = _isMobile()\n  //     common.toggleDevice(isMobile ? DeviceType.Mobile : DeviceType.Desktop)\n  //   }\n  // }\n\n  // onBeforeMount(() => {\n  //   window.addEventListener('resize', _resizeHandler)\n  // })\n\n  onMounted(() => {\n    nextTick(() => {\n      if (_isMobile()) {\n        common.toggleDevice(DeviceType.Mobile)\n      }\n    })\n  })\n\n  // onBeforeUnmount(() => {\n  //   window.removeEventListener('resize', _resizeHandler)\n  // })\n}\n"
  },
  {
    "path": "ui/src/layout/layout-header/SystemHeader.vue",
    "content": "·\n<template>\n  <div class=\"app-top-bar-container border-b flex-center\">\n    <div class=\"logo mt-4\">\n      <LogoFull />\n    </div>\n\n    <div class=\"flex-between w-full align-center\">\n      <h4><el-divider class=\"ml-16 mr-16\" direction=\"vertical\" />{{ $t('views.system.title') }}</h4>\n      <div class=\"flex align-center mr-8\">\n        <TopAbout type=\"system\"></TopAbout>\n        <el-divider class=\"ml-8 mr-8\" direction=\"vertical\" />\n        <el-button\n          link\n          @click=\"goHome\"\n          style=\"color: var(--el-text-color-primary)\"\n          v-if=\"\n            hasPermission(\n              [\n                RoleConst.USER.getWorkspaceRole,\n                RoleConst.EXTENDS_USER.getWorkspaceRole,\n                RoleConst.EXTENDS_WORKSPACE_MANAGE.getWorkspaceRole,\n                RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              ],\n              'OR',\n            )\n          \"\n        >\n          <AppIcon class=\"mr-8\" iconName=\"app-workspace\" style=\"font-size: 16px\"></AppIcon>\n          {{ $t('views.workspace.toWorkspace') }}</el-button\n        >\n      </div>\n    </div>\n    <Avatar></Avatar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { RoleConst } from '@/utils/permission/data'\nimport Avatar from './avatar/index.vue'\nimport TopAbout from './top-about/index.vue'\nimport { useRouter } from 'vue-router'\nimport { hasPermission } from '@/utils/permission'\n\nconst router = useRouter()\nconst goHome = () => {\n  router.push('/')\n}\n</script>\n<style lang=\"scss\" scoped>\n.app-top-bar-container {\n  height: var(--app-header-height);\n  box-sizing: border-box;\n  padding: var(--app-header-padding);\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/layout-header/UserHeader.vue",
    "content": "·\n<template>\n  <div class=\"app-top-bar-container border-b flex-center\">\n    <div class=\"logo mt-4\">\n      <LogoFull />\n    </div>\n\n    <div class=\"flex-between w-full\">\n      <div class=\"ml-24 flex align-center w-120\">\n        <!-- 企业版: 工作空间下拉框-->\n        <el-divider\n          class=\"mr-8\"\n          direction=\"vertical\"\n          v-if=\"hasPermission(EditionConst.IS_EE, 'OR')\"\n        />\n        <WorkspaceDropdown\n          v-if=\"hasPermission(EditionConst.IS_EE, 'OR')\"\n          :data=\"user.workspace_list\"\n          :currentWorkspace=\"currentWorkspace\"\n          @changeWorkspace=\"changeWorkspace\"\n        />\n      </div>\n      <TopMenu></TopMenu>\n      <TopAbout class=\"mr-12\"></TopAbout>\n    </div>\n    <Avatar></Avatar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport TopMenu from './top-menu/index.vue'\nimport Avatar from './avatar/index.vue'\nimport TopAbout from './top-about/index.vue'\nimport { EditionConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport type { WorkspaceItem } from '@/api/type/workspace'\nimport useStore from '@/stores'\nconst router = useRouter()\nconst route = useRoute()\n\nconst { user } = useStore()\nconst currentWorkspace = computed(() => {\n  return user.workspace_list.find((w) => w.id == user.workspace_id)\n})\n\nfunction changeWorkspace(item: WorkspaceItem) {\n  const {\n    meta: { activeMenu },\n  } = route as any\n  if (item.id === user.workspace_id) return\n  user.setWorkspaceId(item.id || 'default')\n  if (activeMenu.includes('application') && route.path != '/application') {\n    router.push('/application')\n  } else if (activeMenu.includes('knowledge') && route.path != '/knowledge') {\n    router.push('/knowledge')\n  } else {\n    window.location.reload()\n  }\n}\n</script>\n<style lang=\"scss\" scoped>\n.app-top-bar-container {\n  height: var(--app-header-height);\n  box-sizing: border-box;\n  padding: var(--app-header-padding);\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/layout-header/avatar/APIKeyDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('layout.apiKey')\"\n    v-model=\"dialogVisible\"\n    width=\"1080\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    align-center\n  >\n    <el-card shadow=\"never\" class=\"layout-bg mb-16\">\n      <el-text type=\"info\" class=\"color-secondary\">{{ $t('layout.apiServiceAddress') }}</el-text>\n      <p style=\"margin-top: 10px\">\n        <span class=\"vertical-middle lighter break-all\">\n          {{ apiUrl }}\n        </span>\n        <el-button type=\"primary\" text @click=\"copyClick(apiUrl)\">\n          <AppIcon iconName=\"app-copy\"></AppIcon>\n        </el-button>\n      </p>\n    </el-card>\n\n    <el-button type=\"primary\" class=\"mb-16\" @click=\"createApiKey\">\n      {{ $t('common.create') }}\n    </el-button>\n    <app-table\n      :data=\"apiKey\"\n      :loading=\"loading\"\n      style=\"min-height: 300px\"\n      :max-height=\"420\"\n      :pagination-config=\"paginationConfig\"\n      @sizeChange=\"handleSizeChange\"\n      @changePage=\"getApiKeyList\"\n      @sort-change=\"handleSortChange\"\n    >\n      <el-table-column prop=\"secret_key\" label=\"API Key\">\n        <template #default=\"{ row }\">\n          <div class=\"api-key-container\">\n            <el-tooltip :content=\"row.secret_key\" placement=\"top\" effect=\"light\" :hide-after=\"0\">\n              <span class=\"api-key-text vertical-middle lighter break-all\">\n                {{ row.secret_key }}\n              </span>\n            </el-tooltip>\n            <el-button type=\"primary\" text @click=\"copyClick(row.secret_key)\" class=\"copy-btn\">\n              <AppIcon iconName=\"app-copy\"></AppIcon>\n            </el-button>\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('views.document.enableStatus.label')\" width=\"100\">\n        <template #default=\"{ row }\">\n          <div v-if=\"row.is_active\" class=\"flex align-center\">\n            <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n              <SuccessFilled/>\n            </el-icon>\n            <span class=\"color-text-primary\">\n              {{ $t('common.status.enabled') }}\n            </span>\n          </div>\n          <div v-else class=\"flex align-center\">\n            <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n            <span class=\"color-text-primary\">\n              {{ $t('common.status.disabled') }}\n            </span>\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('layout.crossSettings')\" width=\"100\" prop=\"allow_cross_domain\">\n        <template #default=\"{ row }\">\n          <el-tag size=\"small\" type=\"info\" class=\"info-tag\" v-if=\"row.allow_cross_domain\">\n            {{ $t('views.system.authentication.scanTheQRCode.alreadyTurnedOn') }}\n          </el-tag>\n          <el-tag size=\"small\" class=\"blue-tag\" v-else>\n            {{ $t('views.system.authentication.scanTheQRCode.notEnabled') }}\n          </el-tag>\n        </template>\n      </el-table-column>\n\n      <el-table-column :label=\"$t('layout.about.expiredTime')\" width=\"265\">\n        <template #default=\"{ row }\">\n          <span v-if=\"row.is_permanent\" class=\"permanent-status\">\n            {{ t('layout.time.neverExpires') }}\n          </span>\n          <span v-else class=\"expiry-info\">\n            <span\n              v-if=\"fromNowDate(row.expire_time)\"\n              :class=\"getExpiryClass(row.expire_time)\"\n              class=\"relative-time\"\n            >\n              ({{ fromNowDate(row.expire_time) }})\n            </span>\n            {{ datetimeFormat(row.expire_time) }}\n          </span>\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('common.createDate')\" width=\"170\" prop=\"create_time\" sortable>\n        <template #default=\"{ row }\">\n          {{ datetimeFormat(row.create_time) }}\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"130\">\n        <template #default=\"{ row }\">\n          <span @click.stop>\n            <el-switch size=\"small\" v-model=\"row.is_active\" @change=\"changeState($event, row)\"/>\n          </span>\n          <el-divider direction=\"vertical\"/>\n          <span class=\"mr-4\">\n            <el-tooltip effect=\"dark\" :content=\"$t('common.setting')\" placement=\"top\">\n              <el-button type=\"primary\" text @click.stop=\"settingApiKey(row)\">\n                <AppIcon iconName=\"app-edit\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </span>\n          <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n            <el-button type=\"primary\" text @click=\"deleteApiKey(row)\">\n              <AppIcon iconName=\"app-delete\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </template>\n      </el-table-column>\n    </app-table>\n    <SettingAPIKeyDialog ref=\"SettingAPIKeyDialogRef\" @refresh=\"refresh\"/>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport {ref, watch, reactive} from 'vue'\nimport {useRoute} from 'vue-router'\nimport {copyClick} from '@/utils/clipboard'\nimport systemKeyApi from '@/api/system/api-key'\nimport {datetimeFormat} from '@/utils/time'\nimport {MsgSuccess, MsgConfirm} from '@/utils/message'\nimport {t} from '@/locales'\nimport SettingAPIKeyDialog from '@/views/application-overview/component/SettingAPIKeyDrawer.vue'\nimport {fromNowDate} from '@/utils/time'\n\nconst route = useRoute()\nconst {\n  params: {id},\n} = route\n\nconst props = defineProps({\n  userId: {\n    type: String,\n    default: '',\n  },\n})\n\nconst emit = defineEmits(['addData'])\n\nconst apiUrl = window.location.origin + `${window.MaxKB.prefix}/api-doc/`\nconst SettingAPIKeyDialogRef = ref()\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\nconst apiKey = ref<any>(null)\nconst orderBy = ref<string>('')\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    apiKey.value = null\n  }\n})\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getApiKeyList()\n}\n\nfunction settingApiKey(row: any) {\n  SettingAPIKeyDialogRef.value.open(row, 'USER')\n}\n\nfunction deleteApiKey(row: any) {\n  MsgConfirm(\n    `${t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm1')}: ${row.secret_key}?`,\n    t(t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm2')),\n    {\n      confirmButtonText: t('common.confirm'),\n      cancelButtonText: t('common.cancel'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      systemKeyApi.delAPIKey(row.id, loading).then(() => {\n        MsgSuccess(t('common.deleteSuccess'))\n        getApiKeyList()\n      })\n    })\n    .catch(() => {\n    })\n}\n\nfunction changeState(bool: boolean, row: any) {\n  const obj = {\n    is_active: bool,\n  }\n  const str = bool ? t('common.status.enabled') : t('common.status.disabled')\n  systemKeyApi.putAPIKey(row.id, obj, loading).then((res) => {\n    MsgSuccess(str)\n    getApiKeyList()\n  })\n}\n\nfunction createApiKey() {\n  systemKeyApi.postAPIKey(loading).then((res) => {\n    MsgSuccess(t('common.createSuccess'))\n    getApiKeyList()\n  })\n}\n\nconst open = () => {\n  getApiKeyList()\n  dialogVisible.value = true\n}\n\nfunction getApiKeyList() {\n  const param = {\n    order_by: orderBy.value,\n  }\n  systemKeyApi\n    .getAPIKey(paginationConfig.current_page, paginationConfig.page_size, param, loading)\n    .then((res: any) => {\n      apiKey.value = res.data.records\n      paginationConfig.total = res.data.total\n    })\n}\n\nfunction getExpiryClass(expireTime: any) {\n  const status = fromNowDate(expireTime)\n  if (status === t('layout.time.expired')) {\n    return 'color-danger' // 红色\n  } else {\n    return 'color-warning' // 橙色\n  }\n}\n\nfunction handleSortChange({prop, order}: { prop: string; order: string }) {\n  orderBy.value = order === 'ascending' ? prop : `-${prop}`\n  getApiKeyList()\n}\n\nfunction refresh() {\n  getApiKeyList()\n}\n\ndefineExpose({open})\n</script>\n<style lang=\"scss\" scoped>\n.api-key-container {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n\n  .api-key-text {\n    flex: 1;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    min-width: 0; /* 允许弹性收缩 */\n    cursor: pointer; /* 显示手型光标提示可悬停 */\n  }\n\n  .copy-btn {\n    flex-shrink: 0; /* 复制按钮不收缩 */\n  }\n}\n\n</style>\n"
  },
  {
    "path": "ui/src/layout/layout-header/avatar/AboutDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"aboutDialogVisible\"\n    class=\"about-dialog border-r-6\"\n    :class=\"!isDefaultTheme ? 'dialog-custom-header' : ''\"\n  >\n    <template #header=\"{ titleId, titleClass }\">\n      <div class=\"logo flex-center\" :id=\"titleId\" :class=\"titleClass\">\n        <LogoFull height=\"59px\"/>\n      </div>\n    </template>\n    <div class=\"about-ui\" v-loading=\"loading\">\n      <div class=\"flex\">\n        <span class=\"label\">{{ $t('layout.about.authorize') }}</span\n        ><span>{{ licenseInfo?.corporation || '-' }}</span>\n      </div>\n      <div class=\"flex\">\n        <span class=\"label\">{{ $t('layout.about.expiredTime') }}</span>\n        <span\n        >{{ licenseInfo?.expired || '-' }}\n          <span class=\"color-danger\"\n                v-if=\"licenseInfo?.expired && fromNowDate(licenseInfo?.expired)\"\n          >（{{ fromNowDate(licenseInfo?.expired) }}）</span>\n        </span\n        >\n      </div>\n      <div class=\"flex\">\n        <span class=\"label\">{{ $t('layout.about.edition.label') }}</span>\n        <span>{{\n            editionText\n          }}</span>\n      </div>\n      <div class=\"flex\">\n        <span class=\"label\">{{ $t('layout.about.version') }}</span\n        ><span>{{ user.version }}</span>\n      </div>\n      <div class=\"flex\">\n        <span class=\"label\">{{ $t('layout.about.serialNo') }}</span\n        ><span>{{ licenseInfo?.serialNo || '-' }}</span>\n      </div>\n      <div class=\"flex\">\n        <span class=\"label\">{{ $t('layout.about.remark') }}</span\n        ><span>{{ licenseInfo?.remark || '-' }}</span>\n      </div>\n      <div class=\"mt-16 flex align-center\" v-if=\"user.showXpack()\">\n        <el-upload\n          ref=\"uploadRef\"\n          action=\"#\"\n          :auto-upload=\"false\"\n          :show-file-list=\"false\"\n          :on-change=\"onChange\"\n          v-if=\"hasPermission([\n            RoleConst.ADMIN,\n            PermissionConst.ABOUT_UPDATE\n          ],'OR')\"\n        >\n          <el-button class=\"border-primary mr-16\"\n          >{{ $t('layout.about.update') }} License\n          </el-button\n          >\n        </el-upload>\n      </div>\n    </div>\n    <div class=\"border-t text-center mt-16 p-16 pb-0\">\n      <el-text type=\"info\">{{ $t('layout.copyright') }}</el-text>\n    </div>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport {ref, computed, watch} from 'vue'\nimport licenseApi from '@/api/system/license'\nimport {fromNowDate} from '@/utils/time'\nimport {Role} from '@/utils/permission/type'\nimport useStore from '@/stores'\nimport { t } from '@/locales'\nimport { hasPermission } from '@/utils/permission'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nconst {user, theme} = useStore()\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\n\nconst aboutDialogVisible = ref(false)\nconst loading = ref(false)\nconst licenseInfo = ref<any>(null)\nconst isUpdate = ref(false)\n\nwatch(aboutDialogVisible, (bool) => {\n  if (!bool) {\n    if (isUpdate.value) {\n      window.location.reload()\n    }\n    isUpdate.value = false\n  }\n})\nconst open = () => {\n  if (user.showXpack()) {\n    getLicenseInfo()\n  }\n\n  aboutDialogVisible.value = true\n}\n\n\nconst onChange = (file: any) => {\n  const fd = new FormData()\n  fd.append('license_file', file.raw)\n  licenseApi.putLicense(fd, loading).then((res: any) => {\n    getLicenseInfo()\n    isUpdate.value = true\n  })\n}\n\nconst editionText = computed(() => {\n  if (!user) return '-'\n  if (user.getEditionName() === 'PE') {\n    return t('layout.about.edition.professional')\n  } else if (user.getEditionName() === 'EE') {\n    return t('layout.about.edition.enterprise')\n  } else {\n    return t('layout.about.edition.community')\n  }\n})\nfunction getLicenseInfo() {\n  licenseApi.getLicense(loading).then((res: any) => {\n    licenseInfo.value = res.data?.license\n  })\n}\n\ndefineExpose({open})\n</script>\n<style lang=\"scss\" scope>\n.about-dialog {\n  padding: 0 0 24px 0;\n  width: 620px;\n  font-weight: 400;\n\n  .el-dialog__header {\n    background: var(--app-header-bg-color);\n    margin-right: 0;\n    height: 140px;\n    box-sizing: border-box;\n    border-radius: 4px 4px 0 0;\n\n    &.show-close {\n      padding-right: 0;\n    }\n  }\n\n  .el-dialog__title {\n    height: 140px;\n    box-sizing: border-box;\n  }\n\n  .about-ui {\n    margin: 0 auto;\n    font-weight: 400;\n    font-size: 14px;\n    margin-top: 24px;\n    line-height: 36px;\n    padding: 0 40px;\n\n    .label {\n      width: 150px;\n      text-align: left;\n      color: var(--app-text-color-secondary);\n    }\n  }\n\n  &.dialog-custom-header {\n    .el-dialog__header {\n      background: var(--el-color-primary-light-9) !important;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/layout-header/avatar/ResetPassword.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"resetPasswordDialog\"\n    :title=\"$t('views.login.resetPassword')\"\n    destroy-on-close\n    append-to-body\n    align-center\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form\n      class=\"reset-password-form\"\n      ref=\"resetPasswordFormRef1\"\n      :model=\"resetPasswordForm\"\n      :rules=\"rules1\"\n    >\n      <p class=\"mb-8 lighter\">{{ $t('views.login.newPassword') }}</p>\n      <el-form-item prop=\"password\" style=\"margin-bottom: 8px\">\n        <el-input\n          type=\"password\"\n          class=\"input-item\"\n          v-model=\"resetPasswordForm.password\"\n          :placeholder=\"$t('views.login.enterPassword')\"\n          show-password\n        >\n        </el-input>\n      </el-form-item>\n      <el-form-item prop=\"re_password\">\n        <el-input\n          type=\"password\"\n          class=\"input-item\"\n          v-model=\"resetPasswordForm.re_password\"\n          :placeholder=\"$t('views.login.enterPassword')\"\n          show-password\n        >\n        </el-input>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <div class=\"dialog-footer\">\n        <el-button @click=\"resetPasswordDialog = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"resetPassword\">\n          {{ $t('common.save') }}\n        </el-button>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport type { ResetCurrentUserPasswordRequest } from '@/api/type/user'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport UserApi from '@/api/user/user'\nimport useStore from '@/stores'\nimport { useRouter } from 'vue-router'\nimport { t } from '@/locales'\n\nconst props = defineProps<{\n  emitConfirm?: boolean // 在父级调接口\n}>()\n\nconst emit = defineEmits<{\n  (e: 'confirm', value: ResetCurrentUserPasswordRequest): void\n}>()\n\nconst router = useRouter()\nconst { login } = useStore()\n\nconst resetPasswordDialog = ref<boolean>(false)\n\nconst resetPasswordForm = ref<ResetCurrentUserPasswordRequest>({\n  code: '',\n  password: '',\n  re_password: '',\n})\n\nconst resetPasswordFormRef1 = ref<FormInstance>()\nconst resetPasswordFormRef2 = ref<FormInstance>()\n\nconst loading = ref<boolean>(false)\nconst isDisabled = ref<boolean>(false)\nconst time = ref<number>(60)\n\nconst rules1 = ref<FormRules<ResetCurrentUserPasswordRequest>>({\n  password: [\n    {\n      required: true,\n      message: t('views.login.enterPassword'),\n      trigger: 'blur',\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur',\n    },\n  ],\n  re_password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.re_password.requiredMessage'),\n      trigger: 'blur',\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur',\n    },\n    {\n      validator: (rule, value, callback) => {\n        if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {\n          callback(new Error(t('views.login.loginForm.re_password.validatorMessage')))\n        } else {\n          callback()\n        }\n      },\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst handleTimeChange = () => {\n  if (time.value <= 0) {\n    isDisabled.value = false\n    time.value = 60\n  } else {\n    setTimeout(() => {\n      time.value--\n      handleTimeChange()\n    }, 1000)\n  }\n}\n\nconst open = () => {\n  resetPasswordForm.value = {\n    //code: '',\n    password: '',\n    re_password: '',\n  }\n  resetPasswordDialog.value = true\n  resetPasswordFormRef1.value?.resetFields()\n  resetPasswordFormRef2.value?.resetFields()\n}\nconst resetPassword = () => {\n  resetPasswordFormRef1.value?.validate().then(() => {\n    if (props.emitConfirm) {\n      emit('confirm', resetPasswordForm.value)\n    } else {\n      return UserApi.resetCurrentPassword(resetPasswordForm.value).then(() => {\n        login.logout()\n        router.push({ name: 'login' })\n      })\n    }\n  })\n}\nconst close = () => {\n  resetPasswordDialog.value = false\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scope></style>\n"
  },
  {
    "path": "ui/src/layout/layout-header/avatar/index.vue",
    "content": "<template>\n  <el-dropdown trigger=\"click\" placement=\"bottom-end\">\n    <div class=\"flex-center cursor\">\n      <el-avatar :size=\"30\">\n        <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n      </el-avatar>\n      <!-- <span class=\"ml-8 color-text-primary ellipsis\"\n            :title=\"user.userInfo?.nick_name\">{{ user.userInfo?.nick_name }}</span>\n      <el-icon class=\"el-icon--right\">\n        <CaretBottom/>\n      </el-icon> -->\n    </div>\n\n    <template #dropdown>\n      <el-dropdown-menu class=\"avatar-dropdown\">\n        <div class=\"userInfo flex align-center\">\n          <div class=\"mr-12 flex align-center\">\n            <el-avatar :size=\"30\">\n              <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n            </el-avatar>\n          </div>\n          <div style=\"width: 90%\">\n            <p class=\"bold mb-4\" style=\"font-size: 14px\">\n              {{ i18n_name(user.userInfo?.nick_name as string) }}\n              <span class=\"color-secondary lighter\">({{ user.userInfo?.username }})</span>\n            </p>\n            <template v-if=\"user.userInfo?.role_name && user.userInfo.role_name.length > 0\">\n              <TagGroup\n                size=\"small\"\n                :tags=\"role_list\"\n                v-if=\"hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')\"\n              />\n            </template>\n          </div>\n        </div>\n        <el-dropdown-item\n          class=\"border-t\"\n          @click=\"router.push({ path: `/system/user` })\"\n          v-if=\"\n            hasPermission(\n              [\n                RoleConst.EXTENDS_ADMIN,\n                RoleConst.EXTENDS_WORKSPACE_MANAGE,\n                RoleConst.ADMIN,\n                RoleConst.WORKSPACE_MANAGE,\n              ],\n              'OR',\n            )\n          \"\n        >\n          <div class=\"flex-between w-full\">\n            {{ $t('views.system.title') }}\n          </div>\n        </el-dropdown-item>\n        <el-dropdown-item\n          @click=\"openResetPassword\"\n          v-if=\"\n            hasPermission(\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE, RoleConst.USER],\n                [PermissionConst.CHANGE_PASSWORD],\n                [],\n                'OR',\n              ),\n              'OR',\n            )\n          \"\n        >\n          {{ $t('views.login.resetPassword') }}\n        </el-dropdown-item>\n        <div>\n          <el-dropdown-item\n            class=\"p-8\"\n            @click=\"openAPIKeyDialog\"\n            v-if=\"\n              hasPermission(\n                new ComplexPermission(\n                  [RoleConst.ADMIN, RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE, RoleConst.USER],\n                  [PermissionConst.SYSTEM_API_KEY_EDIT],\n                  [EditionConst.IS_EE, EditionConst.IS_PE],\n                  'OR',\n                ),\n                'OR',\n              )\n            \"\n          >\n            {{ $t('layout.apiKey') }}\n          </el-dropdown-item>\n        </div>\n        <el-dropdown-item\n          style=\"padding: 0\"\n          @click.stop\n          v-if=\"\n            hasPermission(\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE, RoleConst.USER],\n                [PermissionConst.SWITCH_LANGUAGE],\n                [],\n                'OR',\n              ),\n              'OR',\n            )\n          \"\n        >\n          <el-dropdown class=\"w-full\" trigger=\"hover\" placement=\"left-start\">\n            <div class=\"flex-between w-full\" style=\"line-height: 22px; padding: 12px 11px\">\n              <span> {{ $t('layout.language') }}</span>\n              <el-icon>\n                <ArrowRight />\n              </el-icon>\n            </div>\n\n            <template #dropdown>\n              <el-dropdown-menu class=\"w-180\">\n                <el-dropdown-item\n                  v-for=\"(lang, index) in langList\"\n                  :key=\"index\"\n                  :value=\"lang.value\"\n                  @click=\"changeLang(lang.value)\"\n                  class=\"flex-between\"\n                >\n                  <span :class=\"lang.value === user.userInfo?.language ? 'primary' : ''\">{{\n                    lang.label\n                  }}</span>\n\n                  <el-icon\n                    :class=\"lang.value === user.userInfo?.language ? 'primary' : ''\"\n                    v-if=\"lang.value === user.userInfo?.language\"\n                  >\n                    <Check />\n                  </el-icon>\n                </el-dropdown-item>\n              </el-dropdown-menu>\n            </template>\n          </el-dropdown>\n        </el-dropdown-item>\n        <el-dropdown-item\n          @click=\"openAbout\"\n          v-if=\"\n            hasPermission(\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.USER, RoleConst.WORKSPACE_MANAGE],\n                [PermissionConst.ABOUT_READ],\n                [],\n                'OR',\n              ),\n              'OR',\n            )\n          \"\n        >\n          {{ $t('layout.about.title') }}\n        </el-dropdown-item>\n\n        <el-dropdown-item class=\"border-t\" @click=\"logout\">\n          {{ $t('layout.logout') }}\n        </el-dropdown-item>\n      </el-dropdown-menu>\n    </template>\n  </el-dropdown>\n  <APIKeyDialog :user-id=\"user.userInfo?.id\" ref=\"APIKeyDialogRef\" />\n  <ResetPassword ref=\"resetPasswordRef\"></ResetPassword>\n  <AboutDialog ref=\"AboutDialogRef\"></AboutDialog>\n\n  <!-- <UserPwdDialog ref=\"UserPwdDialogRef\" /> -->\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed } from 'vue'\nimport useStore from '@/stores'\nimport { useRouter } from 'vue-router'\nimport { t } from '@/locales'\nimport ResetPassword from './ResetPassword.vue'\nimport AboutDialog from './AboutDialog.vue'\n// import UserPwdDialog from '@/views/user-manage/component/UserPwdDialog.vue'\nimport APIKeyDialog from './APIKeyDialog.vue'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { langList } from '@/locales/index'\nimport { hasPermission } from '@/utils/permission'\nimport { PermissionConst, RoleConst, EditionConst } from '@/utils/permission/data'\nimport { i18n_name } from '@/utils/common'\n\nconst { user, login } = useStore()\nconst router = useRouter()\n\nconst AboutDialogRef = ref()\nconst APIKeyDialogRef = ref()\nconst resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()\n\n// const { changeLocale } = useLocale()\nconst changeLang = (lang: string) => {\n  user.postUserLanguage(lang)\n  // changeLocale(lang)\n}\nconst openAbout = () => {\n  AboutDialogRef.value?.open()\n}\n\nfunction openAPIKeyDialog() {\n  APIKeyDialogRef.value.open()\n}\n\nconst openResetPassword = () => {\n  resetPasswordRef.value?.open()\n}\nconst m: any = {\n  系统管理员: 'layout.about.inner_admin',\n  工作空间管理员: 'layout.about.inner_wsm',\n  普通用户: 'layout.about.inner_user',\n}\nconst role_list = computed(() => {\n  if (!user.userInfo) {\n    return []\n  }\n  return user.userInfo?.role_name?.map((name) => {\n    const inner = m[name]\n    if (inner) {\n      return t(inner)\n    }\n    return name\n  })\n})\nconst logout = () => {\n  login.logout().then(() => {\n    if (user?.userInfo?.source && ['CAS', 'OIDC', 'OAuth2'].includes(user.userInfo.source)) {\n      router.push({ name: 'login', query: { login_mode: 'manual' } })\n    } else {\n      router.push({ name: 'login' })\n    }\n  })\n}\n\nonMounted(() => {\n  if (user.userInfo?.is_edit_password) {\n    resetPasswordRef.value?.open()\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.avatar-dropdown {\n  min-width: 210px;\n  max-width: 400px;\n\n  .userInfo {\n    padding: 12px 11px;\n  }\n\n  :deep(.el-dropdown-menu__item) {\n    padding: 12px 11px;\n\n    &:hover {\n      background: rgba(var(--el-text-color-primary-rgb), 0.1);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/layout-header/top-about/index.vue",
    "content": "<template>\n  <div class=\"flex align-center top-about\">\n    <el-button\n      round\n      @click=\"toUrl('https://maxkb.cn/pricing.html')\"\n      class=\"pricing-button mr-8\"\n      v-hasPermission=\"EditionConst.IS_CE\"\n    >\n      <AppIcon iconName=\"app-pricing\" class=\"mr-8\"></AppIcon>\n      {{ $t('common.upgrade') }}\n    </el-button>\n    <el-tooltip\n      v-if=\"\n        hasPermission(\n          [\n            RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n            PermissionConst.TRIGGER_READ.getWorkspacePermissionWorkspaceManageRole,\n          ],\n          'OR',\n        ) && type === 'workspace'\n      \"\n      effect=\"dark\"\n      :content=\"$t('views.trigger.title')\"\n      placement=\"top\"\n    >\n      <el-button\n        text\n        @click=\"router.push({ name: 'trigger' })\"\n        :class=\"route.path.includes('trigger') ? 'active' : ''\"\n      >\n        <AppIcon\n          iconName=\"app-trigger\"\n          :class=\"route.path.includes('trigger') ? 'color-primary' : 'color-secondary'\"\n          style=\"font-size: 20px\"\n        ></AppIcon>\n      </el-button>\n    </el-tooltip>\n    <el-tooltip\n      effect=\"dark\"\n      :content=\"$t('layout.github')\"\n      placement=\"top\"\n      v-if=\"theme.themeInfo?.showProject\"\n    >\n      <el-button text @click=\"toUrl(theme.themeInfo?.projectUrl)\">\n        <AppIcon\n          iconName=\"app-github\"\n          class=\"cursor color-secondary\"\n          style=\"font-size: 20px\"\n        ></AppIcon>\n      </el-button>\n    </el-tooltip>\n    <el-tooltip\n      effect=\"dark\"\n      :content=\"$t('layout.wiki')\"\n      placement=\"top\"\n      v-if=\"theme.themeInfo?.showUserManual\"\n    >\n      <el-button text @click=\"toUrl(theme.themeInfo?.userManualUrl)\">\n        <AppIcon\n          iconName=\"app-user-manual\"\n          class=\"cursor color-secondary\"\n          style=\"font-size: 20px\"\n        ></AppIcon>\n      </el-button>\n    </el-tooltip>\n    <el-tooltip\n      effect=\"dark\"\n      :content=\"$t('layout.forum')\"\n      placement=\"top\"\n      v-if=\"theme.themeInfo?.showForum\"\n    >\n      <el-button text @click=\"toUrl(theme.themeInfo?.forumUrl)\">\n        <AppIcon\n          iconName=\"app-help\"\n          class=\"cursor color-secondary\"\n          style=\"font-size: 20px\"\n        ></AppIcon>\n      </el-button>\n    </el-tooltip>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport useStore from '@/stores'\nimport { hasPermission } from '@/utils/permission'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { useRoute, useRouter } from 'vue-router'\nconst route = useRoute()\nconst router = useRouter()\nconst { theme, user } = useStore()\n\nwithDefaults(defineProps<{ type?: 'workspace' | 'system' }>(), {\n  type: 'workspace',\n})\nfunction toUrl(url: string) {\n  window.open(url, '_blank')\n}\n</script>\n<style scoped lang=\"scss\">\n.top-about {\n  .el-button.is-text {\n    max-height: 32px;\n    padding: 6px !important;\n  }\n  .el-button + .el-button {\n    margin-left: 4px !important;\n  }\n  .active {\n    background: #ffffff;\n    &:hover {\n      background: #ffffff;\n    }\n  }\n}\n.pricing-button {\n  background: linear-gradient(90deg, #3370ff 0%, #7f3bf5 100%);\n  color: #ffffff;\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/layout-header/top-menu/MenuItem.vue",
    "content": "<template>\n  <div\n    class=\"menu-item-container h-full border-r-6\"\n    :class=\"isActive ? 'active' : ''\"\n    @click=\"router.push({ name: menu.name })\"\n  >\n    <div class=\"title flex align-center\">\n      <AppIcon\n        :iconName=\"isActive ? menu.meta?.iconActive || menu.meta?.icon : menu?.meta?.icon\"\n        style=\"font-size: 16px\"\n        class=\"mr-4\"\n      />\n      <span> {{ $t(menu.meta?.title as string) }}</span>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { useRouter, useRoute, type RouteRecordRaw } from 'vue-router'\nimport { computed } from 'vue'\nconst router = useRouter()\nconst route = useRoute()\n\nconst props = defineProps<{\n  menu: RouteRecordRaw\n}>()\n\nconst isActive = computed(() => {\n  const { name, path, meta } = route\n  return (name == props.menu.name && path == props.menu.path) || meta?.activeMenu == props.menu.path\n})\n</script>\n<style lang=\"scss\" scoped>\n.menu-item-container {\n  margin-right: 8px;\n  cursor: pointer;\n  font-size: 14px;\n  position: relative;\n  padding: 6px 12px;\n\n  .icon {\n    font-size: 15px;\n    margin-right: 5px;\n    margin-top: 2px;\n  }\n\n  &:hover {\n    background-color: rgba(var(--el-text-color-primary-rgb), 0.1);\n  }\n  &.active {\n    background-color: #ffffff;\n    box-shadow: 0px 2px 4px 0px rgba(var(--el-text-color-primary-rgb), 0.12);\n\n    .title {\n      color: var(--el-color-primary) !important;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/layout-header/top-menu/index.vue",
    "content": "<template>\n  <div class=\"top-menu-container flex align-center h-full\">\n    <MenuItem\n      :menu=\"menu\"\n      v-hasPermission=\"menu.meta?.permission\"\n      v-for=\"(menu, index) in topMenuList\"\n      :key=\"index\"\n    >\n    </MenuItem>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { getChildRouteListByPathAndName } from '@/router/index'\nimport { hasPermission, set_next_route } from '@/utils/permission/index'\nimport MenuItem from './MenuItem.vue'\n\nconst topMenuList = computed(() => {\n  const menu = getChildRouteListByPathAndName('/', 'home').filter(\n    (item) =>\n      item.meta?.menu &&\n      (item.meta.permission ? hasPermission(item.meta.permission as any, 'OR') : true),\n  )\n  menu.sort(\n    (a, b) =>\n      (a.meta ? (a.meta.order ? (a.meta.order as number) : 1) : 1) -\n      (b.meta ? (b.meta.order ? (b.meta.order as number) : 1) : 1),\n  )\n  return menu\n})\n</script>\n<style lang=\"scss\" scope></style>\n"
  },
  {
    "path": "ui/src/layout/layout-template/MainLayout.vue",
    "content": "<template>\n  <div class=\"app-layout\">\n    <div class=\"app-header\" :class=\"!isDefaultTheme ? 'custom-header' : ''\">\n      <el-alert\n        v-if=\"user.isExpire()\"\n        :title=\"$t('layout.isExpire')\"\n        type=\"warning\"\n        class=\"border-b\"\n        show-icon\n        :closable=\"false\"\n      />\n      <SystemHeader v-if=\"isShared\"></SystemHeader>\n      <UserHeader v-else />\n    </div>\n    <div class=\"app-main\" :class=\"user.isExpire() ? 'isExpire' : ''\">\n      <layout-container>\n        <template #left>\n          <Sidebar />\n        </template>\n        <AppMain />\n      </layout-container>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport UserHeader from '@/layout/layout-header/UserHeader.vue'\nimport SystemHeader from '@/layout/layout-header/SystemHeader.vue'\nimport Sidebar from '@/layout/components/sidebar/index.vue'\nimport AppMain from '@/layout/app-main/index.vue'\nimport useStore from '@/stores'\nimport { useRoute } from 'vue-router'\nconst route = useRoute()\nconst {\n  params: { folderId }, // id为knowledgeID\n  query: { from },\n} = route as any\nconst isShared = computed(() => {\n  return (\n    folderId === 'shared' ||\n    from === 'systemShare' ||\n    from === 'systemManage' ||\n    route.path.includes('resource-management')\n  )\n})\nconst { theme, user } = useStore()\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\n</script>\n<style lang=\"scss\" scoped>\n@use './index.scss';\n</style>\n"
  },
  {
    "path": "ui/src/layout/layout-template/SimpleLayout.vue",
    "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport UserHeader from '@/layout/layout-header/UserHeader.vue'\nimport SystemHeader from '@/layout/layout-header/SystemHeader.vue'\nimport AppMain from '@/layout/app-main/index.vue'\nimport useStore from '@/stores'\nimport { useRoute } from 'vue-router'\nconst route = useRoute()\nconst { theme, user } = useStore()\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\nconst {\n  params: { folderId }, // id为knowledgeID\n  query: { from },\n} = route as any\nconst isShared = computed(() => {\n  return (\n    (folderId === 'shared' ||\n      from === 'systemShare' ||\n      from === 'systemManage' ||\n      route.path.includes('resource-management')) &&\n    route.fullPath != '/application'\n  )\n})\n</script>\n\n<template>\n  <div class=\"app-layout\">\n    <div class=\"app-header\" :class=\"!isDefaultTheme ? 'custom-header' : ''\">\n      <el-alert\n        v-if=\"user.isExpire()\"\n        :title=\"$t('layout.isExpire')\"\n        type=\"warning\"\n        class=\"border-b\"\n        show-icon\n        :closable=\"false\"\n      />\n\n      <SystemHeader v-if=\"isShared\"></SystemHeader>\n      <UserHeader v-else />\n    </div>\n    <div class=\"app-main\" :class=\"user.isExpire() ? 'isExpire' : ''\">\n      <AppMain />\n    </div>\n  </div>\n</template>\n<style lang=\"scss\">\n@use './index.scss';\n</style>\n"
  },
  {
    "path": "ui/src/layout/layout-template/SystemMainLayout.vue",
    "content": "<template>\n  <div class=\"app-layout\">\n    <div class=\"app-header\" :class=\"!isDefaultTheme ? 'custom-header' : ''\">\n      <el-alert\n        v-if=\"user.isExpire()\"\n        :title=\"$t('layout.isExpire')\"\n        type=\"warning\"\n        class=\"border-b\"\n        show-icon\n        :closable=\"false\"\n      />\n      <SystemHeader />\n    </div>\n    <div class=\"app-main\" :class=\"user.isExpire() ? 'isExpire' : ''\">\n      <layout-container>\n        <template #left>\n          <Sidebar />\n        </template>\n        <AppMain />\n      </layout-container>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport SystemHeader from '@/layout/layout-header/SystemHeader.vue'\nimport Sidebar from '@/layout/components/sidebar/index.vue'\nimport AppMain from '@/layout/app-main/index.vue'\nimport useStore from '@/stores'\nconst { theme, user } = useStore()\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\n</script>\n<style lang=\"scss\" scoped>\n@use './index.scss';\n</style>\n"
  },
  {
    "path": "ui/src/layout/layout-template/index.scss",
    "content": ".app-layout {\n  background-color: var(--app-layout-bg-color);\n  height: 100%;\n}\n\n.app-header {\n  background: var(--app-header-bg-color);\n  position: fixed;\n  width: 100%;\n  left: 0;\n  top: 0;\n  z-index: 100;\n}\n\n.app-main {\n  position: relative;\n  height: 100%;\n  padding: var(--app-header-height) 0 0 !important;\n  box-sizing: border-box;\n  overflow: auto;\n  &.isExpire {\n    padding-top: calc(var(--app-header-height) + 40px) !important;\n  }\n}\n"
  },
  {
    "path": "ui/src/layout/login-layout/LoginContainer.vue",
    "content": "<template>\n  <div class=\"login-form-container p-24\">\n    <div class=\"login-title\">\n      <div class=\"logo text-center\">\n        <slot name=\"logo\">\n          <LogoFull height=\"45px\" />\n        </slot>\n      </div>\n      <div class=\"sub-title text-center\" v-if=\"subTitle\">\n        <el-text type=\"info\">{{ subTitle }}</el-text>\n      </div>\n    </div>\n    <el-card class=\"login-card\">\n      <slot></slot>\n    </el-card>\n  </div>\n</template>\n<script setup lang=\"ts\">\ndefineProps({\n  title: String,\n  subTitle: String,\n})\n</script>\n<style lang=\"scss\" scoped>\n.login-form-container {\n  width: 480px;\n\n\n  .login-title {\n    margin-bottom: 32px;\n    .sub-title {\n      font-size: 16px;\n    }\n  }\n  .login-card {\n    padding: 18px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/login-layout/LoginLayout.vue",
    "content": "<template>\n  <div class=\"login-warp flex-center\">\n    <div class=\"login-container w-full h-full\">\n      <el-row class=\"container w-full h-full\">\n        <el-col :xs=\"0\" :sm=\"0\" :md=\"10\" :lg=\"10\" :xl=\"10\" class=\"left-container\">\n          <div class=\"login-image\" :style=\"{ backgroundImage: `url(${loginImage})` }\"></div>\n        </el-col>\n        <el-col :xs=\"24\" :sm=\"24\" :md=\"14\" :lg=\"14\" :xl=\"14\" class=\"right-container flex-center\">\n          <el-dropdown trigger=\"click\" type=\"primary\" class=\"lang\" v-if=\"lang\">\n            <template #dropdown>\n              <el-dropdown-menu class=\"w-180\">\n                <el-dropdown-item\n                  v-for=\"(lang, index) in langList\"\n                  :key=\"index\"\n                  :value=\"lang.value\"\n                  @click=\"changeLang(lang.value)\"\n                  class=\"flex-between\"\n                >\n                  <span :class=\"lang.value === user.getLanguage() ? 'primary' : ''\">{{\n                    lang.label\n                  }}</span>\n\n                  <el-icon\n                    :class=\"lang.value === user.getLanguage() ? 'primary' : ''\"\n                    v-if=\"lang.value === user.getLanguage()\"\n                  >\n                    <Check />\n                  </el-icon>\n                </el-dropdown-item>\n              </el-dropdown-menu>\n            </template>\n            <el-button>\n              {{ currentLanguage }}<el-icon class=\"el-icon--right\"><arrow-down /></el-icon>\n            </el-button>\n          </el-dropdown>\n          <slot></slot>\n        </el-col>\n      </el-row>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { getThemeImg } from '@/utils/theme'\nimport useStore from '@/stores'\nimport { useLocalStorage } from '@vueuse/core'\nimport { langList, localeConfigKey, getBrowserLang } from '@/locales/index'\ndefineProps({\n  lang: {\n    type: Boolean,\n    default: true,\n  },\n})\nconst { user, theme } = useStore()\n\nconst changeLang = (lang: string) => {\n  useLocalStorage(localeConfigKey, getBrowserLang()).value = lang\n  window.location.reload()\n}\n\nconst currentLanguage = computed(() => {\n  return langList.value?.filter((v: any) => v.value === user.getLanguage())?.[0]?.label\n})\n\nconst fileURL = computed(() => {\n  if (theme.themeInfo?.loginImage) {\n    if (typeof theme.themeInfo?.loginImage === 'string') {\n      return theme.themeInfo?.loginImage\n    } else {\n      return URL.createObjectURL(theme.themeInfo?.loginImage)\n    }\n  } else {\n    return ''\n  }\n})\n\nconst loginImage = computed(() => {\n  if (theme.themeInfo?.loginImage) {\n    return `${fileURL.value}`\n  } else {\n    const imgName = getThemeImg(theme.themeInfo?.theme)\n    const imgPath = `${window.MaxKB.prefix}/theme/${imgName}.jpg`\n    const imageUrl = new URL(imgPath, import.meta.url).href\n    return imageUrl\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.login-warp {\n  height: 100vh;\n\n  .login-image {\n    background-repeat: no-repeat;\n    background-position: center;\n    background-size: cover;\n    width: 100%;\n    height: 100%;\n  }\n  .right-container {\n    position: relative;\n    .lang {\n      position: absolute;\n      right: 20px;\n      top: 20px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/layout/login-layout/UserLoginLayout.vue",
    "content": "<template>\n  <div class=\"login-warp flex-center\">\n    <div class=\"login-container w-full h-full\">\n      <div class=\"flex-center w-full h-full\">\n        <slot></slot>\n      </div>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\"></script>\n<style lang=\"scss\" scoped>\n.login-warp {\n  height: 100vh;\n  background: url('@/assets/chat/user-login-bg.png') var(--app-layout-bg-color) no-repeat center;\n}\n</style>\n"
  },
  {
    "path": "ui/src/locales/index.ts",
    "content": "import {useLocalStorage, usePreferredLanguages} from '@vueuse/core'\nimport {computed} from 'vue'\nimport {createI18n} from 'vue-i18n'\n\n// 导入语言文件\nconst langModules = import.meta.glob('./lang/*/index.ts', {eager: true}) as Record<\n  string,\n  () => Promise<{ default: object }>\n>\n\n// 定义 Recordable 类型\ntype Recordable<T = any> = Record<string, T>\n\nconst langModuleMap = new Map<string, object>()\n\nexport const langCode: Array<string> = []\n\nexport const localeConfigKey = 'MaxKB-locale'\n\n// 获取浏览器默认语言环境\nconst languages = usePreferredLanguages()\n\nexport function getBrowserLang() {\n  const browserLang = navigator.language ? navigator.language : languages.value[0]\n  let defaultBrowserLang = ''\n  if (browserLang === 'zh-HK' || browserLang === 'zh-TW') {\n    defaultBrowserLang = 'zh-Hant'\n  } else if (browserLang === 'zh-CN') {\n    defaultBrowserLang = 'zh-CN'\n  } else {\n    defaultBrowserLang = 'en-US'\n  }\n  return defaultBrowserLang\n}\n\n// 生成语言模块列表\nconst generateLangModuleMap = () => {\n  const fullPaths = Object.keys(langModules)\n  fullPaths.forEach((fullPath) => {\n    const k = fullPath.replace('./lang', '')\n    const startIndex = 1\n    const lastIndex = k.lastIndexOf('/')\n    const code = k.substring(startIndex, lastIndex)\n    langCode.push(code)\n    langModuleMap.set(code, langModules[fullPath])\n  })\n}\n\n// 导出 Message\nconst importMessages = computed(() => {\n  generateLangModuleMap()\n\n  const message: Recordable = {}\n  langModuleMap.forEach((value: any, key) => {\n    message[key] = value.default\n  })\n  return message\n})\n\nexport const i18n = createI18n({\n  legacy: false,\n  locale: useLocalStorage(localeConfigKey, getBrowserLang()).value || getBrowserLang(),\n  fallbackLocale: getBrowserLang(),\n  messages: importMessages.value,\n  globalInjection: true\n})\n\nexport const langList = computed(() => {\n  if (langModuleMap.size === 0) generateLangModuleMap()\n\n  const list: any = []\n  langModuleMap.forEach((value: any, key) => {\n    list.push({\n      label: value.default.lang,\n      value: key\n    })\n  })\n\n  return list\n})\n\nexport const {t} = i18n.global\n\nexport default i18n\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/ai-chat.ts",
    "content": "export default {\n  mine: 'Mine',\n  logoutContent: 'Logging out will not lose any data. You can still log in to this account.',\n  confirmModification: 'Confirm modification',\n  noHistory: 'No Chat History',\n  createChat: 'New Chat',\n  clearChat: 'Clear Chat',\n  history: 'Chat History',\n  only20history: 'Showing only the last 20 chats',\n  question_count: 'Questions',\n  exportRecords: 'Export Chat History',\n  exportPDF: 'Export PDF',\n  exportImg: 'Exporting images',\n  preview: 'Preview',\n  chatId: 'Chat ID',\n  chatUserId: 'Chat User ID',\n  chatUserType: 'Chat User Type',\n  chatUserGroup: 'Chat User Group',\n  userInput: 'User Input',\n  quote: 'Quote',\n  download: 'Click to Download',\n  noDocument: 'Original Document Not Found',\n  noPermissionDownload: 'No permission to download',\n  passwordValidator: {\n    title: 'Enter Password to Access',\n    errorMessage1: 'Password cannot be empty',\n    errorMessage2: 'Incorrect password',\n  },\n  operation: {\n    play: 'Play',\n    pause: 'Pause',\n    regeneration: 'Regenerate Response',\n    like: 'Like',\n    cancelLike: 'Unlike',\n    oppose: 'Dislike',\n    cancelOppose: 'Undo Dislike',\n    continue: 'Continue',\n    stopChat: 'Stop Response',\n    startChat: 'Start Response',\n  },\n  vote: {\n    likeTitle: 'What do you think makes you satisfied?',\n    opposeTitle: 'Please tell us the reason for your dissatisfaction.',\n    accurate: 'Content is accurate',\n    inaccurate: 'Answer is inaccurate',\n    complete: 'Content is complete',\n    irrelevantAnswer: 'Answer is irrelevant',\n    other: 'Other',\n    placeholder: 'Tell us more about your relevant experiences',\n  },\n  tip: {\n    error500Message: 'Sorry, the service is currently under maintenance. Please try again later!',\n    errorIdentifyMessage: 'Unable to verify user identity',\n    errorLimitMessage:\n      'Sorry, you have reached the maximum number of questions. Please try again tomorrow!',\n    answerMessage:\n      'Sorry, no relevant content found. Please rephrase your question or provide more details.',\n    stopAnswer: 'Response Stopped',\n    answerLoading: 'Generating Response',\n    recorderTip: `<p>This feature requires microphone access. Browsers block recording on insecure pages. Solutions:<br/>\n1. Enable HTTPS;<br/>\n2. If HTTPS is not available, adjust browser security settings. For Chrome:<br/>\n(1) Enter chrome://flags/#unsafely-treat-insecure-origin-as-secure in the address bar;<br/>\n(2) Add your HTTP site, e.g., http://127.0.0.1:8080.</p>`,\n    recorderError: 'Recording Failed',\n    confirm: 'Got it',\n    requiredMessage: 'Please fill in all required fields',\n    inputParamMessage1: 'Please specify a parameter in the URL',\n    inputParamMessage2: 'value',\n    prologueMessage: 'Sorry, the service is currently under maintenance. Please try again later!',\n  },\n  inputPlaceholder: {\n    speaking: 'Speaking',\n    recorderLoading: 'Transcribing',\n    default: 'Type your question',\n    holdToTalk: 'Hold to Talk',\n    chatting: 'Chatting',\n    touchChatMessage: 'Release to send, swipe up to cancel',\n    cancelTouchChat: 'Release to cancel sending',\n  },\n  uploadFile: {\n    label: 'Upload File',\n    most: 'Maximum',\n    limit: 'files allowed, each up to',\n    fileType: 'File Type',\n    tipMessage: 'Please select allowed file types in the upload settings',\n    limitMessage1: 'You can upload up to',\n    limitMessage2: 'files',\n    sizeLimit: 'Each file must not exceed',\n    sizeLimit2: 'Empty files are not supported for upload',\n    imageMessage: 'Please process the image content',\n    documentMessage: 'Please understand the content of the document',\n    audioMessage: 'Please understand the audio content',\n    videoMessage: 'Please understand the video content',\n    otherMessage: 'Please understand the file content',\n    errorMessage: 'Upload Failed',\n    fileMessage: 'Please process the file content',\n    fileRepeat: 'File already exists',\n    invalidUrl: 'Invalid URL',\n    localUpload: 'Local Upload',\n    urlPlaceholder: 'Please enter URL addresses, one per line',\n    urlTitle: 'URL Address',\n    urlErrorMessage: 'File type does not meet requirements',\n  },\n  executionDetails: {\n    title: 'Execution Details',\n    createTime: 'Execution Time',\n    paramOutputTooltip: 'Each document supports previewing up to 500 characters',\n    audioFile: 'Audio File',\n    searchContent: 'Search Query',\n    searchResult: 'Search Results',\n    conditionResult: 'Condition Evaluation',\n    currentChat: 'Current Chat',\n    answer: 'AI Response',\n    replyContent: 'Reply Content',\n    textContent: 'Text Content',\n    input: 'Input',\n    output: 'Output',\n    rerankerContent: 'Re-ranked Content',\n    rerankerResult: 'Re-ranking Results',\n    paragraph: 'Segment',\n    noSubmit: 'No submission from user',\n    errMessage: 'Error Log',\n    knowedMessage: 'Known Information',\n    documentSplitTip: 'Each document can preview only the first five segments',\n    paragraphRules: 'Segmentation Rules',\n    writeContent: 'Content Written',\n    cancel: 'Cancel Execution',\n    errLog: 'Error Log',\n    cancelExecutionTip: 'Are you sure you want to cancel the selected task? ',\n  },\n  KnowledgeSource: {\n    title: 'Knowledge Source',\n    referenceParagraph: 'Cited Segment',\n    consume: 'Tokens',\n    consumeTime: 'Runtime',\n    noSource: 'No source found',\n  },\n  paragraphSource: {\n    title: 'Knowledge Quote',\n    question: 'User Question',\n    optimizationQuestion: 'Optimized Question',\n    questionPadded: 'Padded Question',\n  },\n  editTitle: 'Edit Title',\n  share: 'Share',\n  copyLinkText: 'Copy Link',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/common.ts",
    "content": "export default {\n  syncSuccess: 'Successful',\n  create: 'Create',\n  createSuccess: 'Successful',\n  copy: 'Copy',\n  copySuccess: 'Successful',\n  copyError: 'Copy Failed',\n  save: 'Save',\n  saveSuccess: 'Successful',\n  delete: 'Delete',\n  deleteSuccess: 'Successful',\n  setting: 'Settings',\n  settingSuccess: 'Successful',\n  submit: 'Submit',\n  submitSuccess: 'Successful',\n  edit: 'Edit',\n  editSuccess: 'Successful',\n  modify: 'Modify',\n  modifySuccess: 'Successful',\n  cancel: 'Cancel',\n  confirm: 'OK',\n  close: 'Close',\n  tip: 'Tips',\n  add: 'Add',\n  refresh: 'Refresh',\n  search: 'Search',\n  clear: 'Clear',\n  upgrade: 'Upgrade',\n  createDate: 'Create Date',\n  createTime: 'Create Time',\n  operation: 'Action',\n  character: 'characters',\n  export: 'Export',\n  exportSuccess: 'Successful',\n  unavailable: '(Unavailable)',\n  public: 'Public',\n  private: 'Private',\n  paramSetting: 'Parameter Settings',\n  name: 'Name',\n  creator: 'Creator',\n  createdIn:'created in',\n  author: 'Author',\n  debug: 'Debug',\n  required: 'Required',\n  noData: 'No data',\n  result: 'Result',\n  remove: 'Remove',\n  classify: 'Classify',\n  reason: 'Reason',\n  removeSuccess: 'Successful',\n  publish: 'Publish',\n  noTargetPermission: 'No target resource permission',\n  searchBar: {\n    placeholder: 'Search by name',\n  },\n  fileUpload: {\n    document: 'Documents',\n    image: 'Image',\n    audio: 'Audio',\n    video: 'Video',\n    other: 'Other',\n    addExtensions: 'Add Extensions',\n    existingExtensionsTip: 'The following extensions already exist',\n    localUpload: 'Local Files',\n    urlUpload: 'URL',\n    uploadMethodTip: 'Please select upload method',\n  },\n  status: {\n    label: 'Status',\n    enable: 'Enable',\n    disable: 'Disable',\n    enabled: 'Enabled',\n    disabled: 'Disabled',\n    enableSuccess: 'Successful',\n    disableSuccess: 'Successful',\n    published: 'Published',\n    unpublished: 'Unpublished',\n    success: 'Successful',\n    fail: 'Failed',\n    all: 'All',\n    STARTED: 'Padding',\n    REVOKED: 'Cancelled',\n    REVOKE: 'Cancelling',\n  },\n  param: {\n    outputParam: 'Output Parameters',\n    inputParam: 'Input Parameters',\n    initParam: 'Startup Parameters',\n    editParam: 'Edit Parameter',\n    addParam: 'Add Parameter',\n    exception: 'Exception Capture',\n  },\n  aggregationStrategy: 'Aggregation Strategy',\n  inputPlaceholder: 'Please input',\n  inputContent: 'Input content',\n  selectPlaceholder: 'Please select',\n  title: 'Title',\n  content: 'Content',\n  desc: 'Description',\n  descPlaceholder: 'Please enter description',\n  rename: 'Rename',\n  renameSuccess: 'Successful',\n  EditAvatarDialog: {\n    customizeUpload: 'Custom Upload',\n    upload: 'Upload',\n    default: 'Default Logo',\n    sizeTip: 'Recommended size: 32×32 pixels. Supports JPG, PNG, and GIF formats. Max size: 10 MB',\n    fileSizeExceeded: 'File size exceeds 10 MB',\n    uploadImagePrompt: 'Please upload an image',\n  },\n  info: 'Base Information',\n  otherSetting: 'Other Settings',\n  username: 'username',\n  importCreate: 'Import Create',\n  detail: 'Detail',\n  selected: 'Selected',\n  notFound: {\n    title: '404',\n    NoService: 'Currently unable to access services',\n    NoPermission:\n      'The current user does not have permission to access, please contact the administrator',\n    operate: 'Back to Home',\n  },\n  custom: 'Custom',\n  moveTo: 'Move To',\n  deleteConfirm: 'Confirm delete',\n  expand: 'Expand',\n  collapse: 'Collapse',\n  copyTitle: 'Copy',\n  professional: 'Purchase the Professional Edition',\n  sync: 'Sync',\n  prompt: {\n    label: 'Prompt',\n    placeholder: 'Please enter prompt',\n  },\n  variable: 'Variable',\n  allCheck: 'Select All',\n  type: 'Type',\n  pages: {\n    prev: 'Previous',\n    next: 'Next',\n  },\n  steps: {\n    prev: 'Previous',\n    next: 'Next',\n  },\n  use: 'Use',\n  ExecutionRecord: {\n    title: 'Execution Record',\n    subTitle: 'View Execution Record',\n  },\n  sourceType: 'Source type',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/components.ts",
    "content": "export default {\n  quickCreatePlaceholder: 'Quickly create blank document',\n  quickCreateName: 'document name',\n  noData: 'No Data',\n  loading: 'Loading',\n  noMore: 'No more! ',\n  noDesc: ' No description',\n  selectParagraph: {\n    title: 'Select Segments',\n    error: 'Process only the failed segments',\n    all: 'All Segments',\n  },\n  folder: {\n    addFolder: 'Add Folder',\n    addChildFolder: 'Add Child Folder',\n    editFolder: 'Edit Folder',\n    folderNamePlaceholder: 'Please enter a name',\n    requiredMessage: 'Please select a folder',\n    deleteConfirmMessage: 'Folders with resources will be deleted, please be cautious.',\n    ascTime: 'Creation Time: Ascending',\n    descTime: 'Creation Time: Descending',\n    ascName: 'Name: Ascending',\n    descName: 'Name: Descending',\n    custom: 'Drag to Reorder',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/dynamics-form.ts",
    "content": "export default {\n  input_type_list: {\n    TextInput: 'Input',\n    PasswordInput: 'Password',\n    Slider: 'Slider',\n    SwitchInput: 'Switch',\n    SingleSelect: 'Single Select',\n    MultiSelect: 'Multi Select',\n    DatePicker: 'Date Picker',\n    JsonInput: 'JSON',\n    RadioCard: 'Radio Card',\n    RadioRow: 'Radio Row',\n    UploadInput: 'File upload',\n    TextareaInput: 'Multiline Input',\n    MultiRow: 'Multi Row',\n    Model: 'Model',\n  },\n  default: {\n    label: 'Default',\n    placeholder: 'Please enter a default',\n    requiredMessage: ' is a required property',\n    show: 'Show Default',\n  },\n  tip: {\n    requiredMessage: 'cannot be empty',\n    jsonMessage: 'Incorrect JSON format',\n  },\n  paramForm: {\n    field: {\n      label: 'Parameter',\n      placeholder: 'Please enter a parameter',\n      requiredMessage: 'Parameter is a required property',\n      requiredMessage2: 'Only letters, numbers, and underscores are allowed',\n    },\n    name: {\n      label: 'Name',\n      placeholder: 'Please enter a name',\n      requiredMessage: 'Name is a required property',\n    },\n    tooltip: {\n      label: 'Tooltip',\n      placeholder: 'Please enter a tooltip',\n    },\n    required: {\n      label: 'Required',\n      requiredMessage: 'Required is a required property',\n    },\n    input_type: {\n      label: 'Type',\n      placeholder: 'Please select a type',\n      requiredMessage: 'Type is a required property',\n    },\n  },\n  DatePicker: {\n    placeholder: 'Select Date',\n    year: 'Year',\n    month: 'Month',\n    date: 'Date',\n    datetime: 'Date Time',\n    dataType: {\n      label: 'Date Type',\n      placeholder: 'Please select a date type',\n    },\n    format: {\n      label: 'Format',\n      placeholder: 'Please select a format',\n    },\n  },\n  Select: {\n    label: 'Option Value',\n    placeholder: 'Please enter an option value',\n  },\n  tag: {\n    label: 'Tag',\n    placeholder: 'Please enter an option label',\n  },\n  Slider: {\n    showInput: {\n      label: 'Show Input Box',\n    },\n    valueRange: {\n      label: 'Value Range',\n      minRequired: 'Minimum value is required',\n      maxRequired: 'Maximum value is required',\n    },\n    step: {\n      label: 'Step Value',\n      requiredMessage1: 'Step value is required',\n      requiredMessage2: 'Step value cannot be 0',\n    },\n  },\n  TextInput: {\n    length: {\n      label: 'Text Length',\n      minRequired: 'Minimum length is required',\n      maxRequired: 'Maximum length is required',\n      requiredMessage1: 'Length must be between',\n      requiredMessage2: 'and',\n      requiredMessage3: 'characters',\n      requiredMessage4: 'Text length is a required parameter',\n    },\n  },\n  UploadInput: {\n    limit: {\n      label: 'Maximum number of files per upload',\n      required: 'Maximum number of files is required',\n    },\n    max_file_size: {\n      label: 'Maximum file size (MB)',\n      required: 'Maximum file size is required',\n    },\n    accept: {\n      label: 'File type',\n      required: 'File type is required',\n    },\n  },\n  AssignmentMethod: {\n    label: 'Assignment Method',\n    ref_variables: {\n      label: 'Reference Variables',\n      popover: 'Variable values must comply with',\n      json_format: 'JSON format',\n      popover_label: 'Label',\n      popover_value: 'Value',\n      popover_default: 'Is Default',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/index.ts",
    "content": "import en from 'element-plus/es/locale/lang/en'\nimport components from './components'\nimport layout from './layout'\nimport views from './views'\nimport theme from './theme'\nimport common from './common'\nimport dynamicsForm from './dynamics-form'\nimport chat from './ai-chat'\nimport workflow from './workflow'\nexport default {\n  lang: 'English',\n  layout,\n  views,\n  theme,\n  components,\n  en,\n  common,\n  dynamicsForm,\n  chat,\n  workflow\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/layout.ts",
    "content": "export default {\n  github: 'Project Address',\n  wiki: 'User Manual',\n  forum: 'Forum For Help',\n  logout: 'Log Out',\n  apiKey: 'API Key',\n  apiServiceAddress: 'API Service Address',\n  language: 'Language',\n  isExpire: 'License not uploaded or expired',\n  crossSettings: 'Cross-Origin Settings',\n  about: {\n    title: 'About',\n    expiredTime: 'Expiration Date',\n    edition: {\n      label: 'Edition',\n      community: 'Community Edition',\n      professional: 'Professional Edition',\n      enterprise: 'Enterprise Edition',\n    },\n    version: 'Version',\n    serialNo: 'Serial No.',\n    remark: 'Remarks',\n    update: 'Update',\n    authorize: 'Authorized',\n    inner_admin: 'System Admin',\n    inner_wsm: 'Workspace Manager',\n    inner_user: 'Regular User',\n    root: 'Root Directory',\n    default_workspace: 'Default Workspace',\n    default_user_group: 'Default User Group',\n  },\n  time: {\n    daysLater: 'days later expire',\n    hoursLater: 'hours later expire',\n    minutesLater: 'minutes later expire',\n    expired: 'expired',\n    expiringSoon: 'expiring soon',\n    neverExpires: 'Never expires',\n    daysValid: 'Days valid',\n  },\n  copyright: 'Copyright © 2014-2026 FIT2CLOUD, All rights reserved.',\n  userManualUrl: 'http://docs.maxkb.hk/',\n  forumUrl: 'https://github.com/1Panel-dev/MaxKB/discussions',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/theme.ts",
    "content": "export default {\n  title: 'Appearance Settings',\n  defaultSlogan: 'An open-source platform for building enterprise-grade agents',\n  platformDisplayTheme: 'Platform Display Theme',\n  customTheme: 'Custom Theme',\n  platformLoginSettings: 'Platform Login Settings',\n  pagePreview: 'Page Preview',\n  default: 'Default',\n  restoreDefaults: 'Restore Defaults',\n  orange: 'Orange',\n  green: 'Green',\n  purple: 'Purple',\n  red: 'Red',\n  loginBackground: 'Login Background Image',\n  loginLogo: 'Login Logo',\n  websiteLogo: 'Website Logo',\n  replacePicture: 'Replace Image',\n  websiteLogoTip:\n    'Logo displayed at the top of the website. Recommended size: 48x48. Supports JPG, PNG, GIF. Maximum size: 10MB',\n  loginLogoTip:\n    'Logo on the right side of the login page. Recommended size: 204x52. Supports JPG, PNG, GIF. Maximum size: 10MB',\n  loginBackgroundTip:\n    'Left-side background image. Vector graphics recommended size: 576x900; Bitmap recommended size: 1152x1800. Supports JPG, PNG, GIF. Maximum size: 10MB',\n  websiteName: 'Website Name',\n  websiteNamePlaceholder: 'Please enter the website name',\n  websiteNameTip: 'The platform name displayed in the web page tab',\n  websiteSlogan: 'Welcome Slogan',\n  websiteSloganPlaceholder: 'Please enter the welcome slogan',\n  websiteSloganTip: 'The welcome slogan below the product logo',\n\n  defaultTip: 'The default is the MaxKB platform interface, supports custom settings',\n  logoDefaultTip: 'The default is the MaxKB login interface, supports custom settings',\n  platformSetting: 'Platform Settings',\n  showUserManual: 'Show User Manual',\n  showForum: 'Show Forum Support',\n  showProject: 'Show Project Address',\n  urlPlaceholder: 'Please enter the URL address',\n  abandonUpdate: 'Abandon Update',\n  saveAndApply: 'Save and Apply',\n  fileMessageError: 'File size exceeds 10MB',\n  saveSuccess: 'Appearance settings successfully applied',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/application-overview.ts",
    "content": "export default {\n  title: 'Overview',\n  appInfo: {\n    publicAccessLink: 'Public URL',\n    openText: 'On',\n    closeText: 'Off',\n    demo: 'Preview',\n    embedInWebsite: 'Get Embed Code',\n    accessControl: 'Access Control',\n    displaySetting: 'Display Settings',\n    apiAccessCredentials: 'API Access Credentials',\n    apiKey: 'API Key',\n    refreshToken: {\n      msgConfirm1: 'Are you sure you want to regenerate the public URL?',\n      msgConfirm2:\n        'Regenerating the Public URL will affect any existing embedded codes on third-party sites. You will need to update the embed code and re-integrate it into those sites. Proceed with caution!',\n      refreshSuccess: 'Successfully Refreshed',\n    },\n    APIKeyDialog: {\n      saveSettings: 'Save Settings',\n      msgConfirm1: 'Are you sure you want to delete the API Key',\n      msgConfirm2:\n        'This action is irreversible. Once deleted, the API Key cannot be recovered. Do you still want to proceed?',\n    },\n    EmbedDialog: {\n      fullscreenModeTitle: 'Fullscreen Mode',\n      copyInstructions: 'Copy the code below to embed',\n      floatingModeTitle: 'Floating Mode',\n      mobileModeTitle: 'Mobile Mode',\n    },\n    LimitDialog: {\n      clientQueryLimitLabel: 'Query Limit per Client',\n      authentication: 'Authentication',\n      authenticationValue: 'Password Access',\n      timesDays: 'queries per day',\n      whitelistLabel: 'Allowed Domains',\n      whitelistPlaceholder:\n        'Enter allowed third-party domains, one per line. For example:\\nhttp://127.0.0.1:5678\\nhttps://dataease.io',\n      loginMethod: 'Login Method',\n      loginMethodRequired: 'Please select login method',\n      toSettingChatUser: 'Go to configure chat user',\n      displayCodeRequired: 'Please enter the number of failed attempts',\n      authenticationTooltip:\n        'Enabling login authentication requires chat users to have authorization configured for both the agent and its associated knowledge bases. Without proper authorization, users will not be able to log in or access knowledge base retrieval features.',\n    },\n    SettingAPIKeyDialog: {\n      allowCrossDomainLabel: 'Allow Cross-Domain Access',\n      crossDomainPlaceholder:\n        'Enter allowed cross-domain addresses. If enabled but left blank, no restrictions will apply.\\nEnter one per line, e.g.:\\nhttp://127.0.0.1:5678\\nhttps://dataease.io',\n    },\n  },\n  SettingDisplayDialog: {\n    showSourceLabel: 'Show Knowledge Source',\n    showExecutionDetail: 'Show Execution Details',\n    restoreDefault: 'Restore Default',\n    customThemeColor: 'Custom Theme Color',\n    headerTitleFontColor: 'Header Title Font Color',\n    default: 'Default',\n    askUserAvatar: 'User Avatar (Asking)',\n    replace: 'Replace',\n\n    imageMessage:\n      'Recommended size: 32×32 pixels. Supports JPG, PNG, and GIF formats. Max size: 10 MB',\n    AIAvatar: 'AI Avatar',\n    display: 'Display',\n    floatIcon: 'Floating Icon',\n    iconDefaultPosition: 'Default Icon Position',\n    iconPosition: {\n      left: 'Left',\n      right: 'Right',\n      bottom: 'Bottom',\n      top: 'Top',\n    },\n    draggablePosition: 'Draggable Position',\n    showHistory: 'Show Chat History',\n    displayGuide: 'Show Guide Image (Floating Mode)',\n    disclaimer: 'Disclaimer',\n    disclaimerValue: 'This content is AI-generated and for reference only.',\n    chatBackground: 'Chat Background',\n    chatBackgroundMessage: 'Supported formats: JPG, PNG, GIF. Max size: 10MB.',\n  },\n  monitor: {\n    monitoringStatistics: 'Monitoring Statistics',\n    customRange: 'Custom Range',\n    startDatePlaceholder: 'Start Date',\n    endDatePlaceholder: 'End Date',\n    pastDayOptions: {\n      past7Days: 'Last 7 Days',\n      past30Days: 'Last 30 Days',\n      past90Days: 'Last 90 Days',\n      past183Days: 'Last 6 Months',\n    },\n    charts: {\n      customerTotal: 'Total Users',\n      customerNew: 'New Users',\n      queryCount: 'Total Queries',\n      tokensTotal: 'Total Tokens Used',\n      userSatisfaction: 'User Feedback Metrics',\n      approval: 'Like',\n      disapproval: 'Dislike',\n      tokenUsage: 'User used Tokens',\n      topQuestions: 'Number of user question',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/application.ts",
    "content": "export default {\n  title: 'Agent',\n  createApplication: 'Create Simple Agent',\n  createWorkFlowApplication: 'Create Workflow Agent',\n  importApplication: 'Import Agent',\n  copyApplication: 'Copy Agent',\n  simple: 'SIMPLE',\n  senior: 'WORKFLOW',\n  simpleAgent: 'Simple Agent',\n  AdvancedAgent: 'Advanced Agent',\n  simplePlaceholder: 'Quickly build intelligent agents with basic functions through form settings',\n  advancedPlaceholder:\n    'Using low-code drag-and-drop methods, flexibly orchestrate complex logic and feature-rich agents',\n  appTest: 'Debug Preview',\n  operation: {\n    addModel: 'Add Model',\n    toChat: 'Chat',\n  },\n  delete: {\n    confirmTitle: 'Are you sure you want to delete this agent: ',\n    confirmMessage:\n      'Deleting this agent will no longer provide its services. Please proceed with caution.',\n    resourceCountMessage:\n      'This agent is associated with {count} resources, and will be unavailable after deletion. Please proceed with caution.',\n  },\n  tip: {\n    publishSuccess: 'Published successfully',\n    ExportError: 'Export Failed',\n    professionalMessage:\n      'The Community Edition supports up to 5 agents. If you need more agents, please upgrade to the Professional Edition.',\n    saveErrorMessage: 'Saving failed, please check your input or try again later',\n    loadingErrorMessage: 'Failed to load configuration, please check your input or try again later',\n    noDocPermission: 'No permission to create documents',\n    confirmUse: 'Are you sure you want to use',\n    overwrite: 'overwrite the current workflow',\n  },\n\n  form: {\n    appName: {\n      placeholder: 'Please enter the agent name',\n      requiredMessage: 'Agent name is required',\n    },\n    appDescription: {\n      placeholder:\n        'Describe the Agent scenario and use, e.g.: XXX assistant answering user questions about XXX product usage',\n    },\n    appType: {\n      simplePlaceholder: 'Suitable for beginners to create assistant.',\n      workflowPlaceholder: 'Suitable for advanced users to customize the workflow of assistant',\n    },\n    appTemplate: {\n      blankApp: {\n        title: 'Blank Agent',\n      },\n      assistantApp: {\n        title: 'Knowledge Assistant',\n        description: 'Suitable for advanced users to customize the workflow of assistant',\n      },\n    },\n    aiModel: {\n      label: 'AI Model',\n      placeholder: 'Please select an AI model',\n    },\n    roleSettings: {\n      label: 'System Prompt',\n      placeholder:\n        'System prompt, you can reference variables in the system: {data} is the segment hit in the knowledge base; {question} is the question asked by the user.',\n      tooltip: 'Set the role or instructions for the model to follow',\n    },\n\n    prompt: {\n      label: 'User Prompt',\n      noReferences: '（No references Knowledge）',\n      references: ' (References Knowledge)',\n      placeholder:\n        'User prompt, you can reference variables in the system: {data} is the segment hit in the knowledge base; {question} is the question asked by the user',\n      requiredMessage: 'Please enter User prompt',\n      tooltip: 'The question or command that the user poses to the model',\n\n      noReferencesTooltip:\n        'By adjusting the content of the prompt, you can guide the direction of the large model chat. This prompt will be fixed at the beginning of the context. Variables used: {question} is the question posed by the user.',\n      referencesTooltip:\n        'By adjusting the content of the prompt, you can guide the direction of the large model chat. This prompt will be fixed at the beginning of the context. Variables used: {data} carries known information from the knowledge; {question} is the question posed by the user.',\n      defaultPrompt: `Known information: {data}\n        Question: {question}\n         Response requirements:\n         - Please use concise and professional language to answer the user's question.\n         `,\n    },\n    historyRecord: {\n      label: 'Chat History',\n    },\n    relatedKnowledge: {\n      label: 'Related Knowledge',\n      placeholder: 'Related knowledge are displayed here',\n    },\n    multipleRoundsDialogue: 'Multiple Rounds Dialogue',\n\n    prologue: 'Prologue',\n    defaultPrologue:\n      'Hello, I am XXX Assistant. You can ask me questions about using XXX.\\n- What are the main features of XXX?\\n- Which LLM does XXX support?\\n- What document types does XXX support?',\n    problemOptimization: {\n      label: 'Questions Optimization',\n      tooltip:\n        'Optimize the current question based on historical chat to better match knowledge points.',\n    },\n\n    voiceInput: {\n      label: 'Voice Input',\n      placeholder: 'Please select a speech recognition model',\n      requiredMessage: 'Please select a speech input model',\n      autoSend: 'Automatic Sending',\n    },\n    voicePlay: {\n      label: 'Voice Playback',\n      placeholder: 'Please select a speech synthesis model',\n      requiredMessage: 'Please select a speech playback model',\n      autoPlay: 'Automatic Playback',\n      browser: 'Browser Playback (free)',\n      tts: 'TTS Model',\n      listeningTest: 'Preview',\n    },\n    reasoningContent: {\n      label: 'Output Thinking',\n      tooltip:\n        \"Please set the thinking label based on the model's return, and the content in the middle of the label will be recognized as the thinking process.\",\n      start: 'Start',\n      end: 'End',\n    },\n    mcp_output_enable: 'Output Execution Process',\n  },\n  generateDialog: {\n    label: 'Generate',\n    generatePrompt: 'Generate Prompt',\n    placeholder: 'Please enter the prompt topic',\n    title: 'The prompt is displayed here',\n    remake: 'Regenerate',\n    stop: 'Stop Generating',\n    continue: 'Continue Generating',\n    replace: 'Replace',\n    exit: 'Are you sure you want to exit and discard the AI-generated content?',\n    loading: 'Generating...',\n  },\n  dialog: {\n    addKnowledge: 'Add Related Knowledge',\n    addKnowledgePlaceholder: 'The selected knowledge must use the same embedding model',\n    selectSearchMode: 'Retrieval Mode',\n    vectorSearch: 'Vector Search',\n    vectorSearchTooltip:\n      'Vector search is a retrieval method based on vector distance calculations, suitable for large data volumes in the knowledge.',\n    fullTextSearch: 'Full-text Search',\n    fullTextSearchTooltip:\n      'Full-text search is a retrieval method based on text similarity, suitable for small data volumes in the knowledge.',\n    hybridSearch: 'Hybrid Search',\n    hybridSearchTooltip:\n      'Hybrid search is a retrieval method based on both vector and text similarity, suitable for medium data volumes in the knowledge.',\n    similarityThreshold: 'Similarity higher than',\n    similarityTooltip: 'The higher the similarity, the stronger the correlation.',\n    topReferences: 'Top N Segments',\n    maxCharacters: 'Maximum  Characters per Reference',\n    noReferencesAction: 'When there are no knowledge references',\n    continueQuestioning: 'Continue to ask questions to the Al model',\n    provideAnswer: 'Specify Reply Content',\n    designated_answer:\n      'Hello, I am XXX Assistant. My knowledge only contains information related to XXX products. Please rephrase your question.',\n    defaultPrompt1:\n      \"The content inside the parentheses () represents the user's question. Based on the context, please speculate and complete the user's question ({question}). The requirement is to output a completed question and place it\",\n    defaultPrompt2: 'tag',\n  },\n  applicationAccess: {\n    title: 'Third-Party Access',\n    wecom: 'WeCom',\n    wecomTip: 'Create WeCom Agent',\n    wecomBot: 'WeCom Bot',\n    wecomBotTip: 'Create WeCom intelligent Bot',\n    dingtalk: 'DingTalk',\n    dingtalkTip: 'Create DingTalk Agent',\n    wechat: 'WeChat',\n    wechatTip: 'Create WeChat Agent',\n    lark: 'Lark',\n    larkTip: 'Create Lark Agent',\n    setting: 'Setting',\n    callback: 'Callback Address',\n    callbackTip: 'Please fill in the callback address',\n    wecomPlatform: 'WeCom Open Platform',\n    wechatPlatform: 'WeChat Open Platform',\n    dingtalkPlatform: 'DingTalk Open Platform',\n    larkPlatform: 'Lark Open Platform',\n    slack: 'Slack',\n    slackTip: 'Create Slack Agent',\n    wecomSetting: {\n      title: 'WeCom Configuration',\n      cropId: 'Crop ID',\n      cropIdPlaceholder: 'Please enter crop ID',\n      agentIdPlaceholder: 'Please enter agent ID',\n      secretPlaceholder: 'Please enter secret',\n      tokenPlaceholder: 'Please enter token',\n      encodingAesKeyPlaceholder: 'Please enter EncodingAESKey',\n      authenticationSuccessful: 'Successful',\n      urlInfo:\n        '-APP management-Self-built-Created APP-Receive messages-Set the \"URL\" received by the API',\n    },\n    dingtalkSetting: {\n      title: 'DingTalk Configuration',\n      clientIdPlaceholder: 'Please enter client ID',\n      clientSecretPlaceholder: 'Please enter client secret',\n      urlInfo:\n        '-On the robot page, set the \"Message Receiving Mode\" to HTTP mode, and fill in the above URL into the \"Message Receiving Address\"',\n    },\n    wechatSetting: {\n      title: 'WeChat Configuration',\n      appId: 'APP ID',\n      appIdPlaceholder: 'Please enter APP ID',\n      appSecret: 'APP SECRET',\n      appSecretPlaceholder: 'Please enter APP SECRET',\n      token: 'TOKEN',\n      tokenPlaceholder: 'Please enter TOKEN',\n      aesKey: 'Message Encryption Key',\n      aesKeyPlaceholder: 'Please enter the message encryption key',\n      urlInfo:\n        '-Settings and Development-Basic Configuration-\"Server Address URL\" in server configuration',\n    },\n    wecomBotSetting: {\n      title: 'WeCom Bot Configuration',\n      urlInfo: '-Management Tools-Smart Bot-Create Bot-API Mode Create \"URL\"',\n    },\n    larkSetting: {\n      title: 'Lark Configuration',\n      appIdPlaceholder: 'Please enter APP ID',\n      appSecretPlaceholder: 'Please enter APP secret',\n      verificationTokenPlaceholder: 'Please enter verification token',\n      urlInfo:\n        '-Events and callbacks - event configuration - configure the \"request address\" of the subscription method',\n    },\n    slackSetting: {\n      title: 'Slack Configuration',\n      signingSecretPlaceholder: 'Please enter signing secret',\n      botUserTokenPlaceholder: 'Please enter bot user token',\n    },\n    copyUrl: 'Copy the link and fill it in',\n  },\n  hitTest: {\n    title: 'Retrieval Testing',\n    text: 'Test the hitting effect of the Knowledge based on the given query text.',\n    emptyMessage1: 'Retrieval Testing results will show here',\n    emptyMessage2: 'No matching sections found',\n  },\n  publishTime: 'Publish Time',\n  publishStatus: 'Publish Status',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/chat-log.ts",
    "content": "export default {\n  title: 'Chat Logs',\n  delete: {\n    confirmTitle: 'Confirm deletion of question:',\n    confirmMessage1: 'Deleting this question will cancel the association of',\n    confirmMessage2: 'segments. Please proceed with caution.',\n  },\n  buttons: {\n    clearStrategy: 'Cleanup Strategy',\n  },\n  table: {\n    abstract: 'Title',\n    username: 'User',\n    chat_record_count: 'Total Messages',\n    user: 'User',\n    feedback: {\n      label: 'User Feedback',\n      star: 'Agree',\n      trample: 'Disagree',\n    },\n    mark: 'Marks',\n    recenTimes: 'Last Chat Time',\n  },\n  addToKnowledge: 'Add to Knowledge',\n  daysText: 'Days ago',\n  fileDaysText: 'The attachment uploaded from the previous conversation',\n  selectKnowledge: 'Select Knowledge',\n  selectKnowledgePlaceholder: 'Please select a knowledge',\n  saveToDocument: 'Save to Document',\n  documentPlaceholder: 'Please select a document',\n  editContent: 'Edit Content',\n  editMark: 'Edit Label',\n  form: {\n    content: {\n      placeholder: 'Please enter the content',\n    },\n    title: {\n      placeholder: 'Please set a title for the current content for management and viewing',\n    },\n  },\n  online: 'Online Usage',\n  apiCall: 'API Call',\n  enterpriseWechat: 'Enterprise WeChat Application',\n  wechatPublicAccount: 'WeChat Public Account',\n  lark: 'Lark Application',\n  dingtalk: 'DingTalk Application',\n  enterpriseWechatRobot: 'Enterprise WeChat Bot',\n  slack: 'Slack Bot',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/chat-user.ts",
    "content": "export default {\n  title: 'Chat Users',\n  syncUsers: 'Import Users',\n  syncUsersTip: 'Only import newly added users',\n  setUserGroups: 'Configure User Groups',\n  knowledgeTitleTip:\n    'This configuration will only take effect after enabling chat user login authentication in the associated agent',\n  applicationTitleTip:\n    'This configuration requires login authentication to be enabled in the agent',\n  autoAuthorization: 'Auto Authorization',\n  authorization: 'Authorization',\n  batchDeleteUser: 'Delete selected {count} users?',\n  settingMethod: 'Configuration Method',\n  append: 'Append',\n  group: {\n    title: 'User Groups',\n    name: 'User Group Name',\n    requiredMessage: 'Please select user group',\n    usernameOrName: 'Username/Name',\n    delete: {\n      confirmTitle: 'Confirm to delete user group:',\n      confirmMessage:\n        'All members in this group will be removed after deletion. Proceed with caution!',\n    },\n    batchDeleteMember: 'Remove selected {count} members?',\n  },\n  syncMessage: {\n    title: 'Successfully synced {count} users',\n    usernameExist: 'The following usernames already exist:',\n    nicknameExist: 'The following nicknames already exist:',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/document.ts",
    "content": "export default {\n  uploadDocument: 'Upload Document',\n  importDocument: 'Import Document',\n  syncDocument: 'Sync Document',\n  items: '',\n  migrateDocument: 'Migrate to',\n  setting: {\n    migration: 'Move',\n    cancelGenerateQuestion: 'Cancel Generating Questions',\n    cancelVectorization: 'Cancel Vectorization',\n    cancelGenerate: 'Cancel Generation',\n    export: 'Export to',\n    download: 'Download',\n    replace: 'Replace',\n  },\n\n  tip: {\n    saveMessage: 'Current changes have not been saved. Confirm exit?',\n    cancelSuccess: 'Successful',\n    sendMessage: 'Successful',\n    vectorizationSuccess: 'Successful',\n    nameMessage: 'Document name cannot be empty!',\n    importMessage: 'Successful',\n    migrationSuccess: 'Successful',\n    replaceSuccess: 'Successful',\n    fileLimitCountTip1: 'Maximum upload per time',\n    fileLimitCountTip2: 'files',\n    fileLimitSizeTip1: 'each file must not exceed',\n    toImportDocConfirm:\n      'The workflow of the current knowledge base is not published, and documents cannot be imported. Please publish the workflow first.',\n     fileLimitSizeTip2: 'size must not exceed',\n  },\n  upload: {\n    selectFile: 'Select File',\n    selectFiles: 'Select Folder',\n    uploadMessage: 'Drag and drop files here to upload or',\n    formats: 'Supported formats:',\n    requiredMessage: 'Please upload a file',\n    errorMessage1: 'The file size exceeds 100mb',\n    errorMessage2: 'Unsupported file format',\n    errorMessage3: 'File cannot be empty',\n    errorMessage4: 'Up to 50 files can be uploaded at once',\n    template: 'Template',\n    download: 'Download',\n  },\n\n  fileType: {\n    txt: {\n      label: 'Text File',\n      tip1: '1. It is recommended to standardize the segment markers in the file before uploading.',\n      tip2: '2. Up to 50 files can be uploaded at once, with each file not exceeding 100MB.',\n    },\n    table: {\n      label: 'Table',\n      tip1: '1. Click to download the corresponding template and complete the information:',\n      tip2: '2. The first row must be column headers, and the column headers must be meaningful terms. Each record in the table will be treated as a segment.',\n      tip3: '3. Each sheet in the uploaded spreadsheet file will be treated as a document, with the sheet name as the document name.',\n      tip4: '4. Up to 50 files can be uploaded at once, with each file not exceeding 100MB.',\n    },\n    QA: {\n      label: 'QA Pairs',\n      tip1: '1. Click to download the corresponding template and complete the information:',\n      tip2: '2. Each sheet in the uploaded spreadsheet file will be treated as a document, with the sheet name as the document name.',\n      tip3: '3. Up to 50 files can be uploaded at once, with each file not exceeding 100MB.',\n    },\n  },\n  setRules: {\n    title: {\n      setting: 'Set Segment Rules',\n      preview: 'Preview',\n    },\n    intelligent: {\n      label: 'Automatic Segmentation (Recommended)',\n      text: 'If you are unsure how to set segmentation rules, it is recommended to use automatic segmentation.',\n    },\n    advanced: {\n      label: 'Advanced Segmentation',\n      text: 'Users can customize segmentation delimiters, segment length, and cleaning rules based on document standards.',\n    },\n    patterns: {\n      label: 'Segment Delimiters',\n      tooltip:\n        'Recursively split according to the selected symbols in order. If the split result exceeds the segment length, it will be truncated to the segment length.',\n      placeholder: 'Please select',\n    },\n    limit: {\n      label: 'Segment Length',\n    },\n    with_filter: {\n      label: 'Auto Clean',\n      text: 'Remove duplicate extra symbols, spaces, blank lines, and tab words.',\n    },\n    checkedConnect: {\n      label: 'Add \"Related Questions\" section for question-based QA pairs during import.',\n    },\n  },\n  buttons: {\n    import: 'Start Import',\n    preview: 'Apply',\n    continueImporting: 'Continue Importing Documents',\n  },\n  tag: {\n    label: 'Tag Management',\n    key: 'Tag',\n    value: 'Value',\n    addTag: 'Add Tag',\n    noTag: 'No Tag',\n    relate: 'Link',\n    unrelate: 'Unlink',\n    relatedDoc: 'Linked documents',\n    unrelatedDoc: 'Unlinked documents',\n    setting: 'Tag Settings',\n    create: 'Create Tag',\n    createValue: 'Create Tag Value',\n    edit: 'Edit Tag',\n    editValue: 'Edit Tag Value',\n    deleteConfirm: 'Confirm delete tag: ',\n    deleteTip:\n      'After deletion, resources using this tag will have the tag removed. Please proceed with caution!',\n    requiredMessage1: 'Please enter a tag',\n    requiredMessage2: 'Please enter a value',\n    requiredMessage3: 'Please enter a tag or value',\n  },\n  table: {\n    name: 'Document Name',\n    char_length: 'Character',\n    paragraph: 'Segment',\n    all: 'All',\n    updateTime: 'Update Time',\n  },\n  fileStatus: {\n    label: 'File Status',\n    SUCCESS: 'Success',\n    FAILURE: 'Failure',\n    EMBEDDING: 'Indexing',\n    PENDING: 'Queuing',\n    GENERATE: 'Generating',\n    SYNC: 'Syncing',\n    finish: 'Finish',\n  },\n  enableStatus: {\n    label: 'Status',\n    enable: 'Enabled',\n    close: 'Disabled',\n  },\n  sync: {\n    label: 'Sync',\n    confirmTitle: 'Confirm sync document?',\n    confirmMessage1:\n      'Syncing will delete existing data and retrieve new data. Please proceed with caution.',\n    confirmMessage2: 'Cannot sync, please set the document URL first.',\n    successMessage: 'Successful',\n  },\n  delete: {\n    confirmTitle1: 'Confirm batch deletion of',\n    confirmTitle2: 'documents?',\n    confirmMessage:\n      'Segments within the selected documents will also be deleted. Please proceed with caution.',\n    successMessage: 'Successful',\n    confirmTitle3: 'Confirm deleting document:',\n    confirmMessage1: 'Under this document',\n    confirmMessage2: 'All segments will be deleted, please operate with caution. ',\n  },\n  form: {\n    source_url: {\n      label: 'Document URL',\n      placeholder: 'Enter document URL, one per line. Incorrect URL will cause import failure.',\n      requiredMessage: 'Please enter a document URL',\n    },\n    selector: {\n      label: 'Selector',\n      placeholder: 'Default is body, you can input .classname/#idname/tagname',\n    },\n    hit_handling_method: {\n      label: 'Retrieve-Respond',\n      tooltip: 'When user asks a question, handle matched segments according to the set method.',\n    },\n    similarity: {\n      label: 'Similarity Higher Than',\n      placeholder: 'Directly return segment content',\n      requiredMessage: 'Please enter similarity value',\n    },\n    allow_download: {\n      label: 'Allow download in knowledge base source',\n    },\n  },\n  hitHandlingMethod: {\n    optimization: 'Model optimization',\n    directly_return: 'Respond directly',\n  },\n  movePosition: {\n    title: 'Move position',\n    moveUp: 'Move up',\n    moveDown: 'Move down',\n    moveTop: 'Move top',\n    moveBottom: 'Move bottom',\n  },\n  generateQuestion: {\n    title: 'Generate Questions',\n    successMessage: 'Successful',\n    tip1: 'The {data} in the prompt is a placeholder for segmented content, which is replaced by the segmented content when executed and sent to the AI model;',\n    tip2: 'The AI model generates relevant questions based on the segmented content. Please place the generated questions within the',\n    tip3: 'tags, and the system will automatically relate the questions within these tags;',\n    tip4: 'The generation effect depends on the selected model and prompt. Users can adjust to achieve the best effect.',\n    prompt1:\n      'Content: {data}\\n \\n Please summarize the above and generate 5 questions based on the summary. \\nAnswer requirements: \\n - Please output only questions; \\n - Please place each question in',\n    prompt2: 'tag.',\n  },\n  feishu: {\n    selectDocument: 'Select Document',\n    tip1: 'Only documents and tables are supported. Documents will be segmented based on titles, and tables will be converted to Markdown format before segmentation.',\n    tip2: 'Before importing the document, it is recommended to standardize the document segmentation markers.',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/index.ts",
    "content": "import login from './login'\nimport model from './model'\nimport knowledge from './knowledge'\nimport tool from './tool'\nimport document from './document'\nimport system from './system'\nimport userManage from './user-manage'\nimport role from './role'\nimport workspace from './workspace'\nimport application from './application'\nimport problem from './problem'\nimport applicationOverview from './application-overview'\nimport paragraph from './paragraph'\nimport chatLog from './chat-log'\nimport chatUser from './chat-user'\nimport operateLog from './operate-log'\nimport shared from './shared'\nimport trigger from './trigger'\nexport default {\n  login,\n  model,\n  knowledge,\n  tool,\n  document,\n  system,\n  userManage,\n  role,\n  workspace,\n  application,\n  problem,\n  applicationOverview,\n  paragraph,\n  chatLog,\n  chatUser,\n  operateLog,\n  shared,\n  trigger\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/knowledge.ts",
    "content": "export default {\n  title: 'Knowledge',\n  relatedApplications: 'Linked Agent',\n  document_count: 'docs',\n  relatedApp_count: 'linked agents',\n  setting: {\n    vectorization: 'Vectorization',\n    sync: 'Sync',\n  },\n  tip: {\n    professionalMessage:\n      'The community edition supports up to 50 knowledge. For more knowledge, please upgrade to the professional edition.',\n    syncSuccess: 'Sync task sent successfully',\n    updateModeMessage:\n      'After modifying the knowledge vector model, you need to vectorize the knowledge. Do you want to continue saving?',\n  },\n  delete: {\n    confirmTitle: 'Confirm deletion of knowledge:',\n    confirmMessage1: 'This knowledge is related with',\n    confirmMessage2: 'agent. Deleting it will be irreversible, please proceed with caution.',\n    resourceCountMessage:\n      'This knowledge is associated with {count} resources, and will be unavailable after deletion. Please proceed with caution.',\n  },\n  knowledgeType: {\n    label: 'Type',\n    generalKnowledge: 'General Knowledge',\n    webKnowledge: 'Web Knowledge',\n    larkKnowledge: 'Lark Knowledge',\n    workflowKnowledge: 'Workflow Knowledge',\n    yuqueKnowledge: 'Yuque Knowledge',\n    generalInfo: 'Upload local documents',\n    webInfo: 'Sync text data from a web site',\n    larkInfo: 'Build knowledge through Lark documents',\n    yuqueInfo: 'Build knowledge through Yuque documents',\n    createGeneralKnowledge: 'Create General Knowledge',\n    createWebKnowledge: 'Create Web Knowledge',\n    createLarkKnowledge: 'Create Lark Knowledge',\n    createYuqueKnowledge: 'Create Yuque Knowledge',\n    createWorkflowKnowledge: 'Create Workflow Knowledge',\n    workflowInfo: 'Building a knowledge base through custom workflow methods',\n  },\n  form: {\n    knowledgeName: {\n      label: 'Name',\n      placeholder: 'Please enter the knowledge name',\n      requiredMessage: 'Please enter the knowledge name',\n    },\n    knowledgeDescription: {\n      label: 'Description',\n      placeholder:\n        'Describe the content of the knowledge. A detailed description will help AI understand the content better, improving the accuracy of content retrieval and hit rate.',\n      requiredMessage: 'Please enter the knowledge description',\n    },\n    EmbeddingModel: {\n      label: 'Embedding Model',\n      placeholder: 'Please select a embedding model',\n      requiredMessage: 'Please select the embedding model',\n    },\n\n    source_url: {\n      label: 'Web Root URL',\n      placeholder: 'Please enter the web root URL',\n      requiredMessage: 'Please enter the web root URL',\n    },\n    selector: {\n      label: 'Selector',\n      placeholder: 'Default is body, can input .classname/#idname/tagname',\n    },\n    file_count_limit: {\n      label: 'Maximum number of files uploaded at once',\n    },\n    file_size_limit: {\n      label: 'Maximum size of each document(MB)',\n      placeholder: 'Suggest based on server configuration, otherwise may cause service shutdown',\n    },\n    appTemplate: {\n      blank: {\n        title: 'Blank Creation',\n      },\n      basic: {\n        title: 'Basic Template',\n        description:\n          'Supports basic workflow templates for local files, Lark documents, and web site data sources',\n      },\n    },\n  },\n\n  ResultSuccess: {\n    title: 'Knowledge Created Successfully',\n    paragraph: 'Segments',\n    paragraph_count: 'Segments',\n    documentList: 'Document List',\n    loading: 'Importing',\n    buttons: {\n      toKnowledge: 'To Knowledge List',\n      toDocument: 'Go to Document',\n    },\n  },\n  syncWeb: {\n    title: 'Sync Knowledge',\n    syncMethod: 'Sync Method',\n    replace: 'Replace Sync',\n    replaceText: 'Re-fetch Web site documents, replacing the documents in the local knowledge',\n    complete: 'Full Sync',\n    completeText: 'Delete all documents in the local knowledge and re-fetch web site documents',\n    tip: 'Note: All syncs will delete existing data and re-fetch new data. Please proceed with caution.',\n  },\n  transform: {\n    button: 'Convert',\n    title: 'Convert to Workflow Knowledge Base',\n    message1:\n      \"You can now convert your existing knowledge base into a workflow knowledge base—a more open and flexible type that allows you to autonomously orchestrate the entire process from various data sources to knowledge base writing through drag-and-drop node operations, meeting your enterprise's personalized knowledge management needs. You can utilize the data sources and tools available in our suite. \",\n    message2: 'The new processing method will be applied to all documents imported subsequently.',\n    tip: 'Note: The conversion cannot be undone.',\n    confirm:\n      'Are you sure you want to convert to the workflow knowledge base? This action cannot be undone. Please proceed with caution.',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/login.ts",
    "content": "export default {\n  title: 'Login',\n  loginForm: {\n    username: {\n      label: 'Username',\n      placeholder: 'Please enter username',\n      requiredMessage: 'Please enter username',\n      lengthMessage: 'Length must be between 4 and 20 words',\n    },\n    password: {\n      label: 'Login Password',\n      placeholder: 'Please enter password',\n      requiredMessage: 'Please enter password',\n      lengthMessage: 'Length must be between 6 and 20 words',\n    },\n    captcha: {\n      label: 'Verification Code',\n      placeholder: 'Please enter verification code',\n      requiredMessage: 'Please enter verification code',\n      validatorMessage: 'Verification code is incorrect',\n    },\n    new_password: {\n      label: 'New Password',\n      placeholder: 'Please enter new password',\n      requiredMessage: 'Please enter new password',\n    },\n    re_password: {\n      label: 'Confirm Password',\n      placeholder: 'Please enter confirm password',\n      requiredMessage: 'Please enter confirm password',\n      validatorMessage: 'Password does not match',\n    },\n    email: {\n      label: 'Email',\n      placeholder: 'Please enter email',\n      requiredMessage: 'Please enter email',\n      validatorEmail: 'Please enter a valid email format!',\n    },\n  },\n  jump_tip: 'You will be redirected to the authentication source page for authentication',\n  jump: 'Redirect',\n  resetPassword: 'Change Password',\n  forgotPassword: 'Forgot Password',\n  userRegister: 'User Registration',\n  buttons: {\n    login: 'Login',\n    register: 'Register',\n    backLogin: 'Back to Login',\n    checkCode: 'Verify Now',\n  },\n  newPassword: 'New Password',\n  enterPassword: 'Please enter your new password',\n  useEmail: 'Use Email',\n  moreMethod: 'More Login Methods',\n  verificationCode: {\n    placeholder: 'Please enter the verification code',\n    getVerificationCode: 'Get Verification Code',\n    successMessage: 'If the email address is already registered, we will send an email. Please check your inbox',\n    resend: 'Resend',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/model.ts",
    "content": "export default {\n  title: 'Model',\n  provider: 'Provider',\n  providerPlaceholder: 'Select Provider',\n  addModel: 'Add Model',\n\n  delete: {\n    confirmTitle: 'Delete Model：',\n    confirmMessage:\n      'Deleting the model will affect the resources currently using it. Please proceed with caution.',\n    resourceCountMessage: 'This model is associated with {count} resources, and will be unavailable after deletion. Please proceed with caution.',\n  },\n  tip: {\n    createSuccessMessage: 'Model created successfully',\n    createErrorMessage: 'There are errors in the basic information',\n    errorMessage: 'Variable already exists: ',\n    emptyMessage1: 'Please select the model type and base model in the basic information first',\n    emptyMessage2: 'The selected model does not support parameter settings',\n    updateSuccessMessage: 'Model updated successfully',\n    saveSuccessMessage: 'Model parameters saved successfully',\n    downloadError: 'Download failed',\n    noModel: 'Model does not exist in Ollama',\n  },\n  modelType: {\n    allModel: 'All Models',\n    publicModel: 'Public Models',\n    privateModel: 'Private Models',\n    LLM: 'LLM',\n    EMBEDDING: 'Embedding Model',\n    RERANKER: 'Rerank',\n    STT: 'Speech2Text',\n    TTS: 'TTS',\n    IMAGE: 'Vision Model',\n    TTI: 'Image Generation',\n    TTV: 'Text-to-Video',\n    ITV: 'Image-to-Video',\n  },\n  modelForm: {\n    title: {\n      baseInfo: 'Basic Information',\n      advancedInfo: 'Advanced Settings',\n      modelParams: 'Model Parameters',\n      paramSetting: 'Model Parameter Settings',\n      apiParamPassing: 'Interface Parameters',\n    },\n    modeName: {\n      label: 'Model Name',\n      placeholder: 'Set a name for the base model',\n      tooltip: 'Custom model name in MaxKB',\n      requiredMessage: 'Model name cannot be empty',\n    },\n    permissionType: {\n      label: 'Permission',\n      privateDesc: 'Available only to current user',\n      publicDesc: 'Available to all users',\n      requiredMessage: 'Permission cannot be empty',\n    },\n    model_type: {\n      label: 'Model Type',\n      placeholder: 'Select a model type',\n      tooltip1: 'LLM: An inference model for AI chats in the agent.',\n      tooltip2: 'Embedding Model: A model for vectorizing document content in the knowledge.',\n      tooltip3: 'Speech2Text: A model used for speech recognition in the agent.',\n      tooltip4: 'TTS: A model used for TTS in the agent.',\n      tooltip5:\n        'Rerank: A model used to reorder candidate segments when using multi-route recall in advanced orchestration agent.',\n      tooltip6:\n        'Vision Model: A visual model used for image understanding in advanced orchestration agent.',\n      tooltip7:\n        'Image Generation: A visual model used for image generation in advanced orchestration agent.',\n      tooltip8:\n        'Text-to-Video: A visual model used for text-to-video in the agent.',\n      tooltip9:\n        'Image-to-Video: A visual model used for image-to-video in the agent.',\n      requiredMessage: 'Model type cannot be empty',\n    },\n    base_model: {\n      label: 'Base Model',\n      tooltip: 'For models not listed, enter the model name and press Enter',\n      placeholder: 'Enter the base model name and press Enter to add',\n      requiredMessage: 'Base model cannot be empty',\n    },\n  },\n  download: {\n    downloading: 'Downloading...',\n    cancelDownload: 'Cancel Download',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/operate-log.ts",
    "content": "export default {\n  title: 'Operate Logs',\n  table: {\n    menu: 'Operate Menu',\n    detail: 'Operate Details',\n    user: 'Operate User',\n    ip_address: 'IP Address',\n    opt: 'API Details',\n    operateTime: 'Operate Time',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/paragraph.ts",
    "content": "export default {\n  title: 'Segment',\n  paragraph_count: 'Segments',\n  editParagraph: 'Edit Segment',\n  addParagraph: 'Add Segment',\n  prevAddParagraph: 'Insert Segment Above',\n  paragraphDetail: 'Segment Details',\n  character_count: 'characters',\n  setting: {\n    batchSelected: 'Batch Select',\n    cancelSelected: 'Cancel Selection',\n  },\n  delete: {\n    confirmTitle: 'Confirm deletion of segment:',\n    confirmMessage: 'Deletion cannot be undone. Please proceed with caution.',\n  },\n  relatedProblem: {\n    title: 'Related Questions',\n    placeholder: 'Please select a question',\n  },\n  form: {\n    paragraphTitle: {\n      label: 'Title',\n      placeholder: 'Please enter the segment title',\n    },\n    content: {\n      label: 'Content',\n      placeholder: 'Please enter the segment content',\n      requiredMessage1: 'Please enter the segment content',\n      requiredMessage2: 'Content must not exceed 100,000 words',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/problem.ts",
    "content": "export default {\n  title: 'Questions',\n  createProblem: 'Create Question',\n  detailProblem: 'Question Details',\n  quickCreateProblem: 'Quick Create',\n  quickCreateName: 'question',\n  tip: {\n    placeholder: 'Enter the question, support multiple entries, one per line.',\n    errorMessage: 'Question cannot be empty!',\n    requiredMessage: 'Please enter a question',\n    relatedSuccess: 'Successful'\n  },\n\n  setting: {\n    batchDelete: 'Bulk Delete',\n    cancelRelated: 'Cancel Association'\n  },\n  table: {\n    paragraph_count: 'Related Segments',\n    updateTime: 'Update Time'\n  },\n  delete: {\n    confirmTitle: 'Confirm deletion of question:',\n    confirmMessage1: 'Deleting this question will cancel the association of',\n    confirmMessage2: 'segments. Please proceed with caution.'\n  },\n  relateParagraph: {\n    title: 'Relate to Segment',\n    selectDocument: 'Select a Document',\n    placeholder: 'Search document by name',\n    selectedParagraph: 'Selected Segments',\n    count: 'Count'\n  }\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/role.ts",
    "content": "export default {\n  title: 'Role Management',\n  internalRole: 'System built-in roles',\n  customRole: 'Custom roles',\n  systemAdmin: 'System admin',\n  workspaceAdmin: 'Workspace admin',\n  user: 'Regular user',\n  roleName: 'Role name',\n  inheritingRole: 'Inherited role',\n  delete: {\n    confirmTitle: 'Confirm to delete role:',\n    confirmMessage: 'After deletion, all members under this role will be removed. Please proceed with caution.',\n  },\n  permission: {\n    title: 'Permission configuration',\n    operationTarget: 'Operation target',\n    moduleName: 'Module name'\n  },\n  member: {\n    title: 'Members',\n    add: 'Add Member',\n    workspace: 'workspace',\n    role: 'role',\n    delete: {\n      button: 'remove',\n      confirmTitle: 'Whether to remove the member:',\n  }\n}\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/shared.ts",
    "content": "export default {\n  title: 'Shared',\n  shared_resources: 'Shared Resources',\n  shared_tool: 'Shared Tool',\n  shared_model: 'Shared Model',\n  shared_knowledge: 'Shared Knowledge',\n  authorized_workspace: 'Authorize Workspace',\n  authorized_tip: ' ',\n  select_workspace: 'Select Workspace',\n  BLACK_LIST: 'Black List',\n  WHITE_LIST: 'White List',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/system.ts",
    "content": "export default {\n  title: 'System',\n  subTitle: 'Syetem Settings',\n  test: 'Test Connection',\n  testSuccess: 'Successful',\n  testFailed: 'Test connection failed',\n  password: 'Password',\n  defaultPassword: 'Default Password',\n  authentication: {\n    title: 'Login Authentication',\n    ldap: {\n      title: 'LDAP',\n      address: 'LDAP Address',\n      serverPlaceholder: 'Please enter LDAP address',\n      bindDN: 'Bind DN',\n      bindDNPlaceholder: 'Please enter Bind DN',\n      ou: 'User OU',\n      ouPlaceholder: 'Please enter user OU',\n      ldap_filter: 'User Filter',\n      ldap_filterPlaceholder: 'Please enter user filter',\n      ldap_mapping: 'LDAP Attribute Mapping',\n      ldap_mappingPlaceholder: 'Please enter LDAP attribute mapping',\n      enableAuthentication: 'Enable LDAP Authentication',\n    },\n    cas: {\n      title: 'CAS',\n      ldpUri: 'ldpUri',\n      ldpUriPlaceholder: 'Please enter ldpUri',\n      validateUrl: 'Validation Address',\n      validateUrlPlaceholder: 'Please enter validation address',\n      redirectUrl: 'Callback Address',\n      redirectUrlPlaceholder: 'Please enter callback address',\n      enableAuthentication: 'Enable CAS Authentication',\n    },\n    oidc: {\n      title: 'OIDC',\n      authEndpoint: 'Auth Endpoint',\n      authEndpointPlaceholder: 'Please enter auth endpoint',\n      tokenEndpoint: 'Token Endpoint',\n      tokenEndpointPlaceholder: 'Please enter token endpoint',\n      userInfoEndpoint: 'User Information Endpoint',\n      userInfoEndpointPlaceholder: 'Please enter user information endpoint',\n      clientId: 'Client ID',\n      clientIdPlaceholder: 'Please enter client ID',\n      scopePlaceholder: 'Please enter scope',\n      clientSecret: 'Client Secret',\n      clientSecretPlaceholder: 'Please enter client secret',\n      logoutEndpoint: 'Logout Endpoint',\n      logoutEndpointPlaceholder: 'Please enter logout endpoint',\n      redirectUrl: 'Redirect URL',\n      redirectUrlPlaceholder: 'Please enter redirect URL',\n      enableAuthentication: 'Enable OIDC Authentication',\n    },\n\n    oauth2: {\n      title: 'OAuth2',\n      authEndpoint: 'Auth Endpoint',\n      authEndpointPlaceholder: 'Please enter auth endpoint',\n      tokenEndpoint: 'Token Endpoint',\n      tokenEndpointPlaceholder: 'Please enter token endpoint',\n      userInfoEndpoint: 'User Information Endpoint',\n      userInfoEndpointPlaceholder: 'Please enter user information endpoint',\n      scope: 'Scope',\n      scopePlaceholder: 'Please enter scope',\n      clientId: 'Client ID',\n      clientIdPlaceholder: 'Please enter client ID',\n      clientSecret: 'Client Secret',\n      clientSecretPlaceholder: 'Please enter client secret',\n      redirectUrl: 'Redirect URL',\n      redirectUrlPlaceholder: 'Please enter redirect URL',\n      filedMapping: 'Field Mapping',\n      filedMappingPlaceholder: 'Please enter field mapping',\n      enableAuthentication: 'Enable OAuth2 Authentication',\n    },\n    saml2: {\n      title: 'SAML2',\n      ldp: 'Idp MetaData Url',\n      ldpPlaceholder: 'Please enter Idp MetaData Url',\n      enableAuthnRequests: 'Enable request signature',\n      enableAssertions: 'Enable assertion signatures',\n      privateKey: 'SP Private Key',\n      privateKeyPlaceholder: 'Please enter SP Private Key',\n      certificate: 'SP Certificate',\n      certificatePlaceholder: 'Please enter SP Certificate',\n      filedMapping: 'Field Mapping',\n      spEntityId: 'SP Entity Id',\n      spEntityIdPlaceholder: 'Please enter SP Entity Id',\n      spAcs: 'SP Ace',\n      spAcsPlaceholder: 'Please enter SP Ace',\n      filedMappingPlaceholder: 'Please enter field mapping',\n      enableAuthentication: 'Enable SAML2 Authentication',\n    },\n    scanTheQRCode: {\n      title: 'Scan the QR code',\n      wecom: 'WeCom',\n      dingtalk: 'DingTalk',\n      lark: 'Lark',\n      effective: 'Effective',\n      alreadyTurnedOn: 'Enabled',\n      notEnabled: 'Disabled',\n      validate: 'Validate',\n      validateSuccess: 'Successful',\n      validateFailed: 'Validation failed',\n      validateFailedTip: 'Please fill in all required fields and ensure the format is correct',\n      appKeyPlaceholder: 'Please enter APP key',\n      appSecretPlaceholder: 'Please enter APP secret',\n      corpIdPlaceholder: 'Please enter corp ID',\n      agentIdPlaceholder: 'Please enter agent ID',\n      callbackWarning: 'Please enter a valid URL address',\n      larkQrCode: 'Lark Scan Code Login',\n      dingtalkQrCode: 'DingTalk Scan Code Login',\n      setting: ' Setting',\n      access: 'Access',\n    },\n  },\n  email: {\n    title: 'Email Settings',\n    smtpHost: 'SMTP Host',\n    smtpHostPlaceholder: 'Please enter SMTP host',\n    smtpPort: 'SMTP Port',\n    smtpPortPlaceholder: 'Please enter SMTP port',\n    smtpUser: 'SMTP User',\n    smtpUserPlaceholder: 'Please enter SMTP user',\n    sendEmail: \"Sender's Email\",\n    sendEmailPlaceholder: \"Please enter the sender's email\",\n    smtpPassword: 'SMTP Password',\n    smtpPasswordPlaceholder: 'Please enter SMTP password',\n    enableSSL: 'Enable SSL (if the SMTP port is 465, you usually need to enable SSL)',\n    enableTLS: 'Enable TLS (if the SMTP port is 587, you usually need to enable TLS)',\n  },\n\n  resourceAuthorization: {\n    title: 'Resource Authorization',\n    member: 'Member',\n    permissionSetting: 'Permission Setting',\n    setting: {\n      management: 'Manage',\n      managementDesc: 'Can delete or modify this resource',\n      check: 'View',\n      checkDesc: 'Can only view the resource',\n      role: 'Authorize by Role',\n      roleDesc: 'Authorize users based on their roles to access this resource',\n      notAuthorized: 'No Auth',\n      configure: 'Configure Permission',\n      currentOnly: 'Current resource only',\n      includeAll: 'Include all sub-resources',\n      effectiveResource: 'Effective Resource',\n      defaultPermission: 'Default Permission',\n      defaultPermissionTip: 'Default permissions for all resources under the selected workspace',\n    },\n  },\n  resource_management: {\n    label: 'Resource Management',\n    management: 'Manage',\n  },\n  default_login: 'Default Login Method',\n  login_method: 'Login Method',\n  display_code: 'Account login verification code setting',\n  loginFailed: 'Login failed',\n  loginFailedMessage: 'Display verification code twice',\n  display_codeTip: 'When the value is -1, the verification code is not displayed',\n  time: 'Times',\n  setting: 'Login Setting',\n  failedTip: 'Next, lock the account',\n  minute: 'Minutes',\n  third_party_user_default_role: 'Default Role Assignment for Third-party Users',\n  resourceMapping: {\n    title: 'View Associated Resources',\n    sub_title: 'Associated Resources',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/tool.ts",
    "content": "export default {\n  title: 'Tool',\n  all: 'All',\n  createTool: 'Create Tool',\n  editTool: 'Edit Tool',\n  copyTool: 'Copy Tool',\n  importTool: 'Import Tool',\n  settingTool: 'Set Tool',\n  updatedVersion: 'Updated Version',\n  dataSource: {\n    title: 'Data Source',\n    createDataSource: 'Create Data Source',\n    editDataSource: 'Edit Data Source',\n    copyDataSource: 'Copy Data Source',\n    selectDataSource: 'Select Data Source',\n    requiredMessage: 'Please select data source',\n  },\n  toolStore: {\n    title: 'Tool Store',\n    createFromToolStore: 'Create from Tool Store',\n    internal: 'Built in system',\n    recommend: 'Recommended',\n    webSearch: 'Web Search',\n    databaseQuery: 'Database Query',\n    image: 'Image',\n    developer: 'Developer',\n    communication: 'Communication',\n    searchResult: '{count} search results for',\n    confirmTip: 'Are you sure to update tool: ',\n    updateStoreToolMessage: 'Updating tools may affect resources in use, so proceed with caution.',\n  },\n  mcp: {\n    title: 'MCP Service',\n    label: 'MCP Server Config',\n    placeholder: 'Please enter MCP Server config',\n    tip: 'Only supports SSE and Streamable HTTP calling methods',\n    requiredMessage: 'Please enter MCP Server Config',\n    createMcpTool: 'Create MCP',\n    editMcpTool: 'Edit MCP',\n    copyMcpTool: 'Copy MCP',\n    mcpConfig: 'MCP Service Config',\n  },\n  skill: {\n    title: 'Capabilities',\n    copySkillTool: 'Copy Skills',\n    createSkillTool: 'Create Skills',\n    editSkillTool: 'Edit Skills',\n    initParamPlaceholder: 'Parameters required to configure when enabling the skill',\n    skillFile: 'Skills File',\n    reUpload: 'Re-upload',\n  },\n  tip: {\n    saveMessage: 'Unsaved changes will be lost. Are you sure you want to exit?',\n  },\n  delete: {\n    confirmTitle: 'Confirm deletion of tool:',\n    confirmMessage:\n      'Deleting this tool will cause errors in agent that reference it when they are queried. Please proceed with caution.',\n    resourceCountMessage:\n      'This tool is associated with {count} resources, and will be unavailable after deletion. Please proceed with caution.',\n  },\n  disabled: {\n    confirmTitle: 'Confirm disable tool:',\n    confirmMessage:\n      'Disabling this tool will cause errors in agent that reference it when they are queried. Please proceed with caution.',\n  },\n\n  form: {\n    toolName: {\n      name: 'Tool Name',\n      placeholder: 'Please enter the tool name',\n      requiredMessage: 'Please enter the tool name',\n    },\n    mcpName: {\n      name: 'MCP Name',\n      placeholder: 'Please enter the MCP name',\n      requiredMessage: 'Please enter the MCP name',\n    },\n    paramName: {\n      label: 'Parameter Name',\n      placeholder: 'Please enter the parameter name',\n      requiredMessage: 'Please enter the parameter name',\n    },\n    dataType: {\n      label: 'Data Type',\n    },\n    source: {\n      label: 'Source',\n      reference: 'Reference Parameter',\n    },\n    param: {\n      paramInfo1: 'Displayed when using the tool',\n      paramInfo2: 'Not displayed when using the tool',\n      code: 'Content (Python)',\n      selectPlaceholder: 'Please select parameter',\n      inputPlaceholder: 'Please enter parameter values',\n    },\n    debug: {\n      run: 'Run',\n      output: 'Output',\n      runResult: 'Run Result',\n      runSuccess: 'Successful',\n      runFailed: 'Run Failed',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/trigger.ts",
    "content": "export default {\n  title: 'Trigger',\n  tip: 'When triggered, the system will automatically call task execution based on the input parameters settings',\n  task: 'Task',\n  nextTime: 'Next Execution Time',\n  triggerTask: 'Trigger Task',\n  taskExecution: 'Task Execution',\n  triggerSource: 'Trigger Source',\n  delete: {\n    confirmTitle: 'Confirm deleting trigger:',\n    confirmTitle2: 'triggers?',\n  },\n  triggerCycle: {\n    title: 'Trigger Cycle',\n    days: 'Days',\n    daily: 'Daily Trigger',\n    weekly: 'Weekly Trigger',\n    monthly: 'Monthly Trigger',\n    interval: 'Interval Trigger',\n    monday: 'Monday',\n    tuesday: 'Tuesday',\n    wednesday: 'Wednesday',\n    thursday: 'Thursday',\n    friday: 'Friday',\n    saturday: 'Saturday',\n    sunday: 'Sunday',\n    hours: 'Hours',\n    minutes: 'Minutes',\n    cronExpression: 'Cron expression',\n    switchCycle: 'Switch to Trigger Cycle',\n    switchCron: 'Switch to Cron expression',\n    placeholder: 'Please enter a Cron expression (e.g. 0 0 1 * *)'\n\n  },\n  type: {\n    scheduled: 'Scheduled Trigger',\n    scheduledDesc: 'Execute tasks monthly, weekly, daily, or at intervals',\n    event: 'Event Trigger',\n    eventDesc: 'Execute tasks when a certain event is sent',\n  },\n  createTrigger: 'Create Trigger',\n  editTrigger: 'Edit Trigger',\n  from: {\n    triggerName: {\n      label: 'Trigger Name',\n      placeholder: 'Please enter the trigger name',\n      requiredMessage: 'Please enter the trigger name',\n    },\n    event_url: {\n      label: 'Copy URL to your application',\n    },\n  },\n  requestParameter: 'Request Parameters',\n  triggerParam: 'Trigger Input Parameters',\n  errorMsg: 'Error Message',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/user-manage.ts",
    "content": "export default {\n  title: 'User',\n  createUser: 'Create User',\n  editUser: 'Edit User',\n  roleSetting: 'Role Setting',\n  addRole: 'Add role',\n  setting: {\n    updatePwd: 'Change Password',\n  },\n  tip: {\n    professionalMessage:\n      'The community edition supports up to 2 users. For more users, please upgrade to the professional edition.',\n    updatePwdSuccess: 'User password updated successfully',\n  },\n  delete: {\n    confirmTitle: 'Confirm deletion of user:',\n    confirmMessage:\n      'Deleting this user will also not delete all resources (agent, knowledge, models) created by this user. Please proceed with caution.',\n  },\n  disabled: {\n    confirmTitle: 'Confirm disable tool:',\n    confirmMessage:\n      'Disabling this tool will cause errors when agent that reference it are queried. Please proceed with caution.',\n  },\n  userForm: {\n    nick_name: {\n      label: 'Name',\n      placeholder: 'Please enter name',\n      lengthMessage: 'Length must be between 2 and 20 characters',\n    },\n    phone: {\n      label: 'Phone',\n      placeholder: 'Please enter phone',\n      invalidMessage: 'Invalid phone format',\n    },\n  },\n  source: {\n    label: 'Source',\n    local: 'System User',\n    localCreate: 'Local Create',\n    wecom: 'WeCom',\n    lark: 'Lark',\n    dingtalk: 'DingTalk',\n  },\n  settingRole: 'Set Role',\n  defaultPassword: 'Default Password',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/views/workspace.ts",
    "content": "export default {\n  title: 'Workspace',\n  list: 'Workspace list',\n  name: 'Workspace name',\n  toWorkspace: 'To workspace',\n  delete: {\n    confirmTitle: 'Confirm to delete workspace:',\n    confirmContent:\n      'After deletion, all members in this space will be removed. Please proceed with caution.',\n    confirmContentNotDelete:\n      'This workspace contains knowledge base resources and agent resources, and cannot be deleted.',\n  },\n  member: {\n    delete: {\n      confirmTitle: 'Confirm to remove member:',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/en-US/workflow.ts",
    "content": "export default {\n  node: 'Node',\n  nodeName: 'Node Name',\n  baseComponent: 'Basic',\n  nodeSetting: 'Node Settings',\n  workflow: 'Workflow',\n  knowledgeWorkflow: 'Knowledge Workflow',\n  info: {\n    previewVersion: 'Preview Version:',\n    saveTime: 'Last Saved:',\n  },\n  operation: {\n    toImportDoc: 'Go to Import Documents',\n    importWorkflow: 'Import Workflow',\n    exportWorkflow: 'Export Workflow',\n  },\n  setting: {\n    restoreVersion: 'Restore Previous Version\"',\n    restoreCurrentVersion: 'Restore to This Version',\n    addComponent: 'Add',\n    releaseHistory: 'Release History',\n    autoSave: 'Auto Save',\n    latestRelease: 'Latest Release',\n    copyParam: 'Copy Parameters',\n    debug: 'Run',\n    exit: 'Exit',\n    exitSave: 'Save & Exit',\n    templateCenter: 'Template Center',\n  },\n  tip: {\n    noData: 'No related results found',\n    nameMessage: 'Name cannot be empty!',\n    onlyRight: 'Connections can only be made from the right anchor',\n    notRecyclable: 'Loop connections are not allowed',\n    onlyLeft: 'Connections can only be made to the left anchor',\n    applicationNodeError: 'This agent is unavailable',\n    toolNodeError: 'This tool node is unavailable',\n    repeatedNodeError: 'A node with this name already exists',\n    cannotCopy: 'Cannot be copied',\n    copyError: 'Node already copied',\n    paramErrorMessage: 'Parameter already exists: ',\n    saveMessage: 'Current changes have not been saved. Save before exiting?',\n    searchPlaceholder: 'Please enter node name',\n  },\n  delete: {\n    confirmTitle: 'Confirm to delete this node?',\n    deleteMessage: 'This node cannot be deleted',\n  },\n  control: {\n    zoomOut: 'Zoom Out',\n    zoomIn: 'Zoom In',\n    fitView: 'Fit to Screen',\n    retract: 'Collapse All',\n    extend: 'Expand All',\n    beautify: 'Auto-Arrange',\n  },\n  variable: {\n    global: 'Global Variable',\n    chat: 'Chat Variable',\n    Referencing: 'Referenced Variable',\n    ReferencingRequired: 'Referenced variable is required',\n    ReferencingError: 'Invalid referenced variable',\n    NoReferencing: 'Referenced variable does not exist',\n    placeholder: 'Please select a variable',\n    inputPlaceholder: 'Please enter variable',\n    loop: 'Loop Variable',\n  },\n  condition: {\n    title: 'Execution Condition',\n    front: 'Precondition',\n    AND: 'All',\n    OR: 'Any',\n    text: 'After the connected node is executed, execute the current node',\n  },\n  validate: {\n    startNodeRequired: 'Start node is required',\n    startNodeOnly: 'Only one start node is allowed',\n    baseNodeRequired: 'Base information node is required',\n    baseNodeOnly: 'Only one base information node is allowed',\n    notInWorkFlowNode: 'Node not in workflow',\n    noNextNode: 'Next node does not exist',\n    nodeUnavailable: 'Node unavailable',\n    needConnect1: 'The branch of the node needs to be connected',\n    cannotEndNode: 'This node cannot be used as an end node',\n    loopNodeBreakNodeRequired: 'Wireless loop must have a Break node',\n  },\n  nodes: {\n    knowledgeWriteNode: {\n      label: 'Knowledge write',\n      text: 'Write the input paragraph list into the current knowledge base and complete vectorization processing',\n    },\n    dataSourceWebNode: {\n      label: 'Web Site',\n      text: 'Input the root URL to automatically crawl web data (single link corresponds to a single document), output a list of documents with content',\n      field_label: 'Document list',\n    },\n    dataSourceLocalNode: {\n      label: 'Local File',\n      text: 'Upload local documents, output document list (content not parsed, needs to be used with \"Document Content Extraction\" node to parse)',\n      fileList: 'File List',\n      fileFormat: {\n        label: 'Supported File Formats',\n        requiredMessage: 'Please select file formats',\n      },\n      maxFileNumber: {\n        label: 'Maximum Number of Files per Upload',\n      },\n      maxFileCountNumber: {\n        label: 'Maximum Size per File (MB)',\n      },\n    },\n    classify: {\n      aiCapability: 'AI capability',\n      businessLogic: 'Business logic',\n      other: 'Other',\n      dataProcessing: 'Data Processing',\n    },\n    startNode: {\n      label: 'Start',\n      question: 'User Question',\n      currentTime: 'Current Time',\n    },\n    baseNode: {\n      fileUpload: {\n        label: 'File Upload',\n        tooltip: 'When enabled, the Q&A page will display a file upload button.',\n      },\n      FileUploadSetting: {\n        title: 'File Upload Settings',\n        maxFiles: 'Maximum number of files per upload',\n        fileLimit: 'Maximum size per file (MB)',\n        fileUploadType: {\n          label: 'File types allowed for upload',\n          documentText: 'Requires \"Document Content Extraction\" node to parse document content',\n          imageText: 'Requires \"Image Understanding\" node to parse image content',\n          videoText: 'Requires \"Video Understanding\" node to parse video content',\n          audioText: 'Requires \"Speech-to-Text\" node to parse audio content',\n          uploadMethod: 'Upload Method',\n        },\n      },\n    },\n    KnowledgeBaseNode: {\n      DocumentSetting: 'Document Processing Setting',\n    },\n    aiChatNode: {\n      label: 'AI Chat',\n      text: 'Chat with an AI model',\n      answer: 'AI Content',\n      returnContent: {\n        label: 'Return Content',\n        tooltip: `If turned off, the content of this node will not be output to the user.\n                  If you want the user to see the output of this node, please turn on the switch.`,\n      },\n      defaultPrompt: 'Known Information',\n      think: 'Thinking Process',\n      historyMessage: 'Historical chat records',\n    },\n    searchKnowledgeNode: {\n      label: 'Knowledge Retrieval',\n      text: 'Allows you to query text content related to user questions from the Knowledge',\n      paragraph_list: 'List of retrieved segments',\n      is_hit_handling_method_list: 'List of segments that meet direct response criteria',\n      result: 'Search Result',\n      directly_return: 'Content of segments that meet direct response criteria',\n      searchParam: 'Retrieval Parameters',\n      showKnowledge: {\n        label: 'Results are displayed in the knowledge source',\n        requiredMessage: 'Please set parameters',\n      },\n      searchQuestion: {\n        label: 'Question',\n        placeholder: 'Please select a search question',\n        requiredMessage: 'Please select a search question',\n      },\n    },\n    searchDocumentNode: {\n      label: 'Document Tag Retrieval',\n      text: 'Search for documents that meet the conditions based on the document label within the specified search scope',\n      selectKnowledge: 'Search Scope',\n      searchSetting: 'Search Settings',\n      custom: 'Manual',\n      customTooltip: 'Manually set tag filtering conditions',\n      auto: 'Automatic',\n      autoTooltip: 'Automatically filter setting tag conditions based on the search question',\n      documentList: 'Document List',\n      knowledgeList: 'Knowledge Base List',\n      result: 'Search Results',\n      searchParam: 'Search Parameters',\n      select_variable: 'Select Variable',\n      valueMessage: `Value or name `,\n\n      searchQuestion: {\n        label: 'Search Question',\n        placeholder: 'Please select a search question',\n        requiredMessage: 'Please select a search question',\n      },\n    },\n    questionNode: {\n      label: 'Question Optimization',\n      text: 'Optimize and improve the current question based on historical chat records to better match knowledge segments',\n      result: 'Optimized Question Result',\n      systemDefault: `#Role\nYou are a master of problem optimization, adept at accurately inferring user intentions based on context and optimizing the questions raised by users.\n\n##Skills\n###Skill 1: Optimizing Problems\n2. Receive user input questions.\n3. Carefully analyze the meaning of the problem based on the context.\n4. Output optimized problems.\n\n##Limitations:\n-Only return the optimized problem without any additional explanation or clarification.\n-Ensure that the optimized problem accurately reflects the original problem intent and does not alter the original intention.`,\n    },\n    conditionNode: {\n      label: 'Conditional Branch',\n      text: 'Trigger different nodes based on conditions',\n      branch_name: 'Branch Name',\n      conditions: {\n        label: 'Conditions',\n        info: 'Meets the following',\n        requiredMessage: 'Please select conditions',\n      },\n      valueMessage: 'Please enter a value',\n      addCondition: 'Add Condition',\n      addBranch: 'Add Branch',\n    },\n    replyNode: {\n      label: 'Specified Reply',\n      text: 'Specify reply content, referenced variables will be converted to strings for output',\n      replyContent: 'Reply Content',\n    },\n    rerankerNode: {\n      label: 'Multi-path Recall',\n      text: 'Use a re-ranking model to refine retrieval results from multiple knowledge sources',\n      result_list: 'Re-ranked Results List',\n      result: 'Re-ranking Result',\n      rerankerContent: {\n        label: 'Re-ranking Content',\n        requiredMessage: 'Please select re-ranking content',\n      },\n      higher: 'Higher',\n      ScoreTooltip: 'The higher the Score, the stronger the relevance.',\n      max_paragraph_char_number: 'Maximum Character',\n      reranker_model: {\n        label: 'Rerank',\n        placeholder: 'Please select a rerank',\n      },\n    },\n    formNode: {\n      label: 'Form Input',\n      text: 'Collect user input during Q&A and use it in subsequent processes',\n      form_content_format1: 'Hello, please fill out the form below:',\n      form_content_format2: 'Click the [Submit] button after filling it out.',\n      form_data: 'All Form Content',\n      formContent: {\n        label: 'Form Output Content',\n        requiredMessage:\n          'Please set the output content of this node, { form } is a placeholder for the form.',\n        tooltip: 'Define the output content of this node. { form } is a placeholder for the form',\n      },\n      formAllContent: 'All Form Content',\n      formSetting: 'Form Configuration',\n    },\n    documentExtractNode: {\n      label: 'Document Content Extraction',\n      text: 'Parse input documents to output structured document content',\n      content: 'Document Content',\n    },\n    documentSplitNode: {\n      label: 'Document Splitting',\n      text: 'Split input document content according to the segmentation strategy, output a list of segmented texts',\n      paragraphList: 'List of split segments',\n      splitStrategy: {\n        label: 'Splitting Strategy',\n        placeholder: 'Please select a splitting strategy',\n        requiredMessage: 'Please select a splitting strategy',\n      },\n      chunk_length: {\n        label: 'Chunk length',\n        tooltip1: 'Core objective is to balance retrieval precision and recall efficiency',\n        tooltip2:\n          'Avoid excessively short segmentation: A single segment <50 characters may lead to semantic fragmentation, potentially failing to match query intent during retrieval due to lack of context.',\n        tooltip3:\n          'Avoid excessive segmentation: A single block exceeding 500 characters increases redundant information, reduces retrieval accuracy, and consumes more storage and computing resources.',\n      },\n      title1: 'Segment title set as the associated question of the segment',\n      title2: 'Document name set as the associated question of the segment',\n    },\n    imageUnderstandNode: {\n      label: 'Image Understanding',\n      text: 'Analyze images to identify objects, scenes, and provide answers',\n      answer: 'AI Content',\n      model: {\n        label: 'Vision Model',\n        requiredMessage: 'Please select a vision model',\n      },\n      image: {\n        label: 'Select Image',\n        requiredMessage: 'Please select an image',\n      },\n    },\n    videoUnderstandNode: {\n      label: 'Video Understanding',\n      text: 'Identify objects, scenes, and other information in videos to answer user questions',\n      answer: 'AI Response Content',\n      model: {\n        label: 'Vision Model',\n        requiredMessage: 'Please select a vision model',\n      },\n      video: {\n        label: 'Select Video',\n        requiredMessage: 'Please select a video',\n      },\n    },\n    variableAssignNode: {\n      label: 'Variable Assign',\n      text: 'Update the value of the global variable',\n      assign: 'Set Value',\n    },\n    variableAggregationNode: {\n      label: 'Variable Aggregation',\n      text: 'Aggregate variables of each group according to the aggregation strategy',\n      Strategy: 'Aggregation Strategy',\n      placeholder: 'Return the first non-null value of each group',\n      placeholder1: 'Return the set of variables for each group',\n      group: {\n        noneError: 'Name cannot be empty',\n        dupError: 'Name cannot be duplicated',\n      },\n      addGroup: 'Add Group',\n      editGroup: 'Edit Group',\n    },\n    mcpNode: {\n      label: 'MCP Call',\n      text: 'Call external MCP services to process data',\n      getToolsSuccess: 'Tools fetched successfully',\n      getTool: 'Fetch Tools',\n      toolParam: 'Tool Parameters',\n      mcpServerTip: 'Please enter MCP server configuration in JSON format',\n      mcpToolTip: 'Please select a tool',\n      configLabel: 'MCP Server Config (Only SSE/Streamable HTTP calls are supported)',\n      reference: 'Reference MCP',\n    },\n    imageGenerateNode: {\n      label: 'Image Generation',\n      text: 'Generate images based on provided text content',\n      answer: 'AI Content',\n      model: {\n        label: 'Image Generation Model',\n        requiredMessage: 'Please select an image generation model',\n      },\n      prompt: {\n        label: 'Positive Prompt',\n        tooltip: 'Describe elements and visual features you want in the generated image',\n      },\n      negative_prompt: {\n        label: 'Negative Prompt',\n        tooltip: 'Describe elements you want to exclude from the generated image',\n        placeholder:\n          'Please describe content you do not want to generate, such as color, bloody content',\n      },\n    },\n    textToVideoGenerate: {\n      label: 'Text-to-Video',\n      text: 'Generate video based on provided text content',\n      answer: 'AI Response Content',\n      model: {\n        label: 'Text-to-Video Model',\n        requiredMessage: 'Please select a text-to-video model',\n      },\n      prompt: {\n        label: 'Prompt (Positive)',\n        tooltip:\n          'Positive prompt, used to describe elements and visual features expected in the generated video',\n      },\n      negative_prompt: {\n        label: 'Prompt (Negative)',\n        tooltip:\n          \"Negative prompt, used to describe content you don't want to see in the video, which can restrict the video generation\",\n        placeholder:\n          \"Please describe video content you don't want to generate, such as: colors, bloody content\",\n      },\n    },\n    imageToVideoGenerate: {\n      label: 'Image-to-Video',\n      text: 'Generate video based on provided images',\n      answer: 'AI Response Content',\n      model: {\n        label: 'Image-to-Video Model',\n        requiredMessage: 'Please select an image-to-video model',\n      },\n      prompt: {\n        label: 'Prompt (Positive)',\n        tooltip:\n          'Positive prompt, used to describe elements and visual features expected in the generated video',\n      },\n      negative_prompt: {\n        label: 'Prompt (Negative)',\n        tooltip:\n          \"Negative prompt, used to describe content you don't want to see in the video, which can restrict the video generation\",\n        placeholder:\n          \"Please describe video content you don't want to generate, such as: colors, bloody content\",\n      },\n      first_frame: {\n        label: 'First Frame Image',\n        requiredMessage: 'Please select the first frame image',\n      },\n      last_frame: {\n        label: 'Last Frame Image',\n        requiredMessage: 'Please select the last frame image',\n      },\n    },\n    speechToTextNode: {\n      label: 'Speech2Text',\n      text: 'Convert audio to text through speech recognition model',\n      stt_model: {\n        label: 'Speech Recognition Model',\n      },\n      audio: {\n        label: 'Select Audio File',\n        placeholder: 'Please select an audio file',\n      },\n    },\n    textToSpeechNode: {\n      label: 'TTS',\n      text: 'Convert text to audio through speech synthesis model',\n      tts_model: {\n        label: 'Speech Synthesis Model',\n      },\n      content: {\n        label: 'Select Text Content',\n      },\n    },\n    toolNode: {\n      label: 'Custom Tool',\n      text: 'Execute custom scripts to achieve data processing',\n    },\n    intentNode: {\n      label: 'IntentNode',\n      text: 'Match user questions with user-defined intent classifications',\n      other: 'other',\n      error2: 'Repeated intent',\n      placeholder: 'Please choose a classification option',\n      classify: {\n        label: 'Intent classify',\n      },\n      input: {\n        label: 'Input',\n      },\n    },\n    applicationNode: {\n      label: 'Agent Node',\n    },\n    loopNode: {\n      label: 'Loop',\n      text: 'Repeat a series of tasks by setting the number of loops and logic',\n      loopType: {\n        label: 'Loop Type',\n        requiredMessage: 'Please select a loop type',\n        arrayLoop: 'Array Loop',\n        numberLoop: 'Loop for Specified Times',\n        infiniteLoop: 'Infinite Loop',\n      },\n      loopNumber: {\n        label: 'Loop Number',\n        requiredMessage: 'Please enter the number of loops',\n      },\n      loopArray: {\n        label: 'Circular Array',\n        requiredMessage: 'Circular Array is required',\n        placeholder: 'Please select a circular array',\n      },\n      loopSetting: 'Loop Settings',\n      loopDetail: 'Loop Details',\n    },\n    loopStartNode: {\n      label: 'Loop Start',\n      loopIndex: 'Index',\n      loopItem: 'Loop Element',\n    },\n    loopBodyNode: {\n      label: 'Loop Body',\n      text: 'Loop Body',\n    },\n    loopContinueNode: {\n      label: 'Continue',\n      text: 'Used to terminate the current loop and proceed to the next one.',\n      isContinue: 'Continue',\n    },\n    loopBreakNode: {\n      label: 'Break',\n      text: 'Terminate the current loop and exit the loop body',\n      isBreak: 'Break',\n    },\n    variableSplittingNode: {\n      label: 'Variable Splitting',\n      text: 'By configuring JSON Path expressions, parse and split the input JSON format variable',\n      result: 'Result',\n      splitVariables: 'Split Variables',\n      inputVariables: 'Input Variable',\n      addVariables: 'Add Variables',\n      editVariables: 'Edit Variables',\n      variableListPlaceholder: 'Please add split variables',\n      expression: {\n        label: 'Expression',\n        placeholder: 'Please enter expression',\n        tooltip: 'Please use JSON Path expressions to split variables, e.g.: $.store.book',\n      },\n    },\n    parameterExtractionNode: {\n      label: 'Parameter Extraction',\n      text: 'Use AI models to extract structured parameters',\n      extractParameters: {\n        label: 'Extract Parameters',\n        variableListPlaceholder: 'Please add extraction parameters',\n        parameterType: 'Parameter Type',\n      },\n    },\n  },\n  compare: {\n    is_null: 'Is null',\n    is_not_null: 'Is not null',\n    contain: 'Contains',\n    not_contain: 'Does not contain',\n    eq: 'Equal to',\n    not_eq: 'Not equal to',\n    ge: 'Greater than or equal to',\n    gt: 'Greater than',\n    le: 'Less than or equal to',\n    lt: 'Less than',\n    len_eq: 'Length equal to',\n    len_ge: 'Length greater than or equal to',\n    len_gt: 'Length greater than',\n    len_le: 'Length less than or equal to',\n    len_lt: 'Length less than',\n    is_true: 'Is true',\n    is_not_true: 'Is not true',\n  },\n  SystemPromptPlaceholder: 'System Prompt, can reference variables in the system, such as',\n  UserPromptPlaceholder: 'User Prompt, can reference variables in the system, such as',\n  initiator: 'Iniiator',\n  abnormalInformation: 'Abnormal Information',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/ai-chat.ts",
    "content": "export default {\n  mine: '我的',\n  logoutContent: '退出登录不会丢失任何数据，你仍可以登录此账号。',\n  confirmModification: '确认修改',\n  noHistory: '暂无历史记录',\n  createChat: '新建对话',\n  clearChat: '清空对话',\n  history: '历史记录',\n  only20history: '仅显示最近 20 条对话',\n  question_count: '条提问',\n  exportRecords: '导出聊天记录',\n  exportPDF: '导出 PDF',\n  exportImg: '导出图片',\n  preview: '预览',\n  chatId: '对话 ID',\n  chatUserId: '对话用户 ID',\n  chatUserType: '对话用户类型',\n  chatUserGroup: '对话用户组',\n  userInput: '用户输入',\n  quote: '引用',\n  download: '点击下载文件',\n  noDocument: '原文档不存在',\n  noPermissionDownload: '无权限下载',\n  passwordValidator: {\n    title: '请输入密码打开链接',\n    errorMessage1: '密码不能为空',\n    errorMessage2: '密码错误',\n  },\n  operation: {\n    play: '点击播放',\n    pause: '停止',\n    regeneration: '换个答案',\n    like: '赞同',\n    cancelLike: '取消赞同',\n    oppose: '反对',\n    cancelOppose: '取消反对',\n    continue: '继续',\n    stopChat: '停止回答',\n    startChat: '开始对话',\n  },\n  vote: {\n    likeTitle: '你觉得什么让你满意？',\n    opposeTitle: '请告诉我们不满意的原因',\n    accurate: '内容准确',\n    inaccurate: '回答不准确',\n    complete: '内容完善',\n    irrelevantAnswer: '回答不相关',\n    other: '其他',\n    placeholder: '告诉我们更多关于你的相关体验',\n  },\n  tip: {\n    error500Message: '抱歉，当前正在维护，无法提供服务，请稍后再试！',\n    errorIdentifyMessage: '无法识别用户身份',\n    errorLimitMessage: '抱歉，您的提问已达到最大限制，请明天再来吧！',\n    answerMessage: '抱歉，没有查找到相关内容，请重新描述您的问题或提供更多信息。',\n    stopAnswer: '已停止回答',\n    answerLoading: '回答中',\n    recorderTip: `<p>该功能需要使用麦克风，浏览器禁止不安全页面录音，解决方案如下：<br/>\n1、可开启 https 解决；<br/>\n2、若无 https 配置则需要修改浏览器安全配置，Chrome 设置如下：<br/>\n(1) 地址栏输入chrome://flags/#unsafely-treat-insecure-origin-as-secure；<br/>\n(2) 将 http 站点配置在文本框中，例如: http://127.0.0.1:8080。</p>`,\n    recorderError: '录音失败',\n    confirm: '我知道了',\n    requiredMessage: '请填写所有必填字段',\n    inputParamMessage1: '请在URL中填写参数',\n    inputParamMessage2: '的值',\n    prologueMessage: '抱歉，当前正在维护，无法提供服务，请稍后再试！',\n  },\n  inputPlaceholder: {\n    speaking: '说话中',\n    recorderLoading: '转文字中',\n    default: '请输入问题',\n    holdToTalk: '按住说话',\n    chatting: '对话中',\n    touchChatMessage: '松开发送，上滑取消',\n    cancelTouchChat: '松开取消发送',\n  },\n  uploadFile: {\n    label: '上传文件',\n    most: '最多',\n    limit: '个，每个文件限制',\n    fileType: '文件类型',\n    tipMessage: '请在文件上传配置中选择文件类型',\n    limitMessage1: '最多上传',\n    limitMessage2: '个文件',\n    sizeLimit: '单个文件大小不能超过',\n    sizeLimit2: '空文件不支持上传',\n    imageMessage: '请解析图片内容',\n    documentMessage: '请理解文档内容',\n    audioMessage: '请理解音频内容',\n    videoMessage: '请理解视频内容',\n    otherMessage: '请理解文件内容',\n    errorMessage: '上传失败',\n    fileMessage: '请解析文件内容',\n    fileRepeat: '文件已存在',\n    invalidUrl: '无效的 URL',\n    localUpload: '本地上传',\n    urlPlaceholder: '请输入 URL 地址，每行一个地址',\n    urlTitle: 'URL 地址',\n    urlErrorMessage: '文件类型不符合要求',\n  },\n  executionDetails: {\n    title: '执行详情',\n    createTime: '执行时间',\n    paramOutputTooltip: '每个文档仅支持预览 500 字',\n    audioFile: '语音文件',\n    searchContent: '检索内容',\n    searchResult: '检索结果',\n    conditionResult: '判断结果',\n    currentChat: '本次对话',\n    answer: 'AI 回答',\n    replyContent: '回复内容',\n    textContent: '文本内容',\n    input: '输入',\n    output: '输出',\n    rerankerContent: '重排内容',\n    rerankerResult: '重排结果',\n    paragraph: '分段',\n    noSubmit: '用户未提交',\n    errMessage: '错误日志',\n    knowedMessage: '已知信息',\n    documentSplitTip: '每个文档仅能预览前五个分段',\n    paragraphRules: '分段规则',\n    writeContent: '写入内容',\n    cancel: '取消执行',\n    errLog: '错误日志',\n    cancelExecutionTip: '确定取消所选的任务？',\n  },\n  KnowledgeSource: {\n    title: '知识来源',\n    referenceParagraph: '引用分段',\n    consume: '消耗 tokens',\n    consumeTime: '耗时',\n    noSource: '没有检索到知识来源',\n  },\n  paragraphSource: {\n    title: '知识库引用',\n    question: '用户问题',\n    optimizationQuestion: '优化后问题',\n    questionPadded: '优化后问题',\n  },\n  editTitle: '编辑标题',\n  share: '分享',\n  copyLinkText: '复制链接',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/common.ts",
    "content": "export default {\n  syncSuccess: '同步成功',\n  create: '创建',\n  createSuccess: '创建成功',\n  copy: '复制',\n  copySuccess: '复制成功',\n  copyError: '复制失败',\n  save: '保存',\n  saveSuccess: '保存成功',\n  delete: '删除',\n  deleteSuccess: '删除成功',\n  setting: '设置',\n  settingSuccess: '设置成功',\n  submit: '提交',\n  submitSuccess: '提交成功',\n  edit: '编辑',\n  editSuccess: '编辑成功',\n  modify: '修改',\n  modifySuccess: '修改成功',\n  add: '添加',\n  addSuccess: '添加成功',\n  cancel: '取消',\n  confirm: '确定',\n  close: '关闭',\n  tip: '提示',\n  refresh: '刷新',\n  search: '搜索',\n  clear: '清空',\n  upgrade: '升级',\n  createDate: '创建日期',\n  createTime: '创建时间',\n  operation: '操作',\n  character: '字符',\n  export: '导出',\n  exportSuccess: '导出成功',\n  unavailable: '（不可用）',\n  public: '公有',\n  private: '私有',\n  paramSetting: '参数设置',\n  name: '名称',\n  creator: '创建者',\n  createdIn: '创建于',\n  author: '作者',\n  debug: '调试',\n  required: '必填',\n  noData: '暂无数据',\n  result: '结果',\n  remove: '移除',\n  classify: '分类',\n  reason: '理由',\n  removeSuccess: '移除成功',\n  publish: '发布',\n  noTargetPermission: '无目标资源权限',\n  searchBar: {\n    placeholder: '按名称搜索',\n  },\n  fileUpload: {\n    document: '文档',\n    image: '图片',\n    audio: '音频',\n    video: '视频',\n    other: '其他文件',\n    addExtensions: '添加后缀名',\n    existingExtensionsTip: '文件后缀已存在',\n    localUpload: '本地文件',\n    urlUpload: 'URL 地址',\n    uploadMethodTip: '请选择上传方式',\n  },\n  status: {\n    label: '状态',\n    enable: '启用',\n    disable: '禁用',\n    enabled: '已启用',\n    disabled: '已禁用',\n    enableSuccess: '启用成功',\n    disableSuccess: '禁用成功',\n    published: '已发布',\n    unpublished: '未发布',\n    success: '成功',\n    fail: '失败',\n    all: '全部',\n    STARTED: '执行中',\n    REVOKED: '已取消',\n    REVOKE: '取消中',\n  },\n  param: {\n    outputParam: '输出参数',\n    inputParam: '输入参数',\n    initParam: '启动参数',\n    editParam: '编辑参数',\n    addParam: '添加参数',\n    exception: '异常捕获',\n  },\n  aggregationStrategy: '聚合策略',\n  inputPlaceholder: '请输入',\n  inputContent: '输入内容',\n  selectPlaceholder: '请选择',\n  title: '标题',\n  content: '内容',\n  desc: '描述',\n  descPlaceholder: '请输入描述',\n  rename: '重命名',\n  renameSuccess: '重命名成功',\n  EditAvatarDialog: {\n    customizeUpload: '自定义上传',\n    upload: '上传',\n    default: '默认Logo',\n    sizeTip: '建议尺寸 32*32，支持 JPG、PNG、GIF，大小不超过 10 MB',\n    fileSizeExceeded: '文件大小超过 10 MB',\n    uploadImagePrompt: '请上传一张图片',\n  },\n  info: '基本信息',\n  otherSetting: '其他设置',\n  username: '用户名',\n  importCreate: '导入创建',\n  detail: '详情',\n  selected: '已选',\n  notFound: {\n    title: '404',\n    NoService: '暂时无法访问服务',\n    NoPermission: '当前用户暂无权限访问，请联系管理员',\n    operate: '返回首页',\n  },\n  custom: '自定义',\n  moveTo: '转移到',\n  deleteConfirm: '是否删除',\n  expand: '展开',\n  collapse: '收起',\n  copyTitle: '副本',\n  professional: '购买专业版',\n  sync: '同步',\n  prompt: {\n    label: '提示词',\n    placeholder: '请输入提示词',\n  },\n  variable: '变量',\n  allCheck: '全选',\n  type: '类型',\n  pages: {\n    prev: '上一条',\n    next: '下一条',\n  },\n  steps: {\n    prev: '上一步',\n    next: '下一步',\n  },\n  use: '使用',\n  ExecutionRecord: {\n    title: '执行记录',\n    subTitle: '查看执行记录',\n  },\n  sourceType: '资源类型',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/components.ts",
    "content": "export default {\n  quickCreatePlaceholder: '快速创建空白文档',\n  quickCreateName: '文档名称',\n  noData: '无匹配数据',\n  loading: '加载中',\n  noMore: '到底啦！',\n  selectParagraph: {\n    title: '选择分段',\n    error: '仅执行未成功分段',\n    all: '全部分段',\n  },\n  noDesc: '暂无描述',\n  folder: {\n    addFolder: '添加文件夹',\n    addChildFolder: '添加子文件夹',\n    editFolder: '编辑文件夹',\n    folderNamePlaceholder: '请输入名称',\n    requiredMessage: '请选择文件夹',\n    deleteConfirmMessage: '文件夹下的资源会被删除，请谨慎操作。',\n    ascTime: '按创建时间升序',\n    descTime: '按创建时间降序',\n    ascName: '按名称升序',\n    descName: '按名称降序',\n    custom: '按用户拖拽排序',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/dynamics-form.ts",
    "content": "export default {\n  input_type_list: {\n    TextInput: '文本框',\n    PasswordInput: '密码框',\n    Slider: '滑块',\n    SwitchInput: '开关',\n    SingleSelect: '单选框',\n    MultiSelect: '多选框',\n    DatePicker: '日期',\n    JsonInput: 'JSON 文本框',\n    RadioCard: '选项卡',\n    RadioRow: '单行选项卡',\n    UploadInput: '文件上传',\n    TextareaInput: '多行文本框',\n    MultiRow: '单行多选卡',\n    Model: '模型',\n  },\n  default: {\n    label: '默认值',\n    placeholder: '请输入默认值',\n    requiredMessage: '为必填属性',\n    show: '显示默认值',\n  },\n  tip: {\n    requiredMessage: '不能为空',\n    jsonMessage: 'JSON 格式不正确',\n  },\n  paramForm: {\n    field: {\n      label: '参数',\n      placeholder: '请输入参数',\n      requiredMessage: '参数 为必填属性',\n      requiredMessage2: '只能输入字母数字和下划线',\n    },\n    name: {\n      label: '显示名称',\n      placeholder: '请输入显示名称',\n      requiredMessage: '显示名称 为必填属性',\n    },\n    tooltip: {\n      label: '参数提示说明',\n      placeholder: '请输入参数提示说明',\n    },\n    required: {\n      label: '是否必填',\n      requiredMessage: '是否必填 为必填属性',\n    },\n    input_type: {\n      label: '组件类型',\n      placeholder: '请选择组件类型',\n      requiredMessage: '组建类型 为必填属性',\n    },\n  },\n  DatePicker: {\n    placeholder: '选择日期',\n    year: '年',\n    month: '月',\n    date: '日期',\n    datetime: '日期时间',\n    dataType: {\n      label: '时间类型',\n      placeholder: '请选择时间类型',\n    },\n    format: {\n      label: '格式',\n      placeholder: '请选择格式',\n    },\n  },\n  Select: {\n    label: '选项值',\n    placeholder: '请输入选项值',\n  },\n  tag: {\n    label: '标签',\n    placeholder: '请输入选项标签',\n  },\n  Slider: {\n    showInput: {\n      label: '是否带输入框',\n    },\n    valueRange: {\n      label: '取值范围',\n      minRequired: '最小值必填',\n      maxRequired: '最大值必填',\n    },\n    step: {\n      label: '步长值',\n      requiredMessage1: '步长值必填',\n      requiredMessage2: '步长不能为 0',\n    },\n  },\n  TextInput: {\n    length: {\n      label: '文本长度',\n      minRequired: '最小长度必填',\n      maxRequired: '最大长度必填',\n      requiredMessage1: '长度在',\n      requiredMessage2: '到',\n      requiredMessage3: '个字符',\n      requiredMessage4: '文本长度为必填参数',\n    },\n  },\n  UploadInput: {\n    limit: {\n      label: '单次上传最多文件数',\n      required: '单次上传最多文件数必填',\n    },\n    max_file_size: {\n      label: '每个文件最大(MB)',\n      required: '每个文件最大(MB)必填',\n    },\n    accept: {\n      label: '文件类型',\n      required: '文件类型必填',\n    },\n  },\n  AssignmentMethod: {\n    label: '赋值方式',\n    ref_variables: {\n      popover: '变量的值必须符合',\n      json_format: 'JSON 格式',\n      popover_label: '标签',\n      popover_value: '值',\n      popover_default: '是否为默认值',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/index.ts",
    "content": "import zhCn from 'element-plus/es/locale/lang/zh-cn'\nimport components from './components'\nimport views from './views'\nimport theme from './theme'\nimport layout from './layout'\nimport dynamicsForm from './dynamics-form'\nimport common from './common'\nimport chat from './ai-chat'\nimport workflow from './workflow'\nexport default {\n  lang: '简体中文',\n  zhCn,\n  views,\n  theme,\n  layout,\n  dynamicsForm,\n  common,\n  chat,\n  components,\n  workflow,\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/layout.ts",
    "content": "export default {\n  github: '项目地址',\n  wiki: '用户手册',\n  forum: '论坛求助',\n  logout: '退出登录',\n  apiKey: 'API Key 管理',\n  apiServiceAddress: 'API 服务地址',\n  language: '语言',\n  isExpire: '未上传 License 或 License 已过期。',\n  crossSettings: '跨域设置',\n  about: {\n    title: '关于',\n    expiredTime: '到期时间',\n    edition: {\n      label: '版本',\n      community: '社区版',\n      professional: '专业版',\n      enterprise: '企业版',\n    },\n    version: '版本号',\n    serialNo: '序列号',\n    remark: '备注',\n    update: '更新',\n    authorize: '授权给',\n    inner_admin: '系统管理员',\n    inner_wsm: '工作空间管理员',\n    inner_user: '普通用户',\n    root: '根目录',\n    default_workspace: '默认工作空间',\n    default_user_group: '默认用户组',\n  },\n  time: {\n    daysLater: '天后过期',\n    hoursLater: '小时后过期',\n    minutesLater: '分钟后过期',\n    expired: '已过期',\n    expiringSoon: '即将到期',\n    neverExpires: '永不过期',\n    daysValid: '天有效',\n  },\n  copyright: '版权所有 © 2014-2026 杭州飞致云信息科技有限公司',\n  userManualUrl: 'https://maxkb.cn/docs/v2/',\n  forumUrl: 'https://bbs.fit2cloud.com/c/mk/11',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/theme.ts",
    "content": "export default {\n  title: '外观设置',\n  defaultSlogan: '强大易用的企业级智能体平台',\n  platformDisplayTheme: '平台显示主题',\n  customTheme: '自定义主题',\n  platformLoginSettings: '平台登录设置',\n  pagePreview: '页面预览',\n  default: '默认',\n  restoreDefaults: '恢复默认',\n  orange: '活力橙',\n  green: '松石绿',\n  purple: '神秘紫',\n  red: '胭脂红',\n  loginBackground: '登录背景图',\n  loginLogo: '登录 Logo',\n  websiteLogo: '网站 Logo',\n  replacePicture: '替换图片',\n  websiteLogoTip: '顶部网站显示的 Logo，建议尺寸 48*48，支持 JPG、PNG、GIF，大小不超过 10MB',\n  loginLogoTip: '登录页面右侧 Logo，建议尺寸 204*52，支持 JPG、PNG、GIF，大小不超过 10 MB',\n  loginBackgroundTip:\n    '左侧背景图，矢量图建议尺寸 576*900，位图建议尺寸 1152*1800；支持 JPG、PNG、GIF，大小不超过 10 MB',\n  websiteName: '网站名称',\n  websiteNamePlaceholder: '请输入网站名称',\n  websiteNameTip: '显示在网页 Tab 的平台名称',\n  websiteSlogan: '欢迎语',\n  websiteSloganPlaceholder: '请输入欢迎语',\n  websiteSloganTip: '产品 Logo 下的欢迎语',\n  logoDefaultTip: '默认为 MaxKB 登录界面，支持自定义设置',\n  defaultTip: '默认为 MaxKB 平台界面，支持自定义设置',\n  platformSetting: '平台设置',\n  showUserManual: '显示用户手册',\n  showForum: '显示论坛求助',\n  showProject: '显示项目地址',\n  urlPlaceholder: '请输入 URL 地址',\n  abandonUpdate: '放弃更新',\n  saveAndApply: '保存并应用',\n  fileMessageError: '文件大小超过 10M',\n  saveSuccess: '外观设置成功',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/application-overview.ts",
    "content": "export default {\n  title: '概览',\n  appInfo: {\n    publicAccessLink: '公开访问链接',\n    openText: '开',\n    closeText: '关',\n    demo: '演示',\n    embedInWebsite: '嵌入第三方',\n    accessControl: '访问限制',\n    displaySetting: '显示设置',\n    apiAccessCredentials: 'API 访问凭据',\n    apiKey: 'API Key',\n    refreshToken: {\n      msgConfirm1: '是否重新生成公开访问链接?',\n      msgConfirm2:\n        '重新生成公开访问链接会影响嵌入第三方脚本变更，需要将新脚本重新嵌入第三方，请谨慎操作！',\n      refreshSuccess: '刷新成功',\n    },\n\n    APIKeyDialog: {\n      saveSettings: '保存设置',\n      msgConfirm1: '是否删除 API Key',\n      msgConfirm2: '删除 API Key 后将无法恢复，请确认是否删除？',\n    },\n    EmbedDialog: {\n      fullscreenModeTitle: '全屏模式',\n      copyInstructions: '复制以下代码进行嵌入',\n      floatingModeTitle: '浮窗模式',\n      mobileModeTitle: '移动端模式',\n    },\n    LimitDialog: {\n      clientQueryLimitLabel: '每个客户端提问限制',\n      timesDays: '次/天',\n      authentication: '身份验证',\n      authenticationValue: '密码验证',\n      whitelistLabel: '白名单',\n      whitelistPlaceholder:\n        '请输入允许嵌入第三方的源地址，一行一个，如：\\nhttp://127.0.0.1:5678\\nhttps://dataease.io',\n      loginMethod: '登录方式',\n      loginMethodRequired: '请选择登录方式',\n      displayCodeRequired: '请输入失败次数',\n      toSettingChatUser: '去配置对话用户',\n      authenticationTooltip:\n        '开启登录认证后，智能体和关联的知识库均需要对话用户授权配置，否则用户无权限登录和知识库检索',\n    },\n    SettingAPIKeyDialog: {\n      allowCrossDomainLabel: '允许跨域地址',\n      crossDomainPlaceholder:\n        '请输入允许的跨域地址，开启后不输入跨域地址则不限制。\\n跨域地址一行一个，如：\\nhttp://127.0.0.1:5678 \\nhttps://dataease.io',\n    },\n  },\n  SettingDisplayDialog: {\n    showSourceLabel: '显示知识来源',\n    showExecutionDetail: '显示执行详情',\n    restoreDefault: '恢复默认',\n    customThemeColor: '自定义主题色',\n    headerTitleFontColor: '头部标题字体颜色',\n    default: '默认',\n    askUserAvatar: '提问用户头像',\n    replace: '替换',\n    imageMessage: '建议尺寸 32*32，支持 JPG、PNG、GIF，大小不超过 10 MB',\n    AIAvatar: 'AI 回复头像',\n    display: '显示',\n    floatIcon: '浮窗入口图标',\n    iconDefaultPosition: '图标默认位置',\n    iconPosition: {\n      left: '左',\n      right: '右',\n      bottom: '下',\n      top: '上',\n    },\n    draggablePosition: '可拖拽位置',\n    showHistory: '显示历史记录',\n    displayGuide: '显示引导图(浮窗模式)',\n    disclaimer: '免责声明',\n    disclaimerValue: '「以上内容均由 AI 生成，仅供参考和借鉴」',\n    chatBackground: '聊天背景',\n    chatBackgroundMessage: '支持 JPG、PNG、GIF，大小不超过 10 MB',\n  },\n  monitor: {\n    monitoringStatistics: '监控统计',\n    customRange: '自定义范围',\n    startDatePlaceholder: '开始时间',\n    endDatePlaceholder: '结束时间',\n    pastDayOptions: {\n      past7Days: '过去7天',\n      past30Days: '过去30天',\n      past90Days: '过去90天',\n      past183Days: '过去半年',\n    },\n    charts: {\n      customerTotal: '用户总数',\n      customerNew: '用户新增数',\n      queryCount: '提问次数',\n      tokensTotal: 'Tokens 总数',\n      userSatisfaction: '用户满意度',\n      approval: '赞同',\n      disapproval: '反对',\n      tokenUsage: '用户消耗 Tokens',\n      topQuestions: '用户提问次数',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/application.ts",
    "content": "export default {\n  title: '智能体',\n  createApplication: '创建简易智能体',\n  createWorkFlowApplication: '创建高级智能体',\n  importApplication: '导入智能体',\n  copyApplication: '复制智能体',\n  simple: '简易',\n  senior: '高级',\n  simpleAgent: '简易智能体',\n  AdvancedAgent: '高级智能体',\n  simplePlaceholder: '通过表单设置方式，快速搭建基础功能的智能体',\n  advancedPlaceholder: '使用低代码拖拉拽方式，灵活编排复杂逻辑、功能丰富的智能体',\n  appTest: '调试预览',\n\n  operation: {\n    addModel: '添加模型',\n    toChat: '去对话',\n  },\n  delete: {\n    confirmTitle: '是否删除智能体：',\n    confirmMessage: '删除后该智能体将不再提供服务，请谨慎操作。',\n    resourceCountMessage: '此智能体关联 {count} 个资源，删除后无法使用，请谨慎操作。',\n  },\n  tip: {\n    publishSuccess: '发布成功',\n    ExportError: '导出失败',\n    professionalMessage: '社区版最多支持 5 个智能体，如需拥有更多智能体，请升级为专业版。',\n    saveErrorMessage: '保存失败，请检查输入或稍后再试',\n    loadingErrorMessage: '加载配置失败，请检查输入或稍后再试',\n    noDocPermission: '无文档创建权限',\n    confirmUse: '确定使用',\n    overwrite: '覆盖当前工作流',\n  },\n  form: {\n    appName: {\n      placeholder: '请输入智能体名称',\n      requiredMessage: '请输入智能体名称',\n    },\n    appDescription: {\n      placeholder: '描述该智能体的应用场景及用途，如：XXX 小助手回答用户提出的 XXX 产品使用问题',\n    },\n    appType: {\n      simplePlaceholder: '适合新手创建小助手',\n      workflowPlaceholder: '适合高级用户自定义小助手的工作流',\n    },\n    appTemplate: {\n      blankApp: {\n        title: '空白创建',\n      },\n      assistantApp: {\n        title: '知识库问答助手',\n        description: '基于用户问题，检索知识库相关内容作为AI模型的参考内容',\n      },\n    },\n    aiModel: {\n      label: 'AI 模型',\n      placeholder: '请选择 AI 模型',\n    },\n    roleSettings: {\n      label: '系统提示词',\n      placeholder:\n        '系统提示词，可以引用系统中的变量：{data} 是命中知识库中的分段；{question} 是用户提出的问题。',\n      tooltip: '设定模型扮演的角色或遵循的指令',\n    },\n\n    prompt: {\n      label: '用户提示词',\n      noReferences: ' (无引用知识库)',\n      references: ' (引用知识库)',\n      placeholder:\n        '用户提示词，可以引用系统中的变量：{data} 是命中知识库中的分段；{question} 是用户提出的问题。',\n      requiredMessage: '请输入用户提示词',\n      tooltip: '用户向模型提出的问题或输入的指令',\n      noReferencesTooltip:\n        '通过调整提示词内容，可以引导大模型聊天方向，该提示词会被固定在上下文的开头。可以使用变量：{question} 是用户提出问题的占位符。',\n      referencesTooltip:\n        '通过调整提示词内容，可以引导大模型聊天方向，该提示词会被固定在上下文的开头。可以使用变量：{data} 是引用知识库中分段的占位符；{question} 是用户提出问题的占位符。',\n      defaultPrompt: `已知信息：{data}\n用户问题：{question}\n回答要求：\n- 请使用中文回答用户问题`,\n    },\n    historyRecord: {\n      label: '历史聊天记录',\n    },\n    relatedKnowledge: {\n      label: '关联知识库',\n      placeholder: '关联的知识库展示在这里',\n    },\n    multipleRoundsDialogue: '多轮对话',\n\n    prologue: '开场白',\n    defaultPrologue:\n      '您好，我是 XXX 小助手，您可以向我提出 XXX 使用问题。\\n- XXX 主要功能有什么？\\n- XXX 如何收费？\\n- 需要转人工服务',\n\n    problemOptimization: {\n      label: '问题优化',\n      tooltip: '根据历史聊天优化完善当前问题，更利于匹配知识点。',\n    },\n    voiceInput: {\n      label: '语音输入',\n      placeholder: '请选择语音识别模型',\n      requiredMessage: '请选择语音输入模型',\n      autoSend: '自动发送',\n    },\n    voicePlay: {\n      label: '语音播放',\n      placeholder: '请选择语音合成模型',\n      requiredMessage: '请选择语音播放模型',\n      autoPlay: '自动播放',\n      browser: '浏览器播放(免费)',\n      tts: 'TTS 模型',\n      listeningTest: '试听',\n    },\n    reasoningContent: {\n      label: '输出思考',\n      tooltip: '请根据模型返回的思考标签设置，标签中间的内容将会认定为思考过程',\n      start: '开始',\n      end: '结束',\n    },\n    mcp_output_enable: '输出执行过程',\n  },\n  generateDialog: {\n    label: '生成',\n    generatePrompt: '生成提示词',\n    placeholder: '请输入提示词主题',\n    title: '提示词显示在这里',\n    remake: '重新生成',\n    stop: '停止生成',\n    continue: '继续生成',\n    replace: '替换',\n    exit: '确认退出并舍弃 AI 生成的内容吗？',\n    loading: '生成中...',\n  },\n  dialog: {\n    addKnowledge: '添加关联知识库',\n    addKnowledgePlaceholder: '所选知识库必须使用相同的 Embedding 模型',\n    selectSearchMode: '检索模式',\n    vectorSearch: '向量检索',\n    vectorSearchTooltip: '向量检索是一种基于向量相似度的检索方式，适用于知识库中的大数据量场景。',\n    fullTextSearch: '全文检索',\n    fullTextSearchTooltip: '全文检索是一种基于文本相似度的检索方式，适用于知识库中的小数据量场景。',\n    hybridSearch: '混合检索',\n    hybridSearchTooltip:\n      '混合检索是一种基于向量和文本相似度的检索方式，适用于知识库中的中等数据量场景。',\n    similarityThreshold: '相似度高于',\n    similarityTooltip: '相似度越高相关性越强。',\n    topReferences: '引用分段数 TOP',\n    maxCharacters: '最多引用字符数',\n    noReferencesAction: '无引用知识库分段时',\n    continueQuestioning: '继续向 AI 模型提问',\n    provideAnswer: '指定回答内容',\n    designated_answer:\n      '你好，我是 XXX 小助手，我的知识库只包含了 XXX 产品相关知识，请重新描述您的问题。',\n    defaultPrompt1:\n      '()里面是用户问题,根据上下文回答揣测用户问题({question}) 要求: 输出一个补全问题,并且放在',\n    defaultPrompt2: '标签中',\n  },\n  applicationAccess: {\n    title: '接入第三方',\n    wecom: '企业微信应用',\n    wecomTip: '打造企业微信智能体',\n    wecomBot: '企业微信智能机器人',\n    wecomBotTip: '打造企业微信智能机器人',\n    dingtalk: '钉钉应用',\n    dingtalkTip: '打造钉钉智能体',\n    wechat: '公众号',\n    wechatTip: '打造公众号智能体',\n    lark: '飞书应用',\n    larkTip: '打造飞书智能体',\n    slack: 'Slack',\n    slackTip: '打造 Slack 智能体',\n    setting: '配置',\n    callback: '回调地址',\n    callbackTip: '请输入回调地址',\n    wecomPlatform: '企业微信后台',\n    wechatPlatform: '微信公众平台',\n    dingtalkPlatform: '钉钉开放平台',\n    larkPlatform: '飞书开放平台',\n    wecomSetting: {\n      title: '企业微信应用配置',\n      cropId: '企业 ID',\n      cropIdPlaceholder: '请输入企业 ID',\n      agentIdPlaceholder: '请输入 Agent ID',\n      secretPlaceholder: '请输入 Secret',\n      tokenPlaceholder: '请输入 Token',\n      encodingAesKeyPlaceholder: '请输入 EncodingAESKey',\n      authenticationSuccessful: '认证成功',\n      urlInfo: '-应用管理-自建-创建的应用-接收消息-设置 API 接收的 \"URL\" 中',\n    },\n    wecomBotSetting: {\n      title: '企业微信智能机器人配置',\n      urlInfo: '-管理工具-智能机器人-创建机器人-API模式创建的 \"URL\" 中',\n    },\n    dingtalkSetting: {\n      title: '钉钉应用配置',\n      clientIdPlaceholder: '请输入 Client ID',\n      clientSecretPlaceholder: '请输入 Client Secret',\n      urlInfo: '-机器人页面，设置 \"消息接收模式\" 为 HTTP模式 ，并把上面 URL 填写到\"消息接收地址\"中',\n    },\n    wechatSetting: {\n      title: '公众号应用配置',\n      appId: '开发者ID (APP ID)',\n      appIdPlaceholder: '请输入开发者 ID (APP ID)',\n      appSecret: '开发者密钥 (APP SECRET)',\n      appSecretPlaceholder: '请输入开发者密钥 (APP SECRET)',\n      token: '令牌 (TOKEN)',\n      tokenPlaceholder: '请输入令牌 (TOKEN)',\n      aesKey: '消息加解密密钥',\n      aesKeyPlaceholder: '请输入消息加解密密钥',\n      urlInfo: '-设置与开发-基本配置-服务器配置的 \"服务器地址URL\" 中',\n    },\n    larkSetting: {\n      title: '飞书应用配置',\n      appIdPlaceholder: '请输入 App ID',\n      appSecretPlaceholder: '请输入 App Secret',\n      verificationTokenPlaceholder: '请输入 Verification Token',\n      urlInfo: '-事件与回调-事件配置-配置订阅方式的 \"请求地址\" 中',\n      folderTokenPlaceholder: '请输入 Folder Token',\n    },\n    slackSetting: {\n      title: 'Slack 应用配置',\n      signingSecretPlaceholder: '请输入 Signing Secret',\n      botUserTokenPlaceholder: '请输入 Bot User Token',\n    },\n    copyUrl: '复制链接填入到',\n  },\n  hitTest: {\n    title: '命中测试',\n    text: '针对用户提问调试段落匹配情况，保障回答效果。',\n    emptyMessage1: '命中段落显示在这里',\n    emptyMessage2: '没有命中的分段',\n  },\n  publishTime: '发布时间',\n  publishStatus: '发布状态',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/chat-log.ts",
    "content": "export default {\n  title: '对话日志',\n  delete: {\n    confirmTitle: '是否删除问题：',\n    confirmMessage1: '删除问题关联的',\n    confirmMessage2: '个分段会被取消关联，请谨慎操作。'\n  },\n  buttons: {\n    clearStrategy: '清除策略',\n  },\n  table: {\n    abstract: '摘要',\n    username: '用户',\n    chat_record_count: '对话提问数',\n    user: '用户',\n    feedback: {\n      label: '用户反馈',\n      star: '赞同',\n      trample: '反对'\n    },\n    mark: '改进标注',\n    recenTimes: '最近对话时间'\n  },\n  addToKnowledge: '添加至知识库',\n  daysText: '天之前的对话记录',\n  fileDaysText: '天之前的对话上传的附件',\n  selectKnowledge: '选择知识库',\n  selectKnowledgePlaceholder: '请选择知识库',\n  saveToDocument: '保存至文档',\n  documentPlaceholder: '请选择文档',\n  editContent: '修改内容',\n  editMark: '修改标注',\n  form: {\n    content: {\n      placeholder: '请输入内容'\n    },\n    title: {\n      placeholder: '请给当前内容设置一个标题，以便管理查看'\n    }\n  },\n  online: '在线使用',\n  apiCall: 'API 调用',\n  enterpriseWeChat: '企业微信应用',\n  wechatPublicAccount: '微信公众号',\n  lark: '飞书应用',\n  dingtalk: '钉钉应用',\n  enterpriseWeChatRobot: '企业微信机器人',\n  slack: 'Slack 机器人',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/chat-user.ts",
    "content": "export default {\n  title: '对话用户',\n  syncUsers: '导入用户',\n  syncUsersTip: '仅导入新增用户',\n  setUserGroups: '设置用户组',\n  knowledgeTitleTip: '该配置需要关联的智能体开启对话用户登录认证后才会生效',\n  applicationTitleTip: '该配置需要智能体开启登录认证后生效',\n  autoAuthorization: '自动授权',\n  authorization: '授权',\n  batchDeleteUser: '是否删除选中的 {count} 个用户？',\n  settingMethod: '设置方式',\n  append: '追加',\n  group: {\n    title: '用户组',\n    requiredMessage: '请选择用户组',\n    name: '用户组名称',\n    usernameOrName: '用户名/姓名',\n    delete: {\n      confirmTitle: '是否删除用户组：',\n      confirmMessage: '删除后，该用户组下的成员将全部移除，请谨慎操作！',\n    },\n    batchDeleteMember: '是否移除选中的 {count} 个成员？',\n  },\n  syncMessage: {\n    title: '成功同步 {count} 个用户',\n    usernameExist: '以下用户名已存在：',\n    nicknameExist: '以下姓名已存在：',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/document.ts",
    "content": "export default {\n  uploadDocument: '上传文档',\n  importDocument: '导入文档',\n  syncDocument: '同步文档',\n  items: '项',\n  migrateDocument: '文档迁移到',\n\n  setting: {\n    migration: '迁移',\n    cancelGenerateQuestion: '取消生成问题',\n    cancelVectorization: '取消向量化',\n    cancelGenerate: '取消生成',\n    export: '导出',\n    download: '下载原文档',\n    replace: '替换原文档',\n  },\n  tip: {\n    saveMessage: '当前的更改尚未保存，确认退出吗?',\n    cancelSuccess: '批量取消成功',\n    sendMessage: '发送成功',\n    vectorizationSuccess: '批量向量化成功',\n    nameMessage: '文件名称不能为空！',\n    importMessage: '导入成功',\n    migrationSuccess: '迁移成功',\n    replaceSuccess: '替换成功',\n    fileLimitCountTip1: '每次最多上传',\n    fileLimitCountTip2: '个文件',\n    fileLimitSizeTip1: '每个文件不超过',\n    toImportDocConfirm: '当前知识库的工作流未发布，无法导入文档，请先发布工作流。',\n  },\n  upload: {\n    selectFile: '选择文件',\n    selectFiles: '选择文件夹',\n    uploadMessage: '拖拽文件至此上传或',\n    formats: '支持格式：',\n    requiredMessage: '请上传文件',\n    errorMessage2: '文件格式不支持',\n    errorMessage3: '文件不能为空',\n    // errorMessage4: '每次最多上传50个文件',\n    template: '模板',\n    download: '下载',\n    fileLimitSizeTip: '大小不超过',\n  },\n\n  fileType: {\n    txt: {\n      label: '文本文件',\n      tip1: '1、文件上传前，建议规范文件的分段标识',\n    },\n    table: {\n      label: '表格',\n      tip1: '1、点击下载对应模板并完善信息：',\n      tip2: '2、第一行必须是列标题，且列标题必须是有意义的术语，表中每条记录将作为一个分段',\n      tip3: '3、上传的表格文件中每个 sheet 会作为一个文档，sheet名称为文档名称',\n    },\n    QA: {\n      label: 'QA 问答对',\n      tip1: '1、点击下载对应模板并完善信息',\n      tip2: '2、上传的表格文件中每个 sheet 会作为一个文档，sheet名称为文档名称',\n    },\n    lark: {},\n  },\n  setRules: {\n    title: {\n      setting: '设置分段规则',\n      preview: '分段预览',\n    },\n    intelligent: {\n      label: '智能分段（推荐)',\n      text: '不了解如何设置分段规则推荐使用智能分段',\n    },\n    advanced: {\n      label: '高级分段',\n      text: '用户可根据文档规范自行设置分段标识符、分段长度以及清洗规则',\n    },\n    patterns: {\n      label: '分段标识',\n      tooltip: '按照所选符号先后顺序做递归分割，分割结果超出分段长度将截取至分段长度。',\n      placeholder: '请选择',\n    },\n    limit: {\n      label: '分段长度',\n    },\n    with_filter: {\n      label: '自动清洗',\n      text: '去掉重复多余符号空格、空行、制表符',\n    },\n    checkedConnect: {\n      label: '导入时添加分段标题为关联问题（适用于标题为问题的问答对）',\n    },\n  },\n  buttons: {\n    import: '开始导入',\n    preview: '生成预览',\n    continueImporting: '继续导入文档',\n  },\n  tag: {\n    label: '标签管理',\n    key: '标签',\n    value: '标签值',\n    addTag: '添加标签',\n    noTag: '无标签',\n    relate: '关联',\n    unrelate: '取消关联',\n    relatedDoc: '已关联文档',\n    unrelatedDoc: '未关联文档',\n    addValue: '添加标签值',\n    setting: '标签设置',\n    create: '创建标签',\n    createValue: '创建标签值',\n    edit: '编辑标签',\n    editValue: '编辑标签值',\n    deleteConfirm: '是否删除标签: ',\n    deleteTip: '删除后使用该标签的资源将会删除该标签，请谨慎操作!',\n    requiredMessage1: '请输入标签',\n    requiredMessage2: '请输入标签值',\n    requiredMessage3: '请输入标签或标签值',\n  },\n  table: {\n    name: '文件名称',\n    char_length: '字符数',\n    paragraph: '分段',\n    updateTime: '更新时间',\n  },\n  fileStatus: {\n    label: '文件状态',\n    EMBEDDING: '索引中',\n    PENDING: '排队中',\n    GENERATE: '生成中',\n    SYNC: '同步中',\n    finish: '完成',\n  },\n  enableStatus: {\n    label: '启用状态',\n    enable: '开启',\n    close: '关闭',\n  },\n  sync: {\n    label: '同步',\n    confirmTitle: '确认同步文档?',\n    confirmMessage1: '同步将删除已有数据重新获取新数据，请谨慎操作。',\n    confirmMessage2: '无法同步，请先去设置文档 URL地址',\n    successMessage: '同步文档成功',\n  },\n  delete: {\n    confirmTitle1: '是否批量删除',\n    confirmTitle2: '个文档?',\n    confirmMessage: '所选文档中的分段会跟随删除，请谨慎操作。',\n    successMessage: '批量删除成功',\n    confirmTitle3: '是否删除文档：',\n    confirmMessage1: '此文档下的',\n    confirmMessage2: '个分段都会被删除，请谨慎操作。',\n  },\n  form: {\n    source_url: {\n      label: '文档地址',\n      placeholder: '请输入文档地址，一行一个，地址不正确文档会导入失败。',\n      requiredMessage: '请输入文档地址',\n    },\n    selector: {\n      label: '选择器',\n      placeholder: '默认为 body，可输入 .classname/#idname/tagname',\n    },\n    hit_handling_method: {\n      label: '命中处理方式',\n      tooltip: '用户提问时，命中文档下的分段时按照设置的方式进行处理。',\n    },\n    similarity: {\n      label: '相似度高于',\n      placeholder: '直接返回分段内容',\n      requiredMessage: '请输入相似度',\n    },\n    allow_download: {\n      label: '允许在知识库来源中下载',\n    },\n  },\n  hitHandlingMethod: {\n    optimization: '模型优化',\n    directly_return: '直接回答',\n  },\n  movePosition: {\n    title: '移动位置',\n    moveUp: '上移',\n    moveDown: '下移',\n    moveTop: '头部',\n    moveBottom: '末尾',\n  },\n  generateQuestion: {\n    title: '生成问题',\n    successMessage: '生成问题成功',\n    tip1: '提示词中的 {data} 为分段内容的占位符，执行时替换为分段内容发送给 AI 模型；',\n    tip2: 'AI 模型根据分段内容生成相关问题，请将生成的问题放至',\n    tip3: '标签中，系统会自动关联标签中的问题；',\n    tip4: '生成效果依赖于所选模型和提示词，用户可自行调整至最佳效果。',\n    prompt1: `内容：{data}\\n\\n请总结上面的内容，并根据内容总结生成 5 个问题。\\n回答要求：\\n- 请只输出问题；\\n- 请将每个问题放置`,\n    prompt2: `标签中。`,\n  },\n  feishu: {\n    selectDocument: '选择文档',\n    tip1: '支持文档和表格类型，包含 TXT、Markdown、PDF、DOCX、HTML、XLS、XLSX、CSV、ZIP 格式；',\n    tip2: '导入文档前，建议规范文档的分段标识。',\n    errorMessage1: '请选择文档',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/index.ts",
    "content": "import login from './login'\nimport model from './model'\nimport knowledge from './knowledge'\nimport tool from './tool'\nimport document from './document'\nimport system from './system'\nimport userManage from './user-manage'\nimport role from './role'\nimport workspace from './workspace'\nimport application from './application'\nimport problem from './problem'\nimport applicationOverview from './application-overview'\nimport paragraph from './paragraph'\nimport chatLog from './chat-log'\nimport chatUser from './chat-user'\nimport operateLog from './operate-log'\nimport shared from './shared'\nimport trigger from './trigger'\nexport default {\n  login,\n  model,\n  knowledge,\n  tool,\n  document,\n  system,\n  userManage,\n  role,\n  workspace,\n  application,\n  problem,\n  applicationOverview,\n  paragraph,\n  chatLog,\n  chatUser,\n  operateLog,\n  shared,\n  trigger\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/knowledge.ts",
    "content": "export default {\n  title: '知识库',\n  document_count: '文档数',\n  relatedApp_count: '关联智能体',\n  setting: {\n    vectorization: '向量化',\n    sync: '同步',\n  },\n\n  tip: {\n    professionalMessage: '社区版最多支持 50 个知识库，如需拥有更多知识库，请升级为专业版。',\n    syncSuccess: '同步任务发送成功',\n    updateModeMessage: '修改知识库向量模型后，需要对知识库向量化，是否继续保存？',\n  },\n  delete: {\n    confirmTitle: '是否删除知识库：',\n    confirmMessage1: '此知识库关联',\n    confirmMessage2: '个智能体，删除后无法恢复，请谨慎操作。',\n    resourceCountMessage: '此知识库关联 {count} 个资源，删除后无法使用，请谨慎操作。',\n  },\n  knowledgeType: {\n    label: '知识库类型',\n    generalKnowledge: '通用知识库',\n    webKnowledge: 'Web 知识库',\n    larkKnowledge: '飞书知识库',\n    workflowKnowledge: '工作流知识库',\n    yuqueKnowledge: '语雀知识库',\n    generalInfo: '通过上传文件或手动录入构建知识库',\n    webInfo: '通过网站链接构建知识库',\n    larkInfo: '通过飞书文档构建知识库',\n    yuqueInfo: '通过语雀文档构建知识库',\n    createGeneralKnowledge: '创建通用知识库',\n    createWebKnowledge: '创建 Web 知识库',\n    createLarkKnowledge: '创建飞书知识库',\n    createYuqueKnowledge: '创建语雀知识库',\n    createWorkflowKnowledge: '创建工作流知识库',\n    workflowInfo: '通过自定义工作流方式构建知识库',\n  },\n  form: {\n    knowledgeName: {\n      label: '知识库名称',\n      placeholder: '请输入知识库名称',\n      requiredMessage: '请输入知识库名称',\n    },\n    knowledgeDescription: {\n      label: '知识库描述',\n      placeholder:\n        '描述知识库的内容，详尽的描述将帮助AI能深入理解该知识库的内容，能更准确的检索到内容，提高该知识库的命中率。',\n      requiredMessage: '请输入知识库描述',\n    },\n    EmbeddingModel: {\n      label: '向量模型',\n      placeholder: '请选择向量模型',\n      requiredMessage: '请选择向量模型',\n    },\n\n    source_url: {\n      label: 'Web 根地址',\n      placeholder: '请输入 Web 根地址',\n      requiredMessage: ' 请输入 Web 根地址',\n    },\n    user_id: {\n      requiredMessage: '请输入 User ID',\n    },\n    token: {\n      requiredMessage: '请输入 Token',\n    },\n    selector: {\n      label: '选择器',\n      placeholder: '默认为 body，可输入 .classname/#idname/tagname',\n    },\n    file_count_limit: {\n      label: '每次上传最多文件数',\n    },\n    file_size_limit: {\n      label: '上传的每个文档最大(MB)',\n      placeholder: '建议根据服务器配置调整，否则可能会造成服务宕机',\n    },\n    appTemplate: {\n      blank: {\n        title: '空白创建',\n      },\n      basic: {\n        title: '基础模板',\n        description: '支持本地文件、飞书文档、Web 站点数据源的基础工作流模板',\n      },\n    },\n  },\n\n  ResultSuccess: {\n    title: '知识库创建成功',\n    paragraph: '分段',\n    paragraph_count: '个分段',\n    documentList: '文档列表',\n    loading: '导入中',\n    buttons: {\n      toKnowledge: '返回知识库列表',\n      toDocument: '前往文档',\n    },\n  },\n  syncWeb: {\n    title: '同步知识库',\n    syncMethod: '同步方式',\n    replace: '替换同步',\n    replaceText: '重新获取 Web 站点文档，覆盖替换本地知识库中的文档',\n    complete: '整体同步',\n    completeText: '先删除本地知识库所有文档，重新获取 Web 站点文档',\n    tip: '注意：所有同步都会删除已有数据重新获取新数据，请谨慎操作。',\n  },\n\n  transform: {\n    button: '转换',\n    title: '转换为工作流知识库',\n    message1:\n      '您现在可以将现有知识库转换为工作流知识库 ——这是一种更开放、更灵活的知识库，通过拖拽节点的方式自主编排从不同数据源到知识库写入的全流程，满足企业个性化知识管理需求。可以使用我们工具中的数据源和工具。',\n    message2: '新的处理方式将应用到后续导入的所有文档。',\n    tip: '注意：转换后不可撤回。',\n    comfirm: '确定转换为工作流知识库？转换后无法回退，请谨慎操作。',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/login.ts",
    "content": "export default {\n  title: '账号登录',\n  loginForm: {\n    username: {\n      label: '用户名',\n      placeholder: '请输入用户名',\n      requiredMessage: '请输入用户名',\n      lengthMessage: '长度在 4 到 20 个字符',\n    },\n    password: {\n      label: '登录密码',\n      placeholder: '请输入密码',\n      requiredMessage: '请输入密码',\n      lengthMessage: '长度在 6 到 20 个字符',\n    },\n    captcha: {\n      label: '验证码',\n      placeholder: '请输入验证码',\n      requiredMessage: '请输入验证码',\n      validatorMessage: '验证码不正确',\n    },\n    new_password: {\n      label: '新密码',\n      placeholder: '请输入新密码',\n      requiredMessage: '请输入新密码',\n    },\n    re_password: {\n      label: '确认密码',\n      placeholder: '请输入确认密码',\n      requiredMessage: '请输入确认密码',\n      validatorMessage: '密码不一致',\n    },\n    email: {\n      label: '邮箱',\n      placeholder: '请输入邮箱',\n      requiredMessage: '请输入邮箱',\n      validatorEmail: '请输入有效邮箱格式！',\n    },\n  },\n  jump_tip: '即将跳转至认证源页面进行认证',\n  jump: '跳转',\n  resetPassword: '修改密码',\n  forgotPassword: '忘记密码',\n  userRegister: '用户注册',\n  buttons: {\n    login: '登录',\n    register: '注册',\n    backLogin: '返回登录',\n    checkCode: '立即验证',\n  },\n  newPassword: '新密码',\n  enterPassword: '请输入修改密码',\n  useEmail: '使用邮箱',\n  moreMethod: '更多登录方式',\n  verificationCode: {\n    placeholder: '请输入验证码',\n    getVerificationCode: '获取验证码',\n    successMessage: '如果该邮箱已注册，我们将发送邮件，请注意查收',\n    resend: '重新发送',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/model.ts",
    "content": "export default {\n  title: '模型',\n  provider: '供应商',\n  providerPlaceholder: '选择供应商',\n  addModel: '添加模型',\n  delete: {\n    confirmTitle: '是否删除：',\n    confirmMessage: '模型删除后将影响正在使用该模型的资源，请谨慎操作。',\n    resourceCountMessage: '此模型关联 {count} 个资源，删除后无法使用，请谨慎操作。',\n  },\n  tip: {\n    createSuccessMessage: '创建模型成功',\n    createErrorMessage: '基础信息填写错误',\n    errorMessage: '变量已存在: ',\n    emptyMessage1: '请先选择基础信息的模型类型和基础模型',\n    emptyMessage2: '所选模型不支持参数设置',\n    updateSuccessMessage: '修改模型成功',\n    saveSuccessMessage: '模型参数保存成功',\n    downloadError: '下载失败',\n    noModel: '模型在 Ollama 不存在',\n  },\n  modelType: {\n    allModel: '全部模型',\n    publicModel: '公有模型',\n    privateModel: '私有模型',\n    LLM: '大语言模型',\n    EMBEDDING: '向量模型',\n    RERANKER: '重排模型',\n    STT: '语音识别',\n    TTS: '语音合成',\n    IMAGE: '视觉模型',\n    TTI: '图片生成',\n    TTV: '文生视频',\n    ITV: '图生视频',\n  },\n  modelForm: {\n    title: {\n      baseInfo: '基础信息',\n      advancedInfo: '高级设置',\n      modelParams: '模型参数',\n      paramSetting: '模型参数设置',\n      apiParamPassing: '接口传参',\n    },\n    modeName: {\n      label: '模型名称',\n      placeholder: '请给基础模型设置一个名称',\n      tooltip: 'MaxKB 中自定义的模型名称',\n      requiredMessage: '模型名称不能为空',\n    },\n    permissionType: {\n      label: '权限',\n      privateDesc: '仅当前用户使用',\n      publicDesc: '所有用户都可使用',\n      requiredMessage: '权限不能为空',\n    },\n    model_type: {\n      label: '模型类型',\n      placeholder: '请选择模型类型',\n      tooltip1: '大语言模型：在智能体中与AI对话的推理模型。',\n      tooltip2: '向量模型：在知识库中对文档内容进行向量化的模型。',\n      tooltip3: '语音识别：在智能体中开启语音识别后用于语音转文字的模型。',\n      tooltip4: '语音合成：在智能体中开启语音播放后用于文字转语音的模型。',\n      tooltip5: '重排模型：在高级智能体中使用多路召回时，对候选分段进行重新排序的模型。',\n      tooltip6: '视觉模型：在高级智能体中用于图片理解的视觉模型。',\n      tooltip7: '图片生成：在高级智能体中用于图片生成的视觉模型。',\n      tooltip8: '文生视频：在高级智能体中用于文生视频的模型。',\n      tooltip9: '图生视频：在高级智能体中用于图生视频的模型。',\n      requiredMessage: '模型类型不能为空',\n    },\n    base_model: {\n      label: '基础模型',\n      tooltip: '列表中未列出的模型，直接输入模型名称，回车即可添加',\n      placeholder: '自定义输入基础模型后回车即可',\n      requiredMessage: '基础模型不能为空',\n    },\n  },\n  download: {\n    downloading: '正在下载中',\n    cancelDownload: '取消下载',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/operate-log.ts",
    "content": "export default {\n  title: '操作日志',\n  table: {\n    menu: '操作菜单',\n    detail: '操作详情',\n    user: '操作用户',\n    ip_address: 'IP 地址',\n    opt: 'API详情',\n    operateTime: '操作时间',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/paragraph.ts",
    "content": "export default {\n  title: '段落',\n  paragraph_count: '段落',\n  editParagraph: '编辑分段',\n  addParagraph: '添加分段',\n  prevAddParagraph:'上方插入分段',\n  paragraphDetail: '分段详情',\n  character_count: '个字符',\n  setting: {\n    batchSelected: '批量选择',\n    cancelSelected: '取消选择'\n  },\n  delete: {\n    confirmTitle: '是否删除段落：',\n    confirmMessage: '删除后无法恢复，请谨慎操作。'\n  },\n  relatedProblem: {\n    title: '关联问题',\n    placeholder: '请选择问题'\n  },\n  form: {\n    paragraphTitle: {\n      label: '分段标题',\n      placeholder: '请输入分段标题'\n    },\n    content: {\n      label: '分段内容',\n      placeholder: '请输入分段内容',\n      requiredMessage1: '请输入分段内容',\n      requiredMessage2: '内容最多不超过 100000 个字'\n    }\n  }\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/problem.ts",
    "content": "export default {\n  title: '问题',\n  createProblem: '创建问题',\n  detailProblem: '问题详情',\n  quickCreateProblem: '快速创建问题',\n  quickCreateName: '问题',\n  tip: {\n    placeholder: '请输入问题，支持输入多个，一行一个。',\n    errorMessage: '问题不能为空！',\n    requiredMessage: '请输入问题',\n    relatedSuccess:'批量关联分段成功'\n  },\n\n  setting: {\n    batchDelete: '批量删除',\n    cancelRelated: '取消关联'\n  },\n  table: {\n    paragraph_count: '关联分段数',\n    updateTime: '更新时间'\n  },\n  delete: {\n    confirmTitle: '是否删除问题：',\n    confirmMessage1: '删除问题关联的',\n    confirmMessage2: '个分段会被取消关联，请谨慎操作。'\n  },\n  relateParagraph: {\n    title: '关联分段',\n    selectDocument: '选择文档',\n    placeholder: '按 文档名称 搜索',\n    selectedParagraph: '已选分段',\n    count: '个'\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/role.ts",
    "content": "export default {\n  title: '角色管理',\n  internalRole: '系统内置角色',\n  customRole: '自定义角色',\n  systemAdmin: '系统管理员',\n  workspaceAdmin: '工作空间管理员',\n  user: '普通用户',\n  roleName: '角色名称',\n  inheritingRole: '继承角色',\n  delete: {\n    confirmTitle: '是否删除角色：',\n    confirmMessage: '删除后，该角色下的成员都会被移除，请谨慎操作。',\n  },\n  permission: {\n    title: '权限配置',\n    operationTarget: '操作对象',\n    moduleName: '模块名称'\n  },\n  member: {\n    title: '成员',\n    add: '添加成员',\n    workspace: '工作空间',\n    role: '角色',\n    delete: {\n      button: '移除',\n      confirmTitle: '是否删除成员：',\n    }\n  }\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/shared.ts",
    "content": "export default {\n  title: '共享',\n  shared_resources: '共享资源',\n  shared_tool: '共享工具',\n  shared_model: '共享模型',\n  shared_knowledge: '共享知识库',\n  authorized_workspace: '授权工作空间',\n  authorized_tip: '被授权的工作空间，可使用当前资源',\n  select_workspace: '选择工作空间',\n  BLACK_LIST: '黑名单',\n  WHITE_LIST: '白名单',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/system.ts",
    "content": "import role from './role'\n\nexport default {\n  title: '系统管理',\n  subTitle: '系统设置',\n  test: '测试连接',\n  testSuccess: '测试连接成功',\n  testFailed: '测试连接失败',\n  password: '密码',\n  defaultPassword: '默认密码',\n  authentication: {\n    title: '登录认证',\n    ldap: {\n      title: 'LDAP',\n      address: 'LDAP 地址',\n      serverPlaceholder: '请输入LDAP 地址',\n      bindDN: '绑定 DN',\n      bindDNPlaceholder: '请输入绑定 DN',\n      ou: '用户 OU',\n      ouPlaceholder: '请输入用户 OU',\n      ldap_filter: '用户过滤器',\n      ldap_filterPlaceholder: '请输入用户过滤器',\n      ldap_mapping: 'LDAP 属性映射',\n      ldap_mappingPlaceholder: '请输入 LDAP 属性映射',\n      enableAuthentication: '启用 LDAP 认证',\n    },\n    cas: {\n      title: 'CAS',\n      ldpUri: 'ldpUri',\n      ldpUriPlaceholder: '请输入 ldpUri',\n      validateUrl: '验证地址',\n      validateUrlPlaceholder: '请输入验证地址',\n      redirectUrl: '回调地址',\n      redirectUrlPlaceholder: '请输入回调地址',\n      enableAuthentication: '启用 CAS 认证',\n    },\n    oidc: {\n      title: 'OIDC',\n      authEndpoint: '授权端地址',\n      authEndpointPlaceholder: '请输入授权端地址',\n      tokenEndpoint: 'Token 端地址',\n      tokenEndpointPlaceholder: '请输入 Token 端地址',\n      userInfoEndpoint: '用户信息端地址',\n      userInfoEndpointPlaceholder: '请输入用户信息端地址',\n      scopePlaceholder: '请输入连接范围',\n      clientId: '客户端 ID',\n      clientIdPlaceholder: '请输入客户端 ID',\n      clientSecret: '客户端密钥',\n      clientSecretPlaceholder: '请输入客户端密钥',\n      logoutEndpoint: '注销端地址',\n      logoutEndpointPlaceholder: '请输入注销端地址',\n      redirectUrl: '回调地址',\n      redirectUrlPlaceholder: '请输入回调地址',\n      enableAuthentication: '启用 OIDC 认证',\n    },\n\n    oauth2: {\n      title: 'OAuth2',\n      authEndpoint: '授权端地址',\n      authEndpointPlaceholder: '请输入授权端地址',\n      tokenEndpoint: 'Token 端地址',\n      tokenEndpointPlaceholder: '请输入 Token 端地址',\n      userInfoEndpoint: '用户信息端地址',\n      userInfoEndpointPlaceholder: '请输入用户信息端地址',\n      scope: '连接范围',\n      scopePlaceholder: '请输入连接范围',\n      clientId: '客户端 ID',\n      clientIdPlaceholder: '请输入客户端 ID',\n      clientSecret: '客户端密钥',\n      clientSecretPlaceholder: '请输入客户端密钥',\n      redirectUrl: '回调地址',\n      redirectUrlPlaceholder: '请输入回调地址',\n      filedMapping: '字段映射',\n      filedMappingPlaceholder: '请输入字段映射',\n      enableAuthentication: '启用 OAuth2 认证',\n    },\n    saml2: {\n      title: 'SAML2',\n      ldp: 'Idp MetaData Url',\n      ldpPlaceholder: '请输入 Idp MetaData Url',\n      enableAuthnRequests: '开启请求签名',\n      enableAssertions: '开启断言签名',\n      privateKey: 'SP Private Key',\n      privateKeyPlaceholder: '请输入 SP Private Key',\n      certificate: 'SP Certificate',\n      certificatePlaceholder: '请输入 SP Certificate',\n      filedMapping: '字段映射',\n      spEntityId: 'SP Entity Id',\n      spEntityIdPlaceholder: '请输入 SP Entity Id',\n      spAcs: 'SP Ace',\n      spAcsPlaceholder: '请输入 SP Ace',\n      filedMappingPlaceholder: '请输入字段映射',\n      enableAuthentication: '启用 SAML2 认证',\n    },\n    scanTheQRCode: {\n      title: '扫码登录',\n      wecom: '企业微信',\n      dingtalk: '钉钉',\n      lark: '飞书',\n      effective: '有效',\n      alreadyTurnedOn: '已开启',\n      notEnabled: '未开启',\n      validate: '校验',\n      validateSuccess: '校验成功',\n      validateFailed: '校验失败',\n      validateFailedTip: '请填写所有必填项并确保格式正确',\n      appKeyPlaceholder: '请输入 App Key',\n      appSecretPlaceholder: '请输入 App Secret',\n      corpIdPlaceholder: '请输入 Corp Id',\n      agentIdPlaceholder: '请输入 Agent Id',\n      callbackWarning: '请输入有效的 URL 地址',\n      larkQrCode: '飞书扫码登录',\n      dingtalkQrCode: '钉钉扫码登录',\n      setting: '设置',\n      access: '接入',\n    },\n  },\n  email: {\n    title: '邮箱设置',\n    smtpHost: 'SMTP Host',\n    smtpHostPlaceholder: '请输入 SMTP Host',\n    smtpPort: 'SMTP Port',\n    smtpPortPlaceholder: '请输入 SMTP Port',\n    smtpUser: 'SMTP 账户',\n    smtpUserPlaceholder: '请输入 SMTP 账户',\n    sendEmail: '发件人邮箱',\n    sendEmailPlaceholder: '请输入发件人邮箱',\n    smtpPassword: '发件人密码',\n    smtpPasswordPlaceholder: '请输入发件人密码',\n    enableSSL: '启用 SSL（如果 SMTP 端口是 465，通常需要启用 SSL）',\n    enableTLS: '启用 TLS（如果 SMTP 端口是 587，通常需要启用 TLS）',\n  },\n  resourceAuthorization: {\n    title: '资源授权',\n    member: '成员',\n    permissionSetting: '资源权限配置',\n    setting: {\n      management: '管理',\n      managementDesc: '可对该资源进行删改操作',\n      check: '查看',\n      checkDesc: '仅能查看使用该资源',\n      role: '按用户角色',\n      roleDesc: '根据用户角色中的权限授权用户对该资源的操作权限',\n      notAuthorized: '不授权',\n      configure: '配置权限',\n      currentOnly: '仅当前资源',\n      includeAll: '包含所有子资源',\n      effectiveResource: '生效资源',\n      defaultPermission: '默认权限',\n      defaultPermissionTip: '所选工作空间下所有资源的默认权限',\n    },\n  },\n  resource_management: {\n    label: '资源管理',\n    management: '管理',\n  },\n  default_login: '默认登录方式',\n  login_method: '登录方式',\n  display_code: '账号登录验证码设置',\n  loginFailed: '登录失败',\n  loginFailedMessage: '次显示验证码',\n  failedTip: '次，锁定账号',\n  minute: '分钟',\n  display_codeTip: '值为-1时，不显示验证码',\n  time: '次',\n  setting: '登录设置',\n  third_party_user_default_role: '第三方用户默认角色分配',\n  resourceMapping: {\n    title: '查看关联资源',\n    sub_title: '关联资源',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/tool.ts",
    "content": "export default {\n  title: '工具',\n  createTool: '创建工具',\n  editTool: '编辑工具',\n\n  copyTool: '复制工具',\n  importTool: '导入工具',\n  settingTool: '设置工具',\n  updatedVersion: '更新版本',\n  dataSource: {\n    title: '数据源',\n    createDataSource: '创建数据源',\n    editDataSource: '编辑数据源',\n    copyDataSource: '复制数据源',\n    selectDataSource: '选择数据源',\n    requiredMessage: '请选择数据源',\n  },\n  toolStore: {\n    title: '工具商店',\n    createFromToolStore: '从工具商店创建',\n    internal: '系统内置',\n    recommend: '推荐',\n    webSearch: '联网搜索',\n    databaseQuery: '数据库查询',\n    image: '图像',\n    developer: '开发者',\n    communication: '通信',\n    searchResult: '的搜索结果 {count} 个',\n    confirmTip: '是否更新工具：',\n    updateStoreToolMessage: '更新工具可能会影响正在使用的资源，请谨慎操作。',\n  },\n  skill: {\n    title: '技能',\n    copySkillTool: '复制 Skills',\n    createSkillTool: '创建 Skills',\n    editSkillTool: '编辑 Skills',\n    initParamPlaceholder: '启用 Skills 时需要配置的参数',\n    skillFile: 'Skills 文件',\n    reUpload: '重新上传',\n  },\n  mcp: {\n    title: 'MCP 服务',\n    label: 'MCP Server Config',\n    placeholder: '请输入 MCP Server 配置',\n    tip: '仅支持 SSE、Streamable HTTP 调用方式',\n    requiredMessage: '请输入 MCP Server Config',\n    createMcpTool: '创建 MCP',\n    editMcpTool: '编辑 MCP',\n    copyMcpTool: '复制 MCP',\n    mcpConfig: 'MCP服务配置',\n  },\n\n  delete: {\n    confirmTitle: '是否刪除工具',\n    confirmMessage: '删除后，引用了该工具的智能体提问时会报错 ，请谨慎操作。',\n    resourceCountMessage: '此工具关联 {count} 个资源，删除后无法使用，请谨慎操作。',\n  },\n  disabled: {\n    confirmTitle: '是否禁用工具：',\n    confirmMessage: '禁用后，引用了该工具的智能体提问时会报错 ，请谨慎操作。',\n  },\n  tip: {\n    saveMessage: '当前的更改尚未保存，确认退出吗?',\n  },\n  form: {\n    toolName: {\n      name: '工具名称',\n      placeholder: '请输入工具名称',\n      requiredMessage: '请输入工具名称',\n    },\n    mcpName: {\n      name: 'MCP 名称',\n      placeholder: '请输入 MCP 名称',\n      requiredMessage: '请输入 MCP 名称',\n    },\n    skillName: {\n      placeholder: '请输入 Skills 名称',\n      requiredMessage: '请输入 Skills 名称',\n    },\n    paramName: {\n      label: '参数名',\n      placeholder: '请输入参数名',\n      requiredMessage: '请输入参数名',\n    },\n    dataType: {\n      label: '数据类型',\n    },\n    source: {\n      label: '来源',\n      reference: '引用参数',\n    },\n    param: {\n      paramInfo1: '使用工具时显示',\n      paramInfo2: '使用工具时不显示',\n      code: '工具内容（Python）',\n      selectPlaceholder: '请选择参数',\n      inputPlaceholder: '请输入参数值',\n    },\n    debug: {\n      run: '运行',\n      output: '输出',\n      runResult: '运行结果',\n      runSuccess: '运行成功',\n      runFailed: '运行失败',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/trigger.ts",
    "content": "export default {\n  title: '触发器',\n  tip: '触发时系统将会根据入参设置自动调用任务执行',\n  task: '任务',\n  nextTime: '下次执行时间',\n  triggerTask: '触发任务',\n  taskExecution: '任务执行',\n  triggerSource: '触发来源',\n  delete: {\n    confirmTitle: '是否删除触发器：',\n    confirmTitle2: '个触发器？',\n  },\n  triggerCycle: {\n    title: '触发周期',\n    days: '日',\n    daily: '每日触发',\n    weekly: '每周触发',\n    monthly: '每月触发',\n    interval: '间隔触发',\n    monday: '周一',\n    tuesday: '周二',\n    wednesday: '周三',\n    thursday: '周四',\n    friday: '周五',\n    saturday: '周六',\n    sunday: '周日',\n    hours: '小时',\n    minutes: '分钟',\n    cronExpression: 'Cron 表达式',\n    switchCycle: '切换为触发周期',\n    switchCron: '切换为Cron表达式',\n    placeholder: '请输入Cron表达式（如：0 0 1 * *）'\n  },\n  type: {\n    scheduled: '定时触发',\n    scheduledDesc: '每月、每周、每日或间隔时间执行任务',\n    event: '事件触发',\n    eventDesc: '当某个事件发送时执行任务',\n  },\n  createTrigger: '创建触发器',\n  editTrigger: '修改触发器',\n  from: {\n    triggerName: {\n      label: '触发器名称',\n      placeholder: '请输入触发器名称',\n      requiredMessage: '请输入触发器名称',\n    },\n    event_url: {\n      label: '复制 URL 到你的应用',\n    },\n  },\n  requestParameter: '请求参数',\n  triggerParam: '触发器入参',\n  errorMsg: '错误信息',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/user-manage.ts",
    "content": "export default {\n  title: '用户管理',\n  createUser: '创建用户',\n  editUser: '编辑用户',\n  roleSetting: '角色设置',\n  addRole: '添加角色',\n  setting: {\n    updatePwd: '修改用户密码',\n  },\n  tip: {\n    professionalMessage: '社区版最多支持 2 个用户，如需拥有更多用户，请升级为专业版。',\n    updatePwdSuccess: '修改用户密码成功',\n  },\n  delete: {\n    confirmTitle: '是否删除用户：',\n    confirmMessage: '删除用户，该用户创建的资源（智能体、知识库、模型）不会删除，请谨慎操作。',\n  },\n  disabled: {\n    confirmTitle: '是否禁用工具：',\n    confirmMessage: '禁用后，引用了该工具的智能体提问时会报错 ，请谨慎操作。',\n  },\n  userForm: {\n    nick_name: {\n      label: '姓名',\n      placeholder: '请输入姓名',\n      lengthMessage: '长度在 1 到 20 个字符',\n    },\n\n    phone: {\n      label: '手机号',\n      placeholder: '请输入手机号',\n      invalidMessage: '手机号格式不正确',\n    },\n  },\n  source: {\n    label: '用户来源',\n    local: '系统用户',\n    localCreate: '本地创建',\n    wecom: '企业微信',\n    lark: '飞书',\n    dingtalk: '钉钉',\n  },\n  settingRole: '设置角色',\n  defaultPassword: '默认密码',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/views/workspace.ts",
    "content": "export default {\n  title: '工作空间',\n  list: '工作空间列表',\n  name: '工作空间名称',\n  toWorkspace: '返回工作空间',\n  delete: {\n    confirmTitle: '是否删除工作空间：',\n    confirmContent: '删除后，该空间下的成员都会被移除，请谨慎操作。',\n    confirmContentNotDelete: '该工作空间下存在 知识库资源、智能体资源，无法删除。',\n  },\n  member: {\n    delete: {\n      confirmTitle: '是否移除成员：',\n    }\n  }\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-CN/workflow.ts",
    "content": "export default {\n  node: '节点',\n  nodeName: '节点名称',\n  baseComponent: '基础组件',\n  nodeSetting: '节点设置',\n  workflow: '工作流',\n  knowledgeWorkflow: '知识库工作流',\n  info: {\n    previewVersion: '预览版本：',\n    saveTime: '保存时间：',\n  },\n  operation: {\n    toImportDoc: '去导入文档',\n    importWorkflow: '导入工作流',\n    exportWorkflow: '导出工作流',\n  },\n  setting: {\n    restoreVersion: '恢复版本',\n    restoreCurrentVersion: '恢复此版本',\n    addComponent: '添加组件',\n    releaseHistory: '发布历史',\n    autoSave: '自动保存',\n    latestRelease: '最近发布',\n    copyParam: '复制参数',\n    exit: '直接退出',\n    exitSave: '保存并退出',\n    templateCenter: '模板中心',\n  },\n  tip: {\n    noData: '没有找到相关结果',\n    nameMessage: '名字不能为空！',\n    onlyRight: '只允许从右边的锚点连出',\n    notRecyclable: '不可循环连线',\n    onlyLeft: '只允许连接左边的锚点',\n    applicationNodeError: '该智能体不可用',\n    toolNodeError: '该工具不可用',\n    repeatedNodeError: '节点名称已存在！',\n    cannotCopy: '不能被复制',\n    copyError: '已复制节点',\n    paramErrorMessage: '参数已存在: ',\n    saveMessage: '当前的更改尚未保存，是否保存后退出?',\n    searchPlaceholder: '请输入节点名称'\n  },\n  delete: {\n    confirmTitle: '确定删除该节点？',\n    deleteMessage: '节点不允许删除',\n  },\n  control: {\n    zoomOut: '缩小',\n    zoomIn: '放大',\n    fitView: '适应',\n    retract: '收起全部节点',\n    extend: '展开全部节点',\n    beautify: '一键美化',\n  },\n  variable: {\n    global: '全局变量',\n    chat: '会话变量',\n    Referencing: '引用变量',\n    ReferencingRequired: '引用变量必填',\n    ReferencingError: '引用变量错误',\n    NoReferencing: '不存在的引用变量',\n    placeholder: '请选择变量',\n    inputPlaceholder: '请输入变量',\n    loop: '循环变量',\n  },\n  condition: {\n    title: '执行条件',\n    front: '前置',\n    AND: '所有',\n    OR: '任一',\n    text: '连线节点执行完，执行当前节点',\n  },\n  validate: {\n    startNodeRequired: '开始节点必填',\n    startNodeOnly: '开始节点只能有一个',\n    baseNodeRequired: '基本信息节点必填',\n    baseNodeOnly: '基本信息节点只能有一个',\n    notInWorkFlowNode: '未在流程中的节点',\n    noNextNode: '不存在的下一个节点',\n    nodeUnavailable: '节点不可用',\n    needConnect1: '节点的',\n    needConnect2: '分支需要连接',\n    cannotEndNode: '节点不能当做结束节点',\n    loopNodeBreakNodeRequired: '无限循环 必须存在 Break 节点',\n  },\n  nodes: {\n    knowledgeWriteNode: {\n      label: '知识库写入',\n      text: '将输入的分段列表写入当前知识库，并完成向量化处理',\n    },\n    dataSourceWebNode: {\n      label: 'Web 站点',\n      text: '输入根地址自动抓取 Web 数据（单链接对应单文档），输出含内容的文档列表',\n      field_label: '文档列表',\n    },\n    dataSourceLocalNode: {\n      label: '本地文件',\n      text: '上传本地文档，输出文档列表（不解析内容，需配合 “文档内容提取” 节点解析）',\n      fileList: '文件列表',\n      fileFormat: {\n        label: '支持的文件格式',\n        requiredMessage: '请选择文件格式',\n      },\n      maxFileNumber: {\n        label: '每次上传最大文件数',\n      },\n      maxFileCountNumber: {\n        label: '上传的每个文档最大(MB)',\n      },\n    },\n    classify: {\n      aiCapability: 'AI 能力',\n      businessLogic: '业务逻辑',\n      other: '其他',\n      dataProcessing: '数据处理',\n    },\n    startNode: {\n      label: '开始',\n      question: '用户问题',\n      currentTime: '当前时间',\n    },\n    baseNode: {\n      fileUpload: {\n        label: '文件上传',\n        tooltip: '开启后，问答页面会显示上传文件的按钮。',\n      },\n      FileUploadSetting: {\n        title: '文件上传设置',\n        maxFiles: '单次上传最多文件数',\n        fileLimit: '每个文件最大（MB）',\n        fileUploadType: {\n          label: '上传的文件类型',\n          documentText: '需要使用“文档内容提取”节点解析文档内容',\n          imageText: '需要使用“图片理解”节点解析图片内容',\n          audioText: '需要使用“语音转文本”节点解析音频内容',\n          videoText: '需要使用“视频理解”节点解析视频内容',\n          otherText: '需要自行解析该类型文件',\n          uploadMethod: '上传方式',\n        },\n      },\n    },\n    KnowledgeBaseNode: {\n      DocumentSetting: '文档处理设置',\n    },\n    aiChatNode: {\n      label: 'AI 对话',\n      text: '与 AI 大模型进行对话',\n      answer: 'AI 回答内容',\n      returnContent: {\n        label: '返回内容',\n        tooltip: `关闭后该节点的内容则不输出给用户。\n                  如果你想让用户看到该节点的输出内容，请打开开关。`,\n      },\n      defaultPrompt: '已知信息',\n      think: '思考过程',\n      historyMessage: '历史聊天记录',\n    },\n    searchKnowledgeNode: {\n      label: '知识库检索',\n      text: '关联知识库，查找与问题相关的分段',\n      paragraph_list: '检索结果的分段列表',\n      is_hit_handling_method_list: '满足直接回答的分段列表',\n      result: '检索结果',\n      directly_return: '满足直接回答的分段内容',\n      searchParam: '检索参数',\n      showKnowledge: {\n        label: '结果显示在知识来源中',\n        requiredMessage: '请设置参数',\n      },\n      searchQuestion: {\n        label: '检索问题',\n        placeholder: '请选择检索问题',\n        requiredMessage: '请选择检索问题',\n      },\n    },\n    searchDocumentNode: {\n      label: '文档标签检索',\n      text: '从设定的检索范围中，根据文档标签检索出满足条件的文档',\n      selectKnowledge: '检索范围',\n      searchSetting: '检索设置',\n      custom: '手动',\n      customTooltip: '手动设置标签过滤条件',\n      auto: '自动',\n      autoTooltip: '根据检索问题自动匹配文档标签',\n      documentList: '文档列表',\n      knowledgeList: '知识库列表',\n      result: '检索结果',\n      searchParam: '检索参数',\n      select_variable: '选择变量',\n      valueMessage: `值或变量`,\n      searchQuestion: {\n        label: '检索问题',\n        placeholder: '请选择检索问题',\n        requiredMessage: '请选择检索问题',\n      },\n    },\n    questionNode: {\n      label: '问题优化',\n      text: '根据历史聊天记录优化完善当前问题，更利于匹配知识库分段',\n      result: '问题优化结果',\n      systemDefault: `# 角色\n你是一位问题优化大师，擅长根据上下文精准揣测用户意图，并对用户提出的问题进行优化。\n\n## 技能\n### 技能 1: 优化问题\n2. 接收用户输入的问题。\n3. 依据上下文仔细分析问题含义。\n4. 输出优化后的问题。\n\n## 限制:\n- 仅返回优化后的问题，不进行额外解释或说明。\n- 确保优化后的问题准确反映原始问题意图，不得改变原意。`,\n    },\n    conditionNode: {\n      label: '判断器',\n      text: '根据不同条件执行不同的节点',\n      branch_name: '分支名称',\n      conditions: {\n        label: '条件',\n        info: '符合以下',\n        requiredMessage: '请选择条件',\n      },\n      valueMessage: '请输入值',\n      addCondition: '添加条件',\n      addBranch: '添加分支',\n    },\n    replyNode: {\n      label: '指定回复',\n      text: '指定回复内容，引用变量会转换为字符串进行输出',\n      replyContent: '回复内容',\n    },\n    rerankerNode: {\n      label: '多路召回',\n      text: '使用重排模型对多个知识库的检索结果进行二次召回',\n      result_list: '重排结果列表',\n      result: '重排结果',\n      rerankerContent: {\n        label: '重排内容',\n        requiredMessage: '请选择重排内容',\n      },\n      higher: '高于',\n      ScoreTooltip: 'Score 越高相关性越强。',\n      max_paragraph_char_number: '最大引用字符数',\n      reranker_model: {\n        label: '重排模型',\n        placeholder: '请选择重排模型',\n      },\n    },\n    formNode: {\n      label: '表单收集',\n      text: '在问答过程中用于收集用户信息，可以根据收集到表单数据执行后续流程',\n      form_content_format1: '你好，请先填写下面表单内容：',\n      form_content_format2: '填写后请点击【提交】按钮进行提交。',\n      form_data: '表单全部内容',\n      formContent: {\n        label: '表单输出内容',\n        requiredMessage: '请表单输出内容',\n        tooltip: '设置执行该节点输出的内容，{ form } 为表单的占位符。',\n      },\n      formAllContent: '表单全部内容',\n      formSetting: '表单配置',\n    },\n    documentExtractNode: {\n      label: '文档内容提取',\n      text: '解析输入文档，输出结构化文档内容',\n      content: '文档内容',\n    },\n    documentSplitNode: {\n      label: '文档分段',\n      text: '按分段策略拆分输入文档内容，输出分段文本列表',\n      paragraphList: '分段列表',\n      splitStrategy: {\n        label: '分段策略',\n        placeholder: '请选择分段策略',\n        requiredMessage: '请选择分段策略',\n      },\n      chunk_length: {\n        label: '子分块长度',\n        tooltip1: '核心目标是平衡检索精度与召回效率',\n        tooltip2:\n          '避免过短拆分：单块＜50 字易导致语义碎片化，检索时可能因缺少上下文无法匹配查询意图',\n        tooltip3:\n          '避免过长拆分：单块＞500 字会增加冗余信息，降低检索精准度，且占用更多存储和计算资源',\n      },\n      title1: '分段标题设置为分段的关联问题',\n      title2: '文档名称设置为分段的关联问题',\n    },\n    imageUnderstandNode: {\n      label: '图片理解',\n      text: '识别出图片中的对象、场景等信息回答用户问题',\n      answer: 'AI 回答内容',\n      model: {\n        label: '视觉模型',\n        requiredMessage: '请选择视觉模型',\n      },\n      image: {\n        label: '选择图片',\n        requiredMessage: '请选择图片',\n      },\n    },\n    variableAggregationNode: {\n      label: '变量聚合',\n      text: '按聚合策略聚合每组的变量',\n      Strategy: '聚合策略',\n      placeholder: '返回每组的第一个非空值',\n      placeholder1: '返回每组变量的集合',\n      group: {\n        noneError: '名称不能为空',\n        dupError: '名称不能重复',\n      },\n      addGroup: '添加分组',\n      editGroup: '编辑分组',\n    },\n    variableAssignNode: {\n      label: '变量赋值',\n      text: '更新全局变量的值',\n      assign: '赋值',\n    },\n    mcpNode: {\n      label: 'MCP 调用',\n      text: '通过 SSE/Streamable HTTP 方式执行MCP服务中的工具',\n      getToolsSuccess: '获取工具成功',\n      getTool: '获取工具',\n      toolParam: '工具参数',\n      mcpServerTip: '请输入 JSON 格式的MCP服务器配置',\n      mcpToolTip: '请选择工具',\n      configLabel: 'MCP Server Config (仅支持 SSE/Streamable HTTP 调用方式)',\n      reference: '引用MCP',\n    },\n    imageGenerateNode: {\n      label: '图片生成',\n      text: '根据提供的文本内容生成图片',\n      answer: 'AI 回答内容',\n      model: {\n        label: '图片生成模型',\n        requiredMessage: '请选择图片生成模型',\n      },\n      prompt: {\n        label: '提示词(正向)',\n        tooltip: '正向提示词，用来描述生成图像中期望包含的元素和视觉特点',\n      },\n      negative_prompt: {\n        label: '提示词(负向)',\n        tooltip: '反向提示词，用来描述不希望在画面中看到的内容，可以对画面进行限制。',\n        placeholder: '请描述不想生成的图片内容，比如：颜色、血腥内容',\n      },\n    },\n    textToVideoGenerate: {\n      label: '文生视频',\n      text: '根据提供的文本内容生成视频',\n      answer: 'AI 回答内容',\n      model: {\n        label: '文生视频模型',\n        requiredMessage: '请选择文生视频模型',\n      },\n      prompt: {\n        label: '提示词(正向)',\n        tooltip: '正向提示词，用来描述生成视频中期望包含的元素和视觉特点',\n      },\n      negative_prompt: {\n        label: '提示词(负向)',\n        tooltip: '反向提示词，用来描述不希望在视频中看到的内容，可以对视频进行限制。',\n        placeholder: '请描述不想生成的视频内容，比如：颜色、血腥内容',\n      },\n    },\n    videoUnderstandNode: {\n      label: '视频理解',\n      text: '识别出视频中的对象、场景等信息回答用户问题',\n      answer: 'AI 回答内容',\n      model: {\n        label: '视觉模型',\n        requiredMessage: '请选择视觉模型',\n      },\n      video: {\n        label: '选择视频',\n        requiredMessage: '请选择视频',\n      },\n    },\n    imageToVideoGenerate: {\n      label: '图生视频',\n      text: '根据提供的图片生成视频',\n      answer: 'AI 回答内容',\n      model: {\n        label: '图生视频模型',\n        requiredMessage: '请选择图生视频模型',\n      },\n      prompt: {\n        label: '提示词(正向)',\n        tooltip: '正向提示词，用来描述生成视频中期望包含的元素和视觉特点',\n      },\n      negative_prompt: {\n        label: '提示词(负向)',\n        tooltip: '反向提示词，用来描述不希望在视频中看到的内容，可以对视频进行限制。',\n        placeholder: '请描述不想生成的视频内容，比如：颜色、血腥内容',\n      },\n      first_frame: {\n        label: '首帧图片',\n        requiredMessage: '请选择首帧图片',\n      },\n      last_frame: {\n        label: '尾帧图片',\n        requiredMessage: '请选择尾帧图片',\n      },\n    },\n    speechToTextNode: {\n      label: '语音转文本',\n      text: '将音频通过语音识别模型转换为文本',\n      stt_model: {\n        label: '语音识别模型',\n      },\n      audio: {\n        label: '选择语音文件',\n        placeholder: '请选择语音文件',\n      },\n    },\n    textToSpeechNode: {\n      label: '文本转语音',\n      text: '将文本通过语音合成模型转换为音频',\n      tts_model: {\n        label: '语音合成模型',\n      },\n      content: {\n        label: '选择文本内容',\n      },\n    },\n    toolNode: {\n      label: '自定义工具',\n      text: '通过执行自定义脚本，实现数据处理',\n    },\n    intentNode: {\n      label: '意图识别',\n      text: '将用户问题与用户预设的意图分类进行匹配',\n      other: '其他',\n      error2: '意图重复',\n      placeholder: '请选择分类项',\n      classify: {\n        label: '意图分类',\n      },\n      input: {\n        label: '输入',\n      },\n    },\n    applicationNode: {\n      label: '智能体节点',\n    },\n    loopNode: {\n      label: '循环节点',\n      text: '通过设置循环次数和逻辑，重复执行一系列任务',\n      loopType: {\n        label: '循环类型',\n        requiredMessage: '请选择循环类型',\n        arrayLoop: '数组循环',\n        numberLoop: '指定次数循环',\n        infiniteLoop: '无限循环',\n      },\n      loopNumber: {\n        label: '循环次数',\n        requiredMessage: '循环次数必填',\n      },\n      loopArray: {\n        label: '循环数组',\n        requiredMessage: '循环数组必填',\n        placeholder: '请选择循环数组',\n      },\n      loopSetting: '循环设置',\n      loopDetail: '循环详情',\n    },\n    loopStartNode: {\n      label: '循环开始',\n      loopIndex: '下标',\n      loopItem: '循环元素',\n    },\n    loopBodyNode: {\n      label: '循环体',\n      text: '循环体',\n    },\n    loopContinueNode: {\n      label: 'Continue',\n      text: '用于终止当前循环，执行下次循环',\n      isContinue: 'Continue',\n    },\n    loopBreakNode: {\n      label: 'Break',\n      text: '终止当前循环，跳出循环体',\n      isBreak: 'Break',\n    },\n    variableSplittingNode: {\n      label: '变量拆分',\n      text: '通过配置JSON Path 表达式，对输入的 JSON 格式变量进行解析和拆分',\n      splitVariables: '拆分变量',\n      inputVariables: '输入变量',\n      addVariables: '添加变量',\n      editVariables: '编辑变量',\n      variableListPlaceholder: '请添加拆分变量',\n      expression: {\n        label: '表达式',\n        placeholder: '请输入表达式',\n        tooltip: '请使用JSON Path 表达式拆分变量，例如：$.store.book',\n      },\n    },\n    parameterExtractionNode: {\n      label: '参数提取',\n      text: '利用 AI 模型提取结构化参数',\n      extractParameters: {\n        label: '提取参数',\n        variableListPlaceholder: '请添加提取参数',\n        parameterType: '参数类型',\n      },\n    },\n  },\n  compare: {\n    is_null: '为空',\n    is_not_null: '不为空',\n    contain: '包含',\n    not_contain: '不包含',\n    eq: '等于',\n    not_eq: '不等于',\n    ge: '大于等于',\n    gt: '大于',\n    le: '小于等于',\n    lt: '小于',\n    len_eq: '长度等于',\n    len_ge: '长度大于等于',\n    len_gt: '长度大于',\n    len_le: '长度小于等于',\n    len_lt: '长度小于',\n    is_true: '为真',\n    is_not_true: '不为真',\n  },\n  SystemPromptPlaceholder: '系统提示词，可以引用系统中的变量：如',\n  UserPromptPlaceholder: '用户提示词，可以引用系统中的变量：如',\n  initiator: '发起人',\n  abnormalInformation: '异常信息'\n\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/ai-chat.ts",
    "content": "export default {\n  mine: '我的',\n  logoutContent: '退出登入不會遺失任何資料，您仍可登入此帳號。',\n  confirmModification: '確認修改',\n  noHistory: '暫無歷史記錄',\n  createChat: '新建對話',\n  clearChat: '清空對話',\n  history: '歷史記錄',\n  only20history: '僅顯示最近 20 條對話',\n  question_count: '條提問',\n  exportRecords: '導出聊天記錄',\n  exportPDF: '匯出PDF',\n  exportImg: '匯出圖片',\n  preview: '預覽',\n  chatId: '對話 ID',\n  chatUserId: '對話用戶 ID',\n  chatUserType: '對話用戶類型',\n  chatUserGroup: '聊天使用者群組',\n  userInput: '用戶輸入',\n  quote: '引用',\n  download: '點擊下載文件',\n  noDocument: '原文檔不存在',\n  noPermissionDownload: '無許可權下載',\n  passwordValidator: {\n    title: '請輸入密碼打開連結',\n    errorMessage1: '密碼不能為空',\n    errorMessage2: '密碼錯誤',\n  },\n  operation: {\n    play: '點擊播放',\n    pause: '停止',\n    regeneration: '換個答案',\n    like: '贊同',\n    cancelLike: '取消贊同',\n    oppose: '反對',\n    cancelOppose: '取消反對',\n    continue: '繼續',\n    stopChat: '停止回答',\n    startChat: '開始回答',\n  },\n  vote: {\n    likeTitle: '你覺得什麼讓你滿意？',\n    opposeTitle: '請告訴我們不滿意的原因',\n    accurate: '內容準確',\n    inaccurate: '回答不準確',\n    complete: '內容完善',\n    irrelevantAnswer: '回答不相關',\n    other: '其他',\n    placeholder: '告訴我們更多關於你的相關體驗',\n  },\n  tip: {\n    error500Message: '抱歉，當前正在維護，無法提供服務，請稍後再試！',\n    errorIdentifyMessage: '無法識別用戶身份',\n    errorLimitMessage: '抱歉，您的提問已達最大限制，請明天再來吧！',\n    answerMessage: '抱歉，沒有查找到相關內容，請重新描述您的問題或提供更多資訊。',\n    stopAnswer: '已停止回答',\n    answerLoading: '回答中',\n    recorderTip: `<p>該功能需要使用麥克風，瀏覽器禁止不安全頁面錄音，解決方案如下：<br/>\n1、可開啟 https 解決；<br/>\n2、若無 https 配置則需要修改瀏覽器安全配置，Chrome 設定如下：<br/>\n(1) 地址欄輸入 chrome://flags/#unsafely-treat-insecure-origin-as-secure；<br/>\n(2) 將 http 站點配置在文字框中，例如: http://127.0.0.1:8080。</p>`,\n    recorderError: '錄音失敗',\n    confirm: '我知道了',\n    requiredMessage: '請填寫所有必填欄位',\n    inputParamMessage1: '請在 URL 中填寫參數',\n    inputParamMessage2: '的值',\n    prologueMessage: '抱歉，當前正在維護，無法提供服務，請稍後再試！',\n  },\n  inputPlaceholder: {\n    speaking: '說話中',\n    recorderLoading: '轉文字中',\n    default: '請輸入問題',\n    holdToTalk: '按住說話',\n    chatting: '對話中',\n    touchChatMessage: '松開發送，上滑取消',\n    cancelTouchChat: '松開取消發送',\n  },\n  uploadFile: {\n    label: '上傳文件',\n    most: '最多',\n    limit: '個，每個文件限制',\n    fileType: '文件類型',\n    tipMessage: '請在文件上傳配置中選擇文件類型',\n    limitMessage1: '最多上傳',\n    limitMessage2: '個文件',\n    sizeLimit: '單個文件大小不能超過',\n    sizeLimit2: '空文件不支持上傳',\n    imageMessage: '請解析圖片內容',\n    documentMessage: '請理解檔案內容',\n    audioMessage: '請理解音訊內容',\n    videoMessage: '請理解視頻內容',\n    otherMessage: '請理解檔案內容',\n    fileMessage: '請解析文件內容',\n    errorMessage: '上傳失敗',\n    fileRepeat: '文件已存在',\n    invalidUrl: '无效的 URL',\n    localUpload: '本地上傳',\n    urlPlaceholder: '請輸入 URL 地址，每行一個地址',\n    urlTitle: 'URL 地址',\n    urlErrorMessage: '文件类型不符合要求',\n  },\n  executionDetails: {\n    title: '執行詳細',\n    createTime: '執行時間',\n    paramOutputTooltip: '每個文件僅支持預覽 500 字',\n    audioFile: '語音文件',\n    searchContent: '檢索內容',\n    searchResult: '檢索結果',\n    conditionResult: '判斷結果',\n    currentChat: '本次對話',\n    answer: 'AI 回答',\n    replyContent: '回覆內容',\n    textContent: '文本內容',\n    input: '輸入',\n    output: '輸出',\n    rerankerContent: '重排內容',\n    rerankerResult: '重排結果',\n    paragraph: '段落',\n    noSubmit: '用戶未提交',\n    errMessage: '錯誤日誌',\n    knowedMessage: '已知資訊',\n    documentSplitTip: '每個文件僅能預覽前五個段落',\n    paragraphRules: '分段規則',\n    writeContent: '寫入內容',\n    cancel: '取消執行',\n    errLog: '錯誤日誌',\n    cancelExecutionTip: '確定取消所選的任務？',\n  },\n  KnowledgeSource: {\n    title: '知識來源',\n    referenceParagraph: '引用段落',\n    consume: '消耗 tokens',\n    consumeTime: '耗時',\n    noSource: '沒有检索到知識來源',\n  },\n  paragraphSource: {\n    title: '知識庫引用',\n    question: '用戶問題',\n    optimizationQuestion: '優化後問題',\n    questionPadded: '優化後問題',\n  },\n  editTitle: '編輯標題',\n  share: '分享',\n  copyLinkText: '複製連結',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/common.ts",
    "content": "export default {\n  syncSuccess: '同步完成',\n  create: '創建',\n  createSuccess: '創建成功',\n  copy: '複製',\n  copySuccess: '複製成功',\n  copyError: '複製失敗',\n  save: '儲存',\n  saveSuccess: '儲存成功',\n  delete: '刪除',\n  deleteSuccess: '刪除成功',\n  setting: '設定',\n  settingSuccess: '設定成功',\n  submit: '提交',\n  submitSuccess: '提交成功',\n  edit: '編輯',\n  editSuccess: '編輯成功',\n  modify: '修改',\n  modifySuccess: '修改成功',\n  cancel: '取消',\n  confirm: '確認',\n  close: '關閉',\n  tip: '提示',\n  add: '新增',\n  refresh: '重新整理',\n  search: '搜尋',\n  clear: '清除',\n  upgrade: '升級',\n  createDate: '創建日期',\n  createTime: '創建時間',\n  operation: '操作',\n  character: '字符',\n  export: '匯出',\n  exportSuccess: '匯出成功',\n  unavailable: '（不可用）',\n  public: '公有',\n  private: '私有',\n  paramSetting: '參數設定',\n  name: '名稱',\n  creator: '建立者',\n  createdIn: '創建於',\n  author: '作者',\n  debug: '調試',\n  required: '必填',\n  noData: '暂无数据',\n  result: '結果',\n  remove: '移除',\n  classify: '分類',\n  reason: '理由',\n  removeSuccess: '移除成功',\n  publish: '發佈',\n  noTargetPermission: '無目標資源權限',\n  searchBar: {\n    placeholder: '按名稱搜尋',\n  },\n  fileUpload: {\n    document: '文檔',\n    image: '圖片',\n    audio: '音頻',\n    video: '視頻',\n    other: '其他文件',\n    addExtensions: '添加後綴名',\n    existingExtensionsTip: '文件後綴已存在',\n    localUpload: '本地文件',\n    urlUpload: 'URL 地址',\n    uploadMethodTip: '請選擇上傳方式',\n  },\n  status: {\n    label: '狀態',\n    enable: '啟用',\n    disable: '停用',\n    enabled: '已啟用',\n    disabled: '已停用',\n    enableSuccess: '啟用成功',\n    disableSuccess: '停用成功',\n    published: '已發佈',\n    unpublished: '未發佈',\n    success: '成功',\n    fail: '失敗',\n    all: '全部',\n    STARTED: '執行中',\n    REVOKED: '已取消',\n    REVOKE: '取消中',\n  },\n  param: {\n    outputParam: '輸出參數',\n    inputParam: '輸入參數',\n    initParam: '啟動參數',\n    editParam: '編輯參數',\n    addParam: '新增參數',\n    exception: '异常捕獲',\n  },\n  aggregationStrategy: '聚合策略',\n  inputPlaceholder: '請輸入',\n  inputContent: '輸入內容',\n  selectPlaceholder: '請選擇',\n  title: '標題',\n  content: '内容',\n  desc: '描述',\n  descPlaceholder: '請輸入描述',\n  rename: '重命名',\n  renameSuccess: '重命名成功',\n  EditAvatarDialog: {\n    customizeUpload: '自訂上傳',\n    upload: '上傳',\n    default: '預設 logo',\n    sizeTip: '建議尺寸 32*32，支援 JPG、PNG、GIF，大小不超過 10 MB',\n    fileSizeExceeded: '檔案大小超過 10 MB',\n    uploadImagePrompt: '請上傳一張圖片',\n  },\n  info: '使用者資訊',\n  otherSetting: '其他設定',\n  username: '用戶名',\n  importCreate: '導入創建',\n  detail: '详情',\n  selected: '已選',\n  notFound: {\n    title: '404',\n    NoService: '暫時無法訪問服務',\n    NoPermission: '當前用戶暫無權限訪問，請聯系管理員',\n    operate: '返回首頁',\n  },\n  custom: '自定義',\n  moveTo: '移動到',\n  deleteConfirm: '是否刪除',\n  expand: '展開',\n  collapse: '收起',\n  copyTitle: '副本',\n  professional: '購買專業版',\n  sync: '同步',\n  prompt: {\n    label: '提示詞',\n    placeholder: '請輸入提示詞',\n  },\n  variable: '變量',\n  allCheck: '全選',\n  type: '類型',\n  pages: {\n    prev: '上一條',\n    next: '下一條',\n  },\n  steps: {\n    prev: '上一步',\n    next: '下一步',\n  },\n  use: '使用',\n  ExecutionRecord: {\n    title: '執行記錄',\n    subTitle: '查看執行記錄',\n  },\n  sourceType: '資源類型',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/components.ts",
    "content": "export default {\n  quickCreatePlaceholder: '快速創建空白文檔',\n  quickCreateName: '文檔名稱',\n  noData: '無匹配数据',\n  loading: '加載中',\n  noMore: '到底啦！',\n  noDesc: '暂无描述',\n  selectParagraph: {\n    title: '選擇分段',\n    error: '僅執行未成功分段',\n    all: '全部分段',\n  },\n  folder: {\n    addFolder: '添加文件夾',\n    addChildFolder: '添加子文件夾',\n    editFolder: '編輯文件夾',\n    folderNamePlaceholder: '請輸入名稱',\n    requiredMessage: '請選擇文件夾',\n    deleteConfirmMessage: '文件夹下的資源會被刪除，請謹慎操作。',\n    ascTime: '按創建時間升序',\n    descTime: '按創建時間降序',\n    ascName: '按名稱升序',\n    descName: '按名稱降序',\n    custom: '按用戶拖拽排序',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/dynamics-form.ts",
    "content": "export default {\n  input_type_list: {\n    TextInput: '文字框',\n    PasswordInput: '密文框',\n    Slider: '滑桿',\n    SwitchInput: '開關',\n    SingleSelect: '單選框',\n    MultiSelect: '多選框',\n    DatePicker: '日期選擇器',\n    JsonInput: 'JSON文字框',\n    RadioCard: '選項卡',\n    RadioRow: '單行選項卡',\n    UploadInput: '文件上傳',\n    TextareaInput: '多行文字框',\n    MultiRow: '單行多選卡',\n    Model: '模型',\n  },\n  default: {\n    label: '預設值',\n    placeholder: '請輸入預設值',\n    requiredMessage: '為必填屬性',\n    show: '顯示預設值',\n  },\n  tip: {\n    requiredMessage: '不能為空',\n    jsonMessage: 'JSON 格式不正確',\n  },\n  paramForm: {\n    field: {\n      label: '參數',\n      placeholder: '請輸入參數',\n      requiredMessage: '參數 為必填屬性',\n      requiredMessage2: '只能輸入字母、數字和底線',\n    },\n    name: {\n      label: '顯示名稱',\n      placeholder: '請輸入顯示名稱',\n      requiredMessage: '顯示名稱 為必填屬性',\n    },\n    tooltip: {\n      label: '參數提示說明',\n      placeholder: '請輸入參數提示說明',\n    },\n    required: {\n      label: '是否必填',\n      requiredMessage: '是否必填 為必填屬性',\n    },\n    input_type: {\n      label: '組件類型',\n      placeholder: '請選擇組件類型',\n      requiredMessage: '組件類型 為必填屬性',\n    },\n  },\n  DatePicker: {\n    placeholder: '選擇日期',\n    year: '年',\n    month: '月',\n    date: '日期',\n    datetime: '日期時間',\n    dataType: {\n      label: '時間類型',\n      placeholder: '請選擇時間類型',\n    },\n    format: {\n      label: '格式',\n      placeholder: '請選擇格式',\n    },\n  },\n  Select: {\n    label: '選項值',\n    placeholder: '請輸入選項值',\n  },\n  tag: {\n    label: '標籤',\n    placeholder: '請輸入選項標籤',\n  },\n  Slider: {\n    showInput: {\n      label: '是否帶輸入框',\n    },\n    valueRange: {\n      label: '取值範圍',\n      minRequired: '最小值必填',\n      maxRequired: '最大值必填',\n    },\n    step: {\n      label: '步長值',\n      requiredMessage1: '步長值必填',\n      requiredMessage2: '步長不能為0',\n    },\n  },\n  TextInput: {\n    length: {\n      label: '文字長度',\n      minRequired: '最小長度必填',\n      maxRequired: '最大長度必填',\n      requiredMessage1: '長度在',\n      requiredMessage2: '到',\n      requiredMessage3: '個字元',\n      requiredMessage4: '文字長度為必填參數',\n    },\n  },\n  UploadInput: {\n    limit: {\n      label: '單次上傳最多文件數',\n      required: '單次上傳最多文件數必填',\n    },\n    max_file_size: {\n      label: '每個文件最大(MB)',\n      required: '每個文件最大必填',\n    },\n    accept: {\n      label: '文件類型',\n      required: '文件類型必填',\n    },\n  },\n  AssignmentMethod: {\n    label: '賦值方式',\n    ref_variables: {\n      label: '參考變量',\n      popover: '變量的值必須符合',\n      json_format: 'JSON 格式',\n      popover_label: '標籤',\n      popover_value: '值',\n      popover_default: '是否為預設值',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/index.ts",
    "content": "import zhTw from 'element-plus/es/locale/lang/zh-tw'\nimport components from './components'\nimport layout from './layout'\nimport views from './views'\nimport theme from './theme'\nimport common from './common'\nimport dynamicsForm from './dynamics-form'\nimport chat from './ai-chat'\nimport workflow from './workflow'\nexport default {\n  lang: '繁體中文',\n  layout,\n  views,\n  theme,\n  common,\n  components,\n  zhTw,\n  dynamicsForm,\n  chat,\n  workflow\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/layout.ts",
    "content": "export default {\n  github: '項目地址',\n  wiki: '使用者手冊',\n  forum: '論壇求助',\n  logout: '登出',\n  apiKey: 'API Key 管理',\n  apiServiceAddress: 'API 服務地址',\n  language: '語言',\n  isExpire: '未上傳 License 或 License 已過期。',\n  crossSettings: '跨域設定',\n  about: {\n    title: '關於',\n    expiredTime: '到期時間',\n    edition: {\n      label: '版本',\n      community: '社群版',\n      professional: '專業版',\n      enterprise: '企業版',\n    },\n    version: '版本號',\n    serialNo: '序列號',\n    remark: '備註',\n    update: '更新',\n    authorize: '授權給',\n    inner_admin: '系統管理員',\n    inner_wsm: '工作空間管理員',\n    inner_user: '普通用戶',\n    root: '根目錄',\n    default_workspace: '預設工作空間',\n    default_user_group: '預設使用者群組',\n  },\n  time: {\n    daysLater: '天後过期',\n    hoursLater: '小時後过期',\n    expired: '已過期',\n    minutesLater: '分鐘後过期',\n    expiringSoon: '即將到期',\n    neverExpires: '永不過期',\n    daysValid: '天有效',\n  },\n  copyright: '版權所有 © 2014-2026 杭州飛致雲信息科技有限公司',\n  userManualUrl: 'https://maxkb.cn/docs/v2/',\n  forumUrl: 'https://github.com/1Panel-dev/MaxKB/discussions',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/theme.ts",
    "content": "export default {\n  title: '外觀設置',\n  defaultSlogan: '強大易用的企業級智能體平台',\n  platformDisplayTheme: '平台顯示主題',\n  customTheme: '自定義主題',\n  platformLoginSettings: '平台登錄設置',\n  pagePreview: '頁面預覽',\n  default: '默認',\n  restoreDefaults: '恢復默認',\n  orange: '活力橙',\n  green: '松石綠',\n  purple: '神秘紫',\n  red: '胭脂紅',\n  loginBackground: '登錄背景圖',\n  loginLogo: '登錄 Logo',\n  websiteLogo: '網站 Logo',\n  replacePicture: '替換圖片',\n  websiteLogoTip: '頂部網站顯示的 Logo，建議尺寸 48*48，支持 JPG、PNG、GIF，大小不超過 10MB',\n  loginLogoTip: '登錄頁面右側 Logo，建議尺寸 204*52，支持 JPG、PNG、GIF，大小不超過 10 MB',\n  loginBackgroundTip:\n    '左側背景圖，矢量圖建議尺寸 576*900，位圖建議尺寸 1152*1800；支持 JPG、PNG、GIF，大小不超過 10 MB',\n  websiteName: '網站名稱',\n  websiteNamePlaceholder: '請輸入網站名稱',\n  websiteNameTip: '顯示在網頁 Tab 的平台名稱',\n  websiteSlogan: '歡迎語',\n  websiteSloganPlaceholder: '請輸入歡迎語',\n  websiteSloganTip: '產品 Logo 下的歡迎語',\n  logoDefaultTip: '默认为 MaxKB 登錄界面，支持自定义设置',\n\n  defaultTip: '默認為 MaxKB 平台界面，支持自定義設置',\n  platformSetting: '平台設置',\n  showUserManual: '顯示用戶手冊',\n  showForum: '顯示論壇求助',\n  showProject: '顯示項目地址',\n  urlPlaceholder: '請輸入 URL 地址',\n  abandonUpdate: '放棄更新',\n  saveAndApply: '保存並應用',\n  fileMessageError: '文件大小超過 10M',\n  saveSuccess: '外觀設置成功',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/application-overview.ts",
    "content": "export default {\n  title: '概覽',\n  appInfo: {\n    publicAccessLink: '公開訪問連結',\n    openText: '開',\n    closeText: '關',\n    demo: '示範',\n    embedInWebsite: '嵌入第三方',\n    accessControl: '訪問限制',\n    displaySetting: '顯示設定',\n    apiAccessCredentials: 'API 存取憑證',\n    apiKey: 'API Key',\n    refreshToken: {\n      msgConfirm1: '是否重新產生公開訪問連結?',\n      msgConfirm2:\n        '重新產生公開訪問連結會影響嵌入第三方腳本變更，需要將新腳本重新嵌入第三方，請謹慎操作！',\n      refreshSuccess: '重新整理成功',\n    },\n    APIKeyDialog: {\n      saveSettings: '儲存設定',\n      msgConfirm1: '是否刪除 API Key',\n      msgConfirm2: '刪除 API Key 後將無法恢復，請確認是否刪除？',\n    },\n    EmbedDialog: {\n      fullscreenModeTitle: '全螢幕模式',\n      copyInstructions: '複製以下程式碼進行嵌入',\n      floatingModeTitle: '浮窗模式',\n      mobileModeTitle: '移動端模式',\n    },\n    LimitDialog: {\n      clientQueryLimitLabel: '每個用戶端提問限制',\n      timesDays: '次/天',\n      authentication: '身份驗證',\n      authenticationValue: '密碼驗證',\n      whitelistLabel: '白名單',\n      whitelistPlaceholder:\n        '請輸入允許嵌入第三方的來源位址，一行一個，如：\\nhttp://127.0.0.1:5678\\nhttps://dataease.io',\n      loginMethod: '登陸方式',\n      loginMethodRequired: '請選擇登陸方式',\n      displayCodeRequired: '請輸入失敗次數',\n      toSettingChatUser: '去配置對話用戶',\n      authenticationTooltip:\n        '開啟登陸認證後，智能體和關聯的知識庫均需要對話用戶授權配置，否則用戶無權限登陸和知識庫檢索',\n    },\n    SettingAPIKeyDialog: {\n      dialogTitle: '設定',\n      allowCrossDomainLabel: '允許跨域位址',\n      crossDomainPlaceholder:\n        '請輸入允許的跨域位址，開啟後不輸入跨域位址則不限制。\\n跨域位址一行一個，如：\\nhttp://127.0.0.1:5678 \\nhttps://dataease.io',\n    },\n  },\n  SettingDisplayDialog: {\n    showSourceLabel: '顯示知識來源',\n    showExecutionDetail: '顯示執行細節',\n    restoreDefault: '恢復預設',\n    customThemeColor: '自訂主題色',\n    headerTitleFontColor: '標頭標題字體顏色',\n    default: '預設',\n    askUserAvatar: '提問用戶頭像',\n    replace: '取代',\n    imageMessage: '建議尺寸 32*32，支援 JPG、PNG、GIF，大小不超過 10 MB',\n    AIAvatar: 'AI 回覆頭像',\n    display: '顯示',\n    floatIcon: '浮窗入口圖示',\n    iconDefaultPosition: '圖示預設位置',\n    iconPosition: {\n      left: '左',\n      right: '右',\n      bottom: '下',\n      top: '上',\n    },\n    draggablePosition: '可拖曳位置',\n    showHistory: '顯示歷史紀錄',\n    displayGuide: '顯示引導圖(浮窗模式)',\n    disclaimer: '免責聲明',\n    disclaimerValue: '「以上內容均由 AI 生成，僅供參考和借鏡」',\n    chatBackground: '聊天背景',\n    chatBackgroundMessage: '圖片格式：JPG, PNG, GIF。最大大小：10MB。',\n  },\n  monitor: {\n    monitoringStatistics: '監控統計',\n    customRange: '自訂範圍',\n    startDatePlaceholder: '開始時間',\n    endDatePlaceholder: '結束時間',\n    pastDayOptions: {\n      past7Days: '過去7天',\n      past30Days: '過去30天',\n      past90Days: '過去90天',\n      past183Days: '過去半年',\n      other: '自訂义',\n    },\n    charts: {\n      customerTotal: '用戶總數',\n      customerNew: '用戶新增數',\n      queryCount: '提問次數',\n      tokensTotal: 'Tokens 總數',\n      userSatisfaction: '用戶滿意度',\n      approval: '贊同',\n      disapproval: '反對',\n      tokenUsage: '用戶消耗 Tokens',\n      topQuestions: '用戶提問次數',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/application.ts",
    "content": "export default {\n  title: '智能體',\n  createApplication: '建立簡易智能體',\n  createWorkFlowApplication: '建立進階智能體',\n  importApplication: '匯入智能體',\n  copyApplication: '複製智能體',\n  AdvancedAgent: '進階編智能體',\n  simpleAgent: '簡易智能體',\n  simple: '簡易',\n  senior: '進階',\n  simplePlaceholder: '通過表單設置方式，快速搭建基礎功能的智能體',\n  advancedPlaceholder: '使用低代碼拖拉拽方式，靈活編排複雜邏輯、功能豐富的智能體',\n  appTest: '調試預覽',\n  operation: {\n    toChat: '去對話',\n    addModel: '新增模型',\n  },\n  delete: {\n    confirmTitle: '是否刪除智能體：',\n    confirmMessage: '刪除後該智能體將不再提供服務，請謹慎操作。',\n    resourceCountMessage: '此智能體關聯 {count} 個資源，刪除後無法使用，請謹慎操作。',\n  },\n  tip: {\n    publishSuccess: '發布成功',\n    ExportError: '匯出失敗',\n    professionalMessage: '社群版最多支援 5 個智能體，如需擁有更多智能體，請升級為專業版。',\n    saveErrorMessage: '儲存失敗，請檢查輸入或稍後再試',\n    loadingErrorMessage: '載入配置失敗，請檢查輸入或稍後再試',\n    noDocPermission: '無文檔創建權限',\n    confirmUse: '確定使用',\n    overwrite: '覆蓋當前工作流',\n  },\n  form: {\n    appName: {\n      placeholder: '請輸入智能體名稱',\n      requiredMessage: '請輸入智能體名稱',\n    },\n    appDescription: {\n      placeholder: '描述該智能體的應用場景及用途，如：XXX 小助手回答用戶提出的 XXX 產品使用問題',\n    },\n    appType: {\n      simplePlaceholder: '適合新手建立小助手',\n      workflowPlaceholder: '適合高階用戶自訂小助手的工作流程',\n    },\n\n    appTemplate: {\n      blankApp: {\n        title: '空白创建',\n      },\n      assistantApp: {\n        title: '知識庫問答助手',\n        description: '基於用戶問題，檢索知識庫相關內容作爲AI模型的參考內容',\n      },\n    },\n    aiModel: {\n      label: 'AI 模型',\n      placeholder: '請選擇 AI 模型',\n    },\n    roleSettings: {\n      label: '系統提示詞',\n      placeholder:\n        '系統提示詞，可以引用系統中的變數：{data} 是命中知識庫中的分段；{question} 是用戶提出的問題。',\n      tooltip: '設定模型扮演的角色或遵循的指令',\n    },\n    prompt: {\n      label: '用戶提示詞',\n      noReferences: ' (無引用知識庫)',\n      references: ' (引用知識庫)',\n      placeholder:\n        '用戶提示詞，可以引用系統中的變數：{data} 是命中知識庫中的分段；{question} 是用戶提出的問題。',\n      requiredMessage: '請輸入用戶提示詞',\n      tooltip: '用戶向模型提出的問題或輸入的指令',\n\n      noReferencesTooltip:\n        '透過調整提示詞內容，可以引導大模型對話方向，該提示詞會被固定在上下文的開頭。可以使用變數：{question} 是用戶提出問題的佔位符。',\n      referencesTooltip:\n        '透過調整提示詞內容，可以引導大模型對話方向，該提示詞會被固定在上下文的開頭。可以使用變數：{data} 是引用知識庫中分段的佔位符；{question} 是用戶提出問題的佔位符。',\n      defaultPrompt: `已知資訊：{data}\n用戶問題：{question}\n回答要求：\n- 請使用中文回答用戶問題`,\n    },\n    historyRecord: {\n      label: '歷史對話紀錄',\n    },\n    relatedKnowledge: {\n      label: '關聯知識庫',\n      placeholder: '關聯的知識庫展示在這裡',\n    },\n    multipleRoundsDialogue: '多輪對話',\n\n    prologue: '開場白',\n    defaultPrologue:\n      '您好，我是 XXX 小助手，您可以向我提出 XXX 使用問題。\\n- XXX 主要功能有什麼？\\n- XXX 如何收費？\\n- 需要轉人工服務',\n\n    problemOptimization: {\n      label: '問題優化',\n      tooltip: '根據歷史對話優化完善當前問題，更利於匹配知識點。',\n    },\n    voiceInput: {\n      label: '語音輸入',\n      placeholder: '請選擇語音辨識模型',\n      requiredMessage: '請選擇語音輸入模型',\n      autoSend: '自動發送',\n    },\n    voicePlay: {\n      label: '語音播放',\n      placeholder: '請選擇語音合成模型',\n      requiredMessage: '請選擇語音播放模型',\n      autoPlay: '自動播放',\n      browser: '瀏覽器播放(免費)',\n      tts: 'TTS 模型',\n      listeningTest: '試聽',\n    },\n    reasoningContent: {\n      label: '輸出思考',\n      tooltip: '請根據模型返回的思考標簽設置，標簽中間的內容將會認定爲思考過程',\n      start: '開始',\n      end: '結束',\n    },\n    mcp_output_enable: '輸出執行過程',\n  },\n  generateDialog: {\n    label: '生成',\n    generatePrompt: '生成提示詞',\n    placeholder: '請輸入提示詞主題',\n    title: '提示詞顯示在這裡',\n    remake: '重新生成',\n    stop: '停止生成',\n    continue: '繼續生成',\n    replace: '替換',\n    exit: '確認退出並捨棄 AI 生成的內容嗎？',\n    loading: '生成中...',\n  },\n  dialog: {\n    addKnowledge: '新增關聯知識庫',\n    addKnowledgePlaceholder: '所選知識庫必須使用相同的 Embedding 模型',\n\n    selectSearchMode: '檢索模式',\n    vectorSearch: '向量檢索',\n    vectorSearchTooltip: '向量檢索是一種基於向量相似度的檢索方式，適用於知識庫中的大數據量場景。',\n    fullTextSearch: '全文檢索',\n    fullTextSearchTooltip: '全文檢索是一種基於文本相似度的檢索方式，適用於知識庫中的小數據量場景。',\n    hybridSearch: '混合檢索',\n    hybridSearchTooltip:\n      '混合檢索是一種基於向量和文本相似度的檢索方式，適用於知識庫中的中等數據量場景。',\n    similarityThreshold: '相似度高於',\n    similarityTooltip: '相似度越高相關性越強。',\n    topReferences: '引用分段數 TOP',\n    maxCharacters: '最多引用字元數',\n    noReferencesAction: '無引用知識庫分段時',\n    continueQuestioning: '繼續向 AI 模型提問',\n    provideAnswer: '指定回答內容',\n    designated_answer:\n      '你好，我是 XXX 小助手，我的知識庫只包含了 XXX 產品相關知識，請重新描述您的問題。',\n    defaultPrompt1:\n      '()裡面是用戶問題,根據上下文回答揣測用戶問題({question}) 要求: 輸出一個補全問題,並且放在',\n    defaultPrompt2: '標籤中',\n  },\n  applicationAccess: {\n    title: '接入第三方',\n    wecom: '企業微信應用',\n    wecomTip: '打造企業微信智能體',\n    dingtalk: '釘釘應用',\n    dingtalkTip: '打造釘釘智能體',\n    wecomBot: '企業微信智能機器人',\n    wecomBotTip: '打造企業微信智能機器人',\n    wechat: '公眾號',\n    wechatTip: '打造公眾號智能體',\n    lark: '飛書應用',\n    larkTip: '打造飛書智能體',\n    slack: 'Slack',\n    slackTip: '打造 Slack 智能體',\n    setting: '配置',\n    callback: '回呼位址',\n    callbackTip: '請輸入回呼位址',\n    wecomPlatform: '企業微信後台',\n    wechatPlatform: '微信公众平台',\n    dingtalkPlatform: '釘釘開放平台',\n    larkPlatform: '飛書開放平台',\n    wecomSetting: {\n      title: '企業微信應用配置',\n      cropId: '企業 ID',\n      cropIdPlaceholder: '請輸入企業 ID',\n      agentIdPlaceholder: '請輸入 Agent ID',\n      secretPlaceholder: '請輸入 Secret',\n      tokenPlaceholder: '請輸入 Token',\n      encodingAesKeyPlaceholder: '請輸入 EncodingAESKey',\n      authenticationSuccessful: '認證成功',\n      urlInfo: '-應用管理-自建-建立的應用-接收消息-設定 API 接收的 \"URL\" 中',\n    },\n    dingtalkSetting: {\n      title: '釘釘應用配置',\n      clientIdPlaceholder: '請輸入 Client ID',\n      clientSecretPlaceholder: '請輸入 Client Secret',\n      urlInfo: '-機器人頁面，設定 \"消息接收模式\" 為 HTTP 模式 ，並把上面URL填寫到\"消息接收位址\"中',\n    },\n    wechatSetting: {\n      title: '公眾號應用配置',\n      appId: '開發者 ID (APP ID)',\n      appIdPlaceholder: '請輸入開發者 ID (APP ID)',\n      appSecret: '開發者密鑰 (APP SECRET)',\n      appSecretPlaceholder: '請輸入開發者密鑰 (APP SECRET)',\n      token: '權杖 (TOKEN)',\n      tokenPlaceholder: '請輸入權杖 (TOKEN)',\n      aesKey: '消息加解密密鑰',\n      aesKeyPlaceholder: '請輸入消息加解密密鑰',\n      urlInfo: '-設定與開發-基本配置-伺服器配置的 \"伺服器位址 URL \" 中',\n    },\n    larkSetting: {\n      title: '飛書應用配置',\n      appIdPlaceholder: '請輸入 App ID',\n      appSecretPlaceholder: '請輸入 App Secret',\n      verificationTokenPlaceholder: '請輸入 Verification Token',\n      urlInfo: '-事件與回呼-事件配置-配置訂閱方式的 \"請求位址\" 中',\n    },\n    wecomBotSetting: {\n      title: '企業微信智能機器人配置',\n      urlInfo: '-管理工具-智能机器人-创建机器人-API模式创建的 \"URL\" 中',\n    },\n    slackSetting: {\n      title: 'Slack 應用配置',\n      signingSecretPlaceholder: '請輸入 Signing Secret',\n      botUserTokenPlaceholder: '請輸入 Bot User Token',\n    },\n    copyUrl: '複製連結填入到',\n  },\n  hitTest: {\n    title: '命中測試',\n    text: '針對用戶提問調試段落匹配情況，保障回答效果。',\n    emptyMessage1: '命中的段落顯示在這裡',\n    emptyMessage2: '沒有命中的分段',\n  },\n  publishTime: '發佈時間',\n  publishStatus: '發佈狀態',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/chat-log.ts",
    "content": "export default {\n  title: '對話日誌',\n  delete: {\n    confirmTitle: '是否刪除問題：',\n    confirmMessage1: '刪除問題關聯的',\n    confirmMessage2: '個分段會被取消關聯，請謹慎操作。',\n  },\n  buttons: {\n    clearStrategy: '清除策略',\n  },\n  table: {\n    abstract: '摘要',\n    username: '用戶',\n    chat_record_count: '對話提問數',\n    user: '用戶',\n    feedback: {\n      label: '用戶反饋',\n      star: '贊同',\n      trample: '反對',\n    },\n    mark: '改進標註',\n    recenTimes: '最近對話時間',\n  },\n  addToKnowledge: '添加至知識庫',\n  daysText: '天之前的對話記錄',\n  fileDaysText: '天之前的對話上傳的附件',\n  selectKnowledge: '選擇知識庫',\n  selectKnowledgePlaceholder: '請選擇知識庫',\n  saveToDocument: '保存至文件',\n  documentPlaceholder: '請選擇文件',\n  editContent: '修改內容',\n  editMark: '修改標註',\n  form: {\n    content: {\n      placeholder: '請輸入內容',\n    },\n    title: {\n      placeholder: '請給當前內容設定一個標題，以便管理查看',\n    },\n  },\n  online: '線上使用',\n  apiCall: 'API 呼叫',\n  enterpriseWechat: '企業微信應用',\n  wechatPublicAccount: '微信公眾號',\n  lark: '飛書應用',\n  dingtalk: '釘釘應用',\n  enterpriseWechatRobot: '企業微信機器人',\n  slack: 'Slack 機器人',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/chat-user.ts",
    "content": "export default {\n  title: '對話用戶',\n  syncUsers: '导入用戶',\n  syncUsersTip: '僅导入新增用戶',\n  setUserGroups: '設定用戶組',\n  knowledgeTitleTip: '該配置需要關聯的智能體開啟對話用戶登入認證後才會生效',\n  applicationTitleTip: '該配置需要智能體開啟登入認證後生效',\n  autoAuthorization: '自動授權',\n  authorization: '授權',\n  batchDeleteUser: '是否刪除選中的 {count} 個用戶？',\n  settingMethod: '設定方式',\n  append: '追加',\n  group: {\n    title: '用戶組',\n    requiredMessage: '請選擇用戶組',\n    name: '用戶組名稱',\n    usernameOrName: '用戶名/姓名',\n    delete: {\n      confirmTitle: '是否刪除用戶組：',\n      confirmMessage: '刪除後，該用戶組下的成員將全部移除，請謹慎操作！',\n    },\n    batchDeleteMember: '是否移除選中的 {count} 個成員？',\n  },\n  syncMessage: {\n    title: '成功同步 {count} 個用戶',\n    usernameExist: '以下用戶名已存在：',\n    nicknameExist: '以下姓名已存在：',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/document.ts",
    "content": "export default {\n  uploadDocument: '上傳文檔',\n  importDocument: '導入文檔',\n  syncDocument: '同步文檔',\n  items: '項',\n  migrateDocument: '文檔遷移到',\n  setting: {\n    migration: '遷移',\n    cancelGenerateQuestion: '取消生成問題',\n    cancelVectorization: '取消向量化',\n    cancelGenerate: '取消生成',\n    export: '匯出',\n    download: '下載原文件',\n    replace: '替換原文件',\n  },\n  tip: {\n    saveMessage: '當前的更改尚未保存，確認退出嗎?',\n    cancelSuccess: '批量取消成功',\n    sendMessage: '發送成功',\n    vectorizationSuccess: '批量向量化成功',\n    nameMessage: '文件名稱不能为空！',\n    importMessage: '導入成功',\n    migrationSuccess: '遷移成功',\n    replaceSuccess: '替換成功',\n    fileLimitCountTip1: '每次最多上傳',\n    fileLimitCountTip2: '個文件',\n    fileLimitSizeTip1: '每個文件不超過',\n    toImportDocConfirm: '當前知識庫的工作流未發布，無法導入文檔，請先發布工作流。',\n    fileLimitSizeTip2: '大小不超過',\n  },\n  upload: {\n    selectFile: '選擇文件',\n    selectFiles: '選擇文件夾',\n    uploadMessage: '拖拽文件至此上傳或',\n    formats: '支持格式：',\n    requiredMessage: '請上傳文件',\n    errorMessage1: '文件大小超過 100MB',\n    errorMessage2: '文件格式不支持',\n    errorMessage3: '文件不能为空',\n    errorMessage4: '每次最多上傳50個文件',\n    template: '模板',\n    download: '下載',\n  },\n\n  fileType: {\n    txt: {\n      label: '文本文件',\n      tip1: '1、文件上傳前，建議規範文件的分段標識',\n      tip2: '2、每次最多上傳 50 個文件，每個文件不超过 100MB',\n    },\n    table: {\n      label: '表格',\n      tip1: '1、點擊下載對應模板並完善信息：',\n      tip2: '2、第一行必須是列標題，且列標題必須是有意義的術語，表中每條記錄將作為一個分段',\n      tip3: '3、上傳的表格文件中每個 sheet 會作為一個文檔，sheet 名稱為文檔名稱',\n      tip4: '4、每次最多上傳 50 個文件，每個文件不超过 100MB',\n    },\n    QA: {\n      label: 'QA 問答對',\n      tip1: '1、點擊下載對應模板並完善信息',\n      tip2: '2、上傳的表格文件中每個 sheet 會作為一個文檔，sheet 名稱為文檔名稱',\n      tip3: '3、每次最多上傳 50 個文件，每個文件不超过 100MB',\n    },\n  },\n  setRules: {\n    title: {\n      setting: '設置分段規則',\n      preview: '分段預覽',\n    },\n    intelligent: {\n      label: '智能分段（推薦)',\n      text: '不了解如何設置分段規則推薦使用智能分段',\n    },\n    advanced: {\n      label: '高級分段',\n      text: '用戶可根據文檔規範自行設置分段標識符、分段長度以及清洗規則',\n    },\n    patterns: {\n      label: '分段標識',\n      tooltip: '按照所選符號先後順序做遞歸分割，分割結果超出分段長度將截取至分段長度。',\n      placeholder: '請選擇',\n    },\n    limit: {\n      label: '分段長度',\n    },\n    with_filter: {\n      label: '自動清洗',\n      text: '去掉重複多餘符號空格、空行、制表符',\n    },\n    checkedConnect: {\n      label: '導入時添加分段標題為關聯問題（適用於標題為問題的問答對）',\n    },\n  },\n  buttons: {\n    import: '開始導入',\n    preview: '生成預覽',\n    continueImporting: '繼續導入文檔',\n  },\n  tag: {\n    label: '標籤管理',\n    key: '標籤',\n    value: '標籤值',\n    noTag: '無標籤',\n    relate: '關聯',\n    unrelate: '取消關聯',\n    relatedDoc: '已關聯文檔',\n    unrelatedDoc: '未關聯文檔',\n    addTag: '添加標籤',\n    setting: '標籤設置',\n    create: '創建標籤',\n    createValue: '創建標籤值',\n    edit: '編輯標籤',\n    editValue: '編輯標籤值',\n    deleteConfirm: '是否刪除標籤: ',\n    deleteTip: '刪除後使用該標籤的資源將會刪除該標籤，請謹慎操作!',\n    requiredMessage1: '請輸入標籤',\n    requiredMessage2: '請輸入標籤值',\n    requiredMessage3: '請輸入標籤或標籤值',\n  },\n  table: {\n    name: '文件名稱',\n    char_length: '字符數',\n    paragraph: '分段',\n    updateTime: '更新時間',\n  },\n  fileStatus: {\n    label: '文件狀態',\n    EMBEDDING: '索引中',\n    PENDING: '排隊中',\n    GENERATE: '生成中',\n    SYNC: '同步中',\n    finish: '完圓',\n  },\n  enableStatus: {\n    label: '啟用狀態',\n    enable: '開啟',\n    close: '關閉',\n  },\n  sync: {\n    label: '同步',\n    confirmTitle: '確認同步文檔?',\n    confirmMessage1: '同步將刪除已有數據重新獲取新數據，請謹慎操作。',\n    confirmMessage2: '無法同步，請先去設置文檔 URL地址',\n    successMessage: '同步文檔成功',\n  },\n  delete: {\n    confirmTitle1: '是否批量刪除',\n    confirmTitle2: '個文檔?',\n    confirmMessage: '所選文檔中的分段會跟隨刪除，請謹慎操作。',\n    successMessage: '批量刪除成功',\n    confirmTitle3: '是否刪除文檔：',\n    confirmMessage1: '此文檔下的',\n    confirmMessage2: '個分段都會被刪除，請謹慎操作。',\n  },\n  form: {\n    source_url: {\n      label: '文檔地址',\n      placeholder: '請輸入文檔地址，一行一個，地址不正確文檔會導入失敗。',\n      requiredMessage: '請輸入文檔地址',\n    },\n    selector: {\n      label: '選擇器',\n      placeholder: '默認為 body，可輸入 .classname/#idname/tagname',\n    },\n    hit_handling_method: {\n      label: '命中處理方式',\n      tooltip: '用戶提問時，命中文檔下的分段時按照設置的方式進行處理。',\n    },\n    similarity: {\n      label: '相似度高于',\n      placeholder: '直接返回分段内容',\n      requiredMessage: '请输入相似度',\n    },\n    allow_download: {\n      label: '允許在知識庫來源下載',\n    },\n  },\n  hitHandlingMethod: {\n    optimization: '模型優化',\n    directly_return: '直接回答',\n  },\n  movePosition: {\n    title: '移動位置',\n    moveUp: '上移',\n    moveDown: '下移',\n    MoveTop: '頭部',\n    MoveBottom: '末尾',\n  },\n  generateQuestion: {\n    title: '生成問題',\n    successMessage: '生成問題成功',\n    tip1: '提示詞中的 {data} 為分段內容的佔位符，執行時替換為分段內容並發送給 AI 模型；',\n    tip2: 'AI 模型根據分段內容生成相關問題，請將生成的問題放置於',\n    tip3: '標籤中，系統會自動關聯標籤中的問題；',\n    tip4: '生成效果取決於所選模型和提示詞，用戶可自行調整至最佳效果。',\n    prompt1: `內容：{data}\\n\\n請總結上面的內容，並根據內容總結生成 5 個問題。\\n回答要求：\\n - 請只輸出問題；\\n - 請將每個問題放置在`,\n    prompt2: `標籤中。`,\n  },\n  feishu: {\n    selectDocument: '選擇文檔',\n    tip1: '僅支持文檔和表格類型，文檔會根據標題分段，表格會轉為Markdown格式後再分段。',\n    tip2: '導入文檔前，建議規範文檔的分段標識。',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/index.ts",
    "content": "import application from './application'\nimport role from './role'\nimport workspace from './workspace'\nimport applicationOverview from './application-overview'\nimport knowledge from './knowledge'\nimport system from './system'\nimport tool from './tool'\nimport userManage from './user-manage'\nimport model from './model'\nimport document from './document'\nimport paragraph from './paragraph'\nimport problem from './problem'\nimport chatLog from './chat-log'\nimport chatUser from './chat-user'\nimport login from './login'\nimport operateLog from './operate-log'\nimport shared from './shared'\nimport trigger from './trigger'\nexport default {\n  application,\n  applicationOverview,\n  system,\n  tool,\n  userManage,\n  model,\n  knowledge,\n  document,\n  paragraph,\n  problem,\n  chatLog,\n  login,\n  operateLog,\n  role,\n  workspace,\n  chatUser,\n  shared,\n  trigger\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/knowledge.ts",
    "content": "export default {\n  title: '知識庫',\n  relatedApplications: '關聯智能體',\n  document_count: '文檔數',\n  relatedApp_count: '關聯智能體',\n  setting: {\n    vectorization: '向量化',\n    sync: '同步',\n  },\n  tip: {\n    professionalMessage: '社群版最多支援 50 個知識庫，如需擁有更多知識庫，請升級為專業版。',\n    syncSuccess: '同步任務發送成功',\n    updateModeMessage: '修改知識庫向量模型後，需要對知識庫向量化，是否繼續保存？',\n  },\n  delete: {\n    confirmTitle: '是否刪除知識庫：',\n    confirmMessage1: '此知識庫關聯',\n    confirmMessage2: '個智能體，刪除後無法恢復，請謹慎操作。',\n    resourceCountMessage: '此知識庫關聯 {count} 個資源，刪除後無法使用，請謹慎操作。',\n  },\n  knowledgeType: {\n    label: '知識庫類型',\n    generalKnowledge: '通用知識庫',\n    webKnowledge: 'Web 知識庫',\n    larkKnowledge: '飛書知識庫',\n    workflowKnowledge: '工作流知識庫',\n    yuqueKnowledge: '語雀知識庫',\n    generalInfo: '上傳本地檔案',\n    webInfo: '同步 Web 網站文字資料',\n    larkInfo: '通過飛書文檔構建知識庫',\n    yuqueInfo: '通過語雀文檔構建知識庫',\n    createWorkflowKnowledge: '建立工作流知識庫',\n    workflowInfo: '通過自定義工作流管道構建知識庫',\n  },\n  form: {\n    knowledgeName: {\n      label: '知識庫名稱',\n      placeholder: '請輸入知識庫名稱',\n      requiredMessage: '請輸入知識庫名稱',\n    },\n    knowledgeDescription: {\n      label: '知識庫描述',\n      placeholder:\n        '描述知識庫的內容，詳盡的描述將幫助AI能深入理解該知識庫的內容，能更準確的檢索到內容，提高該知識庫的命中率。',\n      requiredMessage: '請輸入知識庫描述',\n    },\n    EmbeddingModel: {\n      label: '向量模型',\n      placeholder: '請選擇向量模型',\n      requiredMessage: '請選擇向量模型',\n    },\n\n    source_url: {\n      label: 'Web 根位址',\n      placeholder: '請輸入 Web 根位址',\n      requiredMessage: '請輸入 Web 根位址',\n    },\n    selector: {\n      label: '選擇器',\n      placeholder: '預設為 body，可輸入 .classname/#idname/tagname',\n    },\n    file_count_limit: {\n      label: '每次上傳最多文件數',\n    },\n    file_size_limit: {\n      label: '上傳的每個文件最大(MB)',\n      placeholder: '建议根据服务器配置调整，否則會造成服務宕机',\n    },\n    appTemplate: {\n      blank: {\n        title: '空白創建',\n      },\n      basic: {\n        title: '基礎模板',\n        description: '支持本地文件、飛書文檔、Web 站點數據源的基礎工作流模板',\n      },\n    },\n  },\n\n  ResultSuccess: {\n    title: '知識庫建立成功',\n    paragraph: '段落',\n    paragraph_count: '個段落',\n    documentList: '文件列表',\n    loading: '正在導入',\n    buttons: {\n      toKnowledge: '返回知識庫列表',\n      toDocument: '前往文件',\n    },\n  },\n  syncWeb: {\n    title: '同步知識庫',\n    syncMethod: '同步方式',\n    replace: '替換同步',\n    replaceText: '重新獲取 Web 站點文件，覆蓋替換本地知識庫中的文件',\n    complete: '完整同步',\n    completeText: '先刪除本地知識庫所有文件，重新獲取 Web 站點文件',\n    tip: '注意：所有同步都會刪除現有數據並重新獲取新數據，請謹慎操作。',\n  },\n  transform: {\n    button: '轉換',\n    title: '轉換為工作流知識庫',\n    message1:\n      '您現在可以將現有知識庫轉換為工作流知識庫——這是一種更開放、更靈活的知識庫，通過拖拽節點的方式自主編排從不同數據源到知識庫寫入的全流程，滿足企業個性化知識管理需求。可以使用我們工具中的數據源和工具。',\n    message2: '新的處理方式將應用於後續所有導入的文件。',\n    tip: '注意：轉換後不可撤回。',\n    comfirm: '確定轉換為工作流知識庫？轉換後無法回退，請謹慎操作。',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/login.ts",
    "content": "export default {\n  title: '帳號登錄',\n  loginForm: {\n    username: {\n      label: '用戶名稱',\n      placeholder: '請輸入用戶名稱',\n      requiredMessage: '請輸入用戶名稱',\n      lengthMessage: '長度須介於 4 到 20 個字元之間',\n    },\n    password: {\n      label: '登入密碼',\n      placeholder: '請輸入密碼',\n      requiredMessage: '請輸入密碼',\n      lengthMessage: '長度須介於 6 到 20 個字元之間',\n    },\n    captcha: {\n      label: '驗證碼',\n      placeholder: '請輸入驗證碼',\n      requiredMessage: '請輸入驗證碼',\n      validatorMessage: '驗證碼不正確',\n    },\n    new_password: {\n      label: '新密碼',\n      placeholder: '請輸入新密碼',\n      requiredMessage: '請輸入新密碼',\n    },\n    re_password: {\n      label: '確認密碼',\n      placeholder: '請輸入確認密碼',\n      requiredMessage: '請輸入確認密碼',\n      validatorMessage: '密碼不一致',\n    },\n    email: {\n      label: '電子信箱',\n      placeholder: '請輸入電子信箱',\n      requiredMessage: '請輸入電子信箱',\n      validatorEmail: '請輸入有效電子信箱格式！',\n    },\n  },\n  jump_tip: '即將跳轉至認證源頁面進行認證',\n  jump: '跳轉',\n  resetPassword: '修改密碼',\n  forgotPassword: '忘記密碼',\n  userRegister: '用戶註冊',\n  buttons: {\n    login: '登錄',\n    register: '註冊',\n    backLogin: '返回登錄',\n    checkCode: '立即驗證',\n  },\n  newPassword: '新密碼',\n  enterPassword: '請輸入新密碼',\n  useEmail: '使用電子郵箱',\n  moreMethod: '更多登錄方式',\n  verificationCode: {\n    placeholder: '請輸入驗證碼',\n    getVerificationCode: '獲取驗證碼',\n    successMessage: '若該郵箱已註冊，我們將發送郵件，請注意查收',\n    resend: '重新發送',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/model.ts",
    "content": "export default {\n  title: '模型',\n  provider: '供應商',\n  providerPlaceholder: '選擇供應商',\n  addModel: '新增模型',\n  delete: {\n    confirmTitle: '是否刪除： ',\n    confirmMessage: '模型刪除後將影響正在使用該模型的資源，請謹慎操作。',\n    resourceCountMessage: '此模型關聯 {count} 個資源，刪除後無法使用，請謹慎操作。',\n  },\n  tip: {\n    createSuccessMessage: '創建模型成功',\n    createErrorMessage: '基礎資訊有填寫錯誤',\n    errorMessage: '變數已存在: ',\n    emptyMessage1: '請先選擇基礎資訊的模型類型和基礎模型',\n    emptyMessage2: '所選模型不支援參數設定',\n    updateSuccessMessage: '修改模型成功',\n    saveSuccessMessage: '模型參數儲存成功',\n    downloadError: '下載失敗',\n    noModel: '模型在 Ollama 不存在',\n  },\n  modelType: {\n    allModel: '全部模型',\n    publicModel: '公有模型',\n    privateModel: '私有模型',\n    LLM: '大語言模型',\n    EMBEDDING: '向量模型',\n    RERANKER: '重排模型',\n    STT: '語音辨識',\n    TTS: '語音合成',\n    IMAGE: '視覺模型',\n    TTI: '圖片生成',\n    TTV: '文生視頻',\n    ITV: '圖生視頻',\n  },\n  modelForm: {\n    title: {\n      baseInfo: '基礎資訊',\n      advancedInfo: '進階設定',\n      modelParams: '模型參數',\n      paramSetting: '模型參數設定',\n      apiParamPassing: '接口傳參',\n    },\n    modeName: {\n      label: '模型名稱',\n      placeholder: '請給基礎模型設定一個名稱',\n      tooltip: 'MaxKB 中自訂的模型名稱',\n      requiredMessage: '模型名稱不能為空',\n    },\n    permissionType: {\n      label: '權限',\n      privateDesc: '僅當前使用者使用',\n      publicDesc: '所有使用者都可使用',\n      requiredMessage: '權限不能為空',\n    },\n    model_type: {\n      label: '模型類型',\n      placeholder: '請選擇模型類型',\n      tooltip1: '大語言模型：在智能體中與 AI 對話的推理模型。',\n      tooltip2: '向量模型：在知識庫中對文件內容進行向量化化的模型。',\n      tooltip3: '語音辨識：在智能體中開啟語音辨識後用於語音轉文字的模型。',\n      tooltip4: '語音合成：在智能體中開啟語音播放後用於文字轉語音的模型。',\n      tooltip5: '重排模型：在高階智能體中使用多路召回時，對候選分段進行重新排序的模型。',\n      tooltip6: '視覺模型：在高階智能體中用於圖片理解的視覺模型。',\n      tooltip7: '圖片生成：在高階智能體中用於圖片生成的視覺模型。',\n      tooltip8: '文生視頻：在高階智能體中用於文生視頻的模型。',\n      tooltip9: '圖生視頻：在高階智能體中用於圖生視頻的模型。',\n      requiredMessage: '模型類型不能為空',\n    },\n    base_model: {\n      label: '基礎模型',\n      tooltip: '列表中未列出的模型，直接輸入模型名稱，按 Enter 即可新增',\n      placeholder: '自訂輸入基礎模型後按 Enter 即可',\n      requiredMessage: '基礎模型不能為空',\n    },\n  },\n  download: {\n    downloading: '正在下載中',\n    cancelDownload: '取消下載',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/operate-log.ts",
    "content": "export default {\n  title: '操作日誌',\n  table: {\n    menu: '操作菜單',\n    detail: '操作詳情',\n    user: '操作用戶',\n    ip_address: 'IP 地址',\n    opt: 'API 詳情',\n    operateTime: '操作時間',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/paragraph.ts",
    "content": "export default {\n  title: '段落',\n  paragraph_count: '段落',\n  editParagraph: '編輯分段',\n  addParagraph: '添加分段',\n  prevAddParagraph: '上方插入分段',\n  paragraphDetail: '分段詳情',\n  character_count: '個字符',\n  setting: {\n    batchSelected: '批量選擇',\n    cancelSelected: '取消選擇',\n  },\n  delete: {\n    confirmTitle: '是否刪除段落：',\n    confirmMessage: '刪除後無法恢復，請謹慎操作。',\n  },\n  relatedProblem: {\n    title: '關聯問題',\n    placeholder: '請選擇問題',\n  },\n  form: {\n    paragraphTitle: {\n      label: '分段標題',\n      placeholder: '請輸入分段標題',\n    },\n    content: {\n      label: '分段內容',\n      placeholder: '請輸入分段內容',\n      requiredMessage1: '請輸入分段內容',\n      requiredMessage2: '內容最多不超過 100000 個字',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/problem.ts",
    "content": "export default {\n  title: '問題',\n  createProblem: '建立問題',\n  detailProblem: '問題詳情',\n  quickCreateProblem: '快速建立問題',\n  quickCreateName: '問題',\n  tip: {\n    placeholder: '請輸入問題，支持輸入多個，一行一個。',\n    errorMessage: '問題不能為空！',\n    requiredMessage: '請輸入問題',\n    relatedSuccess: '批量關聯分段成功'\n  },\n\n  setting: {\n    batchDelete: '批量刪除',\n    cancelRelated: '取消關聯'\n  },\n  table: {\n    paragraph_count: '關聯分段數',\n    updateTime: '更新時間'\n  },\n  delete: {\n    confirmTitle: '是否刪除問題：',\n    confirmMessage1: '刪除問題關聯的',\n    confirmMessage2: '個分段會被取消關聯，請謹慎操作。'\n  },\n  relateParagraph: {\n    title: '關聯分段',\n    selectDocument: '選擇文件',\n    placeholder: '按 文件名稱 搜尋',\n    selectedParagraph: '已選分段',\n    count: '個'\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/role.ts",
    "content": "export default {\n  title: '角色管理',\n  internalRole: '系統內置角色',\n  customRole: '自定義角色',\n  systemAdmin: '系統管理員',\n  workspaceAdmin: '工作空間管理員',\n  user: '普通用戶',\n  roleName: '角色名稱',\n  inheritingRole: '繼承角色',\n  delete: {\n    confirmTitle: '是否刪除角色：',\n    confirmMessage: '刪除後，該角色下的成員都會被移除，請謹慎操作。',\n  },\n  permission: {\n    title: '權限配置',\n    operationTarget: '操作對象',\n    moduleName: '模塊名稱'\n  },\n  member: {\n    title: '成員'\n  }\n};"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/shared.ts",
    "content": "export default {\n  title: '共享',\n  shared_resources: '共享資源',\n  shared_tool: '共享工具',\n  shared_model: '共享模型',\n  shared_knowledge: '共享知識庫',\n  authorized_workspace: '已授權工作空間',\n  authorized_tip: '被授權的工作空間，可以使用該共享資源',\n  select_workspace: '选择工作空間',\n  BLACK_LIST: '黑名单',\n  WHITE_LIST: '白名单',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/system.ts",
    "content": "export default {\n  title: '系統設置',\n  test: '測試連線',\n  testSuccess: '測試連線成功',\n  testFailed: '測試連線失敗',\n  password: '密碼',\n  defaultPassword: '預設密碼',\n  authentication: {\n    title: '登錄認證',\n    ldap: {\n      title: 'LDAP',\n      address: 'LDAP 位址',\n      serverPlaceholder: '請輸入 LDAP 位址',\n      bindDN: '綁定 DN',\n      bindDNPlaceholder: '請輸入綁定 DN',\n\n      ou: '使用者 OU',\n      ouPlaceholder: '請輸入使用者 OU',\n      ldap_filter: '使用者過濾器',\n      ldap_filterPlaceholder: '請輸入使用者過濾器',\n      ldap_mapping: 'LDAP 屬性對應',\n      ldap_mappingPlaceholder: '請輸入 LDAP 屬性對應',\n      enableAuthentication: '啟用 LDAP 認證',\n    },\n    cas: {\n      title: 'CAS',\n      ldpUri: 'ldpUri',\n      ldpUriPlaceholder: '請輸入 ldpUri',\n      validateUrl: '驗證位址',\n      validateUrlPlaceholder: '請輸入驗證位址',\n      redirectUrl: '回呼位址',\n      redirectUrlPlaceholder: '請輸入回呼位址',\n      enableAuthentication: '啟用 CAS 認證',\n    },\n    oidc: {\n      title: 'OIDC',\n      authEndpoint: '授權端位址',\n      authEndpointPlaceholder: '請輸入授權端位址',\n      tokenEndpoint: 'Token 端位址',\n      tokenEndpointPlaceholder: '請輸入 Token 端位址',\n      userInfoEndpoint: '使用者資訊端位址',\n      userInfoEndpointPlaceholder: '請輸入使用者資訊端位址',\n      clientId: '用戶端 ID',\n      scopePlaceholder: '請輸入連線範圍',\n      clientIdPlaceholder: '請輸入用戶端 ID',\n      clientSecret: '用戶端密鑰',\n      clientSecretPlaceholder: '請輸入用戶端密鑰',\n      logoutEndpoint: '登出端位址',\n      logoutEndpointPlaceholder: '請輸入登出端位址',\n      redirectUrl: '回呼位址',\n      redirectUrlPlaceholder: '請輸入回呼位址',\n      enableAuthentication: '啟用 OIDC 認證',\n    },\n\n    oauth2: {\n      title: 'OAuth2',\n      authEndpoint: '授權端位址',\n      authEndpointPlaceholder: '請輸入授權端位址',\n      tokenEndpoint: 'Token 端位址',\n      tokenEndpointPlaceholder: '請輸入 Token 端位址',\n      userInfoEndpoint: '使用者資訊端位址',\n      userInfoEndpointPlaceholder: '請輸入使用者資訊端位址',\n      scope: '連線範圍',\n      scopePlaceholder: '請輸入連線範圍',\n      clientId: '用戶端 ID',\n      clientIdPlaceholder: '請輸入用戶端 ID',\n      clientSecret: '用戶端密鑰',\n      clientSecretPlaceholder: '請輸入用戶端密鑰',\n      redirectUrl: '回呼位址',\n      redirectUrlPlaceholder: '請輸入回呼位址',\n      filedMapping: '欄位對應',\n      filedMappingPlaceholder: '請輸入欄位對應',\n      enableAuthentication: '啟用 OAuth2 認證',\n    },\n    saml2: {\n      title: 'SAML2',\n      ldp: 'Idp MetaData Url',\n      ldpPlaceholder: '請輸入 Idp MetaData Url',\n      enableAuthnRequests: '開啟請求簽名',\n      enableAssertions: '開啟斷言簽名',\n      privateKey: 'SP Private Key',\n      privateKeyPlaceholder: '請輸入 SP Private Key',\n      certificate: 'SP Certificate',\n      certificatePlaceholder: '請輸入 SP Certificate',\n      filedMapping: '欄位映射',\n      spEntityId: 'SP Entity Id',\n      spEntityIdPlaceholder: '請輸入 SP Entity Id',\n      spAcs: 'SP Ace',\n      spAcsPlaceholder: '請輸入 SP Ace',\n      filedMappingPlaceholder: '請輸入欄位映射',\n      enableAuthentication: '啟用 SAML2 認證',\n    },\n\n    scanTheQRCode: {\n      title: '掃碼登入',\n      wecom: '企業微信',\n      dingtalk: '釘釘',\n      lark: '飛書',\n      effective: '有效',\n      alreadyTurnedOn: '已開啟',\n      notEnabled: '未開啟',\n      validate: '驗證',\n      validateSuccess: '驗證成功',\n      validateFailed: '驗證失敗',\n      validateFailedTip: '請填寫所有必填項並確保格式正確',\n      appKeyPlaceholder: '請輸入 App Key',\n      appSecretPlaceholder: '請輸入 App Secret',\n      corpIdPlaceholder: '請輸入 Corp Id',\n      agentIdPlaceholder: '請輸入 Agent Id',\n      callbackWarning: '請輸入有效的 URL 位址',\n      larkQrCode: '飛書掃碼登錄',\n      dingtalkQrCode: '釘釘掃碼登錄',\n      setting: '設置',\n      access: '接入',\n    },\n  },\n  email: {\n    title: '郵箱設置',\n    smtpHost: 'SMTP Host',\n    smtpHostPlaceholder: '請輸入 SMTP Host',\n    smtpPort: 'SMTP Port',\n    smtpPortPlaceholder: '請輸入 SMTP Port',\n    smtpUser: 'SMTP 帳戶',\n    smtpUserPlaceholder: '請輸入 SMTP 帳戶',\n    sendEmail: '發件人信箱',\n    sendEmailPlaceholder: '請輸入發件人信箱',\n    smtpPassword: '發件人密碼',\n    smtpPasswordPlaceholder: '請輸入發件人密碼',\n    enableSSL: '啟用 SSL（如果 SMTP 端口是 465，通常需要啟用 SSL）',\n    enableTLS: '啟用 TLS（如果 SMTP 端口是 587，通常需要啟用 TLS）',\n  },\n\n  resourceAuthorization: {\n    title: '資源授權',\n    member: '成員',\n    permissionSetting: '資源權限配置',\n    setting: {\n      management: '管理',\n      managementDesc: '可對該資源進行刪改操作',\n      check: '查看',\n      checkDesc: '僅能查看使用該資源',\n      role: '按用戶角色',\n      roleDesc: '根據用戶角色中的權限授權用戶對該資源的操作權限',\n      notAuthorized: '不授權',\n      configure: '配置權限',\n      currentOnly: '僅當前資源',\n      includeAll: '包含所有子資源',\n      effectiveResource: '生效資源',\n      defaultPermission: '預設權限',\n      defaultPermissionTip: '所選工作空間下所有資源的預設權限',\n    },\n  },\n  resource_management: {\n    label: '資源管理',\n    management: '管理',\n  },\n  default_login: '預設登入方式',\n  login_method: '登入方式',\n  display_code: '帳號登入驗證碼設定',\n  loginFailed: '登入失敗',\n  loginFailedMessage: '次顯示驗證碼',\n  display_codeTip: '值為-1時，不顯示驗證碼',\n  failedTip: '次，鎖定帳號',\n  minute: '分鐘',\n  time: '次',\n  setting: '登录設置',\n  third_party_user_default_role: '第三方用戶預設角色分配',\n  resourceMapping: {\n    title: '查看關聯資源',\n    sub_title: '關聯資源',\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/tool.ts",
    "content": "export default {\n  title: '工具',\n  createTool: '创建工具',\n  editTool: '編輯工具',\n  copyTool: '複製工具',\n  importTool: '匯入工具',\n  settingTool: '設定工具',\n  updatedVersion: '更新版本',\n  dataSource: {\n    title: '數據源',\n    createDataSource: '創建數據源',\n    editDataSource: '編輯數據源',\n    copyDataSource: '複製數據源',\n    selectDataSource: '選擇數據源',\n    requiredMessage: '請選擇數據源',\n  },\n  toolStore: {\n    title: '工具商店',\n    createFromToolStore: '從工具商店創建',\n    internal: '系统内置',\n    recommend: '推薦',\n    webSearch: '聯網搜索',\n    databaseQuery: '數據庫查詢',\n    image: '圖像',\n    developer: '開發者',\n    communication: '通信',\n    searchResult: '的搜索結果 {count} 個',\n    confirmTip: '是否更新工具：',\n    updateStoreToolMessage: '更新工具可能會影響正在使用的資源，請謹慎操作。',\n  },\n  mcp: {\n    title: 'MCP 服務',\n    label: 'MCP Server Config',\n    placeholder: '請輸入 MCP Server配置',\n    tip: '僅支援 SSE、Streamable HTTP 呼叫方式',\n    requiredMessage: '請輸入 MCP Server Config',\n    createMcpTool: '建立 MCP',\n    editMcpTool: '編輯 MCP',\n    copyMcpTool: '複製 MCP',\n    mcpConfig: 'MCP服務配置',\n  },\n  skill: {\n    title: '技能',\n    copySkillTool: '複製 Skills',\n    createSkillTool: '創建 Skills',\n    editSkillTool: '編輯 Skills',\n    initParamPlaceholder: '啟用 Skills 時需要配置的參數',\n    skillFile: 'Skills 文件',\n    reUpload: '重新上傳',\n  },\n  tip: {\n    saveMessage: '當前的更改尚未保存，確認退出嗎？',\n  },\n  delete: {\n    confirmTitle: '是否刪除工具',\n    confirmMessage: '刪除後，引用該工具的智能體在查詢時會報錯，請謹慎操作。',\n    resourceCountMessage: '此工具關聯 {count} 個資源，刪除後無法使用，請謹慎操作。',\n  },\n  disabled: {\n    confirmTitle: '是否停用工具：',\n    confirmMessage: '停用後，引用該工具的智能體在查詢時會報錯，請謹慎操作。',\n  },\n  form: {\n    toolName: {\n      name: '工具名稱',\n      placeholder: '請輸入工具名稱',\n      requiredMessage: '請輸入工具名稱',\n    },\n    mcpName: {\n      name: 'MCP 名稱',\n      placeholder: '請輸入 MCP 名稱',\n      requiredMessage: '請輸入 MCP 名稱',\n    },\n    paramName: {\n      label: '參數名',\n      placeholder: '請輸入參數名',\n      requiredMessage: '請輸入參數名',\n    },\n    dataType: {\n      label: '數據類型',\n    },\n    source: {\n      label: '來源',\n      reference: '引用參數',\n    },\n    param: {\n      paramInfo1: '使用工具時顯示',\n      paramInfo2: '使用工具時不顯示',\n      code: '工具内容（Python）',\n      selectPlaceholder: '請选择參數',\n      inputPlaceholder: '請輸入參數值',\n    },\n    debug: {\n      run: '運行',\n      output: '輸出',\n      runResult: '運行結果',\n      runSuccess: '運行成功',\n      runFailed: '運行失敗',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/trigger.ts",
    "content": "export default {\n  title: '觸發器',\n  tip: '觸發時系統將會根據入參設置自動調用任務執行',\n  task: '任務',\n  nextTime: '下次執行時間',\n  triggerTask: '觸發任務',\n  taskExecution: '任務執行',\n  triggerSource: '觸發來源',\n  delete: {\n    confirmTitle: '是否刪除觸發器：',\n    confirmTitle2: '個觸發器？',\n  },\n  triggerCycle: {\n    title: '觸發週期',\n    days: '日',\n    daily: '每日觸發',\n    weekly: '每週觸發',\n    monthly: '每月觸發',\n    interval: '間隔觸發',\n    monday: '星期一',\n    tuesday: '星期二',\n    wednesday: '星期三',\n    thursday: '星期四',\n    friday: '星期五',\n    saturday: '星期六',\n    sunday: '星期日',\n    hours: '小時',\n    minutes: '分鐘',\n    cronExpression: 'Cron 表達式',\n    switchCycle: '切換到觸發循環',\n    switchCron: '切換到Cron運算式',\n    placeholder: '請輸入Cron表達式（如：0 0 1 * *）'\n  },\n  type: {\n    scheduled: '定時觸發',\n    scheduledDesc: '每月、每週、每日或間隔時間執行任務',\n    event: '事件觸發',\n    eventDesc: '當某個事件發送時執行任務',\n  },\n  createTrigger: '創建觸發器',\n  editTrigger: '修改觸發器',\n  from: {\n    triggerName: {\n      label: '觸發器名稱',\n      placeholder: '請輸入觸發器名稱',\n      requiredMessage: '請輸入觸發器名稱',\n    },\n    event_url: {\n      label: '複製 URL 到你的應用',\n    },\n  },\n  requestParameter: '請求參數',\n  triggerParam: '觸發器入參',\n  errorMsg: '錯誤信息',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/user-manage.ts",
    "content": "export default {\n  title: '用戶管理',\n  createUser: '建立用戶',\n  editUser: '編輯用戶',\n  roleSetting: '角色設定',\n  addRole: '添加角色',\n  setting: {\n    updatePwd: '修改用戶密碼',\n  },\n  tip: {\n    professionalMessage: '社群版最多支援 2 個使用者，如需擁有更多使用者，請升級為專業版。',\n    updatePwdSuccess: '使用者密碼修改成功',\n  },\n  delete: {\n    confirmTitle: '是否刪除該用戶？',\n    confirmMessage:\n      '刪除該用戶後，該使用者建立的所有資源（智能體、知識庫、模型）都不會被刪除，請謹慎操作。',\n  },\n  disabled: {\n    confirmTitle: '是否停用工具？',\n    confirmMessage: '停用後，引用該工具的智能體在查詢時會報錯，請謹慎操作。',\n  },\n  userForm: {\n    nick_name: {\n      label: '姓名',\n      placeholder: '請輸入姓名',\n      lengthMessage: '長度須介於 2 到 20 個字元之間',\n    },\n\n    phone: {\n      label: '手機號碼',\n      placeholder: '請輸入手機號碼',\n      invalidMessage: '手機號碼格式不正確',\n    },\n  },\n  source: {\n    label: '用戶來源',\n    local: '系統用戶',\n    localCreate: '本地建立',\n    wecom: '企業微信',\n    lark: '飛書',\n    dingtalk: '釘釘',\n  },\n  settingRole: '設定角色',\n  defaultPassword: '預設密碼',\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/views/workspace.ts",
    "content": "export default {\n  title: '工作空間',\n  list: '工作空間列表',\n  name: '工作空間名稱',\n  toWorkspace: '返回工作空間',\n  delete: {\n    confirmTitle: '是否刪除工作空間：',\n    confirmContent: '刪除後，該空間下的成員都會被移除，請謹慎操作。',\n    confirmContentNotDelete: '該工作空間下存在知識庫資源、智能體資源，無法刪除。',\n  },\n  member: {\n    delete: {\n      confirmTitle: '是否移除成員：',\n    },\n  },\n}\n"
  },
  {
    "path": "ui/src/locales/lang/zh-Hant/workflow.ts",
    "content": "export default {\n  node: '節點',\n  nodeName: '節點名稱',\n  baseComponent: '基礎組件',\n  nodeSetting: '節點設置',\n  workflow: '工作流',\n  knowledgeWorkflow: '知識庫工作流',\n  info: {\n    previewVersion: '預覽版本：',\n    saveTime: '保存時間：',\n  },\n  operation: {\n    toImportDoc: '去導入文檔',\n    importWorkflow: '導入工作流',\n    exportWorkflow: '導出工作流',\n  },\n  setting: {\n    restoreVersion: '恢復版本',\n    restoreCurrentVersion: '恢復此版本',\n    addComponent: '添加組件',\n    releaseHistory: '發布歷史',\n    autoSave: '自動保存',\n    latestRelease: '最近發布',\n    copyParam: '複製參數',\n    exit: '直接退出',\n    exitSave: '保存並退出',\n    templateCenter: '模板中心',\n  },\n  tip: {\n    noData: '沒有找到相關結果',\n    nameMessage: '名字不能為空！',\n    onlyRight: '只允許從右邊的錨點連出',\n    notRecyclable: '不可循環連線',\n    onlyLeft: '只允許連接左邊的錨點',\n    applicationNodeError: '該智能體不可用',\n    toolNodeError: '該函數不可用',\n    repeatedNodeError: '節點名稱已存在！',\n    cannotCopy: '不能被複製',\n    copyError: '已複製節點',\n    paramErrorMessage: '參數已存在: ',\n    saveMessage: '當前修改未保存，是否保存後退出？',\n    searchPlaceholder: '請輸入節點名稱',\n  },\n  delete: {\n    confirmTitle: '確定刪除該節點？',\n    deleteMessage: '節點不允許刪除',\n  },\n  control: {\n    zoomOut: '縮小',\n    zoomIn: '放大',\n    fitView: '適應',\n    retract: '收起全部節點',\n    extend: '展開全部節點',\n    beautify: '一鍵美化',\n  },\n  variable: {\n    global: '全局變量',\n    chat: '會話變量',\n    Referencing: '引用變量',\n    ReferencingRequired: '引用變量必填',\n    ReferencingError: '引用變量錯誤',\n    NoReferencing: '不存在的引用變量',\n    placeholder: '請選擇變量',\n    inputPlaceholder: '請輸入變量',\n    loop: '循環變量',\n  },\n  condition: {\n    title: '執行條件',\n    front: '前置',\n    AND: '所有',\n    OR: '任一',\n    text: '連線節點執行完，執行當前節點',\n  },\n  validate: {\n    startNodeRequired: '開始節點必填',\n    startNodeOnly: '開始節點只能有一個',\n    baseNodeRequired: '基本信息節點必填',\n    baseNodeOnly: '基本信息節點只能有一個',\n    notInWorkFlowNode: '未在流程中的節點',\n    noNextNode: '不存在的下一個節點',\n    nodeUnavailable: '節點不可用',\n    needConnect1: '節點的',\n    needConnect2: '分支需要連接',\n    cannotEndNode: '節點不能當做結束節點',\n    loopNodeBreakNodeRequired: '無限循環必須存在 Break 節點',\n  },\n  nodes: {\n    knowledgeWriteNode: {\n      label: '知識庫寫入',\n      text: '將輸入的分段列表寫入當前知識庫，並完成向量化處理',\n    },\n    dataSourceWebNode: {\n      label: 'Web 網站',\n      text: '輸入根地址自動抓取Web數據（單鏈接對應單文檔），輸出含內容的文檔列表',\n      field_label: '文件列表',\n    },\n    dataSourceLocalNode: {\n      label: '本地文件',\n      text: '上傳本地文件，輸出文件列表（不解析內容，需配合 “文檔內容提取” 節點解析）',\n      fileList: '文件列表',\n      fileFormat: {\n        label: '支持的文件格式',\n        requiredMessage: '請選擇文件格式',\n      },\n      maxFileNumber: {\n        label: '每次上傳最大文件數',\n      },\n      maxFileCountNumber: {\n        label: '上傳的每個文檔最大(MB)',\n      },\n    },\n    classify: {\n      aiCapability: 'AI 能力',\n      businessLogic: '業務邏輯',\n      other: '其他',\n      dataProcessing: '數據處理',\n    },\n    startNode: {\n      label: '開始',\n      question: '用戶問題',\n      currentTime: '當前時間',\n    },\n    baseNode: {\n      fileUpload: {\n        label: '文件上傳',\n        tooltip: '開啟後，問答頁面會顯示上傳文件的按鈕。',\n      },\n      FileUploadSetting: {\n        title: '文件上傳設置',\n        maxFiles: '單次上傳最多文件數',\n        fileLimit: '每個文件最大（MB）',\n        fileUploadType: {\n          label: '上傳的文件類型',\n          documentText: '需要使用「文檔內容提取」節點解析文檔內容',\n          imageText: '需要使用「圖片理解」節點解析圖片內容',\n          videoText: '需要使用「視頻理解」節點解析視頻內容',\n          audioText: '需要使用「語音轉文本」節點解析音頻內容',\n          uploadMethod: '上傳方式',\n        },\n      },\n    },\n    KnowledgeBaseNode: {\n      DocumentSetting: '文檔處理設置',\n    },\n    aiChatNode: {\n      label: 'AI 對話',\n      text: '與 AI 大模型進行對話',\n      answer: 'AI 回答內容',\n      returnContent: {\n        label: '返回內容',\n        tooltip: `關閉後該節點的內容則不輸出給用戶。\n                  如果你想讓用戶看到該節點的輸出內容，請打開開關。`,\n      },\n      defaultPrompt: '已知信息',\n      think: '思考過程',\n      historyMessage: '歷史聊天記錄',\n    },\n    searchKnowledgeNode: {\n      label: '知識庫檢索',\n      text: '關聯知識庫，查找與問題相關的分段',\n      paragraph_list: '檢索結果的分段列表',\n      is_hit_handling_method_list: '滿足直接回答的分段列表',\n      result: '檢索結果',\n      directly_return: '滿足直接回答的分段內容',\n      searchParam: '檢索參數',\n      showKnowledge: {\n        label: '結果顯示在知識來源',\n        requiredMessage: '請設定參數',\n      },\n      searchQuestion: {\n        label: '檢索問題',\n        placeholder: '請選擇檢索問題',\n        requiredMessage: '請選擇檢索問題',\n      },\n    },\n    searchDocumentNode: {\n      label: '文檔標籤檢索',\n      text: '從設定的檢索範圍中，根據文檔標籤檢索出符合條件的文檔',\n      selectKnowledge: '檢索範圍',\n      searchSetting: '檢索設定',\n      custom: '手動',\n      customTooltip: '手動設置標籤過濾條件',\n      auto: '自動',\n      autoTooltip: '根據檢索問題自動匹配文檔標簽',\n      documentList: '文檔列表',\n      knowledgeList: '知識庫列表',\n      result: '檢索結果',\n      searchParam: '檢索參數',\n      select_variable: '選擇變數',\n      valueMessage: `值或變量`,\n\n      searchQuestion: {\n        label: '檢索問題',\n        placeholder: '請選擇檢索問題',\n        requiredMessage: '請選擇檢索問題',\n      },\n    },\n    questionNode: {\n      label: '問題優化',\n      text: '根據歷史聊天記錄優化完善當前問題，更利於匹配知識庫分段',\n      result: '問題優化結果',\n      systemDefault: `# 角色\n妳是壹位問題優化大師，擅長根據上下文精準揣測用戶意圖，並對用戶提出的問題進行優化。\n\n## 技能\n### 技能 1: 優化問題\n2. 接收用戶輸入的問題。\n3. 依據上下文仔細分析問題含義。\n4. 輸出優化後的問題。\n\n## 限制:\n- 僅返回優化後的問題，不進行額外解釋或說明。\n- 確保優化後的問題準確反映原始問題意圖，不得改變原意。`,\n    },\n    conditionNode: {\n      label: '判斷器',\n      text: '根據不同條件執行不同的節點',\n      branch_name: '分支名稱',\n      conditions: {\n        label: '條件',\n        info: '符合以下',\n        requiredMessage: '請選擇條件',\n      },\n      valueMessage: '請輸入值',\n      addCondition: '添加條件',\n      addBranch: '添加分支',\n    },\n    replyNode: {\n      label: '指定回覆',\n      text: '指定回覆內容，引用變量會轉換為字符串進行輸出',\n      replyContent: '回覆內容',\n    },\n    rerankerNode: {\n      label: '多路召回',\n      text: '使用重排模型對多個知識庫的檢索結果進行二次召回',\n      result_list: '重排結果列表',\n      result: '重排結果',\n      rerankerContent: {\n        label: '重排內容',\n        requiredMessage: '請選擇重排內容',\n      },\n      higher: '高於',\n      ScoreTooltip: 'Score 越高相關性越強。',\n      max_paragraph_char_number: '最大引用字符數',\n      reranker_model: {\n        label: '重排模型',\n        placeholder: '請選擇重排模型',\n      },\n    },\n    formNode: {\n      label: '表單收集',\n      text: '在問答過程中用於收集用戶信息，可以根據收集到表單數據執行後續流程',\n      form_content_format1: '你好，請先填寫下面表單內容：',\n      form_content_format2: '填寫後請點擊【提交】按鈕進行提交。',\n      form_data: '表單全部內容',\n      formContent: {\n        label: '表單輸出內容',\n        requiredMessage: '請表單輸出內容',\n        tooltip: '設置執行該節點輸出的內容，{ form } 為表單的佔位符。',\n      },\n      formAllContent: '表單全部內容',\n      formSetting: '表單配置',\n    },\n    documentExtractNode: {\n      label: '文檔內容提取',\n      text: '解析輸入文檔，輸出結構化文檔內容',\n      content: '文檔內容',\n    },\n    documentSplitNode: {\n      label: '文檔拆分',\n      text: '按分段策略拆分輸入文檔內容，輸出分段文本列表',\n      paragraphList: '分段列表',\n      splitStrategy: {\n        label: '分段策略',\n        placeholder: '請選擇分段策略',\n        requiredMessage: '請選擇分段策略',\n      },\n      chunk_length: {\n        label: '子分塊長度',\n        tooltip1: '核心目標是平衡檢索精度與召回效率',\n        tooltip2:\n          '避免過短拆分：單塊＜50 字易導致語義碎片化，檢索時可能因缺少上下文無法匹配查詢意圖',\n        tooltip3:\n          '避免過長拆分：單塊＞500 字會增加冗餘信息，降低檢索精准度，且佔用更多存儲和計算資源',\n      },\n      title1: '分段標題設置為分段的關聯問題',\n      title2: '文檔名稱設置為分段的關聯問題',\n    },\n    videoUnderstandNode: {\n      label: '视频理解',\n      text: '识别出视频中的对象、场景等信息回答用户问题',\n      answer: 'AI 回答内容',\n      model: {\n        label: '视觉模型',\n        requiredMessage: '请选择视觉模型',\n      },\n      video: {\n        label: '选择视频',\n        requiredMessage: '请选择视频',\n      },\n    },\n    imageUnderstandNode: {\n      label: '圖片理解',\n      text: '識別出圖片中的物件、場景等信息回答用戶問題',\n      answer: 'AI 回答內容',\n      model: {\n        label: '視覺模型',\n        requiredMessage: '請選擇視覺模型',\n      },\n      image: {\n        label: '選擇圖片',\n        requiredMessage: '請選擇圖片',\n      },\n    },\n    variableAssignNode: {\n      label: '變數賦值',\n      text: '更新全域變數的值',\n      assign: '賦值',\n    },\n    variableAggregationNode: {\n      label: '變量聚合',\n      text: '按聚合策略聚合每組的變量',\n      Strategy: '聚合策略',\n      placeholder: '返回每組的第一個非空值',\n      placeholder1: '返回每組變量的集合',\n      group: {\n        noneError: '名稱不能為空',\n        dupError: '名稱不能重複',\n      },\n      addGroup: '添加分組',\n      editGroup: '編輯分組',\n    },\n    mcpNode: {\n      label: 'MCP 調用',\n      text: '通過 SSE/Streamable HTTP 方式執行 MCP 服務中的工具',\n      getToolsSuccess: '獲取工具成功',\n      getTool: '獲取工具',\n      toolParam: '工具參數',\n      mcpServerTip: '請輸入 JSON 格式的 MCP 服務器配置',\n      mcpToolTip: '請選擇工具',\n      configLabel: 'MCP Server Config (僅支持SSE/Streamable HTTP 調用方式)',\n      reference: '引用MCP',\n    },\n    imageGenerateNode: {\n      label: '圖片生成',\n      text: '根據提供的文本內容生成圖片',\n      answer: 'AI 回答內容',\n      model: {\n        label: '圖片生成模型',\n        requiredMessage: '請選擇圖片生成模型',\n      },\n      prompt: {\n        label: '提示詞(正向)',\n        tooltip: '正向提示詞，用來描述生成圖像中期望包含的元素和視覺特點',\n      },\n      negative_prompt: {\n        label: '提示詞(負向)',\n        tooltip: '反向提示詞，用來描述不希望在畫面中看到的內容，可以對畫面進行限制。',\n        placeholder: '請描述不想生成的圖片內容，比如：顏色、血腥內容',\n      },\n    },\n    textToVideoGenerate: {\n      label: '文生影片',\n      text: '根據提供的文字內容生成影片',\n      answer: 'AI 回答內容',\n      model: {\n        label: '文生影片模型',\n        requiredMessage: '請選擇文生影片模型',\n      },\n      prompt: {\n        label: '提示詞(正向)',\n        tooltip: '正向提示詞，用來描述生成影片中期望包含的元素和視覺特點',\n      },\n      negative_prompt: {\n        label: '提示詞(負向)',\n        tooltip: '反向提示詞，用來描述不希望在影片中看到的內容，可以對影片進行限制。',\n        placeholder: '請描述不想生成的影片內容，例如：顏色、血腥內容',\n      },\n    },\n    imageToVideoGenerate: {\n      label: '圖生影片',\n      text: '根據提供的圖片生成影片',\n      answer: 'AI 回答內容',\n      model: {\n        label: '圖生影片模型',\n        requiredMessage: '請選擇圖生影片模型',\n      },\n      prompt: {\n        label: '提示詞(正向)',\n        tooltip: '正向提示詞，用來描述生成影片中期望包含的元素和視覺特點',\n      },\n      negative_prompt: {\n        label: '提示詞(負向)',\n        tooltip: '反向提示詞，用來描述不希望在影片中看到的內容，可以對影片進行限制。',\n        placeholder: '請描述不想生成的影片內容，例如：顏色、血腥內容',\n      },\n      first_frame: {\n        label: '首幀圖片',\n        requiredMessage: '請選擇首幀圖片',\n      },\n      last_frame: {\n        label: '尾幀圖片',\n        requiredMessage: '請選擇尾幀圖片',\n      },\n    },\n    speechToTextNode: {\n      label: '語音轉文本',\n      text: '將音頻通過語音識別模型轉換為文本',\n      stt_model: {\n        label: '語音識別模型',\n      },\n      audio: {\n        label: '選擇語音文件',\n        placeholder: '請選擇語音文件',\n      },\n    },\n    textToSpeechNode: {\n      label: '文本轉語音',\n      text: '將文本通過語音合成模型轉換為音頻',\n      tts_model: {\n        label: '語音合成模型',\n      },\n      content: {\n        label: '選擇文本內容',\n      },\n    },\n    toolNode: {\n      label: '自定義工具',\n      text: '通過執行自定義腳本，實現數據處理',\n    },\n    intentNode: {\n      label: '意圖識別',\n      text: '將用戶問題與用戶預設的意圖分類進行匹配',\n      other: '其他',\n      error2: '意圖重複',\n      placeholder: '請選擇分類項',\n      classify: {\n        label: '意圖分類',\n      },\n      input: {\n        label: '輸入',\n      },\n    },\n    applicationNode: {\n      label: '智能體節點',\n    },\n    loopNode: {\n      label: '循環節點',\n      text: '通過設置循環次數和邏輯，重複執行一系列任務',\n      loopType: {\n        label: '循環類型',\n        requiredMessage: '請選擇循環類型',\n        arrayLoop: '數組循環',\n        numberLoop: '指定次數循環',\n        infiniteLoop: '無限循環',\n      },\n      loopNumber: {\n        label: '循環次數',\n        requiredMessage: '請填寫循環次數',\n      },\n      loopArray: {\n        label: '循環數組',\n        requiredMessage: '循環數組必填',\n        placeholder: '請選擇循環數組',\n      },\n      loopSetting: '循環設置',\n      loopDetail: '循環詳情',\n    },\n    loopStartNode: {\n      label: '循環開始',\n      loopIndex: '下標',\n      loopItem: '循環元素',\n    },\n    loopBodyNode: { label: '循環體', text: '循環體' },\n    loopContinueNode: {\n      label: 'Continue',\n      text: '用於終止當前循環，執行下次循環',\n      isContinue: 'Continue',\n    },\n    loopBreakNode: { label: 'Break', text: '終止當前循環，跳出循環體', isBreak: 'Break' },\n    variableSplittingNode: {\n      label: '變量拆分',\n      text: '通過配置 JSON Path 表達式，對輸入的 JSON 格式變量進行解析和拆分',\n      result: '結果',\n      splitVariables: '拆分變量',\n      inputVariables: '輸入變量',\n      addVariables: '添加變量',\n      editVariables: '編輯變量',\n      variableListPlaceholder: '請添加折開變數',\n      expression: {\n        label: '表達式',\n        placeholder: '請輸入表達式',\n        tooltip: '請使用 JSON Path 表達式拆分變量，例如：$.store.book',\n      },\n    },\n    parameterExtractionNode: {\n      label: '參數提取',\n      text: '利用 AI 模型提取結構化參數',\n      extractParameters: {\n        label: '提取參數',\n        variableListPlaceholder: '請添加選取參數',\n        parameterType: '參數類型',\n      },\n    },\n  },\n  compare: {\n    is_null: '為空',\n    is_not_null: '不為空',\n    contain: '包含',\n    not_contain: '不包含',\n    eq: '等於',\n    not_eq: '不等於',\n    ge: '大於等於',\n    gt: '大於',\n    le: '小於等於',\n    lt: '小於',\n    len_eq: '長度等於',\n    len_ge: '長度大於等於',\n    len_gt: '長度大於',\n    len_le: '長度小於等於',\n    len_lt: '長度小於',\n    is_true: '為真',\n    is_not_true: '不為真',\n  },\n  SystemPromptPlaceholder: '系統提示詞，可以引用系統中的變量：如',\n  UserPromptPlaceholder: '用戶提示詞，可以引用系統中的變量：如',\n  initiator: '發起人',\n  abnormalInformation: '異常信息',\n}\n"
  },
  {
    "path": "ui/src/locales/useLocale.ts",
    "content": "import { useLocalStorage } from '@vueuse/core'\nimport { computed } from 'vue'\nimport { useI18n } from 'vue-i18n'\nimport { i18n, langCode, localeConfigKey } from '@/locales/index'\n\nexport function useLocale() {\n  const { locale } = useI18n({ useScope: 'global' })\n  function changeLocale(lang: string) {\n    // 如果切换的语言不在对应语言文件里则默认为简体中文\n    if (!langCode.includes(lang)) {\n      lang = 'en-US'\n    }\n\n    locale.value = lang\n    useLocalStorage(localeConfigKey, 'en-US').value = lang\n  }\n\n  const getComponentsLocale = computed(() => {\n    const localeMessage = i18n.global.getLocaleMessage(locale.value) as Record<string, any>\n    return localeMessage.componentsLocale\n  })\n\n  return {\n    changeLocale,\n    getComponentsLocale,\n    locale,\n  }\n}\n"
  },
  {
    "path": "ui/src/main.ts",
    "content": "import '@/styles/index.scss'\nimport ElementPlus from 'element-plus'\nimport * as ElementPlusIcons from '@element-plus/icons-vue'\nimport zhCn from 'element-plus/es/locale/lang/zh-cn'\nimport enUs from 'element-plus/es/locale/lang/en'\nimport zhTW from 'element-plus/es/locale/lang/zh-tw'\nimport { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from './App.vue'\nimport router from '@/router'\nimport i18n from '@/locales'\nimport Components from '@/components'\nimport directives from '@/directives'\nimport { getDefaultWhiteList } from 'xss'\nimport { config, XSSPlugin } from 'md-editor-v3'\nimport screenfull from 'screenfull'\n\nimport katex from 'katex'\nimport 'katex/dist/katex.min.css'\n\nimport Cropper from 'cropperjs'\nimport 'cropperjs/dist/cropper.css'\n\nimport mermaid from 'mermaid'\n\nimport highlight from 'highlight.js'\nimport 'highlight.js/styles/atom-one-dark.css'\n\nconfig({\n  editorExtensions: {\n    highlight: {\n      instance: highlight,\n    },\n    screenfull: {\n      instance: screenfull,\n    },\n    katex: {\n      instance: katex,\n    },\n    cropper: {\n      instance: Cropper,\n    },\n    mermaid: {\n      instance: mermaid,\n    },\n  },\n  markdownItPlugins(plugins) {\n    return [\n      ...plugins,\n      {\n        type: 'xss',\n        plugin: XSSPlugin,\n        options: {\n          xss() {\n            return {\n              whiteList: Object.assign({}, getDefaultWhiteList(), {\n                video: ['src', 'controls', 'width', 'height', 'preload', 'playsinline'],\n                source: ['src', 'type'],\n                input: ['class', 'disabled', 'type', 'checked'],\n                iframe: [\n                  'class',\n                  'width',\n                  'height',\n                  'src',\n                  'title',\n                  'border',\n                  'frameborder',\n                  'framespacing',\n                  'allow',\n                  'allowfullscreen',\n                ],\n              }),\n              onTagAttr: (tag: string, name: any, value: any) => {\n                if (tag === 'video') {\n                  // 禁止自动播放\n                  if (name === 'autoplay') return ''\n\n                  // 限制 preload\n                  if (name === 'preload' && !['none', 'metadata'].includes(value)) {\n                    return 'preload=\"metadata\"'\n                  }\n                }\n                return undefined\n              },\n            }\n          },\n        },\n      },\n    ]\n  },\n})\nconst app = createApp(App)\napp.use(createPinia())\nfor (const [key, component] of Object.entries(ElementPlusIcons)) {\n  app.component(key, component)\n}\nconst locale_map: any = {\n  'zh-CN': zhCn,\n  'zh-Hant': zhTW,\n  'en-US': enUs,\n}\napp.use(ElementPlus, {\n  locale: locale_map[localStorage.getItem('MaxKB-locale') || navigator.language || 'en-US'],\n})\napp.use(directives)\napp.use(router)\napp.use(i18n)\napp.use(Components)\napp.mount('#app')\n"
  },
  {
    "path": "ui/src/permission/application/index.ts",
    "content": "import workspace from './workspace'\nimport systemManage from './system-manage'\nconst permission = {\n  workspace,\n  systemManage,\n}\nexport default permission\n"
  },
  {
    "path": "ui/src/permission/application/system-manage.ts",
    "content": "import {hasPermission} from '@/utils/permission/index'\nimport {PermissionConst, RoleConst} from '@/utils/permission/data'\n\nconst systemManage = {\n    create: () => false,\n    folderCreate: () => false,\n    edit: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_EDIT\n            ],\n            'OR'\n    ),\n    folderEdit: () => false,\n    folderRead: () => false,\n    folderManage: () => false,\n    folderAuth: () => false,\n    export: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_EXPORT\n            ],\n            'OR'\n    ),\n    delete: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_DELETE\n            ],\n            'OR'\n    ),\n    trigger_read: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_TRIGGER_READ\n            ],\n            'OR'\n    ),\n    trigger_create: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_TRIGGER_CREATE\n            ],\n            'OR'\n    ),\n    trigger_edit: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_TRIGGER_EDIT\n            ],\n            'OR'\n    ),\n    trigger_delete: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_TRIGGER_DELETE\n            ],\n            'OR'\n    ),\n    debug: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_EDIT\n            ],\n            'OR'\n    ),\n    folderDelete: () => false,\n    auth: () => \n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_AUTH\n            ],\n            'OR'\n    ),\n    overview_embed: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_OVERVIEW_EMBED\n            ],\n            'OR'\n    ),\n    overview_access: () =>\n      hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_OVERVIEW_ACCESS\n            ],\n            'OR'\n    ),\n    overview_display: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_OVERVIEW_DISPLAY\n            ],\n            'OR'\n    ),\n    overview_api_key: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_OVERVIEW_API_KEY\n            ],\n            'OR'\n    ),\n    access_edit: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_ACCESS_EDIT\n            ],\n            'OR'\n    ),\n    application_chat_user_edit: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_CHAT_USER_EDIT\n            ],\n            'OR'\n    ),\n    chat_log_clear: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_CHAT_LOG_CLEAR_POLICY\n            ],\n            'OR'\n    ),\n    chat_log_export: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_CHAT_LOG_EXPORT\n            ],\n            'OR'\n    ),\n    chat_log_add_knowledge: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_APPLICATION_CHAT_LOG_ADD_KNOWLEDGE\n            ],\n            'OR'\n    ),\n    overview_read: () => \n      hasPermission(\n        [\n          RoleConst.ADMIN,\n          PermissionConst.RESOURCE_APPLICATION_OVERVIEW_READ\n        ],\n        'OR'\n    ),\n    jump_read: () => \n      hasPermission(\n        [\n          RoleConst.ADMIN,\n          PermissionConst.RESOURCE_APPLICATION_OVERVIEW_READ,\n          PermissionConst.RESOURCE_APPLICATION_READ,\n        ],\n        'OR'\n    ),  \n    access_read: () => \n      hasPermission(\n        [\n          RoleConst.ADMIN,\n          PermissionConst.RESOURCE_APPLICATION_ACCESS_READ\n        ],'OR'    \n    ),\n    chat_user_read: () => \n      hasPermission(\n        [\n          RoleConst.ADMIN,\n          PermissionConst.RESOURCE_APPLICATION_CHAT_USER_READ\n        ],'OR'\n    ),\n    chat_user_edit: () =>false,\n\n    chat_log_read: () => \n      hasPermission(\n        [\n          RoleConst.ADMIN,\n          PermissionConst.RESOURCE_APPLICATION_CHAT_LOG_READ\n        ],\n        'OR')\n}\nexport default systemManage\n"
  },
  {
    "path": "ui/src/permission/application/workspace.ts",
    "content": "import { hasPermission } from '@/utils/permission/index'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\nconst workspace = {\n    create: () => \n        hasPermission(\n            [\n              RoleConst.USER.getWorkspaceRole,\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_CREATE.getWorkspacePermission,\n              PermissionConst.APPLICATION_CREATE.getWorkspacePermissionWorkspaceManageRole,  \n            ],\n            'OR'\n    ),\n    folderCreate: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(folder_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_FOLDER_CREATE.getApplicationWorkspaceResourcePermission(folder_id),\n              PermissionConst.APPLICATION_FOLDER_CREATE.getWorkspacePermissionWorkspaceManageRole,  \n            ],\n            'OR'\n    ),\n    folderRead: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(folder_id)],[],'AND'),  \n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_FOLDER_READ.getApplicationWorkspaceResourcePermission(folder_id),\n              PermissionConst.APPLICATION_FOLDER_READ.getWorkspacePermissionWorkspaceManageRole,  \n            ],\n            'OR'\n    ),\n    folderEdit: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(folder_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_FOLDER_EDIT.getApplicationWorkspaceResourcePermission(folder_id),\n              PermissionConst.APPLICATION_FOLDER_EDIT.getWorkspacePermissionWorkspaceManageRole,\n            ],\n            'OR'\n    ),\n    folderAuth: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(folder_id)],[],'AND'),  \n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_FOLDER_AUTH.getApplicationWorkspaceResourcePermission(folder_id),\n              PermissionConst.APPLICATION_FOLDER_AUTH.getWorkspacePermissionWorkspaceManageRole,  \n            ],\n            'OR'\n    ),\n    folderDelete: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(folder_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_FOLDER_DELETE.getApplicationWorkspaceResourcePermission(folder_id),\n              PermissionConst.APPLICATION_FOLDER_DELETE.getWorkspacePermissionWorkspaceManageRole\n            ],\n            'OR'\n    ),\n    folderManage: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(folder_id)],[],'AND'),  \n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_FOLDER_EDIT.getApplicationWorkspaceResourcePermission(folder_id),\n              PermissionConst.APPLICATION_FOLDER_EDIT.getWorkspacePermissionWorkspaceManageRole,  \n            ],\n            'OR'\n    ),\n    edit: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_EDIT.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_EDIT.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    debug: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_READ.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_READ.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    trigger_read: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_TRIGGER_READ.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_TRIGGER_READ.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n        trigger_create: (source_id:string) => \n    hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_TRIGGER_CREATE.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_TRIGGER_CREATE.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n            trigger_edit: (source_id:string) => \n    hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_TRIGGER_EDIT.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_TRIGGER_EDIT.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n                trigger_delete: (source_id:string) => \n    hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_TRIGGER_DELETE.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_TRIGGER_DELETE.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    auth: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_RESOURCE_AUTHORIZATION.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_RESOURCE_AUTHORIZATION.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    export: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_EXPORT.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_EXPORT.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    delete: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_DELETE.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_DELETE.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n\n    overview_embed: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_OVERVIEW_EMBEDDED.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_OVERVIEW_EMBEDDED.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    overview_access: (source_id:string) => \n      hasPermission(\n            [new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_OVERVIEW_ACCESS.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_OVERVIEW_ACCESS.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    )\n\n        ,\n    overview_display: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_OVERVIEW_DISPLAY.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_OVERVIEW_DISPLAY.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    overview_api_key: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_OVERVIEW_API_KEY.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_OVERVIEW_API_KEY.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    access_edit: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_ACCESS_EDIT.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_ACCESS_EDIT.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    application_chat_user_edit: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_CHAT_USER_EDIT.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_CHAT_USER_EDIT.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    chat_log_clear: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_CHAT_LOG_CLEAR_POLICY.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_CHAT_LOG_CLEAR_POLICY.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    chat_log_export: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_CHAT_LOG_EXPORT.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_CHAT_LOG_EXPORT.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    chat_log_add_knowledge: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.APPLICATION_CHAT_LOG_ADD_KNOWLEDGE.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.APPLICATION_CHAT_LOG_ADD_KNOWLEDGE.getApplicationWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n    overview_read: () => false,\n    jump_read: (source_id: string) => \n      hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.APPLICATION_OVERVIEW_READ.getWorkspacePermissionWorkspaceManageRole,\n        PermissionConst.APPLICATION_READ.getWorkspacePermissionWorkspaceManageRole,\n        PermissionConst.APPLICATION_OVERVIEW_READ.getApplicationWorkspaceResourcePermission(\n          source_id,\n          ),\n        PermissionConst.APPLICATION_READ.getApplicationWorkspaceResourcePermission(\n          source_id,\n        ),\n      ],\n      'OR',\n    )\n    ,\n    access_read: () => false,\n    chat_user_read: () => false,\n    chat_log_read: () => false\n}\n\n\nexport default workspace"
  },
  {
    "path": "ui/src/permission/index.ts",
    "content": "import tool from '@/permission/tool'\nimport model from '@/permission/model'\nimport knowledge from '@/permission/knowledge'\nimport application from '@/permission/application'\nconst permission = {\n  tool,\n  model,\n  knowledge,\n  application,\n}\nexport default permission\n"
  },
  {
    "path": "ui/src/permission/knowledge/index.ts",
    "content": "import systemShare from './system-share'\nimport workspace from './workspace'\nimport systemManage from './system-manage'\nimport workspaceShare from './workspace-share'\nconst permission = {\n  systemShare,\n  workspace,\n  systemManage,\n  workspaceShare\n}\nexport default permission\n"
  },
  {
    "path": "ui/src/permission/knowledge/system-manage.ts",
    "content": "import { hasPermission } from '@/utils/permission/index'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nconst systemManage = {\n  is_share: () =>\n    hasPermission(\n      new ComplexPermission(\n        [RoleConst.ADMIN],\n        [PermissionConst.SHARED_KNOWLEDGE_READ],\n        [EditionConst.IS_EE],\n        'OR',\n      ),\n      'OR',\n    ),\n  create: () => false,\n  sync: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_SYNC\n    ],'OR'\n  ),\n  vector: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_VECTOR\n    ],'OR'\n  ),\n  generate: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_GENERATE\n    ],'OR'\n  ),\n  edit: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_EDIT\n    ],'OR'\n  ),\n  export: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_EXPORT\n    ],'OR'\n  ),\n  delete: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DELETE\n    ],'OR'\n  ),\n  // 文档\n  doc_read: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_READ,\n      PermissionConst.RESOURCE_KNOWLEDGE_WORKFLOW_READ,\n    ], 'OR'),\n  jump_read: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_READ\n    ],'OR'),\n  doc_create: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_CREATE\n    ],'OR'\n  ),\n  doc_vector: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_VECTOR\n    ],'OR'\n  ),\n  doc_generate: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_GENERATE\n    ],'OR'\n  ),\n  doc_migrate: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_MIGRATE\n    ],'OR'\n  ),\n  doc_edit: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_EDIT\n    ],'OR'\n  ),\n  doc_sync: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_SYNC\n    ],'OR'\n  ),\n  doc_delete: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_DELETE\n    ],'OR'\n  ),\n  doc_export: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_EXPORT\n    ],'OR'\n  ),\n  doc_download: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE\n    ],'OR'\n  ),\n  doc_tag: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_TAG\n    ],'OR'\n  ),\n  doc_replace: () => hasPermission(\n    [\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_REPLACE\n    ],'OR'\n  ),\n  knowledge_chat_user_read: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_CHAT_USER_READ\n    ],'OR'),\n  knowledge_chat_user_edit: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_CHAT_USER_EDIT\n    ],'OR'),\n  \n  problem_read: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_PROBLEM_READ\n    ],'OR'),  \n  problem_create: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_PROBLEM_CREATE\n    ],'OR'\n    ),\n  problem_relate: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_PROBLEM_RELATE\n    ],'OR'\n    ),\n  problem_delete: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_PROBLEM_DELETE\n    ],'OR'\n    ),\n  problem_edit: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_PROBLEM_EDIT\n    ],'OR'\n    ),\n  tag_read: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_TAG_READ\n    ],'OR'\n    ),\n  tag_create: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_TAG_CREATE\n    ],'OR'\n    ),  \n  tag_edit: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_TAG_EDIT\n    ],'OR'\n    ),  \n  tag_delete: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_TAG_DELETE\n    ],'OR'\n    ),  \n  debug: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_WORKFLOW_READ\n    ],'OR'\n    ),  \n  workflow_edit: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_WORKFLOW_EDIT\n    ],'OR'\n    ), \n  workflow_export: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_WORKFLOW_EXPORT\n    ],'OR'\n    ), \n  chat_user_edit: () =>false,\n\n  \n  auth: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_AUTH\n    ],'OR'\n    ),\n  relate_map: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_RELATE_RESOURCE_VIEW\n    ],'OR'\n    ),\n  folderRead: () => false,\n  folderManage: () => false,\n  folderCreate: () => false,\n  folderEdit: () => false,\n  folderAuth: () => false,\n  folderDelete: () => false,\n  hit_test: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_KNOWLEDGE_HIT_TEST\n    ], 'OR'),\n}\n\nexport default systemManage\n"
  },
  {
    "path": "ui/src/permission/knowledge/system-share.ts",
    "content": "import { hasPermission } from '@/utils/permission/index'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nconst share = {\n  is_share: () => false,\n  create: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_CREATE], 'OR'),\n  sync: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_SYNC], 'OR'),\n  vector: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_VECTOR], 'OR'),\n  generate: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_GENERATE], 'OR'),\n  edit: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_EDIT], 'OR'),\n  export: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_EXPORT], 'OR'),\n  delete: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DELETE], 'OR'),\n\n  doc_read: () => false,\n  jump_read: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_READ, PermissionConst.SHARED_KNOWLEDGE_WORKFLOW_READ], 'OR'),\n  doc_create: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_CREATE], 'OR'),\n  doc_vector: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_VECTOR], 'OR'),\n  doc_generate: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_GENERATE], 'OR'),\n  doc_migrate: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_MIGRATE], 'OR'),\n  doc_edit: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_EDIT], 'OR'),\n  doc_sync: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_SYNC], 'OR'),\n  doc_delete: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_DELETE], 'OR'),\n  doc_export: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_EXPORT], 'OR'),\n  doc_download: () =>\n    hasPermission(\n      [RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE],\n      'OR',\n    ),\n  doc_tag: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_TAG], 'OR'),\n  doc_replace: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_REPLACE], 'OR'),\n  problem_create: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_PROBLEM_CREATE], 'OR'),\n  knowledge_chat_user_read: () => false,\n  knowledge_chat_user_edit: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_CHAT_USER_EDIT], 'OR'),\n  problem_read: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_PROBLEM_READ], 'OR'),\n  problem_relate: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_PROBLEM_RELATE], 'OR'),\n  problem_delete: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_PROBLEM_DELETE], 'OR'),\n  problem_edit: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_PROBLEM_EDIT], 'OR'),\n  tag_read: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_TAG_READ], 'OR'),\n  tag_create: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_TAG_CREATE], 'OR'),\n  tag_edit: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_TAG_EDIT], 'OR'),\n  tag_delete: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_TAG_DELETE], 'OR'),\n  debug: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_WORKFLOW_READ], 'OR'),\n  workflow_edit: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_WORKFLOW_EDIT], 'OR'),\n  workflow_export: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_WORKFLOW_EXPORT], 'OR'),  \n  chat_user_edit: () => false,\n\n  auth: () => false,\n  relate_map: () => hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_RELATE_RESOURCE_VIEW], 'OR'),\n  folderRead: () => false,\n  folderManage: () => false,\n  folderCreate: () => false,\n  folderEdit: () => false,\n  folderAuth: () => false,\n  folderDelete: () => false,\n  hit_test: () => false,\n  \n}\nexport default share\n"
  },
  {
    "path": "ui/src/permission/knowledge/workspace-share.ts",
    "content": "import { hasPermission } from '@/utils/permission/index'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nconst workspaceShare = {\n  is_share: () => true,\n  create: () => false,\n  sync: () => false,\n  vector: () => false,\n  generate: () => false,\n  edit: () => false,\n  export: () => false,\n  delete: () => false,\n  auth: () => false,\n  relate_map: () => false,\n  \n  doc_read: () => false,\n  jump_read: () => false,\n  doc_create: () => false,\n  doc_vector: () => false,\n  doc_generate: () => false,\n  doc_migrate: () => false,\n  doc_edit: () => false,\n  doc_sync: () => false,\n  doc_delete: () => false,\n  doc_export: () => false,\n  doc_download: () => false,\n  doc_tag: () => false,\n  doc_replace: () => false,\n\n  knowledge_chat_user_read: () => false,\n  knowledge_chat_user_edit: () => false,\n\n  tag_read: () => false,\n  tag_create: () => false,\n  tag_delete: () => false,\n  tag_edit: () => false,\n\n  problem_read: () => false,\n  problem_create: () => false,\n  problem_relate: () => false,\n  problem_delete: () => false,\n  problem_edit: () => false,\n  chat_user_edit: () => false,\n\n  folderRead: () => false,\n  folderManage: () => false,\n  folderCreate: () => false,\n  folderEdit: () => false,\n  folderAuth: () => false,\n  folderDelete: () => false,\n  hit_test: () => false,\n  debug: () => false,\n  workflow_edit: () => false,\n  workflow_export: () => false,\n}\n\nexport default workspaceShare\n"
  },
  {
    "path": "ui/src/permission/knowledge/workspace.ts",
    "content": "import { hasPermission } from '@/utils/permission/index'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nconst workspace = {\n  is_share: () =>\n    hasPermission(\n      new ComplexPermission(\n        [RoleConst.USER.getWorkspaceRole, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n        [\n          PermissionConst.KNOWLEDGE_READ.getWorkspacePermission,\n          PermissionConst.KNOWLEDGE_READ.getWorkspacePermissionWorkspaceManageRole,\n        ],\n        [EditionConst.IS_EE],\n        'OR',\n      ),\n      'OR',\n    ),\n  create: () =>\n    hasPermission(\n      [\n        RoleConst.USER.getWorkspaceRole,\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_CREATE.getWorkspacePermission,\n        PermissionConst.KNOWLEDGE_CREATE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  folderRead: (folder_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(folder_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_FOLDER_READ.getKnowledgeWorkspaceResourcePermission(folder_id),\n        PermissionConst.KNOWLEDGE_FOLDER_READ.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  folderManage: () => true,\n  folderAuth: (folder_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(folder_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_FOLDER_AUTH.getKnowledgeWorkspaceResourcePermission(folder_id),\n        PermissionConst.KNOWLEDGE_FOLDER_AUTH.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  folderCreate: (folder_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(folder_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_FOLDER_CREATE.getKnowledgeWorkspaceResourcePermission(folder_id),\n        PermissionConst.KNOWLEDGE_FOLDER_CREATE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  folderDelete: (folder_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(folder_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_FOLDER_DELETE.getKnowledgeWorkspaceResourcePermission(folder_id),\n        PermissionConst.KNOWLEDGE_FOLDER_DELETE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  folderEdit: (folder_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(folder_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_FOLDER_EDIT.getKnowledgeWorkspaceResourcePermission(folder_id),\n        PermissionConst.KNOWLEDGE_FOLDER_EDIT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  sync: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_SYNC.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_SYNC.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  vector: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_VECTOR.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_VECTOR.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  generate: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_GENERATE.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_GENERATE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  edit: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_EDIT.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_EDIT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  auth: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_RESOURCE_AUTHORIZATION.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_RESOURCE_AUTHORIZATION.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n    relate_map: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_RELATE_RESOURCE_VIEW.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_RELATE_RESOURCE_VIEW.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  export: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_EXPORT.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_EXPORT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  delete: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DELETE.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_DELETE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_read: () => false,\n  jump_read: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_READ.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_WORKFLOW_READ.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_DOCUMENT_READ.getWorkspacePermissionWorkspaceManageRole,\n        PermissionConst.KNOWLEDGE_WORKFLOW_READ.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_create: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_CREATE.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_DOCUMENT_CREATE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_vector: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_DOCUMENT_VECTOR.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_generate: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_GENERATE.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_DOCUMENT_GENERATE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_migrate: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_MIGRATE.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_DOCUMENT_MIGRATE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_edit: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_EDIT.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_DOCUMENT_EDIT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_sync: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_SYNC.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_DOCUMENT_SYNC.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_delete: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_DELETE.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_DOCUMENT_DELETE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_export: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_EXPORT.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_DOCUMENT_EXPORT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_download: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE\n          .getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_tag: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_TAG.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_DOCUMENT_TAG.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  doc_replace: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_DOCUMENT_REPLACE.getKnowledgeWorkspaceResourcePermission(\n          source_id,\n        ),\n        PermissionConst.KNOWLEDGE_DOCUMENT_REPLACE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  knowledge_chat_user_read: (source_id: string) => false,\n  knowledge_chat_user_edit: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_CHAT_USER_EDIT.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_CHAT_USER_EDIT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  problem_read: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_PROBLEM_READ.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_PROBLEM_READ.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  problem_create: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_PROBLEM_CREATE.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_PROBLEM_CREATE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  problem_relate: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_PROBLEM_RELATE.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_PROBLEM_RELATE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  problem_delete: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_PROBLEM_DELETE.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_PROBLEM_DELETE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  problem_edit: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_PROBLEM_EDIT.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_PROBLEM_EDIT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  tag_read: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_TAG_READ.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_TAG_READ.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  tag_create: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_TAG_CREATE.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_TAG_CREATE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  tag_edit: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_TAG_EDIT.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_TAG_EDIT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  tag_delete: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_TAG_DELETE.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_TAG_DELETE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  chat_user_edit: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_CHAT_USER_EDIT.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_CHAT_USER_EDIT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  debug: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_WORKFLOW_READ.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_WORKFLOW_READ.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  workflow_edit: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_WORKFLOW_EDIT.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_WORKFLOW_EDIT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  workflow_export: (source_id: string) =>\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(source_id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.KNOWLEDGE_WORKFLOW_EXPORT.getKnowledgeWorkspaceResourcePermission(source_id),\n        PermissionConst.KNOWLEDGE_WORKFLOW_EXPORT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  hit_test: () => false,\n}\n\nexport default workspace\n"
  },
  {
    "path": "ui/src/permission/model/index.ts",
    "content": "import systemShare from './system-share'\nimport workspace from './workspace'\nimport systemManage from './system-manage'\nconst permission = {\n  systemShare,\n  workspace,\n  systemManage,\n}\nexport default permission\n"
  },
  {
    "path": "ui/src/permission/model/system-manage.ts",
    "content": "import { hasPermission } from '@/utils/permission/index'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nconst systemManage = {\n  is_share: () =>\n    hasPermission(\n      new ComplexPermission(\n        [RoleConst.ADMIN],\n        [PermissionConst.MODEL_READ],\n        [EditionConst.IS_EE],\n        'OR',\n      ),\n      'OR',\n    ),\n  create: () => false,\n  jump_read: () => false,\n  modify: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_MODEL_EDIT], 'OR'),\n  paramSetting: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_MODEL_EDIT], 'OR'),\n  delete: () =>\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_MODEL_DELETE], 'OR'),\n\n  auth: () => \n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_MODEL_AUTH], 'OR'),\n  relate_map: () => \n    hasPermission([\n      RoleConst.ADMIN,\n      PermissionConst.RESOURCE_MODEL_RELATE_RESOURCE_VIEW\n    ],'OR'\n    ),\n  \n  folderRead: () => false,\n  folderManage: () => false,\n  folderCreate: () => false,\n  folderEdit: () => false,\n  folderAuth: () => false,\n  folderDelete: () => false,\n  debug: () => false,\n  \n}\n\nexport default systemManage\n"
  },
  {
    "path": "ui/src/permission/model/system-share.ts",
    "content": "import {hasPermission} from '@/utils/permission/index'\nimport {ComplexPermission} from '@/utils/permission/type'\nimport {EditionConst, PermissionConst, RoleConst} from '@/utils/permission/data'\n\nconst share = {\n  is_share: () => false,\n  jump_read: () => false,\n  create: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_MODEL_CREATE,\n      ],\n      'OR',\n    ),\n  modify: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_MODEL_EDIT,\n      ],\n      'OR',\n    ),\n  paramSetting: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_MODEL_EDIT,\n      ],\n      'OR',\n    ),\n  delete: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_MODEL_DELETE,\n      ],\n      'OR',\n    ),\n  auth: () => false,\n  relate_map: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_MODEL_RELATE_RESOURCE_VIEW,\n      ],\n      'OR',\n    ),\n  folderRead: () => false,\n  folderManage: () => false,\n  folderCreate: () => false,\n  folderEdit: () => false,\n  folderAuth: () => false,\n  folderDelete: () => false,\n  debug: () => false,\n}\nexport default share\n"
  },
  {
    "path": "ui/src/permission/model/workspace.ts",
    "content": "import { hasPermission } from '@/utils/permission/index'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nconst workspace = {\n  is_share: () =>\n    hasPermission(\n      new ComplexPermission(\n        [RoleConst.USER.getWorkspaceRole,RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n        [PermissionConst.MODEL_READ.getWorkspacePermission,PermissionConst.MODEL_READ.getWorkspacePermissionWorkspaceManageRole],\n        [EditionConst.IS_EE],'OR'),\n      'OR',\n    ),\n  jump_read: () => false,\n  create: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        RoleConst.USER.getWorkspaceRole,\n        PermissionConst.MODEL_CREATE.getWorkspacePermission,\n        PermissionConst.MODEL_CREATE.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  folderRead: () => true,\n  folderManage: () => true,\n  folderAuth: () => false,\n  folderCreate: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        RoleConst.USER.getWorkspaceRole,\n        PermissionConst.MODEL_CREATE.getWorkspacePermission,\n        PermissionConst.MODEL_CREATE.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  modify: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.MODEL.getModelWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.MODEL_EDIT.getModelWorkspaceResourcePermission(source_id),\n        PermissionConst.MODEL_EDIT.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  auth: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.MODEL.getModelWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.MODEL_RESOURCE_AUTHORIZATION.getModelWorkspaceResourcePermission(source_id),\n        PermissionConst.MODEL_RESOURCE_AUTHORIZATION.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  relate_map: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.MODEL.getModelWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.MODEL_RELATE_RESOURCE_VIEW.getModelWorkspaceResourcePermission(source_id),\n        PermissionConst.MODEL_RELATE_RESOURCE_VIEW.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  folderEdit: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        RoleConst.USER.getWorkspaceRole,\n        PermissionConst.MODEL_EDIT.getWorkspacePermission,\n        PermissionConst.MODEL_EDIT.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  paramSetting: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.MODEL.getModelWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.MODEL_EDIT.getModelWorkspaceResourcePermission(source_id),\n        PermissionConst.MODEL_EDIT.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  delete: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.MODEL.getModelWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.MODEL_DELETE.getModelWorkspaceResourcePermission(source_id),\n        PermissionConst.MODEL_DELETE.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  folderDelete: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        RoleConst.USER.getWorkspaceRole,\n        PermissionConst.MODEL_DELETE.getWorkspacePermission,\n        PermissionConst.MODEL_DELETE.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  debug: () => false,\n}\n\nexport default workspace\n"
  },
  {
    "path": "ui/src/permission/tool/index.ts",
    "content": "import systemShare from './system-share'\nimport workspace from './workspace'\nimport systemManage from './system-manage'\nconst permission = {\n  systemShare,\n  workspace,\n  systemManage,\n}\nexport default permission\n"
  },
  {
    "path": "ui/src/permission/tool/system-manage.ts",
    "content": "import {hasPermission} from '@/utils/permission/index'\nimport {ComplexPermission} from '@/utils/permission/type'\nimport {EditionConst, PermissionConst, RoleConst} from '@/utils/permission/data'\n\nconst systemManage = {\n  read: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.RESOURCE_TOOL_READ,\n      ],\n      'OR',\n    ),\n  jump_read: () => false,\n  is_share: () =>\n    hasPermission(\n      new ComplexPermission(\n        [RoleConst.ADMIN],\n        [PermissionConst.SHARED_TOOL_READ],\n        [EditionConst.IS_EE],\n        'OR',\n      ),\n      'OR',\n    ),\n  delete: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.RESOURCE_TOOL_DELETE,\n      ],\n      'OR',\n    ),\n  trigger_read: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_TOOL_TRIGGER_READ\n            ],\n            'OR'\n    ),\n  trigger_create: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_TOOL_TRIGGER_CREATE\n            ],\n            'OR'\n    ),\n  trigger_edit: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_TOOL_TRIGGER_EDIT\n            ],\n            'OR'\n    ),\n  trigger_delete: () =>\n        hasPermission(\n            [\n              RoleConst.ADMIN,\n              PermissionConst.RESOURCE_TOOL_TRIGGER_DELETE\n            ],\n            'OR'\n    ),\n  create: () => false,\n  import: () => false,\n  switch: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.RESOURCE_TOOL_EDIT,\n      ],\n      'OR',\n    ),\n  edit: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.RESOURCE_TOOL_EDIT,\n      ],\n      'OR',\n    ),\n  copy: () => false,\n  export: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.RESOURCE_TOOL_EXPORT,\n      ],\n      'OR',\n    ),\n  debug: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.RESOURCE_TOOL_EDIT,\n      ],\n      'OR',\n    ),\n\n  auth: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.RESOURCE_TOOL_AUTH,\n      ],\n      'OR',\n    ),\n  relate_map: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.RESOURCE_TOOL_RELATE_RESOURCE_VIEW\n      ],\n      'OR'\n    ),\n  record: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.RESOURCE_TOOL_EXECUTE_RECORD\n      ],\n      'OR'\n    ),\n  folderRead: () => false,\n  folderManage: () => false,\n  folderCreate: () => false,\n  folderEdit: () => false,\n  folderAuth: () => false,\n  folderDelete: () => false,\n\n}\n\nexport default systemManage\n"
  },
  {
    "path": "ui/src/permission/tool/system-share.ts",
    "content": "import {hasPermission} from '@/utils/permission/index'\nimport {ComplexPermission} from '@/utils/permission/type'\nimport {EditionConst, PermissionConst, RoleConst} from '@/utils/permission/data'\n\nconst share = {\n  read: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_READ,\n      ],\n      'OR',\n    ),\n  jump_read: () => false,  \n  is_share: () => false,\n  create: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_CREATE,\n      ],\n      'OR',\n    ),\n  import: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_IMPORT,\n      ],\n      'OR',\n    ),\n  delete: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_DELETE,\n      ],\n      'OR',\n    ),\n  trigger_read: ()=> false,\n  trigger_create: ()=> false,\n  trigger_edit: ()=> false,\n  trigger_delete: ()=> false,\n  switch: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_EDIT,\n      ],\n      'OR',\n    ),\n  edit: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_EDIT,\n      ],\n      'OR',\n    ),\n  copy: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_CREATE,\n      ],\n      'OR',\n    ),\n  export: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_EXPORT,\n      ],\n      'OR',\n    ),\n  debug: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_EDIT,\n      ],\n      'OR',\n    ),\n\n  auth: () => false,\n  relate_map: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_RELATE_RESOURCE_VIEW,\n      ],\n      'OR',\n    ),\n  record: () =>\n    hasPermission(\n      [\n        RoleConst.ADMIN,\n        PermissionConst.SHARED_TOOL_EXECUTE_RECORD,\n      ],\n      'OR',\n    ),\n  folderRead: () => false,\n  folderManage: () => false,\n  folderCreate: () => false,\n  folderEdit: () => false,\n  folderAuth: () => false,\n  folderDelete: () => false,\n}\nexport default share\n"
  },
  {
    "path": "ui/src/permission/tool/workspace.ts",
    "content": "import { hasPermission } from '@/utils/permission/index'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nconst workspace = {\n  read: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        RoleConst.USER.getWorkspaceRole,\n        PermissionConst.TOOL_READ.getWorkspacePermission,\n        PermissionConst.TOOL_READ.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  jump_read: () => false,\n  is_share: () =>\n    hasPermission(\n      new ComplexPermission(\n        [RoleConst.ADMIN,RoleConst.USER.getWorkspaceRole,RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n        [PermissionConst.TOOL_READ.getWorkspacePermission,PermissionConst.TOOL_READ.getWorkspacePermissionWorkspaceManageRole],\n        [EditionConst.IS_EE],'OR'),\n      'OR',\n    ),\n  create: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        RoleConst.USER.getWorkspaceRole,\n        PermissionConst.TOOL_CREATE.getWorkspacePermission,\n        PermissionConst.TOOL_CREATE.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  import: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        RoleConst.USER.getWorkspaceRole,\n        PermissionConst.TOOL_IMPORT.getWorkspacePermission,\n        PermissionConst.TOOL_IMPORT.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  folderCreate: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(folder_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.TOOL_FOLDER_CREATE.getToolWorkspaceResourcePermission(folder_id),\n              PermissionConst.TOOL_FOLDER_CREATE.getWorkspacePermissionWorkspaceManageRole,  \n            ],\n            'OR'\n    ),\n  folderRead: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(folder_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.TOOL_FOLDER_READ.getToolWorkspaceResourcePermission(folder_id),\n              PermissionConst.TOOL_FOLDER_READ.getWorkspacePermissionWorkspaceManageRole,  \n            ],\n            'OR'\n    ),\n  folderEdit: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(folder_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.TOOL_FOLDER_EDIT.getToolWorkspaceResourcePermission(folder_id),\n              PermissionConst.TOOL_FOLDER_EDIT.getWorkspacePermissionWorkspaceManageRole,  \n            ],\n            'OR'\n    ),\n  folderAuth: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(folder_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.TOOL_FOLDER_AUTH.getToolWorkspaceResourcePermission(folder_id),\n              PermissionConst.TOOL_FOLDER_AUTH.getWorkspacePermissionWorkspaceManageRole,  \n            ],\n            'OR'\n    ),\n  folderDelete: (folder_id: string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(folder_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.TOOL_FOLDER_DELETE.getToolWorkspaceResourcePermission(folder_id),\n              PermissionConst.TOOL_FOLDER_DELETE.getWorkspacePermissionWorkspaceManageRole,  \n            ],\n            'OR'\n    ),\n  folderManage: () => true,\n  delete: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TOOL_DELETE.getToolWorkspaceResourcePermission(source_id),\n        PermissionConst.TOOL_DELETE.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR',\n    ),\n    record: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TOOL_EXECUTE_RECORD.getToolWorkspaceResourcePermission(source_id),\n        PermissionConst.TOOL_EXECUTE_RECORD.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR',\n    ),\n  trigger_read: (source_id:string) => \n        hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.TOOL_TRIGGER_READ.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.TOOL_TRIGGER_READ.getToolWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n  trigger_create: (source_id:string) => \n    hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.TOOL_TRIGGER_CREATE.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.TOOL_TRIGGER_CREATE.getToolWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n  trigger_edit: (source_id: string) => \n    hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.TOOL_TRIGGER_EDIT.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.TOOL_TRIGGER_EDIT.getToolWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n  trigger_delete: (source_id:string) => \n    hasPermission(\n            [\n              new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n              RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n              PermissionConst.TOOL_TRIGGER_DELETE.getWorkspacePermissionWorkspaceManageRole,\n              PermissionConst.TOOL_TRIGGER_DELETE.getToolWorkspaceResourcePermission(source_id)  \n            ],\n            'OR'\n    ),\n  switch: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TOOL_EDIT.getToolWorkspaceResourcePermission(source_id),\n        PermissionConst.TOOL_EDIT.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  edit: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TOOL_EDIT.getToolWorkspaceResourcePermission(source_id),\n        PermissionConst.TOOL_EDIT.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  copy: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TOOL_EDIT.getToolWorkspaceResourcePermission(source_id),\n        PermissionConst.TOOL_EDIT.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  export: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TOOL_EXPORT.getToolWorkspaceResourcePermission(source_id),\n        PermissionConst.TOOL_EXPORT.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  auth: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TOOL_RESOURCE_AUTHORIZATION.getToolWorkspaceResourcePermission(source_id),\n        PermissionConst.TOOL_RESOURCE_AUTHORIZATION.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  relate_map: (source_id:string) =>\n    hasPermission(\n      [\n        new ComplexPermission([RoleConst.USER],[PermissionConst.TOOL.getToolWorkspaceResourcePermission(source_id)],[],'AND'),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TOOL_RELATE_RESOURCE_VIEW.getToolWorkspaceResourcePermission(source_id),\n        PermissionConst.TOOL_RELATE_RESOURCE_VIEW.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n  debug: () =>\n    hasPermission(\n      [\n        RoleConst.USER.getWorkspaceRole,\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TOOL_EDIT.getWorkspacePermission,\n        PermissionConst.TOOL_EDIT.getWorkspacePermissionWorkspaceManageRole\n      ],\n      'OR'\n    ),\n\n}\n\nexport default workspace\n"
  },
  {
    "path": "ui/src/request/Result.ts",
    "content": "export class Result<T> {\n  message: string\n  code: number\n  data: T\n  constructor(message: string, code: number, data: T) {\n    this.message = message\n    this.code = code\n    this.data = data\n  }\n\n  static success(data: any) {\n    return new Result('请求成功', 200, data)\n  }\n  static error(message: string, code: number) {\n    return new Result(message, code, null)\n  }\n}\n\nexport default Result\n"
  },
  {
    "path": "ui/src/request/chat/Result.ts",
    "content": "export class Result<T> {\n  message: string\n  code: number\n  data: T\n  constructor(message: string, code: number, data: T) {\n    this.message = message\n    this.code = code\n    this.data = data\n  }\n\n  static success(data: any) {\n    return new Result('请求成功', 200, data)\n  }\n  static error(message: string, code: number) {\n    return new Result(message, code, null)\n  }\n}\n\nexport default Result\n"
  },
  {
    "path": "ui/src/request/chat/index.ts",
    "content": "import axios, { type InternalAxiosRequestConfig, AxiosHeaders } from 'axios'\nimport { MsgError } from '@/utils/message'\nimport type { NProgress } from 'nprogress'\nimport type { Ref } from 'vue'\nimport type { Result } from '@/request/Result'\nimport useStore from '@/stores'\n\nimport { ref, type WritableComputedRef } from 'vue'\n\nconst axiosConfig = {\n  baseURL: (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/chat') + '/api',\n  withCredentials: false,\n  timeout: 600000,\n  headers: {},\n}\n\nconst instance = axios.create(axiosConfig)\n\n/* 设置请求拦截器 */\ninstance.interceptors.request.use(\n  (config: InternalAxiosRequestConfig) => {\n    if (config.headers === undefined) {\n      config.headers = new AxiosHeaders()\n    }\n    const { chatUser } = useStore()\n    const token = chatUser.getToken()\n    const language = chatUser.getLanguage()\n    config.headers['Accept-Language'] = `${language}`\n    if (token) {\n      config.headers['AUTHORIZATION'] = `Bearer ${token}`\n    }\n    return config\n  },\n  (err: any) => {\n    return Promise.reject(err)\n  },\n)\n\n//设置响应拦截器\ninstance.interceptors.response.use(\n  (response: any) => {\n    if (response.data) {\n      if (response.data.code !== 200 && !(response.data instanceof Blob)) {\n        MsgError(response.data.message)\n        return Promise.reject(response.data)\n      }\n    }\n    return response\n  },\n  (err: any) => {\n    if (err.code === 'ECONNABORTED') {\n      MsgError(err.message)\n      console.error(err)\n    }\n    return Promise.reject(err)\n  },\n)\n\nexport const request = instance\n\n/* 简化请求方法，统一处理返回结果，并增加loading处理，这里以{success,data,message}格式的返回值为例，具体项目根据实际需求修改 */\nconst promise: (\n  request: Promise<any>,\n  loading?: NProgress | Ref<boolean> | WritableComputedRef<boolean>,\n) => Promise<Result<any>> = (request, loading = ref(false)) => {\n  return new Promise((resolve, reject) => {\n    if ((loading as NProgress).start) {\n      ;(loading as NProgress).start()\n    } else {\n      ;(loading as Ref).value = true\n    }\n    request\n      .then((response) => {\n        // blob类型的返回状态是response.status\n        if (response.status === 200) {\n          resolve(response?.data || response)\n        } else {\n          reject(response?.data || response)\n        }\n      })\n      .catch((error) => {\n        reject(error)\n      })\n      .finally(() => {\n        if ((loading as NProgress).start) {\n          ;(loading as NProgress).done()\n        } else {\n          ;(loading as Ref).value = false\n        }\n      })\n  })\n}\n\n/**\n * 发送get请求   一般用来请求资源\n * @param url    资源url\n * @param params 参数\n * @param loading loading\n * @returns 异步promise对象\n */\nexport const get: (\n  url: string,\n  params?: unknown,\n  loading?: NProgress | Ref<boolean>,\n  timeout?: number,\n) => Promise<Result<any>> = (\n  url: string,\n  params: unknown,\n  loading?: NProgress | Ref<boolean>,\n  timeout?: number,\n) => {\n  return promise(request({ url: url, method: 'get', params, timeout: timeout }), loading)\n}\n\n/**\n * faso post请求 一般用来添加资源\n * @param url    资源url\n * @param params 参数\n * @param data   添加数据\n * @param loading loading\n * @returns 异步promise对象\n */\nexport const post: (\n  url: string,\n  data?: unknown,\n  params?: unknown,\n  loading?: NProgress | Ref<boolean>,\n  timeout?: number,\n) => Promise<Result<any> | any> = (url, data, params, loading, timeout) => {\n  return promise(request({ url: url, method: 'post', data, params, timeout }), loading)\n}\n\n/**|\n * 发送put请求 用于修改服务器资源\n * @param url     资源地址\n * @param params  params参数地址\n * @param data    需要修改的数据\n * @param loading 进度条\n * @returns\n */\nexport const put: (\n  url: string,\n  data?: unknown,\n  params?: unknown,\n  loading?: NProgress | Ref<boolean>,\n  timeout?: number,\n) => Promise<Result<any>> = (url, data, params, loading, timeout) => {\n  return promise(request({ url: url, method: 'put', data, params, timeout }), loading)\n}\n\n/**\n * 删除\n * @param url     删除url\n * @param params  params参数\n * @param loading 进度条\n * @returns\n */\nexport const del: (\n  url: string,\n  params?: unknown,\n  data?: unknown,\n  loading?: NProgress | Ref<boolean>,\n  timeout?: number,\n) => Promise<Result<any>> = (url, params, data, loading, timeout) => {\n  return promise(request({ url: url, method: 'delete', params, data, timeout }), loading)\n}\n\n/**\n * 流处理\n * @param url  url地址\n * @param data 请求body\n * @returns\n */\nexport const postStream: (url: string, data?: unknown) => Promise<Result<any> | any> = (\n  url,\n  data,\n) => {\n  const { chatUser } = useStore()\n  const token = chatUser.getToken()\n  const language = chatUser.getLanguage()\n  const headers: HeadersInit = { 'Content-Type': 'application/json' }\n  if (token) {\n    headers['AUTHORIZATION'] = `Bearer ${token}`\n  }\n  headers['Accept-Language'] = `${language}`\n  return fetch(url, {\n    method: 'POST',\n    body: data ? JSON.stringify(data) : undefined,\n    headers: headers,\n  })\n}\n\nexport const exportExcel: (\n  fileName: string,\n  url: string,\n  params: any,\n  loading?: NProgress | Ref<boolean>,\n) => Promise<any> = (\n  fileName: string,\n  url: string,\n  params: any,\n  loading?: NProgress | Ref<boolean>,\n) => {\n  return promise(request({ url: url, method: 'get', params, responseType: 'blob' }), loading).then(\n    (res: any) => {\n      if (res) {\n        const blob = new Blob([res], {\n          type: 'application/vnd.ms-excel',\n        })\n        const link = document.createElement('a')\n        link.href = window.URL.createObjectURL(blob)\n        link.download = fileName\n        link.click()\n        //释放内存\n        window.URL.revokeObjectURL(link.href)\n      }\n      return true\n    },\n  )\n}\n\nexport const exportFile: (\n  fileName: string,\n  url: string,\n  params: any,\n  loading?: NProgress | Ref<boolean>,\n) => Promise<any> = (\n  fileName: string,\n  url: string,\n  params: any,\n  loading?: NProgress | Ref<boolean>,\n) => {\n  return promise(request({ url: url, method: 'get', params, responseType: 'blob' }), loading).then(\n    (res: any) => {\n      if (res) {\n        const blob = new Blob([res], {\n          type: 'application/octet-stream',\n        })\n        const link = document.createElement('a')\n        link.href = window.URL.createObjectURL(blob)\n        link.download = fileName\n        link.click()\n        //释放内存\n        window.URL.revokeObjectURL(link.href)\n      }\n      return true\n    },\n  )\n}\n\nexport const exportExcelPost: (\n  fileName: string,\n  url: string,\n  params: any,\n  data: any,\n  loading?: NProgress | Ref<boolean>,\n) => Promise<any> = (\n  fileName: string,\n  url: string,\n  params: any,\n  data: any,\n  loading?: NProgress | Ref<boolean>,\n) => {\n  return promise(\n    request({\n      url: url,\n      method: 'post',\n      params, // 查询字符串参数\n      data, // 请求体数据\n      responseType: 'blob',\n    }),\n    loading,\n  ).then((res: any) => {\n    if (res) {\n      const blob = new Blob([res], {\n        type: 'application/vnd.ms-excel',\n      })\n      const link = document.createElement('a')\n      link.href = window.URL.createObjectURL(blob)\n      link.download = fileName\n      link.click()\n      // 释放内存\n      window.URL.revokeObjectURL(link.href)\n    }\n    return true\n  })\n}\n\nexport const download: (\n  url: string,\n  method: string,\n  data?: any,\n  params?: any,\n  loading?: NProgress | Ref<boolean>,\n) => Promise<any> = (\n  url: string,\n  method: string,\n  data?: any,\n  params?: any,\n  loading?: NProgress | Ref<boolean>,\n) => {\n  return promise(request({ url: url, method: method, data, params, responseType: 'blob' }), loading)\n}\n\n/**\n * 与服务器建立ws链接\n * @param url websocket路径\n * @returns  返回一个websocket实例\n */\nexport const socket = (url: string) => {\n  let protocol = 'ws://'\n  if (window.location.protocol === 'https:') {\n    protocol = 'wss://'\n  }\n  let uri = protocol + window.location.host + url\n  if (!import.meta.env.DEV) {\n    uri = protocol + window.location.host + import.meta.env.VITE_BASE_PATH + url\n  }\n  return new WebSocket(uri)\n}\nexport default instance\n"
  },
  {
    "path": "ui/src/request/index.ts",
    "content": "import axios, { type InternalAxiosRequestConfig, AxiosHeaders } from 'axios'\nimport { MsgError } from '@/utils/message'\nimport type { NProgress } from 'nprogress'\nimport type { Ref } from 'vue'\nimport type { Result } from '@/request/Result'\nimport useStore from '@/stores'\nimport router from '@/router'\n\nimport { ref, type WritableComputedRef } from 'vue'\n\nconst axiosConfig = {\n  baseURL: (window.MaxKB?.prefix ? window.MaxKB?.prefix : '/admin') + '/api',\n  withCredentials: false,\n  timeout: 1800000, // 30分钟 timeout\n  headers: {},\n}\n\nconst instance = axios.create(axiosConfig)\n\n/* 设置请求拦截器 */\ninstance.interceptors.request.use(\n  (config: InternalAxiosRequestConfig) => {\n    if (config.headers === undefined) {\n      config.headers = new AxiosHeaders()\n    }\n    if (config.url && config.url.startsWith('http')) {\n      return config\n    }\n    const { user, login } = useStore()\n    const token = login.getToken()\n    const language = user.getLanguage()\n    config.headers['Accept-Language'] = `${language}`\n    if (token) {\n      config.headers['AUTHORIZATION'] = `Bearer ${token}`\n    }\n    return config\n  },\n  (err: any) => {\n    return Promise.reject(err)\n  },\n)\n\n//设置响应拦截器\ninstance.interceptors.response.use(\n  (response: any) => {\n    if (response.data) {\n      if (response.data.code !== 200 && !(response.data instanceof Blob)) {\n        if (response.config.url.includes('/application/authentication')) {\n          return Promise.reject(response.data)\n        }\n        if (\n          !response.config.url.includes('/valid') &&\n          !response.config.url.includes('/tool/debug')\n        ) {\n          MsgError(response.data.message)\n          return Promise.reject(response.data)\n        }\n      }\n    }\n    return response\n  },\n  (err: any) => {\n    if (err.code === 'ECONNABORTED') {\n      MsgError(err.message)\n      console.error(err)\n    }\n    if (err.response?.status === 404) {\n      if (!err.response.config.url.includes('/application/authentication')) {\n        router.push('/404 ')\n      }\n    }\n    if (err.response?.status === 401) {\n      if (\n        !err.response.config.url.includes('chat/open') &&\n        !err.response.config.url.includes('application/profile')\n      ) {\n        router.push({ name: 'login' })\n      }\n    }\n\n    if (err.response?.status === 403 && !err.response.config.url.includes('chat/open')) {\n      MsgError(\n        err.response.data && err.response.data.message\n          ? err.response.data.message\n          : 'No permission to access',\n      )\n    }\n    return Promise.reject(err)\n  },\n)\n\nexport const request = instance\n\n/* 简化请求方法，统一处理返回结果，并增加loading处理，这里以{success,data,message}格式的返回值为例，具体项目根据实际需求修改 */\nconst promise: (\n  request: Promise<any>,\n  loading?: NProgress | Ref<boolean> | WritableComputedRef<boolean>,\n) => Promise<Result<any>> = (request, loading = ref(false)) => {\n  return new Promise((resolve, reject) => {\n    if ((loading as NProgress).start) {\n      ;(loading as NProgress).start()\n    } else {\n      ;(loading as Ref).value = true\n    }\n    request\n      .then((response) => {\n        // blob类型的返回状态是response.status\n        if (response.status === 200) {\n          resolve(response?.data || response)\n        } else {\n          reject(response?.data || response)\n        }\n      })\n      .catch((error) => {\n        reject(error)\n      })\n      .finally(() => {\n        if ((loading as NProgress).start) {\n          ;(loading as NProgress).done()\n        } else {\n          ;(loading as Ref).value = false\n        }\n      })\n  })\n}\n\n/**\n * 发送get请求   一般用来请求资源\n * @param url    资源url\n * @param params 参数\n * @param loading loading\n * @returns 异步promise对象\n */\nexport const get: (\n  url: string,\n  params?: unknown,\n  loading?: NProgress | Ref<boolean>,\n  timeout?: number,\n) => Promise<Result<any>> = (\n  url: string,\n  params: unknown,\n  loading?: NProgress | Ref<boolean>,\n  timeout?: number,\n) => {\n  return promise(request({ url: url, method: 'get', params, timeout: timeout }), loading)\n}\n\n/**\n * faso post请求 一般用来添加资源\n * @param url    资源url\n * @param params 参数\n * @param data   添加数据\n * @param loading loading\n * @returns 异步promise对象\n */\nexport const post: (\n  url: string,\n  data?: unknown,\n  params?: unknown,\n  loading?: NProgress | Ref<boolean>,\n  timeout?: number,\n) => Promise<Result<any> | any> = (url, data, params, loading, timeout) => {\n  return promise(request({ url: url, method: 'post', data, params, timeout }), loading)\n}\n\n/**|\n * 发送put请求 用于修改服务器资源\n * @param url     资源地址\n * @param params  params参数地址\n * @param data    需要修改的数据\n * @param loading 进度条\n * @returns\n */\nexport const put: (\n  url: string,\n  data?: unknown,\n  params?: unknown,\n  loading?: NProgress | Ref<boolean>,\n  timeout?: number,\n) => Promise<Result<any>> = (url, data, params, loading, timeout) => {\n  return promise(request({ url: url, method: 'put', data, params, timeout }), loading)\n}\n\n/**\n * 删除\n * @param url     删除url\n * @param params  params参数\n * @param loading 进度条\n * @returns\n */\nexport const del: (\n  url: string,\n  params?: unknown,\n  data?: unknown,\n  loading?: NProgress | Ref<boolean>,\n  timeout?: number,\n) => Promise<Result<any>> = (url, params, data, loading, timeout) => {\n  return promise(request({ url: url, method: 'delete', params, data, timeout }), loading)\n}\n\n/**\n * 流处理\n * @param url  url地址\n * @param data 请求body\n * @returns\n */\nexport const postStream: (url: string, data?: unknown) => Promise<Result<any> | any> = (\n  url,\n  data,\n) => {\n  const { user, login } = useStore()\n  const token = login.getToken()\n  const language = user.getLanguage()\n  const headers: HeadersInit = { 'Content-Type': 'application/json' }\n  if (token) {\n    headers['AUTHORIZATION'] = `Bearer ${token}`\n  }\n  headers['Accept-Language'] = `${language}`\n  return fetch(url, {\n    method: 'POST',\n    body: data ? JSON.stringify(data) : undefined,\n    headers: headers,\n  })\n}\n\nexport const exportExcel: (\n  fileName: string,\n  url: string,\n  params: any,\n  loading?: NProgress | Ref<boolean>,\n) => Promise<any> = (\n  fileName: string,\n  url: string,\n  params: any,\n  loading?: NProgress | Ref<boolean>,\n) => {\n  return promise(request({ url: url, method: 'get', params, responseType: 'blob' }), loading).then(\n    (res: any) => {\n      if (res) {\n        const blob = new Blob([res], {\n          type: 'application/vnd.ms-excel',\n        })\n        const link = document.createElement('a')\n        link.href = window.URL.createObjectURL(blob)\n        link.download = fileName\n        link.click()\n        //释放内存\n        window.URL.revokeObjectURL(link.href)\n      }\n      return true\n    },\n  )\n}\n\nfunction extractFilename(contentDisposition: string) {\n  if (!contentDisposition) return null\n\n  // 处理 URL 编码的文件名\n  const urlEncodedMatch =\n    contentDisposition.match(/filename=([^;]*)/i) ||\n    contentDisposition.match(/filename\\*=UTF-8''([^;]*)/i)\n  if (urlEncodedMatch && urlEncodedMatch[1]) {\n    try {\n      return decodeURIComponent(urlEncodedMatch[1].replace(/\"/g, ''))\n    } catch (e) {\n      console.error('解码URL编码文件名失败:', e)\n    }\n  }\n\n  // 处理 Base64 编码的文件名\n  const base64Part = contentDisposition.match(/=\\?utf-8\\?b\\?(.*?)\\?=/i)?.[1]\n  if (base64Part) {\n    try {\n      const decoded = decodeURIComponent(escape(atob(base64Part)))\n      const filenameMatch = decoded.match(/filename=\"(.*?)\"/i)\n      return filenameMatch ? filenameMatch[1] : null\n    } catch (e) {\n      console.error('解码Base64文件名失败:', e)\n    }\n  }\n\n  return null\n}\n\nexport const exportFile: (\n  fileName: string,\n  url: string,\n  params: any,\n  loading?: NProgress | Ref<boolean>,\n) => Promise<any> = (\n  fileName: string,\n  url: string,\n  params: any,\n  loading?: NProgress | Ref<boolean>,\n) => {\n  return promise(\n    request({\n      url: url,\n      method: 'get',\n      params,\n      responseType: 'blob',\n      transformResponse: [\n        function (data, headers) {\n          // 在这里可以访问 headers\n          if (data.type === 'application/json') {\n            data.text().then((text: string) => {\n              try {\n                const json = JSON.parse(text)\n                MsgError(json.message || text)\n              } catch {\n                MsgError(text)\n              }\n            })\n            throw new Error('Response is not a valid file')\n          }\n          // const contentType = headers['content-type'];\n          const contentDisposition = headers['content-disposition']\n          // console.log('Content-Type:', contentType);\n          // console.log('Content-Disposition:', contentDisposition);\n          // 如果没有提供文件名，则使用默认名称\n          fileName = extractFilename(contentDisposition) || fileName\n          return data // 必须返回数据\n        },\n      ],\n    }),\n    loading,\n  )\n    .then((res: any) => {\n      if (res) {\n        const blob = new Blob([res], {\n          type: 'application/octet-stream',\n        })\n        const link = document.createElement('a')\n        link.href = window.URL.createObjectURL(blob)\n        link.download = fileName\n        link.click()\n        //释放内存\n        window.URL.revokeObjectURL(link.href)\n      }\n      return true\n    })\n    .catch(() => {})\n}\n\nexport const exportExcelPost: (\n  fileName: string,\n  url: string,\n  params: any,\n  data: any,\n  loading?: NProgress | Ref<boolean>,\n) => Promise<any> = (\n  fileName: string,\n  url: string,\n  params: any,\n  data: any,\n  loading?: NProgress | Ref<boolean>,\n) => {\n  return promise(\n    request({\n      url: url,\n      method: 'post',\n      params, // 查询字符串参数\n      data, // 请求体数据\n      responseType: 'blob',\n    }),\n    loading,\n  ).then((res: any) => {\n    if (res) {\n      const blob = new Blob([res], {\n        type: 'application/vnd.ms-excel',\n      })\n      const link = document.createElement('a')\n      link.href = window.URL.createObjectURL(blob)\n      link.download = fileName\n      link.click()\n      // 释放内存\n      window.URL.revokeObjectURL(link.href)\n    }\n    return true\n  })\n}\n\nexport const exportFilePost: (\n  fileName: string,\n  url: string,\n  params: any,\n  data: any,\n  loading?: NProgress | Ref<boolean>,\n) => Promise<any> = (\n  fileName: string,\n  url: string,\n  params: any,\n  data: any,\n  loading?: NProgress | Ref<boolean>,\n) => {\n  return promise(\n    request({\n      url: url,\n      method: 'post',\n      params,\n      data,\n      responseType: 'blob',\n      transformResponse: [\n        function (data, headers) {\n          // 在这里可以访问 headers\n          if (data.type === 'application/json') {\n            data.text().then((text: string) => {\n              try {\n                const json = JSON.parse(text)\n                MsgError(json.message || text)\n              } catch {\n                MsgError(text)\n              }\n            })\n            throw new Error('Response is not a valid file')\n          }\n          // const contentType = headers['content-type'];\n          const contentDisposition = headers['content-disposition']\n          // console.log('Content-Type:', contentType);\n          // console.log('Content-Disposition:', contentDisposition);\n          // 如果没有提供文件名，则使用默认名称\n          fileName = extractFilename(contentDisposition) || fileName\n          return data // 必须返回数据\n        },\n      ],\n    }),\n    loading,\n  )\n    .then((res: any) => {\n      if (res) {\n        const blob = new Blob([res], {\n          type: 'application/octet-stream',\n        })\n        const link = document.createElement('a')\n        link.href = window.URL.createObjectURL(blob)\n        link.download = fileName\n        link.click()\n        //释放内存\n        window.URL.revokeObjectURL(link.href)\n      }\n      return true\n    })\n    .catch(() => {})\n}\n\nexport const download: (\n  url: string,\n  method: string,\n  data?: any,\n  params?: any,\n  loading?: NProgress | Ref<boolean>,\n) => Promise<any> = (\n  url: string,\n  method: string,\n  data?: any,\n  params?: any,\n  loading?: NProgress | Ref<boolean>,\n) => {\n  return promise(request({ url: url, method: method, data, params, responseType: 'blob' }), loading)\n}\n\n/**\n * 与服务器建立ws链接\n * @param url websocket路径\n * @returns  返回一个websocket实例\n */\nexport const socket = (url: string) => {\n  let protocol = 'ws://'\n  if (window.location.protocol === 'https:') {\n    protocol = 'wss://'\n  }\n  let uri = protocol + window.location.host + url\n  if (!import.meta.env.DEV) {\n    uri = protocol + window.location.host + import.meta.env.VITE_BASE_PATH + url\n  }\n  return new WebSocket(uri)\n}\nexport default instance\n"
  },
  {
    "path": "ui/src/router/chat/index.ts",
    "content": "import { hasPermission } from '@/utils/permission/index'\nimport NProgress from 'nprogress'\nimport {\n  createRouter,\n  createWebHistory,\n  type NavigationGuardNext,\n  type RouteLocationNormalized,\n  type RouteRecordRaw,\n  type RouteRecordName,\n} from 'vue-router'\nimport useStore from '@/stores'\nimport { routes } from '@/router/chat/routes'\nNProgress.configure({ showSpinner: false, speed: 500, minimum: 0.3 })\nconst router = createRouter({\n  history: createWebHistory(window.MaxKB?.prefix ? window.MaxKB?.prefix : import.meta.env.BASE_URL),\n  routes: routes,\n})\n\n// 路由前置拦截器\nrouter.beforeEach(\n  async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {\n    NProgress.start()\n    if (to.path === '/404' || to.name === 'Share') {\n      next()\n      return\n    }\n    const { chatUser } = useStore()\n    if (['login', 'chat'].includes(to.name ? to.name.toString() : '')) {\n      chatUser.setAccessToken(to.params.accessToken.toString())\n    } else {\n      next({\n        path: '/404',\n      })\n      return\n    }\n    let authentication = false\n    try {\n      authentication = await chatUser.isAuthentication()\n    } catch (e: any) {\n      next()\n      return\n    }\n    const p_token = to.query.token\n    if (p_token) {\n      chatUser.setToken(p_token as string)\n    }\n    const token = chatUser.getToken()\n    if (authentication) {\n      if (!token && to.name != 'login') {\n        next({\n          name: 'login',\n          params: {\n            accessToken: to.params.accessToken,\n          },\n          query: to.query,\n        })\n        return\n      } else {\n        if (to.name == 'login') {\n          next()\n          return\n        } else {\n          try {\n            await chatUser.applicationProfile()\n            await chatUser.getChatUserProfile()\n          } catch (e: any) {\n            if (e.response?.status === 401) {\n              next({\n                name: 'login',\n                params: {\n                  accessToken: to.params.accessToken,\n                },\n                query: to.query,\n              })\n            }\n            return\n          }\n          if (p_token) {\n            const q = to.query\n            delete q.token\n            next({ ...to, query: q })\n          } else {\n            next()\n          }\n          return\n        }\n      }\n    } else {\n      try {\n        await chatUser.anonymousAuthentication()\n      } catch (e: any) {\n        next()\n        return\n      }\n    }\n    if (!chatUser.application) {\n      try {\n        await chatUser.applicationProfile()\n      } catch (e: any) {\n        if (e.response?.status === 401) {\n          next({\n            name: 'login',\n            params: {\n              accessToken: to.params.accessToken,\n            },\n            query: to.query,\n          })\n        }\n        return\n      }\n    }\n    next()\n  },\n)\nrouter.afterEach(() => {\n  NProgress.done()\n})\n\nexport const getChildRouteListByPathAndName = (path: any, name?: RouteRecordName | any) => {\n  return getChildRouteList(routes, path, name)\n}\n\nexport const getChildRouteList: (\n  routeList: Array<RouteRecordRaw>,\n  path: string,\n  name?: RouteRecordName | null | undefined,\n) => Array<RouteRecordRaw> = (routeList, path, name) => {\n  for (let index = 0; index < routeList.length; index++) {\n    const route = routeList[index]\n    if (name === route.name && path === route.path) {\n      return route.children || []\n    }\n    if (route.children && route.children.length > 0) {\n      const result = getChildRouteList(route.children, path, name)\n      if (result && result?.length > 0) {\n        return result\n      }\n    }\n  }\n  return []\n}\n\nexport default router\n"
  },
  {
    "path": "ui/src/router/chat/routes.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router'\n\nexport const routes: Array<RouteRecordRaw> = [\n  // 对话\n  {\n    path: '/:accessToken',\n    name: 'chat',\n    component: () => import('@/views/chat/index.vue'),\n  },\n  // 对话用户登录\n  {\n    path: '/login/:accessToken',\n    name: 'login',\n    component: () => import('@/views/chat/user-login/index.vue'),\n  },\n  // 对话用户登录\n  {\n    path: '/404',\n    name: '404',\n    component: () => import('@/views/error/404.vue'),\n  },\n  {\n    path: '/no-service',\n    name: 'NoService',\n    component: () => import('@/views/error/NoService.vue'),\n  },\n  // 对话\n  {\n    path: '/share/:link',\n    name: 'Share',\n    component: () => import('@/views/chat/Share.vue'),\n  },\n]\n"
  },
  {
    "path": "ui/src/router/common.ts",
    "content": "import {\n  createRouter,\n  createWebHistory,\n  type NavigationGuardNext,\n  type RouteLocationNormalized,\n  type RouteRecordRaw,\n  type RouteRecordName,\n} from 'vue-router'\nimport { hasPermission, set_next_route } from '@/utils/permission/index'\nexport const getChildRouteList: (\n  routeList: Array<RouteRecordRaw>,\n  path: string,\n  name?: RouteRecordName | null | undefined,\n) => Array<RouteRecordRaw> = (routeList, path, name) => {\n  for (let index = 0; index < routeList.length; index++) {\n    const route = routeList[index]\n    if (name === route.name && path === route.path) {\n      return route.children || []\n    }\n    if (route.children && route.children.length > 0) {\n      const result = getChildRouteList(route.children, path, name)\n      if (result && result?.length > 0) {\n        return result\n      }\n    }\n  }\n  return []\n}\n/**\n * 获取同级路由\n * @param routeList\n * @param name\n * @returns\n */\nexport const getSameRouteList: (\n  routeList: Array<RouteRecordRaw>,\n  name?: RouteRecordName | null | undefined,\n) => Array<RouteRecordRaw> = (routeList, name) => {\n  for (let index = 0; index < routeList.length; index++) {\n    const route = routeList[index]\n    if (name === route.name) {\n      return routeList\n    }\n    if (route.children && route.children.length > 0) {\n      const result = getSameRouteList(route.children, name)\n      if (result && result?.length > 0) {\n        return result\n      }\n    }\n  }\n  return []\n}\n\n/**\n * 获取有权限的路由\n * @param routes\n * @param to\n * @returns\n */\nexport const getPermissionRoute = (routes: Array<RouteRecordRaw>, to: RouteLocationNormalized) => {\n  const routeName: string = to.meta\n    ? to.meta.sameRoute\n      ? (to.meta.sameRoute as string)\n      : (to.name as string)\n    : (to.name as string)\n  const routeList = getSameRouteList(routes, routeName)\n  const route = routeList.find((route: any) => {\n    return (\n      (to.meta.group ? to.meta.group == route.meta.group : true) &&\n      (route.meta.permission ? hasPermission(route.meta.permission as any, 'OR') : true)\n    )\n  })\n\n  const finalRoute =\n    route?.children && route.children.length > 0\n      ? findAccessibleRoute(route.children) || route\n      : route\n\n  if (finalRoute?.name && finalRoute.name !== to.name) {\n    return { name: finalRoute.name, params: to.params }\n  }\n\n  const globalRoute = findAccessibleRoute(routes)\n  if (globalRoute && globalRoute.name !== to.name) {\n    return { name: globalRoute.name, params: to.params }\n  }\n\n  return { name: 'noPermission' }\n}\n\n\n// 寻找有权限的路由\n \nconst findAccessibleRoute = (routes: Array<RouteRecordRaw>): RouteRecordRaw | null => {\n  for (const route of routes) {\n    const permission = route.meta?.permission\n    if (permission && !hasPermission(permission as any, 'OR')) {\n      continue\n    }\n    if (route.path.includes(':')) {\n      continue\n    }\n    if (route.children && route.children.length > 0) {\n      const child = findAccessibleRoute(route.children)\n      if (child) return child\n    }\n\n    if (!route.children || route.children.length === 0) {\n      return route\n    }\n  }\n\n  return null\n}"
  },
  {
    "path": "ui/src/router/index.ts",
    "content": "import { hasPermission, set_next_route } from '@/utils/permission/index'\nimport { getChildRouteList } from '@/router/common'\nimport NProgress from 'nprogress'\nimport { getPermissionRoute } from '@/router/common'\nimport {\n  createRouter,\n  createWebHistory,\n  type NavigationGuardNext,\n  type RouteLocationNormalized,\n  type RouteRecordName,\n} from 'vue-router'\nimport useStore from '@/stores'\nimport { routes } from '@/router/routes'\nNProgress.configure({ showSpinner: false, speed: 500, minimum: 0.3 })\nconst router = createRouter({\n  history: createWebHistory(window.MaxKB?.prefix ? window.MaxKB?.prefix : import.meta.env.BASE_URL),\n  routes: routes,\n})\n\n// 路由前置拦截器\nrouter.beforeEach(\n  async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {\n    NProgress.start()\n    if (to.name === '404') {\n      next()\n      return\n    }\n    const { user, login } = useStore()\n\n    const notAuthRouteNameList = ['login', 'ForgotPassword', 'ResetPassword', 'Chat', 'UserLogin']\n    if (!notAuthRouteNameList.includes(to.name ? to.name.toString() : '')) {\n      if (to.query && to.query.token) {\n        localStorage.setItem('token', to.query.token.toString())\n      }\n      const token = login.getToken()\n      if (!token) {\n        next({\n          path: '/login',\n        })\n        return\n      }\n      if (!user.userInfo) {\n        await user.profile()\n      }\n    }\n    set_next_route(to)\n    // 判断是否有菜单权限\n    if (to.meta.permission ? hasPermission(to.meta.permission as any, 'OR') : true) {\n      if(to.name=='noPermissionD'){\n         const n = getPermissionRoute(routes, to)\n         if(n.name=='noPermission'){\n          next()\n          return\n         }else{\n          next(n)\n          return\n         }\n      }else{\n        next()\n      }\n    } else {\n      const n = getPermissionRoute(routes, to)\n      next(n)\n    }\n  },\n)\nrouter.afterEach(() => {\n  NProgress.done()\n})\nexport const getChildRouteListByPathAndName = (path: any, name?: RouteRecordName | any) => {\n  return getChildRouteList(routes, path, name)\n}\n\nexport default router\n"
  },
  {
    "path": "ui/src/router/modules/application-detail.ts",
    "content": "import { SourceTypeEnum } from '@/enums/common'\nimport { get_next_route } from '@/utils/permission'\n\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\nconst ApplicationDetailRouter = {\n  path: '/application/:from/:id/:type',\n  name: 'ApplicationDetail',\n  meta: { title: 'views.applicationOverview.title', activeMenu: '/application', breadcrumb: true },\n  component: () => import('@/layout/layout-template/MainLayout.vue'),\n  hidden: true,\n  children: [\n    {\n      path: 'overview',\n      name: 'AppOverview',\n      meta: {\n        icon: 'app-all-menu',\n        iconActive: 'app-all-menu-active',\n        title: 'views.applicationOverview.title',\n        active: 'overview',\n        parentPath: '/application/:from/:id/:type',\n        parentName: 'ApplicationDetail',\n        permission: [\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return new ComplexPermission([RoleConst.USER], [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(to ? to.params.id : '',)], [], 'AND')\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else { return RoleConst.WORKSPACE_MANAGE.getWorkspaceRole() }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else { return PermissionConst.APPLICATION_OVERVIEW_READ.getWorkspacePermissionWorkspaceManageRole() }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return PermissionConst.APPLICATION_OVERVIEW_READ.getApplicationWorkspaceResourcePermission(\n                to ? to.params.id : '',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.path.includes('resource-management')) { return RoleConst.ADMIN }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.path.includes('resource-management')) { return PermissionConst.RESOURCE_APPLICATION_OVERVIEW_READ }\n          },\n        ]\n      },\n      component: () => import('@/views/application-overview/index.vue'),\n    },\n    {\n      path: 'setting',\n      name: 'AppSetting',\n      meta: {\n        icon: 'app-setting',\n        iconActive: 'app-setting-active',\n        title: 'common.setting',\n        active: 'setting',\n        parentPath: '/application/:from/:id/:type',\n        parentName: 'ApplicationDetail',\n        permission: [\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return new ComplexPermission([RoleConst.USER], [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(to ? to.params.id : '',)], [], 'AND')\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return RoleConst.WORKSPACE_MANAGE.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return PermissionConst.APPLICATION_READ.getWorkspacePermissionWorkspaceManageRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return PermissionConst.APPLICATION_READ.getApplicationWorkspaceResourcePermission(\n                to ? to.params.id : '',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.path.includes('resource-management')) { return RoleConst.ADMIN }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.path.includes('resource-management')) { return PermissionConst.RESOURCE_APPLICATION_READ }\n          },\n        ]\n      },\n      component: () => import('@/views/application/ApplicationSetting.vue'),\n    },\n    {\n      path: 'access',\n      name: 'AppAccess',\n      meta: {\n        icon: 'app-access',\n        iconActive: 'app-access-active',\n        title: 'views.application.applicationAccess.title',\n        active: 'access',\n        parentPath: '/application/:from/:id/:type',\n        parentName: 'ApplicationDetail',\n        permission: [\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return new ComplexPermission([RoleConst.USER], [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(to ? to.params.id : '',)], [EditionConst.IS_EE, EditionConst.IS_PE], 'AND')\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return new ComplexPermission([RoleConst.WORKSPACE_MANAGE.getWorkspaceRole(),], [PermissionConst.APPLICATION_ACCESS_READ.getWorkspacePermissionWorkspaceManageRole()], [EditionConst.IS_EE, EditionConst.IS_PE], 'OR')\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return new ComplexPermission([], [() => {\n                const to: any = get_next_route()\n                return PermissionConst.APPLICATION_ACCESS_READ.getApplicationWorkspaceResourcePermission(\n                  to ? to.params.id : '',)\n              }], [EditionConst.IS_EE, EditionConst.IS_PE], 'OR')\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.path.includes('resource-management')) { return RoleConst.ADMIN }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.path.includes('resource-management')) { return PermissionConst.RESOURCE_APPLICATION_ACCESS_READ }\n          },\n        ]\n      },\n      component: () => import('@/views/application/ApplicationAccess.vue'),\n    },\n    {\n      path: 'chat-user',\n      name: 'applicationChatUser',\n      meta: {\n        icon: 'app-user-chat',\n        iconActive: 'app-user-chat-active',\n        title: 'views.chatUser.title',\n        active: 'chat-user',\n        parentPath: '/application/:from/:id/:type',\n        parentName: 'ApplicationDetail',\n        resourceType: SourceTypeEnum.APPLICATION,\n        permission: [\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return new ComplexPermission([RoleConst.USER], [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(to ? to.params.id : '',)], [EditionConst.IS_EE, EditionConst.IS_PE], 'AND')\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return new ComplexPermission([RoleConst.WORKSPACE_MANAGE.getWorkspaceRole()], [PermissionConst.APPLICATION_CHAT_USER_READ.getWorkspacePermissionWorkspaceManageRole()], [EditionConst.IS_EE, EditionConst.IS_PE], 'OR')\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return new ComplexPermission([], [PermissionConst.APPLICATION_CHAT_USER_READ.getApplicationWorkspaceResourcePermission(\n                to ? to.params.id : '',)], [EditionConst.IS_EE, EditionConst.IS_PE], 'OR')\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.path.includes('resource-management')) { return RoleConst.ADMIN }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.path.includes('resource-management')) { return PermissionConst.RESOURCE_APPLICATION_CHAT_USER_READ }\n          },\n        ]\n      },\n      component: () => import('@/views/chat-user/index.vue'),\n    },\n    {\n      path: 'chat-log',\n      name: 'ChatLog',\n      meta: {\n        icon: 'app-document',\n        iconActive: 'app-document-active',\n        title: 'views.chatLog.title',\n        active: 'chat-log',\n        parentPath: '/application/:from/:id/:type',\n        parentName: 'ApplicationDetail',\n        permission: [\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return new ComplexPermission([RoleConst.USER], [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(to ? to.params.id : '',)], [], 'AND')\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return RoleConst.WORKSPACE_MANAGE.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return PermissionConst.APPLICATION_CHAT_LOG_READ.getWorkspacePermissionWorkspaceManageRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.from == 'resource-management') { } else {\n              return PermissionConst.APPLICATION_CHAT_LOG_READ.getApplicationWorkspaceResourcePermission(\n                to ? to.params.id : '',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.path.includes('resource-management')) { return RoleConst.ADMIN }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.path.includes('resource-management')) { return PermissionConst.RESOURCE_APPLICATION_CHAT_LOG_READ }\n          },\n        ]\n      },\n      component: () => import('@/views/chat-log/index.vue'),\n    },\n  ],\n}\n\nexport default ApplicationDetailRouter\n"
  },
  {
    "path": "ui/src/router/modules/application.ts",
    "content": "import { PermissionConst, RoleConst } from '@/utils/permission/data'\nconst applicationRouter = {\n  path: '/application',\n  name: 'application',\n  meta: {\n    title: 'views.application.title',\n    menu: true,\n    permission: [\n      RoleConst.USER.getWorkspaceRole,\n      RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n      PermissionConst.APPLICATION_READ.getWorkspacePermissionWorkspaceManageRole,\n      PermissionConst.APPLICATION_READ.getWorkspacePermission,\n    ],\n    icon: 'app-agent',\n    iconActive: 'app-agent-active',\n    group: 'workspace',\n    order: 1,\n  },\n  redirect: '/application',\n  component: () => import('@/layout/layout-template/SimpleLayout.vue'),\n  children: [\n    {\n      path: '/application',\n      name: 'application-index',\n      meta: { title: '智能体主页', activeMenu: '/application', sameRoute: 'application' },\n      component: () => import('@/views/application/index.vue'),\n      hidden: true,\n    },\n  ],\n}\n\nexport default applicationRouter\n"
  },
  {
    "path": "ui/src/router/modules/document.ts",
    "content": "import { SourceTypeEnum } from '@/enums/common'\nimport { get_next_route } from '@/utils/permission'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n/* type 类型\n    BASE = 0, '通用类型'\n    WEB = 1, 'web站点类型'\n    LARK = 2, '飞书类型'\n    YUQUE = 3, '语雀类型'\n    WORKFLOW = 4, '工作流类型'\n*/\nconst DocumentRouter = {\n  path: '/knowledge/:id/:folderId/:type',\n  name: 'KnowledgeDetail',\n  meta: { title: 'common.fileUpload.document', activeMenu: '/knowledge', breadcrumb: true },\n  component: () => import('@/layout/layout-template/MainLayout.vue'),\n  hidden: true,\n  children: [\n    {\n      path: 'document',\n      name: 'Document',\n      meta: {\n        icon: 'app-document',\n        iconActive: 'app-document-active',\n        title: 'common.fileUpload.document',\n        active: 'document',\n        parentPath: '/knowledge/:id/:folderId/:type',\n        parentName: 'KnowledgeDetail',\n        group: 'KnowledgeDetail',\n        permission: [\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return new ComplexPermission(\n                [RoleConst.USER],\n                [\n                  PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(\n                    to ? to.params.id : '',\n                  ),\n                ],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return RoleConst.WORKSPACE_MANAGE.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_READ\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return PermissionConst.KNOWLEDGE_DOCUMENT_READ.getKnowledgeWorkspaceResourcePermission(\n                to ? to.params.id : '',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return PermissionConst.KNOWLEDGE_DOCUMENT_READ.getWorkspacePermissionWorkspaceManageRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return new ComplexPermission(\n                [RoleConst.EXTENDS_USER.getWorkspaceRole()],\n                [PermissionConst.KNOWLEDGE_DOCUMENT_READ.getWorkspacePermission()],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return RoleConst.USER.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return RoleConst.ADMIN\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_READ\n            }\n          },\n        ],\n      },\n      component: () => import('@/views/document/index.vue'),\n    },\n    {\n      path: 'knowledge-workflow-setting',\n      name: 'knowledgeWorkflowSetting',\n      meta: {\n        title: 'workflow.workflow',\n        icon: 'app-workflow',\n        activeMenu: '/knowledge',\n        parentPath: '/knowledge/:id/:folderId/:type',\n        parentName: 'KnowledgeDetail',\n        resourceType: SourceTypeEnum.KNOWLEDGE,\n        group: 'KnowledgeDetail',\n        permission: [\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return new ComplexPermission(\n                [RoleConst.USER],\n                [\n                  PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(\n                    to ? to.params.id : '',\n                  ),\n                ],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return RoleConst.WORKSPACE_MANAGE.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return PermissionConst.SHARED_KNOWLEDGE_WORKFLOW_READ\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return PermissionConst.KNOWLEDGE_WORKFLOW_READ.getKnowledgeWorkspaceResourcePermission(\n                to ? to.params.id : '',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return PermissionConst.KNOWLEDGE_WORKFLOW_READ.getWorkspacePermissionWorkspaceManageRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return new ComplexPermission(\n                [RoleConst.EXTENDS_USER.getWorkspaceRole()],\n                [PermissionConst.KNOWLEDGE_WORKFLOW_READ.getWorkspacePermission()],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return RoleConst.USER.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return RoleConst.ADMIN\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return PermissionConst.RESOURCE_KNOWLEDGE_WORKFLOW_READ\n            }\n          },\n        ].map((p) => () => {\n          // const to: any = get_next_route()\n          // if (to.params.type !== '4') {\n          //   return false\n          // }\n          return p()\n        }),\n      },\n      // redirect: (menu: any) => {\n      // return `/knowledge/${menu.params.id}/${menu.params.folderId}/workflow`\n      // },\n      component: () => import('@/views/knowledge/WorkflowTransform.vue'),\n    },\n    {\n      path: 'problem',\n      name: 'Problem',\n      meta: {\n        icon: 'app-problems',\n        iconActive: 'QuestionFilled',\n        title: 'views.problem.title',\n        active: 'problem',\n        parentPath: '/knowledge/:id/:folderId/:type',\n        parentName: 'KnowledgeDetail',\n        group: 'KnowledgeDetail',\n        permission: [\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return new ComplexPermission(\n                [RoleConst.USER],\n                [\n                  PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(\n                    to ? to.params.id : '',\n                  ),\n                ],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return RoleConst.WORKSPACE_MANAGE.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return PermissionConst.SHARED_KNOWLEDGE_PROBLEM_READ\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return PermissionConst.KNOWLEDGE_PROBLEM_READ.getKnowledgeWorkspaceResourcePermission(\n                to ? to.params.id : '',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return PermissionConst.KNOWLEDGE_PROBLEM_READ.getWorkspacePermissionWorkspaceManageRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return new ComplexPermission(\n                [RoleConst.EXTENDS_USER.getWorkspaceRole()],\n                [PermissionConst.KNOWLEDGE_PROBLEM_READ.getWorkspacePermission()],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return RoleConst.USER.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return RoleConst.ADMIN\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return PermissionConst.RESOURCE_KNOWLEDGE_PROBLEM_READ\n            }\n          },\n        ],\n      },\n      component: () => import('@/views/problem/index.vue'),\n    },\n    {\n      path: 'hit-test',\n      name: 'KnowledgeHitTest',\n      meta: {\n        icon: 'app-hit-test',\n        title: 'views.application.hitTest.title',\n        active: 'hit-test',\n        parentPath: '/knowledge/:id/:folderId/:type',\n        parentName: 'KnowledgeDetail',\n        group: 'KnowledgeDetail',\n        permission: [\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return new ComplexPermission(\n                [RoleConst.USER],\n                [\n                  PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(\n                    to ? to.params.id : '',\n                  ),\n                ],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return RoleConst.WORKSPACE_MANAGE.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return PermissionConst.SHARED_KNOWLEDGE_HIT_TEST_READ\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return PermissionConst.KNOWLEDGE_HIT_TEST_READ.getKnowledgeWorkspaceResourcePermission(\n                to ? to.params.id : '',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return PermissionConst.KNOWLEDGE_HIT_TEST_READ.getWorkspacePermissionWorkspaceManageRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return new ComplexPermission(\n                [RoleConst.EXTENDS_USER.getWorkspaceRole()],\n                [PermissionConst.KNOWLEDGE_HIT_TEST_READ.getWorkspacePermission()],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return RoleConst.USER.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return RoleConst.ADMIN\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return PermissionConst.RESOURCE_KNOWLEDGE_HIT_TEST\n            }\n          },\n        ],\n      },\n      component: () => import('@/views/hit-test/index.vue'),\n    },\n    {\n      path: 'chat-user',\n      name: 'KnowledgeChatUser',\n      meta: {\n        icon: 'app-user-chat',\n        iconActive: 'app-user-chat-active',\n        title: 'views.chatUser.title',\n        active: 'chat-user',\n        parentPath: '/knowledge/:id/:folderId/:type',\n        parentName: 'KnowledgeDetail',\n        resourceType: SourceTypeEnum.KNOWLEDGE,\n        group: 'KnowledgeDetail',\n        permission: [\n          new ComplexPermission(\n            [\n              RoleConst.ADMIN,\n              () => {\n                const to: any = get_next_route()\n                if (to.params.folderId == 'shared') {\n                  return RoleConst.ADMIN\n                } else if (to.params.folderId == 'resource-management') {\n                  return RoleConst.ADMIN\n                } else {\n                  return RoleConst.WORKSPACE_MANAGE.getWorkspaceRole()\n                }\n              },\n            ],\n            [\n              () => {\n                const to: any = get_next_route()\n                if (to.params.folderId == 'shared') {\n                  return PermissionConst.SHARED_KNOWLEDGE_CHAT_USER_READ\n                } else if (to.params.folderId == 'resource-management') {\n                  return PermissionConst.RESOURCE_KNOWLEDGE_CHAT_USER_READ\n                } else {\n                  return PermissionConst.KNOWLEDGE_CHAT_USER_READ.getKnowledgeWorkspaceResourcePermission(\n                    to ? to.params.id : '',\n                  )\n                }\n              },\n              () => {\n                const to: any = get_next_route()\n                if (to.params.folder_id == 'shared') {\n                  return PermissionConst.SHARED_KNOWLEDGE_CHAT_USER_READ\n                } else if (to.params.folderId == 'resource-management') {\n                  return PermissionConst.RESOURCE_KNOWLEDGE_CHAT_USER_READ\n                } else {\n                  return PermissionConst.KNOWLEDGE_CHAT_USER_READ.getWorkspacePermissionWorkspaceManageRole()\n                }\n              },\n            ],\n            [EditionConst.IS_EE, EditionConst.IS_PE],\n            'OR',\n          ),\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return new ComplexPermission(\n                [RoleConst.USER],\n                [\n                  PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(\n                    to ? to.params.id : '',\n                  ),\n                ],\n                [EditionConst.IS_EE, EditionConst.IS_PE],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return new ComplexPermission(\n                [RoleConst.EXTENDS_USER.getWorkspaceRole()],\n                [PermissionConst.KNOWLEDGE_CHAT_USER_READ.getWorkspacePermission()],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return RoleConst.USER.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return RoleConst.ADMIN\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return PermissionConst.RESOURCE_KNOWLEDGE_CHAT_USER_READ\n            }\n          },\n        ],\n      },\n      component: () => import('@/views/chat-user/index.vue'),\n    },\n    {\n      path: 'setting',\n      name: 'KnowledgeSetting',\n      meta: {\n        icon: 'app-setting',\n        iconActive: 'app-setting-active',\n        title: 'common.setting',\n        active: 'setting',\n        parentPath: '/knowledge/:id/:folderId/:type',\n        parentName: 'KnowledgeDetail',\n        group: 'KnowledgeDetail',\n        permission: [\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return new ComplexPermission(\n                [RoleConst.USER],\n                [\n                  PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(\n                    to ? to.params.id : '',\n                  ),\n                ],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return RoleConst.WORKSPACE_MANAGE.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return PermissionConst.SHARED_KNOWLEDGE_EDIT\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return PermissionConst.KNOWLEDGE_EDIT.getKnowledgeWorkspaceResourcePermission(\n                to ? to.params.id : '',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'shared') {\n              return RoleConst.ADMIN\n            } else if (to.params.folderId == 'resource-management') {\n            } else {\n              return PermissionConst.KNOWLEDGE_EDIT.getWorkspacePermissionWorkspaceManageRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return new ComplexPermission(\n                [RoleConst.EXTENDS_USER.getWorkspaceRole()],\n                [PermissionConst.KNOWLEDGE_EDIT.getWorkspacePermission()],\n                [],\n                'AND',\n              )\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'share') {\n              return RoleConst.USER.getWorkspaceRole()\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return RoleConst.ADMIN\n            }\n          },\n          () => {\n            const to: any = get_next_route()\n            if (to.params.folderId == 'resource-management') {\n              return PermissionConst.RESOURCE_KNOWLEDGE_EDIT\n            }\n          },\n        ],\n      },\n      component: () => import('@/views/knowledge/KnowledgeSetting.vue'),\n    },\n  ],\n}\n\nexport default DocumentRouter\n"
  },
  {
    "path": "ui/src/router/modules/knowledge.ts",
    "content": "import { PermissionConst, EditionConst, RoleConst } from '@/utils/permission/data'\nconst ModelRouter = {\n  path: '/knowledge',\n  name: 'knowledge',\n  meta: {\n    title: 'views.knowledge.title',\n    menu: true,\n    permission: [\n      RoleConst.USER.getWorkspaceRole,\n      RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n      PermissionConst.KNOWLEDGE_READ.getWorkspacePermission,\n      PermissionConst.KNOWLEDGE_READ.getWorkspacePermissionWorkspaceManageRole,\n    ],\n    icon: 'app-knowledge',\n    iconActive: 'app-knowledge-active',\n    group: 'workspace',\n    order: 2,\n  },\n  redirect: '/knowledge',\n  component: () => import('@/layout/layout-template/SimpleLayout.vue'),\n  children: [\n    {\n      path: '/knowledge',\n      name: 'knowledge-index',\n      meta: { title: '知识库主页', activeMenu: '/knowledge', sameRoute: 'knowledge' },\n      component: () => import('@/views/knowledge/index.vue'),\n    },\n\n    // 上传文档\n    {\n      path: '/knowledge/document/upload/:folderId/:type',\n      name: 'UploadDocument',\n      meta: { activeMenu: '/knowledge' },\n      component: () => import('@/views/document/UploadDocument.vue'),\n      hidden: true,\n    },\n    // 上传文档 - 飞书文档\n    {\n      path: '/knowledge/import/lark/:folderId',\n      name: 'ImportLarkDocument',\n      meta: { activeMenu: '/knowledge' },\n      component: () => import('@/views/document/ImportLarkDocument.vue'),\n      hidden: true,\n    },\n    // 上传文档 - 工作流\n    {\n      path: '/knowledge/import/workflow/:folderId',\n      name: 'ImportWorkflowDocument',\n      meta: { activeMenu: '/knowledge' },\n      component: () => import('@/views/document/ImportWorkflowDocument.vue'),\n      hidden: true,\n    },\n  ],\n}\n\nexport default ModelRouter\n"
  },
  {
    "path": "ui/src/router/modules/model.ts",
    "content": "import { PermissionConst, EditionConst, RoleConst } from '@/utils/permission/data'\nconst ModelRouter = {\n  path: '/model',\n  name: 'model',\n  meta: {\n    title: 'views.model.title',\n    menu: true,\n    permission: [\n      RoleConst.USER.getWorkspaceRole,\n      RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n      PermissionConst.MODEL_READ.getWorkspacePermission,\n      PermissionConst.MODEL_READ.getWorkspacePermissionWorkspaceManageRole,\n    ],\n    icon: 'app-model',\n    iconActive: 'app-model-active',\n    group: 'workspace',\n    order: 4,\n  },\n  redirect: '/model',\n  component: () => import('@/layout/layout-template/SimpleLayout.vue'),\n  children: [\n    {\n      path: '/model',\n      name: 'model-index',\n      meta: {\n        title: '模型主页',\n        activeMenu: '/model',\n        sameRoute: 'model',\n      },\n      component: () => import('@/views/model/index.vue'),\n    },\n  ],\n}\n\nexport default ModelRouter\n"
  },
  {
    "path": "ui/src/router/modules/paragraph.ts",
    "content": "const ParagraphRouter = {\n  path: '/paragraph/:id/:documentId',\n  name: 'Paragraph',\n  meta: { title: 'common.fileUpload.document', activeMenu: '/knowledge', breadcrumb: true },\n  component: () => import('@/layout/layout-template/SimpleLayout.vue'),\n  hidden: true,\n  children: [\n    {\n      path: '/paragraph/:id/:documentId',\n      name: 'ParagraphIndex',\n      meta: { activeMenu: '/knowledge' },\n      component: () => import('@/views/paragraph/index.vue'),\n    },\n  ],\n}\n\nexport default ParagraphRouter\n"
  },
  {
    "path": "ui/src/router/modules/system.ts",
    "content": "import { PermissionConst, EditionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\nconst systemRouter = {\n  path: '/system',\n  name: 'system',\n  meta: { title: 'views.system.title' },\n  hidden: true,\n  component: () => import('@/layout/layout-template/SystemMainLayout.vue'),\n  children: [\n    {\n      path: '/system/user',\n      name: 'user',\n      meta: {\n        icon: 'User',\n        iconActive: 'UserFilled',\n        title: 'views.userManage.title',\n        activeMenu: '/system',\n        parentPath: '/system',\n        parentName: 'system',\n        sameRoute: 'user',\n        permission: [RoleConst.ADMIN, PermissionConst.USER_READ],\n      },\n      component: () => import('@/views/system/user-manage/index.vue'),\n    },\n    {\n      path: '/system/workspace',\n      name: 'workspace',\n      meta: {\n        icon: 'app-workspace',\n        iconActive: 'app-workspace-active',\n        title: 'views.workspace.title',\n        activeMenu: '/system',\n        parentPath: '/system',\n        parentName: 'system',\n        sameRoute: 'workspace',\n        permission: [\n          new ComplexPermission(\n            [RoleConst.WORKSPACE_MANAGE, RoleConst.ADMIN],\n            [PermissionConst.WORKSPACE_WORKSPACE_READ, PermissionConst.WORKSPACE_READ],\n            [EditionConst.IS_EE],\n            'OR',\n          ),\n        ],\n      },\n      component: () => import('@/views/system/workspace/index.vue'),\n    },\n    {\n      path: '/system/role',\n      name: 'role',\n      meta: {\n        icon: 'app-role',\n        iconActive: 'app-role-active',\n        title: 'views.role.title',\n        activeMenu: '/system',\n        parentPath: '/system',\n        parentName: 'system',\n        sameRoute: 'role',\n        permission: [\n          new ComplexPermission(\n            [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n            [PermissionConst.ROLE_READ, PermissionConst.WORKSPACE_ROLE_READ],\n            [EditionConst.IS_EE, EditionConst.IS_PE],\n            'OR',\n          ),\n        ],\n      },\n      component: () => import('@/views/system/role/index.vue'),\n    },\n\n    {\n      path: '/system/resource-management',\n      name: 'resourceManagement',\n      meta: {\n        icon: 'app-resource-management',\n        iconActive: 'app-resource-management',\n        title: 'views.system.resource_management.label',\n        activeMenu: '/system',\n        parentPath: '/system',\n        parentName: 'system',\n        permission: [\n          new ComplexPermission(\n            [RoleConst.ADMIN],\n            [PermissionConst.RESOURCE_APPLICATION_READ],\n            [EditionConst.IS_EE],\n            'OR',\n          ),\n          new ComplexPermission(\n            [RoleConst.ADMIN],\n            [PermissionConst.RESOURCE_KNOWLEDGE_READ],\n            [EditionConst.IS_EE],\n            'OR',\n          ),\n          new ComplexPermission(\n            [RoleConst.ADMIN],\n            [PermissionConst.RESOURCE_TOOL_READ],\n            [EditionConst.IS_EE],\n            'OR',\n          ),\n          new ComplexPermission(\n            [RoleConst.ADMIN],\n            [PermissionConst.RESOURCE_MODEL_READ],\n            [EditionConst.IS_EE],\n            'OR',\n          ),\n        ],\n      },\n      children: [\n        {\n          path: '/system/resource-management/application',\n          name: 'ApplicationResourceIndex',\n          meta: {\n            title: 'views.application.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            sameRoute: 'workspace',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.RESOURCE_APPLICATION_READ],\n                [EditionConst.IS_EE],\n                'OR',\n              ),\n            ],\n          },\n          component: () =>\n            import('@/views/system-resource-management/ApplicationResourceIndex.vue'),\n        },\n        {\n          path: '/system/resource-management/knowledge',\n          name: 'KnowledgeResourceIndex',\n          meta: {\n            title: 'views.knowledge.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            sameRoute: 'workspace',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.RESOURCE_KNOWLEDGE_READ],\n                [EditionConst.IS_EE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-resource-management/KnowledgeResourceIndex.vue'),\n        },\n        {\n          path: '/system/resource-management/tool',\n          name: 'ToolResourceIndex',\n          meta: {\n            title: 'views.tool.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            sameRoute: 'workspace',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.RESOURCE_TOOL_READ],\n                [EditionConst.IS_EE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-resource-management/ToolResourceIndex.vue'),\n        },\n        {\n          path: '/system/resource-management/model',\n          name: 'ModelResourceIndex',\n          meta: {\n            title: 'views.model.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.RESOURCE_MODEL_READ],\n                [EditionConst.IS_EE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-resource-management/ModelResourceIndex.vue'),\n        },\n      ],\n    },\n    {\n      path: '/system/authorization',\n      name: 'authorization',\n      meta: {\n        icon: 'app-resource-authorization',\n        iconActive: 'app-resource-authorization-active',\n        title: 'views.system.resourceAuthorization.title',\n        activeMenu: '/system',\n        parentPath: '/system',\n        parentName: 'system',\n        sameRoute: 'authorization',\n        permission: [\n          new ComplexPermission(\n            [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n            [\n              PermissionConst.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_READ,\n              PermissionConst.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_READ\n                .getWorkspacePermissionWorkspaceManageRole,\n            ],\n            [],\n            'OR',\n          ),\n          new ComplexPermission(\n            [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n            [\n              PermissionConst.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_READ,\n              PermissionConst.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_READ\n                .getWorkspacePermissionWorkspaceManageRole,\n            ],\n            [],\n            'OR',\n          ),\n          new ComplexPermission(\n            [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n            [\n              PermissionConst.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_READ,\n              PermissionConst.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_READ\n                .getWorkspacePermissionWorkspaceManageRole,\n            ],\n            [],\n            'OR',\n          ),\n          new ComplexPermission(\n            [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n            [\n              PermissionConst.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_READ,\n              PermissionConst.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_READ\n                .getWorkspacePermissionWorkspaceManageRole,\n            ],\n            [],\n            'OR',\n          ),\n        ],\n      },\n\n      children: [\n        {\n          path: '/system/authorization/application',\n          name: 'authorizationApplication',\n          meta: {\n            title: 'views.application.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            resource: 'APPLICATION',\n            sameRoute: 'authorization',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                [\n                  PermissionConst.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_READ,\n                  PermissionConst.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_READ\n                    .getWorkspacePermissionWorkspaceManageRole,\n                ],\n                [],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system/resource-authorization/index.vue'),\n        },\n        {\n          path: '/system/authorization/knowledge',\n          name: 'authorizationKnowledge',\n          meta: {\n            title: 'views.knowledge.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            resource: 'KNOWLEDGE',\n            sameRoute: 'authorization',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                [\n                  PermissionConst.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_READ,\n                  PermissionConst.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_READ\n                    .getWorkspacePermissionWorkspaceManageRole,\n                ],\n                [],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system/resource-authorization/index.vue'),\n        },\n        {\n          path: '/system/authorization/tool',\n          name: 'authorizationTool',\n          meta: {\n            title: 'views.tool.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            resource: 'TOOL',\n            sameRoute: 'authorization',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                [\n                  PermissionConst.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_READ,\n                  PermissionConst.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_READ\n                    .getWorkspacePermissionWorkspaceManageRole,\n                ],\n                [],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system/resource-authorization/index.vue'),\n        },\n        {\n          path: '/system/authorization/model',\n          name: 'authorizationModel',\n          meta: {\n            title: 'views.model.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            resource: 'MODEL',\n            sameRoute: 'authorization',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                [\n                  PermissionConst.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_READ,\n                  PermissionConst.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_READ\n                    .getWorkspacePermissionWorkspaceManageRole,\n                ],\n                [],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system/resource-authorization/index.vue'),\n        },\n      ],\n    },\n    {\n      path: '/system/shared',\n      name: 'shared',\n      meta: {\n        icon: 'app-shared',\n        iconActive: 'app-shared-active',\n        title: 'views.shared.shared_resources',\n        activeMenu: '/system',\n        parentPath: '/system',\n        parentName: 'system',\n        permission: [\n          new ComplexPermission(\n            [RoleConst.ADMIN],\n            [PermissionConst.SHARED_KNOWLEDGE_READ],\n            [EditionConst.IS_EE],\n            'OR',\n          ),\n          new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.SHARED_TOOL_READ],\n                [EditionConst.IS_EE],\n                'OR',\n              ),\n          new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.SHARED_MODEL_READ],\n                [EditionConst.IS_EE],\n                'OR',\n              ),\n        ],\n      },\n      children: [\n        {\n          path: '/system/shared/knowledge',\n          name: 'knowledgeBase',\n          meta: {\n            title: 'views.knowledge.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.SHARED_KNOWLEDGE_READ],\n                [EditionConst.IS_EE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-shared/KnowLedgeSharedIndex.vue'),\n        },\n        {\n          path: '/system/shared/tool',\n          name: 'tools',\n          meta: {\n            title: 'views.tool.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.SHARED_TOOL_READ],\n                [EditionConst.IS_EE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-shared/ToolSharedIndex.vue'),\n        },\n        {\n          path: '/system/shared/model',\n          name: 'models',\n          meta: {\n            title: 'views.model.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.SHARED_MODEL_READ],\n                [EditionConst.IS_EE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-shared/ModelSharedIndex.vue'),\n        },\n      ],\n    },\n    {\n      path: '/system/chat',\n      name: 'SystemChat',\n      meta: {\n        icon: 'app-user-chat',\n        iconActive: 'app-user-chat',\n        title: 'views.chatUser.title',\n        activeMenu: '/system',\n        parentPath: '/system',\n        parentName: 'system',\n        permission: [\n          new ComplexPermission(\n            [RoleConst.WORKSPACE_MANAGE, RoleConst.ADMIN],\n            [PermissionConst.WORKSPACE_CHAT_USER_READ, PermissionConst.CHAT_USER_READ],\n            [EditionConst.IS_EE, EditionConst.IS_PE],\n            'OR',\n          ),\n          new ComplexPermission(\n            [RoleConst.WORKSPACE_MANAGE, RoleConst.ADMIN],\n            [PermissionConst.WORKSPACE_USER_GROUP_READ, PermissionConst.USER_GROUP_READ],\n            [EditionConst.IS_EE, EditionConst.IS_PE],\n            'OR',\n          ),\n          new ComplexPermission(\n            [RoleConst.WORKSPACE_MANAGE, RoleConst.ADMIN],\n            [PermissionConst.CHAT_USER_AUTH_READ],\n            [EditionConst.IS_EE, EditionConst.IS_PE],\n            'OR',\n          ),\n        ],\n      },\n      children: [\n        {\n          path: '/system/chat/chat-user',\n          name: 'ChatUser',\n          meta: {\n            title: 'views.chatUser.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            sameRoute: 'SystemChat',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.WORKSPACE_MANAGE, RoleConst.ADMIN],\n                [PermissionConst.CHAT_USER_READ, PermissionConst.WORKSPACE_CHAT_USER_READ],\n                [EditionConst.IS_EE, EditionConst.IS_PE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-chat-user/chat-user/index.vue'),\n        },\n        {\n          path: '/system/chat/group',\n          name: 'Group',\n          meta: {\n            title: 'views.chatUser.group.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            sameRoute: 'SystemChat',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.WORKSPACE_MANAGE, RoleConst.ADMIN],\n                [PermissionConst.WORKSPACE_USER_GROUP_READ, PermissionConst.USER_GROUP_READ],\n                [EditionConst.IS_EE, EditionConst.IS_PE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-chat-user/group/index.vue'),\n        },\n        {\n          path: '/system/chat/authentication',\n          name: 'Authentication',\n          meta: {\n            title: 'views.system.authentication.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            sameRoute: 'SystemChat',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.CHAT_USER_AUTH_READ],\n                [EditionConst.IS_EE, EditionConst.IS_PE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-chat-user/authentication/index.vue'),\n        },\n      ],\n    },\n    {\n      path: '/system/setting',\n      name: 'setting',\n      meta: {\n        icon: 'app-setting',\n        iconActive: 'app-setting-active',\n        title: 'views.system.subTitle',\n        activeMenu: '/system',\n        parentPath: '/system',\n        parentName: 'system',\n        sameRoute: 'setting',\n        permission: [\n          new ComplexPermission(\n            [RoleConst.ADMIN],\n            [PermissionConst.APPEARANCE_SETTINGS_READ],\n            [EditionConst.IS_EE, EditionConst.IS_PE],\n            'OR',\n          ),\n          new ComplexPermission(\n            [RoleConst.ADMIN],\n            [PermissionConst.LOGIN_AUTH_READ],\n            [EditionConst.IS_EE, EditionConst.IS_PE],\n            'OR',\n          ),\n          new ComplexPermission([RoleConst.ADMIN], [PermissionConst.EMAIL_SETTING_READ], [], 'OR'),\n        ],\n      },\n      children: [\n        {\n          path: '/system/setting/theme',\n          name: 'theme',\n          meta: {\n            title: 'theme.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            sameRoute: 'setting',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.APPEARANCE_SETTINGS_READ],\n                [EditionConst.IS_EE, EditionConst.IS_PE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-setting/theme/index.vue'),\n        },\n        {\n          path: '/system/authentication',\n          name: 'SystemAuthentication',\n          meta: {\n            title: 'views.system.authentication.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            sameRoute: 'setting',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.LOGIN_AUTH_READ],\n                [EditionConst.IS_EE, EditionConst.IS_PE],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-setting/authentication/index.vue'),\n        },\n        {\n          path: '/system/email',\n          name: 'email',\n          meta: {\n            title: 'views.system.email.title',\n            activeMenu: '/system',\n            parentPath: '/system',\n            parentName: 'system',\n            sameRoute: 'setting',\n            permission: [\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.EMAIL_SETTING_READ],\n                [],\n                'OR',\n              ),\n            ],\n          },\n          component: () => import('@/views/system-setting/email/index.vue'),\n        },\n      ],\n    },\n    {\n      path: '/operate',\n      name: 'operate',\n      meta: {\n        icon: 'app-document',\n        iconActive: 'app-document-active',\n        title: 'views.operateLog.title',\n        activeMenu: '/system',\n        parentPath: '/system',\n        parentName: 'system',\n        sameRoute: 'operate',\n        permission: [\n          new ComplexPermission(\n            [RoleConst.ADMIN],\n            [PermissionConst.OPERATION_LOG_READ],\n            [EditionConst.IS_EE, EditionConst.IS_PE],\n            'OR',\n          ),\n        ],\n      },\n      component: () => import('@/views/system/operate-log/index.vue'),\n    },\n  ],\n}\n\nexport default systemRouter\n"
  },
  {
    "path": "ui/src/router/modules/tool.ts",
    "content": "import { PermissionConst, EditionConst, RoleConst } from '@/utils/permission/data'\nconst ModelRouter = {\n  path: '/tool',\n  name: 'tool',\n  meta: {\n    title: 'views.tool.title',\n    menu: true,\n    permission: [\n      RoleConst.USER.getWorkspaceRole,\n      RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n      PermissionConst.TOOL_READ.getWorkspacePermission,\n      PermissionConst.TOOL_READ.getWorkspacePermissionWorkspaceManageRole,\n    ],\n    icon: 'app-tool',\n    iconActive: 'app-tool-active',\n    group: 'workspace',\n    order: 3,\n  },\n  redirect: '/tool',\n  component: () => import('@/layout/layout-template/SimpleLayout.vue'),\n  children: [\n    {\n      path: '/tool',\n      name: 'tool-index',\n      meta: { title: '工具主页', activeMenu: '/tool' },\n      sameRoute: 'tool',\n      component: () => import('@/views/tool/index.vue'),\n    },\n  ],\n}\n\nexport default ModelRouter\n"
  },
  {
    "path": "ui/src/router/modules/trigger.ts",
    "content": "import { PermissionConst, EditionConst, RoleConst } from '@/utils/permission/data'\nconst ModelRouter = {\n  path: '/trigger',\n  name: 'trigger',\n  meta: {\n    title: 'views.trigger.title',\n    permission: [\n      RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n      PermissionConst.TRIGGER_READ.getWorkspacePermissionWorkspaceManageRole,\n    ],\n    group: 'workspace',\n    order: 5,\n  },\n  hidden: true,\n  redirect: '/trigger',\n  component: () => import('@/layout/layout-template/SimpleLayout.vue'),\n  children: [\n    {\n      path: '/trigger',\n      name: 'trigger-index',\n      meta: { title: '触发器主页', activeMenu: '/trigger' },\n      sameRoute: 'trigger',\n      component: () => import('@/views/trigger/index.vue'),\n    },\n  ],\n}\n\nexport default ModelRouter\n"
  },
  {
    "path": "ui/src/router/routes.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router'\n\nconst modules: any = import.meta.glob('./modules/*.ts', { eager: true })\n\nconst rolesRoutes: RouteRecordRaw[] = [...Object.keys(modules).map((key) => modules[key].default)]\n\nexport const routes: Array<RouteRecordRaw> = [\n  {\n    path: '/',\n    name: 'home',\n    redirect: '/application',\n    children: [\n      ...rolesRoutes,\n      {\n        path: '/no-permission',\n        name: 'noPermission',\n        redirect: '/no-permission',\n        meta: {},\n        children: [\n          {\n            path: '/no-permission',\n            name: 'noPermissionD',\n            meta: {},\n            component: () => import('@/views/error/NoPermission.vue'),\n          },\n        ],\n        component: () => import('@/layout/layout-template/SimpleLayout.vue'),\n      },\n    ],\n  },\n\n  // 高级编排\n  {\n    path: '/application/:from/:id/workflow',\n    name: 'ApplicationWorkflow',\n    meta: { activeMenu: '/application' },\n    component: () => import('@/views/application-workflow/index.vue'),\n  },\n  // 知识库工作流\n  {\n    path: '/knowledge/:id/:folderId/workflow',\n    name: 'KnowledgeWorkflow',\n    meta: { activeMenu: '/knowledge' },\n    component: () => import('@/views/knowledge-workflow/index.vue'),\n  },\n  {\n    path: '/tool/:id/:folderId/workflow',\n    name: 'ToolWorkflow',\n    meta: { activeMenu: '/tool' },\n    component: () => import('@/views/tool-workflow/index.vue'),\n  },\n  // 对话\n  {\n    path: '/chat/:accessToken',\n    name: 'Chat',\n    component: () => import('@/views/chat/index.vue'),\n  },\n  {\n    path: '/demo',\n    name: 'demo',\n    component: () => import('@/views/demo/index.vue'),\n  },\n\n  // 对话用户登录\n  {\n    path: '/user-login/:accessToken',\n    name: 'UserLogin',\n    component: () => import('@/views/chat/user-login/index.vue'),\n  },\n\n  {\n    path: '/login',\n    name: 'login',\n    component: () => import('@/views/login/index.vue'),\n  },\n  {\n    path: '/forgot_password',\n    name: 'ForgotPassword',\n    component: () => import('@/views/login/ForgotPassword.vue'),\n  },\n  {\n    path: '/reset_password/:code/:email',\n    name: 'ResetPassword',\n    component: () => import('@/views/login/ResetPassword.vue'),\n  },\n  {\n    path: '/permission',\n    name: 'permission',\n    component: () => import('@/views/Permission.vue'),\n  },\n  {\n    path: '/no-service',\n    name: 'NoService',\n    component: () => import('@/views/error/NoService.vue'),\n  },\n  {\n    path: '/:pathMatch(.*)',\n    name: '404',\n    component: () => import('@/views/error/404.vue'),\n  },\n]\n"
  },
  {
    "path": "ui/src/stores/index.ts",
    "content": "import useCommonStore from './modules/common'\nimport useLoginStore from './modules/login'\nimport useUserStore from './modules/user'\nimport useFolderStore from './modules/folder'\nimport useThemeStore from './modules/theme'\nimport useKnowledgeStore from './modules/knowledge'\nimport useModelStore from './modules/model'\nimport usePromptStore from './modules/prompt'\nimport useApplicationStore from './modules/application'\nimport useChatUserStore from './modules/chat-user'\nimport useToolStore from './modules/tool'\nconst useStore = () => ({\n  common: useCommonStore(),\n  login: useLoginStore(),\n  user: useUserStore(),\n  folder: useFolderStore(),\n  theme: useThemeStore(),\n  knowledge: useKnowledgeStore(),\n  model: useModelStore(),\n  prompt: usePromptStore(),\n  application: useApplicationStore(),\n  chatUser: useChatUserStore(),\n  tool: useToolStore(),\n})\n\nexport default useStore\n"
  },
  {
    "path": "ui/src/stores/modules/application.ts",
    "content": "import { defineStore } from 'pinia'\nconst useApplicationStore = defineStore('application', {\n  state: () => ({\n    location: `${window.location.origin}${window.MaxKB.chatPrefix ? window.MaxKB.chatPrefix : window.MaxKB.prefix}/`,\n  }),\n  actions: {},\n})\n\nexport default useApplicationStore\n"
  },
  {
    "path": "ui/src/stores/modules/chat-user.ts",
    "content": "import {defineStore} from 'pinia'\nimport ChatAPI from '@/api/chat/chat'\nimport type {ChatProfile, ChatUserProfile} from '@/api/type/chat'\nimport type {LoginRequest} from '@/api/type/user'\nimport type {Ref} from 'vue'\nimport {getBrowserLang} from '@/locales/index'\n\ninterface ChatUser {\n  // 用户id\n  id: string\n}\n\ninterface Chat {\n  chat_profile?: ChatProfile\n  application?: any\n  chatUserProfile?: ChatUserProfile\n  token?: string\n  accessToken?: string\n}\n\nconst useChatUserStore = defineStore('chat-user', {\n  state: (): Chat => ({\n    chat_profile: undefined,\n    application: undefined,\n    accessToken: undefined,\n  }),\n  actions: {\n    getLanguage() {\n      return localStorage.getItem(`${this.accessToken}-locale`) || getBrowserLang()\n    },\n    setAccessToken(accessToken: string) {\n      this.accessToken = accessToken\n    },\n    getChatProfile() {\n      return ChatAPI.chatProfile(this.accessToken as string).then((ok) => {\n        this.chat_profile = ok.data\n\n        return this.chat_profile\n      })\n    },\n    async getChatUserProfile() {\n      const res = await ChatAPI.getChatUserProfile()\n      this.chatUserProfile = res.data\n      return res.data\n    },\n    applicationProfile() {\n      return ChatAPI.applicationProfile().then((ok) => {\n        this.application = ok.data\n        localStorage.setItem(`${this.accessToken}-locale`, ok.data?.language || this.getLanguage())\n      })\n    },\n    isAuthentication() {\n      if (this.chat_profile) {\n        return Promise.resolve(this.chat_profile.authentication)\n      } else {\n        return this.getChatProfile().then((ok) => {\n          return ok.authentication\n        })\n      }\n    },\n    getToken() {\n      if (this.token) {\n        return this.token\n      }\n      const token = sessionStorage.getItem(`${this.accessToken}-accessToken`)\n      if (token) {\n        this.token = token\n        return token\n      }\n      const local_token = localStorage.getItem(`${this.accessToken}-accessToken`)\n      if (local_token) {\n        this.token = local_token\n        return local_token\n      }\n      return localStorage.getItem(`accessToken`)\n    },\n    setToken(token: string) {\n      this.token = token\n      sessionStorage.setItem(`${this.accessToken}-accessToken`, token)\n      localStorage.setItem(`${this.accessToken}-accessToken`, token)\n    },\n    /**\n     *匿名认证\n     */\n    anonymousAuthentication() {\n      return ChatAPI.anonymousAuthentication(this.accessToken as string).then((ok) => {\n        this.setToken(ok.data)\n        return this.token\n      })\n    },\n    passwordAuthentication(password: string) {\n      return ChatAPI.passwordAuthentication(this.accessToken as string, password).then((ok) => {\n        this.setToken(ok.data)\n        return this.token\n      })\n    },\n    login(request: any, loading?: Ref<boolean>) {\n      return ChatAPI.login(this.accessToken as string, request, loading).then((ok) => {\n        this.setToken(ok.data.token)\n        return this.token\n      })\n    },\n    ldapLogin(request: LoginRequest, loading?: Ref<boolean>) {\n      return ChatAPI.ldapLogin(this.accessToken as string, request, loading).then((ok) => {\n        this.setToken(ok.data.token)\n        return this.token\n      })\n    },\n    logout() {\n      return ChatAPI.logout().then(() => {\n        sessionStorage.removeItem(`${this.accessToken}-accessToken`)\n        localStorage.removeItem(`${this.accessToken}-accessToken`)\n        this.token = undefined\n        return true\n      })\n    },\n    async dingCallback(code: string, accessToken: string) {\n      return ChatAPI.getDingCallback(code, accessToken).then((ok) => {\n        this.setToken(ok.data.token)\n        return this.token\n      })\n    },\n    async dingOauth2Callback(code: string, accessToken: string) {\n      return ChatAPI.getDingOauth2Callback(code, accessToken).then((ok) => {\n        this.setToken(ok.data.token)\n        return this.token\n      })\n    },\n    async wecomCallback(code: string, accessToken: string) {\n      return ChatAPI.getWecomCallback(code, accessToken).then((ok) => {\n        this.setToken(ok.data.token)\n        return this.token\n      })\n    },\n    async larkCallback(code: string, accessToken: string) {\n      return ChatAPI.getLarkCallback(code, accessToken).then((ok) => {\n        this.setToken(ok.data.token)\n        return this.token\n      })\n    },\n    async getQrType() {\n      return ChatAPI.getQrType().then((ok) => {\n        return ok.data\n      })\n    },\n    async getQrSource() {\n      return ChatAPI.getQrSource().then((ok) => {\n        return ok.data\n      })\n    },\n  },\n})\n\nexport default useChatUserStore\n"
  },
  {
    "path": "ui/src/stores/modules/common.ts",
    "content": "import { defineStore } from 'pinia'\nimport { DeviceType, ValidType } from '@/enums/common'\nimport type { Ref } from 'vue'\nimport userApi from '@/api/system/user-manage'\n\nexport interface commonTypes {\n  breadcrumb: any\n  paginationConfig: any | null\n  search: any\n  device: string\n}\n\nconst useCommonStore = defineStore('common',{\n  state: (): commonTypes => ({\n    breadcrumb: null,\n    // 搜索和分页缓存\n    paginationConfig: {},\n    search: {},\n    device: DeviceType.Desktop\n  }),\n  actions: {\n    saveBreadcrumb(data: any) {\n      this.breadcrumb = data\n    },\n    savePage(val: string, data: any) {\n      this.paginationConfig[val] = data\n    },\n    saveCondition(val: string, data: any) {\n      this.search[val] = data\n    },\n    toggleDevice(value: DeviceType) {\n      this.device = value\n    },\n    isMobile() {\n      return this.device === DeviceType.Mobile\n    },\n    async asyncGetValid(valid_type: ValidType, valid_count: number, loading?: Ref<boolean>) {\n      return new Promise((resolve, reject) => {\n        userApi\n          .getValid(valid_type, valid_count, loading)\n          .then((data) => {\n            resolve(data)\n          })\n          .catch((error) => {\n            reject(error)\n          })\n      })\n    }\n  }\n})\n\nexport default useCommonStore\n"
  },
  {
    "path": "ui/src/stores/modules/folder.ts",
    "content": "import { defineStore } from 'pinia'\nimport { type Ref } from 'vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst useFolderStore = defineStore('folder', {\n  state: () => ({\n    currentFolder: {} as any,\n  }),\n  actions: {\n    setCurrentFolder(folder: any) {\n      this.currentFolder = folder\n    },\n    async asyncGetFolder(source: string, data: any, systemType: any, loading?: Ref<boolean>) {\n      return new Promise((resolve, reject) => {\n        loadSharedApi({\n          type: 'folder',\n          systemType,\n        })\n          .getFolder(source, data, loading)\n          .then((res: any) => {\n            resolve(res)\n          })\n          .catch((error: any) => {\n            reject(error)\n          })\n      })\n    },\n  },\n})\n\nexport default useFolderStore\n"
  },
  {
    "path": "ui/src/stores/modules/knowledge.ts",
    "content": "import { defineStore } from 'pinia'\nimport type { knowledgeData } from '@/api/type/knowledge'\nimport type { UploadUserFile } from 'element-plus'\nimport { type Ref } from 'vue'\nimport knowledgeApi from '@/api/knowledge/knowledge'\n\nexport interface knowledgeStateTypes {\n  baseInfo: knowledgeData | null\n  webInfo: any\n  documentsType: string\n  documentsFiles: UploadUserFile[]\n  knowledgeList: any[]\n}\n\nconst useKnowledgeStore = defineStore('knowledge', {\n  state: (): knowledgeStateTypes => ({\n    baseInfo: null,\n    webInfo: null,\n    documentsType: '',\n    documentsFiles: [],\n    knowledgeList: [],\n  }),\n  actions: {\n    saveBaseInfo(info: knowledgeData | null) {\n      this.baseInfo = info\n    },\n    saveWebInfo(info: any) {\n      this.webInfo = info\n    },\n    saveDocumentsType(val: string) {\n      this.documentsType = val\n    },\n    saveDocumentsFile(file: UploadUserFile[]) {\n      this.documentsFiles = file\n    },\n    setKnowledgeList(list: any[]) {\n      this.knowledgeList = list\n    },\n  },\n})\n\nexport default useKnowledgeStore\n"
  },
  {
    "path": "ui/src/stores/modules/login.ts",
    "content": "import { defineStore } from 'pinia'\nimport { type Ref } from 'vue'\nimport LoginApi from '@/api/user/login'\nimport type { LoginRequest } from '@/api/type/login'\nimport useUserStore from './user'\n\nconst useLoginStore = defineStore('login', {\n  state: () => ({\n    token: '',\n  }),\n  actions: {\n    getToken(): string | null {\n      if (this.token) {\n        return this.token\n      }\n      return localStorage.getItem('token')\n    },\n\n    async asyncLogin(data: any, loading?: Ref<boolean>) {\n      return LoginApi.login(data).then((ok) => {\n        this.token = ok?.data?.token\n        localStorage.setItem('token', ok?.data?.token)\n        const user = useUserStore()\n        return user.profile(loading)\n      })\n    },\n    async asyncLdapLogin(data: LoginRequest, loading?: Ref<boolean>) {\n      return LoginApi.ldapLogin(data).then((ok) => {\n        this.token = ok?.data?.token\n        localStorage.setItem('token', ok?.data?.token)\n        const user = useUserStore()\n        return user.profile(loading)\n      })\n    },\n    async dingCallback(code: string) {\n      return LoginApi.getDingCallback(code).then((ok) => {\n        this.token = ok?.data?.token\n        localStorage.setItem('token', ok?.data?.token)\n        const user = useUserStore()\n        return user.profile()\n      })\n    },\n    async dingOauth2Callback(code: string) {\n      return LoginApi.getDingOauth2Callback(code).then((ok) => {\n        this.token = ok?.data?.token\n        localStorage.setItem('token', ok?.data?.token)\n        const user = useUserStore()\n        return user.profile()\n      })\n    },\n    async wecomCallback(code: string) {\n      return LoginApi.getWecomCallback(code).then((ok) => {\n        this.token = ok?.data?.token\n        localStorage.setItem('token', ok?.data?.token)\n        const user = useUserStore()\n        return user.profile()\n      })\n    },\n    async larkCallback(code: string) {\n      return LoginApi.getLarkCallback(code).then((ok) => {\n        this.token = ok?.data?.token\n        localStorage.setItem('token', ok?.data?.token)\n        const user = useUserStore()\n        return user.profile()\n      })\n    },\n\n    async logout() {\n      return LoginApi.logout().then(() => {\n        localStorage.removeItem('token')\n        return true\n      })\n    },\n    async getAuthType() {\n      return LoginApi.getAuthType().then((ok) => {\n        return ok.data\n      })\n    },\n    async getQrType() {\n      return LoginApi.getQrType().then((ok) => {\n        return ok.data\n      })\n    },\n    async getQrSource() {\n      return LoginApi.getQrSource().then((ok) => {\n        return ok.data\n      })\n    },\n    async samlLogin() {\n      return LoginApi.samlLogin().then((ok) => {\n      })\n    }\n  },\n})\n\nexport default useLoginStore\n"
  },
  {
    "path": "ui/src/stores/modules/model.ts",
    "content": "import { defineStore } from 'pinia'\nimport { type Ref } from 'vue'\nimport ProviderApi from '@/api/model/provider'\nimport ModelApi from '@/api/model/model'\nimport type { ListModelRequest } from '@/api/type/model'\n\nconst useModelStore = defineStore('model', {\n  state: () => ({}),\n  actions: {\n    // 仅限在应用下拉列表使用，非共享资源\n    async asyncGetSelectModel(data?: ListModelRequest, loading?: Ref<boolean>) {\n      return new Promise((resolve, reject) => {\n        ModelApi.getSelectModelList(data, loading)\n          .then((res: any) => {\n            resolve(res)\n          })\n          .catch((error: any) => {\n            reject(error)\n          })\n      })\n    },\n    async asyncGetProvider(loading?: Ref<boolean>) {\n      return new Promise((resolve, reject) => {\n        ProviderApi.getProvider(loading)\n          .then((res) => {\n            resolve(res)\n          })\n          .catch((error) => {\n            reject(error)\n          })\n      })\n    },\n  },\n})\n\nexport default useModelStore\n"
  },
  {
    "path": "ui/src/stores/modules/prompt.ts",
    "content": "import { defineStore } from 'pinia'\nimport { t } from '@/locales'\nexport interface promptTypes {\n  user: string\n  formValue: { model_id: string; prompt: string; model_params_setting: any }\n}\n\nconst usePromptStore = defineStore('prompt', {\n  state: (): promptTypes[] => JSON.parse(localStorage.getItem('PROMPT_CACHE') || '[]'),\n  actions: {\n    save(user: string, formValue: any) {\n      this.$state.forEach((item: any, index: number) => {\n        if (item.user === user) {\n          this.$state.splice(index, 1)\n        }\n      })\n      this.$state.push({ user, formValue })\n      localStorage.setItem('PROMPT_CACHE', JSON.stringify(this.$state))\n    },\n    get(user: string) {\n      for (let i = 0; i < this.$state.length; i++) {\n        if (this.$state[i].user === user) {\n          return this.$state[i].formValue\n        }\n      }\n      return {\n        model_id: '',\n        model_params_setting: {},\n        prompt:\n          t('views.document.generateQuestion.prompt1', { data: '{data}' }) +\n          '<question></question>' +\n          t('views.document.generateQuestion.prompt2'),\n      }\n    },\n  },\n})\n\nexport default usePromptStore\n"
  },
  {
    "path": "ui/src/stores/modules/theme.ts",
    "content": "import { defineStore } from 'pinia'\nimport { cloneDeep } from 'lodash'\nimport { useElementPlusTheme } from 'use-element-plus-theme'\nimport ThemeApi from '@/api/system-settings/theme'\nimport type {Ref} from \"vue\";\nexport interface themeStateTypes {\n  themeInfo: any\n}\nconst defalueColor = '#3370FF'\n\nconst useThemeStore = defineStore('theme', {\n  state: (): themeStateTypes => ({\n    themeInfo: null,\n  }),\n  actions: {\n    isDefaultTheme() {\n      return !this.themeInfo?.theme || this.themeInfo?.theme === defalueColor\n    },\n\n    setTheme(data?: any) {\n      const { changeTheme } = useElementPlusTheme(this.themeInfo?.theme || defalueColor)\n      changeTheme(data?.['theme'] || defalueColor)\n      this.themeInfo = cloneDeep(data)\n    },\n\n    async theme(loading?: Ref<boolean>) {\n      return await ThemeApi.getThemeInfo(loading).then((ok) => {\n        this.setTheme(ok.data)\n      })\n    },\n  },\n})\n\nexport default useThemeStore\n"
  },
  {
    "path": "ui/src/stores/modules/tool.ts",
    "content": "import { defineStore } from 'pinia'\nimport { type Ref } from 'vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport type { pageRequest } from '@/api/type/common'\nimport useUserStore from './user'\nimport useFolderStore from './folder'\n\nconst useToolStore = defineStore('tool', {\n  state: () => ({\n    toolList: [] as any[],\n    tool_type: '' as string,\n  }),\n  actions: {\n    setToolList(list: any[]) {\n      this.toolList = list\n    },\n    setToolType(type: string) {\n      this.tool_type = type\n    },\n  },\n})\n\nexport default useToolStore\n"
  },
  {
    "path": "ui/src/stores/modules/user.ts",
    "content": "import {defineStore} from 'pinia'\nimport {type Ref} from 'vue'\nimport type {User} from '@/api/type/user'\nimport UserApi from '@/api/user/user'\nimport LoginApi from '@/api/user/login'\nimport {useLocalStorage} from '@vueuse/core'\n\nimport {localeConfigKey, getBrowserLang} from '@/locales/index'\nimport useThemeStore from './theme'\nimport {defaultPlatformSetting} from '@/utils/theme'\nimport useLoginStore from './login'\n\nexport interface userStateTypes {\n  userInfo: User | null\n  version?: string\n  license_is_valid: boolean\n  edition: 'CE' | 'PE' | 'EE'\n  workspace_id: string\n  workspace_list: Array<any>,\n  rasKey: string\n}\n\nconst useUserStore = defineStore('user', {\n  state: (): userStateTypes => ({\n    userInfo: null,\n    version: '',\n    license_is_valid: false,\n    edition: 'CE',\n    workspace_id: '',\n    workspace_list: [],\n    rasKey: '',\n  }),\n  actions: {\n    getLanguage() {\n      return localStorage.getItem('MaxKB-locale') || getBrowserLang()\n    },\n    setWorkspaceId(workspace_id: string) {\n      this.workspace_id = workspace_id\n      localStorage.setItem('workspace_id', workspace_id)\n    },\n    getWorkspaceId(): string | null {\n      this.workspace_id = this.workspace_id || localStorage.getItem('workspace_id') || 'default'\n      return this.workspace_id\n    },\n\n    getPermissions() {\n      if (this.userInfo) {\n        if (this.isEE()) {\n          return [...this.userInfo?.permissions, 'X-PACK-EE']\n        } else if (this.isPE()) {\n          return [...this.userInfo?.permissions, 'X-PACK-PE']\n        }\n        return this.userInfo?.permissions\n      } else {\n        return []\n      }\n    },\n    getEdition() {\n      if (this.userInfo) {\n        if (this.isEE()) {\n          return 'X-PACK-EE'\n        } else if (this.isPE()) {\n          return 'X-PACK-PE'\n        } else {\n          return 'X-PACK-CE'\n        }\n      }\n      return 'X-PACK-CE'\n    },\n    getRole() {\n      if (this.userInfo) {\n        return this.userInfo?.role\n      } else {\n        return []\n      }\n    },\n\n    is_admin() {\n      return this.userInfo?.role.includes('ADMIN')\n    },\n    showXpack() {\n      return this.edition != 'CE'\n    },\n    isEnterprise() {\n      return this.edition != 'CE' && !this.license_is_valid\n    },\n    isExpire() {\n      return this.edition != 'CE' && !this.license_is_valid\n    },\n    isCE() {\n      return this.edition == 'CE'\n    },\n    isPE() {\n      return this.edition == 'PE' && this.license_is_valid\n    },\n    isEE() {\n      return this.edition == 'EE' && this.license_is_valid\n    },\n    getHasPermissionWorkspaceManage() {\n      const workspaceManagePermissions = this.userInfo?.role\n        .filter((permission) => permission.startsWith('WORKSPACE_MANAGE'))\n        .map((permission) => {\n          const parts = permission.split('/WORKSPACE/');\n          return parts.length > 1 ? parts[1] : null; // 提取工作空间ID\n        })\n        .filter((id) => id !== null); // 过滤掉无效的ID\n      if (workspaceManagePermissions && workspaceManagePermissions.length > 0) {\n        if (workspaceManagePermissions.includes(localStorage.getItem('workspace_id') || 'default')) {\n          return\n        }\n        this.setWorkspaceId(workspaceManagePermissions[0])\n      }\n    },\n    getEditionName() {\n      return this.edition\n    },\n    async profile(loading?: Ref<boolean>) {\n      return UserApi.getUserProfile(loading).then((ok) => {\n        this.userInfo = ok.data\n        const workspace_list =\n          ok.data.workspace_list && ok.data.workspace_list.length > 0\n            ? ok.data.workspace_list\n            : [{id: 'default', name: 'default'}]\n        const workspace_id = this.getWorkspaceId()\n        if (!workspace_id || !workspace_list.some((w) => w.id == workspace_id)) {\n          this.setWorkspaceId(workspace_list[0].id)\n        }\n        this.workspace_list = workspace_list\n        useLocalStorage<string>(localeConfigKey, 'en-US').value =\n          ok?.data?.language || this.getLanguage()\n        const theme = useThemeStore()\n        theme.setTheme()\n        return this.asyncGetProfile()\n      })\n    },\n\n    async asyncGetProfile() {\n      return new Promise((resolve, reject) => {\n        UserApi.getProfile()\n          .then(async (ok) => {\n            // this.version = ok.data?.version || '-'\n            this.license_is_valid = ok.data.license_is_valid\n            this.edition = ok.data.edition\n            this.version = ok.data.version\n            this.rasKey = ok.data.ras\n            const theme = useThemeStore()\n            if (this.isEE() || this.isPE()) {\n              await theme.theme()\n            } else {\n              theme.setTheme()\n              theme.themeInfo = {\n                ...defaultPlatformSetting,\n              }\n            }\n            resolve(ok)\n          })\n          .catch((error) => {\n            reject(error)\n          })\n      })\n    },\n    async postUserLanguage(lang: string, loading?: Ref<boolean>) {\n      return new Promise((resolve, reject) => {\n        LoginApi.postLanguage({language: lang}, loading)\n          .then(async (ok) => {\n            useLocalStorage(localeConfigKey, 'en-US').value = lang\n            window.location.reload()\n            resolve(ok)\n          })\n          .catch((error) => {\n            reject(error)\n          })\n      })\n    },\n  },\n})\n\nexport default useUserStore\n"
  },
  {
    "path": "ui/src/styles/app.scss",
    "content": "@font-face {\n  font-family: AlibabaPuHuiTi;\n  src:\n    url('./font/AlibabaPuHuiTi-3-55-Regular.woff') format('woff'),\n    url('./font/AlibabaPuHuiTi-3-55-Regular.ttf') format('truetype'),\n    url('./font/AlibabaPuHuiTi-3-55-Regular.eot') format('eot'),\n    url('./font/AlibabaPuHuiTi-3-55-Regular.otf') format('opentype'),\n    url('./font/AlibabaPuHuiTi-3-55-Regular.woff2') format('woff2');\n}\n* {\n  margin: 0;\n  padding: 0;\n}\n\nhtml {\n  height: 100%;\n  box-sizing: border-box;\n  font-size: 100%;\n}\n\nbody {\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n  font-family: 'PingFang SC', AlibabaPuHuiTi !important;\n  font-size: 14px;\n  font-style: normal;\n  font-weight: 500;\n  height: 100%;\n  margin: 0;\n  padding: 0;\n  color: var(--el-text-color-primary);\n}\n\n#app {\n  height: 100%;\n}\n\n:focus {\n  outline: none;\n}\n\na:active {\n  outline: none;\n}\n\na,\na:focus,\na:hover {\n  cursor: pointer;\n  color: inherit;\n  text-decoration: none;\n}\n\ndiv:focus {\n  outline: none;\n}\n\nul {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\npre {\n  white-space: pre-wrap;\n  word-break: break-all;\n}\n\n/* 滚动条整体部分 */\n::-webkit-scrollbar {\n  width: 6px; // 纵向滚动条宽度\n  height: 6px; // 横向滚动条高度\n}\n\n/* 滑块 */\n::-webkit-scrollbar-thumb {\n  border-radius: 5px;\n}\n\n/* 轨道 */\n::-webkit-scrollbar-track {\n  border-radius: 5px;\n  background-color: transparent;\n}\n\n.clearfix:after {\n  content: '';\n  display: block;\n  clear: both;\n}\n\nh1 {\n  font-size: 24px;\n}\n\nh2 {\n  font-size: 20px;\n  font-weight: 500;\n}\n\nh3 {\n  font-size: 18px;\n}\n\nh4 {\n  font-size: 16px;\n}\n\nh5 {\n  font-size: 14px;\n  font-weight: 500;\n}\n.font-small {\n  font-size: 12px;\n}\n\n.bold {\n  font-weight: 600;\n}\n.medium {\n  font-weight: 500;\n}\n.lighter {\n  font-weight: 400;\n}\n\n.w-full {\n  width: 100%;\n}\n.h-full {\n  height: 100%;\n}\n.w-120 {\n  width: 120px;\n}\n.w-180 {\n  width: 180px;\n}\n\n.w-240 {\n  width: 240px;\n}\n.w-280 {\n  width: 280px;\n}\n.w-500 {\n  width: 500px;\n}\n.max-w-200 {\n  max-width: 200px;\n}\n.max-w-350 {\n  max-width: 350px;\n}\n\n.mt-4 {\n  margin-top: calc(var(--app-base-px) - 4px);\n}\n\n.mt-8 {\n  margin-top: var(--app-base-px);\n}\n.mt-12 {\n  margin-top: calc(var(--app-base-px) + 4px);\n}\n.mt-16 {\n  margin-top: calc(var(--app-base-px) * 2);\n}\n.mt-20 {\n  margin-top: calc(var(--app-base-px) * 2 + 4px);\n}\n.mt-24 {\n  margin-top: calc(var(--app-base-px) * 3);\n}\n\n.mb-4 {\n  margin-bottom: calc(var(--app-base-px) - 4px);\n}\n.mb-8 {\n  margin-bottom: var(--app-base-px);\n}\n.mb-12 {\n  margin-bottom: calc(var(--app-base-px) + 4px);\n}\n.mb-16 {\n  margin-bottom: calc(var(--app-base-px) * 2);\n}\n.mb-24 {\n  margin-bottom: calc(var(--app-base-px) * 3);\n}\n.ml-4 {\n  margin-left: calc(var(--app-base-px) - 4px);\n}\n.ml-8 {\n  margin-left: var(--app-base-px);\n}\n.ml-12 {\n  margin-left: calc(var(--app-base-px) + 4px);\n}\n.ml-16 {\n  margin-left: calc(var(--app-base-px) * 2);\n}\n.ml-24 {\n  margin-left: calc(var(--app-base-px) * 3);\n}\n.mr-4 {\n  margin-right: calc(var(--app-base-px) - 4px);\n}\n.mr-8 {\n  margin-right: var(--app-base-px);\n}\n.mr-12 {\n  margin-right: calc(var(--app-base-px) + 4px);\n}\n.mr-16 {\n  margin-right: calc(var(--app-base-px) * 2);\n}\n.mr-24 {\n  margin-right: calc(var(--app-base-px) * 3);\n}\n.p-8 {\n  padding: var(--app-base-px);\n}\n.p-12 {\n  padding: calc(var(--app-base-px) + 4px);\n}\n.p-16 {\n  padding: calc(var(--app-base-px) * 2);\n}\n.p-24 {\n  padding: calc(var(--app-base-px) * 3);\n}\n.p-4-12 {\n  padding: calc(var(--app-base-px) - 4px) calc(var(--app-base-px) + 4px);\n}\n.p-8-12 {\n  padding: calc(var(--app-base-px)) calc(var(--app-base-px) + 4px);\n}\n.p-8-16 {\n  padding: calc(var(--app-base-px)) calc(var(--app-base-px) * 2);\n}\n.p-12-16 {\n  padding: calc(var(--app-base-px) + 4px) calc(var(--app-base-px) * 2);\n}\n.p-12-24 {\n  padding: calc(var(--app-base-px) + 4px) calc(var(--app-base-px) * 3);\n}\n.p-16-24 {\n  padding: calc(var(--app-base-px) * 2) calc(var(--app-base-px) * 3);\n}\n\n.pt-0 {\n  padding-top: 0;\n}\n.pb-0 {\n  padding-bottom: 0;\n}\n\n.float-right {\n  float: right;\n}\n\n.flex {\n  display: flex;\n}\n\n.flex-center {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.flex-between {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n.flex-wrap {\n  display: flex;\n  flex-wrap: wrap;\n  align-content: space-between;\n}\n\n.align-center {\n  align-items: center;\n}\n\n.align-baseline {\n  align-items: baseline;\n}\n\n.justify-center {\n  justify-content: center;\n}\n\n.justify-end {\n  justify-content: end;\n}\n\n.text-left {\n  text-align: left;\n}\n.text-center {\n  text-align: center;\n}\n.text-right {\n  text-align: right;\n}\n\n.vertical-middle {\n  vertical-align: middle;\n}\n\n.line-height-22 {\n  line-height: 22px;\n}\n\n.border {\n  border: 1px solid var(--el-border-color);\n}\n\n.border-l {\n  border-left: 1px solid var(--el-border-color);\n}\n\n.border-b {\n  border-bottom: 1px solid var(--el-border-color);\n}\n.border-r {\n  border-right: 1px solid var(--el-border-color);\n}\n.border-t {\n  border-top: 1px solid var(--el-border-color);\n}\n\n.border-b-light {\n  border-bottom: 1px solid var(--el-border-color-lighter);\n}\n\n.border-r-6 {\n  border-radius: var(--app-border-radius-small);\n}\n.border-r-8 {\n  border-radius: var(--app-border-radius-base);\n}\n\n.border-t-dashed {\n  border-top: 1px dashed var(--el-border-color);\n}\n.border-primary {\n  border: 1px solid var(--el-color-primary);\n  color: var(--el-color-primary);\n}\n\n.border-none {\n  border: none !important;\n}\n\n.border-active {\n  border-color: var(--el-color-primary) !important;\n}\n\n.cursor {\n  cursor: pointer;\n}\n.notAllowed {\n  cursor: not-allowed;\n}\n\n/*\n  超出省略号\n*/\n\n.ellipsis {\n  display: inline-block;\n  max-width: 130px;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  overflow: hidden;\n}\n\n/*\n  双行超出省略号，其他行数自定义 -webkit-line-clamp\n*/\n.ellipsis-2 {\n  display: -webkit-box;\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 2;\n  overflow: hidden;\n}\n\n.ellipsis-1 {\n  display: -webkit-box;\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 1;\n  overflow: hidden;\n  word-break: break-all;\n}\n\n.break-all {\n  word-break: break-all;\n}\n\n.pre-wrap {\n  white-space: pre-wrap;\n}\n\n// 颜色\n\n.color-primary {\n  color: var(--el-color-primary);\n}\n\n.color-danger {\n  color: var(--el-color-danger);\n}\n.color-warning {\n  color: var(--el-color-warning);\n}\n.color-success {\n  color: var(--el-color-success);\n}\n.color-info {\n  color: var(--el-color-info);\n}\n.color-text-primary {\n  color: var(--el-text-color-primary);\n}\n.color-secondary {\n  color: var(--app-text-color-secondary);\n}\n.color-input-placeholder {\n  color: var(--app-input-color-placeholder);\n}\n.color-organe {\n  color: #ff8800;\n}\n\n/*\n  头像渐变背景\n*/\n.avatar-gradient {\n  background: var(--app-avatar-gradient-color);\n}\n\n.avatar-light {\n  background: var(--el-color-primary-light-3);\n}\n.avatar-purple {\n  background: #7f3bf5;\n}\n.avatar-green {\n  background: var(--el-color-success);\n}\n.avatar-blue {\n  background: #3370ff;\n}\n\n.avatar-orange {\n  background: #ff8800;\n}\n\n.layout-bg {\n  background: var(--app-layout-bg-color);\n}\n.white-bg {\n  background: #ffffff;\n}\n\n/*\n  标题前带竖线样式\n*/\n.title-decoration-1 {\n  position: relative;\n  padding-left: 12px;\n  &:before {\n    position: absolute;\n    left: 2px;\n    top: 50%;\n    transform: translate(-50%, -50%);\n    width: 2px;\n    height: 80%;\n    content: '';\n    background: var(--el-color-primary);\n  }\n}\n\n/* tag */\n.default-tag {\n  background: var(--el-color-primary-light-8);\n  color: var(--el-color-primary);\n  border: none;\n}\n.danger-tag {\n  background: var(--tag-danger-bg);\n  color: #d03f3b;\n  border: none;\n}\n.success-tag {\n  background: var(--tag-success-bg);\n  color: var(--tag-success-color);\n  border: none;\n}\n.warning-tag {\n  background: var(--tag-warning-bg);\n  color: var(--tag-warning-color);\n  border: none;\n}\n\n.info-tag {\n  background: rgba(var(--el-text-color-primary-rgb), 0.1);\n  color: var(--app-text-color-secondary);\n  border: none;\n}\n\n.blue-tag {\n  background: var(--tag-default-bg);\n  color: var(--tag-default-color);\n  border: none;\n}\n\n// 蓝色提示框\n.update-info {\n  background: #d6e2ff;\n  line-height: 25px;\n}\n\n/*\n  card 无边框无阴影 灰色背景\n*/\n.card-never {\n  background: var(--app-layout-bg-color);\n  border: none !important;\n}\n\n/*\n   图标旋转90度\n*/\n.rotate-90 {\n  transform: rotateZ(90deg);\n}\n.rotate-180 {\n  transform: rotateZ(180deg);\n}\n\n/*\n  内容部分 自适应高度\n*/\n.main-calc-height {\n  height: var(--app-main-height);\n  box-sizing: border-box;\n}\n\n// 自定义主题\n.custom-header {\n  background: var(--el-color-primary-light-9) !important;\n}\n\n.chat-background {\n  background-repeat: no-repeat;\n  background-position: center;\n  background-size: cover;\n}\n\n.arrow-icon {\n  transition: 0.2s;\n}\n"
  },
  {
    "path": "ui/src/styles/component.scss",
    "content": "// 复合框\n.complex-search {\n  border: 1px solid var(--el-border-color);\n  border-radius: var(--el-border-radius-base);\n  .el-select__wrapper {\n    box-shadow: none !important;\n    border-radius: 0 var(--el-border-radius-base) var(--el-border-radius-base) 0;\n  }\n  .el-input__wrapper {\n    box-shadow: none !important;\n    border-radius: 0 var(--el-border-radius-base) var(--el-border-radius-base) 0;\n  }\n  &__left {\n    border-right: 1px solid var(--el-border-color);\n    .el-select__wrapper {\n      box-shadow: none !important;\n      border-radius: var(--el-border-radius-base) 0 0 var(--el-border-radius-base);\n    }\n  }\n}\n.complex-input {\n  border: 1px solid var(--el-border-color);\n  border-radius: var(--el-border-radius-base);\n\n  &__left {\n    .el-input__wrapper {\n      box-shadow: none !important;\n      border-radius: var(--el-border-radius-base) 0 0 var(--el-border-radius-base);\n    }\n  }\n}\n\n// 编辑头像\n.edit-avatar {\n  position: relative;\n  line-height: 0;\n  .edit-mask {\n    position: absolute;\n    left: 0;\n    background: rgba(0, 0, 0, 0.4);\n  }\n}\n\n// radio 一行一个样式\n.radio-block {\n  width: 100%;\n  display: inline-flex;\n  .el-radio {\n    align-items: flex-start;\n    height: 100%;\n    width: 100%;\n    margin-top: 8px;\n  }\n  .el-radio__label {\n    width: 100%;\n    margin-top: -8px;\n    line-height: 30px;\n  }\n}\n// radio 一行一个样式 有输入框 上传头像的内容\n.radio-block-avatar {\n  width: 100%;\n  display: block;\n  .el-radio {\n    align-items: flex-start;\n    height: 100%;\n    width: 100%;\n    margin: 0;\n  }\n  .el-radio__label {\n    width: 100%;\n    margin-top: -8px;\n    line-height: 30px;\n  }\n}\n\n.card__radio {\n  width: 100%;\n  display: block;\n  line-height: 22px;\n  .el-radio {\n    white-space: break-spaces;\n    width: 100%;\n    height: 100%;\n    line-height: 22px;\n    color: var(--app-text-color);\n  }\n\n  .el-radio__label {\n    padding-left: 24px;\n    width: 100%;\n  }\n  .el-radio__input {\n    position: absolute;\n    top: 5px;\n  }\n  .el-card__body {\n    padding: calc(var(--app-base-px) + 4px) calc(var(--app-base-px) * 2);\n  }\n}\n\n// radio-button-group\n.app-radio-button-group {\n  background: #ffffff;\n  border: 1px solid var(--el-border-color);\n  border-radius: var(--el-border-radius-base);\n  .el-radio-button {\n    padding: 3px;\n  }\n  .el-radio-button__inner {\n    border: none !important;\n    border-radius: var(--el-border-radius-base) !important;\n    padding: 5px 8px;\n    font-weight: 400;\n    color: var(--el-text-color-primary) !important;\n    outline: none !important;\n  }\n  .el-radio-button__original-radio:checked + .el-radio-button__inner {\n    color: var(--el-color-primary) !important;\n    background: var(--el-color-primary-light-9) !important;\n    border: none !important;\n    box-shadow: none !important;\n    font-weight: 500;\n  }\n}\n\n/*\n  表格第一行插入自定义行\n*/\n.table-quick-append {\n  background: #ffffff;\n  .el-table__append-wrapper {\n    position: absolute;\n    top: 0;\n    border-bottom: var(--el-table-border);\n    width: 100%;\n    height: 49px;\n    box-sizing: border-box;\n    align-items: center;\n    display: flex;\n    padding: 0 12px;\n    background: #ffffff;\n    cursor: pointer;\n    z-index: 2;\n    &:hover {\n      background: var(--el-color-primary-light-9);\n      z-index: 1;\n    }\n  }\n  .el-table__body {\n    margin-top: 49px;\n  }\n}\n\n.create-dropdown {\n  width: 280px;\n  .el-dropdown-menu__item {\n    padding: 8px 12px;\n    &:hover {\n      background: rgba(var(--el-text-color-primary-rgb), 0.1);\n    }\n  }\n}\n\n.custom-slider {\n  .el-input-number.is-without-controls .el-input__wrapper {\n    padding: 0 !important;\n  }\n}\n\n// 分段 dialog\n.paragraph-dialog {\n  padding: 0 !important;\n  .el-scrollbar {\n    height: auto !important;\n  }\n  .el-dialog__header {\n    padding: 16px 24px;\n    .el-dialog__headerbtn {\n      top: 8px;\n    }\n  }\n  .el-dialog__body {\n    border-top: 1px solid var(--el-border-color);\n  }\n  .el-dialog__footer {\n    padding: 16px 24px;\n    border-top: 1px solid var(--el-border-color);\n  }\n\n  .title {\n    color: var(--app-text-color);\n  }\n}\n\n.import-button {\n  .el-upload {\n    display: block !important;\n  }\n}\n\n// 下载中\n.dotting {\n  display: inline-block;\n  width: 10px;\n  min-height: 2px;\n  padding-right: 2px;\n  margin-left: 2px;\n  padding-left: 2px;\n  border-left: 2px solid currentColor;\n  border-right: 2px solid currentColor;\n  background-color: currentColor;\n  background-clip: content-box;\n  box-sizing: border-box;\n  -webkit-animation: dot 0.8s infinite step-start both;\n  animation: dot 0.8s infinite step-start both;\n  &:before {\n    content: '...';\n  }\n  &::before {\n    content: '';\n  }\n}\n\n@-webkit-keyframes dot {\n  25% {\n    border-color: transparent;\n    background-color: transparent;\n  }\n  50% {\n    border-right-color: transparent;\n    background-color: transparent;\n  }\n  75% {\n    border-right-color: transparent;\n  }\n}\n@keyframes dot {\n  25% {\n    border-color: transparent;\n    background-color: transparent;\n  }\n  50% {\n    border-right-color: transparent;\n    background-color: transparent;\n  }\n  75% {\n    border-right-color: transparent;\n  }\n}\n\n.dot-success {\n  height: 8px;\n  width: 8px;\n  background-color: var(--el-color-success);\n  border-radius: 50%;\n}\n\n// checkbox 一行一个样式\n.checkbox-group-block {\n  .el-checkbox {\n    display: block;\n  }\n}\n\n.card-checkbox {\n  input.checkbox[type='checkbox'] {\n    -webkit-appearance: none;\n    -moz-appearance: none;\n    appearance: none;\n    height: 14px;\n    width: 14px;\n    position: relative;\n\n    &::after {\n      position: absolute;\n      top: 0;\n      background-color: white;\n      color: #000;\n      height: 13px;\n      width: 13px;\n      visibility: visible;\n      text-align: center;\n      box-sizing: border-box;\n      border: var(--el-border);\n      border-radius: var(--el-border-radius-small);\n      box-sizing: content-box;\n      content: '';\n    }\n\n    &:checked::after {\n      content: '✓';\n      color: #ffffff;\n      border-color: var(--el-color-primary);\n      background: var(--el-color-primary);\n    }\n  }\n}\n\n// tooltip\n.auto-tooltip-popper {\n  max-width: 500px;\n}\n\n// 带滚动条dialog\n.scrollbar-dialog {\n  padding: 16px !important;\n  .el-dialog__header {\n    padding: 4px 16px 12px 12px;\n  }\n}\n// 拖拽排序\n.drag-card.no-drag {\n  .handle {\n    .handle-img {\n      display: none;\n    }\n  }\n}\n.drag-card:not(.no-drag) {\n  .handle {\n    .handle-img {\n      display: none;\n    }\n    &:hover {\n      .handle-img {\n        display: block;\n      }\n    }\n  }\n}\n\n// 分段预览的tab样式\n.paragraph-tabs {\n  .el-tabs__item {\n    background: rgba(var(--el-text-color-primary-rgb), 0.1);\n    margin: 4px;\n    border-radius: 4px;\n    padding: 5px 10px 5px 8px !important;\n    height: auto;\n    &:nth-child(2) {\n      margin-left: 0;\n    }\n    &:last-child {\n      margin-right: 0;\n    }\n    &.is-active {\n      border: 1px solid var(--el-color-primary);\n      background: var(--el-color-primary-light-9);\n      color: var(--el-text-color-primary);\n    }\n  }\n  .el-tabs__nav-wrap::after {\n    display: none;\n  }\n  .el-tabs__active-bar {\n    display: none;\n  }\n}\n\n.button-new-tag {\n  color: var(--app-input-color-placeholder);\n  border: 1px dashed var(--el-button-border-color);\n  padding: 5px 6px !important;\n  font-size: 14px !important;\n  background: none;\n  &:hover {\n    background: var(--app-layout-bg-color);\n    color: var(--app-input-color-placeholder);\n    border: 1px dashed var(--el-button-border-color);\n  }\n}\n\n// card-box 在popover中的样式\n.popover-card-box {\n  .card-footer {\n    padding: 0 !important;\n  }\n  .status-tag {\n    top: 0 !important;\n    right: 0 !important;\n  }\n}\n\n// card upload\n.upload__decoration {\n  font-size: 12px;\n  line-height: 20px;\n  color: var(--el-text-color-secondary);\n}\n.el-upload__text {\n  .hover:hover {\n    color: var(--el-color-primary-light-5);\n  }\n}\n"
  },
  {
    "path": "ui/src/styles/element-plus.scss",
    "content": ":root {\n  --el-color-primary: #3370ff;\n  --el-color-success: #34c724;\n  --el-text-color-primary: #1f2329;\n  --el-text-color-primary-rgb: 31, 35, 41;\n  --el-border-radius-base: 6px;\n  --el-menu-item-height: 45px;\n  --el-border-color: rgba(var(--el-text-color-primary-rgb), 0.15);\n}\n\n.el-avatar {\n  --el-avatar-bg-color: var(--el-color-primary);\n  --el-avatar-size-small: 33px;\n  --el-avatar-border-radius: 8px;\n  cursor: pointer;\n  flex-shrink: 0;\n}\n.el-icon {\n  flex-shrink: 0;\n}\n\n// card\n.el-card {\n  --el-card-padding: calc(var(--app-base-px) * 2);\n  --el-card-border-radius: 6px;\n  box-shadow: 0px 2px 4px 0px rgba(var(--el-text-color-primary-rgb), 0.12) !important;\n  overflow: visible;\n  &.is-never-shadow {\n    border: 1px solid var(--el-card-border-color);\n    box-shadow: none !important;\n  }\n  .el-card__body {\n    overflow: inherit;\n  }\n  &.is-hover-shadow:hover {\n    box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08) !important;\n  }\n}\n\n// tree\n.el-tree {\n  background: none;\n  color: var(--el-text-color-primary);\n  font-weight: 400;\n}\n.el-tree-node__content {\n  border-radius: var(--el-border-radius-base);\n  padding: 7px 0;\n  &:hover {\n    background: rgba(var(--el-text-color-primary-rgb), 0.1);\n  }\n}\n.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {\n  background: var(--el-color-primary-light-9);\n  color: var(--el-color-primary);\n  font-weight: 500;\n}\n.el-tree-node__expand-icon {\n  color: var(--app-text-color-secondary);\n  font-size: 16px;\n}\n\n// button\n.el-button {\n  --el-button-font-weight: 400;\n  --el-button-text-color: var(--el-text-color-primary);\n  padding: 5px 12px;\n  &.is-text {\n    padding: 4px !important;\n    font-size: 16px;\n    max-height: 24px;\n    &:not(.is-disabled):hover {\n      background: rgba(var(--el-text-color-primary-rgb), 0.1);\n    }\n  }\n  &:focus {\n    background-color: none;\n    border-color: none;\n  }\n  &:focus-visible {\n    outline: none !important;\n  }\n  &.is-link:focus {\n    background: none;\n    border: none;\n  }\n  &.is-link:not(.is-disabled):hover {\n    color: var(--el-color-primary);\n  }\n}\n.el-button--text {\n  border: none !important;\n  &:focus {\n    border: none !important;\n  }\n}\n.el-button--large {\n  font-size: 16px;\n}\n.el-button--primary {\n  --el-button-text-color: var(--el-color-white);\n}\n// dropdown\n.el-dropdown {\n  color: var(--el-text-color-primary);\n}\n.el-dropdown-menu__item {\n  color: var(--el-text-color-primary);\n  font-weight: 400;\n  padding: 5px 11px;\n  min-width: 80px;\n  i {\n    margin-right: 8px;\n  }\n  &:not(.is-disabled):focus,\n  &:not(.is-active):focus {\n    background-color: rgba(var(--el-text-color-primary-rgb), 0.1);\n    color: var(--el-text-color-primary);\n  }\n  &.is-active,\n  &.is-active:hover {\n    color: var(--el-menu-active-color);\n    background: var(--el-color-primary-light-9);\n  }\n  &.active {\n    color: var(--el-color-primary);\n  }\n}\n\n// message\n.el-message {\n  --el-message-close-icon-color: var(--app-text-color-secondary);\n}\n.el-message__content {\n  word-break: break-all;\n}\n.el-message-box {\n  --el-messagebox-font-size: 16px;\n  --el-messagebox-width: 475px;\n  padding: 24px;\n  .el-message-box__header {\n    padding: 0;\n  }\n  .el-message-box__title {\n    word-break: break-all;\n    width: 95%;\n  }\n}\n\n.el-message-box__content {\n  padding: 24px 0;\n  color: var(--app-text-color);\n  font-weight: 400;\n}\n.el-message-box__btns {\n  padding: 0;\n  button {\n    min-width: 80px;\n    &:nth-child(2) {\n      margin-left: 12px;\n    }\n  }\n  button.danger {\n    background: var(--el-color-danger);\n    border: var(--el-color-danger);\n    color: #ffffff;\n  }\n}\n.el-message-box__headerbtn {\n  right: 10px;\n  top: 16px;\n  .el-message-box__close {\n    font-size: 20px;\n  }\n}\n\n// drawer\n.el-drawer {\n  .el-drawer__header {\n    padding: 16px 24px;\n    margin: 0;\n    border-bottom: 1px solid var(--el-border-color);\n    color: var(--app-text-color);\n  }\n  .el-drawer__body {\n    padding: 16px 24px;\n  }\n  .el-drawer__footer {\n    border-top: 1px solid var(--el-border-color);\n    padding: 16px 24px;\n  }\n}\n\n// popper\n.el-popper {\n  --el-popper-border-radius: 6px;\n}\n\n// table\n.el-table {\n  --el-table-header-bg-color: var(--app-layout-bg-color);\n  --el-table-text-color: var(--app-text-color);\n  --el-table-border-color: #dee0e3;\n  --el-table-row-hover-bg-color: #eff0f1;\n  font-weight: 400;\n  thead {\n    color: var(--app-text-color-secondary);\n    th {\n      font-weight: 500;\n    }\n  }\n\n  th.el-table__cell {\n    border-top: var(--el-table-border);\n  }\n  .el-table__cell {\n    padding: 12px 0;\n  }\n  .el-checkbox {\n    height: 23px;\n  }\n  tr.highlight {\n    background: var(--el-table-current-row-bg-color);\n  }\n}\n\n// dialog\n.el-dialog {\n  --el-dialog-padding-primary: 24px;\n  --el-dialog-border-radius: 8px;\n  --el-dialog-title-font-size: 16px;\n  .el-dialog__body {\n    color: var(--el-text-color-primary);\n  }\n}\n.el-dialog__headerbtn {\n  top: 12px;\n  right: 8px;\n  .el-dialog__close {\n    font-size: 20px;\n  }\n}\n\n@media only screen and (max-width: 768px) {\n  .el-dialog {\n    width: 90% !important;\n  }\n}\n\n// el-form\n\n.el-form {\n  --el-form-inline-content-width: 100%;\n}\n\n.el-form-item {\n  margin-bottom: 16px;\n  .el-form-item {\n    margin-bottom: 16px;\n    &:last-child {\n      margin-bottom: 0px;\n    }\n  }\n}\n.el-form-item__label {\n  display: block;\n  font-weight: 400;\n  width: 100% !important;\n  color: var(--el-text-color-primary);\n}\n\n.el-form-item__content {\n  font-weight: 400 !important;\n  color: var(--el-text-color-primary);\n}\n\n.el-form-item__error {\n  position: unset;\n  font-size: 14px;\n}\n\n.el-form--label-top .el-form-item .el-form-item__label {\n  padding-right: 0;\n}\n\n.el-divider__text {\n  background: var(--app-layout-bg-color);\n}\n\n// el-cascader\n.el-cascader-node {\n  padding-left: 2px;\n}\n.el-cascader-node__prefix {\n  right: 10px;\n  left: auto;\n}\n\n.el-anchor {\n  --el-anchor-font-size: 14px;\n}\n\n.el-breadcrumb__separator.el-icon {\n  font-size: 12px;\n}\n\n// el-tag\n.el-tag {\n  --el-tag-font-size: 14px;\n  padding: 0 6px;\n  font-weight: 400;\n}\n.el-tag--small {\n  --el-tag-font-size: 12px;\n}\n.el-tag--plain.el-tag--info {\n  color: var(--app-text-color-secondary);\n  font-weight: 500;\n  border-color: rgba(var(--el-text-color-primary-rgb), 0.1);\n}\n.el-tag.never {\n  background: none;\n}\n\n.el-check-tag {\n  font-weight: 400;\n  padding: 7px 8px;\n  color: var(--el-text-color-primary);\n  background-color: rgba(var(--el-text-color-primary-rgb), 0.1);\n}\n.el-check-tag.el-check-tag--primary.is-checked {\n  background: var(--el-color-primary-light-8);\n  color: var(--el-color-primary);\n}\n\n// el-select\n.el-select__selected-item {\n  color: var(--el-text-color-primary);\n  font-weight: 400;\n  .el-tag {\n    color: var(--el-text-color-primary);\n    background-color: rgba(var(--el-text-color-primary-rgb), 0.1);\n    height: 24px;\n    font-size: 14px;\n    font-weight: 400;\n  }\n}\n.el-select__caret {\n  color: var(--app-text-color-secondary);\n}\n.el-select__wrapper.is-disabled .el-select__caret {\n  color: var(--app-text-color-disable);\n}\n\n// el-textarea\n.el-textarea {\n  --el-input-text-color: var(--el-text-color-primary);\n}\n\n// el-input\n.el-input {\n  --el-input-text-color: var(--el-text-color-primary);\n}\n.el-input .el-input__password {\n  color: var(--app-text-color-secondary) !important;\n}\n\n.el-input-group__prepend div.el-select .el-select__wrapper {\n  background: #ffffff;\n  &:hover {\n    background: #ffffff;\n  }\n  .el-select__placeholder {\n    color: var(--el-text-color-regular);\n  }\n}\n\n// el-pagination\n.el-pagination .el-select {\n  width: 105px;\n}\n.el-pagination__total {\n  color: var(--el-text-color-primary);\n}\n\n// el-checkbox\n.el-checkbox {\n  --el-checkbox-font-weight: 400;\n  --el-checkbox-text-color: var(--el-text-color-primary);\n}\n.el-checkbox__input.is-checked + .el-checkbox__label {\n  color: var(--el-text-color-primary);\n}\n\n// el-radio\n.el-radio {\n  --el-radio-font-weight: 400;\n  color: var(--el-text-color-primary);\n}\n.el-radio__input.is-checked + .el-radio__label {\n  color: var(--el-text-color-primary);\n}\n\n// el-tabs\n.el-tabs__header {\n  margin: 0 0 12px;\n}\n.el-tabs__item {\n  padding: 0 14px;\n}\n\n.el-tabs__nav-wrap:after {\n  height: 1px;\n}\n.el-tabs__active-bar {\n  height: 3px;\n}\n\n// el-upload\n.el-upload {\n  --el-upload-dragger-padding-horizontal: 32px;\n}\n\n// el-switch\n.el-switch__core {\n  border: 1px solid var(--app-border-color-dark);\n  background: var(--app-border-color-dark);\n}\n"
  },
  {
    "path": "ui/src/styles/index.scss",
    "content": "@use 'element-plus/dist/index.css';\n@use './element-plus.scss';\n@use './variables.scss';\n@use './app.scss';\n@use './component.scss';\n@use './md-editor.scss';\n@import 'nprogress/nprogress.css';\n@import 'md-editor-v3/lib/style.css';\n\n"
  },
  {
    "path": "ui/src/styles/md-editor.scss",
    "content": ".md-editor {\n  font-weight: 400;\n  --md-color: var(--el-text-color-primary);\n  .cm-content {\n    font-weight: 400;\n    color: var(--el-text-color-primary);\n  }\n}\n\n.md-editor-preview {\n  padding: 0;\n  margin: 0;\n  font-size: inherit;\n  word-break: break-word;\n  table {\n    display: block;\n  }\n  p {\n    padding: 0 !important;\n  }\n  .md-editor-admonition {\n    margin: 0;\n    padding: 0;\n  }\n  img {\n    border: 0 !important;\n    max-width: 360px !important;\n  }\n  ul {\n    list-style: circle;\n  }\n}\n\n@media only screen and (max-width: 768px) {\n  .md-editor-preview {\n    img {\n      max-width: 100% !important;\n    }\n  }\n}\n\n.md-editor-preview-wrapper {\n  padding: 0;\n}\n\n.md-editor-footer {\n  height: auto !important;\n}\n\n.ͼ1 .cm-placeholder {\n  color: var(--app-input-color-placeholder);\n  font-size: 14px;\n  font-weight: 400;\n}\n\n.md-editor-preview .md-editor-code .md-editor-code-head {\n  z-index: inherit !important;\n}\n"
  },
  {
    "path": "ui/src/styles/variables.scss",
    "content": ":root {\n  --app-base-px: 8px;\n  --app-layout-bg-color: #f5f6f7;\n  --app-text-color-secondary: #646a73;\n  --app-text-color-disable: #bbbfc4;\n  --app-input-color-placeholder: #8f959e;\n  --app-view-padding: 24px;\n  --app-view-bg-color: #ffffff;\n  --app-border-color-dark: #bbbfc4;\n  --app-border-radius-small: 6px;\n  --app-border-radius-base: 8px;\n  --app-border-radius-large: 16px;\n  --md-bk-hover-color: var(--el-border-color-hover);\n  /** header 组件 */\n  --app-header-height: 56px;\n  --app-header-padding: 0 20px;\n\n  --app-header-bg-color: linear-gradient(90deg, #ebf1ff 24.34%, #e5fbf8 56.18%, #f2ebfe 90.18%);\n  --app-logo-color: linear-gradient(180deg, #3370ff 0%, #7f3bf5 100%);\n  --app-avatar-gradient-color: linear-gradient(270deg, #9258f7 0%, #3370ff 100%);\n\n  /* 计算高度 */\n  --app-main-height: calc(100vh - var(--app-header-height) - var(--app-view-padding) * 2 - 40px);\n\n  /** sidebar 组件 */\n  --sidebar-width: 240px;\n  /** tag */\n  --tag-default-bg: rgba(51, 112, 255, 0.2);\n  --tag-default-color: #2b5fd9;\n  --tag-success-bg: rgba(52, 199, 36, 0.2);\n  --tag-success-color: #2ca91f;\n  --tag-warning-bg: rgba(255, 136, 0, 0.2);\n  --tag-warning-color: #d97400;\n  --tag-danger-bg: rgba(245, 74, 69, 0.2);\n\n  /** card */\n  --card-width: 330px;\n  --card-min-height: 166px;\n  --card-min-width: 220px;\n\n  /** ai-chat */\n  --dialog-bg-gradient-color:\n    linear-gradient(188deg, rgba(235, 241, 255, 0.2) 39.6%, rgba(231, 249, 255, 0.2) 94.3%), #eff0f1;\n\n  /** 资源授权 */\n  --setting-left-width: 280px;\n}\n"
  },
  {
    "path": "ui/src/utils/application.ts",
    "content": "export function isWorkFlow(type: string | undefined) {\n  return type === 'WORK_FLOW'\n}\n\nexport function mapToUrlParams(map: any[]) {\n  const params = new URLSearchParams()\n\n  map.forEach((item: any) => {\n    params.append(encodeURIComponent(item.name), encodeURIComponent(item.value))\n  })\n\n  return params.toString() // 返回 URL 查询字符串\n}\n"
  },
  {
    "path": "ui/src/utils/array.ts",
    "content": "/**\n * 拆分数组 每n个拆分为一个数组\n * @param sourceDataList 资源数据\n * @param splitNum       每多少个拆分为一个数组\n * @returns              拆分后数组\n */\nexport function splitArray<T>(sourceDataList: Array<T>, splitNum: number) {\n  const count =\n    sourceDataList.length % splitNum == 0\n      ? sourceDataList.length / splitNum\n      : sourceDataList.length / splitNum + 1\n  const arrayList: Array<Array<T>> = []\n  for (let i = 0; i < count; i++) {\n    let index = i * splitNum\n    const list: Array<T> = []\n    let j = 0\n    while (j < splitNum && index < sourceDataList.length) {\n      list.push(sourceDataList[index++])\n      j++\n    }\n    arrayList.push(list)\n  }\n  return arrayList\n}\n/*\n树形结构转平\n*/\nexport function TreeToFlatten(treeData: any[]) {\n  return treeData.reduce((acc, node) => {\n    const { children, ...rest } = node\n    return [...acc, rest, ...(children ? TreeToFlatten(children) : [])]\n  }, [])\n}\n\n/*\n  从指定数组中过滤出对应的对象\n*/\nexport function relatedObject(list: any, val: any, attr: string) {\n  const filterData: any = list.find((item: any) => item[attr] === val)\n  return filterData || null\n}\n\n// 排序\nexport function arraySort(list: Array<any>, property: any, desc?: boolean) {\n  return list.sort((a: any, b: any) => {\n    return desc ? b[property] - a[property] : a[property] - b[property]\n  })\n}\n\n// 判断对象里所有属性全部为空\nexport function isAllPropertiesEmpty(obj: object) {\n  return Object.values(obj).every(\n    (value) =>\n      value === null || typeof value === 'undefined' || (typeof value === 'string' && !value),\n  )\n}\n\n// 数组对象中某一属性值的集合\nexport function getAttrsArray(array: Array<any>, attr: string) {\n  return array.map((item) => {\n    return item[attr]\n  })\n}\n\n// 求和\nexport function getSum(array: Array<any>) {\n  return array.reduce((total, item) => total + item, 0)\n}\n\n// 对象数组去重\nexport function uniqueArray(array: Array<any>, key: string) {\n  const map = new Map()\n  return array.filter((item) => {\n    return !map.has(item[key]) && map.set(item[key], 1)\n  })\n}\n"
  },
  {
    "path": "ui/src/utils/bus.ts",
    "content": "import mitt from \"mitt\";\nconst bus: any = {};\nconst emitter = mitt();\nbus.on = emitter.on;\nbus.off = emitter.off;\nbus.emit = emitter.emit;\n\nexport default bus;\n"
  },
  {
    "path": "ui/src/utils/clipboard.ts",
    "content": "import Clipboard from 'vue-clipboard3'\nimport { MsgSuccess, MsgError } from '@/utils/message'\nimport { t } from '@/locales'\n/*\n  复制粘贴\n*/\nexport async function copyClick(info: string) {\n  const { toClipboard } = Clipboard()\n  try {\n    await toClipboard(info)\n    MsgSuccess(t('common.copySuccess'))\n  } catch (e) {\n    console.error(e)\n    MsgError(t('common.copyError'))\n  }\n}\n"
  },
  {
    "path": "ui/src/utils/common.ts",
    "content": "import {nanoid} from 'nanoid'\nimport {t} from '@/locales'\n\n/**\n * 数字处理\n */\nexport function toThousands(num: any) {\n  return num?.toString().replace(/\\d+/, function (n: any) {\n    return n.replace(/(\\d)(?=(?:\\d{3})+$)/g, '$1,')\n  })\n}\n\nexport function numberFormat(num: number) {\n  return num < 1000 ? toThousands(num) : toThousands((num / 1000).toFixed(1)) + 'k'\n}\n\nexport function filesize(size: number) {\n  if (!size) return ''\n  /* byte */\n  const num = 1024.0\n\n  if (size < num) return size + 'B'\n  if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + 'K' //kb\n  if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + 'M' //M\n  if (size < Math.pow(num, 4)) return (size / Math.pow(num, 3)).toFixed(2) + 'G' //G\n  return (size / Math.pow(num, 4)).toFixed(2) + 'T' //T\n}\n\n// 头像\nexport const defaultIcon = '/${window.MaxKB.prefix}/favicon.ico'\n\nexport function isAppIcon(url: string | undefined) {\n  return url === defaultIcon ? '' : url\n}\n\nexport function isFunction(fn: any) {\n  return typeof fn === 'function'\n}\n\n/*\n  随机id\n*/\nexport const randomId = function () {\n  return nanoid()\n}\n\n/*\n  获取文件后缀\n*/\nexport function fileType(name: string) {\n  const suffix = name.split('.')\n  return suffix[suffix.length - 1]\n}\n\n/*\n  获得文件对应图片\n*/\nconst typeList: any = {\n  txt: ['txt', 'pdf', 'docx', 'md', 'html', 'zip', 'xlsx', 'xls', 'csv'],\n  table: ['xlsx', 'xls', 'csv'],\n  QA: ['xlsx', 'csv', 'xls', 'zip'],\n}\n\nexport function getImgUrl(name: string) {\n  const list = Object.values(typeList).flat()\n\n  const type = list.includes(fileType(name).toLowerCase())\n    ? fileType(name).toLowerCase()\n    : 'unknown'\n  return new URL(`../assets/fileType/${type}-icon.svg`, import.meta.url).href\n}\n\n// 是否是白名单后缀\nexport function isRightType(name: string, type: string) {\n  return typeList[type].includes(fileType(name).toLowerCase())\n}\n\n// 下载\nexport function downloadByURL(url: string, name: string) {\n  const a = document.createElement('a')\n  a.setAttribute('href', url)\n  a.setAttribute('target', '_blank')\n  a.setAttribute('download', name)\n  document.body.appendChild(a)\n  a.click()\n  document.body.removeChild(a)\n}\n\n// 替换固定数据国际化\nconst i18n_default_name_map:any = {\n  \"系统管理员\": 'layout.about.inner_admin',\n  \"工作空间管理员\": 'layout.about.inner_wsm',\n  \"普通用户\": 'layout.about.inner_user',\n  \"根目录\": 'layout.about.root',\n  \"默认工作空间\": 'layout.about.default_workspace',\n  \"默认用户组\": 'layout.about.default_user_group',\n}\n\nexport function i18n_name(name: string) {\n  const key = i18n_default_name_map[name]\n  return key ? t(key) : name\n}\n\n\n// 截取文件名\nexport function cutFilename(filename: string, num: number) {\n  const lastIndex = filename.lastIndexOf('.')\n  const suffix = lastIndex === -1 ? '' : filename.substring(lastIndex + 1)\n  return filename.substring(0, num - suffix.length - 1) + '.' + suffix\n}\n\ninterface LoadScriptOptions {\n  jsId?: string // 自定义脚本 ID\n  forceReload?: boolean // 是否强制重新加载（默认 false）\n}\n\nexport const loadScript = (url: string, options: LoadScriptOptions = {}): Promise<void> => {\n  const { jsId, forceReload = false } = options;\n  const scriptId = jsId || `script-${btoa(url).slice(0, 12)}`;\n\n  const cleanupScript = (script: HTMLScriptElement) => {\n    if (script && script.parentElement) {\n      script.parentElement.removeChild(script);\n    }\n  };\n\n  return new Promise((resolve, reject) => {\n    if (typeof document === 'undefined') {\n      reject(new Error('Cannot load script in non-browser environment'));\n      return;\n    }\n\n    const existingScript = document.getElementById(scriptId) as HTMLScriptElement | null;\n\n    if (existingScript && !forceReload) {\n      if (existingScript.src === url) {\n        console.log(`[loadScript] Reuse existing script: ${url}`);\n        resolve();\n        return;\n      }\n      existingScript.remove();\n    }\n\n    const script = document.createElement('script');\n    script.id = scriptId;\n    script.src = url;\n    script.async = true;\n\n    script.onload = () => {\n      console.log(`[loadScript] Script loaded: ${url}`);\n      resolve();\n    };\n\n    script.onerror = () => {\n      console.error(`[loadScript] Failed to load: ${url}`);\n      cleanupScript(script);\n      reject(new Error(`Failed to load script: ${url}`));\n    };\n\n    document.head.appendChild(script);\n  });\n};\n\n// 清理脚本（可选）\nconst cleanupScript = (script: HTMLScriptElement) => {\n  script.onload = null\n  script.onerror = null\n  script.parentElement?.removeChild(script)\n}\n\nexport function getNormalizedUrl(url: string) {\n  if (url && !url.endsWith('/') && !/\\.[^/]+$/.test(url)) {\n    return url + '/'\n  }\n  return url\n}\n\nexport function getFileUrl(fileId?: string) {\n  if (fileId) {\n    return `${window.MaxKB.prefix}/oss/file/${fileId}`\n  }\n  return ''\n}\n\nexport const resetUrl = (url: string, defaultUrl?: string) => {\n  if (url && url.startsWith('./')) {\n    return `${window.MaxKB.prefix}/${url.substring(2)}`\n  }\n  return url ? url : defaultUrl ? defaultUrl : ''\n}\n"
  },
  {
    "path": "ui/src/utils/dynamics-api/permission-api.ts",
    "content": "import {PermissionConst, EditionConst, RoleConst} from '@/utils/permission/data'\nimport {hasPermission} from '@/utils/permission/index'\nimport roleSystemApi from '@/api/system/role'\nimport roleWorkspaceApi from '@/api/workspace/role'\nimport systemWorkspaceApi from '@/api/system/workspace'\nimport workspaceApi from '@/api/workspace/workspace'\nimport systemChatUserApi from '@/api/system/chat-user'\nimport workspaceChatUserApi from '@/api/workspace/chat-user'\nimport systemUserGroupApi from '@/api/system/user-group'\nimport workspaceUserGroupApi from '@/api/workspace/user-group'\nimport useStore from \"@/stores\";\n\n// 系统管理员 API\nconst systemApiMap = {\n  role: roleSystemApi,\n  workspace: systemWorkspaceApi,\n  chatUser: systemChatUserApi,\n  userGroup: systemUserGroupApi,\n} as any\n\n// 企业版工作空间管理员 API\nconst workspaceApiMap = {\n  role: roleWorkspaceApi,\n  workspace: workspaceApi,\n  chatUser: workspaceChatUserApi,\n  userGroup: workspaceUserGroupApi,\n} as any\n\n/** 动态导入 API 模块的函数\n *  loadPermissionApi('role')\n */\nconst {user} = useStore()\nconst systemPermissionMap = {\n  workspace: [PermissionConst.WORKSPACE_READ, RoleConst.ADMIN],\n  role: [PermissionConst.ROLE_READ, RoleConst.ADMIN],\n  chatUser: [PermissionConst.CHAT_USER_READ, RoleConst.ADMIN],\n  userGroup: [PermissionConst.USER_GROUP_READ, RoleConst.ADMIN],\n}\nconst workspacePermissionMap = {\n  workspace: [PermissionConst.WORKSPACE_WORKSPACE_READ, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n  role: [PermissionConst.WORKSPACE_ROLE_READ, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n  chatUser: [PermissionConst.WORKSPACE_CHAT_USER_READ, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n  userGroup: [PermissionConst.WORKSPACE_USER_GROUP_READ, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n}\n\nexport function loadPermissionApi(type: string) {\n  if (hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')) {\n    user.getHasPermissionWorkspaceManage()\n    if (hasPermission(systemPermissionMap[type as keyof typeof systemPermissionMap], 'OR')) {\n      // 加载系统管理员 API\n      return systemApiMap[type]\n    } else if (\n      hasPermission(workspacePermissionMap[type as keyof typeof workspacePermissionMap], 'OR')\n    ) {\n      // 加载企业版工作空间管理员 API\n      return workspaceApiMap[type]\n    }\n  }\n}\n"
  },
  {
    "path": "ui/src/utils/dynamics-api/shared-api.ts",
    "content": "import knowledgeWorkspaceApi from '@/api/knowledge/knowledge'\nimport documentWorkspaceApi from '@/api/knowledge/document'\nimport paragraphWorkspaceApi from '@/api/knowledge/paragraph'\nimport problemWorkspaceApi from '@/api/knowledge/problem'\nimport resourceMappingApi from '@/api/workspace/resource-mapping'\nimport modelWorkspaceApi from '@/api/model/model'\nimport toolWorkspaceApi from '@/api/tool/tool'\nimport chatUserWorkspaceApi from '@/api/chat-user/chat-user'\nimport applicationWorkspaceApi from '@/api/application/application'\nimport applicationKeyWorkspaceApi from '@/api/application/application-key'\nimport workflowVersionWorkspaceApi from '@/api/application/workflow-version'\nimport chatLogWorkspaceApi from '@/api/application/chat-log'\nimport resourceAuthorizationWorkspaceApi from '@/api/workspace/resource-authorization'\nimport triggerApi from '@/api/trigger/trigger'\nimport sharedWorkspaceApi from '@/api/shared-workspace'\nimport toolSystemShareApi from '@/api/system-shared/tool'\nimport modelSystemShareApi from '@/api/system-shared/model'\nimport knowledgeSystemShareApi from '@/api/system-shared/knowledge'\nimport documentSystemShareApi from '@/api/system-shared/document'\nimport paragraphSystemShareApi from '@/api/system-shared/paragraph'\nimport problemSystemShareApi from '@/api/system-shared/problem'\nimport chatUserSystemShareApi from '@/api/system-shared/chat-user'\nimport workspaceApi from '@/api/workspace/workspace'\nimport folderWorkspaceApi from '@/api/workspace/folder'\nimport systemUserApi from '@/api/user/user'\nimport ToolResourceApi from '@/api/system-resource-management/tool'\nimport knowledgeResourceApi from '@/api/system-resource-management/knowledge'\nimport documentResourceApi from '@/api/system-resource-management/document'\nimport paragraphResourceApi from '@/api/system-resource-management/paragraph'\nimport problemResourceApi from '@/api/system-resource-management/problem'\nimport modelResourceApi from '@/api/system-resource-management/model'\nimport chatUserResourceApi from '@/api/system-resource-management/chat-user'\nimport applicationResourceApi from '@/api/system-resource-management/application'\nimport applicationKeyResourceApi from '@/api/system-resource-management/application-key'\nimport workflowVersionResourceApi from '@/api/system-resource-management/workflow-version'\nimport chatLogResourceApi from '@/api/system-resource-management/chat-log'\nimport resourceAuthorizationResourceApi\n  from '@/api/system-resource-management/resource-authorization'\nimport folderResourceApi from '@/api/system-resource-management/folder'\nimport systemResourceMappingApi from '@/api/system-shared/resource-mapping'\nimport resourceManageMappingApi from '@/api/system-resource-management/resource-mapping'\nimport resourceTriggerApi from '@/api/system-resource-management/trigger'\n\n\n// 普通 API\nconst workspaceApiMap = {\n  knowledge: knowledgeWorkspaceApi,\n  model: modelWorkspaceApi,\n  tool: toolWorkspaceApi,\n  document: documentWorkspaceApi,\n  paragraph: paragraphWorkspaceApi,\n  problem: problemWorkspaceApi,\n  chatUser: chatUserWorkspaceApi,\n  workspace: workspaceApi,\n  application: applicationWorkspaceApi,\n  applicationKey: applicationKeyWorkspaceApi,\n  workflowVersion: workflowVersionWorkspaceApi,\n  chatLog: chatLogWorkspaceApi,\n  resourceAuthorization: resourceAuthorizationWorkspaceApi,\n  folder: folderWorkspaceApi,\n  resourceMapping: resourceMappingApi,\n  trigger: triggerApi,\n} as any\n\n// 系统分享 API\nconst systemShareApiMap = {\n  knowledge: knowledgeSystemShareApi,\n  model: modelSystemShareApi,\n  tool: toolSystemShareApi,\n  document: documentSystemShareApi,\n  paragraph: paragraphSystemShareApi,\n  problem: problemSystemShareApi,\n  chatUser: chatUserSystemShareApi,\n  workspace: systemUserApi, // 共享的应该查全部人吧\n  resourceMapping: systemResourceMappingApi,\n} as any\n\n// 资源管理 API\nconst systemManageApiMap = {\n  knowledge: knowledgeResourceApi,\n  document: documentResourceApi,\n  paragraph: paragraphResourceApi,\n  problem: problemResourceApi,\n  model: modelResourceApi,\n  tool: ToolResourceApi,\n  chatUser: chatUserResourceApi,\n  application: applicationResourceApi,\n  applicationKey: applicationKeyResourceApi,\n  workflowVersion: workflowVersionResourceApi,\n  chatLog: chatLogResourceApi,\n  resourceAuthorization: resourceAuthorizationResourceApi,\n  folder: folderResourceApi,\n  resourceMapping: resourceManageMappingApi,\n  trigger: resourceTriggerApi,\n} as any\n\nconst data = {\n  systemShare: systemShareApiMap,\n  workspace: workspaceApiMap,\n  systemManage: systemManageApiMap,\n  workspaceShare: workspaceApiMap,\n}\n\n/** 动态导入 API 模块的函数\n *  loadSharedApi('knowledge', true,'systemShare')\n */\nexport function loadSharedApi({\n                                type,\n                                isShared,\n                                systemType,\n                              }: {\n  type: string\n  isShared?: boolean | undefined\n  systemType?: 'systemShare' | 'workspace' | 'systemManage' | 'workspaceShare'\n}) {\n  if (isShared) {\n    // 共享 API\n    return sharedWorkspaceApi\n  } else {\n    return data[systemType || 'workspace'][type]\n  }\n}\n"
  },
  {
    "path": "ui/src/utils/folder.ts",
    "content": "/**\n * 位运算排序工具函数\n * 高16位-整数，低16位-小数\n */\n\nconst FRACTION_BITS = 16\nconst FRACTION_MASK = 0xffff\n\n// 编码32位整数\nfunction encode(integer: number, fraction = 0) {\n  return (integer << FRACTION_BITS) | (fraction & FRACTION_MASK)\n}\n\n// 计算两个位置中间值 \nfunction mid(pos1: number, pos2: number) {\n  const midPos = (pos1 + pos2) >> 1\n  if (midPos === pos1 || midPos === pos2) {\n    return null\n  }\n  return midPos\n}\n\n// 重平衡，相邻位差小于2时，无法插入\n// positions - { nodeId: position }\nfunction rebalance(positions: any) {\n  const sorted = Object.entries(positions).sort((a: any, b: any) => a[1] - b[1])\n\n  const rebalanced = {} as Record<string, number>\n  sorted.forEach(([nodeId], index) => {\n    rebalanced[nodeId] = encode(index + 1, 0)\n  })\n  return rebalanced\n}\n\n\nexport { encode, rebalance, mid }\n"
  },
  {
    "path": "ui/src/utils/message.ts",
    "content": "import { ElMessageBox, ElMessage } from 'element-plus'\nimport { t } from '@/locales'\n\nexport const MsgSuccess = (message: string) => {\n  ElMessage.success({\n    message: message,\n    type: 'success',\n    showClose: true,\n    duration: 3000\n  })\n}\n\nexport const MsgInfo = (message: string) => {\n  ElMessage.info({\n    message: message,\n    type: 'info',\n    showClose: true,\n    duration: 3000\n  })\n}\n\nexport const MsgWarning = (message: string) => {\n  ElMessage.warning({\n    message: message,\n    type: 'warning',\n    showClose: true,\n    duration: 3000\n  })\n}\n\nexport const MsgError = (message: string) => {\n  ElMessage.error({\n    message: message,\n    type: 'error',\n    showClose: true,\n    duration: 3000\n  })\n}\n\nexport const MsgAlert = (title: string, description: string, options?: any) => {\n  const defaultOptions: object = {\n    confirmButtonText: t('common.confirm'),\n    ...options\n  }\n  return ElMessageBox.alert(description, title, defaultOptions)\n}\n\n/**\n * 删除知识库\n * @param 参数 message: {title, description,type}\n */\n\nexport const MsgConfirm = (title: string, description: string, options?: any) => {\n  const defaultOptions: object = {\n    showCancelButton: true,\n    confirmButtonText: t('common.confirm'),\n    cancelButtonText: t('common.cancel'),\n    ...options\n  }\n  return ElMessageBox.confirm(description, title, defaultOptions)\n}\n"
  },
  {
    "path": "ui/src/utils/permission/data.ts",
    "content": "import {Permission, Role, Edition} from '@/utils/permission/type'\n// class Operate(Enum):\n//     \"\"\"\n//      一个权限组的操作权限\n//     \"\"\"\n//     READ = 'READ'\n//     EDIT = \"READ+EDIT\"\n//     CREATE = \"READ+CREATE\"\n//     DELETE = \"READ+DELETE\"\n//     \"\"\"\n//     使用权限\n//     \"\"\"\n//     USE = \"USE\"\n//     IMPORT = \"READ+IMPORT\"\n//     EXPORT = \"READ+EXPORT\"  # 导入导出\n//     SYNC = \"READ+SYNC\"  # 同步\n//     GENERATE = \"READ+GENERATE\"  # 生成\n//     ADD_MEMBER = \"READ+ADD_MEMBER\"  # 添加成员\n//     REMOVE_MEMBER = \"READ+REMOVE_MEMBER\"  # 添加成员\n//     VECTOR = \"READ+VECTOR\"  # 向量化\n//     MIGRATE = \"READ+MIGRATE\"  # 迁移\n//     RELATE = \"READ+RELATE\"  # 关联\n//     USER_GROUP = \"READ+USER_GROUP\"  # 用户组\n//     ANNOTATION = \"READ+ANNOTATION\"  # 标注\n//     CLEAR_POLICY = \"READ+CLEAR_POLICY\"\nconst PermissionConst = {\n\n  APPLICATION: new Permission('APPLICATION'),\n  KNOWLEDGE: new Permission('KNOWLEDGE'),\n  TOOL: new Permission('TOOL'),\n  MODEL: new Permission('MODEL'),\n\n  USER_READ: new Permission('USER_MANAGEMENT:READ'),\n  USER_CREATE: new Permission('USER_MANAGEMENT:READ+CREATE'),\n  USER_EDIT: new Permission('USER_MANAGEMENT:READ+EDIT'),\n  USER_DELETE: new Permission('USER_MANAGEMENT:READ+DELETE'),\n\n  WORKSPACE_USER_RESOURCE_PERMISSION_READ: new Permission(\n    'WORKSPACE_USER_RESOURCE_PERMISSION:READ',\n  ),\n  WORKSPACE_USER_RESOURCE_PERMISSION_EDIT: new Permission(\n    'WORKSPACE_USER_RESOURCE_PERMISSION:READ+EDIT',\n  ),\n\n  WORKSPACE_ROLE_READ: new Permission('WORKSPACE_ROLE:READ'),\n  WORKSPACE_ROLE_ADD_MEMBER: new Permission('WORKSPACE_ROLE:READ+ADD_MEMBER'),\n  WORKSPACE_ROLE_REMOVE_MEMBER: new Permission('WORKSPACE_ROLE:READ+REMOVE_MEMBER'),\n\n  WORKSPACE_READ: new Permission('WORKSPACE:READ'),\n  WORKSPACE_CREATE: new Permission('WORKSPACE:READ+CREATE'),\n  WORKSPACE_EDIT: new Permission('WORKSPACE:READ+EDIT'),\n  WORKSPACE_DELETE: new Permission('WORKSPACE:READ+DELETE'),\n  WORKSPACE_ADD_MEMBER: new Permission('WORKSPACE:READ+ADD_MEMBER'),\n  WORKSPACE_REMOVE_MEMBER: new Permission('WORKSPACE:READ+REMOVE_MEMBER'),\n\n  WORKSPACE_WORKSPACE_READ: new Permission('WORKSPACE_WORKSPACE:READ'),\n  WORKSPACE_WORKSPACE_ADD_MEMBER: new Permission('WORKSPACE_WORKSPACE:READ+ADD_MEMBER'),\n  WORKSPACE_WORKSPACE_REMOVE_MEMBER: new Permission('WORKSPACE_WORKSPACE:READ+REMOVE_MEMBER'),\n\n  WORKSPACE_CHAT_USER_READ: new Permission('WORKSPACE_CHAT_USER:READ'),\n  WORKSPACE_CHAT_USER_CREATE: new Permission('WORKSPACE_CHAT_USER:READ+CREATE'),\n  WORKSPACE_CHAT_USER_EDIT: new Permission('WORKSPACE_CHAT_USER:READ+EDIT'),\n  WORKSPACE_CHAT_USER_DELETE: new Permission('WORKSPACE_CHAT_USER:READ+DELETE'),\n  WORKSPACE_CHAT_USER_GROUP: new Permission('WORKSPACE_CHAT_USER:READ+USER_GROUP'),\n\n  WORKSPACE_USER_GROUP_READ: new Permission('WORKSPACE_USER_GROUP:READ'),\n  WORKSPACE_USER_GROUP_CREATE: new Permission('WORKSPACE_USER_GROUP:READ+CREATE'),\n  WORKSPACE_USER_GROUP_EDIT: new Permission('WORKSPACE_USER_GROUP:READ+EDIT'),\n  WORKSPACE_USER_GROUP_DELETE: new Permission('WORKSPACE_USER_GROUP:READ+DELETE'),\n  WORKSPACE_USER_GROUP_ADD_MEMBER: new Permission('WORKSPACE_USER_GROUP:READ+ADD_MEMBER'),\n  WORKSPACE_USER_GROUP_REMOVE_MEMBER: new Permission('WORKSPACE_USER_GROUP:READ+REMOVE_MEMBER'),\n\n  CHAT_USER_AUTH_READ: new Permission('CHAT_USER_AUTH:READ'),\n  CHAT_USER_AUTH_EDIT: new Permission('CHAT_USER_AUTH:READ+EDIT'),\n\n  CHAT_USER_READ: new Permission('CHAT_USER:READ'),\n  CHAT_USER_CREATE: new Permission('CHAT_USER:READ+CREATE'),\n  CHAT_USER_SYNC: new Permission('CHAT_USER:READ+SYNC'),\n  CHAT_USER_EDIT: new Permission('CHAT_USER:READ+EDIT'),\n  CHAT_USER_DELETE: new Permission('CHAT_USER:READ+DELETE'),\n  CHAT_USER_GROUP: new Permission('CHAT_USER:READ+USER_GROUP'),\n\n  USER_GROUP_READ: new Permission('USER_GROUP:READ'),\n  USER_GROUP_CREATE: new Permission('USER_GROUP:READ+CREATE'),\n  USER_GROUP_EDIT: new Permission('USER_GROUP:READ+EDIT'),\n  USER_GROUP_DELETE: new Permission('USER_GROUP:READ+DELETE'),\n  USER_GROUP_ADD_MEMBER: new Permission('USER_GROUP:READ+ADD_MEMBER'),\n  USER_GROUP_REMOVE_MEMBER: new Permission('USER_GROUP:READ+REMOVE_MEMBER'),\n\n  ROLE_READ: new Permission('ROLE:READ'),\n  ROLE_CREATE: new Permission('ROLE:READ+CREATE'),\n  ROLE_EDIT: new Permission('ROLE:READ+EDIT'),\n  ROLE_DELETE: new Permission('ROLE:READ+DELETE'),\n  ROLE_ADD_MEMBER: new Permission('ROLE:READ+ADD_MEMBER'),\n  ROLE_REMOVE_MEMBER: new Permission('ROLE:READ+REMOVE_MEMBER'),\n\n  APPLICATION_FOLDER_READ: new Permission('APPLICATION_FOLDER:READ'),\n  APPLICATION_FOLDER_CREATE: new Permission('APPLICATION_FOLDER:READ+CREATE'),\n  APPLICATION_FOLDER_EDIT: new Permission('APPLICATION_FOLDER:READ+EDIT'),\n  APPLICATION_FOLDER_DELETE: new Permission('APPLICATION_FOLDER:READ+DELETE'),\n  APPLICATION_FOLDER_AUTH: new Permission('APPLICATION_FOLDER:READ+AUTH'),\n\n  KNOWLEDGE_FOLDER_READ: new Permission('KNOWLEDGE_FOLDER:READ'),\n  KNOWLEDGE_FOLDER_CREATE: new Permission('KNOWLEDGE_FOLDER:READ+CREATE'),\n  KNOWLEDGE_FOLDER_EDIT: new Permission('KNOWLEDGE_FOLDER:READ+EDIT'),\n  KNOWLEDGE_FOLDER_DELETE: new Permission('KNOWLEDGE_FOLDER:READ+DELETE'),\n  KNOWLEDGE_FOLDER_AUTH: new Permission('KNOWLEDGE_FOLDER:READ+AUTH'),\n\n  TOOL_FOLDER_READ: new Permission('TOOL_FOLDER:READ'),\n  TOOL_FOLDER_CREATE: new Permission('TOOL_FOLDER:READ+CREATE'),\n  TOOL_FOLDER_EDIT: new Permission('TOOL_FOLDER:READ+EDIT'),\n  TOOL_FOLDER_DELETE: new Permission('TOOL_FOLDER:READ+DELETE'),\n  TOOL_FOLDER_AUTH: new Permission('TOOL_FOLDER:READ+AUTH'),\n\n  TRIGGER_READ: new Permission('TRIGGER:READ'),\n  TRIGGER_CREATE: new Permission('TRIGGER:READ+CREATE'),\n  TRIGGER_EDIT: new Permission('TRIGGER:READ+EDIT'),\n  TRIGGER_DELETE: new Permission('TRIGGER:READ+DELETE'),\n  TRIGGER_RECORD: new Permission('TRIGGER:READ+RECORD'),\n\n  KNOWLEDGE_READ: new Permission('KNOWLEDGE:READ'),\n  KNOWLEDGE_CREATE: new Permission('KNOWLEDGE:READ+CREATE'),\n  KNOWLEDGE_SYNC: new Permission('KNOWLEDGE:READ+SYNC'),\n  KNOWLEDGE_VECTOR: new Permission('KNOWLEDGE:READ+VECTOR'),\n  KNOWLEDGE_EDIT: new Permission('KNOWLEDGE:READ+EDIT'),\n  KNOWLEDGE_EXPORT: new Permission('KNOWLEDGE:READ+EXPORT'),\n  KNOWLEDGE_DELETE: new Permission('KNOWLEDGE:READ+DELETE'),\n  KNOWLEDGE_GENERATE: new Permission('KNOWLEDGE:READ+GENERATE'),\n  KNOWLEDGE_RELATE_RESOURCE_VIEW: new Permission('KNOWLEDGE:READ+RELATE_VIEW'),\n\n  KNOWLEDGE_WORKFLOW_READ: new Permission('KNOWLEDGE_WORKFLOW:READ'),\n  KNOWLEDGE_WORKFLOW_EDIT: new Permission('KNOWLEDGE_WORKFLOW:READ+EDIT'),\n  KNOWLEDGE_WORKFLOW_EXPORT: new Permission('KNOWLEDGE_WORKFLOW:READ+EXPORT'),\n\n  KNOWLEDGE_DOCUMENT_READ: new Permission('KNOWLEDGE_DOCUMENT:READ'),\n  KNOWLEDGE_DOCUMENT_CREATE: new Permission('KNOWLEDGE_DOCUMENT:READ+CREATE'),\n  KNOWLEDGE_DOCUMENT_DELETE: new Permission('KNOWLEDGE_DOCUMENT:READ+DELETE'),\n  KNOWLEDGE_DOCUMENT_EDIT: new Permission('KNOWLEDGE_DOCUMENT:READ+EDIT'),\n  KNOWLEDGE_DOCUMENT_SYNC: new Permission('KNOWLEDGE_DOCUMENT:READ+SYNC'),\n  KNOWLEDGE_DOCUMENT_MIGRATE: new Permission('KNOWLEDGE_DOCUMENT:READ+MIGRATE'),\n  KNOWLEDGE_DOCUMENT_VECTOR: new Permission('KNOWLEDGE_DOCUMENT:READ+VECTOR'),\n  KNOWLEDGE_DOCUMENT_GENERATE: new Permission('KNOWLEDGE_DOCUMENT:READ+GENERATE'),\n  KNOWLEDGE_DOCUMENT_EXPORT: new Permission('KNOWLEDGE_DOCUMENT:READ+EXPORT'),\n  KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE: new Permission('KNOWLEDGE_DOCUMENT:READ+DOWNLOAD'),\n  KNOWLEDGE_DOCUMENT_TAG: new Permission('KNOWLEDGE_DOCUMENT:READ+TAG'),\n  KNOWLEDGE_DOCUMENT_REPLACE: new Permission('KNOWLEDGE_DOCUMENT:READ+REPLACE'),\n\n  KNOWLEDGE_TAG_READ: new Permission('KNOWLEDGE_TAG:READ'),\n  KNOWLEDGE_TAG_CREATE: new Permission('KNOWLEDGE_TAG:READ+CREATE'),\n  KNOWLEDGE_TAG_EDIT: new Permission('KNOWLEDGE_TAG:READ+EDIT'),\n  KNOWLEDGE_TAG_DELETE: new Permission('KNOWLEDGE_TAG:READ+DELETE'),\n\n  KNOWLEDGE_PROBLEM_READ: new Permission('KNOWLEDGE_PROBLEM:READ'),\n  KNOWLEDGE_PROBLEM_CREATE: new Permission('KNOWLEDGE_PROBLEM:READ+CREATE'),\n  KNOWLEDGE_PROBLEM_EDIT: new Permission('KNOWLEDGE_PROBLEM:READ+EDIT'),\n  KNOWLEDGE_PROBLEM_RELATE: new Permission('KNOWLEDGE_PROBLEM:READ+RELATE'),\n  KNOWLEDGE_PROBLEM_DELETE: new Permission('KNOWLEDGE_PROBLEM:READ+DELETE'),\n\n  MODEL_READ: new Permission('MODEL:READ'),\n  MODEL_CREATE: new Permission('MODEL:READ+CREATE'),\n  MODEL_EDIT: new Permission('MODEL:READ+EDIT'),\n  MODEL_DELETE: new Permission('MODEL:READ+DELETE'),\n  MODEL_RELATE_RESOURCE_VIEW: new Permission('MODEL:READ+RELATE_VIEW'),\n\n  APPLICATION_READ: new Permission('APPLICATION:READ'),\n  APPLICATION_EXPORT: new Permission('APPLICATION:READ+EXPORT'),\n  APPLICATION_DELETE: new Permission('APPLICATION:READ+DELETE'),\n  APPLICATION_EDIT: new Permission('APPLICATION:READ+EDIT'),\n  APPLICATION_CREATE: new Permission('APPLICATION:READ+CREATE'),\n  APPLICATION_IMPORT: new Permission('APPLICATION:READ+IMPORT'),\n  APPLICATION_SETTING: new Permission('APPLICATION:READ+SETTING'),\n  APPLICATION_TO_CHAT: new Permission('APPLICATION:READ+TO_CHAT'),\n  APPLICATION_TRIGGER_READ: new Permission('APPLICATION:READ+TRIGGER_READ'),\n  APPLICATION_TRIGGER_CREATE: new Permission('APPLICATION:READ+TRIGGER_CREATE'),\n  APPLICATION_TRIGGER_EDIT: new Permission('APPLICATION:READ+TRIGGER_EDIT'),\n  APPLICATION_TRIGGER_DELETE: new Permission('APPLICATION:READ+TRIGGER_DELETE'),\n\n\n  APPLICATION_OVERVIEW_READ: new Permission('APPLICATION_OVERVIEW:READ'),\n  APPLICATION_OVERVIEW_EMBEDDED: new Permission('APPLICATION_OVERVIEW:READ+EMBED'),\n  APPLICATION_OVERVIEW_ACCESS: new Permission('APPLICATION_OVERVIEW:READ+ACCESS'),\n  APPLICATION_OVERVIEW_DISPLAY: new Permission('APPLICATION_OVERVIEW:READ+DISPLAY'),\n  APPLICATION_OVERVIEW_API_KEY: new Permission('APPLICATION_OVERVIEW:READ+API_KEY'),\n  APPLICATION_OVERVIEW_PUBLIC: new Permission('APPLICATION_OVERVIEW:READ+PUBLIC_ACCESS'),\n\n  APPLICATION_CHAT_LOG_READ: new Permission('APPLICATION_CHAT_LOG:READ'),\n  APPLICATION_CHAT_LOG_ANNOTATION: new Permission('APPLICATION_CHAT_LOG:READ+ANNOTATION'),\n  APPLICATION_CHAT_LOG_EXPORT: new Permission('APPLICATION_CHAT_LOG:READ+EXPORT'),\n  APPLICATION_CHAT_LOG_CLEAR_POLICY: new Permission('APPLICATION_CHAT_LOG:READ+CLEAR_POLICY'),\n  APPLICATION_CHAT_LOG_ADD_KNOWLEDGE: new Permission('APPLICATION_CHAT_LOG:READ+ADD_KNOWLEDGE'),\n\n  APPLICATION_ACCESS_READ: new Permission('APPLICATION_ACCESS:READ'),\n  APPLICATION_ACCESS_EDIT: new Permission('APPLICATION_ACCESS:READ+EDIT'),\n\n  APPLICATION_CHAT_USER_READ: new Permission('APPLICATION_CHAT_USER:READ'),\n  APPLICATION_CHAT_USER_EDIT: new Permission('APPLICATION_CHAT_USER:READ+EDIT'),\n\n  KNOWLEDGE_CHAT_USER_READ: new Permission('KNOWLEDGE_CHAT_USER:READ'),\n  KNOWLEDGE_CHAT_USER_EDIT: new Permission('KNOWLEDGE_CHAT_USER:READ+EDIT'),\n\n  SHARED_TOOL_READ: new Permission('SYSTEM_TOOL:READ'),\n  SHARED_TOOL_CREATE: new Permission('SYSTEM_TOOL:READ+CREATE'),\n  SHARED_TOOL_EDIT: new Permission('SYSTEM_TOOL:READ+EDIT'),\n  SHARED_TOOL_DELETE: new Permission('SYSTEM_TOOL:READ+DELETE'),\n  SHARED_TOOL_IMPORT: new Permission('SYSTEM_TOOL:READ+IMPORT'),\n  SHARED_TOOL_EXPORT: new Permission('SYSTEM_TOOL:READ+EXPORT'),\n  SHARED_TOOL_RELATE_RESOURCE_VIEW: new Permission('SYSTEM_TOOL:READ+RELATE_VIEW'),\n  SHARED_TOOL_EXECUTE_RECORD: new Permission('SYSTEM_TOOL:READ+RECORD'),\n\n  SHARED_MODEL_READ: new Permission('SYSTEM_MODEL:READ'),\n  SHARED_MODEL_CREATE: new Permission('SYSTEM_MODEL:READ+CREATE'),\n  SHARED_MODEL_EDIT: new Permission('SYSTEM_MODEL:READ+EDIT'),\n  SHARED_MODEL_DELETE: new Permission('SYSTEM_MODEL:READ+DELETE'),\n  SHARED_MODEL_RELATE_RESOURCE_VIEW: new Permission('SYSTEM_MODEL:READ+RELATE_VIEW'),\n\n  SHARED_KNOWLEDGE_READ: new Permission('SYSTEM_KNOWLEDGE:READ'),\n  SHARED_KNOWLEDGE_CREATE: new Permission('SYSTEM_KNOWLEDGE:READ+CREATE'),\n  SHARED_KNOWLEDGE_EDIT: new Permission('SYSTEM_KNOWLEDGE:READ+EDIT'),\n  SHARED_KNOWLEDGE_SYNC: new Permission('SYSTEM_KNOWLEDGE:READ+SYNC'),\n  SHARED_KNOWLEDGE_VECTOR: new Permission('SYSTEM_KNOWLEDGE:READ+VECTOR'),\n  SHARED_KNOWLEDGE_EXPORT: new Permission('SYSTEM_KNOWLEDGE:READ+EXPORT'),\n  SHARED_KNOWLEDGE_GENERATE: new Permission('SYSTEM_KNOWLEDGE:READ+GENERATE'),\n  SHARED_KNOWLEDGE_DELETE: new Permission('SYSTEM_KNOWLEDGE:READ+DELETE'),\n  SHARED_KNOWLEDGE_RELATE_RESOURCE_VIEW: new Permission('SYSTEM_KNOWLEDGE:READ+RELATE_VIEW'),\n\n  SHARED_KNOWLEDGE_WORKFLOW_READ: new Permission('SYSTEM_KNOWLEDGE_WORKFLOW:READ'),\n  SHARED_KNOWLEDGE_WORKFLOW_EDIT: new Permission('SYSTEM_KNOWLEDGE_WORKFLOW:READ+EDIT'),\n  SHARED_KNOWLEDGE_WORKFLOW_EXPORT: new Permission('SYSTEM_KNOWLEDGE_WORKFLOW:READ+EXPORT'),\n\n  SHARED_KNOWLEDGE_DOCUMENT_READ: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ'),\n  SHARED_KNOWLEDGE_DOCUMENT_CREATE: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+CREATE'),\n  SHARED_KNOWLEDGE_DOCUMENT_EDIT: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+EDIT'),\n  SHARED_KNOWLEDGE_DOCUMENT_DELETE: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+DELETE'),\n  SHARED_KNOWLEDGE_DOCUMENT_SYNC: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+SYNC'),\n  SHARED_KNOWLEDGE_DOCUMENT_VECTOR: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+VECTOR'),\n  SHARED_KNOWLEDGE_DOCUMENT_GENERATE: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+GENERATE'),\n  SHARED_KNOWLEDGE_DOCUMENT_MIGRATE: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+MIGRATE'),\n  SHARED_KNOWLEDGE_DOCUMENT_EXPORT: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+EXPORT'),\n  SHARED_KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+DOWNLOAD'),\n  SHARED_KNOWLEDGE_DOCUMENT_TAG: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+TAG'),\n  SHARED_KNOWLEDGE_DOCUMENT_REPLACE: new Permission('SYSTEM_KNOWLEDGE_DOCUMENT:READ+REPLACE'),\n\n  SHARED_KNOWLEDGE_TAG_READ: new Permission('SYSTEM_KNOWLEDGE_TAG:READ'),\n  SHARED_KNOWLEDGE_TAG_EDIT: new Permission('SYSTEM_KNOWLEDGE_TAG:READ+EDIT'),\n  SHARED_KNOWLEDGE_TAG_CREATE: new Permission('SYSTEM_KNOWLEDGE_TAG:READ+CREATE'),\n  SHARED_KNOWLEDGE_TAG_DELETE: new Permission('SYSTEM_KNOWLEDGE_TAG:READ+DELETE'),\n\n  SHARED_KNOWLEDGE_PROBLEM_READ: new Permission('SYSTEM_KNOWLEDGE_PROBLEM:READ'),\n  SHARED_KNOWLEDGE_PROBLEM_CREATE: new Permission('SYSTEM_KNOWLEDGE_PROBLEM:READ+CREATE'),\n  SHARED_KNOWLEDGE_PROBLEM_EDIT: new Permission('SYSTEM_KNOWLEDGE_PROBLEM:READ+EDIT'),\n  SHARED_KNOWLEDGE_PROBLEM_DELETE: new Permission('SYSTEM_KNOWLEDGE_PROBLEM:READ+DELETE'),\n  SHARED_KNOWLEDGE_PROBLEM_RELATE: new Permission('SYSTEM_KNOWLEDGE_PROBLEM:READ+RELATE'),\n\n  SHARED_KNOWLEDGE_HIT_TEST_READ: new Permission('SYSTEM_KNOWLEDGE_HIT_TEST:READ'),\n  KNOWLEDGE_HIT_TEST_READ: new Permission('KNOWLEDGE_HIT_TEST:READ'),\n\n  SHARED_KNOWLEDGE_CHAT_USER_READ: new Permission('SYSTEM_KNOWLEDGE_CHAT_USER:READ'),\n  SHARED_KNOWLEDGE_CHAT_USER_EDIT: new Permission('SYSTEM_KNOWLEDGE_CHAT_USER:READ+EDIT'),\n\n  TOOL_CREATE: new Permission('TOOL:READ+CREATE'),\n  TOOL_EDIT: new Permission('TOOL:READ+EDIT'),\n  TOOL_READ: new Permission('TOOL:READ'),\n  TOOL_DELETE: new Permission('TOOL:READ+DELETE'),\n  TOOL_IMPORT: new Permission('TOOL:READ+IMPORT'),\n  TOOL_EXPORT: new Permission('TOOL:READ+EXPORT'),\n  TOOL_RELATE_RESOURCE_VIEW: new Permission('TOOL:READ+RELATE_VIEW'),\n  TOOL_EXECUTE_RECORD: new Permission('TOOL:READ+RECORD'),\n  TOOL_TRIGGER_READ: new Permission('TOOL:READ+TRIGGER_READ'),\n  TOOL_TRIGGER_CREATE: new Permission('TOOL:READ+TRIGGER_CREATE'),\n  TOOL_TRIGGER_EDIT: new Permission('TOOL:READ+TRIGGER_EDIT'),\n  TOOL_TRIGGER_DELETE: new Permission('TOOL:READ+TRIGGER_DELETE'),\n\n  RESOURCE_TOOL_CREATE: new Permission('SYSTEM_RESOURCE_TOOL:READ+CREATE'),\n  RESOURCE_TOOL_EDIT: new Permission('SYSTEM_RESOURCE_TOOL:READ+EDIT'),\n  RESOURCE_TOOL_READ: new Permission('SYSTEM_RESOURCE_TOOL:READ'),\n  RESOURCE_TOOL_DELETE: new Permission('SYSTEM_RESOURCE_TOOL:READ+DELETE'),\n  RESOURCE_TOOL_IMPORT: new Permission('SYSTEM_RESOURCE_TOOL:READ+IMPORT'),\n  RESOURCE_TOOL_EXPORT: new Permission('SYSTEM_RESOURCE_TOOL:READ+EXPORT'),\n  RESOURCE_TOOL_EXECUTE_RECORD: new Permission('SYSTEM_RESOURCE_TOOL:READ+RECORD'),\n  RESOURCE_TOOL_TRIGGER_READ: new Permission('SYSTEM_RESOURCE_TOOL:READ+TRIGGER_READ'),\n  RESOURCE_TOOL_TRIGGER_CREATE: new Permission('SYSTEM_RESOURCE_TOOL:READ+TRIGGER_CREATE'),\n  RESOURCE_TOOL_TRIGGER_EDIT: new Permission('SYSTEM_RESOURCE_TOOL:READ+TRIGGER_EDIT'),\n  RESOURCE_TOOL_TRIGGER_DELETE: new Permission('SYSTEM_RESOURCE_TOOL:READ+TRIGGER_DELETE'),\n\n  RESOURCE_KNOWLEDGE_READ: new Permission('SYSTEM_RESOURCE_KNOWLEDGE:READ'),\n  RESOURCE_KNOWLEDGE_CREATE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE:READ+CREATE'),\n  RESOURCE_KNOWLEDGE_SYNC: new Permission('SYSTEM_RESOURCE_KNOWLEDGE:READ+SYNC'),\n  RESOURCE_KNOWLEDGE_VECTOR: new Permission('SYSTEM_RESOURCE_KNOWLEDGE:READ+VECTOR'),\n  RESOURCE_KNOWLEDGE_EDIT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE:READ+EDIT'),\n  RESOURCE_KNOWLEDGE_EXPORT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE:READ+EXPORT'),\n  RESOURCE_KNOWLEDGE_DELETE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE:READ+DELETE'),\n  RESOURCE_KNOWLEDGE_GENERATE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE:READ+GENERATE'),\n\n  RESOURCE_KNOWLEDGE_WORKFLOW_READ: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_WORKFLOW:READ'),\n  RESOURCE_KNOWLEDGE_WORKFLOW_EDIT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_WORKFLOW:READ+EDIT'),\n  RESOURCE_KNOWLEDGE_WORKFLOW_EXPORT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_WORKFLOW:READ+EXPORT'),\n\n  RESOURCE_KNOWLEDGE_DOCUMENT_READ: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_CREATE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+CREATE'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_DELETE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+DELETE'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_EDIT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+EDIT'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_SYNC: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+SYNC'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_MIGRATE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+MIGRATE'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_VECTOR: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+VECTOR'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_GENERATE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+GENERATE'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_EXPORT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+EXPORT'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_DOWNLOAD_SOURCE_FILE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+DOWNLOAD'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_TAG: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+TAG'),\n  RESOURCE_KNOWLEDGE_DOCUMENT_REPLACE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_DOCUMENT:READ+REPLACE'),\n\n  RESOURCE_KNOWLEDGE_TAG_READ: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM:READ'),\n  RESOURCE_KNOWLEDGE_TAG_CREATE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM:READ+CREATE'),\n  RESOURCE_KNOWLEDGE_TAG_EDIT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM:READ+EDIT'),\n  RESOURCE_KNOWLEDGE_TAG_DELETE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM:READ+DELETE'),\n\n  RESOURCE_KNOWLEDGE_PROBLEM_READ: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM:READ'),\n  RESOURCE_KNOWLEDGE_PROBLEM_CREATE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM:READ+CREATE'),\n  RESOURCE_KNOWLEDGE_PROBLEM_EDIT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM:READ+EDIT'),\n  RESOURCE_KNOWLEDGE_PROBLEM_RELATE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM:READ+RELATE'),\n  RESOURCE_KNOWLEDGE_PROBLEM_DELETE: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_PROBLEM:READ+DELETE'),\n\n  RESOURCE_KNOWLEDGE_CHAT_USER_READ: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_CHAT_USER:READ'),\n  RESOURCE_KNOWLEDGE_CHAT_USER_EDIT: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_CHAT_USER:READ+EDIT'),\n  RESOURCE_KNOWLEDGE_HIT_TEST: new Permission('SYSTEM_RESOURCE_KNOWLEDGE_HIT_TEST:READ'),\n\n  RESOURCE_APPLICATION_READ: new Permission('SYSTEM_RESOURCE_APPLICATION:READ'),\n  RESOURCE_APPLICATION_EDIT: new Permission('SYSTEM_RESOURCE_APPLICATION:READ+EDIT'),\n  RESOURCE_APPLICATION_IMPORT: new Permission('SYSTEM_RESOURCE_APPLICATION:READ+IMPORT'),\n  RESOURCE_APPLICATION_EXPORT: new Permission('SYSTEM_RESOURCE_APPLICATION:READ+EXPORT'),\n  RESOURCE_APPLICATION_DELETE: new Permission('SYSTEM_RESOURCE_APPLICATION:READ+DELETE'),\n  RESOURCE_APPLICATION_TRIGGER_READ: new Permission('SYSTEM_RESOURCE_APPLICATION:READ+TRIGGER_READ'),\n  RESOURCE_APPLICATION_TRIGGER_CREATE: new Permission('SYSTEM_RESOURCE_APPLICATION:READ+TRIGGER_CREATE'),\n  RESOURCE_APPLICATION_TRIGGER_EDIT: new Permission('SYSTEM_RESOURCE_APPLICATION:READ+TRIGGER_EDIT'),\n  RESOURCE_APPLICATION_TRIGGER_DELETE: new Permission('SYSTEM_RESOURCE_APPLICATION:READ+TRIGGER_DELETE'),\n\n  RESOURCE_APPLICATION_OVERVIEW_READ: new Permission('SYSTEM_RESOURCE_APPLICATION_OVERVIEW:READ'),\n  RESOURCE_APPLICATION_OVERVIEW_EMBED: new Permission('SYSTEM_RESOURCE_APPLICATION_OVERVIEW:READ+EMBED'),\n  RESOURCE_APPLICATION_OVERVIEW_ACCESS: new Permission('SYSTEM_RESOURCE_APPLICATION_OVERVIEW:READ+ACCESS'),\n  RESOURCE_APPLICATION_OVERVIEW_DISPLAY: new Permission('SYSTEM_RESOURCE_APPLICATION_OVERVIEW:READ+DISPLAY'),\n  RESOURCE_APPLICATION_OVERVIEW_API_KEY: new Permission('SYSTEM_RESOURCE_APPLICATION_OVERVIEW:READ+API_KEY'),\n  RESOURCE_APPLICATION_OVERVIEW_PUBLIC: new Permission('SYSTEM_RESOURCE_APPLICATION_OVERVIEW:READ+PUBLIC_ACCESS'),\n\n  RESOURCE_APPLICATION_CHAT_LOG_READ: new Permission('SYSTEM_RESOURCE_APPLICATION_CHAT_LOG:READ'),\n  RESOURCE_APPLICATION_CHAT_LOG_ANNOTATION: new Permission('SYSTEM_RESOURCE_APPLICATION_CHAT_LOG:READ+ANNOTATION'),\n  RESOURCE_APPLICATION_CHAT_LOG_EXPORT: new Permission('SYSTEM_RESOURCE_APPLICATION_CHAT_LOG:READ+EXPORT'),\n  RESOURCE_APPLICATION_CHAT_LOG_CLEAR_POLICY: new Permission('SYSTEM_RESOURCE_APPLICATION_CHAT_LOG:READ+CLEAR_POLICY'),\n  RESOURCE_APPLICATION_CHAT_LOG_ADD_KNOWLEDGE: new Permission('SYSTEM_RESOURCE_APPLICATION_CHAT_LOG:READ+ADD_KNOWLEDGE'),\n\n  RESOURCE_APPLICATION_ACCESS_READ: new Permission('SYSTEM_RESOURCE_APPLICATION_ACCESS:READ'),\n  RESOURCE_APPLICATION_ACCESS_EDIT: new Permission('SYSTEM_RESOURCE_APPLICATION_ACCESS:READ+EDIT'),\n\n  RESOURCE_APPLICATION_CHAT_USER_READ: new Permission('SYSTEM_RESOURCE_APPLICATION_CHAT_USER:READ'),\n  RESOURCE_APPLICATION_CHAT_USER_EDIT: new Permission('SYSTEM_RESOURCE_APPLICATION_CHAT_USER:READ+EDIT'),\n\n  RESOURCE_MODEL_READ: new Permission('SYSTEM_RESOURCE_MODEL:READ'),\n  RESOURCE_MODEL_EDIT: new Permission('SYSTEM_RESOURCE_MODEL:READ+EDIT'),\n  RESOURCE_MODEL_DELETE: new Permission('SYSTEM_RESOURCE_MODEL:READ+DELETE'),\n\n  RESOURCE_MODEL_AUTH: new Permission('SYSTEM_RESOURCE_MODEL:READ+AUTH'),\n  RESOURCE_APPLICATION_AUTH: new Permission('SYSTEM_RESOURCE_APPLICATION:READ+AUTH'),\n  RESOURCE_KNOWLEDGE_AUTH: new Permission('SYSTEM_RESOURCE_KNOWLEDGE:READ+AUTH'),\n  RESOURCE_KNOWLEDGE_RELATE_RESOURCE_VIEW: new Permission('SYSTEM_RESOURCE_KNOWLEDGE:READ+RELATE_VIEW'),\n  RESOURCE_MODEL_RELATE_RESOURCE_VIEW: new Permission('SYSTEM_RESOURCE_MODEL:READ+RELATE_VIEW'),\n  RESOURCE_TOOL_RELATE_RESOURCE_VIEW: new Permission('SYSTEM_RESOURCE_TOOL:READ+RELATE_VIEW'),\n  RESOURCE_TOOL_AUTH: new Permission('SYSTEM_RESOURCE_TOOL:READ+AUTH'),\n\n  APPEARANCE_SETTINGS_READ: new Permission('APPEARANCE_SETTINGS:READ'),\n  APPEARANCE_SETTINGS_EDIT: new Permission('APPEARANCE_SETTINGS:READ+EDIT'),\n\n  LOGIN_AUTH_READ: new Permission('LOGIN_AUTH:READ'),\n  LOGIN_AUTH_EDIT: new Permission('LOGIN_AUTH:READ+EDIT'),\n\n  EMAIL_SETTING_READ: new Permission('EMAIL_SETTING:READ'),\n  EMAIL_SETTING_EDIT: new Permission('EMAIL_SETTING:READ+EDIT'),\n\n  OPERATION_LOG_READ: new Permission('OPERATION_LOG:READ'),\n  OPERATION_LOG_EXPORT: new Permission('OPERATION_LOG:READ+EXPORT'),\n  OPERATION_LOG_CLEAR_POLICY: new Permission('OPERATION_LOG:READ+CLEAR_POLICY'),\n\n  ABOUT_READ: new Permission('OTHER:READ'),\n  ABOUT_UPDATE: new Permission('OTHER:READ+UPDATE'),\n  SWITCH_LANGUAGE: new Permission('OTHER:READ+EDIT'),\n  CHANGE_PASSWORD: new Permission('OTHER:READ+CREATE'),\n  SYSTEM_API_KEY_EDIT: new Permission('OTHER:READ+DELETE'),\n\n  APPLICATION_RESOURCE_AUTHORIZATION: new Permission(\n    'APPLICATION:READ+AUTH',\n  ),\n  KNOWLEDGE_RESOURCE_AUTHORIZATION: new Permission(\n    'KNOWLEDGE:READ+AUTH',\n  ),\n  TOOL_RESOURCE_AUTHORIZATION: new Permission(\n    'TOOL:READ+AUTH',\n  ),\n  MODEL_RESOURCE_AUTHORIZATION: new Permission(\n    'MODEL:READ+AUTH',\n  ),\n\n  APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_READ: new Permission(\n    'APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION:READ',\n  ),\n  KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_READ: new Permission(\n    'KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION:READ',\n  ),\n  TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_READ: new Permission(\n    'TOOL_WORKSPACE_USER_RESOURCE_PERMISSION:READ',\n  ),\n  MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_READ: new Permission(\n    'MODEL_WORKSPACE_USER_RESOURCE_PERMISSION:READ',\n  ),\n  APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT: new Permission(\n    'APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION:READ+EDIT',\n  ),\n  KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT: new Permission(\n    'KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION:READ+EDIT',\n  ),\n  TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT: new Permission(\n    'TOOL_WORKSPACE_USER_RESOURCE_PERMISSION:READ+EDIT',\n  ),\n  MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT: new Permission(\n    'MODEL_WORKSPACE_USER_RESOURCE_PERMISSION:READ+EDIT',\n  ),\n}\nconst RoleConst = {\n  ADMIN: new Role('ADMIN'),\n  WORKSPACE_MANAGE: new Role('WORKSPACE_MANAGE'),\n  USER: new Role('USER'),\n  EXTENDS_ADMIN: new Role('EXTENDS_ADMIN'),\n  EXTENDS_WORKSPACE_MANAGE: new Role('EXTENDS_WORKSPACE_MANAGE'),\n  EXTENDS_USER: new Role('EXTENDS_USER'),\n}\nconst EditionConst = {\n  IS_PE: new Edition('X-PACK-PE'),\n  IS_EE: new Edition('X-PACK-EE'),\n  IS_CE: new Edition('X-PACK-CE'),\n}\nexport {PermissionConst, RoleConst, EditionConst}\n\n"
  },
  {
    "path": "ui/src/utils/permission/index.ts",
    "content": "import useStore from '@/stores'\nimport {\n  Role,\n  Permission,\n  ComplexPermission,\n  Edition,\n  type PF,\n  type CPF,\n  type CRF,\n} from '@/utils/permission/type'\nimport { isFunction } from '@/utils/common'\n\n/**\n * 是否包含当前权限\n * @param permission 当前权限\n * @returns  True 包含 false 不包含\n */\nconst hasPermissionChild = (\n  permission: Role | string | Permission | ComplexPermission | Edition | PF,\n) => {\n  const { user } = useStore()\n  const permissions = user.getPermissions()\n  const role: Array<string> = user.getRole()\n  const edition = user.getEdition()\n  if (!permission) {\n    return true\n  }\n  if (isFunction(permission)) {\n    permission = (permission as PF)()\n  }\n  if (permission instanceof Role) {\n    return role.includes(permission.role)\n  }\n  if (permission instanceof Permission) {\n    return permissions.includes(permission.permission)\n  }\n  if (permission instanceof Edition) {\n    return permission.edition === edition\n  }\n  if (permission instanceof ComplexPermission) {\n    const permissionOk = permission.permissionList.some((p) =>\n      permissions.includes(isFunction(p) ? (p as CPF)().toString() : p.toString()),\n    )\n    const roleList = permission.roleList\n    const roleOk = roleList.some((r) =>\n      role.includes(isFunction(r) ? (r as CRF)().toString() : r.toString()),\n    )\n    const editionList = permission.editionList\n    const editionOK =\n      permission.editionList.length > 0\n        ? editionList.some((e) => edition.toString() == e.toString())\n        : true\n\n    return permission.compare === 'AND'\n      ? permissionOk && roleOk && editionOK\n      : (permissionOk || roleOk) && editionOK\n  }\n  if (typeof permission === 'string') {\n    return permissions.includes(permission)\n  }\n\n  return false\n}\n/**\n * 判断是否有角色和权限\n * @param role         角色\n * @param permissions  权限\n * @param requiredPermissions  权限\n * @returns\n */\nexport const hasPermission = (\n  permission:\n    | Array<Role | string | Permission | ComplexPermission | Edition | PF>\n    | Role\n    | string\n    | Permission\n    | Edition\n    | ComplexPermission\n    | PF,\n  compare: 'OR' | 'AND',\n): boolean => {\n  if (permission instanceof Array) {\n    return compare === 'OR'\n      ? permission.some((p) => hasPermissionChild(p))\n      : permission.every((p) => hasPermissionChild(p))\n  } else {\n    return hasPermissionChild(permission)\n  }\n}\n\nconst R = {\n  to: null,\n}\nexport const get_next_route = () => {\n  return R.to\n}\n\nexport const set_next_route = (to: any) => {\n  R.to = to\n}\n"
  },
  {
    "path": "ui/src/utils/permission/type.ts",
    "content": "import useStore from '@/stores'\nimport { useRoute } from 'vue-router'\nexport type PF = () => Role | string | Permission | ComplexPermission\nexport type CRF = () => Role | string\nexport type CPF = () => Permission | string\n/**\n * 角色对象\n */\nexport class Role {\n  role: string\n\n  constructor(role: string) {\n    this.role = role\n  }\n\n  getWorkspaceRole = () => {\n    const { user } = useStore()\n    return new Role(`${this.role}:/WORKSPACE/${user.getWorkspaceId()}`)\n  }\n  getWorkspaceRoleString = () => {\n    const { user } = useStore()\n    return `${this.role}:/WORKSPACE/${user.getWorkspaceId()}`\n  }\n  toString() {\n    return this.role\n  }\n}\n/**\n * 权限对象\n */\nexport class Permission {\n  permission: string\n\n  constructor(permission: string) {\n    this.permission = permission\n  }\n  /**\n   * 工作空间权限\n   * @param workspace_id 工作空间id\n   * @returns 工作空间权限\n   */\n  getWorkspacePermission = () => {\n    const { user } = useStore()\n    return `${this.permission}:/WORKSPACE/${user.getWorkspaceId()}`\n  }\n  /**\n   * 自定义工作空间管理员权限\n   * @returns\n   */\n  getWorkspacePermissionWorkspaceManageRole = () => {\n    const { user } = useStore()\n    return `${this.permission}:/WORKSPACE/${user.getWorkspaceId()}:ROLE/WORKSPACE_MANAGE`\n  }\n  /**\n   * 工作空间资源权限\n   * @param workspace_id 工作空间id\n   * @param resource     资源\n   * @param resource_id  资源id\n   * @returns  工作空间资源权限\n   */\n  getWorkspaceResourcePermission = (resource: string, resource_id: string) => {\n    const { user } = useStore()\n    return `${this.permission}:/WORKSPACE/${user.getWorkspaceId()}/${resource}/${resource_id}`\n  }\n  /**\n   *\n   * @param resource_id 资源id\n   * @returns 工作空间下知识库资源权限\n   */\n  getKnowledgeWorkspaceResourcePermission = (resource_id: string) => {\n    return this.getWorkspaceResourcePermission('KNOWLEDGE', resource_id)\n  }\n  getTest=()=>{\n    const route=useRoute()\n    debugger\n    console.log(route)\n    return \"\"\n  }\n  /**\n   *\n   * @param resource_id  资源id\n   * @returns 工作空间下应用资源权限\n   */\n  getApplicationWorkspaceResourcePermission = (resource_id: string) => {\n    return this.getWorkspaceResourcePermission('APPLICATION', resource_id)\n  }\n  /**\n   * \n   * @param resource_id 资源id\n   * @returns 工作空间下模型资源权限\n   */\n  getModelWorkspaceResourcePermission = (resource_id: string) => {\n    return this.getWorkspaceResourcePermission('MODEL', resource_id)\n  }\n  /**\n   * \n   * @param resource_id \n   * @returns 工作空间下工具资源权限\n   */\n  getToolWorkspaceResourcePermission = (resource_id: string) => {\n    return this.getWorkspaceResourcePermission('TOOL', resource_id)\n  }\n\n  toString() {\n    return this.permission\n  }\n}\n\n/**\n * 复杂权限对象\n */\nexport class ComplexPermission {\n  roleList: Array<string | Role | CRF>\n\n  permissionList: Array<string | Permission | CPF>\n\n  editionList: Array<string | Edition>\n\n  compare: 'OR' | 'AND'\n\n  constructor(\n    roleList: Array<string | Role | CRF>,\n    permissionList: Array<string | Permission | CPF>,\n    editionList: Array<string | Edition>,\n    compare: 'OR' | 'AND',\n  ) {\n    this.roleList = roleList\n    this.permissionList = permissionList\n    this.editionList = editionList\n    this.compare = compare\n  }\n}\n/**\n * 版本\n */\nexport class Edition {\n  edition: string\n  constructor(edition: string) {\n    this.edition = edition\n  }\n  toString() {\n    return this.edition\n  }\n}\n"
  },
  {
    "path": "ui/src/utils/status.ts",
    "content": "import { type Dict } from '@/api/type/common'\ninterface TaskTypeInterface {\n  // 向量化\n  EMBEDDING: number\n  // 生成问题\n  GENERATE_PROBLEM: number\n  // 同步\n  SYNC: number\n}\ninterface StateInterface {\n  // 等待\n  PENDING: '0'\n  // 执行中\n  STARTED: '1'\n  // 成功\n  SUCCESS: '2'\n  // 失败\n  FAILURE: '3'\n  // 取消任务\n  REVOKE: '4'\n  // 取消成功\n  REVOKED: '5'\n  IGNORED: 'n'\n}\nconst TaskType: TaskTypeInterface = {\n  EMBEDDING: 1,\n  GENERATE_PROBLEM: 2,\n  SYNC: 3\n}\nconst State: StateInterface = {\n  // 等待\n  PENDING: '0',\n  // 执行中\n  STARTED: '1',\n  // 成功\n  SUCCESS: '2',\n  // 失败\n  FAILURE: '3',\n  // 取消任务\n  REVOKE: '4',\n  // 取消成功\n  REVOKED: '5',\n  IGNORED: 'n'\n}\nclass Status {\n  task_status: Dict<any>\n  constructor(status?: string) {\n    if (!status) {\n      status = ''\n    }\n    status = status.split('').reverse().join('')\n    this.task_status = {}\n    for (const key in TaskType) {\n      const value = TaskType[key as keyof TaskTypeInterface]\n      const index = value - 1\n      this.task_status[value] = status[index] ? status[index] : 'n'\n    }\n  }\n  toString() {\n    const r = []\n    for (const key in TaskType) {\n      const value = TaskType[key as keyof TaskTypeInterface]\n      r.push(this.task_status[value])\n    }\n    return r.reverse().join('')\n  }\n}\nexport { Status, State, TaskType, type TaskTypeInterface, type StateInterface }\n"
  },
  {
    "path": "ui/src/utils/theme.ts",
    "content": "import { t } from '@/locales'\n\nexport const themeList = [\n  {\n    label: t('theme.default'),\n    value: '#3370FF',\n    loginBackground: 'default',\n  },\n  {\n    label: t('theme.orange'),\n    value: '#FF8800',\n    loginBackground: 'orange',\n  },\n  {\n    label: t('theme.green'),\n    value: '#00B69D',\n    loginBackground: 'green',\n  },\n  {\n    label: t('theme.purple'),\n    value: '#7F3BF5',\n    loginBackground: 'purple',\n  },\n  {\n    label: t('theme.red'),\n    value: '#F01D94',\n    loginBackground: 'red',\n  },\n]\n\nexport function getThemeImg(val: string) {\n  if (!val) return 'default'\n  return themeList.filter((v) => v.value === val)?.[0]?.loginBackground || 'default'\n}\n\nexport const defaultSetting = {\n  icon: '',\n  loginLogo: '',\n  loginImage: '',\n  title: 'MaxKB',\n  slogan: t('theme.defaultSlogan'),\n}\n\nexport const defaultPlatformSetting = {\n  showUserManual: true,\n  userManualUrl: t('layout.userManualUrl'),\n  showForum: true,\n  forumUrl: t('layout.forumUrl'),\n  showProject: true,\n  projectUrl: 'https://github.com/1Panel-dev/MaxKB',\n}\n\nexport function hexToRgba(hex?: string, alpha?: number) {\n  // 将16进制颜色值的两个字符一起转换成十进制\n  if (!hex) {\n    return ''\n  } else {\n    const r = parseInt(hex.slice(1, 3), 16)\n    const g = parseInt(hex.slice(3, 5), 16)\n    const b = parseInt(hex.slice(5, 7), 16)\n\n    // 返回RGBA格式的字符串\n    return `rgba(${r}, ${g}, ${b}, ${alpha})`\n  }\n}\n"
  },
  {
    "path": "ui/src/utils/time.ts",
    "content": "import moment from 'moment'\nimport 'moment/dist/locale/zh-cn'\n\nmoment.locale('zh-cn')\nimport {t} from '@/locales'\n\nexport const expiredTimeList = {\n  'never': t('layout.time.neverExpires'),\n  '7': '7 ' + t('layout.time.daysValid'),\n  '30': '30 ' + t('layout.time.daysValid'),\n  '90': '90 ' + t('layout.time.daysValid'),\n  '180': '180 ' + t('layout.time.daysValid'),\n  'custom': t('common.custom'),\n}\n\n// 当天日期 YYYY-MM-DD\nexport const nowDate = moment().format('YYYY-MM-DD')\n\n// 当前时间的前n天\nexport function beforeDay(n: number | string) {\n  return moment().subtract(n, 'days').format('YYYY-MM-DD')\n}\n\n// 当前时间的n天后的时间戳\nexport function AfterTimestamp(n: number | string) {\n  return moment().add(parseInt(n as string), 'days').format('YYYY-MM-DD HH:mm:ss')\n}\n\nexport function formatEndDate(date: any) {\n  return Number(moment(date).endOf('day').format('x'));\n} // date的23点59分\n\nexport function formatStartDate(date: any) {\n  return Number(moment(date).startOf('day').format('x'));\n} // date的0点\n\nconst getCheckDate = (timestamp: any) => {\n  if (!timestamp) return false\n  const dt = new Date(timestamp)\n  if (isNaN(dt.getTime())) return false\n  return dt\n}\nexport const datetimeFormat = (timestamp: any) => {\n  const dt = getCheckDate(timestamp)\n  if (!dt) return timestamp\n\n  const y = dt.getFullYear()\n  const m = (dt.getMonth() + 1 + '').padStart(2, '0')\n  const d = (dt.getDate() + '').padStart(2, '0')\n  const hh = (dt.getHours() + '').padStart(2, '0')\n  const mm = (dt.getMinutes() + '').padStart(2, '0')\n  const ss = (dt.getSeconds() + '').padStart(2, '0')\n\n  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`\n}\n\nexport const dateFormat = (timestamp: any) => {\n  const dt = getCheckDate(timestamp)\n  if (!dt) return timestamp\n\n  const y = dt.getFullYear()\n  const m = (dt.getMonth() + 1 + '').padStart(2, '0')\n  const d = (dt.getDate() + '').padStart(2, '0')\n\n  return `${y}-${m}-${d}`\n}\n\nexport function fromNowDate(time: any) {\n  const curTime = new Date()\n  const futureTime = new Date(time)\n  const timeDiff = futureTime.getTime() - curTime.getTime()\n\n  // 统一时间单位\n  const absTimeDiff = Math.abs(timeDiff)\n  const min = 60 * 1000\n  const hour = min * 60\n  const day = hour * 24\n\n  // 按优先级判断\n  if (timeDiff < 0) {\n    return t('layout.time.expired') // 已过期\n  }\n\n  if (absTimeDiff < hour) {\n    const mins = Math.floor(timeDiff / min)\n    return mins > 0 ? mins + t('layout.time.minutesLater') : t('layout.time.expiringSoon')\n  }\n\n  if (absTimeDiff < day) {\n    return Math.floor(timeDiff / hour) + t('layout.time.hoursLater')\n  }\n\n  if (absTimeDiff < day * 7) {\n    return Math.floor(timeDiff / day) + t('layout.time.daysLater')\n  }\n  return ''\n}\n"
  },
  {
    "path": "ui/src/utils/trigger.ts",
    "content": "import { t } from '@/locales'\nimport { relatedObject } from '@/utils/array'\n\nconst times = Array.from({ length: 24 }, (_, i) => {\n  const time = i.toString().padStart(2, '0') + ':00'\n  return { label: time, value: time }\n})\nconst days = Array.from({ length: 31 }, (_, i) => {\n  i = i + 1\n  const day = i.toString() + t('views.trigger.triggerCycle.days')\n  return { label: day, value: i.toString(), children: times }\n})\nconst hours = Array.from({ length: 24 }, (_, i) => {\n  i = i + 1\n  const time = i.toString().padStart(2, '0')\n  return { label: time, value: i }\n})\nconst minutes = Array.from({ length: 60 }, (_, i) => {\n  i = i + 1\n  const time = i.toString().padStart(2, '0')\n  return { label: time, value: i }\n})\nexport const triggerCycleOptions = [\n  {\n    value: 'daily',\n    label: t('views.trigger.triggerCycle.daily'),\n    multiple: true,\n    children: times,\n  },\n  {\n    value: 'weekly',\n    label: t('views.trigger.triggerCycle.weekly'),\n    children: [\n      { label: t('views.trigger.triggerCycle.sunday'), value: 7, children: times },\n      { label: t('views.trigger.triggerCycle.monday'), value: 1, children: times },\n      { label: t('views.trigger.triggerCycle.tuesday'), value: 2, children: times },\n      { label: t('views.trigger.triggerCycle.wednesday'), value: 3, children: times },\n      { label: t('views.trigger.triggerCycle.thursday'), value: 4, children: times },\n      { label: t('views.trigger.triggerCycle.friday'), value: 5, children: times },\n      { label: t('views.trigger.triggerCycle.saturday'), value: 6, children: times },\n    ],\n  },\n  { value: 'monthly', label: t('views.trigger.triggerCycle.monthly'), children: days },\n  {\n    value: 'interval',\n    label: t('views.trigger.triggerCycle.interval'),\n    children: [\n      { label: t('views.trigger.triggerCycle.hours'), value: 'hours', children: hours },\n      { label: t('views.trigger.triggerCycle.minutes'), value: 'minutes', children: minutes },\n    ],\n  },\n]\n\nexport function getTriggerCycleLabel(data: any) {\n  const { schedule_type, days, time, interval_unit, interval_value } = data\n  if (!schedule_type) return ''\n  const scheduleOption = triggerCycleOptions.find((option) => option.value === schedule_type)\n  if (!scheduleOption) return ''\n  const baseLabel = scheduleOption.label\n  switch (schedule_type) {\n    case 'daily':\n      if (time) {\n        const timeLabel = relatedObject(scheduleOption.children, time.toString(), 'value')?.label\n        return `${baseLabel}/${timeLabel}`\n      }\n      return baseLabel\n\n    case 'weekly':\n      if (days && time) {\n        const dayOption:any = scheduleOption.children.find(\n          (day) => day.value.toString() === days.toString(),\n        )\n        if (dayOption) {\n          const timeLabel = relatedObject(dayOption.children, time.toString(), 'value')?.label\n          return `${baseLabel}/${dayOption.label}/${timeLabel}`\n        }\n      }\n      return baseLabel\n\n    case 'monthly':\n      if (days && time) {\n        const dayOption:any = scheduleOption.children.find(\n          (day) => day.value.toString() === days.toString(),\n        )\n        if (dayOption) {\n          const timeLabel = relatedObject(dayOption.children, time.toString(), 'value')?.label\n          return `${baseLabel}/${dayOption.label}/${timeLabel}`\n        }\n      }\n      return baseLabel\n\n    case 'interval':\n      if (interval_unit) {\n        const unitOption:any = scheduleOption.children.find((unit) => unit.value === interval_unit)\n        if (unitOption) {\n          const intervalLabel = relatedObject(unitOption.children, interval_value, 'value')?.label\n          return `${baseLabel}/${unitOption.label}/${intervalLabel}`\n        }\n      }\n      return baseLabel\n\n    default:\n      return baseLabel\n  }\n}\n"
  },
  {
    "path": "ui/src/views/Permission.vue",
    "content": "<template>\n  <div>说明: v-hasPermission 是使用v-show 本质上组件是渲染的 v-if=\"hasPermission('xxxx')\"</div>\n  <div>这种方式组件不会渲染(用于比如像组件挂载的时候需要调用接口,不想让组件渲染)</div>\n  <div>比如工作空间的下拉列表组件使用v-if 示例： 企业版组件:</div>\n\n  <button v-if=\"hasPermission(EditionConst.IS_CE, 'OR')\">我是社区版组件</button>\n\n  <button v-hasPermission=\"EditionConst.IS_CE\">我是社区版组件</button>\n  <!-- ================我是企业版组件================== -->\n  <button v-if=\"hasPermission(EditionConst.IS_EE, 'OR')\">我是企业版组件</button>\n  <button v-hasPermission=\"EditionConst.IS_EE\">我是企业版组件</button>\n\n  <!-- ================企业版组件 并且是ADMIN角色================== -->\n  <button v-if=\"hasPermission([EditionConst.IS_EE, RoleConst.ADMIN], 'AND')\">\n    我是企业版并且是ADMIN角色\n  </button>\n  <button\n    v-hasPermission=\"new ComplexPermission([RoleConst.ADMIN], [], [EditionConst.IS_EE], 'AND')\"\n  >\n    我是企业版并且是ADMIN角色\n  </button>\n  <!-- ================企业版组件 并且是当前工作空间管理员================== -->\n  <button\n    v-if=\"hasPermission([EditionConst.IS_EE, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole], 'AND')\"\n  >\n    我是企业版并且拥有当前工作空间管理员角色\n  </button>\n  <button\n    v-hasPermission=\"\n      new ComplexPermission(\n        [RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n        [],\n        [EditionConst.IS_EE],\n        'OR',\n      )\n    \"\n  >\n    我是企业版并且拥有当前工作空间管理员角色\n  </button>\n  <!-- ================企业版组件 （并且是当前工作空间管理员 或者有用户只读）================== -->\n  <button\n    v-if=\"\n      hasPermission(\n        new ComplexPermission(\n          [RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n          [PermissionConst.USER_READ],\n          [EditionConst.IS_EE],\n          'OR',\n        ),\n        'OR',\n      )\n    \"\n  >\n    我是企业版 （并且是当前工作空间管理员 或者有用户只读）\n  </button>\n  <button\n    v-hasPermission=\"\n      new ComplexPermission(\n        [RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n        [PermissionConst.USER_READ],\n        [EditionConst.IS_EE],\n        'OR',\n      )\n    \"\n  >\n    我是企业版（并且是当前工作空间管理员 或者有用户只读）\n  </button>\n</template>\n<script setup lang=\"ts\">\nimport { PermissionConst, EditionConst, RoleConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport { ComplexPermission } from '@/utils/permission/type'\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application/ApplicationAccess.vue",
    "content": "<template>\n  <div class=\"p-16-24\">\n    <h4 class=\"mb-16\">{{ $t('views.application.applicationAccess.title') }}</h4>\n\n    <el-row :gutter=\"16\">\n      <el-col\n        :xs=\"24\"\n        :sm=\"24\"\n        :md=\"12\"\n        :lg=\"12\"\n        :xl=\"12\"\n        class=\"mb-16\"\n        v-for=\"(item, index) in platforms\"\n        :key=\"index\"\n      >\n        <el-card shadow=\"never\" class=\"border-none cursor\">\n          <div class=\"flex-between\">\n            <div class=\"flex align-center ml-8 mr-8\">\n              <img :src=\"item.logoSrc\" alt=\"\" class=\"icon\" />\n              <div class=\"ml-12\">\n                <h5 class=\"mb-4\">{{ item.name }}</h5>\n                <el-text type=\"info\" class=\"font-small\">{{ item.description }}</el-text>\n              </div>\n            </div>\n            <div>\n              <el-switch\n                size=\"small\"\n                v-model=\"item.isActive\"\n                @change=\"changeStatus(item.key, item.isActive)\"\n                :disabled=\"!item.exists\"\n                v-if=\"permissionPrecise.access_edit(id)\"\n              />\n              <el-divider direction=\"vertical\" />\n              <el-button\n                class=\"mr-4\"\n                @click=\"openDrawer(item.key)\"\n                v-if=\"permissionPrecise.access_edit(id)\"\n                >{{ $t('views.application.applicationAccess.setting') }}</el-button\n              >\n            </div>\n          </div>\n        </el-card>\n      </el-col>\n    </el-row>\n    <AccessSettingDrawer ref=\"AccessSettingDrawerRef\" @refresh=\"refresh\" />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { reactive, ref, onMounted, computed } from 'vue'\nimport AccessSettingDrawer from './component/AccessSettingDrawer.vue'\nimport { MsgSuccess } from '@/utils/message'\nimport { useRoute } from 'vue-router'\nimport { t } from '@/locales'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['application'][apiType.value]\n})\n\n// 平台数据\nconst platforms = reactive([\n  {\n    key: 'wecomBot',\n    logoSrc: new URL(`../../assets/logo/logo_wechat-bot.svg`, import.meta.url).href,\n    name: t('views.application.applicationAccess.wecomBot'),\n    description: t('views.application.applicationAccess.wecomBotTip'),\n    isActive: false,\n    exists: false,\n  },\n  {\n    key: 'wecom',\n    logoSrc: new URL(`../../assets/logo/logo_wechat-work.svg`, import.meta.url).href,\n    name: t('views.application.applicationAccess.wecom'),\n    description: t('views.application.applicationAccess.wecomTip'),\n    isActive: false,\n    exists: false,\n  },\n  {\n    key: 'dingtalk',\n    logoSrc: new URL(`../../assets/logo/logo_dingtalk.svg`, import.meta.url).href,\n    name: t('views.application.applicationAccess.dingtalk'),\n    description: t('views.application.applicationAccess.dingtalkTip'),\n    isActive: false,\n    exists: false,\n  },\n  {\n    key: 'wechat',\n    logoSrc: new URL(`../../assets/logo/logo_wechat.svg`, import.meta.url).href,\n    name: t('views.application.applicationAccess.wechat'),\n    description: t('views.application.applicationAccess.wechatTip'),\n    isActive: false,\n    exists: false,\n  },\n  {\n    key: 'lark',\n    logoSrc: new URL(`../../assets/logo/logo_lark.svg`, import.meta.url).href,\n    name: t('views.application.applicationAccess.lark'),\n    description: t('views.application.applicationAccess.larkTip'),\n    isActive: false,\n    exists: false,\n  },\n  {\n    key: 'slack',\n    logoSrc: new URL(`../../assets/logo/logo_slack.svg`, import.meta.url).href,\n    name: t('views.application.applicationAccess.slack'),\n    description: t('views.application.applicationAccess.slackTip'),\n    isActive: false,\n    exists: false,\n  },\n])\n\nconst AccessSettingDrawerRef = ref()\nconst loading = ref(false)\nconst {\n  params: { id },\n} = route as any\n\nfunction openDrawer(key: string) {\n  AccessSettingDrawerRef.value.open(id, key)\n}\n\nfunction refresh() {\n  getPlatformStatus()\n}\n\nfunction getPlatformStatus() {\n  loading.value = true\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getPlatformStatus(id)\n    .then((res: any) => {\n      platforms.forEach((platform) => {\n        platform.isActive = res.data[platform.key][1]\n        platform.exists = res.data[platform.key][0]\n      })\n      loading.value = false\n    })\n}\n\nfunction changeStatus(type: string, value: boolean) {\n  const data = {\n    type: type,\n    status: value,\n  }\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .updatePlatformStatus(id, data)\n    .then(() => {\n      MsgSuccess(t('common.saveSuccess'))\n    })\n}\n\nonMounted(() => {\n  getPlatformStatus()\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application/ApplicationSetting.vue",
    "content": "<template>\n  <div class=\"p-16-24 application-setting\">\n    <div class=\"flex-between w-full mb-16\">\n      <h3>\n        {{ $t('common.setting') }}\n      </h3>\n      <div>\n        <el-button\n          type=\"primary\"\n          @click=\"submit(applicationFormRef)\"\n          :disabled=\"loading\"\n          v-if=\"permissionPrecise.edit(id)\"\n        >\n          {{ $t('common.save') }}\n        </el-button>\n        <el-button\n          type=\"primary\"\n          @click=\"publish(applicationFormRef)\"\n          :disabled=\"loading\"\n          v-if=\"permissionPrecise.edit(id)\"\n        >\n          {{ $t('common.publish') }}\n        </el-button>\n      </div>\n    </div>\n\n    <el-card style=\"--el-card-padding: 0\">\n      <el-row v-loading=\"loading\">\n        <el-col :span=\"10\">\n          <div class=\"p-24 mb-16\" style=\"padding-bottom: 0\">\n            <h4 class=\"title-decoration-1\">\n              {{ $t('common.info') }}\n            </h4>\n          </div>\n          <div class=\"scrollbar-height-left\">\n            <el-scrollbar>\n              <el-form\n                hide-required-asterisk\n                ref=\"applicationFormRef\"\n                :model=\"applicationForm\"\n                :rules=\"rules\"\n                label-position=\"top\"\n                require-asterisk-position=\"right\"\n                class=\"p-24\"\n                style=\"padding-top: 0\"\n              >\n                <el-form-item prop=\"name\">\n                  <template #label>\n                    <div class=\"flex-between\">\n                      <span>{{ $t('common.name') }} <span class=\"color-danger\">*</span></span>\n                    </div>\n                  </template>\n                  <el-input\n                    v-model=\"applicationForm.name\"\n                    maxlength=\"64\"\n                    :placeholder=\"$t('views.application.form.appName.placeholder')\"\n                    show-word-limit\n                    @blur=\"applicationForm.name = applicationForm.name?.trim()\"\n                  />\n                </el-form-item>\n                <el-form-item :label=\"$t('common.desc')\">\n                  <el-input\n                    v-model=\"applicationForm.desc\"\n                    type=\"textarea\"\n                    :placeholder=\"$t('views.application.form.appDescription.placeholder')\"\n                    :rows=\"3\"\n                    maxlength=\"256\"\n                    show-word-limit\n                  />\n                </el-form-item>\n\n                <el-form-item :label=\"$t('views.application.form.aiModel.label')\">\n                  <template #label>\n                    <div class=\"flex-between\">\n                      <span>{{ $t('views.application.form.aiModel.label') }}</span>\n\n                      <el-button\n                        type=\"primary\"\n                        link\n                        @click=\"openAIParamSettingDialog\"\n                        :disabled=\"!applicationForm.model_id\"\n                      >\n                        <AppIcon iconName=\"app-setting\" class=\"mr-4\"></AppIcon>\n                        {{ $t('common.paramSetting') }}\n                      </el-button>\n                    </div>\n                  </template>\n                  <ModelSelect\n                    v-model=\"applicationForm.model_id\"\n                    :placeholder=\"$t('views.application.form.aiModel.placeholder')\"\n                    :options=\"modelOptions\"\n                    @change=\"model_change\"\n                    @submitModel=\"getSelectModel\"\n                    showFooter\n                    :model-type=\"'LLM'\"\n                  >\n                  </ModelSelect>\n                </el-form-item>\n                <el-form-item>\n                  <template #label>\n                    <div class=\"flex-between\">\n                      <div class=\"flex align-center\">\n                        <span>{{ $t('views.application.form.roleSettings.label') }}</span>\n                        <el-tooltip\n                          effect=\"dark\"\n                          :content=\"$t('views.application.form.roleSettings.tooltip')\"\n                          placement=\"right\"\n                        >\n                          <AppIcon iconName=\"app-warning\" class=\"app-warning-icon ml-4\"></AppIcon>\n                        </el-tooltip>\n                      </div>\n\n                      <el-button\n                        type=\"primary\"\n                        link\n                        @click=\"openGeneratePromptDialog\"\n                        :disabled=\"!applicationForm.model_id\"\n                      >\n                        <AppIcon iconName=\"app-generate-star\" class=\"mr-4\"></AppIcon>\n                        {{ $t('views.application.generateDialog.label') }}\n                      </el-button>\n                    </div>\n                  </template>\n                  <MdEditorMagnify\n                    :title=\"$t('views.application.form.roleSettings.label')\"\n                    v-model=\"applicationForm.model_setting.system\"\n                    style=\"height: 120px\"\n                    @submitDialog=\"submitSystemDialog\"\n                    :placeholder=\"\n                      $t('views.application.form.roleSettings.placeholder', {\n                        data: '{data}',\n                        question: '{question}',\n                      })\n                    \"\n                  />\n                </el-form-item>\n                <el-form-item\n                  prop=\"model_setting.no_references_prompt\"\n                  :rules=\"{\n                    required: applicationForm.model_id,\n                    message: $t('views.application.form.prompt.requiredMessage'),\n                    trigger: 'blur',\n                  }\"\n                >\n                  <template #label>\n                    <div class=\"flex align-center\">\n                      <span class=\"mr-4\"\n                        >{{\n                          $t('views.application.form.prompt.label') +\n                          $t('views.application.form.prompt.noReferences')\n                        }}\n                      </span>\n                      <el-tooltip\n                        effect=\"dark\"\n                        :content=\"$t('views.application.form.prompt.tooltip')\"\n                        placement=\"right\"\n                        popper-class=\"max-w-350\"\n                      >\n                        <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                      </el-tooltip>\n                      <span class=\"color-danger ml-4\" v-if=\"applicationForm.model_id\">*</span>\n                    </div>\n                  </template>\n\n                  <MdEditorMagnify\n                    :title=\"\n                      $t('views.application.form.prompt.label') +\n                      $t('views.application.form.prompt.noReferences')\n                    \"\n                    v-model=\"applicationForm.model_setting.no_references_prompt\"\n                    style=\"height: 120px\"\n                    @submitDialog=\"submitNoReferencesPromptDialog\"\n                    :placeholder=\"\n                      $t('views.application.form.prompt.placeholder', {\n                        data: '{data}',\n                        question: '{question}',\n                      })\n                    \"\n                  />\n                </el-form-item>\n                <el-form-item\n                  :label=\"$t('views.application.form.historyRecord.label')\"\n                  @click.prevent\n                >\n                  <el-input-number\n                    v-model=\"applicationForm.dialogue_number\"\n                    :min=\"0\"\n                    :value-on-clear=\"0\"\n                    controls-position=\"right\"\n                    class=\"w-full\"\n                    :step=\"1\"\n                    :step-strictly=\"true\"\n                  />\n                </el-form-item>\n\n                <p class=\"mb-12 lighter\">\n                  {{ $t('views.knowledge.title') }}\n                </p>\n\n                <!-- 知识库 -->\n                <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n                  <el-form-item\n                    :label=\"$t('views.application.form.prompt.label')\"\n                    prop=\"model_setting.prompt\"\n                    :rules=\"{\n                      required: applicationForm.model_id,\n                      message: $t('views.application.form.prompt.requiredMessage'),\n                      trigger: 'blur',\n                    }\"\n                  >\n                    <template #label>\n                      <div\n                        class=\"flex align-center cursor\"\n                        @click=\"collapseData.prompt = !collapseData.prompt\"\n                      >\n                        <el-icon\n                          class=\"mr-8 arrow-icon\"\n                          :class=\"collapseData.prompt ? 'rotate-90' : ''\"\n                        >\n                          <CaretRight />\n                        </el-icon>\n                        <span class=\"mr-4\">\n                          {{ $t('views.application.form.prompt.label') }}\n                          {{ $t('views.application.form.prompt.references') }}\n                        </span>\n                        <el-tooltip\n                          effect=\"dark\"\n                          :content=\"$t('views.application.form.prompt.tooltip')\"\n                          popper-class=\"max-w-350\"\n                          placement=\"right\"\n                        >\n                          <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                        </el-tooltip>\n                        <span class=\"color-danger ml-4\" v-if=\"applicationForm.model_id\">*</span>\n                      </div>\n                    </template>\n\n                    <MdEditorMagnify\n                      v-if=\"collapseData.prompt\"\n                      :title=\"\n                        $t('views.application.form.prompt.label') +\n                        $t('views.application.form.prompt.references')\n                      \"\n                      v-model=\"applicationForm.model_setting.prompt\"\n                      style=\"height: 150px\"\n                      @submitDialog=\"submitPromptDialog\"\n                      :placeholder=\"\n                        $t('views.application.form.prompt.placeholder', {\n                          data: '{data}',\n                          question: '{question}',\n                        })\n                      \"\n                    />\n                  </el-form-item>\n                  <div\n                    class=\"flex-between mb-12 cursor\"\n                    @click=\"collapseData.knowledge_setting = !collapseData.knowledge_setting\"\n                  >\n                    <div class=\"flex align-center\">\n                      <el-icon\n                        class=\"mr-8 arrow-icon\"\n                        :class=\"collapseData.knowledge_setting ? 'rotate-90' : ''\"\n                      >\n                        <CaretRight />\n                      </el-icon>\n                      <span class=\"lighter\">{{\n                        $t('views.application.form.relatedKnowledge.label')\n                      }}</span>\n                    </div>\n\n                    <div>\n                      <span class=\"mr-4\">\n                        <el-button type=\"primary\" link @click=\"openParamSettingDialog\">\n                          <AppIcon iconName=\"app-setting\"></AppIcon>\n                        </el-button>\n                      </span>\n\n                      <el-button type=\"primary\" link @click=\"openKnowledgeDialog\">\n                        <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n                      </el-button>\n                    </div>\n                  </div>\n                  <div class=\"w-full\" v-if=\"collapseData.knowledge_setting\">\n                    <el-text type=\"info\" v-if=\"applicationForm.knowledge_id_list?.length === 0\"\n                      >{{ $t('views.application.form.relatedKnowledge.placeholder') }}\n                    </el-text>\n                    <div v-else>\n                      <template\n                        v-for=\"(item, index) in applicationForm.knowledge_id_list\"\n                        :key=\"index\"\n                      >\n                        <div\n                          class=\"flex-between border border-r-6 white-bg mb-4\"\n                          style=\"padding: 5px 8px\"\n                        >\n                          <div class=\"flex align-center\" style=\"width: 80%\">\n                            <KnowledgeIcon\n                              :type=\"relatedObject(knowledgeList, item, 'id')?.type\"\n                              class=\"mr-8\"\n                              :size=\"20\"\n                            />\n\n                            <span\n                              class=\"ellipsis cursor\"\n                              :title=\"relatedObject(knowledgeList, item, 'id')?.name\"\n                            >\n                              {{ relatedObject(knowledgeList, item, 'id')?.name }}</span\n                            >\n                          </div>\n                          <el-button text @click=\"removeKnowledge(item)\">\n                            <el-icon><Close /></el-icon>\n                          </el-button>\n                        </div>\n                      </template>\n                    </div>\n                  </div>\n                </el-card>\n                <!-- 技能 -->\n                <div class=\"mb-8 mt-12 flex-between\">\n                  <span class=\"mr-4 lighter\">\n                    {{ $t('views.tool.skill.title') }}\n                  </span>\n                  <div class=\"flex\" v-if=\"toolPermissionPrecise.read()\">\n                    <el-checkbox\n                      v-model=\"applicationForm.mcp_output_enable\"\n                      :label=\"$t('views.application.form.mcp_output_enable')\"\n                    />\n                  </div>\n                </div>\n                <el-card shadow=\"never\" class=\"card-never mb-8\" style=\"--el-card-padding: 12px\">\n                  <!-- MCP-->\n                  <div v-if=\"toolPermissionPrecise.read()\">\n                    <div class=\"flex-between mb-8\" @click=\"collapseData.MCP = !collapseData.MCP\">\n                      <div class=\"flex align-center lighter cursor\">\n                        <el-icon\n                          class=\"mr-8 arrow-icon\"\n                          :class=\"collapseData.MCP ? 'rotate-90' : ''\"\n                        >\n                          <CaretRight /> </el-icon\n                        >MCP\n                        <span class=\"ml-4\" v-if=\"applicationForm.mcp_tool_ids?.length\">\n                          ({{ applicationForm.mcp_tool_ids?.length }})</span\n                        >\n                      </div>\n                      <div class=\"flex\">\n                        <el-button\n                          type=\"primary\"\n                          link\n                          @click=\"openMcpServersDialog\"\n                          @refreshForm=\"refreshParam\"\n                        >\n                          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                        </el-button>\n                      </div>\n                    </div>\n                    <div\n                      class=\"w-full mb-16\"\n                      v-if=\"\n                        applicationForm.mcp_tool_ids &&\n                        applicationForm.mcp_tool_ids.length > 0 &&\n                        toolPermissionPrecise.read() &&\n                        collapseData.MCP\n                      \"\n                    >\n                      <template v-for=\"(item, index) in applicationForm.mcp_tool_ids\" :key=\"index\">\n                        <div\n                          class=\"flex-between border border-r-6 white-bg mb-4\"\n                          style=\"padding: 5px 8px\"\n                          v-if=\"relatedObject(mcpToolSelectOptions, item, 'id')\"\n                        >\n                          <div class=\"flex align-center\" style=\"line-height: 20px\">\n                            <el-avatar\n                              v-if=\"relatedObject(mcpToolSelectOptions, item, 'id')?.icon\"\n                              shape=\"square\"\n                              :size=\"20\"\n                              style=\"background: none\"\n                              class=\"mr-8\"\n                            >\n                              <img\n                                :src=\"\n                                  resetUrl(relatedObject(mcpToolSelectOptions, item, 'id')?.icon)\n                                \"\n                                alt=\"\"\n                              />\n                            </el-avatar>\n                            <ToolIcon v-else type=\"MCP\" class=\"mr-8\" :size=\"20\" />\n\n                            <div\n                              class=\"ellipsis-1\"\n                              :title=\"relatedObject(mcpToolSelectOptions, item, 'id')?.name\"\n                            >\n                              {{\n                                relatedObject(mcpToolSelectOptions, item, 'id')?.name ||\n                                $t('common.custom') + ' MCP'\n                              }}\n                            </div>\n                          </div>\n                          <el-button text @click=\"removeMcpTool(item)\">\n                            <el-icon><Close /></el-icon>\n                          </el-button>\n                        </div>\n                      </template>\n                    </div>\n                    <div\n                      class=\"w-full mb-16\"\n                      v-if=\"\n                        applicationForm.mcp_servers &&\n                        applicationForm.mcp_servers.length > 0 &&\n                        toolPermissionPrecise.read() &&\n                        collapseData.MCP\n                      \"\n                    >\n                      <div\n                        class=\"flex-between border border-r-6 white-bg mb-4\"\n                        style=\"padding: 5px 8px\"\n                      >\n                        <div class=\"flex align-center\" style=\"line-height: 20px\">\n                          <ToolIcon type=\"MCP\" class=\"mr-8\" :size=\"20\" />\n                          <div class=\"ellipsis\">\n                            {{ $t('common.custom') + ' MCP' }}\n                          </div>\n                        </div>\n                        <el-button text @click=\"applicationForm.mcp_servers = ''\">\n                          <el-icon>\n                            <Close />\n                          </el-icon>\n                        </el-button>\n                      </div>\n                    </div>\n                  </div>\n\n                  <!-- 工具       -->\n                  <div v-if=\"toolPermissionPrecise.read()\">\n                    <div class=\"flex-between mb-8\" @click=\"collapseData.tool = !collapseData.tool\">\n                      <div class=\"flex align-center lighter cursor\">\n                        <el-icon\n                          class=\"mr-8 arrow-icon\"\n                          :class=\"collapseData.tool ? 'rotate-90' : ''\"\n                        >\n                          <CaretRight />\n                        </el-icon>\n                        {{ $t('views.tool.title') }}\n                        <span class=\"ml-4\" v-if=\"applicationForm.tool_ids?.length\">\n                          ({{ applicationForm.tool_ids?.length }})</span\n                        >\n                      </div>\n                      <div class=\"flex\">\n                        <el-button\n                          type=\"primary\"\n                          link\n                          @click=\"openToolDialog\"\n                          @refreshForm=\"refreshParam\"\n                        >\n                          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                        </el-button>\n                      </div>\n                    </div>\n                    <div\n                      class=\"w-full mb-16\"\n                      v-if=\"\n                        applicationForm.tool_ids &&\n                        applicationForm.tool_ids.length > 0 &&\n                        toolPermissionPrecise.read() &&\n                        collapseData.tool\n                      \"\n                    >\n                      <template v-for=\"(item, index) in applicationForm.tool_ids\" :key=\"index\">\n                        <div\n                          v-if=\"relatedObject(toolSelectOptions, item, 'id')\"\n                          class=\"flex-between border border-r-6 white-bg mb-4\"\n                          style=\"padding: 5px 8px\"\n                        >\n                          <div class=\"flex align-center\" style=\"line-height: 20px\">\n                            <el-avatar\n                              v-if=\"relatedObject(toolSelectOptions, item, 'id')?.icon\"\n                              shape=\"square\"\n                              :size=\"20\"\n                              style=\"background: none\"\n                              class=\"mr-8\"\n                            >\n                              <img\n                                :src=\"resetUrl(relatedObject(toolSelectOptions, item, 'id')?.icon)\"\n                                alt=\"\"\n                              />\n                            </el-avatar>\n                            <ToolIcon v-else class=\"mr-8\" :size=\"20\" />\n\n                            <div\n                              class=\"ellipsis-1\"\n                              :title=\"relatedObject(toolSelectOptions, item, 'id')?.name\"\n                            >\n                              {{ relatedObject(toolSelectOptions, item, 'id')?.name }}\n                            </div>\n                          </div>\n                          <el-button text @click=\"removeTool(item)\">\n                            <el-icon><Close /></el-icon>\n                          </el-button>\n                        </div>\n                      </template>\n                    </div>\n                  </div>\n\n                  <!-- 技能   -->\n                  <div v-if=\"toolPermissionPrecise.read()\">\n                    <div class=\"flex-between mb-8\" @click=\"collapseData.skill = !collapseData.skill\">\n                      <div class=\"flex align-center lighter cursor\">\n                        <el-icon\n                          class=\"mr-8 arrow-icon\"\n                          :class=\"collapseData.skill ? 'rotate-90' : ''\"\n                        >\n                          <CaretRight />\n                        </el-icon>\n                        Skills\n                        <span class=\"ml-4\" v-if=\"applicationForm.skill_tool_ids?.length\">\n                          ({{ applicationForm.skill_tool_ids?.length }})</span\n                        >\n                      </div>\n                      <div class=\"flex\">\n                        <el-button\n                          type=\"primary\"\n                          link\n                          @click=\"openSkillToolDialog\"\n                          @refreshForm=\"refreshParam\"\n                        >\n                          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                        </el-button>\n                      </div>\n                    </div>\n                    <div\n                      class=\"w-full mb-16\"\n                      v-if=\"\n                        applicationForm.skill_tool_ids &&\n                        applicationForm.skill_tool_ids.length > 0 &&\n                        toolPermissionPrecise.read() &&\n                        collapseData.skill\n                      \"\n                    >\n                      <template v-for=\"(item, index) in applicationForm.skill_tool_ids\" :key=\"index\">\n                        <div\n                          v-if=\"relatedObject(skillToolSelectOptions, item, 'id')\"\n                          class=\"flex-between border border-r-6 white-bg mb-4\"\n                          style=\"padding: 5px 8px\"\n                        >\n                          <div class=\"flex align-center\" style=\"line-height: 20px\">\n                            <el-avatar\n                              v-if=\"relatedObject(skillToolSelectOptions, item, 'id')?.icon\"\n                              shape=\"square\"\n                              :size=\"20\"\n                              style=\"background: none\"\n                              class=\"mr-8\"\n                            >\n                              <img\n                                :src=\"resetUrl(relatedObject(skillToolSelectOptions, item, 'id')?.icon)\"\n                                alt=\"\"\n                              />\n                            </el-avatar>\n                            <ToolIcon v-else class=\"mr-8\" :size=\"20\" type=\"SKILL\" />\n\n                            <div\n                              class=\"ellipsis-1\"\n                              :title=\"relatedObject(skillToolSelectOptions, item, 'id')?.name\"\n                            >\n                              {{ relatedObject(skillToolSelectOptions, item, 'id')?.name }}\n                            </div>\n                          </div>\n                          <el-button text @click=\"removeSkillTool(item)\">\n                            <el-icon><Close /></el-icon>\n                          </el-button>\n                        </div>\n                      </template>\n                    </div>\n                  </div>\n\n                  <!-- 应用       -->\n                  <div v-if=\"toolPermissionPrecise.read()\">\n                    <div\n                      class=\"flex-between mb-8\"\n                      @click=\"collapseData.agent = !collapseData.agent\"\n                    >\n                      <div class=\"flex align-center lighter cursor\">\n                        <el-icon\n                          class=\"mr-8 arrow-icon\"\n                          :class=\"collapseData.agent ? 'rotate-90' : ''\"\n                        >\n                          <CaretRight />\n                        </el-icon>\n                        {{ $t('views.application.title') }}\n                        <span class=\"ml-4\" v-if=\"applicationForm.application_ids?.length\">\n                          ({{ applicationForm.application_ids?.length }})</span\n                        >\n                      </div>\n                      <div class=\"flex\">\n                        <el-button\n                          type=\"primary\"\n                          link\n                          @click=\"openApplicationDialog\"\n                          @refreshForm=\"refreshParam\"\n                        >\n                          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                        </el-button>\n                      </div>\n                    </div>\n                    <div\n                      class=\"w-full mb-16\"\n                      v-if=\"\n                        applicationForm.application_ids &&\n                        applicationForm.application_ids.length > 0 &&\n                        toolPermissionPrecise.read() &&\n                        collapseData.agent\n                      \"\n                    >\n                      <template\n                        v-for=\"(item, index) in applicationForm.application_ids\"\n                        :key=\"index\"\n                      >\n                        <div\n                          v-if=\"relatedObject(applicationSelectOptions, item, 'id')\"\n                          class=\"flex-between border border-r-6 white-bg mb-4\"\n                          style=\"padding: 5px 8px\"\n                        >\n                          <div class=\"flex align-center\" style=\"line-height: 20px\">\n                            <el-avatar\n                              v-if=\"relatedObject(applicationSelectOptions, item, 'id')?.icon\"\n                              shape=\"square\"\n                              :size=\"20\"\n                              style=\"background: none\"\n                              class=\"mr-8\"\n                            >\n                              <img\n                                :src=\"\n                                  resetUrl(\n                                    relatedObject(applicationSelectOptions, item, 'id')?.icon,\n                                  )\n                                \"\n                                alt=\"\"\n                              />\n                            </el-avatar>\n                            <AppIcon v-else class=\"mr-8\" :size=\"20\" />\n\n                            <div\n                              class=\"ellipsis-1\"\n                              :title=\"relatedObject(applicationSelectOptions, item, 'id')?.name\"\n                            >\n                              {{ relatedObject(applicationSelectOptions, item, 'id')?.name }}\n                            </div>\n                          </div>\n                          <el-button text @click=\"removeApplication(item)\">\n                            <el-icon><Close /></el-icon>\n                          </el-button>\n                        </div>\n                      </template>\n                    </div>\n                  </div>\n                </el-card>\n                <!-- 开场白 -->\n                <el-form-item :label=\"$t('views.application.form.prologue')\">\n                  <MdEditorMagnify\n                    :title=\"$t('views.application.form.prologue')\"\n                    v-model=\"applicationForm.prologue\"\n                    style=\"height: 150px\"\n                    @submitDialog=\"submitPrologueDialog\"\n                  />\n                </el-form-item>\n\n                <el-form-item @click.prevent>\n                  <template #label>\n                    <div class=\"flex-between\">\n                      <span class=\"mr-4\">\n                        {{ $t('views.application.form.reasoningContent.label') }}\n                      </span>\n\n                      <div class=\"flex\">\n                        <el-button type=\"primary\" link @click=\"openReasoningParamSettingDialog\">\n                          <AppIcon iconName=\"app-setting\"></AppIcon>\n                        </el-button>\n                        <el-switch\n                          class=\"ml-8\"\n                          size=\"small\"\n                          v-model=\"applicationForm.model_setting.reasoning_content_enable\"\n                          @change=\"sttModelEnableChange\"\n                        />\n                      </div>\n                    </div>\n                  </template>\n                </el-form-item>\n\n                <el-form-item\n                  prop=\"stt_model_id\"\n                  :rules=\"{\n                    required: applicationForm.stt_model_enable,\n                    message: $t('views.application.form.voiceInput.requiredMessage'),\n                    trigger: 'change',\n                  }\"\n                >\n                  <template #label>\n                    <div class=\"flex-between\">\n                      <span class=\"mr-4\">\n                        {{ $t('views.application.form.voiceInput.label') }}\n                        <span class=\"color-danger\" v-if=\"applicationForm.stt_model_enable\">*</span>\n                      </span>\n\n                      <div class=\"flex\">\n                        <el-checkbox\n                          v-if=\"applicationForm.stt_model_enable\"\n                          v-model=\"applicationForm.stt_autosend\"\n                          >{{ $t('views.application.form.voiceInput.autoSend') }}</el-checkbox\n                        >\n                        <el-switch\n                          class=\"ml-8\"\n                          size=\"small\"\n                          v-model=\"applicationForm.stt_model_enable\"\n                          @change=\"sttModelEnableChange\"\n                        />\n                      </div>\n                    </div>\n                  </template>\n                  <div class=\"flex-between w-full\">\n                    <ModelSelect\n                      v-show=\"applicationForm.stt_model_enable\"\n                      v-model=\"applicationForm.stt_model_id\"\n                      :placeholder=\"$t('views.application.form.voiceInput.placeholder')\"\n                      :options=\"sttModelOptions\"\n                      @change=\"sttModelChange\"\n                      :model-type=\"'STT'\"\n                    >\n                    </ModelSelect>\n\n                    <el-button\n                      v-if=\"applicationForm.stt_model_enable\"\n                      @click=\"openSTTParamSettingDialog\"\n                      :disabled=\"!applicationForm.stt_model_id\"\n                      class=\"ml-8\"\n                    >\n                      <el-icon>\n                        <Operation />\n                      </el-icon>\n                    </el-button>\n                  </div>\n                </el-form-item>\n                <el-form-item\n                  prop=\"tts_model_id\"\n                  :rules=\"{\n                    required:\n                      applicationForm.tts_type === 'TTS' && applicationForm.tts_model_enable,\n                    message: $t('views.application.form.voicePlay.requiredMessage'),\n                    trigger: 'change',\n                  }\"\n                >\n                  <template #label>\n                    <div class=\"flex-between\">\n                      <span class=\"mr-4\"\n                        >{{ $t('views.application.form.voicePlay.label') }}\n                        <span\n                          class=\"color-danger\"\n                          v-if=\"\n                            applicationForm.tts_type === 'TTS' && applicationForm.tts_model_enable\n                          \"\n                          >*</span\n                        >\n                      </span>\n                      <div class=\"flex\">\n                        <el-checkbox\n                          v-if=\"applicationForm.tts_model_enable\"\n                          v-model=\"applicationForm.tts_autoplay\"\n                          >{{ $t('views.application.form.voicePlay.autoPlay') }}</el-checkbox\n                        >\n                        <el-switch\n                          class=\"ml-8\"\n                          size=\"small\"\n                          v-model=\"applicationForm.tts_model_enable\"\n                          @change=\"ttsModelEnableChange\"\n                        />\n                      </div>\n                    </div>\n                  </template>\n                  <div class=\"w-full\">\n                    <el-radio-group\n                      v-model=\"applicationForm.tts_type\"\n                      v-show=\"applicationForm.tts_model_enable\"\n                      class=\"mb-8\"\n                    >\n                      <el-radio value=\"BROWSER\">{{\n                        $t('views.application.form.voicePlay.browser')\n                      }}</el-radio>\n                      <el-radio value=\"TTS\">{{\n                        $t('views.application.form.voicePlay.tts')\n                      }}</el-radio>\n                    </el-radio-group>\n                  </div>\n                  <div class=\"flex-between w-full\">\n                    <ModelSelect\n                      v-if=\"applicationForm.tts_type === 'TTS' && applicationForm.tts_model_enable\"\n                      v-model=\"applicationForm.tts_model_id\"\n                      :placeholder=\"$t('views.application.form.voicePlay.placeholder')\"\n                      :options=\"ttsModelOptions\"\n                      @change=\"ttsModelChange()\"\n                      :model-type=\"'TTS'\"\n                    ></ModelSelect>\n\n                    <el-button\n                      v-if=\"applicationForm.tts_type === 'TTS'\"\n                      @click=\"openTTSParamSettingDialog\"\n                      :disabled=\"!applicationForm.tts_model_id\"\n                      class=\"ml-8\"\n                    >\n                      <el-icon>\n                        <Operation />\n                      </el-icon>\n                    </el-button>\n                  </div>\n                </el-form-item>\n\n\n              </el-form>\n            </el-scrollbar>\n          </div>\n        </el-col>\n\n        <!-- 预览 -->\n        <el-col :span=\"14\" class=\"p-24 border-l\">\n          <h4 class=\"title-decoration-1 mb-16\">\n            {{ $t('views.application.appTest') }}\n          </h4>\n          <div class=\"dialog-bg\">\n            <div class=\"scrollbar-height\">\n              <AiChat :applicationDetails=\"applicationForm\" :type=\"'debug-ai-chat'\"></AiChat>\n            </div>\n          </div>\n        </el-col>\n      </el-row>\n    </el-card>\n\n    <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshForm\" />\n    <GeneratePromptDialog @replace=\"replace\" ref=\"GeneratePromptDialogRef\" />\n    <TTSModeParamSettingDialog ref=\"TTSModeParamSettingDialogRef\" @refresh=\"refreshTTSForm\" />\n    <STTModeParamSettingDialog ref=\"STTModeParamSettingDialogRef\" @refresh=\"refreshSTTForm\" />\n    <ParamSettingDialog ref=\"ParamSettingDialogRef\" @refresh=\"refreshParam\" />\n    <AddKnowledgeDialog\n      ref=\"AddKnowledgeDialogRef\"\n      @addData=\"addKnowledge\"\n      :data=\"knowledgeList\"\n      :loading=\"knowledgeLoading\"\n    />\n    <ReasoningParamSettingDialog\n      ref=\"ReasoningParamSettingDialogRef\"\n      @refresh=\"submitReasoningDialog\"\n    />\n    <McpServersDialog ref=\"mcpServersDialogRef\" @refresh=\"submitMcpServersDialog\" />\n    <ToolDialog ref=\"toolDialogRef\" @refresh=\"submitToolDialog\" tool_type=\"CUSTOM\"/>\n    <ToolDialog ref=\"skillToolDialogRef\" @refresh=\"submitSkillToolDialog\" tool_type=\"SKILL\"/>\n    <ApplicationDialog ref=\"applicationDialogRef\" @refresh=\"submitApplicationDialog\" />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, onMounted, computed, onBeforeMount } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport { groupBy, set } from 'lodash'\nimport AIModeParamSettingDialog from './component/AIModeParamSettingDialog.vue'\nimport GeneratePromptDialog from './component/GeneratePromptDialog.vue'\nimport ParamSettingDialog from './component/ParamSettingDialog.vue'\nimport AddKnowledgeDialog from './component/AddKnowledgeDialog.vue'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport type { ApplicationFormType } from '@/api/type/application'\nimport { relatedObject } from '@/utils/array'\nimport { MsgSuccess, MsgWarning } from '@/utils/message'\nimport { t } from '@/locales'\nimport TTSModeParamSettingDialog from './component/TTSModeParamSettingDialog.vue'\nimport STTModeParamSettingDialog from './component/STTModelParamSettingDialog.vue'\nimport ReasoningParamSettingDialog from './component/ReasoningParamSettingDialog.vue'\nimport permissionMap from '@/permission'\nimport { EditionConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { resetUrl } from '@/utils/common'\nimport McpServersDialog from '@/views/application/component/McpServersDialog.vue'\nimport ToolDialog from '@/views/application/component/ToolDialog.vue'\nimport ApplicationDialog from '@/views/application/component/ApplicationDialog.vue'\nimport useStore from '@/stores'\nconst route = useRoute()\nconst router = useRouter()\nconst {\n  params: { id },\n} = route as any\nconst { user, folder } = useStore()\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['application'][apiType.value]\n})\nconst toolPermissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst defaultPrompt = t('views.application.form.prompt.defaultPrompt', {\n  data: '{data}',\n  question: '{question}',\n})\n\nconst optimizationPrompt =\n  t('views.application.dialog.defaultPrompt1', {\n    question: '{question}',\n  }) +\n  '<data></data>' +\n  t('views.application.dialog.defaultPrompt2')\n\nconst collapseData = reactive({\n  prompt: true,\n  knowledge_setting: true,\n  MCP: true,\n  tool: true,\n  skill: true,\n  agent: true,\n})\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\nconst ReasoningParamSettingDialogRef = ref<InstanceType<typeof ReasoningParamSettingDialog>>()\nconst TTSModeParamSettingDialogRef = ref<InstanceType<typeof TTSModeParamSettingDialog>>()\nconst STTModeParamSettingDialogRef = ref<InstanceType<typeof STTModeParamSettingDialog>>()\nconst ParamSettingDialogRef = ref<InstanceType<typeof ParamSettingDialog>>()\nconst GeneratePromptDialogRef = ref<InstanceType<typeof GeneratePromptDialog>>()\n\nconst applicationFormRef = ref<FormInstance>()\nconst AddKnowledgeDialogRef = ref()\n\nconst loading = ref(false)\nconst knowledgeLoading = ref(false)\n\nconst applicationForm = ref<ApplicationFormType>({\n  name: '',\n  desc: '',\n  model_id: '',\n  dialogue_number: 1,\n  prologue: t('views.application.form.defaultPrologue'),\n  knowledge_id_list: [],\n  knowledge_setting: {\n    top_n: 3,\n    similarity: 0.6,\n    max_paragraph_char_number: 5000,\n    search_mode: 'embedding',\n    no_references_setting: {\n      status: 'ai_questioning',\n      value: '{question}',\n    },\n  },\n  model_setting: {\n    prompt: defaultPrompt,\n    system: '',\n    no_references_prompt: '{question}',\n    reasoning_content_enable: false,\n  },\n  model_params_setting: {},\n  problem_optimization: false,\n  problem_optimization_prompt: optimizationPrompt,\n  stt_model_id: '',\n  tts_model_id: '',\n  stt_model_enable: false,\n  tts_model_enable: false,\n  tts_type: 'BROWSER',\n  type: 'SIMPLE',\n  application_enable: true,\n  application_ids: [],\n  mcp_enable: true,\n  mcp_tool_ids: [],\n  mcp_servers: '',\n  mcp_source: 'referencing',\n  tool_enable: true,\n  tool_ids: [],\n  skill_tool_ids: [],\n  mcp_output_enable: false,\n})\n\nconst rules = reactive<FormRules<ApplicationFormType>>({\n  name: [\n    {\n      required: true,\n      message: t('views.application.form.appName.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n})\nconst modelOptions = ref<any>(null)\nconst knowledgeList = ref<Array<any>>([])\nconst sttModelOptions = ref<any>(null)\nconst ttsModelOptions = ref<any>(null)\n\n\nfunction submitPrologueDialog(val: string) {\n  applicationForm.value.prologue = val\n}\nfunction submitPromptDialog(val: string) {\n  applicationForm.value.model_setting.prompt = val\n}\nfunction submitNoReferencesPromptDialog(val: string) {\n  applicationForm.value.model_setting.no_references_prompt = val\n}\nfunction submitSystemDialog(val: string) {\n  applicationForm.value.model_setting.system = val\n}\nfunction submitReasoningDialog(val: any) {\n  applicationForm.value.model_setting = {\n    ...applicationForm.value.model_setting,\n    ...val,\n  }\n}\nconst publish = (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  formEl.validate().then(() => {\n    return loadSharedApi({ type: 'application', systemType: apiType.value })\n      .putApplication(id, applicationForm.value, loading)\n      .then(() => {\n        return loadSharedApi({ type: 'application', systemType: apiType.value }).publish(\n          id,\n          {},\n          loading,\n        )\n      })\n      .then(() => {\n        MsgSuccess(t('views.application.tip.publishSuccess'))\n      })\n  })\n}\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      loadSharedApi({ type: 'application', systemType: apiType.value })\n        .putApplication(id, applicationForm.value, loading)\n        .then(() => {\n          MsgSuccess(t('common.saveSuccess'))\n        })\n    }\n  })\n}\nconst model_change = (model_id?: string) => {\n  applicationForm.value.model_id = model_id\n  if (model_id) {\n    AIModeParamSettingDialogRef.value?.reset_default(model_id, id)\n  } else {\n    refreshForm({})\n  }\n}\nconst openAIParamSettingDialog = () => {\n  if (applicationForm.value.model_id) {\n    AIModeParamSettingDialogRef.value?.open(\n      applicationForm.value.model_id,\n      id,\n      applicationForm.value.model_params_setting,\n    )\n  }\n}\n\nconst openGeneratePromptDialog = () => {\n  if (applicationForm.value.model_id) {\n    GeneratePromptDialogRef.value?.open(applicationForm.value.model_id, id)\n  }\n}\n\nconst replace = (v: any) => {\n  applicationForm.value.model_setting.system = v\n}\n\nconst openReasoningParamSettingDialog = () => {\n  ReasoningParamSettingDialogRef.value?.open(applicationForm.value.model_setting)\n}\n\nconst openTTSParamSettingDialog = () => {\n  if (applicationForm.value.tts_model_id) {\n    TTSModeParamSettingDialogRef.value?.open(\n      applicationForm.value.tts_model_id,\n      id,\n      applicationForm.value.tts_model_params_setting,\n    )\n  }\n}\n\nconst openSTTParamSettingDialog = () => {\n  if (applicationForm.value.stt_model_id) {\n    STTModeParamSettingDialogRef.value?.open(\n      applicationForm.value.stt_model_id,\n      id,\n      applicationForm.value.stt_model_params_setting,\n    )\n  }\n}\n\nconst openParamSettingDialog = () => {\n  ParamSettingDialogRef.value?.open(applicationForm.value)\n}\n\nfunction removeTool(id: any) {\n  if (applicationForm.value.tool_ids) {\n    applicationForm.value.tool_ids = applicationForm.value.tool_ids.filter((v: any) => v !== id)\n  }\n}\n\nfunction removeMcpTool(id: any) {\n  if (applicationForm.value.mcp_tool_ids) {\n    applicationForm.value.mcp_tool_ids = applicationForm.value.mcp_tool_ids.filter(\n      (v: any) => v !== id,\n    )\n  }\n}\n\nfunction removeSkillTool(id: any) {\n  if (applicationForm.value.skill_tool_ids) {\n    applicationForm.value.skill_tool_ids = applicationForm.value.skill_tool_ids.filter(\n      (v: any) => v !== id,\n    )\n  }\n}\n\nfunction removeApplication(id: any) {\n  if (applicationForm.value.application_ids) {\n    applicationForm.value.application_ids = applicationForm.value.application_ids.filter(\n      (v: any) => v !== id,\n    )\n  }\n}\n\nconst mcpServersDialogRef = ref()\nfunction openMcpServersDialog() {\n  const config = {\n    mcp_servers: applicationForm.value.mcp_servers,\n    mcp_tool_ids: applicationForm.value.mcp_tool_ids,\n    mcp_source: applicationForm.value.mcp_source,\n  }\n  mcpServersDialogRef.value.open(config, mcpToolSelectOptions.value)\n}\n\nfunction submitMcpServersDialog(config: any) {\n  applicationForm.value.mcp_servers = config.mcp_servers\n  applicationForm.value.mcp_tool_ids = config.mcp_tool_ids\n  applicationForm.value.mcp_source = config.mcp_source\n  collapseData.MCP = true\n}\n\nconst toolDialogRef = ref()\nfunction openToolDialog() {\n  toolDialogRef.value.open(applicationForm.value.tool_ids)\n}\n\nfunction submitToolDialog(config: any) {\n  applicationForm.value.tool_ids = config.tool_ids\n  collapseData.tool = true\n}\n\nconst applicationDialogRef = ref()\nfunction openApplicationDialog() {\n  applicationDialogRef.value.open(applicationForm.value.application_ids)\n}\n\nfunction submitApplicationDialog(config: any) {\n  applicationForm.value.application_ids = config.application_ids\n  collapseData.agent = true\n}\n\nconst applicationSelectOptions = ref<any[]>([])\nfunction getApplicationSelectOptions() {\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getAllApplication({ folder_id: folder.currentFolder?.id || user.getWorkspaceId() })\n    .then((res: any) => {\n      applicationSelectOptions.value = res.data.filter((item: any) => item.is_publish)\n    })\n}\n\nconst toolSelectOptions = ref<any[]>([])\nfunction getToolSelectOptions() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          scope: 'WORKSPACE',\n          tool_type: 'CUSTOM',\n          workspace_id: applicationForm.value?.workspace_id,\n        }\n      : {\n          scope: 'WORKSPACE',\n          tool_type: 'CUSTOM',\n        }\n\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getAllToolList(obj)\n    .then((res: any) => {\n      toolSelectOptions.value = [...res.data.shared_tools, ...res.data.tools].filter(\n        (item: any) => item.is_active,\n      )\n    })\n}\n\nconst mcpToolSelectOptions = ref<any[]>([])\nfunction getMcpToolSelectOptions() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          scope: 'WORKSPACE',\n          tool_type: 'MCP',\n          workspace_id: applicationForm.value?.workspace_id,\n        }\n      : {\n          scope: 'WORKSPACE',\n          tool_type: 'MCP',\n        }\n\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getAllToolList(obj)\n    .then((res: any) => {\n      mcpToolSelectOptions.value = [...res.data.shared_tools, ...res.data.tools].filter(\n        (item: any) => item.is_active,\n      )\n    })\n}\n\nconst skillToolDialogRef = ref()\nfunction openSkillToolDialog() {\n  skillToolDialogRef.value.open(applicationForm.value.skill_tool_ids)\n}\n\nfunction submitSkillToolDialog(config: any) {\n  applicationForm.value.skill_tool_ids = config.tool_ids\n  collapseData.skill = true\n}\n\nconst skillToolSelectOptions = ref<any[]>([])\nfunction getSkillToolSelectOptions() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          scope: 'WORKSPACE',\n          tool_type: 'SKILL',\n          workspace_id: applicationForm.value?.workspace_id,\n        }\n      : {\n          scope: 'WORKSPACE',\n          tool_type: 'SKILL',\n        }\n\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getAllToolList(obj)\n    .then((res: any) => {\n      skillToolSelectOptions.value = [...res.data.shared_tools, ...res.data.tools].filter(\n        (item: any) => item.is_active,\n      )\n    })\n}\n\nfunction refreshParam(data: any) {\n  applicationForm.value = { ...applicationForm.value, ...data }\n}\n\nfunction refreshForm(data: any) {\n  applicationForm.value.model_params_setting = data\n}\n\nfunction refreshTTSForm(data: any) {\n  applicationForm.value.tts_model_params_setting = data\n}\n\nfunction refreshSTTForm(data: any) {\n  applicationForm.value.stt_model_params_setting = data\n}\n\nfunction removeKnowledge(id: any) {\n  if (applicationForm.value.knowledge_id_list) {\n    applicationForm.value.knowledge_id_list.splice(\n      applicationForm.value.knowledge_id_list.indexOf(id),\n      1,\n    )\n  }\n}\n\nfunction addKnowledge(val: Array<any>) {\n  knowledgeList.value = val\n  applicationForm.value.knowledge_id_list = val.map((item) => item.id)\n}\n\nfunction openKnowledgeDialog() {\n  AddKnowledgeDialogRef.value.open(applicationForm.value.knowledge_id_list)\n}\n\nfunction getDetail() {\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getApplicationDetail(id, loading)\n    .then((res: any) => {\n      applicationForm.value = res.data\n      applicationForm.value.model_id = res.data.model\n      applicationForm.value.stt_model_id = res.data.stt_model\n      applicationForm.value.tts_model_id = res.data.tts_model\n      applicationForm.value.tts_type = res.data.tts_type\n      knowledgeList.value = res.data.knowledge_list\n      applicationForm.value.model_setting.no_references_prompt =\n        res.data.model_setting.no_references_prompt || '{question}'\n\n      // 企业版和专业版\n      if (hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')) {\n        loadSharedApi({ type: 'application', systemType: apiType.value })\n          .getApplicationSetting(id)\n          .then((ok: any) => {\n            applicationForm.value = { ...applicationForm.value, ...ok.data }\n          })\n      }\n    })\n}\n\nfunction getSelectModel() {\n  loading.value = true\n\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'LLM',\n          workspace_id: applicationForm.value?.workspace_id,\n        }\n      : {\n          model_type: 'LLM',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n      loading.value = false\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nfunction getSTTModel() {\n  loading.value = true\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'STT',\n          workspace_id: applicationForm.value?.workspace_id,\n        }\n      : {\n          model_type: 'STT',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      sttModelOptions.value = groupBy(res?.data, 'provider')\n      loading.value = false\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nfunction getTTSModel() {\n  loading.value = true\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'TTS',\n          workspace_id: applicationForm.value?.workspace_id,\n        }\n      : {\n          model_type: 'TTS',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      ttsModelOptions.value = groupBy(res?.data, 'provider')\n      loading.value = false\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nfunction ttsModelChange() {\n  if (applicationForm.value.tts_model_id) {\n    TTSModeParamSettingDialogRef.value?.reset_default(applicationForm.value.tts_model_id, id)\n  } else {\n    refreshTTSForm({})\n  }\n}\n\nfunction sttModelChange() {\n  if (applicationForm.value.stt_model_id) {\n    STTModeParamSettingDialogRef.value?.reset_default(applicationForm.value.stt_model_id, id)\n  } else {\n    refreshSTTForm({})\n  }\n}\n\nfunction ttsModelEnableChange() {\n  if (!applicationForm.value.tts_model_enable) {\n    applicationForm.value.tts_model_id = undefined\n    applicationForm.value.tts_type = 'BROWSER'\n  }\n}\n\nfunction sttModelEnableChange() {\n  if (!applicationForm.value.stt_model_enable) {\n    applicationForm.value.stt_model_id = undefined\n  }\n}\nonBeforeMount(() => {\n  if (route.path.includes('WORK_FLOW')) {\n    if (apiType.value == 'workspace') {\n      router.push(`/application/workspace/${route.params.id}/workflow`)\n    } else {\n      router.push(`/application/resource-management/${route.params.id}/workflow`)\n    }\n  }\n})\nonMounted(() => {\n  getSelectModel()\n  getDetail()\n  getSTTModel()\n  getTTSModel()\n  if (toolPermissionPrecise.value.read()) {\n    getToolSelectOptions()\n    getMcpToolSelectOptions()\n    getApplicationSelectOptions()\n    getSkillToolSelectOptions()\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.application-setting {\n  .relate-knowledge-card {\n    color: var(--app-text-color);\n  }\n\n  .dialog-bg {\n    border-radius: 8px;\n    background: var(--dialog-bg-gradient-color);\n    overflow: hidden;\n    box-sizing: border-box;\n  }\n\n  .scrollbar-height-left {\n    height: calc(var(--app-main-height) - 64px);\n  }\n\n  .scrollbar-height {\n    padding-top: 16px;\n    height: calc(var(--app-main-height) - 96px);\n  }\n}\n\n.prologue-md-editor {\n  height: 150px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application/component/AIModeParamSettingDialog.vue",
    "content": "<template>\n  <el-dialog\n    align-center\n    :title=\"$t('common.paramSetting')\"\n    v-model=\"dialogVisible\"\n    style=\"width: 550px\"\n    append-to-body\n    destroy-on-close\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <DynamicsForm\n      v-model=\"form_data\"\n      :model=\"form_data\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      :render_data=\"model_form_field\"\n      ref=\"dynamicsFormRef\"\n    >\n    </DynamicsForm>\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\">\n          {{ $t('common.cancel') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"submit\" :loading=\"loading\">\n          {{ $t('common.confirm') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, nextTick, ref } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { useRoute } from 'vue-router'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst model_form_field = ref<Array<FormField>>([])\nconst emit = defineEmits(['refresh'])\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst form_data = ref<any>({})\nconst dialogVisible = ref(false)\nconst loading = ref(false)\nconst getApi = (model_id: string, application_id?: string) => {\n  return loadSharedApi({ type: 'model', systemType: apiType.value }).getModelParamsForm(\n    model_id,\n    loading,\n  )\n}\n\nconst modelID = ref('')\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['model'][apiType.value]\n})\n\nconst open = (\n  model_id: string,\n  application_id?: string,\n  model_setting_data?: any,\n  model_form_field_list?: Array<any>,\n) => {\n  dialogVisible.value = true\n\n  modelID.value = model_id\n  form_data.value = {}\n\n  if (model_form_field_list) {\n    model_form_field.value = model_form_field_list\n    // 渲染动态表单\n    nextTick(() => {\n      dynamicsFormRef.value?.render(model_form_field.value, model_setting_data)\n    })\n  } else {\n    const api = getApi(model_id, application_id)\n    api.then((ok: any) => {\n      model_form_field.value = ok.data\n      // 渲染动态表单\n      dynamicsFormRef.value?.render(model_form_field.value, model_setting_data)\n    })\n  }\n}\n\nconst reset_default = (model_id: string, application_id?: string) => {\n  const api = getApi(model_id, application_id)\n  api.then((ok: any) => {\n    model_form_field.value = ok.data\n    const model_setting_data = ok.data\n      .map((item: any) => {\n        if (item.show_default_value === false) {\n          return { [item.field]: undefined }\n        } else {\n          return { [item.field]: item.default_value }\n        }\n      })\n      .reduce((x: any, y: any) => ({ ...x, ...y }), {})\n\n    emit('refresh', model_setting_data)\n  })\n}\n\nconst submit = async () => {\n  dynamicsFormRef.value?.validate().then(() => {\n    emit('refresh', form_data.value)\n    dialogVisible.value = false\n  })\n}\n\ndefineExpose({ open, reset_default })\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application/component/AccessSettingDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"60%\" :append-to-body=\"true\">\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <h4>{{ drawerTitle }}</h4>\n      </div>\n    </template>\n\n    <el-form\n      v-if=\"dataLoaded\"\n      ref=\"formRef\"\n      :model=\"form[configType]\"\n      label-width=\"120px\"\n      :rules=\"rules[configType]\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n    >\n      <h4 class=\"title-decoration-1 mb-16\">{{ infoTitle }}</h4>\n\n      <template v-for=\"(item, key) in configFields[configType]\" :key=\"key\">\n        <el-form-item :label=\"item.label\" :prop=\"key\">\n          <el-input\n            v-model=\"form[configType][key]\"\n            :type=\"isPasswordField(key) ? (passwordVisible[key] ? 'text' : 'password') : 'text'\"\n            :placeholder=\"item.placeholder\"\n            :show-password=\"isPasswordField(key)\"\n          >\n          </el-input>\n        </el-form-item>\n      </template>\n      <div v-if=\"configType === 'wechat'\" class=\"flex align-center mb-16\">\n        <span class=\"lighter mr-8\">{{\n          $t('views.application.applicationAccess.wecomSetting.authenticationSuccessful')\n        }}</span>\n        <el-switch v-if=\"configType === 'wechat'\" v-model=\"form[configType].is_certification\" />\n      </div>\n\n      <h4 class=\"title-decoration-1 mb-16\">\n        {{ $t('views.application.applicationAccess.callback') }}\n      </h4>\n      <el-form-item label=\"URL\" prop=\"callback_url\">\n        <el-input\n          v-model=\"form[configType].callback_url\"\n          :placeholder=\"$t('views.application.applicationAccess.callbackTip')\"\n          readonly\n        >\n          <template #append>\n            <el-button @click=\"copyClick(form[configType].callback_url)\">\n              <AppIcon iconName=\"app-copy\"></AppIcon>\n            </el-button>\n          </template>\n        </el-input>\n        <el-text type=\"info\" v-if=\"configType === 'wechat'\">\n          {{ $t('views.application.applicationAccess.copyUrl') }}\n          <a\n            class=\"color-primary\"\n            href=\"https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev\"\n            target=\"_blank\"\n            >{{ $t('views.application.applicationAccess.wechatPlatform') }}</a\n          >{{ $t('views.application.applicationAccess.wechatSetting.urlInfo') }}\n        </el-text>\n        <el-text type=\"info\" v-if=\"configType === 'dingtalk'\">\n          {{ $t('views.application.applicationAccess.copyUrl') }}\n          <a\n            class=\"color-primary\"\n            href=\"https://open-dev.dingtalk.com/fe/app?hash=%23%2Fcorp%2Fapp#/corp/app\"\n            target=\"_blank\"\n            >{{ $t('views.application.applicationAccess.dingtalkPlatform') }}</a\n          >{{ $t('views.application.applicationAccess.dingtalkSetting.urlInfo') }}\n        </el-text>\n        <el-text type=\"info\" v-if=\"configType === 'wecom'\">\n          {{ $t('views.application.applicationAccess.copyUrl') }}\n          <a\n            class=\"color-primary\"\n            href=\"https://work.weixin.qq.com/wework_admin/frame#apps\"\n            target=\"_blank\"\n            >{{ $t('views.application.applicationAccess.wecomPlatform') }}</a\n          >{{ $t('views.application.applicationAccess.wecomSetting.urlInfo') }}\n        </el-text>\n        <el-text type=\"info\" v-if=\"configType === 'lark'\">\n          {{ $t('views.application.applicationAccess.copyUrl') }}\n          <a class=\"color-primary\" href=\"https://open.feishu.cn/app/\" target=\"_blank\">{{\n            $t('views.application.applicationAccess.larkPlatform')\n          }}</a\n          >{{ $t('views.application.applicationAccess.larkSetting.urlInfo') }}\n        </el-text>\n        <el-text type=\"info\" v-if=\"configType === 'wecomBot'\">\n          {{ $t('views.application.applicationAccess.copyUrl') }}\n          <a\n            class=\"color-primary\"\n            href=\"https://work.weixin.qq.com/wework_admin/frame#/manageTools\"\n            target=\"_blank\"\n            >{{ $t('views.application.applicationAccess.wecomPlatform') }}</a\n          >{{ $t('views.application.applicationAccess.wecomBotSetting.urlInfo') }}\n        </el-text>\n      </el-form-item>\n    </el-form>\n\n    <template #footer>\n      <div>\n        <el-button @click=\"closeDrawer\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit\" :disabled=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </div>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive, computed } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { useRoute } from 'vue-router'\nimport { MsgError, MsgSuccess } from '@/utils/message'\nimport { copyClick } from '@/utils/clipboard'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\ntype PlatformType = 'wechat' | 'dingtalk' | 'wecom' | 'lark' | 'slack' | 'wecomBot'\n\nconst route = useRoute()\n\nconst {\n  params: { id },\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst emit = defineEmits(['refresh'])\nconst formRef = ref<FormInstance>()\nconst visible = ref(false)\nconst loading = ref(false)\nconst dataLoaded = ref(false)\nconst configType = ref<PlatformType>('wechat')\n\nconst form = reactive<any>({\n  wechat: {\n    app_id: '',\n    app_secret: '',\n    token: '',\n    encoding_aes_key: '',\n    is_certification: false,\n    callback_url: '',\n  },\n  dingtalk: { client_id: '', client_secret: '', callback_url: '' },\n  wecom: {\n    app_id: '',\n    agent_id: '',\n    secret: '',\n    token: '',\n    encoding_aes_key: '',\n    callback_url: '',\n  },\n  lark: { app_id: '', app_secret: '', verification_token: '', callback_url: '' },\n  slack: { signing_secret: '', bot_user_token: '', callback_url: '' },\n  wecomBot: {\n    token: '',\n    encoding_aes_key: '',\n    callback_url: '',\n  },\n})\n\nconst rules = reactive<{ [propName: string]: any }>({\n  wechat: {\n    app_id: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wechatSetting.appIdPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    app_secret: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wechatSetting.appSecretPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    token: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wechatSetting.tokenPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    encoding_aes_key: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wechatSetting.aesKeyPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n  },\n  dingtalk: {\n    client_id: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.dingtalkSetting.clientIdPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    client_secret: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.dingtalkSetting.clientSecretPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n  },\n  wecom: {\n    app_id: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wecomSetting.cropIdPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    agent_id: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wecomSetting.agentIdPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    secret: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wecomSetting.secretPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    token: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wecomSetting.tokenPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    encoding_aes_key: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wecomSetting.encodingAesKeyPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n  },\n  lark: {\n    app_id: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.larkSetting.appIdPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    app_secret: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.larkSetting.appSecretPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    verification_token: [\n      {\n        required: false,\n        message: t('views.application.applicationAccess.larkSetting.verificationTokenPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n  },\n  slack: {\n    signing_secret: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.slackSetting.signingSecretPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    bot_user_token: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.slackSetting.botUserTokenPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n  },\n  wecomBot: {\n    token: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wecomSetting.tokenPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n    encoding_aes_key: [\n      {\n        required: true,\n        message: t('views.application.applicationAccess.wecomSetting.encodingAesKeyPlaceholder'),\n        trigger: 'blur',\n      },\n    ],\n  },\n})\n\nconst configFields: { [propName: string]: { [propName: string]: any } } = {\n  wechat: {\n    app_id: {\n      label: t('views.application.applicationAccess.wechatSetting.appId'),\n      placeholder: '',\n    },\n    app_secret: {\n      label: t('views.application.applicationAccess.wechatSetting.appSecret'),\n      placeholder: '',\n    },\n    token: { label: t('views.application.applicationAccess.wechatSetting.token'), placeholder: '' },\n    encoding_aes_key: {\n      label: t('views.application.applicationAccess.wechatSetting.aesKey'),\n      placeholder: '',\n    },\n  },\n  dingtalk: {\n    client_id: { label: 'Client ID', placeholder: '' },\n    client_secret: { label: 'Client Secret', placeholder: '' },\n  },\n  wecom: {\n    app_id: {\n      label: t('views.application.applicationAccess.wecomSetting.cropId'),\n      placeholder: '',\n    },\n    agent_id: { label: 'Agent ID', placeholder: '' },\n    secret: { label: 'Secret', placeholder: '' },\n    token: { label: 'Token', placeholder: '' },\n    encoding_aes_key: { label: 'EncodingAESKey', placeholder: '' },\n  },\n  wecomBot: {\n    token: { label: 'Token', placeholder: '' },\n    encoding_aes_key: { label: 'EncodingAESKey', placeholder: '' },\n  },\n  lark: {\n    app_id: { label: 'App ID', placeholder: '' },\n    app_secret: { label: 'App Secret', placeholder: '' },\n    verification_token: { label: 'Verification Token', placeholder: '' },\n  },\n  slack: {\n    signing_secret: { label: 'Signing Secret', placeholder: '' },\n    bot_user_token: { label: 'Bot User Token', placeholder: '' },\n  },\n}\n\nconst passwordFields = new Set([\n  'app_secret',\n  'client_secret',\n  'secret',\n  'bot_user_token',\n  'signing_secret',\n])\n\nconst drawerTitle = computed(\n  () =>\n    ({\n      wechat: t('views.application.applicationAccess.wechatSetting.title'),\n      dingtalk: t('views.application.applicationAccess.dingtalkSetting.title'),\n      wecom: t('views.application.applicationAccess.wecomSetting.title'),\n      lark: t('views.application.applicationAccess.larkSetting.title'),\n      slack: t('views.application.applicationAccess.slackSetting.title'),\n      wecomBot: t('views.application.applicationAccess.wecomBotSetting.title'),\n    })[configType.value],\n)\n\nconst infoTitle = computed(\n  () =>\n    ({\n      wechat: t('common.info'),\n      dingtalk: t('common.info'),\n      wecom: t('common.info'),\n      lark: t('common.info'),\n      slack: t('common.info'),\n      wecomBot: t('common.info'),\n    })[configType.value],\n)\n\nconst passwordVisible = reactive<Record<string, boolean>>(\n  Object.keys(configFields[configType.value]).reduce(\n    (acc, key) => {\n      if (passwordFields.has(key)) {\n        acc[key] = false\n      }\n      return acc\n    },\n    {} as Record<string, boolean>,\n  ),\n)\n\nconst isPasswordField = (key: any) => passwordFields.has(key)\n\nconst closeDrawer = () => {\n  visible.value = false\n}\n\nconst submit = async () => {\n  if (loading.value) return\n\n  formRef.value?.validate(async (valid) => {\n    if (valid) {\n      try {\n        loadSharedApi({ type: 'application', systemType: apiType.value })\n          .updatePlatformConfig(id, configType.value, form[configType.value], loading)\n          .then(() => {\n            MsgSuccess(t('common.saveSuccess'))\n            closeDrawer()\n            emit('refresh')\n          })\n      } catch {\n        MsgError(t('views.application.tip.saveErrorMessage'))\n      }\n    }\n  })\n}\n\nconst open = async (id: string, type: PlatformType) => {\n  visible.value = true\n  configType.value = type\n  loading.value = true\n  dataLoaded.value = false\n  formRef.value?.resetFields()\n  try {\n    const res = await loadSharedApi({\n      type: 'application',\n      systemType: apiType.value,\n    }).getPlatformConfig(id, type)\n    if (res.data) {\n      form[configType.value] = res.data\n    }\n    dataLoaded.value = true\n  } catch {\n    MsgError(t('views.application.tip.loadingErrorMessage'))\n  } finally {\n    loading.value = false\n    form[configType.value].callback_url =\n      `${window.location.origin}${window.MaxKB.prefix}/api/chat/${type}/${id}`\n  }\n}\n\ndefineExpose({ open })\n</script>\n"
  },
  {
    "path": "ui/src/views/application/component/AddKnowledgeDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.application.dialog.addKnowledge')\"\n    v-model=\"dialogVisible\"\n    width=\"1000\"\n    append-to-body\n    class=\"addKnowledge-dialog\"\n    align-center\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <template #header=\"{ titleId, titleClass }\">\n      <div class=\"flex-between mb-8\">\n        <div class=\"flex\">\n          <h4 :id=\"titleId\" :class=\"titleClass\" class=\"mr-8\">\n            {{ $t('views.application.dialog.addKnowledge') }}\n          </h4>\n          <el-text type=\"info\">\n            {{ $t('views.application.dialog.addKnowledgePlaceholder') }}\n          </el-text>\n        </div>\n\n        <el-button link class=\"mr-24\" @click=\"refresh\">\n          <el-icon :size=\"18\"><Refresh /></el-icon>\n        </el-button>\n      </div>\n    </template>\n    <LayoutContainer class=\"application-manage\">\n      <template #left>\n        <folder-tree\n          :data=\"folderList\"\n          :currentNodeKey=\"currentFolder?.id\"\n          @handleNodeClick=\"folderClickHandle\"\n          v-loading=\"folderLoading\"\n          :canOperation=\"false\"\n          showShared\n          :shareTitle=\"$t('views.shared.shared_knowledge')\"\n          :treeStyle=\"{ height: 'calc(100vh - 240px)' }\"\n        />\n      </template>\n      <div class=\"layout-bg\">\n        <div class=\"flex-between p-16 ml-8\">\n          <h4>{{ currentFolder?.name }}</h4>\n          <el-input\n            v-model=\"searchValue\"\n            :placeholder=\"$t('common.search')\"\n            prefix-icon=\"Search\"\n            class=\"w-240 mr-8\"\n            clearable\n          />\n        </div>\n\n        <el-scrollbar>\n          <div class=\"p-16-24 pt-0\" style=\"height: calc(100vh - 200px)\">\n            <el-row :gutter=\"12\" v-loading=\"loading || apiLoading\" v-if=\"filterData.length\">\n              <el-col\n                :span=\"12\"\n                v-for=\"(item, index) in filterData.filter((v: any) => v.resource_type !== 'folder')\"\n                :key=\"index\"\n                class=\"mb-16\"\n              >\n                <el-popover\n                  placement=\"bottom-start\"\n                  :width=\"400\"\n                  popper-style=\"--el-popover-border-radius:8px;--el-popover-padding:16px 16px 0\"\n                  :persistent=\"false\"\n                  :show-after=\"500\"\n                >\n                  <template #reference>\n                    <CardCheckbox\n                      value-field=\"id\"\n                      :data=\"item\"\n                      v-model=\"checkList\"\n                      @change=\"changeHandle\"\n                    >\n                      <span class=\"ellipsis cursor ml-12\" :title=\"item.name\"> {{ item.name }}</span>\n                    </CardCheckbox>\n                  </template>\n                  <template #default>\n                    <CardBox\n                      :title=\"item.name\"\n                      :description=\"item.desc\"\n                      class=\"cursor border-none popover-card-box\"\n                      shadow=\"never\"\n                      style=\"--el-card-padding: 0px; --card-min-height: 148px\"\n                    >\n                      <template #icon>\n                        <KnowledgeIcon :type=\"item.type\" />\n                      </template>\n                      <template #subTitle>\n                        <el-text class=\"color-secondary lighter flex align-center\" size=\"small\">\n                          <span\n                            :title=\"i18n_name(item.nick_name)\"\n                            class=\"ellipsis\"\n                            style=\"max-width: 90px\"\n                          >\n                            {{ i18n_name(item.nick_name) }}\n                          </span>\n                          <span class=\"ml-4 mr-4\"> {{ $t('common.createdIn') }}</span>\n                          <span> {{ dateFormat(item.create_time) }}</span>\n                        </el-text>\n                      </template>\n                      <template #footer>\n                        <div class=\"footer-content flex-between\">\n                          <div>\n                            <span class=\"bold mr-4\">{{ item?.document_count || 0 }}</span>\n                            <span class=\"color-secondary\">{{\n                              $t('views.knowledge.document_count')\n                            }}</span>\n                            <el-divider direction=\"vertical\" />\n                            <span class=\"bold mr-4\">{{\n                              numberFormat(item?.char_length) || 0\n                            }}</span>\n                            <span class=\"color-secondary\">{{ $t('common.character') }}</span>\n                          </div>\n                        </div>\n                      </template>\n                    </CardBox>\n                  </template>\n                </el-popover>\n              </el-col>\n            </el-row>\n            <el-empty :description=\"$t('common.noData')\" v-else />\n          </div>\n        </el-scrollbar>\n      </div>\n    </LayoutContainer>\n\n    <template #footer>\n      <div class=\"flex-between\">\n        <div class=\"flex\">\n          <el-text class=\"color-secondary mr-8\" v-if=\"checkList.length > 0\">\n            {{ $t('common.selected') }} {{ checkList.length }}\n          </el-text>\n          <el-button link type=\"primary\" v-if=\"checkList.length > 0\" @click=\"clearCheck\">\n            {{ $t('common.clear') }}\n          </el-button>\n        </div>\n        <span>\n          <el-button @click.prevent=\"dialogVisible = false\">\n            {{ $t('common.cancel') }}\n          </el-button>\n          <el-button type=\"primary\" @click=\"submitHandle\">\n            {{ $t('common.add') }}\n          </el-button>\n        </span>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { uniqueArray } from '@/utils/array'\nimport { numberFormat, i18n_name } from '@/utils/common'\nimport { dateFormat } from '@/utils/time'\nconst route = useRoute()\nconst props = defineProps({\n  data: {\n    type: Array<any>,\n    default: () => [],\n  },\n  loading: Boolean,\n})\n\nconst emit = defineEmits(['addData'])\nconst { folder, user } = useStore()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst checkList = ref<Array<string>>([])\nconst currentEmbedding = ref('')\nconst searchValue = ref('')\nconst searchData = ref<Array<any>>([])\nconst knowledgeList = ref<Array<any>>([])\nconst apiLoading = ref(false)\n\nconst filterData = computed(() => {\n  return currentEmbedding.value\n    ? searchData.value.filter((v) => v.embedding_model_id === currentEmbedding.value)\n    : searchData.value\n})\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    checkList.value = []\n    currentEmbedding.value = ''\n    searchValue.value = ''\n    searchData.value = []\n    knowledgeList.value = []\n  }\n})\n\nwatch(searchValue, (val) => {\n  if (val) {\n    searchData.value = knowledgeList.value.filter(\n      (v) => v.name.includes(val) && v.folder_id === currentFolder.value?.id,\n    )\n  } else {\n    searchData.value = knowledgeList.value.filter((v) => v.folder_id === currentFolder.value?.id)\n  }\n})\n\nfunction changeHandle() {\n  if (checkList.value.length > 0) {\n    currentEmbedding.value = knowledgeList.value?.find(\n      (v) => v.id === checkList.value[0],\n    )?.embedding_model_id\n  } else if (checkList.value.length === 0) {\n    currentEmbedding.value = ''\n  }\n}\nfunction clearCheck() {\n  checkList.value = []\n  currentEmbedding.value = ''\n}\n\nconst open = (checked: any) => {\n  checkList.value = checked\n  getFolder()\n  if (checkList.value.length > 0) {\n    currentEmbedding.value = props.data.filter(\n      (v) => v.id === checkList.value[0],\n    )[0].embedding_model_id\n  }\n\n  dialogVisible.value = true\n}\n\nconst submitHandle = () => {\n  emit(\n    'addData',\n    knowledgeList.value.filter((item: any) => checkList.value.includes(item.id)),\n  )\n  dialogVisible.value = false\n}\n\nconst refresh = () => {\n  searchValue.value = ''\n  knowledgeList.value = []\n  getList()\n}\n\nconst folderList = ref<any[]>([])\nconst currentFolder = ref<any>({})\nconst folderLoading = ref(false)\n// 文件\nfunction folderClickHandle(row: any) {\n  if (row.id === currentFolder.value?.id) {\n    return\n  }\n  currentFolder.value = row\n  getList()\n}\n\nfunction getFolder() {\n  const params = {}\n  folder.asyncGetFolder('KNOWLEDGE', params, apiType.value, folderLoading).then((res: any) => {\n    folderList.value = res.data\n    currentFolder.value = res.data?.[0] || {}\n    getList()\n  })\n}\n\nfunction getList() {\n  const folder_id = currentFolder.value?.id || user.getWorkspaceId()\n  loadSharedApi({\n    type: 'knowledge',\n    isShared: folder_id === 'share',\n    systemType: apiType.value,\n  })\n    .getKnowledgeList({ folder_id }, apiLoading)\n    .then((res: any) => {\n      knowledgeList.value = uniqueArray([...knowledgeList.value, ...res.data], 'id')\n      searchData.value = res.data\n    })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\">\n.addKnowledge-dialog {\n  padding: 0;\n  .el-dialog__header {\n    padding: 12px 20px 4px 24px;\n    border-bottom: 1px solid var(--el-border-color-light);\n  }\n  .el-dialog__footer {\n    padding: 12px 24px 12px 24px;\n    border-top: 1px solid var(--el-border-color-light);\n  }\n\n  .el-dialog__headerbtn {\n    top: 2px;\n    right: 6px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application/component/ApplicationDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    width=\"1000\"\n    append-to-body\n    class=\"addTool-dialog\"\n    align-center\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <template #header=\"{ titleId, titleClass }\">\n      <div class=\"flex-between mb-8\">\n        <div class=\"flex\">\n          <h4 :id=\"titleId\" :class=\"titleClass\" class=\"mr-8\">\n            {{ $t('views.application.title') }}\n          </h4>\n        </div>\n\n        <el-button link class=\"mr-24\" @click=\"refresh\">\n          <el-icon :size=\"18\"><Refresh /></el-icon>\n        </el-button>\n      </div>\n    </template>\n    <LayoutContainer class=\"application-manage\">\n      <template #left>\n        <folder-tree\n          :data=\"folderList\"\n          :currentNodeKey=\"currentFolder?.id\"\n          @handleNodeClick=\"folderClickHandle\"\n          v-loading=\"folderLoading\"\n          :canOperation=\"false\"\n          :treeStyle=\"{ height: 'calc(100vh - 240px)' }\"\n        />\n      </template>\n      <div class=\"layout-bg\">\n        <div class=\"flex-between p-16 ml-8\">\n          <h4>{{ currentFolder?.name }}</h4>\n          <el-input\n            v-model=\"searchValue\"\n            :placeholder=\"$t('common.search')\"\n            prefix-icon=\"Search\"\n            class=\"w-240 mr-8\"\n            clearable\n          />\n        </div>\n\n        <el-scrollbar>\n          <div class=\"p-16-24 pt-0\" style=\"height: calc(100vh - 200px)\">\n            <el-row :gutter=\"12\" v-loading=\"apiLoading\" v-if=\"searchData.length\">\n              <el-col :span=\"12\" v-for=\"(item, index) in searchData\" :key=\"index\" class=\"mb-16\">\n                <el-popover\n                  placement=\"bottom-start\"\n                  :width=\"400\"\n                  popper-style=\"--el-popover-border-radius:8px;--el-popover-padding:16px 16px 0\"\n                  :persistent=\"false\"\n                  :show-after=\"500\"\n                >\n                  <template #reference>\n                    <CardCheckbox\n                      value-field=\"id\"\n                      :data=\"item\"\n                      v-model=\"checkList\"\n                      @change=\"changeHandle\"\n                    >\n                      <template #icon>\n                        <el-avatar\n                          v-if=\"item?.icon\"\n                          shape=\"square\"\n                          :size=\"32\"\n                          style=\"background: none\"\n                          class=\"mr-8\"\n                        >\n                          <img :src=\"resetUrl(item?.icon)\" alt=\"\" />\n                        </el-avatar>\n                        <ToolIcon v-else :size=\"32\" :type=\"item?.tool_type\" />\n                      </template>\n                      <span class=\"ellipsis cursor ml-12\" :title=\"item.name\"> {{ item.name }}</span>\n                    </CardCheckbox>\n                  </template>\n                  <template #default>\n                    <CardBox\n                      :title=\"item.name\"\n                      :description=\"item.desc\"\n                      class=\"cursor border-none popover-card-box\"\n                      shadow=\"never\"\n                      style=\"--el-card-padding: 0px; --card-min-height: 148px\"\n                    >\n                      <template #icon>\n                        <el-avatar shape=\"square\" :size=\"32\" style=\"background: none\">\n                          <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n                        </el-avatar>\n                      </template>\n                      <template #subTitle>\n                        <el-text class=\"color-secondary lighter flex align-center\" size=\"small\">\n                          <span\n                            :title=\"i18n_name(item.nick_name)\"\n                            class=\"ellipsis\"\n                            style=\"max-width: 90px\"\n                          >\n                            {{ i18n_name(item.nick_name) }}\n                          </span>\n                          <span class=\"ml-4 mr-4\"> {{ $t('common.createdIn') }}</span>\n                          <span> {{ dateFormat(item.create_time) }}</span>\n                        </el-text>\n                      </template>\n                      <template #tag>\n                        <el-tag v-if=\"isWorkFlow(item.type)\" size=\"small\" class=\"warning-tag\">\n                          {{ $t('views.application.senior') }}\n                        </el-tag>\n                        <el-tag size=\"small\" class=\"blue-tag\" v-else>\n                          {{ $t('views.application.simple') }}\n                        </el-tag>\n                      </template>\n\n                      <template #footer>\n                        <div v-if=\"item.is_publish\" class=\"flex align-center\">\n                          <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n                            <SuccessFilled />\n                          </el-icon>\n                          <span class=\"color-secondary\">\n                            {{ $t('common.status.published') }}\n                          </span>\n                          <el-divider direction=\"vertical\" />\n                          <AppIcon iconName=\"app-clock\" class=\"color-secondary mr-8\"></AppIcon>\n\n                          <span class=\"color-secondary\">{{ dateFormat(item.update_time) }}</span>\n                        </div>\n                        <div v-else class=\"flex align-center\">\n                          <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n                          <span class=\"color-secondary\">\n                            {{ $t('common.status.unpublished') }}\n                          </span>\n                        </div>\n                      </template>\n                    </CardBox>\n                  </template>\n                </el-popover>\n              </el-col>\n            </el-row>\n            <el-empty :description=\"$t('common.noData')\" v-else />\n          </div>\n        </el-scrollbar>\n      </div>\n    </LayoutContainer>\n\n    <template #footer>\n      <div class=\"flex-between\">\n        <div class=\"flex\">\n          <el-text type=\"info\" class=\"color-secondary mr-8\" v-if=\"checkList.length > 0\">\n            {{ $t('common.selected') }} {{ checkList.length }}\n          </el-text>\n          <el-button link type=\"primary\" v-if=\"checkList.length > 0\" @click=\"clearCheck\">\n            {{ $t('common.clear') }}\n          </el-button>\n        </div>\n        <span>\n          <el-button @click.prevent=\"dialogVisible = false\">\n            {{ $t('common.cancel') }}\n          </el-button>\n          <el-button type=\"primary\" @click=\"submitHandle\">\n            {{ $t('common.add') }}\n          </el-button>\n        </span>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { i18n_name, resetUrl } from '@/utils/common'\nimport { isWorkFlow } from '@/utils/application'\nimport { dateFormat } from '@/utils/time'\nconst route = useRoute()\n\nconst emit = defineEmits(['refresh'])\nconst { folder, user } = useStore()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst checkList = ref<Array<string>>([])\nconst searchValue = ref('')\nconst searchData = ref<Array<any>>([])\nconst applicationList = ref<Array<any>>([])\nconst apiLoading = ref(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    checkList.value = []\n    searchValue.value = ''\n    searchData.value = []\n    applicationList.value = []\n  }\n})\n\nwatch(searchValue, (val) => {\n  if (val) {\n    searchData.value = applicationList.value.filter((v) => v.name.includes(val))\n  } else {\n    searchData.value = applicationList.value\n  }\n})\n\nfunction changeHandle() {}\nfunction clearCheck() {\n  checkList.value = []\n}\n\nconst open = (checked: any) => {\n  checkList.value = checked || []\n  getFolder()\n  dialogVisible.value = true\n}\n\nconst submitHandle = () => {\n  emit('refresh', {\n    application_ids: checkList.value,\n  })\n  dialogVisible.value = false\n}\n\nconst refresh = () => {\n  searchValue.value = ''\n  applicationList.value = []\n  getList()\n}\n\nconst folderList = ref<any[]>([])\nconst currentFolder = ref<any>({})\nconst folderLoading = ref(false)\n// 文件\nfunction folderClickHandle(row: any) {\n  if (row.id === currentFolder.value?.id) {\n    return\n  }\n  currentFolder.value = row\n  getList()\n}\n\nfunction getFolder() {\n  const params = {}\n  folder.asyncGetFolder('APPLICATION', params, apiType.value, folderLoading).then((res: any) => {\n    folderList.value = res.data\n    currentFolder.value = res.data?.[0] || {}\n    getList()\n  })\n}\n\nfunction getList() {\n  const folder_id = currentFolder.value?.id || user.getWorkspaceId()\n  loadSharedApi({\n    type: 'application',\n    isShared: folder_id === 'share',\n    systemType: 'workspace',\n  })\n    .getAllApplication({\n      folder_id: folder_id,\n    })\n    .then((res: any) => {\n      applicationList.value = res.data\n      applicationList.value = applicationList.value?.filter(\n        (item: any) => item.is_publish && item.id !== route.params.id,\n      )\n      searchData.value = res.data\n      searchData.value = searchData.value?.filter(\n        (item: any) => item.is_publish && item.id !== route.params.id,\n      )\n    })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\">\n.addTool-dialog {\n  padding: 0;\n  .el-dialog__header {\n    padding: 12px 20px 4px 24px;\n    border-bottom: 1px solid var(--el-border-color-light);\n  }\n  .el-dialog__footer {\n    padding: 12px 24px 12px 24px;\n    border-top: 1px solid var(--el-border-color-light);\n  }\n\n  .el-dialog__headerbtn {\n    top: 2px;\n    right: 6px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application/component/CopyApplicationDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.application.copyApplication')\"\n    v-model=\"dialogVisible\"\n    width=\"650\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form\n      ref=\"applicationFormRef\"\n      :model=\"applicationForm\"\n      :rules=\"rules\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-form-item :label=\"$t('common.name')\" prop=\"name\">\n        <el-input\n          v-model=\"applicationForm.name\"\n          maxlength=\"64\"\n          :placeholder=\"$t('views.application.form.appName.placeholder')\"\n          show-word-limit\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('common.desc')\">\n        <el-input\n          v-model=\"applicationForm.desc\"\n          type=\"textarea\"\n          :placeholder=\"$t('views.application.form.appDescription.placeholder')\"\n          :rows=\"3\"\n          maxlength=\"256\"\n          show-word-limit\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\" :loading=\"loading\">\n          {{ $t('common.cancel') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle(applicationFormRef)\" :loading=\"loading\">\n          {{ $t('common.copy') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport { cloneDeep } from 'lodash'\nimport type { ApplicationFormType } from '@/api/type/application'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport applicationApi from '@/api/application/application'\nimport { MsgSuccess, MsgAlert } from '@/utils/message'\nimport { isWorkFlow } from '@/utils/application'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nconst router = useRouter()\nconst { common, user } = useStore()\n\nconst defaultPrompt = t('views.application.form.prompt.defaultPrompt', {\n  data: '{data}',\n  question: '{question}',\n})\nconst applicationFormRef = ref()\n\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\n\nconst applicationForm = ref<ApplicationFormType>({\n  name: '',\n  desc: '',\n  model_id: '',\n  dialogue_number: 0,\n  prologue: t('views.application.form.defaultPrologue'),\n  knowledge_id_list: [],\n  knowledge_setting: {\n    top_n: 3,\n    similarity: 0.6,\n    max_paragraph_char_number: 5000,\n    search_mode: 'embedding',\n    no_references_setting: {\n      status: 'ai_questioning',\n      value: '{question}',\n    },\n  },\n  model_setting: {\n    prompt: defaultPrompt,\n  },\n  problem_optimization: false,\n  type: 'SIMPLE',\n})\n\nconst rules = reactive<FormRules<ApplicationFormType>>({\n  name: [\n    {\n      required: true,\n      message: t('views.application.form.appName.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst currentFolder = ref('')\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    applicationForm.value = {\n      name: '',\n      desc: '',\n      model_id: '',\n      dialogue_number: 0,\n      prologue: t('views.application.form.defaultPrologue'),\n      knowledge_id_list: [],\n      knowledge_setting: {\n        top_n: 3,\n        similarity: 0.6,\n        max_paragraph_char_number: 5000,\n        search_mode: 'embedding',\n        no_references_setting: {\n          status: 'ai_questioning',\n          value: '{question}',\n        },\n      },\n      model_setting: {\n        prompt: defaultPrompt,\n      },\n      problem_optimization: false,\n      type: 'SIMPLE',\n    }\n    applicationFormRef.value?.clearValidate()\n  }\n})\n\nconst open = (data: any, folder: string) => {\n  currentFolder.value = folder\n  const obj = cloneDeep(data)\n  delete obj['id']\n  obj['name'] = obj['name'] + ` ${t('common.copyTitle')}`\n  applicationForm.value = obj\n  dialogVisible.value = true\n}\n\nconst submitHandle = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      applicationApi\n        .postApplication({ ...applicationForm.value, folder_id: currentFolder.value }, loading)\n        .then((res) => {\n          return user.profile().then(() => {\n            return res\n          })\n        })\n        .then((res) => {\n          MsgSuccess(t('common.createSuccess'))\n          if (isWorkFlow(applicationForm.value.type)) {\n            router.push({ path: `/application/workspace/${res.data.id}/workflow` })\n          } else {\n            router.push({ path: `/application/workspace/${res.data.id}/${res.data.type}/setting` })\n          }\n          dialogVisible.value = false\n        })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application/component/CreateApplicationDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"\n      isWorkFlow(applicationForm.type)\n        ? $t('views.application.createWorkFlowApplication')\n        : $t('views.application.createApplication')\n    \"\n    v-model=\"dialogVisible\"\n    width=\"650\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form\n      ref=\"applicationFormRef\"\n      :model=\"applicationForm\"\n      :rules=\"rules\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-form-item :label=\"$t('common.name')\" prop=\"name\">\n        <el-input\n          v-model=\"applicationForm.name\"\n          maxlength=\"64\"\n          :placeholder=\"$t('views.application.form.appName.placeholder')\"\n          show-word-limit\n          @blur=\"applicationForm.name = applicationForm.name?.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('common.desc')\">\n        <el-input\n          v-model=\"applicationForm.desc\"\n          type=\"textarea\"\n          :placeholder=\"$t('views.application.form.appDescription.placeholder')\"\n          :rows=\"3\"\n          maxlength=\"256\"\n          show-word-limit\n        />\n      </el-form-item>\n\n      <el-form-item\n        :label=\"$t('views.document.upload.template')\"\n        v-if=\"applicationForm.type === 'WORK_FLOW' && !work_flow_template\"\n      >\n        <div class=\"w-full\">\n          <el-row :gutter=\"16\">\n            <el-col :span=\"12\">\n              <el-card\n                class=\"template-radio-card cursor text-center flex-center\"\n                shadow=\"never\"\n                @click=\"selectedType('blank')\"\n                :class=\"appTemplate === 'blank' ? 'border-active' : ''\"\n              >\n                <div class=\"flex-center p-24\">\n                  <AppIcon iconName=\"app-add-outlined\" class=\"mr-12\"></AppIcon>\n                  {{ $t('views.application.form.appTemplate.blankApp.title') }}\n                </div>\n              </el-card>\n            </el-col>\n            <el-col :span=\"12\">\n              <CardBox\n                :title=\"$t('views.application.form.appTemplate.assistantApp.title')\"\n                :description=\"$t('views.application.form.appTemplate.assistantApp.description')\"\n                shadow=\"never\"\n                class=\"template-radio-card cursor\"\n                :class=\"appTemplate === 'assistant' ? 'border-active' : ''\"\n                @click=\"selectedType('assistant')\"\n              >\n                <template #icon>\n                  <LogoIcon height=\"32px\" />\n                </template>\n              </CardBox>\n            </el-col>\n          </el-row>\n        </div>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\" :loading=\"loading\">\n          {{ $t('common.cancel') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle(applicationFormRef)\" :loading=\"loading\">\n          {{ $t('common.create') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport type { ApplicationFormType } from '@/api/type/application'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport applicationApi from '@/api/application/application'\nimport { MsgSuccess, MsgAlert } from '@/utils/message'\nimport { isWorkFlow } from '@/utils/application'\nimport { baseNodes } from '@/workflow/common/data'\nimport { applicationTemplate } from '@/workflow/common/template'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nconst { user } = useStore()\nconst router = useRouter()\nconst emit = defineEmits(['refresh'])\n\nconst defaultPrompt = t('views.application.form.prompt.defaultPrompt', {\n  data: '{data}',\n  question: '{question}',\n})\n\nconst optimizationPrompt =\n  t('views.application.dialog.defaultPrompt1', {\n    question: '{question}',\n  }) +\n  '<data></data>' +\n  t('views.application.dialog.defaultPrompt2')\n\nconst workflowDefault = ref<any>({\n  edges: [],\n  nodes: baseNodes,\n})\nconst appTemplate = ref('blank')\n\nconst applicationFormRef = ref()\n\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\nconst work_flow_template = ref()\n\nconst applicationForm = ref<ApplicationFormType>({\n  name: '',\n  desc: '',\n  model_id: undefined,\n  dialogue_number: 1,\n  prologue: t('views.application.form.defaultPrologue'),\n  knowledge_id_list: [],\n  knowledge_setting: {\n    top_n: 3,\n    similarity: 0.6,\n    max_paragraph_char_number: 5000,\n    search_mode: 'embedding',\n    no_references_setting: {\n      status: 'ai_questioning',\n      value: '{question}',\n    },\n  },\n  model_setting: {\n    prompt: defaultPrompt,\n    system: '',\n    no_references_prompt: '{question}',\n  },\n  model_params_setting: {},\n  problem_optimization: false,\n  problem_optimization_prompt: optimizationPrompt,\n  stt_model_id: undefined,\n  tts_model_id: undefined,\n  stt_model_enable: false,\n  tts_model_enable: false,\n  tts_type: 'BROWSER',\n  type: 'SIMPLE',\n  work_flow_template: undefined,\n})\n\nconst rules = reactive<FormRules<ApplicationFormType>>({\n  name: [\n    {\n      required: true,\n      message: t('views.application.form.appName.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n  model_id: [\n    {\n      required: false,\n      message: t('views.application.form.aiModel.placeholder'),\n      trigger: 'change',\n    },\n  ],\n})\n\nconst currentFolder = ref('')\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    applicationForm.value = {\n      name: '',\n      desc: '',\n      model_id: undefined,\n      dialogue_number: 1,\n      prologue: t('views.application.form.defaultPrologue'),\n      knowledge_id_list: [],\n      knowledge_setting: {\n        top_n: 3,\n        similarity: 0.6,\n        max_paragraph_char_number: 5000,\n        search_mode: 'embedding',\n        no_references_setting: {\n          status: 'ai_questioning',\n          value: '{question}',\n        },\n      },\n      model_setting: {\n        prompt: defaultPrompt,\n        system: '',\n        no_references_prompt: '{question}',\n      },\n      model_params_setting: {},\n      problem_optimization: false,\n      problem_optimization_prompt: optimizationPrompt,\n      stt_model_id: undefined,\n      tts_model_id: undefined,\n      stt_model_enable: false,\n      tts_model_enable: false,\n      tts_type: 'BROWSER',\n      type: 'SIMPLE',\n    }\n    applicationFormRef.value?.clearValidate()\n  }\n})\n\nconst open = (folder: string, type?: string, work_flow?: any) => {\n  currentFolder.value = folder\n  applicationForm.value.type = type || 'SIMPLE'\n  dialogVisible.value = true\n  work_flow_template.value = work_flow\n}\n\nconst submitHandle = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      if (isWorkFlow(applicationForm.value.type)) {\n        workflowDefault.value.nodes[0].properties.node_data.desc = applicationForm.value.desc\n        workflowDefault.value.nodes[0].properties.node_data.name = applicationForm.value.name\n        applicationForm.value['work_flow'] = workflowDefault.value\n        if (work_flow_template.value) {\n          applicationForm.value['work_flow_template'] = work_flow_template.value\n        }\n      }\n      loading.value = true\n      applicationApi\n        .postApplication({ ...applicationForm.value, folder_id: currentFolder.value })\n        .then((res) => {\n          return user.profile().then(() => {\n            return res\n          })\n        })\n        .then((res) => {\n          MsgSuccess(t('common.createSuccess'))\n          emit('refresh')\n          if (isWorkFlow(applicationForm.value.type)) {\n            router.push({ path: `/application/workspace/${res.data.id}/workflow` })\n          } else {\n            router.push({ path: `/application/workspace/${res.data.id}/${res.data.type}/setting` })\n          }\n          dialogVisible.value = false\n        })\n        .finally(() => {\n          loading.value = false\n        })\n    }\n  })\n}\n\nfunction selectedType(type: string) {\n  appTemplate.value = type\n  workflowDefault.value = applicationTemplate[type]\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped>\n.template-radio-card {\n  height: 120px !important;\n  min-height: 120px !important;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application/component/GeneratePromptDialog.vue",
    "content": "<template>\n  <el-dialog\n    align-center\n    :title=\"$t('views.application.generateDialog.generatePrompt')\"\n    v-model=\"dialogVisible\"\n    style=\"width: 600px\"\n    append-to-body\n    :close-on-click-modal=\"true\"\n    :close-on-press-escape=\"true\"\n    :before-close=\"handleDialogClose\"\n  >\n    <div class=\"generate-prompt-dialog-bg border-r-8\">\n      <div class=\"scrollbar-height\">\n        <!-- 生成内容 -->\n        <div class=\"p-16 pb-0 lighter\">\n          <el-scrollbar ref=\"scrollDiv\">\n            <div\n              ref=\"dialogScrollbar\"\n              v-if=\"answer\"\n              class=\"pre-wrap lighter\"\n              style=\"max-height: calc(100vh - 400px)\"\n            >\n              {{ answer }}\n            </div>\n            <p v-else-if=\"loading\" shadow=\"always\" style=\"margin: 0.5rem 0\">\n              <el-icon class=\"is-loading color-primary mr-4\"><Loading /></el-icon>\n              {{ $t('views.application.generateDialog.loading') }}\n              <span class=\"dotting\"></span>\n            </p>\n            <p v-else class=\"flex align-center\">\n              <AppIcon iconName=\"app-generate-star\" class=\"color-primary mr-4\"></AppIcon>\n              {{ $t('views.application.generateDialog.title') }}\n            </p>\n          </el-scrollbar>\n\n          <div v-if=\"answer && !loading && !isStreaming && !showContinueButton\" class=\"mt-8\">\n            <el-button type=\"primary\" @click=\"() => emit('replace', answer)\">\n              {{ $t('views.application.generateDialog.replace') }}\n            </el-button>\n            <el-button @click=\"copyClick(answer)\">\n              {{ $t('common.copy') }}\n            </el-button>\n            <el-button @click=\"reAnswerClick\" :disabled=\"!answer || loading\" :loading=\"loading\">\n              {{ $t('views.application.generateDialog.remake') }}\n            </el-button>\n          </div>\n          <div class=\"mt-8\" v-else>\n            <el-button type=\"primary\" v-if=\"showContinueButton\" @click=\"continueStreaming\" link>\n              {{ $t('views.application.generateDialog.continue') }}\n            </el-button>\n          </div>\n        </div>\n\n        <!-- 文本输入框 -->\n\n        <div class=\"generate-prompt-operate p-16\">\n          <div v-if=\"showStopButton\" class=\"text-center mb-8\">\n            <el-button class=\"border-primary video-stop-button\" @click=\"pauseStreaming\">\n              <app-icon iconName=\"app-video-stop\" class=\"mr-8\"></app-icon>\n              {{ $t('views.application.generateDialog.stop') }}\n            </el-button>\n          </div>\n\n          <div class=\"operate-textarea\">\n            <el-input\n              ref=\"quickInputRef\"\n              v-model=\"inputValue\"\n              :autosize=\"{ minRows: 1, maxRows: 10 }\"\n              type=\"textarea\"\n              :placeholder=\"$t('views.application.generateDialog.placeholder')\"\n              :maxlength=\"100000\"\n              class=\"chat-operate-textarea\"\n              @keydown.enter=\"handleSubmit($event)\"\n            />\n\n            <div class=\"operate\">\n              <div class=\"text-right\">\n                <el-button\n                  text\n                  class=\"sent-button\"\n                  :disabled=\"!inputValue.trim() || loading || isStreaming\"\n                  @click=\"handleSubmit\"\n                >\n                  <img\n                    v-show=\"!inputValue.trim() || loading || isStreaming\"\n                    src=\"@/assets/chat/icon_send.svg\"\n                    alt=\"\"\n                  />\n                  <SendIcon v-show=\"inputValue.trim() && !loading && !isStreaming\" />\n                </el-button>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, onUnmounted, reactive, ref, nextTick, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { MsgConfirm } from '@/utils/message'\nimport { t } from '@/locales'\nimport systemGeneratePromptAPI from '@/api/system-resource-management/application'\nimport generatePromptAPI from '@/api/application/application'\nimport useStore from '@/stores'\nimport { copyClick } from '@/utils/clipboard'\nconst emit = defineEmits(['replace'])\nconst { user } = useStore()\nconst route = useRoute()\n\nconst chatMessages = ref<Array<any>>([])\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n// 原始输入\nconst originalUserInput = ref<string>('')\nconst modelID = ref('')\nconst applicationID = ref('')\nconst dialogVisible = ref(false)\nconst inputValue = ref<string>('')\nconst loading = ref<boolean>(false)\n\nconst promptTemplates = {\n  INIT_TEMPLATE: `\n请根据用户描述生成一个完整的AI角色人设模板:\n\n用户需求：{userInput}\n\n重要说明：\n1. 角色设定必须服务于\"{userInput}\"内容设定应用的核心功能\n2. 允许用户对角色设定的具体内容进行调整和优化\n3. 如果用户要求修改某个技能或部分，在保持应用主题的前提下进行相应调整\n\n请按以下格式生成：\n\n必须严格遵循以下规则：\n1. **严格禁止输出解释、前言、额外说明**，只输出最终结果。\n2. **严格使用以下格式**，不能缺少标题、不能多出其他段落。\n3. **如果用户要求修改角色设定的某个部分，在保持应用核心功能的前提下进行调整**。\n4. **如果用户需求与角色设定生成完全无关（如闲聊、其他话题），则主要依据应用信息生成标准角色设定，但不完全忽略用户输入，可从中提取有价值的辅助信息（如领域背景、语气风格等）作为次要参考**。\n\n# 角色:\n角色概述和主要职责的一句话描述\n\n## 目标：\n角色的工作目标,如果有多目标可以分点列出,但建议更聚焦1-2个目标\n\n## 核心技能：\n### 技能 1: [技能名称，如作品推荐/信息查询/专业分析等]\n1. [执行步骤1 - 描述该技能的第一个具体操作步骤，包括条件判断和处理方式]\n2. [执行步骤2 - 描述该技能的第二个具体操作步骤，包括如何获取或处理信息]\n3. [执行步骤3 - 描述该技能的最终输出步骤，说明如何呈现结果]\n\n===回复示例===\n- 📋 [标识符]: <具体内容格式说明>\n- 🎯 [标识符]: <具体内容格式说明>\n- 💡 [标识符]: <具体内容格式说明>\n===示例结束===\n\n### 技能 2: [技能名称]\n1. [执行步骤1 - 描述触发条件和初始处理方式]\n2. [执行步骤2 - 描述信息获取和深化处理的具体方法]\n3. [执行步骤3 - 描述最终输出的具体要求和格式]\n\n### 技能 3: [技能名称]\n- [核心能力描述 - 说明该技能的主要作用和知识基础]\n- [应用方法 - 描述如何运用该技能为用户提供服务，包括具体的实施方式]\n\n## 工作流：\n1. 描述角色工作流程的第一步\n2. 描述角色工作流程的第二步\n3. 描述角色工作流程的第三步\n\n## 输出格式：\n如果对角色的输出格式有特定要求，可以在这里强调并举例说明想要的输出格式\n\n\n## 限制：\n1. **严格限制回答范围**：仅回答与角色设定相关的问题。\n   - 如果用户提问与角色无关，必须使用以下固定格式回复：\n     “对不起，我只能回答与[角色设定]相关的问题，您的问题不在服务范围内。”\n   - 不得提供任何与角色设定无关的回答。\n2. 描述角色在互动过程中需要遵循的限制条件2\n3. 描述角色在互动过程中需要遵循的限制条件3\n\n输出时不得包含任何解释或附加说明，只能返回符合以上格式的内容。\n  `,\n}\n\nconst isStreaming = ref<boolean>(false) // 是否正在流式输出\nconst isPaused = ref<boolean>(false) // 是否暂停\nconst fullContent = ref<string>('') // 完整内容缓存\nconst currentDisplayIndex = ref<number>(0) // 当前显示到的字符位置\nlet streamTimer: number | null = null // 定时器引用\nconst isOutputComplete = ref<boolean>(false)\n\n// 模拟流式输出的定时器函数\nconst startStreamingOutput = () => {\n  if (streamTimer) {\n    clearInterval(streamTimer)\n  }\n\n  isStreaming.value = true\n  isPaused.value = false\n\n  streamTimer = setInterval(() => {\n    if (isApiComplete.value && !isPaused.value) {\n      // 更新显示内容\n      const currentAnswer = chatMessages.value[chatMessages.value.length - 1]\n      if (currentAnswer && currentAnswer.role === 'ai') {\n        currentAnswer.content = fullContent.value\n      }\n      stopStreaming()\n      return\n    }\n    if (!isPaused.value && currentDisplayIndex.value < fullContent.value.length) {\n      // 每次输出1-3个字符，模拟真实的流式输出\n      const step = Math.min(3, fullContent.value.length - currentDisplayIndex.value)\n      currentDisplayIndex.value += step\n\n      // 更新显示内容\n      const currentAnswer = chatMessages.value[chatMessages.value.length - 1]\n      if (currentAnswer && currentAnswer.role === 'ai') {\n        currentAnswer.content = fullContent.value.substring(0, currentDisplayIndex.value)\n      }\n    } else if (loading.value === false && currentDisplayIndex.value >= fullContent.value.length) {\n      stopStreaming()\n    }\n  }, 50) as any\n}\n\n// 停止流式输出\nconst stopStreaming = () => {\n  if (streamTimer) {\n    clearInterval(streamTimer)\n    streamTimer = null\n  }\n  isStreaming.value = false\n  isPaused.value = false\n  loading.value = false\n  isOutputComplete.value = true\n}\n\nconst showStopButton = computed(() => {\n  return isStreaming.value\n})\n\n// 暂停流式输出\nconst pauseStreaming = () => {\n  isPaused.value = true\n  isStreaming.value = false\n}\n\n// 继续流式输出\nconst continueStreaming = () => {\n  if (currentDisplayIndex.value < fullContent.value.length) {\n    startStreamingOutput()\n  }\n}\n\n/**\n * 获取一个递归函数,处理流式数据\n * @param chat    每一条对话记录\n * @param reader  流数据\n * @param stream  是否是流式数据\n */\nconst getWrite = (reader: any) => {\n  let tempResult = ''\n  const middleAnswer = reactive({ content: '', role: 'ai' })\n  chatMessages.value.push(middleAnswer)\n\n  // 初始化状态并\n  fullContent.value = ''\n  currentDisplayIndex.value = 0\n  isOutputComplete.value = false\n\n  let streamingStarted = false\n\n  /**\n   *\n   * @param done  是否结束\n   * @param value 值\n   */\n  const write_stream = ({ done, value }: { done: boolean; value: any }) => {\n    try {\n      if (done) {\n        // 流数据接收完成，但定时器继续运行直到显示完所有内容\n        loading.value = false\n        isApiComplete.value = true\n        return\n      }\n      const decoder = new TextDecoder('utf-8')\n      let str = decoder.decode(value, { stream: true })\n      // 这里解释一下 start 因为数据流返回流并不是按照后端chunk返回 我们希望得到的chunk是data:{xxx}\\n\\n 但是它获取到的可能是 data:{ -> xxx}\\n\\n 总而言之就是 fetch不能保证每个chunk都说以data:开始 \\n\\n结束\n      tempResult += str\n      const split = tempResult.match(/data:.*}\\n\\n/g)\n      if (split) {\n        str = split.join('')\n        tempResult = tempResult.replace(str, '')\n      } else {\n        return reader.read().then(write_stream)\n      }\n      // 这里解释一下 end\n      if (str && str.startsWith('data:')) {\n        if (split) {\n          for (const index in split) {\n            const chunk = JSON?.parse(split[index].replace('data:', ''))\n            if (chunk.error) {\n              loading.value = false\n              stopStreaming()\n              middleAnswer.content = chunk.error\n              return Promise.reject(new Error(chunk.error))\n            }\n            if (!chunk.is_end) {\n              // 实时将新接收的内容添加到完整内容中\n              fullContent.value += chunk.content\n              if (!streamingStarted) {\n                streamingStarted = true\n                startStreamingOutput()\n              }\n            }\n            if (chunk.is_end) {\n              return Promise.resolve()\n            }\n          }\n        }\n      }\n    } catch (e) {\n      loading.value = false\n      stopStreaming()\n      return Promise.reject(e)\n    }\n    return reader.read().then(write_stream)\n  }\n\n  return write_stream\n}\nconst isApiComplete = ref<boolean>(false)\nconst answer = computed(() => {\n  const result = chatMessages.value[chatMessages.value.length - 1]\n\n  if (result && result.role == 'ai') {\n    return result.content\n  }\n  return ''\n})\n\n// 按钮状态计算\nconst showContinueButton = computed(() => {\n  return (\n    !isStreaming.value && isPaused.value && currentDisplayIndex.value < fullContent.value.length\n  )\n})\n\nfunction generatePrompt(inputValue: any) {\n  isApiComplete.value = false\n  loading.value = true\n  const workspaceId = user.getWorkspaceId() || 'default'\n  chatMessages.value.push({ content: inputValue, role: 'user' })\n  const requestData = {\n    messages: chatMessages.value,\n    prompt: promptTemplates.INIT_TEMPLATE,\n  }\n  if (apiType.value === 'workspace') {\n    generatePromptAPI\n      .generate_prompt(workspaceId, modelID.value, applicationID.value, requestData)\n      .then((response) => {\n        nextTick(() => {\n          if (dialogScrollbar.value) {\n            // 将滚动条滚动到最下面\n            scrollDiv.value.setScrollTop(getMaxHeight())\n          }\n        })\n        const reader = response.body.getReader()\n        reader.read().then(getWrite(reader))\n      })\n  } else if (apiType.value === 'systemManage') {\n    systemGeneratePromptAPI\n      .generate_prompt(applicationID.value, modelID.value, requestData)\n      .then((response) => {\n        nextTick(() => {\n          if (dialogScrollbar.value) {\n            // 将滚动条滚动到最下面\n            scrollDiv.value.setScrollTop(getMaxHeight())\n          }\n        })\n        const reader = response.body.getReader()\n        reader.read().then(getWrite(reader))\n      })\n  }\n}\n\n// 重新生成点击\nconst reAnswerClick = () => {\n  if (originalUserInput.value) {\n    generatePrompt(\n      `上一次回答不满意。请针对原始问题\"${originalUserInput.value}\"并结合对话记录，严格按照格式规范重新生成。`,\n    )\n  }\n}\n\nconst quickInputRef = ref()\n\nconst handleSubmit = (event?: any) => {\n  if (!event?.ctrlKey && !event?.shiftKey && !event?.altKey && !event?.metaKey) {\n    // 如果没有按下组合键，则会阻止默认事件\n    event?.preventDefault()\n    if (!inputValue.value.trim() || loading.value || isStreaming.value) {\n      return\n    }\n    if (!originalUserInput.value) {\n      originalUserInput.value = inputValue.value\n    }\n    if (isPaused.value || isStreaming.value) {\n      return\n    }\n    if (inputValue.value) {\n      generatePrompt(inputValue.value)\n      inputValue.value = ''\n    }\n  } else {\n    // 如果同时按下ctrl/shift/cmd/opt +enter，则会换行\n    insertNewlineAtCursor(event)\n  }\n}\nconst insertNewlineAtCursor = (event?: any) => {\n  const textarea = quickInputRef.value.$el.querySelector(\n    '.el-textarea__inner',\n  ) as HTMLTextAreaElement\n  const startPos = textarea.selectionStart\n  const endPos = textarea.selectionEnd\n  // 阻止默认行为（避免额外的换行符）\n  event.preventDefault()\n  // 在光标处插入换行符\n  inputValue.value = inputValue.value.slice(0, startPos) + '\\n' + inputValue.value.slice(endPos)\n  nextTick(() => {\n    textarea.setSelectionRange(startPos + 1, startPos + 1) // 光标定位到换行后位置\n  })\n}\n\nconst open = (modelId: string, applicationId: string) => {\n  modelID.value = modelId\n  applicationID.value = applicationId\n  dialogVisible.value = true\n  originalUserInput.value = ''\n  chatMessages.value = []\n}\n\nconst scrollDiv = ref()\nconst dialogScrollbar = ref()\n\nconst getMaxHeight = () => {\n  return dialogScrollbar.value!.scrollHeight\n}\n\n/**\n * 处理跟随滚动条\n */\nconst handleScroll = () => {\n  if (scrollDiv.value) {\n    // 内部高度小于外部高度 就需要出滚动条\n    if (scrollDiv.value.wrapRef.offsetHeight < dialogScrollbar.value?.scrollHeight) {\n      // 如果当前滚动条距离最下面的距离在 规定距离 滚动条就跟随\n      scrollDiv.value.setScrollTop(getMaxHeight())\n    }\n  }\n}\n\nconst handleDialogClose = (done: () => void) => {\n  if (answer.value) {\n    // 弹出 消息\n    MsgConfirm(t('common.tip'), t('views.application.generateDialog.exit'), {\n      confirmButtonText: t('common.confirm'),\n      cancelButtonText: t('common.cancel'),\n      distinguishCancelAndClose: true,\n    })\n      .then(() => {\n        // 点击确认，清除状态\n        stopStreaming()\n        chatMessages.value = []\n        fullContent.value = ''\n        currentDisplayIndex.value = 0\n        isOutputComplete.value = false\n        done() // 真正关闭\n      })\n      .catch(() => {\n        // 点击取消\n      })\n  } else {\n    done()\n  }\n}\n\n// 组件卸载时清理定时器\nonUnmounted(() => {\n  stopStreaming()\n})\n\nwatch(\n  answer,\n  () => {\n    handleScroll()\n  },\n  { deep: true, immediate: true },\n)\n\ndefineExpose({\n  open,\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.generate-prompt-dialog-bg {\n  background: var(--dialog-bg-gradient-color);\n  overflow: hidden;\n  box-sizing: border-box;\n}\n\n.generate-prompt-operate {\n  position: relative;\n  width: 100%;\n  box-sizing: border-box;\n  z-index: 10;\n\n  :deep(.operate-textarea) {\n    box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n    background-color: #ffffff;\n    border-radius: var(--app-border-radius-large);\n    border: 1px solid #ffffff;\n    box-sizing: border-box;\n\n    &:has(.el-textarea__inner:focus) {\n      border: 1px solid var(--el-color-primary);\n    }\n\n    .el-textarea__inner {\n      border-radius: var(--app-border-radius-large) !important;\n      box-shadow: none;\n      resize: none;\n      padding: 13px 16px;\n      box-sizing: border-box;\n      min-height: 47px !important;\n      height: 0;\n    }\n\n    .operate {\n      padding: 6px 10px;\n\n      .el-icon {\n        font-size: 20px;\n      }\n\n      .sent-button {\n        max-height: none;\n\n        .el-icon {\n          font-size: 24px;\n        }\n      }\n\n      .el-loading-spinner {\n        margin-top: -15px;\n\n        .circular {\n          width: 31px;\n          height: 31px;\n        }\n      }\n    }\n  }\n  .video-stop-button {\n    box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n\n    &:hover {\n      background: #ffffff;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application/component/McpServersDialog.vue",
    "content": "<template>\n  <el-dialog\n    align-center\n    :title=\"$t('common.setting')\"\n    v-model=\"dialogVisible\"\n    style=\"width: 550px\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"paramFormRef\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n      hide-required-asterisk\n      @submit.prevent\n    >\n      <el-form-item>\n        <el-radio-group v-model=\"form.mcp_source\" @change=\"mcpSourceChange\">\n          <el-radio value=\"referencing\">\n            {{ $t('workflow.nodes.mcpNode.reference') }}\n          </el-radio>\n          <el-radio value=\"custom\">{{ $t('common.custom') }}</el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item\n        v-if=\"form.mcp_source === 'referencing'\"\n        :rules=\"[\n          {\n            required: true,\n            message: $t('common.selectPlaceholder') + ` MCP ${$t('views.tool.title')}`,\n          },\n        ]\"\n        prop=\"mcp_tool_ids\"\n      >\n        <template #label>\n          {{ `MCP ${$t('views.tool.title')}` }}\n          <span class=\"color-danger\">*</span>\n        </template>\n        <el-select v-model=\"form.mcp_tool_ids\" filterable multiple :reserve-keyword=\"false\">\n          <el-option\n            v-for=\"mcpTool in mcpToolSelectOptions\"\n            :key=\"mcpTool.id\"\n            :label=\"mcpTool.name\"\n            :value=\"mcpTool.id\"\n          >\n            <div class=\"flex align-center\">\n              <el-avatar\n                v-if=\"mcpTool?.icon\"\n                shape=\"square\"\n                :size=\"20\"\n                style=\"background: none\"\n                class=\"mr-8\"\n              >\n                <img :src=\"resetUrl(mcpTool?.icon)\" alt=\"\" />\n              </el-avatar>\n              <el-avatar v-else shape=\"square\" :size=\"20\" class=\"mr-8\">\n                <img src=\"@/assets/tool/icon_mcp.svg\" style=\"width: 75%\" alt=\"\" />\n              </el-avatar>\n              <span>{{ mcpTool.name }}</span>\n              <el-tag v-if=\"mcpTool.scope === 'SHARED'\" size=\"small\" type=\"info\" class=\"info-tag ml-8 mt-4\">\n                {{ $t('views.shared.title') }}\n              </el-tag>\n            </div>\n          </el-option>\n        </el-select>\n      </el-form-item>\n      <el-form-item\n        v-else\n        prop=\"mcp_servers\"\n        :rules=\"[\n          {\n            required: true,\n            message: $t('common.inputPlaceholder') + ' ' + $t('views.tool.mcp.label'),\n          },\n        ]\"\n      >\n        <template #label>\n          {{ $t('views.tool.mcp.label') }}\n          <span class=\"color-danger\">*</span>\n          <el-text type=\"info\" class=\"color-secondary\">\n            （{{ $t('views.tool.mcp.tip') }}）\n          </el-text>\n        </template>\n        <el-input\n          v-model=\"form.mcp_servers\"\n          :rows=\"6\"\n          type=\"textarea\"\n          :placeholder=\"mcpServerJson\"\n        />\n      </el-form-item>\n    </el-form>\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit()\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { MsgError } from '@/utils/message.ts'\nimport { t } from '@/locales'\nimport { resetUrl } from '@/utils/common'\n\nconst emit = defineEmits(['refresh'])\n\nconst paramFormRef = ref()\n\nconst mcpServerJson = `{\n  \"math\": {\n    \"url\": \"your_server\",\n    \"transport\": \"sse\"\n  }\n}`\n\nconst form = ref<any>({\n  mcp_servers: '',\n  mcp_tool_ids: [],\n  mcp_source: 'referencing',\n})\n\nconst mcpToolSelectOptions = ref<any[]>([])\n\nconst dialogVisible = ref<boolean>(false)\n\nconst loading = ref(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      mcp_servers: '',\n      mcp_tool_ids: [],\n      mcp_source: 'referencing',\n    }\n    paramFormRef.value?.clearValidate()\n  }\n})\n\nfunction mcpSourceChange() {\n  if (form.value.mcp_source === 'referencing') {\n    form.value.mcp_servers = ''\n  } else {\n    form.value.mcp_tool_ids = []\n  }\n}\n\nconst open = (data: any, selectOptions: any) => {\n  form.value = { ...form.value, ...data }\n  if (data.mcp_servers && Object.keys(data.mcp_servers).length > 0) {\n    form.value.mcp_source = 'custom'\n  } else if (data.mcp_tool_ids) {\n    form.value.mcp_source = 'referencing'\n    form.value.mcp_tool_ids = data.mcp_tool_ids\n    form.value.mcp_servers = ''\n  } else {\n    form.value.mcp_source = data.mcp_source || 'referencing'\n  }\n  dialogVisible.value = true\n  mcpToolSelectOptions.value = selectOptions || []\n}\n\nconst submit = () => {\n  paramFormRef.value.validate((valid: any) => {\n    if (valid) {\n      try {\n        JSON.parse(form.value.mcp_servers || '{}')\n      } catch (e) {\n        MsgError(t('workflow.nodes.mcpNode.mcpServerTip'))\n        return\n      }\n      emit('refresh', form.value)\n      dialogVisible.value = false\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application/component/ParamSettingDialog.vue",
    "content": "<template>\n  <el-dialog\n    class=\"scrollbar-dialog\"\n    align-center\n    :title=\"$t('common.paramSetting')\"\n    v-model=\"dialogVisible\"\n    width=\"550px\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-scrollbar max-height=\"550\">\n      <div class=\"p-8\">\n        <el-form label-position=\"top\" ref=\"paramFormRef\" :model=\"form\" v-loading=\"loading\">\n          <el-form-item :label=\"$t('views.application.dialog.selectSearchMode')\">\n            <el-radio-group\n              v-model=\"form.knowledge_setting.search_mode\"\n              class=\"card__radio\"\n              @change=\"changeHandle\"\n            >\n              <el-card\n                shadow=\"never\"\n                class=\"mb-16\"\n                :class=\"form.knowledge_setting.search_mode === 'embedding' ? 'border-active' : ''\"\n              >\n                <el-radio value=\"embedding\" size=\"large\">\n                  <p class=\"mb-4\">\n                    {{ $t('views.application.dialog.vectorSearch') }}\n                  </p>\n                  <el-text type=\"info\">{{\n                    $t('views.application.dialog.vectorSearchTooltip')\n                  }}</el-text>\n                </el-radio>\n              </el-card>\n              <el-card\n                shadow=\"never\"\n                class=\"mb-16\"\n                :class=\"form.knowledge_setting.search_mode === 'keywords' ? 'border-active' : ''\"\n              >\n                <el-radio value=\"keywords\" size=\"large\">\n                  <p class=\"mb-4\">\n                    {{ $t('views.application.dialog.fullTextSearch') }}\n                  </p>\n                  <el-text type=\"info\">{{\n                    $t('views.application.dialog.fullTextSearchTooltip')\n                  }}</el-text>\n                </el-radio>\n              </el-card>\n              <el-card\n                shadow=\"never\"\n                :class=\"form.knowledge_setting.search_mode === 'blend' ? 'border-active' : ''\"\n              >\n                <el-radio value=\"blend\" size=\"large\">\n                  <p class=\"mb-4\">\n                    {{ $t('views.application.dialog.hybridSearch') }}\n                  </p>\n                  <el-text type=\"info\">{{\n                    $t('views.application.dialog.hybridSearchTooltip')\n                  }}</el-text>\n                </el-radio>\n              </el-card>\n            </el-radio-group>\n          </el-form-item>\n          <el-row :gutter=\"10\">\n            <el-col :span=\"12\">\n              <el-form-item>\n                <template #label>\n                  <div class=\"flex align-center\">\n                    <span class=\"mr-4\">{{\n                      $t('views.application.dialog.similarityThreshold')\n                    }}</span>\n                    <el-tooltip\n                      effect=\"dark\"\n                      :content=\"$t('views.application.dialog.similarityTooltip')\"\n                      placement=\"right\"\n                    >\n                      <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                    </el-tooltip>\n                  </div>\n                </template>\n                <el-input-number\n                  v-model=\"form.knowledge_setting.similarity\"\n                  :min=\"0\"\n                  :max=\"form.knowledge_setting.search_mode === 'blend' ? 2 : 1\"\n                  :precision=\"3\"\n                  :step=\"0.1\"\n                  :value-on-clear=\"0\"\n                  controls-position=\"right\"\n                  class=\"w-full\"\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"12\">\n              <el-form-item :label=\"$t('views.application.dialog.topReferences')\">\n                <el-input-number\n                  v-model=\"form.knowledge_setting.top_n\"\n                  :min=\"1\"\n                  :max=\"10000\"\n                  :value-on-clear=\"1\"\n                  controls-position=\"right\"\n                  class=\"w-full\"\n                />\n              </el-form-item>\n            </el-col>\n          </el-row>\n          <el-form-item :label=\"$t('views.application.dialog.maxCharacters')\">\n            <el-slider\n              v-model=\"form.knowledge_setting.max_paragraph_char_number\"\n              show-input\n              :show-input-controls=\"false\"\n              :min=\"500\"\n              :max=\"100000\"\n              class=\"custom-slider\"\n            />\n          </el-form-item>\n\n          <el-form-item\n            v-if=\"!isWorkflowType\"\n            :label=\"$t('views.application.dialog.noReferencesAction')\"\n          >\n            <el-form\n              label-position=\"top\"\n              ref=\"noReferencesformRef\"\n              :model=\"noReferencesform\"\n              :rules=\"noReferencesRules\"\n              :hide-required-asterisk=\"true\"\n              class=\"w-full\"\n            >\n              <el-radio-group\n                v-model=\"form.knowledge_setting.no_references_setting.status\"\n                class=\"radio-block-avatar\"\n              >\n                <el-radio value=\"ai_questioning\">\n                  <p>\n                    {{ $t('views.application.dialog.continueQuestioning') }}\n                  </p>\n                </el-radio>\n\n                <el-radio value=\"designated_answer\">\n                  <p>{{ $t('views.application.dialog.provideAnswer') }}</p>\n                  <el-form-item\n                    v-if=\"\n                      form.knowledge_setting.no_references_setting.status === 'designated_answer'\n                    \"\n                    prop=\"designated_answer\"\n                  >\n                    <el-input\n                      v-model=\"noReferencesform.designated_answer\"\n                      :rows=\"2\"\n                      type=\"textarea\"\n                      maxlength=\"2048\"\n                      :placeholder=\"defaultValue['designated_answer']\"\n                    />\n                  </el-form-item>\n                </el-radio>\n              </el-radio-group>\n            </el-form>\n          </el-form-item>\n          <el-form-item @click.prevent v-if=\"!isWorkflowType\">\n            <template #label>\n              <div class=\"flex align-center\">\n                <span class=\"mr-4\">{{\n                  $t('views.application.form.problemOptimization.label')\n                }}</span>\n              </div>\n            </template>\n            <el-switch size=\"small\" v-model=\"form.problem_optimization\"></el-switch>\n          </el-form-item>\n          <el-form-item\n            v-if=\"form.problem_optimization\"\n            :label=\"$t('common.prompt.label')\"\n          >\n            <el-input\n              v-model=\"form.problem_optimization_prompt\"\n              :rows=\"6\"\n              type=\"textarea\"\n              maxlength=\"2048\"\n              :placeholder=\"defaultPrompt\"\n            />\n          </el-form-item>\n        </el-form>\n      </div>\n    </el-scrollbar>\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit(noReferencesformRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive } from 'vue'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { isWorkFlow } from '@/utils/application'\nimport { t } from '@/locales'\nimport { cloneDeep } from 'lodash'\nconst emit = defineEmits(['refresh'])\n\nconst paramFormRef = ref()\nconst noReferencesformRef = ref()\n\nconst defaultValue = {\n  ai_questioning: '{question}',\n  designated_answer: t('views.application.dialog.designated_answer'),\n}\n\nconst defaultPrompt =\n  t('views.application.dialog.defaultPrompt1', {\n    question: '{question}',\n  }) +\n  '<data></data>' +\n  t('views.application.dialog.defaultPrompt2')\n\nconst form = ref<any>({\n  knowledge_setting: {\n    search_mode: 'embedding',\n    top_n: 3,\n    similarity: 0.6,\n    max_paragraph_char_number: 5000,\n    no_references_setting: {\n      status: 'ai_questioning',\n      value: '{question}',\n    },\n  },\n  problem_optimization: false,\n  problem_optimization_prompt: defaultPrompt,\n})\n\nconst noReferencesform = ref<any>({\n  ai_questioning: defaultValue['ai_questioning'],\n  designated_answer: defaultValue['designated_answer'],\n})\n\nconst noReferencesRules = reactive<FormRules<any>>({\n  ai_questioning: [\n    {\n      required: true,\n      message: t('views.application.form.aiModel.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n  designated_answer: [\n    {\n      required: true,\n      message: t('common.prompt.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\n\nconst isWorkflowType = ref(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    noReferencesform.value = {\n      ai_questioning: defaultValue['ai_questioning'],\n      designated_answer: defaultValue['designated_answer'],\n    }\n    noReferencesformRef.value?.clearValidate()\n  }\n})\n\nconst open = (data: any, type?: string) => {\n  isWorkflowType.value = isWorkFlow(type)\n  form.value = {\n    knowledge_setting: cloneDeep(data.knowledge_setting),\n    problem_optimization: data.problem_optimization,\n    problem_optimization_prompt: data.problem_optimization_prompt,\n  }\n  if (!isWorkflowType.value) {\n    noReferencesform.value[form.value.knowledge_setting.no_references_setting.status] =\n      form.value.knowledge_setting.no_references_setting.value\n  }\n\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (isWorkflowType.value) {\n    delete form.value['no_references_setting']\n    emit('refresh', form.value)\n    dialogVisible.value = false\n  } else {\n    if (!formEl) return\n    await formEl.validate((valid, fields) => {\n      if (valid) {\n        form.value.knowledge_setting.no_references_setting.value =\n          noReferencesform.value[form.value.knowledge_setting.no_references_setting.status]\n        emit('refresh', form.value)\n        dialogVisible.value = false\n      }\n    })\n  }\n}\n\nfunction changeHandle(val: string) {\n  if (val === 'keywords') {\n    form.value.knowledge_setting.similarity = 0\n  } else {\n    form.value.knowledge_setting.similarity = 0.6\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/application/component/ReasoningParamSettingDialog.vue",
    "content": "<template>\n  <el-dialog\n    align-center\n    :title=\"$t('common.setting')\"\n    class=\"param-dialog\"\n    v-model=\"dialogVisible\"\n    style=\"width: 550px\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form label-position=\"top\" ref=\"paramFormRef\" :model=\"form\">\n      <el-text type=\"info\" class=\"color-secondary\">{{\n        $t('views.application.form.reasoningContent.tooltip')\n      }}</el-text>\n      <el-row class=\"mt-16\" :gutter=\"20\">\n        <el-col :span=\"12\">\n          <el-form-item\n            :label=\"$t('views.application.form.reasoningContent.start')\"\n          >\n            <el-input\n              type=\"textarea\"\n              v-model=\"form.reasoning_content_start\"\n              :rows=\"6\"\n              maxlength=\"50\"\n              placeholder=\"<think>\"\n            />\n          </el-form-item>\n        </el-col>\n        <el-col :span=\"12\">\n          <el-form-item :label=\"$t('views.application.form.reasoningContent.end')\">\n            <el-input\n              type=\"textarea\"\n              v-model=\"form.reasoning_content_end\"\n              :rows=\"6\"\n              maxlength=\"50\"\n              placeholder=\"</think>\"\n            />\n          </el-form-item>\n        </el-col>\n      </el-row>\n    </el-form>\n\n    <template #footer>\n      <span class=\"dialog-footer p-16\">\n        <el-button @click.prevent=\"dialogVisible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit()\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive } from 'vue'\n\nconst emit = defineEmits(['refresh'])\n\nconst form = ref<any>({\n  reasoning_content_start: '<think>',\n  reasoning_content_end: '</think>'\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      reasoning_content_start: '<think>',\n      reasoning_content_end: '</think>'\n    }\n  }\n})\n\nconst open = (data: any) => {\n  form.value = { ...form.value, ...data }\n  dialogVisible.value = true\n}\n\nconst submit = () => {\n  emit('refresh', form.value)\n  dialogVisible.value = false\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application/component/STTModelParamSettingDialog.vue",
    "content": "<template>\n    <el-dialog\n      align-center\n      :title=\"$t('common.paramSetting')\"\n      v-model=\"dialogVisible\"\n      style=\"width: 550px\"\n      append-to-body\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n    >\n    <DynamicsForm\n      v-model=\"form_data\"\n      :model=\"form_data\"      \n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      :render_data=\"model_form_field\"\n      ref=\"dynamicsFormRef\"\n    >\n    </DynamicsForm>\n\n    <template #footer>\n      <div class=\"flex-between\">\n        <span class=\"dialog-footer\">\n          <el-button @click.prevent=\"dialogVisible = false\">\n            {{ $t('common.cancel') }}\n          </el-button>\n          <el-button type=\"primary\" @click=\"submit\" :loading=\"loading\">\n            {{ $t('common.confirm') }}\n          </el-button>\n        </span>\n      </div>\n    </template>\n    </el-dialog>\n</template>\n\n\n\n<script setup lang=\"ts\">\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport { ref, computed } from 'vue'\nimport { useRoute } from 'vue-router'\n\n\nconst route = useRoute()\n\nconst {\n  params: { id }\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst form_data = ref<any>({})\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst stt_model_id = ref<string>('')\nconst loading = ref<boolean>(false)\nconst model_form_field = ref<Array<FormField>>([])\nconst emit = defineEmits(['refresh'])\n\nconst open = (model_id: string, application_id?: string, model_setting_data?: any) => {\n  form_data.value = {}\n  stt_model_id.value = model_id\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getModelParamsForm(model_id, loading)  \n    .then(( ok: any ) => {\n      model_form_field.value = ok.data\n      const resp = ok.data\n        .map((item: any) => ({\n          [item.field]: item.show_default_value !== false ? item.default_value : undefined,\n        }))\n        .reduce((x: any, y: any) => ({ ...x, ...y }), {})\n\n      if (model_setting_data) {\n        Object.keys(model_setting_data).forEach((key) => {\n          if (!(key in resp)) {\n            delete model_setting_data[key]\n          }\n        })\n      }\n      model_setting_data = { ...resp, ...model_setting_data }\n      // 渲染动态表单\n      dynamicsFormRef.value?.render(model_form_field.value, model_setting_data)\n    })\n  dialogVisible.value = true\n}\n\nconst submit = async () => {\n  dynamicsFormRef.value?.validate().then(() => {\n      emit('refresh', form_data.value)\n      dialogVisible.value = false\n  })\n}\n\nconst reset_default = (model_id: string, application_id?: string) => {\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getModelParamsForm(model_id, loading)\n    .then((ok: any) => {\n      model_form_field.value = ok.data\n      const model_setting_data = ok.data\n        .map((item: any) => ({\n          [item.field]: item.show_default_value !== false ? item.default_value : undefined,\n        }))\n        .reduce((x: any, y: any) => (({ ...x, ...y })), {})\n\n        emit('refresh', model_setting_data)\n    })\n}\n\n\n\ndefineExpose({ open, reset_default })\n\n</script>\n\n<style lang=\"scss\" scoped></style>"
  },
  {
    "path": "ui/src/views/application/component/TTSModeParamSettingDialog.vue",
    "content": "<template>\n  <el-dialog\n    align-center\n    :title=\"$t('common.paramSetting')\"\n    v-model=\"dialogVisible\"\n    style=\"width: 550px\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <DynamicsForm\n      v-model=\"form_data\"\n      :model=\"form_data\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      :render_data=\"model_form_field\"\n      ref=\"dynamicsFormRef\"\n    >\n    </DynamicsForm>\n\n    <template #footer>\n      <div class=\"flex-between\">\n        <el-button @click=\"testPlay\" :loading=\"playLoading\">\n          <AppIcon iconName=\"app-video-play\" class=\"mr-4\"></AppIcon>\n          {{ $t('views.application.form.voicePlay.listeningTest') }}\n        </el-button>\n\n        <span class=\"dialog-footer\">\n          <el-button @click.prevent=\"dialogVisible = false\">\n            {{ $t('common.cancel') }}\n          </el-button>\n          <el-button type=\"primary\" @click=\"submit\" :loading=\"loading\">\n            {{ $t('common.confirm') }}\n          </el-button>\n        </span>\n      </div>\n    </template>\n  </el-dialog>\n  <!-- 先渲染，不然不能播放   -->\n  <audio ref=\"audioPlayer\" controls hidden=\"hidden\"></audio>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport { useRoute } from 'vue-router'\nimport { MsgError } from '@/utils/message'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst tts_model_id = ref('')\nconst model_form_field = ref<Array<FormField>>([])\nconst emit = defineEmits(['refresh'])\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst form_data = ref<any>({})\nconst dialogVisible = ref(false)\nconst loading = ref(false)\nconst playLoading = ref(false)\n\nconst open = (model_id: string, application_id?: string, model_setting_data?: any) => {\n  form_data.value = {}\n  tts_model_id.value = model_id\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getModelParamsForm(model_id, loading)\n    .then((ok: any) => {\n      model_form_field.value = ok.data\n      const resp = ok.data\n        .map((item: any) => ({\n          [item.field]: item.show_default_value !== false ? item.default_value : undefined,\n        }))\n        .reduce((x: any, y: any) => ({ ...x, ...y }), {})\n      // 删除不存在的字段\n      if (model_setting_data) {\n        Object.keys(model_setting_data).forEach((key) => {\n          if (!(key in resp)) {\n            delete model_setting_data[key]\n          }\n        })\n      }\n      model_setting_data = { ...resp, ...model_setting_data }\n      // 渲染动态表单\n      dynamicsFormRef.value?.render(model_form_field.value, model_setting_data)\n    })\n  dialogVisible.value = true\n}\n\nconst reset_default = (model_id: string, application_id?: string) => {\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getModelParamsForm(model_id, loading)\n    .then((ok: any) => {\n      model_form_field.value = ok.data\n      const model_setting_data = ok.data\n        .map((item: any) => ({\n          [item.field]: item.show_default_value !== false ? item.default_value : undefined,\n        }))\n        .reduce((x: any, y: any) => ({ ...x, ...y }), {})\n\n      emit('refresh', model_setting_data)\n    })\n}\n\nconst submit = async () => {\n  dynamicsFormRef.value?.validate().then(() => {\n    emit('refresh', form_data.value)\n    dialogVisible.value = false\n  })\n}\n\nconst audioPlayer = ref<HTMLAudioElement | null>(null)\nconst testPlay = () => {\n  const data = {\n    ...form_data.value,\n    tts_model_id: tts_model_id.value,\n  }\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .playDemoText(id as string, data, playLoading)\n    .then(async (res: any) => {\n      if (res.type === 'application/json') {\n        const text = await res.text()\n        MsgError(text)\n        return\n      }\n      // 创建 Blob 对象\n      const blob = new Blob([res], { type: 'audio/mp3' })\n\n      // 创建对象 URL\n      const url = URL.createObjectURL(blob)\n\n      // 检查 audioPlayer 是否已经引用了 DOM 元素\n      if (audioPlayer.value instanceof HTMLAudioElement) {\n        audioPlayer.value.src = url\n        audioPlayer.value.play() // 自动播放音频\n      } else {\n        console.error('audioPlayer.value is not an instance of HTMLAudioElement')\n      }\n    })\n    .catch((err: any) => {\n      console.log('err: ', err)\n    })\n}\n\ndefineExpose({ open, reset_default })\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application/component/ToolDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    width=\"1000\"\n    append-to-body\n    class=\"addTool-dialog\"\n    align-center\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <template #header=\"{ titleId, titleClass }\">\n      <div class=\"flex-between mb-8\">\n        <div class=\"flex\">\n          <h4 :id=\"titleId\" :class=\"titleClass\" class=\"mr-8\">\n            {{ $t('views.tool.settingTool') }}\n          </h4>\n        </div>\n\n        <el-button link class=\"mr-24\" @click=\"refresh\">\n          <el-icon :size=\"18\"><Refresh /></el-icon>\n        </el-button>\n      </div>\n    </template>\n    <!-- 共享的知识库工作流中，只能查共享的工具，这里不需要展示左边的树，只需展示右边的内容   -->\n    <LayoutContainer class=\"application-manage\" :show-left=\"apiType !== 'systemShare'\">\n      <template #left>\n        <folder-tree\n          :data=\"folderList\"\n          :currentNodeKey=\"currentFolder?.id\"\n          @handleNodeClick=\"folderClickHandle\"\n          v-loading=\"folderLoading\"\n          :canOperation=\"false\"\n          showShared\n          :shareTitle=\"$t('views.shared.shared_tool')\"\n          :treeStyle=\"{ height: 'calc(100vh - 240px)' }\"\n        />\n      </template>\n      <div class=\"layout-bg\">\n        <div class=\"flex-between p-16 ml-8\">\n          <h4>{{ currentFolder?.name }}</h4>\n          <el-input\n            v-model=\"searchValue\"\n            :placeholder=\"$t('common.search')\"\n            prefix-icon=\"Search\"\n            class=\"w-240 mr-8\"\n            clearable\n          />\n        </div>\n\n        <el-scrollbar>\n          <div class=\"p-16-24 pt-0\" style=\"height: calc(100vh - 200px)\">\n            <el-row :gutter=\"12\" v-loading=\"apiLoading\" v-if=\"searchData.length\">\n              <el-col :span=\"12\" v-for=\"(item, index) in searchData\" :key=\"index\" class=\"mb-16\">\n                <el-popover\n                  placement=\"bottom-start\"\n                  :width=\"400\"\n                  popper-style=\"--el-popover-border-radius:8px;--el-popover-padding:16px 16px 0\"\n                  :show-after=\"500\"\n                >\n                  <template #reference>\n                    <CardCheckbox\n                      value-field=\"id\"\n                      :data=\"item\"\n                      v-model=\"checkList\"\n                      @change=\"changeHandle\"\n                    >\n                      <template #icon>\n                        <el-avatar\n                          v-if=\"item?.icon\"\n                          shape=\"square\"\n                          :size=\"32\"\n                          style=\"background: none\"\n                          class=\"mr-8\"\n                        >\n                          <img :src=\"resetUrl(item?.icon)\" alt=\"\" />\n                        </el-avatar>\n                        <ToolIcon v-else :size=\"32\" :type=\"item?.tool_type\" />\n                      </template>\n                      <span class=\"ellipsis cursor ml-12\" :title=\"item.name\"> {{ item.name }}</span>\n                    </CardCheckbox>\n                  </template>\n                  <template #default>\n                    <CardBox\n                      :title=\"item.name\"\n                      :description=\"item.desc\"\n                      class=\"cursor border-none popover-card-box\"\n                      shadow=\"never\"\n                      style=\"--el-card-padding: 0px; --card-min-height: 148px\"\n                    >\n                      <template #icon>\n                        <el-avatar\n                          v-if=\"item?.icon\"\n                          shape=\"square\"\n                          :size=\"32\"\n                          style=\"background: none\"\n                        >\n                          <img :src=\"resetUrl(item?.icon)\" alt=\"\" />\n                        </el-avatar>\n                        <ToolIcon v-else :size=\"32\" :type=\"item?.tool_type\" />\n                      </template>\n                      <template #title>\n                        <div class=\"flex align-center\" style=\"margin-top: 1px\">\n                          <span class=\"ellipsis-1\" :title=\"item.name\">\n                            {{ item.name }}\n                          </span>\n                          <el-tag\n                            v-if=\"item.version\"\n                            size=\"small\"\n                            class=\"ml-4\"\n                            type=\"info\"\n                            effect=\"plain\"\n                          >\n                            {{ item.version }}\n                          </el-tag>\n                        </div>\n                      </template>\n                      <template #subTitle>\n                        <el-text class=\"color-secondary lighter flex align-center\" size=\"small\">\n                          <span\n                            :title=\"i18n_name(item.nick_name)\"\n                            class=\"ellipsis\"\n                            style=\"max-width: 90px\"\n                          >\n                            {{ i18n_name(item.nick_name) }}\n                          </span>\n                          <span class=\"ml-4 mr-4\"> {{ $t('common.createdIn') }}</span>\n                          <span> {{ dateFormat(item.create_time) }}</span>\n                        </el-text>\n                      </template>\n\n                      <template #footer>\n                        <div v-if=\"item.is_active\" class=\"flex align-center\">\n                          <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n                            <SuccessFilled />\n                          </el-icon>\n                          <span class=\"color-secondary\">\n                            {{ $t('common.status.enabled') }}\n                          </span>\n                        </div>\n                        <div v-else class=\"flex align-center\">\n                          <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n                          <span class=\"color-secondary\">\n                            {{ $t('common.status.disabled') }}\n                          </span>\n                        </div>\n                      </template>\n                    </CardBox>\n                  </template>\n                </el-popover>\n              </el-col>\n            </el-row>\n            <el-empty :description=\"$t('common.noData')\" v-else />\n          </div>\n        </el-scrollbar>\n      </div>\n    </LayoutContainer>\n\n    <template #footer>\n      <div class=\"flex-between\">\n        <div class=\"flex\">\n          <el-text type=\"info\" class=\"color-secondary mr-8\" v-if=\"checkList.length > 0\">\n            {{ $t('common.selected') }} {{ checkList.length }}\n          </el-text>\n          <el-button link type=\"primary\" v-if=\"checkList.length > 0\" @click=\"clearCheck\">\n            {{ $t('common.clear') }}\n          </el-button>\n        </div>\n        <span>\n          <el-button @click.prevent=\"dialogVisible = false\">\n            {{ $t('common.cancel') }}\n          </el-button>\n          <el-button type=\"primary\" @click=\"submitHandle\">\n            {{ $t('common.add') }}\n          </el-button>\n        </span>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { resetUrl, i18n_name } from '@/utils/common'\nimport { dateFormat } from '@/utils/time'\nconst route = useRoute()\n\nconst props = defineProps({\n  tool_type: {\n    type: String,\n    default: 'CUSTOM',\n  },\n})\nconst emit = defineEmits(['refresh'])\nconst { folder, user } = useStore()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst checkList = ref<Array<string>>([])\nconst searchValue = ref('')\nconst searchData = ref<Array<any>>([])\nconst toolList = ref<Array<any>>([])\nconst apiLoading = ref(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    checkList.value = []\n    searchValue.value = ''\n    searchData.value = []\n    toolList.value = []\n  }\n})\n\nwatch(searchValue, (val) => {\n  if (val) {\n    searchData.value = toolList.value.filter((v) => v.name.includes(val))\n  } else {\n    searchData.value = toolList.value\n  }\n})\n\nfunction changeHandle() {}\nfunction clearCheck() {\n  checkList.value = []\n}\n\nconst open = (checked: any) => {\n  checkList.value = checked || []\n  if (apiType.value === 'systemShare') {\n    getList()\n  } else {\n    getFolder()\n  }\n  dialogVisible.value = true\n}\n\nconst submitHandle = () => {\n  emit('refresh', {\n    tool_ids: checkList.value,\n  })\n  dialogVisible.value = false\n}\n\nconst refresh = () => {\n  searchValue.value = ''\n  toolList.value = []\n  getList()\n}\n\nconst folderList = ref<any[]>([])\nconst currentFolder = ref<any>({})\nconst folderLoading = ref(false)\n// 文件\nfunction folderClickHandle(row: any) {\n  if (row.id === currentFolder.value?.id) {\n    return\n  }\n  currentFolder.value = row\n  getList()\n}\n\nfunction getFolder() {\n  const params = {}\n  folder.asyncGetFolder('TOOL', params, apiType.value, folderLoading).then((res: any) => {\n    folderList.value = res.data\n    currentFolder.value = res.data?.[0] || {}\n    getList()\n  })\n}\n\nfunction getList() {\n  const folder_id = currentFolder.value?.id || user.getWorkspaceId()\n  loadSharedApi({\n    type: 'tool',\n    isShared: folder_id === 'share',\n    systemType: apiType.value,\n  })\n    .getToolList({\n      folder_id: folder_id,\n      tool_type: props.tool_type,\n    })\n    .then((res: any) => {\n      toolList.value = res.data?.tools || res.data || []\n      toolList.value = toolList.value?.filter((item: any) => item.is_active)\n      searchData.value = res.data.tools || res.data\n      searchData.value = searchData.value?.filter((item: any) => item.is_active)\n    })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\">\n.addTool-dialog {\n  padding: 0;\n  .el-dialog__header {\n    padding: 12px 20px 4px 24px;\n    border-bottom: 1px solid var(--el-border-color-light);\n  }\n  .el-dialog__footer {\n    padding: 12px 24px 12px 24px;\n    border-top: 1px solid var(--el-border-color-light);\n  }\n\n  .el-dialog__headerbtn {\n    top: 2px;\n    right: 6px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application/index.vue",
    "content": "<template>\n  <LayoutContainer showCollapse resizable class=\"application-manage\">\n    <template #left>\n      <h4 class=\"p-12-16 pb-0 mt-12\">{{ $t('views.application.title') }}</h4>\n\n      <folder-tree\n        :source=\"SourceTypeEnum.APPLICATION\"\n        :data=\"folderList\"\n        :currentNodeKey=\"folder.currentFolder?.id\"\n        @handleNodeClick=\"folderClickHandle\"\n        @refreshTree=\"refreshFolder\"\n        :draggable=\"true\"\n      />\n    </template>\n    <ContentContainer>\n      <template #header>\n        <FolderBreadcrumb :folderList=\"folderList\" @click=\"folderClickHandle\" />\n      </template>\n      <template #search>\n        <div class=\"flex\">\n          <div class=\"flex-between complex-search\">\n            <el-select\n              class=\"complex-search__left\"\n              v-model=\"search_type\"\n              style=\"width: 120px\"\n              @change=\"search_type_change\"\n            >\n              <el-option :label=\"$t('common.creator')\" value=\"create_user\" />\n\n              <el-option :label=\"$t('common.name')\" value=\"name\" />\n\n              <el-option :label=\"$t('views.application.publishStatus')\" value=\"publish_status\" />\n            </el-select>\n            <el-input\n              v-if=\"search_type === 'name'\"\n              v-model=\"search_form.name\"\n              @change=\"searchHandle\"\n              :placeholder=\"$t('common.searchBar.placeholder')\"\n              style=\"width: 220px\"\n              clearable\n            />\n            <el-select\n              v-else-if=\"search_type === 'create_user'\"\n              v-model=\"search_form.create_user\"\n              @change=\"searchHandle\"\n              filterable\n              clearable\n              style=\"width: 220px\"\n            >\n              <el-option v-for=\"u in user_options\" :key=\"u.id\" :value=\"u.id\" :label=\"u.nick_name\" />\n            </el-select>\n            <el-select\n              v-else-if=\"search_type === 'publish_status'\"\n              v-model=\"search_form.publish_status\"\n              @change=\"searchHandle\"\n              filterable\n              clearable\n              style=\"width: 220px\"\n            >\n              <el-option :label=\"$t('common.status.published')\" value=\"published\" />\n              <el-option :label=\"$t('common.status.unpublished')\" value=\"unpublished\" />\n            </el-select>\n          </div>\n          <el-button\n            class=\"ml-8\"\n            v-if=\"permissionPrecise.create()\"\n            @click=\"openTemplateStoreDialog()\"\n          >\n            <AppIcon iconName=\"app-template-center\" class=\"mr-4\" />\n            {{ $t('workflow.setting.templateCenter') }}\n          </el-button>\n          <el-dropdown trigger=\"click\" v-if=\"permissionPrecise.create()\">\n            <el-button type=\"primary\" class=\"ml-8\">\n              {{ $t('common.create') }}\n              <el-icon class=\"el-icon--right\">\n                <arrow-down />\n              </el-icon>\n            </el-button>\n            <template #dropdown>\n              <el-dropdown-menu class=\"create-dropdown\">\n                <el-dropdown-item @click=\"openCreateDialog('SIMPLE')\">\n                  <div class=\"flex\">\n                    <el-avatar shape=\"square\" class=\"avatar-blue mt-4\" :size=\"32\">\n                      <img\n                        src=\"@/assets/application/icon_simple_application.svg\"\n                        style=\"width: 65%\"\n                        alt=\"\"\n                      />\n                    </el-avatar>\n                    <div class=\"pre-wrap ml-8\">\n                      <div class=\"lighter\">\n                        {{ $t('views.application.simpleAgent') }}\n                      </div>\n                      <el-text type=\"info\" size=\"small\" class=\"color-secondary\"\n                        >{{ $t('views.application.simplePlaceholder') }}\n                      </el-text>\n                    </div>\n                  </div>\n                </el-dropdown-item>\n                <el-dropdown-item @click=\"openCreateDialog('WORK_FLOW')\">\n                  <div class=\"flex\">\n                    <el-avatar shape=\"square\" class=\"avatar-purple mt-4\" :size=\"32\">\n                      <img\n                        src=\"@/assets/application/icon_workflow_application.svg\"\n                        style=\"width: 65%\"\n                        alt=\"\"\n                      />\n                    </el-avatar>\n                    <div class=\"pre-wrap ml-8\">\n                      <div class=\"lighter\">{{ $t('views.application.AdvancedAgent') }}</div>\n                      <el-text type=\"info\" size=\"small\" class=\"color-secondary\"\n                        >{{ $t('views.application.advancedPlaceholder') }}\n                      </el-text>\n                    </div>\n                  </div>\n                </el-dropdown-item>\n                <el-upload\n                  class=\"import-button\"\n                  ref=\"elUploadRef\"\n                  :file-list=\"[]\"\n                  action=\"#\"\n                  multiple\n                  :auto-upload=\"false\"\n                  :show-file-list=\"false\"\n                  :limit=\"1\"\n                  :on-change=\"(file: any, fileList: any) => importApplication(file)\"\n                >\n                  <el-dropdown-item>\n                    <div class=\"flex align-center w-full\">\n                      <el-avatar shape=\"square\" class=\"mt-4\" :size=\"32\" style=\"background: none\">\n                        <img src=\"@/assets/icon_import.svg\" alt=\"\" />\n                      </el-avatar>\n                      <div class=\"pre-wrap ml-8\">\n                        <div class=\"lighter\">{{ $t('views.application.importApplication') }}</div>\n                      </div>\n                    </div>\n                  </el-dropdown-item>\n                </el-upload>\n                <el-dropdown-item @click=\"openCreateFolder\" divided>\n                  <div class=\"flex align-center\">\n                    <AppIcon iconName=\"app-folder\" style=\"font-size: 32px\"></AppIcon>\n                    <div class=\"pre-wrap ml-4\">\n                      <div class=\"lighter\">\n                        {{ $t('components.folder.addFolder') }}\n                      </div>\n                    </div>\n                  </div>\n                </el-dropdown-item>\n              </el-dropdown-menu>\n            </template>\n          </el-dropdown>\n        </div>\n      </template>\n      <div\n        v-loading.fullscreen.lock=\"paginationConfig.current_page === 1 && loading\"\n        style=\"max-height: calc(100vh - 120px)\"\n      >\n        <InfiniteScroll\n          :size=\"applicationList.length\"\n          :total=\"paginationConfig.total\"\n          :page_size=\"paginationConfig.page_size\"\n          v-model:current_page=\"paginationConfig.current_page\"\n          @load=\"getList\"\n          :loading=\"loading\"\n        >\n          <el-row v-if=\"applicationList.length > 0\" :gutter=\"15\" class=\"w-full\">\n            <template v-for=\"(item, index) in applicationList\" :key=\"index\">\n              <el-col :xs=\"24\" :sm=\"12\" :md=\"12\" :lg=\"8\" :xl=\"6\" class=\"mb-16\">\n                <CardBox\n                  :title=\"item.name\"\n                  :description=\"item.desc\"\n                  class=\"cursor\"\n                  @click=\"goApp(item)\"\n                >\n                  <template #icon>\n                    <el-avatar shape=\"square\" :size=\"32\" style=\"background: none\">\n                      <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n                    </el-avatar>\n                  </template>\n                  <template #subTitle>\n                    <el-text class=\"color-secondary lighter flex align-center\" size=\"small\">\n                      <span\n                        :title=\"i18n_name(item.nick_name)\"\n                        class=\"ellipsis\"\n                        style=\"max-width: 90px\"\n                      >\n                        {{ i18n_name(item.nick_name) }}\n                      </span>\n                      <span class=\"ml-4 mr-4\"> {{ $t('common.createdIn') }}</span>\n                      <span> {{ dateFormat(item.create_time) }}</span>\n                    </el-text>\n                  </template>\n                  <template #tag>\n                    <el-tag size=\"small\" v-if=\"isWorkFlow(item.type)\" class=\"warning-tag\">\n                      {{ $t('views.application.senior') }}\n                    </el-tag>\n                    <el-tag size=\"small\" class=\"blue-tag\" v-else>\n                      {{ $t('views.application.simple') }}\n                    </el-tag>\n                  </template>\n\n                  <template #footer>\n                    <div v-if=\"item.is_publish\" class=\"flex align-center\">\n                      <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n                        <SuccessFilled />\n                      </el-icon>\n                      <span class=\"color-secondary\">\n                        {{ $t('common.status.published') }}\n                      </span>\n                      <el-divider direction=\"vertical\" />\n                      <AppIcon iconName=\"app-clock\" class=\"color-secondary mr-8\"></AppIcon>\n\n                      <span class=\"color-secondary\">{{ dateFormat(item.update_time) }}</span>\n                    </div>\n                    <div v-else class=\"flex align-center\">\n                      <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n                      <span class=\"color-secondary\">\n                        {{ $t('common.status.unpublished') }}\n                      </span>\n                    </div>\n                  </template>\n                  <template #mouseEnter>\n                    <div @click.stop>\n                      <el-tooltip\n                        effect=\"dark\"\n                        :content=\"$t('views.application.operation.toChat')\"\n                        placement=\"top\"\n                      >\n                        <el-button text @click.stop=\"toChat(item)\">\n                          <AppIcon iconName=\"app-create-chat\" class=\"color-secondary\"></AppIcon>\n                        </el-button>\n                      </el-tooltip>\n                      <el-divider direction=\"vertical\" />\n                      <el-dropdown trigger=\"click\">\n                        <el-button text @click.stop>\n                          <AppIcon iconName=\"app-more\"></AppIcon>\n                        </el-button>\n                        <template #dropdown>\n                          <el-dropdown-menu>\n                            <el-dropdown-item\n                              @mousedown.stop=\"settingApplication($event, item)\"\n                              v-if=\"permissionPrecise.edit(item.id)\"\n                              @click.stop\n                            >\n                              <AppIcon iconName=\"app-setting\" class=\"color-secondary\"></AppIcon>\n                              {{ $t('common.setting') }}\n                            </el-dropdown-item>\n\n                            <el-dropdown-item\n                              @click.stop=\"openAuthorization(item)\"\n                              v-if=\"permissionPrecise.auth(item.id)\"\n                            >\n                              <AppIcon\n                                iconName=\"app-resource-authorization\"\n                                class=\"color-secondary\"\n                              ></AppIcon>\n                              {{ $t('views.system.resourceAuthorization.title') }}\n                            </el-dropdown-item>\n                            <el-dropdown-item\n                              @click.stop=\"openTriggerDrawer(item)\"\n                              v-if=\"\n                                apiType === 'workspace' && permissionPrecise.trigger_read(item.id)\n                              \"\n                            >\n                              <AppIcon iconName=\"app-trigger\" class=\"color-secondary\"></AppIcon>\n                              {{ $t('views.trigger.title') }}\n                            </el-dropdown-item>\n                            <el-dropdown-item\n                              @click.stop=\"openMoveToDialog(item)\"\n                              v-if=\"permissionPrecise.edit(item.id) && apiType === 'workspace'\"\n                            >\n                              <AppIcon iconName=\"app-migrate\" class=\"color-secondary\"></AppIcon>\n                              {{ $t('common.moveTo') }}\n                            </el-dropdown-item>\n                            <el-dropdown-item\n                              @click=\"copyApplication(item)\"\n                              v-if=\"permissionPrecise.create()\"\n                            >\n                              <AppIcon iconName=\"app-copy\" class=\"color-secondary\"></AppIcon>\n                              {{ $t('common.copy') }}\n                            </el-dropdown-item>\n                            <el-dropdown-item\n                              divided\n                              @click.stop=\"exportApplication(item)\"\n                              v-if=\"permissionPrecise.export(item.id)\"\n                            >\n                              <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                              {{ $t('common.export') }}\n                            </el-dropdown-item>\n                            <el-dropdown-item\n                              divided\n                              @click.stop=\"deleteApplication(item)\"\n                              v-if=\"permissionPrecise.delete(item.id)\"\n                            >\n                              <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                              {{ $t('common.delete') }}\n                            </el-dropdown-item>\n                          </el-dropdown-menu>\n                        </template>\n                      </el-dropdown>\n                    </div>\n                  </template>\n                </CardBox>\n              </el-col>\n            </template>\n          </el-row>\n          <el-empty :description=\"$t('common.noData')\" v-else />\n        </InfiniteScroll>\n      </div>\n    </ContentContainer>\n    <CreateApplicationDialog ref=\"CreateApplicationDialogRef\" />\n    <CopyApplicationDialog ref=\"CopyApplicationDialogRef\" />\n    <CreateFolderDialog ref=\"CreateFolderDialogRef\" @refresh=\"refreshFolder\" />\n    <MoveToDialog\n      ref=\"MoveToDialogRef\"\n      :source=\"SourceTypeEnum.APPLICATION\"\n      @refresh=\"refreshApplicationList\"\n      v-if=\"apiType === 'workspace'\"\n    />\n    <ResourceAuthorizationDrawer\n      :type=\"SourceTypeEnum.APPLICATION\"\n      ref=\"ResourceAuthorizationDrawerRef\"\n    />\n    <TemplateStoreDialog ref=\"templateStoreDialogRef\" :api-type=\"apiType\" @refresh=\"getList\" />\n    <ResourceTriggerDrawer\n      ref=\"resourceTriggerDrawerRef\"\n      :source=\"SourceTypeEnum.APPLICATION\"\n    ></ResourceTriggerDrawer>\n  </LayoutContainer>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, computed } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport CreateApplicationDialog from '@/views/application/component/CreateApplicationDialog.vue'\nimport CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'\nimport CopyApplicationDialog from '@/views/application/component/CopyApplicationDialog.vue'\nimport MoveToDialog from '@/components/folder-tree/MoveToDialog.vue'\nimport ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'\nimport ResourceTriggerDrawer from '@/views/trigger/ResourceTriggerDrawer.vue'\nimport TemplateStoreDialog from '@/views/application/template-store/TemplateStoreDialog.vue'\nimport ApplicationApi from '@/api/application/application'\nimport WorkspaceApi from '@/api/workspace/workspace'\nimport { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'\nimport { i18n_name, resetUrl } from '@/utils/common'\nimport { isWorkFlow } from '@/utils/application'\nimport { dateFormat } from '@/utils/time'\nimport { SourceTypeEnum } from '@/enums/common'\nimport permissionMap from '@/permission'\nimport { hasPermission } from '@/utils/permission'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nimport useStore from '@/stores'\nimport { t } from '@/locales'\nconst router = useRouter()\n\nconst apiType = computed<'workspace'>(() => {\n  return 'workspace'\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['application'][apiType.value]\n})\n\nconst { folder, application, user } = useStore()\n\nconst loading = ref(false)\n\nconst search_type = ref('name')\nconst search_form = ref<any>({\n  name: '',\n  create_user: '',\n  publish_status: undefined,\n})\n\nconst user_options = ref<any[]>([])\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 30,\n  total: 0,\n})\n\nconst folderList = ref<any[]>([])\nconst applicationList = ref<any[]>([])\nconst CopyApplicationDialogRef = ref()\n\nconst resourceTriggerDrawerRef = ref<InstanceType<typeof ResourceTriggerDrawer>>()\nconst openTriggerDrawer = (data: any) => {\n  resourceTriggerDrawerRef.value?.open(data)\n}\n\nconst ResourceAuthorizationDrawerRef = ref()\n\nfunction openAuthorization(item: any) {\n  ResourceAuthorizationDrawerRef.value.open(item.id)\n}\n\nconst MoveToDialogRef = ref()\n\nfunction openMoveToDialog(data: any) {\n  const obj = {\n    id: data.id,\n    folder_id: data.folder,\n  }\n  MoveToDialogRef.value?.open(obj)\n}\n\nfunction refreshApplicationList(row: any) {\n  // 不是根目录才会移除\n  if (folder.currentFolder?.parent_id) {\n    const index = applicationList.value.findIndex((v) => v.id === row.id)\n    applicationList.value.splice(index, 1)\n  }\n}\n\nconst goApp = (item: any) => {\n  router.push({ path: get_route(item) })\n}\n\nconst get_route = (item: any) => {\n  if (\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(item.id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.APPLICATION_OVERVIEW_READ.getWorkspacePermissionWorkspaceManageRole,\n        PermissionConst.APPLICATION_OVERVIEW_READ.getApplicationWorkspaceResourcePermission(\n          item.id,\n        ),\n      ],\n      'OR',\n    )\n  ) {\n    return `/application/workspace/${item.id}/${item.type}/overview`\n  } else if (\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(item.id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.APPLICATION_EDIT.getWorkspacePermissionWorkspaceManageRole,\n        PermissionConst.APPLICATION_EDIT.getApplicationWorkspaceResourcePermission(item.id),\n      ],\n      'OR',\n    )\n  ) {\n    if (item.type == 'WORK_FLOW') {\n      return `/application/workspace/${item.id}/workflow`\n    } else {\n      return `/application/workspace/${item.id}/${item.type}/setting`\n    }\n  } else if (\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(item.id)],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'AND',\n        ),\n        new ComplexPermission(\n          [RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n          [PermissionConst.APPLICATION_ACCESS_READ.getWorkspacePermissionWorkspaceManageRole],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'OR',\n        ),\n        new ComplexPermission(\n          [],\n          [\n            PermissionConst.APPLICATION_ACCESS_READ.getApplicationWorkspaceResourcePermission(\n              item.id,\n            ),\n          ],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'OR',\n        ),\n      ],\n      'OR',\n    )\n  ) {\n    return `/application/workspace/${item.id}/${item.type}/access`\n  } else if (\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(item.id)],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'AND',\n        ),\n        new ComplexPermission(\n          [RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n          [PermissionConst.APPLICATION_CHAT_USER_READ.getWorkspacePermissionWorkspaceManageRole],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'OR',\n        ),\n        new ComplexPermission(\n          [],\n          [\n            PermissionConst.APPLICATION_CHAT_USER_READ.getApplicationWorkspaceResourcePermission(\n              item.id,\n            ),\n          ],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'OR',\n        ),\n      ],\n      'OR',\n    )\n  ) {\n    return `/application/workspace/${item.id}/${item.type}/chat-user`\n  } else if (\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(item.id)],\n          [],\n          'AND',\n        ),\n        PermissionConst.APPLICATION_CHAT_LOG_READ.getWorkspacePermissionWorkspaceManageRole,\n        PermissionConst.APPLICATION_CHAT_LOG_READ.getApplicationWorkspaceResourcePermission(\n          item.id,\n        ),\n      ],\n      'OR',\n    )\n  ) {\n    return `/application//workspace${item.id}/${item.type}/chat-log`\n  } else return `/application/`\n}\n\nconst CreateApplicationDialogRef = ref()\n\nfunction openCreateDialog(type?: string) {\n  CreateApplicationDialogRef.value.open(folder.currentFolder?.id || 'default', type)\n}\n\nconst search_type_change = () => {\n  search_form.value = { name: '', create_user: '' }\n}\n\nfunction toChat(row: any) {\n  const api =\n    row.type == 'WORK_FLOW'\n      ? (id: string) => ApplicationApi.getApplicationDetail(id)\n      : (id: string) => Promise.resolve({ data: row })\n  api(row.id).then((ok) => {\n    let aips = ok.data?.work_flow?.nodes\n      ?.filter((v: any) => v.id === 'base-node')\n      .map((v: any) => {\n        return v.properties.api_input_field_list\n          ? v.properties.api_input_field_list.map((v: any) => {\n              return {\n                name: v.variable,\n                value: v.default_value,\n              }\n            })\n          : v.properties.input_field_list\n            ? v.properties.input_field_list\n                .filter((v: any) => v.assignment_method === 'api_input')\n                .map((v: any) => {\n                  return {\n                    name: v.variable,\n                    value: v.default_value,\n                  }\n                })\n            : []\n      })\n      .reduce((x: Array<any>, y: Array<any>) => [...x, ...y])\n    aips = aips ? aips : []\n    const apiParams = mapToUrlParams(aips) ? '?' + mapToUrlParams(aips) : ''\n    ApplicationApi.getAccessToken(row.id, loading).then((res: any) => {\n      const newUrl = application.location + res?.data?.access_token + apiParams\n      window.open(newUrl)\n    })\n  })\n}\n\nfunction mapToUrlParams(map: any[]) {\n  const params = new URLSearchParams()\n\n  map.forEach((item: any) => {\n    params.append(encodeURIComponent(item.name), encodeURIComponent(item.value))\n  })\n\n  return params.toString() // 返回 URL 查询字符串\n}\n\nfunction copyApplication(row: any) {\n  ApplicationApi.getApplicationDetail(row.id, loading).then((res: any) => {\n    if (res?.data) {\n      CopyApplicationDialogRef.value.open(\n        { ...res.data, model_id: res.data.model },\n        folder.currentFolder?.id || 'default',\n      )\n    }\n  })\n}\n\nfunction settingApplication(event: any, row: any) {\n  if (isWorkFlow(row.type)) {\n    if (event?.ctrlKey) {\n      event?.preventDefault()\n      event.stopPropagation()\n      const newUrl = router.resolve({\n        path: `/application/workspace/${row.id}/workflow`,\n      }).href\n      window.open(newUrl)\n    } else {\n      router.push({ path: `/application/workspace/${row.id}/workflow` })\n    }\n  } else {\n    router.push({ path: `/application/workspace/${row.id}/${row.type}/setting` })\n  }\n}\n\nfunction deleteApplication(row: any) {\n  MsgConfirm(\n    `${t('views.application.delete.confirmTitle')}${row.name} ?`,\n    row.resource_count > 0\n      ? t('views.application.delete.resourceCountMessage', row.resource_count)\n      : '',\n    {\n      confirmButtonText: t('common.confirm'),\n      cancelButtonText: t('common.cancel'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      ApplicationApi.delApplication(row.id, loading).then(() => {\n        const index = applicationList.value.findIndex((v) => v.id === row.id)\n        applicationList.value.splice(index, 1)\n        MsgSuccess(t('common.deleteSuccess'))\n      })\n    })\n    .catch(() => {})\n}\n\nconst exportApplication = (application: any) => {\n  ApplicationApi.exportApplication(application.id, application.name, loading).catch((e) => {\n    if (e.response.status !== 403) {\n      e.response.data.text().then((res: string) => {\n        MsgError(`${t('views.application.tip.ExportError')}:${JSON.parse(res).message}`)\n      })\n    }\n  })\n}\n\nconst elUploadRef = ref()\nconst importApplication = (file: any) => {\n  const formData = new FormData()\n  formData.append('file', file.raw, file.name)\n  elUploadRef.value.clearFiles()\n  ApplicationApi.importApplication(folder.currentFolder.id, formData, loading)\n    .then(async (res: any) => {\n      if (res?.data) {\n        applicationList.value = []\n        user.profile()\n      }\n    })\n    .then(() => {\n      getList()\n    })\n    .catch((e) => {\n      if (e.code === 400) {\n        MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), {\n          cancelButtonText: t('common.confirm'),\n          confirmButtonText: t('common.professional'),\n        }).then(() => {\n          window.open('https://maxkb.cn/pricing.html', '_blank')\n        })\n      }\n    })\n}\n\n// 文件夹相关\nconst CreateFolderDialogRef = ref()\n\nfunction openCreateFolder() {\n  CreateFolderDialogRef.value.open(SourceTypeEnum.APPLICATION, folder.currentFolder.id)\n}\n\nfunction getFolder(bool?: boolean) {\n  const params = {}\n  folder\n    .asyncGetFolder(SourceTypeEnum.APPLICATION, params, apiType.value, loading)\n    .then((res: any) => {\n      folderList.value = res.data\n\n      if (bool) {\n        // 初始化刷新\n        folder.setCurrentFolder(res.data?.[0] || {})\n      }\n      getList()\n    })\n}\n\nfunction folderClickHandle(row: any) {\n  if (row.id === folder.currentFolder?.id) {\n    return\n  }\n  folder.setCurrentFolder(row)\n  paginationConfig.current_page = 1\n  applicationList.value = []\n  getList()\n}\n\nfunction refreshFolder() {\n  paginationConfig.current_page = 1\n  applicationList.value = []\n  getFolder()\n}\n\nfunction searchHandle() {\n  paginationConfig.current_page = 1\n  applicationList.value = []\n  getList()\n}\n\nconst templateStoreDialogRef = ref()\n\nfunction openTemplateStoreDialog() {\n  templateStoreDialogRef.value?.open(folder.currentFolder.id)\n}\n\nfunction getList() {\n  const params: any = {\n    folder_id: folder.currentFolder?.id || 'default',\n  }\n  if (search_form.value[search_type.value]) {\n    params[search_type.value] = search_form.value[search_type.value]\n  }\n  ApplicationApi.getApplication(paginationConfig, params, loading).then((res) => {\n    paginationConfig.total = res.data.total\n    applicationList.value = [...applicationList.value, ...res.data.records]\n  })\n}\n\nonMounted(() => {\n  getFolder(folder.currentFolder?.id ? false : true)\n  WorkspaceApi.getAllMemberList(user.getWorkspaceId(), loading).then((res) => {\n    user_options.value = res.data\n  })\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application/template-store/InternalDescDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visibleInternalDesc\" size=\"60%\" :append-to-body=\"true\">\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <el-button class=\"cursor mr-4\" link @click.prevent=\"visibleInternalDesc = false\">\n          <el-icon :size=\"20\">\n            <Back />\n          </el-icon>\n        </el-button>\n        <h4>{{ $t('common.detail') }}</h4>\n      </div>\n    </template>\n\n    <div>\n      <div class=\"border-b\">\n        <div class=\"flex-between mb-24\">\n          <div class=\"title flex align-center\">\n            <el-avatar shape=\"square\" :size=\"64\" style=\"background: none\">\n              <img src=\"@/assets/knowledge/icon_basic_template.svg\" alt=\"\" />\n            </el-avatar>\n            <div class=\"ml-16\">\n              <h3 class=\"mb-8\">{{ toolDetail.name }}</h3>\n              <el-text type=\"info\" v-if=\"toolDetail?.desc\">\n                {{ toolDetail.desc }}\n              </el-text>\n              <span\n                class=\"color-secondary flex align-center mt-8\"\n                v-if=\"toolDetail?.downloads != undefined\"\n              >\n                <AppIcon iconName=\"app-download\" class=\"mr-4\" />\n                <span> {{ numberFormat(toolDetail.downloads || 0) }} </span>\n              </span>\n            </div>\n          </div>\n          <div @click.stop>\n            <el-button type=\"primary\" @click=\"addInternalTool(toolDetail)\">\n              {{ $t('common.use') }}\n            </el-button>\n          </div>\n        </div>\n      </div>\n      <MdPreview\n        ref=\"editorRef\"\n        editorId=\"preview-only\"\n        :modelValue=\"markdownContent\"\n        style=\"background: none\"\n        noImgZoomIn\n      />\n    </div>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport { isAppIcon, numberFormat } from '@/utils/common'\nconst emit = defineEmits(['refresh', 'addTool'])\n\nconst visibleInternalDesc = ref(false)\nconst markdownContent = ref('')\nconst toolDetail = ref<any>({})\n\nwatch(visibleInternalDesc, (bool) => {\n  if (!bool) {\n    markdownContent.value = ''\n  }\n})\n\nconst open = (data: any, detail: any) => {\n  toolDetail.value = detail\n  if (data) {\n    markdownContent.value = cloneDeep(data)\n  }\n  visibleInternalDesc.value = true\n}\n\nconst addInternalTool = (data: any) => {\n  emit('addTool', data)\n  visibleInternalDesc.value = false\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/application/template-store/TemplateCard.vue",
    "content": "<template>\n  <CardBox :title=\"props.tool.name\" :description=\"props.tool.desc\" class=\"cursor tool-card\">\n    <template #icon>\n      <el-avatar shape=\"square\" :size=\"32\" style=\"background: none\">\n        <img :src=\"resetUrl(props.tool?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n      </el-avatar>\n    </template>\n    <template #title>\n      <span :title=\"props.tool?.name\" class=\"ellipsis\"> {{ props.tool?.name }}</span>\n    </template>\n    <template #footer>\n      <span\n        class=\"card-footer-left color-secondary flex align-center\"\n        v-if=\"props.tool?.downloads != undefined\"\n      >\n        <AppIcon iconName=\"app-download\" class=\"mr-4\" />\n        <span> {{ numberFormat(props.tool.downloads || 0) }} </span>\n      </span>\n\n      <div class=\"card-footer-operation mb-8\" @click.stop>\n        <el-button @click=\"emit('handleDetail')\">\n          {{ $t('common.detail') }}\n        </el-button>\n        <el-button type=\"primary\" :loading=\"props.addLoading\" @click=\"emit('handleAdd')\">\n          {{ $t('common.use') }}\n        </el-button>\n      </div>\n    </template>\n  </CardBox>\n</template>\n\n<script setup lang=\"ts\">\nimport { isAppIcon, numberFormat, resetUrl } from '@/utils/common'\n\nconst props = defineProps<{\n  tool: any\n  getSubTitle: (v: any) => string\n  addLoading: boolean\n}>()\n\nconst emit = defineEmits<{\n  (e: 'handleAdd'): void\n  (e: 'handleDetail'): void\n}>()\n</script>\n\n<style lang=\"scss\" scoped>\n.tool-card {\n  :deep(.card-footer) {\n    & > div:first-of-type {\n      flex: 1;\n    }\n\n    .card-footer-operation {\n      display: none;\n    }\n  }\n\n  &:hover {\n    .card-footer-left {\n      display: none;\n    }\n\n    .card-footer-operation {\n      display: flex !important;\n\n      .el-button {\n        flex: 1;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application/template-store/TemplateStoreDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    width=\"1000\"\n    append-to-body\n    class=\"tool-store-dialog\"\n    align-center\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <template #header=\"{ titleId }\">\n      <div class=\"dialog-header flex-between mb-8\">\n        <h4 :id=\"titleId\" class=\"medium w-240 mr-8\">\n          {{ $t('workflow.setting.templateCenter') }}\n        </h4>\n\n        <div class=\"flex align-center\" style=\"margin-right: 28px\">\n          <el-input\n            v-model=\"searchValue\"\n            :placeholder=\"$t('common.search')\"\n            prefix-icon=\"Search\"\n            class=\"w-240 mr-8\"\n            clearable\n            @change=\"getList\"\n          />\n          <el-divider direction=\"vertical\" />\n        </div>\n      </div>\n    </template>\n\n    <!-- <LayoutContainer v-loading=\"loading\" :minLeftWidth=\"204\">\n      <template #left>\n        <el-anchor\n          direction=\"vertical\"\n          :offset=\"130\"\n          type=\"default\"\n          container=\".category-scrollbar\"\n          @click=\"handleClick\"\n        >\n          <el-anchor-link\n            v-for=\"category in categories\"\n            :key=\"category.id\"\n            :href=\"`#category-${category.id}`\"\n            :title=\"category.title\"\n          />\n        </el-anchor>\n      </template> -->\n\n    <el-scrollbar class=\"layout-bg\" wrap-class=\"p-16-24 category-scrollbar\">\n      <template v-if=\"filterList === null\">\n        <div v-for=\"category in categories\" :key=\"category.id\">\n          <!-- <h4\n              class=\"title-decoration-1 mb-16 mt-8 color-text-primary\"\n              :id=\"`category-${category.id}`\"\n            >\n              {{ category.title }}\n            </h4> -->\n          <el-row :gutter=\"16\">\n            <el-col v-for=\"tool in category.tools\" :key=\"tool.id\" :span=\"8\" class=\"mb-16\">\n              <TemplateCard\n                :tool=\"tool\"\n                :addLoading=\"addLoading\"\n                :get-sub-title=\"getSubTitle\"\n                @handleAdd=\"handleOpenAdd(tool)\"\n                @handleDetail=\"handleDetail(tool)\"\n              >\n              </TemplateCard>\n            </el-col>\n          </el-row>\n        </div>\n      </template>\n      <div v-else>\n        <!-- <h4 class=\"color-text-primary medium mb-16\">\n            <span class=\"color-primary\">{{ searchValue }}</span>\n            {{ t('views.tool.toolStore.searchResult', { count: filterList.length }) }}\n          </h4> -->\n        <el-row :gutter=\"16\" v-if=\"filterList.length\">\n          <el-col v-for=\"tool in filterList\" :key=\"tool.id\" :span=\"8\" class=\"mb-16\">\n            <TemplateCard\n              :tool=\"tool\"\n              :addLoading=\"addLoading\"\n              :get-sub-title=\"getSubTitle\"\n              @handleAdd=\"handleOpenAdd(tool)\"\n              @handleDetail=\"handleDetail(tool)\"\n            />\n          </el-col>\n        </el-row>\n        <el-empty v-else :description=\"$t('common.noData')\" />\n      </div>\n    </el-scrollbar>\n    <!-- </LayoutContainer> -->\n  </el-dialog>\n  <InternalDescDrawer ref=\"internalDescDrawerRef\" @addTool=\"handleOpenAdd\" />\n  <CreateApplicationDialog ref=\"CreateKnowledgeDialogRef\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport ToolStoreApi from '@/api/tool/store'\nimport { t } from '@/locales'\nimport TemplateCard from './TemplateCard.vue'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport InternalDescDrawer from './InternalDescDrawer.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport useStore from '@/stores'\nimport { useRoute } from 'vue-router'\nimport CreateApplicationDialog from '@/views/application/component/CreateApplicationDialog.vue'\n\nconst { user } = useStore()\nconst route = useRoute()\nconst {\n  params: { id },\n  /*\n  folderId 可以区分 resource-management shared还是 workspace\n  */\n} = route as any\n\ninterface ToolCategory {\n  id: string\n  title: string\n  tools: any[]\n}\n\nconst props = defineProps({\n  apiType: {\n    type: String as () => 'workspace' | 'systemShare' | 'systemManage' | 'workspaceShare',\n    default: 'workspace',\n  },\n  source: {\n    type: String,\n    default: 'application',\n  },\n})\nconst emit = defineEmits(['refresh'])\n\nconst dialogVisible = ref(false)\nconst loading = ref(false)\nconst searchValue = ref('')\nconst folderId = ref('')\nconst categories = ref<ToolCategory[]>([])\n\nconst filterList = ref<any>(null)\n\nfunction getSubTitle(tool: any) {\n  return categories.value.find((i) => i.id === tool.label)?.title ?? ''\n}\n\nfunction open(id: string) {\n  folderId.value = id\n  filterList.value = null\n  dialogVisible.value = true\n\n  getList()\n}\n\nasync function getList() {\n  filterList.value = null\n  const [v1] = await Promise.all([getStoreToolList()])\n\n  const merged = [...v1].reduce((acc, category) => {\n    const existing = acc.find((item: any) => item.id === category.id)\n    if (existing) {\n      existing.tools = [...existing.tools, ...category.tools]\n    } else {\n      acc.push({ ...category })\n    }\n    return acc\n  }, [] as ToolCategory[])\n\n  categories.value = merged.filter((item: any) => item.tools.length > 0)\n}\n\nasync function getStoreToolList() {\n  try {\n    const res = await ToolStoreApi.getStoreAppList({ name: searchValue.value }, loading)\n    const tags = res.data.additionalProperties.tags\n    const storeTools = res.data.apps\n    let categories = []\n    //\n    storeTools.forEach((tool: any) => {\n      tool.desc = tool.description\n    })\n    if (searchValue.value.length) {\n      filterList.value = [...res.data.apps, ...(filterList.value || [])]\n    } else {\n      filterList.value = null\n      categories = tags.map((tag: any) => ({\n        id: tag.key,\n        title: tag.name, // 国际化\n        tools: storeTools.filter((tool: any) => tool.label === tag.key),\n      }))\n    }\n    return categories\n  } catch (error) {\n    console.error(error)\n    return []\n  }\n}\n\nconst handleClick = (e: MouseEvent) => {\n  e.preventDefault()\n}\n\nconst internalDescDrawerRef = ref<InstanceType<typeof InternalDescDrawer>>()\n\nasync function handleDetail(tool: any) {\n  internalDescDrawerRef.value?.open(tool.readMe, tool)\n}\n\nconst CreateKnowledgeDialogRef = ref()\n\nfunction handleOpenAdd(data?: any, isEdit?: boolean) {\n  if (props.source === 'work_flow') {\n    MsgConfirm(\n      t('common.tip'),\n      `${t('views.application.tip.confirmUse')} ${data.name} ${t('views.application.tip.overwrite')}?`,\n      {\n        confirmButtonText: t('common.confirm'),\n        cancelButtonText: t('common.cancel'),\n      },\n    )\n      .then(() => {\n        handleStoreAdd(data)\n      })\n      .catch(() => {})\n  } else {\n    CreateKnowledgeDialogRef.value.open(folderId.value, 'WORK_FLOW', data)\n  }\n}\n\nconst addLoading = ref(false)\n\nfunction handleStoreAdd(tool: any) {\n  try {\n    loadSharedApi({ type: 'application', systemType: props.apiType })\n      .putApplication(id, { work_flow_template: tool }, addLoading)\n      .then(() => {\n        emit('refresh')\n        MsgSuccess(t('common.addSuccess'))\n      })\n    dialogVisible.value = false\n  } catch (error) {\n    console.error(error)\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\">\n.tool-store-dialog {\n  padding: 0;\n\n  .el-dialog__headerbtn {\n    top: 7px;\n  }\n\n  .el-dialog__header {\n    padding: 12px 20px 4px 24px;\n    border-bottom: 1px solid var(--el-border-color-light);\n\n    .dialog-header {\n      position: relative;\n\n      .store-type {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate(-50%, -50%);\n      }\n    }\n  }\n\n  .layout-container__left {\n    background-color: var(--app-layout-bg-color);\n    border-radius: 0 0 0 8px;\n  }\n\n  .layout-container__right {\n    background-color: var(--app-layout-bg-color);\n    border-radius: 0 0 8px 0;\n  }\n\n  .el-anchor {\n    background-color: var(--app-layout-bg-color);\n\n    .el-anchor__marker {\n      display: none;\n    }\n\n    .el-anchor__list {\n      padding: 8px;\n    }\n\n    .el-anchor__item {\n      .el-anchor__link {\n        padding: 8px 16px;\n        font-weight: 500;\n        font-size: 14px;\n        color: var(--el-text-color-primary);\n        border-radius: 6px;\n\n        &.is-active {\n          color: var(--el-color-primary);\n          background-color: #3370ff1a;\n        }\n      }\n    }\n  }\n\n  .category-scrollbar {\n    height: calc(100vh - 200px);\n    // min-height: 500px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application-overview/component/APIKeyDialog.vue",
    "content": "<template>\n  <el-dialog\n    title=\"API Key\"\n    v-model=\"dialogVisible\"\n    width=\"1000\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    align-center\n  >\n    <el-button type=\"primary\" class=\"mb-16\" @click=\"createApiKey\">\n      {{ $t('common.create') }}\n    </el-button>\n    <app-table\n      :data=\"apiKey\"\n      :loading=\"loading\"\n      style=\"min-height: 300px\"\n      class=\"mb-16\"\n      :max-height=\"500\"\n      :pagination-config=\"paginationConfig\"\n      @sizeChange=\"handleSizeChange\"\n      @changePage=\"getApiKeyList\"\n      @sort-change=\"handleSortChange\"\n    >\n      <el-table-column prop=\"secret_key\" label=\"API Key\">\n        <template #default=\"{ row }\">\n          <div class=\"api-key-container\">\n            <el-tooltip :content=\"row.secret_key\" placement=\"top\" effect=\"light\" :hide-after=\"0\">\n              <span class=\"api-key-text vertical-middle lighter break-all\">\n                {{ row.secret_key }}\n              </span>\n            </el-tooltip>\n            <el-button type=\"primary\" text @click=\"copyClick(row.secret_key)\" class=\"copy-btn\">\n              <AppIcon iconName=\"app-copy\"></AppIcon>\n            </el-button>\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('views.document.enableStatus.label')\" width=\"100\">\n        <template #default=\"{ row }\">\n          <div v-if=\"row.is_active\" class=\"flex align-center\">\n            <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n              <SuccessFilled/>\n            </el-icon>\n            <span class=\"color-text-primary\">\n              {{ $t('common.status.enabled') }}\n            </span>\n          </div>\n          <div v-else class=\"flex align-center\">\n            <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n            <span class=\"color-text-primary\">\n              {{ $t('common.status.disabled') }}\n            </span>\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('layout.crossSettings')\" width=\"100\" prop=\"allow_cross_domain\">\n        <template #default=\"{ row }\">\n          <el-tag size=\"small\" type=\"info\" class=\"info-tag\" v-if=\"row.allow_cross_domain\">\n            {{ $t('views.system.authentication.scanTheQRCode.alreadyTurnedOn') }}\n          </el-tag>\n          <el-tag size=\"small\" class=\"blue-tag\" v-else>\n            {{ $t('views.system.authentication.scanTheQRCode.notEnabled') }}\n          </el-tag>\n        </template>\n      </el-table-column>\n\n      <el-table-column :label=\"$t('layout.about.expiredTime')\" width=\"265\">\n        <template #default=\"{ row }\">\n          <span v-if=\"row.is_permanent\" class=\"permanent-status\">\n            {{ t('layout.time.neverExpires') }}\n          </span>\n          <span v-else class=\"expiry-info\">\n            <span\n              v-if=\"fromNowDate(row.expire_time)\"\n              :class=\"getExpiryClass(row.expire_time)\"\n              class=\"relative-time\"\n            >\n              ({{ fromNowDate(row.expire_time) }})\n            </span>\n            {{ datetimeFormat(row.expire_time) }}\n          </span>\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('common.createDate')\" width=\"170\" prop=\"create_time\" sortable>\n        <template #default=\"{ row }\">\n          {{ datetimeFormat(row.create_time) }}\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"130\">\n        <template #default=\"{ row }\">\n          <span @click.stop>\n            <el-switch size=\"small\" v-model=\"row.is_active\" @change=\"changeState($event, row)\"/>\n          </span>\n          <el-divider direction=\"vertical\"/>\n          <span class=\"mr-4\">\n            <el-tooltip effect=\"dark\" :content=\"$t('common.setting')\" placement=\"top\">\n              <el-button type=\"primary\" text @click.stop=\"settingApiKey(row)\">\n                <AppIcon iconName=\"app-edit\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </span>\n          <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n            <el-button type=\"primary\" text @click=\"deleteApiKey(row)\">\n              <AppIcon iconName=\"app-delete\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </template>\n      </el-table-column>\n    </app-table>\n    <SettingAPIKeyDialog ref=\"SettingAPIKeyDialogRef\" @refresh=\"refresh\"/>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport {ref, watch, computed, reactive} from 'vue'\nimport {useRoute} from 'vue-router'\nimport {copyClick} from '@/utils/clipboard'\nimport SettingAPIKeyDialog from './SettingAPIKeyDrawer.vue'\nimport {datetimeFormat, fromNowDate} from '@/utils/time'\nimport {MsgSuccess, MsgConfirm} from '@/utils/message'\nimport {t} from '@/locales'\nimport {loadSharedApi} from '@/utils/dynamics-api/shared-api'\n\nconst orderBy = ref<string>('')\nconst route = useRoute()\nconst {\n  params: {id},\n} = route\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getApiKeyList()\n}\n\nconst emit = defineEmits(['addData'])\n\nconst SettingAPIKeyDialogRef = ref()\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\nconst apiKey = ref<any>(null)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    apiKey.value = null\n  }\n})\n\nfunction settingApiKey(row: any) {\n  SettingAPIKeyDialogRef.value.open(row, 'APPLICATION')\n}\n\nfunction deleteApiKey(row: any) {\n  MsgConfirm(\n    `${t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm1')}: ${row.secret_key}?`,\n    t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm2'),\n    {\n      confirmButtonText: t('common.confirm'),\n      cancelButtonText: t('common.cancel'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loadSharedApi({type: 'applicationKey', systemType: apiType.value})\n        .delAPIKey(id as string, row.id, loading)\n        .then(() => {\n          MsgSuccess(t('common.deleteSuccess'))\n          getApiKeyList()\n        })\n    })\n    .catch(() => {\n    })\n}\n\nasync function changeState(bool: boolean, row: any) {\n  const obj = {\n    is_active: bool,\n  }\n  const str = obj.is_active ? t('common.status.enabled') : t('common.status.disabled')\n  await loadSharedApi({type: 'applicationKey', systemType: apiType.value})\n    .putAPIKey(id as string, row.id, obj, loading)\n    .then(() => {\n      MsgSuccess(str)\n      getApiKeyList()\n      return true\n    })\n    .catch(() => {\n      return false\n    })\n}\n\nfunction createApiKey() {\n  loadSharedApi({type: 'applicationKey', systemType: apiType.value})\n    .postAPIKey(id as string, loading)\n    .then(() => {\n      MsgSuccess(t('common.createSuccess'))\n      getApiKeyList()\n    })\n}\n\nconst open = () => {\n  getApiKeyList()\n  dialogVisible.value = true\n}\n\nfunction getApiKeyList() {\n  const param = {\n    order_by: orderBy.value,\n  }\n  loadSharedApi({type: 'applicationKey', systemType: apiType.value})\n    .getAPIKey(\n      id as string,\n      paginationConfig.current_page,\n      paginationConfig.page_size,\n      param,\n      loading,\n    )\n    .then((res: any) => {\n      apiKey.value = res.data.records\n      paginationConfig.total = res.data.total\n    })\n}\n\nfunction handleSortChange({prop, order}: { prop: string; order: string }) {\n  orderBy.value = order === 'ascending' ? prop : `-${prop}`\n  getApiKeyList()\n}\n\nfunction getExpiryClass(expireTime: any) {\n  const status = fromNowDate(expireTime)\n  if (status === t('layout.time.expired')) {\n    return 'color-danger' // 红色\n  } else {\n    return 'color-warning' // 橙色\n  }\n}\n\nfunction refresh() {\n  getApiKeyList()\n}\n\ndefineExpose({open})\n</script>\n<style lang=\"scss\" scoped>\n.api-key-container {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n\n  .api-key-text {\n    flex: 1;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    min-width: 0; /* 允许弹性收缩 */\n    cursor: pointer; /* 显示手型光标提示可悬停 */\n  }\n\n  .copy-btn {\n    flex-shrink: 0; /* 复制按钮不收缩 */\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application-overview/component/DisplaySettingDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.applicationOverview.appInfo.displaySetting')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    width=\"550\"\n  >\n    <el-form label-position=\"top\" ref=\"displayFormRef\" :model=\"form\">\n      <el-form-item>\n        <span>{{\n          $t('layout.language')\n        }}</span>\n        <el-select v-model=\"form.language\" clearable>\n          <el-option\n            v-for=\"item in langList\"\n            :key=\"item.value\"\n            :label=\"item.label\"\n            :value=\"item.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-space direction=\"vertical\" alignment=\"start\" :size=\"2\">\n          <el-checkbox\n            v-model=\"form.show_source\"\n            :label=\"$t('views.applicationOverview.SettingDisplayDialog.showSourceLabel')\"\n          />\n\n          <el-checkbox\n            v-model=\"form.show_exec\"\n            :label=\"\n              $t('views.applicationOverview.SettingDisplayDialog.showExecutionDetail')\n            \"\n          />\n        </el-space>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\">{{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(displayFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess, MsgError } from '@/utils/message'\nimport { langList, t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\nconst {\n  params: { id },\n} = route\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst emit = defineEmits(['refresh'])\n\nconst displayFormRef = ref()\nconst form = ref<any>({\n  show_source: false,\n  show_exec: false,\n  language: '',\n})\n\nconst detail = ref<any>(null)\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      show_source: false,\n      show_exec: false,\n      language: '',\n    }\n  }\n})\nconst open = (data: any, content: any) => {\n  detail.value = content\n  form.value.show_source = data.show_source\n  form.value.show_exec = data.show_exec\n  form.value.language = data.language\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      loadSharedApi({ type: 'application', systemType: apiType.value })\n        .putAccessToken(id as string, form.value, loading)\n        .then(() => {\n          emit('refresh')\n\n          MsgSuccess(t('common.settingSuccess'))\n          dialogVisible.value = false\n        })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application-overview/component/EmbedDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.applicationOverview.appInfo.embedInWebsite')\"\n    v-model=\"dialogVisible\"\n    width=\"900\"\n    class=\"embed-dialog\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-row :gutter=\"12\">\n      <el-col :span=\"8\">\n        <div class=\"border\">\n          <p class=\"title p-16 bold\">\n            {{ $t('views.applicationOverview.appInfo.EmbedDialog.fullscreenModeTitle') }}\n          </p>\n          <img src=\"@/assets/application/window1.png\" alt=\"\" class=\"ml-8\" height=\"150\" />\n          <div class=\"code layout-bg border-t p-8\">\n            <div class=\"flex-between p-8\">\n              <span class=\"bold\">{{\n                $t('views.applicationOverview.appInfo.EmbedDialog.copyInstructions')\n              }}</span>\n              <el-button text @click=\"copyClick(source1)\">\n                <AppIcon iconName=\"app-copy\"></AppIcon>\n              </el-button>\n            </div>\n            <el-scrollbar height=\"180\" always>\n              <div class=\"pre-wrap p-8 pt-0\">\n                {{ source1 }}\n              </div>\n            </el-scrollbar>\n          </div>\n        </div>\n      </el-col>\n      <el-col :span=\"8\">\n        <div class=\"border\">\n          <p class=\"title p-16 bold\">\n            {{ $t('views.applicationOverview.appInfo.EmbedDialog.mobileModeTitle') }}\n          </p>\n          <img src=\"@/assets/application/window3.png\" alt=\"\" class=\"ml-8\" height=\"150\" />\n          <div class=\"code layout-bg border-t p-8\">\n            <div class=\"flex-between p-8\">\n              <span class=\"bold\">{{\n                $t('views.applicationOverview.appInfo.EmbedDialog.copyInstructions')\n              }}</span>\n              <el-button text @click=\"copyClick(source3)\">\n                <AppIcon iconName=\"app-copy\"></AppIcon>\n              </el-button>\n            </div>\n            <el-scrollbar height=\"180\" always>\n              <div class=\"pre-wrap p-8 pt-0\">\n                {{ source3 }}\n              </div>\n            </el-scrollbar>\n          </div>\n        </div>\n      </el-col>\n      <el-col :span=\"8\">\n        <div class=\"border\">\n          <p class=\"title p-16 bold\">\n            {{ $t('views.applicationOverview.appInfo.EmbedDialog.floatingModeTitle') }}\n          </p>\n          <img src=\"@/assets/application/window2.png\" alt=\"\" class=\"ml-8\" height=\"150\" />\n          <div class=\"code layout-bg border-t p-8\">\n            <div class=\"flex-between p-8\">\n              <span class=\"bold\">{{\n                $t('views.applicationOverview.appInfo.EmbedDialog.copyInstructions')\n              }}</span>\n              <el-button text @click=\"copyClick(source2)\">\n                <AppIcon iconName=\"app-copy\"></AppIcon>\n              </el-button>\n            </div>\n            <el-scrollbar height=\"180\" always>\n              <div class=\"pre-wrap p-8 pt-0\">\n                {{ source2 }}\n              </div>\n            </el-scrollbar>\n          </div>\n        </div>\n      </el-col>\n    </el-row>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { copyClick } from '@/utils/clipboard'\nimport useStore from '@/stores'\n\nconst { application } = useStore()\n\nconst props = defineProps({\n  data: Object,\n  apiInputParams: String,\n})\n\nconst emit = defineEmits(['addData'])\n\nconst dialogVisible = ref<boolean>(false)\n\nconst source1 = ref('')\n\nconst source2 = ref('')\nconst source3 = ref('')\n\nconst urlParams1 = computed(() => (props.apiInputParams ? '?' + props.apiInputParams : ''))\nconst urlParams2 = computed(() => (props.apiInputParams ? '&' + props.apiInputParams : ''))\nconst urlParams3 = computed(() =>\n  props.apiInputParams ? '?mode=mobile&' + props.apiInputParams : '?mode=mobile',\n)\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    source1.value = ''\n    source2.value = ''\n    source3.value = ''\n  }\n})\n\nconst open = (val: string) => {\n  source1.value = `<iframe\nsrc=\"${application.location + val + urlParams1.value}\"\nstyle=\"width: 100%; height: 100%;\"\nframeborder=\"0\"\nallow=\"microphone\">\n</iframe>\n`\n\n  source2.value = `<script\nasync\ndefer\nsrc=\"${application.location}api/embed?protocol=${window.location.protocol.replace(\n    ':',\n    '',\n  )}&host=${window.location.host}&token=${val}${urlParams2.value}\">\n<\\/script>\n`\n  source3.value = `<iframe\nsrc=\"${application.location + val + urlParams3.value}\"\nstyle=\"width: 100%; height: 100%;\"\nframeborder=\"0\"\nallow=\"microphone\">\n</iframe>\n`\n\n  dialogVisible.value = true\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped>\n.embed-dialog {\n  .title {\n    color: var(--app-text-color) !important;\n  }\n\n  .code {\n    color: var(--app-text-color) !important;\n\n    font-weight: 400;\n    font-size: 13px;\n    white-space: pre;\n    height: 210px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application-overview/component/LimitDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.applicationOverview.appInfo.accessControl')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    width=\"650\"\n  >\n    <el-form label-position=\"top\" ref=\"limitFormRef\" :model=\"form\">\n      <el-form-item\n        :label=\"$t('views.applicationOverview.appInfo.LimitDialog.clientQueryLimitLabel')\"\n      >\n        <el-input-number\n          v-model=\"form.access_num\"\n          :min=\"0\"\n          :step=\"1\"\n          :max=\"10000000\"\n          :value-on-clear=\"0\"\n          controls-position=\"right\"\n          style=\"width: 268px\"\n          step-strictly\n        />\n        <span class=\"ml-4\">{{\n          $t('views.applicationOverview.appInfo.LimitDialog.timesDays')\n        }}</span>\n      </el-form-item>\n\n      <el-form-item\n        :label=\"$t('views.applicationOverview.appInfo.LimitDialog.whitelistLabel')\"\n        @click.prevent\n      >\n        <el-switch size=\"small\" v-model=\"form.white_active\"></el-switch>\n      </el-form-item>\n      <el-form-item>\n        <el-input\n          v-model=\"form.white_list\"\n          :placeholder=\"$t('views.applicationOverview.appInfo.LimitDialog.whitelistPlaceholder')\"\n          :rows=\"10\"\n          type=\"textarea\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\">{{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(limitFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst route = useRoute()\nconst {\n  params: { id },\n} = route\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst emit = defineEmits(['refresh'])\n\nconst limitFormRef = ref()\nconst form = ref<any>({\n  access_num: 0,\n  white_active: true,\n  white_list: '',\n  authentication_value: '',\n  authentication: false,\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      access_num: 0,\n      white_active: true,\n      white_list: '',\n    }\n  }\n})\n\nconst open = (data: any) => {\n  form.value.access_num = data.access_num\n  form.value.white_active = data.white_active\n  form.value.white_list = data.white_list?.length ? data.white_list?.join('\\n') : ''\n  form.value.authentication_value = data.authentication_value\n  form.value.authentication = data.authentication\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      const obj = {\n        white_list: form.value.white_list ? form.value.white_list.split('\\n') : [],\n        white_active: form.value.white_active,\n        access_num: form.value.access_num,\n        authentication: form.value.authentication,\n        authentication_value: form.value.authentication_value,\n      }\n      loadSharedApi({ type: 'application', systemType: apiType.value })\n        .putAccessToken(id as string, obj, loading)\n        .then(() => {\n          emit('refresh')\n\n          MsgSuccess(t('common.settingSuccess'))\n          dialogVisible.value = false\n        })\n    }\n  })\n}\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application-overview/component/SettingAPIKeyDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('common.setting')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form label-position=\"top\" ref=\"settingFormRef\" :model=\"form\">\n      <el-form-item\n        :label=\"$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.allowCrossDomainLabel')\"\n        @click.prevent\n      >\n        <el-switch size=\"small\" v-model=\"form.allow_cross_domain\"></el-switch>\n      </el-form-item>\n      <el-form-item>\n        <el-input\n          v-model=\"form.cross_domain_list\"\n          :placeholder=\"\n            $t('views.applicationOverview.appInfo.SettingAPIKeyDialog.crossDomainPlaceholder')\n          \"\n          :rows=\"10\"\n          type=\"textarea\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit(settingFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport overviewSystemApi from '@/api/system/api-key'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\nconst {\n  params: { id },\n} = route\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst emit = defineEmits(['refresh'])\n\nconst settingFormRef = ref()\nconst form = ref<any>({\n  allow_cross_domain: false,\n  cross_domain_list: '',\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\n\nconst APIKeyId = ref('')\nconst APIType = ref('APPLICATION')\nconst isCreate = ref(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      allow_cross_domain: false,\n      cross_domain_list: '',\n    }\n  }\n})\n\nconst open = (data: any, type: string) => {\n  if (data) {\n    isCreate.value = false\n    APIKeyId.value = data.id\n    form.value.allow_cross_domain = data.allow_cross_domain\n    form.value.cross_domain_list = data.cross_domain_list?.length\n      ? data.cross_domain_list?.join('\\n')\n      : ''\n  } else {\n    isCreate.value = true\n  }\n  APIType.value = type\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      const obj = {\n        allow_cross_domain: form.value.allow_cross_domain,\n        cross_domain_list: form.value.cross_domain_list\n          ? form.value.cross_domain_list.split('\\n').filter(function (item: string) {\n              return item !== ''\n            })\n          : [],\n      }\n\n      const apiCall =\n        APIType.value === 'APPLICATION'\n          ? loadSharedApi({ type: 'applicationKey', systemType: apiType.value }).putAPIKey(\n              id as string,\n              APIKeyId.value,\n              obj,\n              loading,\n            )\n          : overviewSystemApi.putAPIKey(APIKeyId.value, obj, loading)\n\n      apiCall.then(() => {\n        emit('refresh')\n\n        MsgSuccess(t('common.settingSuccess'))\n        dialogVisible.value = false\n      })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application-overview/component/SettingAPIKeyDrawer.vue",
    "content": "<template>\n  <el-drawer\n    :title=\" $t('common.edit') + ' API Key'\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :append-to-body=\"true\"\n    size=\"60%\"\n  >\n    <el-form label-position=\"top\" ref=\"settingFormRef\" :model=\"form\">\n      <el-form-item label=\"API KEY\">\n        <div class=\"complex-input flex align-center w-full\"\n             style=\"background-color: var(--el-disabled-bg-color);\">\n          <el-input class=\"complex-input__left\" v-model=\"form.secret_key\"\n                    :disabled=\"true\"></el-input>\n          <el-tooltip :content=\"$t('common.copy')\" placement=\"top\">\n            <el-button text>\n              <AppIcon iconName=\"app-copy\" class=\"color-secondary\"\n                       @click=\"copyClick(form.secret_key)\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </div>\n      </el-form-item>\n      <el-form-item\n        :label=\"$t('layout.about.expiredTime')\"\n        prop=\"expiredTimeType\"\n        :rules=\"{\n          required: true,\n          message: $t('common.selectPlaceholder'),\n          trigger: 'change',\n        }\"\n      >\n        <el-select\n          :teleported=\"false\"\n          v-model=\"form.expiredTimeType\"\n          @change=\"changeExpiredTimeHandle\"\n        >\n          <el-option\n            v-for=\"(option, value) of expiredTimeList\"\n            :key=\"value\"\n            :label=\"option\"\n            :value=\"value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item v-if=\"form.expiredTimeType === 'custom'\">\n        <el-date-picker\n          v-model=\"form.expire_time\"\n          type=\"datetime\"\n          format=\"YYYY-MM-DD HH:mm:ss\"\n          style=\"width: 100%\"\n          :placeholder=\"$t('common.selectPlaceholder')\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('layout.crossSettings')\" @click.prevent>\n        <el-switch size=\"small\" v-model=\"form.allow_cross_domain\"></el-switch>\n      </el-form-item>\n      <el-form-item v-if=\"form.allow_cross_domain\">\n        <el-input\n          v-model=\"form.cross_domain_list\"\n          :placeholder=\"\n            $t('views.applicationOverview.appInfo.SettingAPIKeyDialog.crossDomainPlaceholder')\n          \"\n          :rows=\"10\"\n          type=\"textarea\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('views.document.enableStatus.label')\" @click.prevent>\n        <el-switch size=\"small\" v-model=\"form.is_active\"></el-switch>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit(settingFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport {ref, watch, computed} from 'vue'\nimport {useRoute} from 'vue-router'\nimport type {FormInstance, FormRules} from 'element-plus'\nimport overviewSystemApi from '@/api/system/api-key'\nimport {MsgSuccess} from '@/utils/message'\nimport {t} from '@/locales'\nimport {loadSharedApi} from '@/utils/dynamics-api/shared-api'\nimport {expiredTimeList, AfterTimestamp} from '@/utils/time'\nimport {copyClick} from \"@/utils/clipboard.ts\";\n\nconst route = useRoute()\nconst {\n  params: {id},\n} = route\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst emit = defineEmits(['refresh'])\n\nconst settingFormRef = ref()\nconst form = ref<any>({\n  secret_key: '',\n  allow_cross_domain: false,\n  cross_domain_list: '',\n  expired_time: '',\n  is_active: true,\n  is_permanent: true,\n  expiredTimeType: 'never',\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\n\nconst APIKeyId = ref('')\nconst APIType = ref('APPLICATION')\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      allow_cross_domain: false,\n      cross_domain_list: '',\n    }\n  }\n})\n\nconst open = (data: any, type: string) => {\n  if (data) {\n    APIKeyId.value = data.id\n    form.value = {\n      secret_key: data.secret_key || '',\n      allow_cross_domain: data.allow_cross_domain || false,\n      cross_domain_list: data.cross_domain_list?.length\n        ? data.cross_domain_list?.join('\\n')\n        : '',\n      expire_time: data.expire_time || '',\n      expiredTimeType: data.is_permanent ? 'never' : 'custom',\n      is_active: data.is_active,\n    }\n  }\n  APIType.value = type\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      const obj = {\n        allow_cross_domain: form.value.allow_cross_domain,\n        cross_domain_list: form.value.cross_domain_list\n          ? form.value.cross_domain_list.split('\\n').filter(function (item: string) {\n            return item !== ''\n          })\n          : [],\n        expire_time: form.value.expire_time,\n        is_permanent: form.value.expiredTimeType === 'never',\n        is_active: form.value.is_active,\n      }\n\n      const apiCall =\n        APIType.value === 'APPLICATION'\n          ? loadSharedApi({type: 'applicationKey', systemType: apiType.value}).putAPIKey(\n            id as string,\n            APIKeyId.value,\n            obj,\n            loading,\n          )\n          : overviewSystemApi.putAPIKey(APIKeyId.value, obj, loading)\n\n      apiCall.then(() => {\n        emit('refresh')\n\n        MsgSuccess(t('common.settingSuccess'))\n        dialogVisible.value = false\n      })\n    }\n  })\n}\n\nfunction changeExpiredTimeHandle(value: string) {\n  if (value === 'custom') {\n    form.value.expire_time = ''\n  } else if (value === 'never') {\n    form.value.expire_time = null\n  } else {\n    form.value.expire_time = AfterTimestamp(value)\n  }\n}\n\ndefineExpose({open})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application-overview/component/StatisticsCharts.vue",
    "content": "<template>\n  <el-row :gutter=\"16\">\n    <el-col\n      :xs=\"12\"\n      :sm=\"12\"\n      :md=\"12\"\n      :lg=\"6\"\n      :xl=\"6\"\n      v-for=\"(item, index) in statisticsType\"\n      :key=\"index\"\n      class=\"mb-16\"\n    >\n      <el-card shadow=\"never\">\n        <div class=\"flex align-center ml-8 mr-8\">\n          <el-avatar :size=\"40\" shape=\"square\" :style=\"{ background: item.background }\">\n            <appIcon :iconName=\"item.icon\" :style=\"{ fontSize: '24px', color: item.color }\" />\n          </el-avatar>\n          <div class=\"ml-12\">\n            <p class=\"color-secondary lighter mb-4\">{{ item.name }}</p>\n            <div v-if=\"item.id !== 'starCharts'\" class=\"flex align-baseline\">\n              <h2>{{ numberFormat(item.sum?.[0]) }}</h2>\n              <span v-if=\"item.sum.length > 1\" class=\"ml-12\" style=\"color: #f54a45\"\n                >+{{ numberFormat(item.sum?.[1]) }}</span\n              >\n            </div>\n            <div v-else class=\"flex align-center mr-8\">\n              <AppIcon iconName=\"app-like-color\"></AppIcon>\n              <h2 class=\"ml-4\">{{ item.sum?.[0] }}</h2>\n              <AppIcon class=\"ml-12\" iconName=\"app-oppose-color\"></AppIcon>\n              <h2 class=\"ml-4\">{{ item.sum?.[1] }}</h2>\n            </div>\n          </div>\n        </div>\n      </el-card>\n    </el-col>\n    <el-col\n      :xs=\"24\"\n      :sm=\"24\"\n      :md=\"24\"\n      :lg=\"12\"\n      :xl=\"12\"\n      v-for=\"(item, index) in statisticsType\"\n      :key=\"index\"\n      class=\"mb-16\"\n    >\n      <el-card shadow=\"never\">\n        <div class=\"p-8\">\n          <AppCharts height=\"316px\" :id=\"item.id\" type=\"line\" :option=\"item.option\" />\n        </div>\n      </el-card>\n    </el-col>\n\n    <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"12\" :xl=\"12\" class=\"mb-16\">\n      <el-card shadow=\"never\" class=\"StatisticsCharts-card\">\n        <el-select v-model=\"tokenUsageCount\" class=\"top-select\">\n          <el-option\n            v-for=\"item in topOptions\"\n            :key=\"item.value\"\n            :label=\"item.label\"\n            :value=\"item.value\"\n          />\n        </el-select>\n        <div class=\"p-8\">\n          <AppCharts\n            v-if=\"tokenUsage.length > 0\"\n            height=\"316px\"\n            id=\"tokenUsageCharts\"\n            type=\"bar\"\n            :option=\"tokenUsageOption\"\n          />\n\n          <div v-else>\n            <h4 class=\"ml-4\">{{ tokenUsageOption.title }}</h4>\n            <el-empty :description=\"$t('common.noData')\" style=\"height: 316px\" />\n          </div>\n        </div>\n      </el-card>\n    </el-col>\n    <el-col :xs=\"24\" :sm=\"24\" :md=\"24\" :lg=\"12\" :xl=\"12\" class=\"mb-16\">\n      <el-card shadow=\"never\" class=\"StatisticsCharts-card\">\n        <el-select v-model=\"topQuestionsCount\" class=\"top-select\">\n          <el-option\n            v-for=\"item in topOptions\"\n            :key=\"item.value\"\n            :label=\"item.label\"\n            :value=\"item.value\"\n          />\n        </el-select>\n        <div class=\"p-8\">\n          <AppCharts\n            v-if=\"topQuestions.length > 0\"\n            height=\"316px\"\n            id=\"topQuestionsCharts\"\n            type=\"bar\"\n            :option=\"topQuestionsOption\"\n          />\n          <div v-else>\n            <h4 class=\"ml-4\">{{ topQuestionsOption.title }}</h4>\n            <el-empty :description=\"$t('common.noData')\" style=\"height: 316px\" />\n          </div>\n        </div>\n      </el-card>\n    </el-col>\n  </el-row>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted } from 'vue'\nimport AppCharts from '@/components/app-charts/index.vue'\nimport { getAttrsArray, getSum } from '@/utils/array'\nimport { numberFormat } from '@/utils/common'\nimport { t } from '@/locales'\n\nconst props = defineProps({\n  data: {\n    type: Array,\n    default: () => [],\n  },\n  tokenUsage: {\n    type: Array,\n    default: () => [],\n  },\n  topQuestions: {\n    type: Array,\n    default: () => [],\n  },\n})\n\nconst statisticsType = computed(() => [\n  {\n    id: 'customerCharts',\n    name: t('views.applicationOverview.monitor.charts.customerTotal'),\n    icon: 'app-user',\n    background: '#EBF1FF',\n    color: '#3370FF',\n    sum: [\n      getSum(getAttrsArray(props.data, 'customer_num') || 0),\n      getSum(getAttrsArray(props.data, 'customer_added_count') || 0),\n    ],\n    option: {\n      title: t('views.applicationOverview.monitor.charts.customerTotal'),\n      xData: getAttrsArray(props.data, 'day'),\n      yData: [\n        {\n          name: t('views.applicationOverview.monitor.charts.customerTotal'),\n          area: true,\n          data: getAttrsArray(props.data, 'customer_num'),\n        },\n        {\n          name: t('views.applicationOverview.monitor.charts.customerNew'),\n          area: true,\n          data: getAttrsArray(props.data, 'customer_added_count'),\n        },\n      ],\n    },\n  },\n  {\n    id: 'chatRecordCharts',\n    name: t('views.applicationOverview.monitor.charts.queryCount'),\n    icon: 'app-question',\n    background: '#FFF3E5',\n    color: '#FF8800',\n    sum: [getSum(getAttrsArray(props.data, 'chat_record_count') || 0)],\n    option: {\n      title: t('views.applicationOverview.monitor.charts.queryCount'),\n      xData: getAttrsArray(props.data, 'day'),\n      yData: [\n        {\n          data: getAttrsArray(props.data, 'chat_record_count'),\n        },\n      ],\n    },\n  },\n  {\n    id: 'tokensCharts',\n    name: t('views.applicationOverview.monitor.charts.tokensTotal'),\n    icon: 'app-tokens',\n    background: '#E5FBF8',\n    color: '#00D6B9',\n    sum: [getSum(getAttrsArray(props.data, 'tokens_num') || 0)],\n    option: {\n      title: t('views.applicationOverview.monitor.charts.tokensTotal'),\n      xData: getAttrsArray(props.data, 'day'),\n      yData: [\n        {\n          data: getAttrsArray(props.data, 'tokens_num'),\n        },\n      ],\n    },\n  },\n  {\n    id: 'starCharts',\n    name: t('views.applicationOverview.monitor.charts.userSatisfaction'),\n    icon: 'app-user-stars',\n    background: '#FEEDEC',\n    color: '#F54A45',\n    sum: [\n      getSum(getAttrsArray(props.data, 'star_num') || 0),\n      getSum(getAttrsArray(props.data, 'trample_num') || 0),\n    ],\n    option: {\n      title: t('views.applicationOverview.monitor.charts.userSatisfaction'),\n      xData: getAttrsArray(props.data, 'day'),\n      yData: [\n        {\n          name: t('views.applicationOverview.monitor.charts.approval'),\n          data: getAttrsArray(props.data, 'star_num'),\n        },\n        {\n          name: t('views.applicationOverview.monitor.charts.disapproval'),\n          data: getAttrsArray(props.data, 'trample_num'),\n        },\n      ],\n    },\n  },\n])\n\nconst topOptions = [\n  { label: 'TOP 10', value: 10 },\n  { label: 'TOP 20', value: 20 },\n  { label: 'TOP 50', value: 50 },\n  { label: 'TOP 100', value: 100 },\n]\nconst tokenUsageCount = ref(10)\nconst topQuestionsCount = ref(10)\nconst tokenUsageOption = computed(() => {\n  return {\n    title: t('views.applicationOverview.monitor.charts.tokenUsage'),\n    xData: getAttrsArray(props.tokenUsage?.slice(0, tokenUsageCount.value), 'username'),\n    yData: [\n      {\n        data: getAttrsArray(props.tokenUsage?.slice(0, tokenUsageCount.value), 'token_usage'),\n      },\n    ],\n    dataZoom: props.tokenUsage.length > 20,\n  }\n})\nconst topQuestionsOption = computed(() => {\n  return {\n    title: t('views.applicationOverview.monitor.charts.topQuestions'),\n    xData: getAttrsArray(props.topQuestions?.slice(0, topQuestionsCount.value), 'username'),\n    yData: [\n      {\n        data: getAttrsArray(\n          props.topQuestions?.slice(0, topQuestionsCount.value),\n          'chat_record_count',\n        ),\n      },\n    ],\n    dataZoom: props.topQuestions.length > 20,\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.StatisticsCharts-card {\n  position: relative;\n  .top-select {\n    position: absolute;\n    top: 16px;\n    right: 16px;\n    z-index: 10;\n    width: 100px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application-overview/index.vue",
    "content": "<template>\n  <div class=\"p-16\">\n    <h2 class=\"mb-16 ml-8\">{{ $t('views.applicationOverview.title') }}</h2>\n    <el-scrollbar>\n      <div class=\"main-calc-height p-8 pt-0\">\n        <el-card style=\"--el-card-padding: 24px\">\n          <h4 class=\"title-decoration-1 mb-16\">\n            {{ $t('common.info') }}\n          </h4>\n          <el-card shadow=\"never\" class=\"overview-card\" v-loading=\"loading\">\n            <div class=\"title flex align-center\">\n              <div class=\"edit-avatar mr-12\">\n                <el-avatar shape=\"square\" :size=\"32\" style=\"background: none\">\n                  <img :src=\"resetUrl(detail?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n                </el-avatar>\n              </div>\n\n              <h4>{{ detail?.name || '-' }}</h4>\n            </div>\n\n            <el-row :gutter=\"12\">\n              <el-col :span=\"12\" class=\"mt-16\">\n                <div class=\"flex\">\n                  <el-text type=\"info\"\n                    >{{ $t('views.applicationOverview.appInfo.publicAccessLink') }}\n                  </el-text>\n                  <el-switch\n                    v-model=\"accessToken.is_active\"\n                    class=\"ml-8\"\n                    size=\"small\"\n                    inline-prompt\n                    :active-text=\"$t('views.applicationOverview.appInfo.openText')\"\n                    :inactive-text=\"$t('views.applicationOverview.appInfo.closeText')\"\n                    :before-change=\"() => changeState(accessToken.is_active)\"\n                  />\n                </div>\n\n                <div class=\"mt-4 mb-16 url-height flex align-center\" style=\"margin-bottom: 37px\">\n                  <span class=\"vertical-middle lighter break-all ellipsis-1\">\n                    {{ shareUrl }}\n                  </span>\n                  <el-tooltip effect=\"dark\" :content=\"$t('common.copy')\" placement=\"top\">\n                    <el-button type=\"primary\" text @click=\"copyClick(shareUrl)\">\n                      <AppIcon iconName=\"app-copy\"></AppIcon>\n                    </el-button>\n                  </el-tooltip>\n                  <el-tooltip effect=\"dark\" :content=\"$t('common.refresh')\" placement=\"top\">\n                    <el-button\n                      @click=\"refreshAccessToken\"\n                      type=\"primary\"\n                      text\n                      style=\"margin-left: 1px\"\n                    >\n                      <AppIcon iconName=\"app-refresh\"></AppIcon>\n                    </el-button>\n                  </el-tooltip>\n                </div>\n                <div>\n                  <el-button\n                    v-if=\"accessToken?.is_active\"\n                    :disabled=\"!accessToken?.is_active\"\n                    tag=\"a\"\n                    :href=\"shareUrl\"\n                    target=\"_blank\"\n                  >\n                    <AppIcon iconName=\"app-create-chat\" class=\"mr-4\"></AppIcon>\n                    {{ $t('views.application.operation.toChat') }}\n                  </el-button>\n                  <el-button v-else :disabled=\"!accessToken?.is_active\">\n                    <AppIcon iconName=\"app-create-chat\" class=\"mr-4\"></AppIcon>\n                    {{ $t('views.application.operation.toChat') }}\n                  </el-button>\n                  <el-button\n                    :disabled=\"!accessToken?.is_active\"\n                    @click=\"openDialog\"\n                    v-if=\"permissionPrecise.overview_embed(id)\"\n                  >\n                    <AppIcon iconName=\"app-export\" class=\"mr-4\"></AppIcon>\n                    {{ $t('views.applicationOverview.appInfo.embedInWebsite') }}\n                  </el-button>\n                  <!-- 访问限制 -->\n                  <el-button @click=\"openLimitDialog\" v-if=\"permissionPrecise.overview_access(id)\">\n                    <AppIcon iconName=\"app-lock\" class=\"mr-4\"></AppIcon>\n                    {{ $t('views.applicationOverview.appInfo.accessControl') }}\n                  </el-button>\n                  <!-- 显示设置 -->\n                  <el-button\n                    @click=\"openDisplaySettingDialog\"\n                    v-if=\"permissionPrecise.overview_display(id)\"\n                  >\n                    <AppIcon iconName=\"app-setting\" class=\"mr-4\"></AppIcon>\n                    {{ $t('views.applicationOverview.appInfo.displaySetting') }}\n                  </el-button>\n                </div>\n              </el-col>\n              <el-col :span=\"12\" class=\"mt-16\">\n                <div class=\"flex\">\n                  <el-text type=\"info\"\n                    >{{ $t('views.applicationOverview.appInfo.apiAccessCredentials') }}\n                  </el-text>\n                </div>\n                <div class=\"mt-4 mb-16 url-height\">\n                  <div>\n                    <el-text>API {{ $t('common.fileUpload.document') }}：</el-text>\n                    <el-button\n                      type=\"primary\"\n                      link\n                      @click=\"toUrl(apiUrl)\"\n                      class=\"vertical-middle lighter break-all\"\n                    >\n                      {{ apiUrl }}\n                    </el-button>\n                  </div>\n                  <div class=\"flex align-center\">\n                    <span class=\"flex\">\n                      <el-text style=\"width: 80px\">Base URL：</el-text>\n                    </span>\n\n                    <span class=\"vertical-middle lighter break-all ellipsis-1\">{{\n                      baseUrl + id\n                    }}</span>\n                    <el-tooltip effect=\"dark\" :content=\"$t('common.copy')\" placement=\"top\">\n                      <el-button type=\"primary\" text @click=\"copyClick(baseUrl + id)\">\n                        <AppIcon iconName=\"app-copy\"></AppIcon>\n                      </el-button>\n                    </el-tooltip>\n                  </div>\n                </div>\n                <div>\n                  <el-button\n                    @click=\"openAPIKeyDialog\"\n                    v-if=\"permissionPrecise.overview_api_key(id)\"\n                  >\n                    <el-icon class=\"mr-4\">\n                      <Key />\n                    </el-icon>\n                    {{ $t('views.applicationOverview.appInfo.apiKey') }}\n                  </el-button>\n                </div>\n              </el-col>\n            </el-row>\n          </el-card>\n        </el-card>\n        <el-card style=\"--el-card-padding: 24px\" class=\"mt-16\">\n          <h4 class=\"title-decoration-1 mb-16\">\n            {{ $t('views.applicationOverview.monitor.monitoringStatistics') }}\n          </h4>\n          <div class=\"mb-16\">\n            <el-select v-model=\"history_day\" class=\"mr-12 w-180\" @change=\"changeDayHandle\">\n              <el-option\n                v-for=\"item in dayOptions\"\n                :key=\"item.value\"\n                :label=\"item.label\"\n                :value=\"item.value\"\n              />\n            </el-select>\n            <el-date-picker\n              v-if=\"history_day === 'other'\"\n              v-model=\"daterangeValue\"\n              type=\"daterange\"\n              :start-placeholder=\"$t('views.applicationOverview.monitor.startDatePlaceholder')\"\n              :end-placeholder=\"$t('views.applicationOverview.monitor.endDatePlaceholder')\"\n              format=\"YYYY-MM-DD\"\n              value-format=\"YYYY-MM-DD\"\n              @change=\"changeDayRangeHandle\"\n            />\n          </div>\n          <div v-loading=\"statisticsLoading\">\n            <StatisticsCharts\n              :data=\"statisticsData\"\n              :token-usage=\"tokenUsage\"\n              :top-questions=\"topQuestions\"\n            />\n          </div>\n        </el-card>\n        <br />\n      </div>\n    </el-scrollbar>\n\n    <EmbedDialog\n      ref=\"EmbedDialogRef\"\n      :data=\"detail\"\n      :api-input-params=\"mapToUrlParams(apiInputParams)\"\n    />\n    <APIKeyDialog ref=\"APIKeyDialogRef\" />\n\n    <!-- 社区版访问限制 -->\n    <component :is=\"currentLimitDialog\" ref=\"LimitDialogRef\" @refresh=\"refresh\" />\n    <!-- 显示设置 -->\n    <component :is=\"currentDisplaySettingDialog\" ref=\"DisplaySettingDialogRef\" @refresh=\"refresh\" />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted, shallowRef, nextTick } from 'vue'\nimport { useRoute } from 'vue-router'\nimport EmbedDialog from './component/EmbedDialog.vue'\nimport APIKeyDialog from './component/APIKeyDialog.vue'\nimport LimitDialog from './component/LimitDialog.vue'\nimport XPackLimitDrawer from './xpack-component/XPackLimitDrawer.vue'\nimport DisplaySettingDialog from './component/DisplaySettingDialog.vue'\nimport XPackDisplaySettingDialog from './xpack-component/XPackDisplaySettingDialog.vue'\nimport StatisticsCharts from './component/StatisticsCharts.vue'\nimport { nowDate, beforeDay } from '@/utils/time'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { copyClick } from '@/utils/clipboard'\nimport { resetUrl } from '@/utils/common'\nimport { mapToUrlParams } from '@/utils/application'\nimport { t } from '@/locales'\nimport { EditionConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['application'][apiType.value]\n})\n\nconst apiUrl = window.location.origin + `${window.MaxKB.chatPrefix}/api-doc/`\n\nconst baseUrl = window.location.origin + `${window.MaxKB.chatPrefix}/api/`\n\nconst APIKeyDialogRef = ref()\nconst EmbedDialogRef = ref()\n\nconst accessToken = ref<any>({})\nconst detail = ref<any>(null)\n\nconst loading = ref(false)\n\nconst urlParams = computed(() =>\n  mapToUrlParams(apiInputParams.value) ? '?' + mapToUrlParams(apiInputParams.value) : '',\n)\nconst shareUrl = computed(\n  () =>\n    `${window.location.origin}${window.MaxKB.chatPrefix}/` +\n    accessToken.value?.access_token +\n    urlParams.value,\n)\n\nconst dayOptions = [\n  {\n    value: 7,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past7Days'),\n  },\n  {\n    value: 30,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past30Days'),\n  },\n  {\n    value: 90,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past90Days'),\n  },\n  {\n    value: 183,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past183Days'),\n  },\n  {\n    value: 'other',\n    label: t('common.custom'),\n  },\n]\n\nconst history_day = ref<number | string>(7)\n\n// 日期组件时间\nconst daterangeValue = ref('')\n\n// 提交日期时间\nconst daterange = ref({\n  start_time: '',\n  end_time: '',\n})\n\nconst statisticsLoading = ref(false)\nconst statisticsData = ref([])\nconst tokenUsage = ref([])\nconst topQuestions = ref([])\n\nconst apiInputParams = ref([])\n\nfunction toUrl(url: string) {\n  window.open(url, '_blank')\n}\n\n// 显示设置\nconst DisplaySettingDialogRef = ref()\nconst currentDisplaySettingDialog = shallowRef<any>(null)\n\nfunction openDisplaySettingDialog() {\n  // 企业版和专业版\n  if (hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')) {\n    currentDisplaySettingDialog.value = XPackDisplaySettingDialog\n  } else {\n    // 社区版\n    currentDisplaySettingDialog.value = DisplaySettingDialog\n  }\n  nextTick(() => {\n    if (currentDisplaySettingDialog.value == XPackDisplaySettingDialog) {\n      loadSharedApi({ type: 'application', systemType: apiType.value })\n        .getApplicationSetting(id)\n        .then((ok: any) => {\n          DisplaySettingDialogRef.value?.open(ok.data, detail.value)\n        })\n    } else {\n      DisplaySettingDialogRef.value?.open(accessToken.value, detail.value)\n    }\n  })\n}\n\n// 访问限制\nconst LimitDialogRef = ref()\nconst currentLimitDialog = shallowRef<any>(null)\n\nfunction openLimitDialog() {\n  // 企业版和专业版\n  if (hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')) {\n    currentLimitDialog.value = XPackLimitDrawer\n  } else {\n    // 社区版\n    currentLimitDialog.value = LimitDialog\n  }\n  nextTick(() => {\n    LimitDialogRef.value.open(accessToken.value)\n  })\n}\n\nfunction changeDayHandle(val: number | string) {\n  if (val !== 'other') {\n    daterange.value.start_time = beforeDay(val)\n    daterange.value.end_time = nowDate\n    getAppStatistics()\n  }\n}\n\nfunction changeDayRangeHandle(val: string) {\n  daterange.value.start_time = val[0]\n  daterange.value.end_time = val[1]\n  getAppStatistics()\n}\n\nfunction getAppStatistics() {\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getStatistics(id, daterange.value, statisticsLoading)\n    .then((res: any) => {\n      statisticsData.value = res.data\n    })\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getTokenUsage(id, daterange.value, statisticsLoading)\n    .then((res: any) => {\n      // [{'token_usage': 200, 'username': '张三'}, ...]\n      tokenUsage.value = res.data\n    })\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .topQuestions(id, daterange.value, statisticsLoading)\n    .then((res: any) => {\n      // [{'chat_record_count': 200, 'username': '张三'}, ...]\n      topQuestions.value = res.data\n    })\n}\n\nfunction refreshAccessToken() {\n  MsgConfirm(\n    t('views.applicationOverview.appInfo.refreshToken.msgConfirm1'),\n    t('views.applicationOverview.appInfo.refreshToken.msgConfirm2'),\n    {\n      confirmButtonText: t('common.confirm'),\n      cancelButtonText: t('common.cancel'),\n    },\n  )\n    .then(() => {\n      const obj = {\n        access_token_reset: true,\n      }\n      const str = t('views.applicationOverview.appInfo.refreshToken.refreshSuccess')\n      updateAccessToken(obj, str)\n    })\n    .catch(() => {})\n}\n\nasync function changeState(bool: boolean) {\n  const obj = {\n    is_active: !bool,\n  }\n  const str = obj.is_active ? t('common.status.enableSuccess') : t('common.status.disableSuccess')\n  await updateAccessToken(obj, str)\n    .then(() => {\n      return true\n    })\n    .catch(() => {\n      return false\n    })\n}\n\nasync function updateAccessToken(obj: any, str: string) {\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .putAccessToken(id as string, obj, loading)\n    .then((res: any) => {\n      accessToken.value = res?.data\n      MsgSuccess(str)\n    })\n}\n\nfunction openAPIKeyDialog() {\n  APIKeyDialogRef.value.open()\n}\n\nfunction openDialog() {\n  EmbedDialogRef.value.open(accessToken.value?.access_token)\n}\n\nfunction getAccessToken() {\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getAccessToken(id, loading)\n    .then((res: any) => {\n      accessToken.value = res?.data\n    })\n}\n\nfunction getDetail() {\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getApplicationDetail(id, loading)\n    .then((res: any) => {\n      detail.value = res.data\n      detail.value.work_flow?.nodes\n        ?.filter((v: any) => v.id === 'base-node')\n        .map((v: any) => {\n          apiInputParams.value = v.properties.api_input_field_list\n            ? v.properties.api_input_field_list.map((v: any) => {\n                return {\n                  name: v.variable,\n                  value: v.default_value,\n                }\n              })\n            : v.properties.input_field_list\n              ? v.properties.input_field_list\n                  .filter((v: any) => v.assignment_method === 'api_input')\n                  .map((v: any) => {\n                    return {\n                      name: v.variable,\n                      value: v.default_value,\n                    }\n                  })\n              : []\n        })\n    })\n}\n\nfunction refresh() {\n  getAccessToken()\n}\n\nonMounted(() => {\n  getDetail()\n  getAccessToken()\n  changeDayHandle(history_day.value)\n})\n</script>\n<style lang=\"scss\" scoped>\n.overview-card {\n  position: relative;\n\n  .active-button {\n    position: absolute;\n    right: 16px;\n    top: 21px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application-overview/xpack-component/XPackDisplaySettingDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.applicationOverview.appInfo.displaySetting')\"\n    width=\"900\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    align-center\n    class=\"display-setting-dialog\"\n  >\n    <template #header=\"{ titleId, titleClass }\">\n      <div class=\"flex-between mb-8\">\n        <h4 :id=\"titleId\" :class=\"titleClass\">\n          {{ $t('views.applicationOverview.appInfo.displaySetting') }}\n        </h4>\n        <div class=\"flex align-center\">\n          <el-button @click.prevent=\"resetForm\" link>\n            <el-icon class=\"mr-4\">\n              <Refresh />\n            </el-icon>\n            {{ $t('views.applicationOverview.SettingDisplayDialog.restoreDefault') }}\n          </el-button>\n          <el-divider direction=\"vertical\" />\n        </div>\n      </div>\n    </template>\n\n    <div class=\"flex\" style=\"height: 570px\">\n      <div class=\"setting-preview border border-r-6 mr-16\" style=\"min-width: 400px\">\n        <div\n          class=\"setting-preview-container\"\n          :style=\"{ backgroundImage: `url(${imgUrl?.chat_background})` }\"\n        >\n          <div class=\"setting-preview-header\" :style=\"customStyle\">\n            <div class=\"flex-between\">\n              <div class=\"flex align-center\">\n                <!-- 应用头像 -->\n                <div class=\"mr-12 ml-24 flex\">\n                  <el-avatar\n                    v-if=\"isAppIcon(imgUrl?.icon)\"\n                    shape=\"square\"\n                    :size=\"32\"\n                    style=\"background: none\"\n                  >\n                    <img :src=\"imgUrl?.icon\" alt=\"\" />\n                  </el-avatar>\n                  <LogoIcon v-else height=\"32px\" />\n                </div>\n\n                <h4 class=\"ellipsis\">\n                  {{ detail?.name || $t('common.name') }}\n                </h4>\n              </div>\n              <div class=\"mr-16\">\n                <el-button link>\n                  <AppIcon\n                    :iconName=\"'app-magnify'\"\n                    :style=\"{\n                      color: xpackForm.custom_theme?.header_font_color,\n                    }\"\n                    style=\"font-size: 20px\"\n                  ></AppIcon>\n                </el-button>\n                <el-button link>\n                  <el-icon\n                    :size=\"20\"\n                    class=\"color-secondary\"\n                    :style=\"{\n                      color: xpackForm.custom_theme?.header_font_color,\n                    }\"\n                  >\n                    <Close />\n                  </el-icon>\n                </el-button>\n              </div>\n            </div>\n          </div>\n          <div>\n            <div class=\"p-16\" style=\"position: relative\">\n              <div class=\"flex\">\n                <div class=\"avatar\" v-if=\"xpackForm.show_avatar\">\n                  <el-image\n                    v-if=\"imgUrl.avatar\"\n                    :src=\"imgUrl.avatar\"\n                    alt=\"\"\n                    fit=\"cover\"\n                    style=\"width: 28px; height: 28px; display: block\"\n                  />\n                  <LogoIcon\n                    v-else\n                    height=\"28px\"\n                    style=\"width: 28px; height: 28px; display: block\"\n                  />\n                </div>\n\n                <img\n                  src=\"@/assets/application/display-bg2.png\"\n                  alt=\"\"\n                  :width=\"\n                    xpackForm.show_avatar\n                      ? xpackForm.show_user_avatar\n                        ? '232px'\n                        : '270px'\n                      : xpackForm.show_user_avatar\n                        ? '260px'\n                        : '300px'\n                  \"\n                />\n              </div>\n              <div class=\"flex mt-4\" style=\"justify-content: flex-end\">\n                <img\n                  src=\"@/assets/application/display-bg3.png\"\n                  alt=\"\"\n                  :width=\"\n                    xpackForm.show_user_avatar\n                      ? xpackForm.show_avatar\n                        ? '227px'\n                        : '255px'\n                      : xpackForm.show_avatar\n                        ? '265px'\n                        : '292px'\n                  \"\n                  style=\"object-fit: contain\"\n                />\n                <div class=\"avatar ml-8\" v-if=\"xpackForm.show_user_avatar\">\n                  <el-image\n                    v-if=\"imgUrl.user_avatar\"\n                    :src=\"imgUrl.user_avatar\"\n                    alt=\"\"\n                    fit=\"cover\"\n                    style=\"width: 28px; height: 28px; display: block\"\n                  />\n                  <el-avatar v-else :size=\"28\">\n                    <img src=\"@/assets/user-icon.svg\" style=\"width: 50%\" alt=\"\" />\n                  </el-avatar>\n                </div>\n              </div>\n            </div>\n            <div\n              style=\"position: absolute; bottom: 0; padding-bottom: 8px; box-sizing: border-box\"\n              class=\"p-16 text-center w-full\"\n            >\n              <img src=\"@/assets/application/display-bg1.png\" alt=\"\" class=\"w-full\" />\n              <el-text type=\"info\" v-if=\"xpackForm.disclaimer\" class=\"mt-8 font-small\">\n                <auto-tooltip :content=\"xpackForm.disclaimer_value\">\n                  {{ xpackForm.disclaimer_value }}\n                </auto-tooltip>\n              </el-text>\n            </div>\n          </div>\n        </div>\n        <!-- 悬浮头像 -->\n        <div class=\"float_icon\">\n          <el-image\n            v-if=\"imgUrl.float_icon\"\n            :src=\"imgUrl.float_icon\"\n            alt=\"\"\n            fit=\"cover\"\n            style=\"width: 40px; height: 40px; display: block\"\n          />\n          <img\n            v-else\n            src=\"/MaxKB.gif\"\n            height=\"50px\"\n            style=\"width: 40px; height: 40px; display: block\"\n          />\n        </div>\n      </div>\n      <el-scrollbar>\n        <div class=\"p-8\">\n          <el-form ref=\"displayFormRef\" :model=\"xpackForm\">\n            <el-row class=\"w-full mb-8\">\n              <el-col :span=\"12\">\n                <h5 class=\"mb-8\">\n                  {{\n                    $t('views.applicationOverview.SettingDisplayDialog.customThemeColor')\n                  }}\n                </h5>\n                <div>\n                  <el-color-picker v-model=\"xpackForm.custom_theme.theme_color\" />\n                  {{\n                    !xpackForm.custom_theme.theme_color\n                      ? $t('views.applicationOverview.SettingDisplayDialog.default')\n                      : ''\n                  }}\n                </div>\n              </el-col>\n              <el-col :span=\"12\">\n                <h5 class=\"mb-8\">\n                  {{\n                    $t(\n                      'views.applicationOverview.SettingDisplayDialog.headerTitleFontColor',\n                    )\n                  }}\n                </h5>\n                <el-color-picker v-model=\"xpackForm.custom_theme.header_font_color\" />\n              </el-col>\n            </el-row>\n            <el-row class=\"w-full mb-8\">\n              <h5 class=\"mb-8\">\n                {{ $t('layout.language') }}\n              </h5>\n              <el-select v-model=\"xpackForm.language\" clearable>\n                <el-option\n                  v-for=\"item in langList\"\n                  :key=\"item.value\"\n                  :label=\"item.label\"\n                  :value=\"item.value\"\n                />\n              </el-select>\n            </el-row>\n            <!-- 应用 LOGO -->\n            <el-card shadow=\"never\" class=\"mb-8\">\n              <div class=\"flex-between mb-8\">\n                <span class=\"lighter\">{{ $t('views.application.title') + ' LOGO' }}</span>\n                <span class=\"flex align-center\">\n                  <el-upload\n                    class=\"ml-8\"\n                    ref=\"uploadRef\"\n                    action=\"#\"\n                    :auto-upload=\"false\"\n                    :show-file-list=\"false\"\n                    accept=\"image/jpeg, image/png, image/gif\"\n                    :on-change=\"(file: any, fileList: any) => onChange(file, fileList, 'icon')\"\n                  >\n                    <el-button size=\"small\">\n                      {{ $t('views.applicationOverview.SettingDisplayDialog.replace') }}\n                    </el-button>\n                  </el-upload>\n                </span>\n              </div>\n              <el-text type=\"info\" size=\"small\">\n                {{ $t('views.applicationOverview.SettingDisplayDialog.imageMessage') }}\n              </el-text>\n            </el-card>\n            <!-- 聊天背景 -->\n            <el-card shadow=\"never\" class=\"mb-8\">\n              <div class=\"flex-between mb-8\">\n                <span class=\"lighter\">{{\n                  $t('views.applicationOverview.SettingDisplayDialog.chatBackground')\n                }}</span>\n                <span class=\"flex align-center\">\n                  <el-upload\n                    class=\"ml-8\"\n                    ref=\"uploadRef\"\n                    action=\"#\"\n                    :auto-upload=\"false\"\n                    :show-file-list=\"false\"\n                    accept=\"image/jpeg, image/png, image/gif\"\n                    :on-change=\"\n                      (file: any, fileList: any) => onChange(file, fileList, 'chat_background')\n                    \"\n                  >\n                    <el-button size=\"small\">\n                      {{ $t('views.applicationOverview.SettingDisplayDialog.replace') }}\n                    </el-button>\n                  </el-upload>\n                </span>\n              </div>\n              <el-text type=\"info\" size=\"small\">\n                {{\n                  $t('views.applicationOverview.SettingDisplayDialog.chatBackgroundMessage')\n                }}\n              </el-text>\n            </el-card>\n            <!-- AI回复头像 -->\n            <el-card shadow=\"never\" class=\"mb-8\">\n              <div class=\"flex-between mb-8\">\n                <span class=\"lighter\">{{\n                  $t('views.applicationOverview.SettingDisplayDialog.AIAvatar')\n                }}</span>\n                <span class=\"flex align-center\">\n                  <el-checkbox v-model=\"xpackForm.show_avatar\">{{\n                    $t('views.applicationOverview.SettingDisplayDialog.display')\n                  }}</el-checkbox>\n                  <el-upload\n                    class=\"ml-8\"\n                    ref=\"uploadRef\"\n                    action=\"#\"\n                    :auto-upload=\"false\"\n                    :show-file-list=\"false\"\n                    accept=\"image/jpeg, image/png, image/gif\"\n                    :on-change=\"(file: any, fileList: any) => onChange(file, fileList, 'avatar')\"\n                  >\n                    <el-button size=\"small\">\n                      {{ $t('views.applicationOverview.SettingDisplayDialog.replace') }}\n                    </el-button>\n                  </el-upload>\n                </span>\n              </div>\n              <el-text type=\"info\" size=\"small\">\n                {{ $t('views.applicationOverview.SettingDisplayDialog.imageMessage') }}\n              </el-text>\n            </el-card>\n            <!-- 提问头像 -->\n            <el-card shadow=\"never\" class=\"mb-8\">\n              <div class=\"flex-between mb-8\">\n                <span class=\"lighter\">{{\n                  $t('views.applicationOverview.SettingDisplayDialog.askUserAvatar')\n                }}</span>\n                <span class=\"flex align-center\">\n                  <el-checkbox v-model=\"xpackForm.show_user_avatar\">\n                    {{\n                      $t('views.applicationOverview.SettingDisplayDialog.display')\n                    }}</el-checkbox\n                  >\n                  <el-upload\n                    class=\"ml-8\"\n                    ref=\"uploadRef\"\n                    action=\"#\"\n                    :auto-upload=\"false\"\n                    :show-file-list=\"false\"\n                    accept=\"image/jpeg, image/png, image/gif\"\n                    :on-change=\"\n                      (file: any, fileList: any) => onChange(file, fileList, 'user_avatar')\n                    \"\n                  >\n                    <el-button size=\"small\">\n                      {{ $t('views.applicationOverview.SettingDisplayDialog.replace') }}\n                    </el-button>\n                  </el-upload>\n                </span>\n              </div>\n              <el-text type=\"info\" size=\"small\"\n                >{{ $t('views.applicationOverview.SettingDisplayDialog.imageMessage') }}\n              </el-text>\n            </el-card>\n            <!-- 浮窗图标 -->\n            <el-card shadow=\"never\" class=\"mb-8\">\n              <div class=\"flex-between mb-8\">\n                <span class=\"lighter\">{{\n                  $t('views.applicationOverview.SettingDisplayDialog.floatIcon')\n                }}</span>\n                <el-upload\n                  ref=\"uploadRef\"\n                  action=\"#\"\n                  :auto-upload=\"false\"\n                  :show-file-list=\"false\"\n                  accept=\"image/jpeg, image/png, image/gif\"\n                  :on-change=\"(file: any, fileList: any) => onChange(file, fileList, 'float_icon')\"\n                >\n                  <el-button size=\"small\">\n                    {{ $t('views.applicationOverview.SettingDisplayDialog.replace') }}\n                  </el-button>\n                </el-upload>\n              </div>\n              <el-text type=\"info\" size=\"small\">\n                {{ $t('views.applicationOverview.SettingDisplayDialog.imageMessage') }}\n              </el-text>\n              <div class=\"border-t mt-8\">\n                <div class=\"flex-between mb-8\">\n                  <span class=\"lighter\">{{\n                    $t('views.applicationOverview.SettingDisplayDialog.iconDefaultPosition')\n                  }}</span>\n                  <el-checkbox\n                    v-model=\"xpackForm.draggable\"\n                    :label=\"\n                      $t('views.applicationOverview.SettingDisplayDialog.draggablePosition')\n                    \"\n                  />\n                </div>\n                <el-row :gutter=\"8\" class=\"w-full mb-8\">\n                  <el-col :span=\"12\">\n                    <div class=\"flex align-center\">\n                      <el-select v-model=\"xpackForm.float_location.x.type\" style=\"width: 80px\">\n                        <el-option\n                          :label=\"\n                            $t(\n                              'views.applicationOverview.SettingDisplayDialog.iconPosition.left',\n                            )\n                          \"\n                          value=\"left\"\n                        />\n                        <el-option\n                          :label=\"\n                            $t(\n                              'views.applicationOverview.SettingDisplayDialog.iconPosition.right',\n                            )\n                          \"\n                          value=\"right\"\n                        />\n                      </el-select>\n                      <el-input-number\n                        v-model=\"xpackForm.float_location.x.value\"\n                        :min=\"0\"\n                        :step=\"1\"\n                        :precision=\"0\"\n                        :value-on-clear=\"0\"\n                        step-strictly\n                        controls-position=\"right\"\n                      />\n                      <span class=\"ml-4\">px</span>\n                    </div>\n                  </el-col>\n                  <el-col :span=\"12\">\n                    <div class=\"flex align-center\">\n                      <el-select v-model=\"xpackForm.float_location.y.type\" style=\"width: 80px\">\n                        <el-option\n                          :label=\"\n                            $t(\n                              'views.applicationOverview.SettingDisplayDialog.iconPosition.top',\n                            )\n                          \"\n                          value=\"top\"\n                        />\n                        <el-option\n                          :label=\"\n                            $t(\n                              'views.applicationOverview.SettingDisplayDialog.iconPosition.bottom',\n                            )\n                          \"\n                          value=\"bottom\"\n                        />\n                      </el-select>\n                      <el-input-number\n                        v-model=\"xpackForm.float_location.y.value\"\n                        :min=\"0\"\n                        :step=\"1\"\n                        :precision=\"0\"\n                        :value-on-clear=\"0\"\n                        step-strictly\n                        controls-position=\"right\"\n                      />\n                      <span class=\"ml-4\">px</span>\n                    </div>\n                  </el-col>\n                </el-row>\n              </div>\n            </el-card>\n\n            <el-space direction=\"vertical\" alignment=\"start\" :size=\"2\">\n              <el-checkbox\n                v-model=\"xpackForm.show_source\"\n                :label=\"\n                  $t('views.applicationOverview.SettingDisplayDialog.showSourceLabel')\n                \"\n              />\n              <el-checkbox\n                v-model=\"xpackForm.show_exec\"\n                :label=\"\n                  $t('views.applicationOverview.SettingDisplayDialog.showExecutionDetail')\n                \"\n              />\n              <el-checkbox\n                v-model=\"xpackForm.show_history\"\n                :label=\"$t('views.applicationOverview.SettingDisplayDialog.showHistory')\"\n              />\n              <el-checkbox\n                v-model=\"xpackForm.show_guide\"\n                :label=\"$t('views.applicationOverview.SettingDisplayDialog.displayGuide')\"\n              />\n              <el-checkbox\n                v-model=\"xpackForm.disclaimer\"\n                :label=\"$t('views.applicationOverview.SettingDisplayDialog.disclaimer')\"\n                @change=\"changeDisclaimer\"\n              />\n              <span v-if=\"xpackForm.disclaimer\"\n                ><el-tooltip :content=\"xpackForm.disclaimer_value\" placement=\"top\">\n                  <el-input\n                    v-model=\"xpackForm.disclaimer_value\"\n                    style=\"width: 422px; margin-bottom: 10px\"\n                    @change=\"changeValue\"\n                    :maxlength=\"128\"\n                  /> </el-tooltip\n              ></span>\n            </el-space>\n          </el-form>\n        </div>\n      </el-scrollbar>\n    </div>\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\">{{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(displayFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { FormInstance, FormRules, UploadFiles } from 'element-plus'\nimport { isAppIcon } from '@/utils/common'\nimport { MsgSuccess, MsgError } from '@/utils/message'\nimport { langList, t } from '@/locales'\nimport { cloneDeep } from 'lodash'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\nconst {\n  params: { id },\n} = route\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst emit = defineEmits(['refresh'])\n\nconst defaultSetting = {\n  show_source: false,\n  show_exec: false,\n  language: '',\n  show_history: true,\n  draggable: true,\n  show_guide: true,\n  icon: '',\n  icon_url: '',\n  chat_background: '',\n  chat_background_url: '',\n  avatar: '',\n  avatar_url: '',\n  float_icon: '',\n  float_icon_url: '',\n  user_avatar: '',\n  user_avatar_url: '',\n  disclaimer: false,\n  disclaimer_value: t('views.applicationOverview.SettingDisplayDialog.disclaimerValue'),\n  custom_theme: {\n    theme_color: '',\n    header_font_color: '#1f2329',\n  },\n  float_location: {\n    y: { type: 'bottom', value: 30 },\n    x: { type: 'right', value: 0 },\n  },\n  show_avatar: true,\n  show_user_avatar: false,\n}\n\nconst displayFormRef = ref()\n\nconst xpackForm = ref<any>({\n  show_source: false,\n  show_exec: false,\n  language: '',\n  icon: '',\n  icon_url: '',\n  show_history: true,\n  draggable: false,\n  show_guide: false,\n  chat_background: '',\n  chat_background_url: '',\n  avatar: '',\n  avatar_url: '',\n  float_icon: '',\n  float_icon_url: '',\n  user_avatar: '',\n  user_avatar_url: '',\n  disclaimer: false,\n  disclaimer_value: t('views.applicationOverview.SettingDisplayDialog.disclaimerValue'),\n  custom_theme: {\n    theme_color: '',\n    header_font_color: '#1f2329',\n  },\n  float_location: {\n    y: { type: 'bottom', value: 30 },\n    x: { type: 'right', value: 0 },\n  },\n  show_avatar: true,\n  show_user_avatar: false,\n})\n\nconst imgUrl = ref<any>({\n  avatar: '',\n  float_icon: '',\n  user_avatar: '',\n  icon: '',\n  chat_background: '',\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\n\nconst detail = ref<any>(null)\n\nconst customStyle = computed(() => {\n  return {\n    background: xpackForm.value.custom_theme?.theme_color,\n    color: xpackForm.value.custom_theme?.header_font_color,\n  }\n})\n\nfunction resetForm() {\n  xpackForm.value = cloneDeep(defaultSetting)\n  imgUrl.value = {\n    avatar: '',\n    float_icon: '',\n    user_avatar: '',\n    icon: '',\n    chat_background: '',\n  }\n}\n\nconst onChange = (file: any, fileList: UploadFiles, attr: string) => {\n  //1、判断文件大小是否合法，文件限制不能大于 10 MB\n  const isLimit = file?.size / 1024 / 1024 < 10\n  if (!isLimit) {\n    MsgError(t('common.EditAvatarDialog.fileSizeExceeded'))\n    return false\n  } else {\n    xpackForm.value[attr] = file.raw\n    imgUrl.value[attr] = URL.createObjectURL(file.raw)\n    xpackForm.value[`${attr}_url`] = ''\n  }\n}\n\nconst open = (data: any, content: any) => {\n  detail.value = content\n  xpackForm.value.show_source = data.show_source\n  xpackForm.value.show_exec = data.show_exec\n  xpackForm.value.show_history = data.show_history\n  xpackForm.value.language = data.language\n  xpackForm.value.draggable = data.draggable\n  xpackForm.value.show_guide = data.show_guide\n  imgUrl.value.avatar = data.avatar\n  imgUrl.value.icon = data.icon\n  imgUrl.value.chat_background = data.chat_background\n  imgUrl.value.float_icon = data.float_icon\n  imgUrl.value.user_avatar = data.user_avatar\n  xpackForm.value.disclaimer = data.disclaimer\n  xpackForm.value.disclaimer_value = data.disclaimer_value\n  if (\n    xpackForm.value.disclaimer_value ===\n    t('views.applicationOverview.SettingDisplayDialog.disclaimerValue')\n  ) {\n    xpackForm.value.disclaimer_value = t(\n      'views.applicationOverview.SettingDisplayDialog.disclaimerValue',\n    )\n  }\n  xpackForm.value.avatar_url = data.avatar\n  xpackForm.value.chat_background_url = data.chat_background\n  xpackForm.value.icon_url = data.icon\n  xpackForm.value.user_avatar_url = data.user_avatar\n  xpackForm.value.float_icon_url = data.float_icon\n  xpackForm.value.show_avatar = data.show_avatar\n  xpackForm.value.show_user_avatar = data.show_user_avatar\n  xpackForm.value.custom_theme = {\n    theme_color: data.custom_theme?.theme_color || '',\n    header_font_color: data.custom_theme?.header_font_color || '#1f2329',\n  }\n  xpackForm.value.float_location = data.float_location\n  dialogVisible.value = true\n}\n\nconst changeValue = (value: string) => {\n  xpackForm.value.disclaimer_value = value\n}\n\nconst changeDisclaimer = (value: boolean) => {\n  xpackForm.value.disclaimer = value\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      const fd = new FormData()\n      Object.keys(xpackForm.value).map((item) => {\n        if (['custom_theme', 'float_location'].includes(item)) {\n          fd.append(item, JSON.stringify(xpackForm.value[item]))\n        } else {\n          fd.append(item, xpackForm.value[item])\n        }\n      })\n      loadSharedApi({ type: 'application', systemType: apiType.value })\n        .putXpackAccessToken(id as string, fd, loading)\n        .then(() => {\n          emit('refresh')\n          MsgSuccess(t('common.settingSuccess'))\n          dialogVisible.value = false\n        })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\">\n.setting-preview {\n  background: var(--app-layout-bg-color);\n  height: 570px;\n  position: relative;\n\n  .float_icon {\n    position: absolute;\n    right: 8px;\n    bottom: 15px;\n  }\n\n  .setting-preview-container {\n    position: absolute;\n    left: 16px;\n    top: 25px;\n    border-radius: 8px;\n    border: 1px solid #ffffff;\n    background: var(--dialog-bg-gradient-color);\n    background-repeat: no-repeat;\n    background-position: center;\n    background-size: auto 100%;\n    box-shadow: 0px 4px 8px 0px rgba(var(--el-text-color-primary-rgb), 0.1);\n    overflow: hidden;\n    width: 330px;\n    height: 520px;\n    .setting-preview-header {\n      background: var(--app-header-bg-color);\n      height: var(--app-header-height);\n      line-height: var(--app-header-height);\n      box-sizing: border-box;\n      border-bottom: 1px solid var(--el-border-color);\n    }\n  }\n}\n\n.display-setting-dialog {\n  .el-dialog__header {\n    padding-right: 17px;\n  }\n\n  .el-dialog__headerbtn {\n    top: 14px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application-overview/xpack-component/XPackLimitDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"dialogVisible\" size=\"60%\">\n    <template #header>\n      <h4>{{ $t('views.applicationOverview.appInfo.accessControl') }}</h4>\n    </template>\n    <el-form\n      label-position=\"top\"\n      ref=\"limitFormRef\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item\n        :label=\"$t('views.applicationOverview.appInfo.LimitDialog.clientQueryLimitLabel')\"\n      >\n        <el-input-number\n          v-model=\"form.access_num\"\n          :min=\"0\"\n          :step=\"1\"\n          :max=\"10000000\"\n          :value-on-clear=\"0\"\n          controls-position=\"right\"\n          style=\"width: 268px\"\n          step-strictly\n        />\n        <span class=\"ml-4\">{{\n          $t('views.applicationOverview.appInfo.LimitDialog.timesDays')\n        }}</span>\n      </el-form-item>\n      <!--     身份验证 -->\n      <el-form-item\n        :label=\"$t('views.applicationOverview.appInfo.LimitDialog.authentication')\"\n        @click.prevent\n      >\n        <el-switch size=\"small\" v-model=\"form.authentication\" @change=\"firstGeneration\"></el-switch>\n      </el-form-item>\n      <el-radio-group\n        v-if=\"form.authentication\"\n        v-model=\"form.authentication_value.type\"\n        class=\"card__radio\"\n      >\n        <el-card\n          shadow=\"never\"\n          class=\"mb-16\"\n          :class=\"form.authentication_value?.type === 'password' ? 'border-active' : ''\"\n        >\n          <el-radio value=\"password\" size=\"large\">\n            <p class=\"mb-4 lighter\">\n              {{ $t('views.applicationOverview.appInfo.LimitDialog.authenticationValue') }}\n            </p>\n          </el-radio>\n          <el-form-item class=\"ml-24\" v-if=\"form.authentication_value.type === 'password'\">\n            <div class=\"complex-input flex align-center\">\n              <el-input\n                class=\"complex-input__left\"\n                v-model=\"form.authentication_value.password_value\"\n                readonly\n                style=\"width: 268px\"\n              >\n              </el-input>\n              <div>\n                <el-tooltip :content=\"$t('common.copy')\" placement=\"top\">\n                  <el-button text @click=\"copyClick(form.authentication_value.password_value)\">\n                    <AppIcon iconName=\"app-copy\" class=\"color-secondary\"></AppIcon>\n                  </el-button>\n                </el-tooltip>\n                <el-tooltip :content=\"$t('common.refresh')\" placement=\"top\">\n                  <el-button\n                    @click=\"refreshAuthentication\"\n                    text\n                    style=\"margin: 0 4px 0 0 !important\"\n                  >\n                    <AppIcon iconName=\"app-refresh\" class=\"color-secondary\"></AppIcon>\n                  </el-button>\n                </el-tooltip>\n              </div>\n            </div>\n          </el-form-item>\n        </el-card>\n        <el-card\n          shadow=\"never\"\n          class=\"mb-16\"\n          :class=\"form.authentication_value.type === 'login' ? 'border-active' : ''\"\n        >\n          <el-radio value=\"login\" size=\"large\">\n            <p class=\"mb-16 lighter flex align-center\">\n              {{ $t('views.system.authentication.title') }}\n              <el-tooltip\n                popper-class=\"max-w-350\"\n                effect=\"dark\"\n                :content=\"$t('views.applicationOverview.appInfo.LimitDialog.authenticationTooltip')\"\n              >\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n\n              <el-button\n                v-if=\"form.authentication_value.type === 'login'\"\n                type=\"primary\"\n                link\n                @click=\"router.push({ name: 'applicationChatUser' })\"\n              >\n                {{ $t('views.applicationOverview.appInfo.LimitDialog.toSettingChatUser') }}\n              </el-button>\n            </p>\n          </el-radio>\n          <el-form-item\n            v-if=\"form.authentication_value.type === 'login'\"\n            :label=\"$t('views.applicationOverview.appInfo.LimitDialog.loginMethod')\"\n            :rules=\"[\n              {\n                required: true,\n                message: $t('views.applicationOverview.appInfo.LimitDialog.loginMethodRequired'),\n                trigger: 'change',\n              },\n            ]\"\n            prop=\"authentication_value.login_value\"\n            class=\"ml-24 border-t\"\n            style=\"padding-top: 16px\"\n          >\n            <el-checkbox-group v-model=\"form.authentication_value.login_value\">\n              <template v-for=\"t in auth_list\" :key=\"t.value\">\n                <el-checkbox :label=\"t.label\" :value=\"t.value\" />\n              </template>\n            </el-checkbox-group>\n          </el-form-item>\n          <el-form-item\n            class=\"ml-24\"\n            v-if=\"\n              form.authentication_value.type === 'login' &&\n              form.authentication_value?.login_value?.includes('LOCAL')\n            \"\n            :label=\"$t('views.system.display_code')\"\n            :rules=\"[\n              {\n                required: true,\n                message: $t('views.applicationOverview.appInfo.LimitDialog.displayCodeRequired'),\n                trigger: 'change',\n              },\n            ]\"\n            prop=\"authentication_value.max_attempts\"\n          >\n            <span style=\"font-size: 13px\">\n              {{ $t('views.system.loginFailed') }}\n            </span>\n            <el-input-number\n              style=\"margin-left: 8px\"\n              v-model=\"form.authentication_value.max_attempts\"\n              :min=\"-1\"\n              :max=\"10\"\n              :step=\"1\"\n              controls-position=\"right\"\n            />\n            <span class=\"ml-8\" style=\"font-size: 13px\">\n              {{ $t('views.system.loginFailedMessage') }}\n            </span>\n            <span class=\"ml-8 font-small\" style=\"color: #909399\">\n              ({{ $t('views.system.display_codeTip') }})\n            </span>\n          </el-form-item>\n        </el-card>\n      </el-radio-group>\n\n      <el-form-item\n        :label=\"$t('views.applicationOverview.appInfo.LimitDialog.whitelistLabel')\"\n        @click.prevent\n      >\n        <el-switch size=\"small\" v-model=\"form.white_active\"></el-switch>\n      </el-form-item>\n      <el-form-item v-if=\"form.white_active\">\n        <el-input\n          v-model=\"form.white_list\"\n          :placeholder=\"$t('views.applicationOverview.appInfo.LimitDialog.whitelistPlaceholder')\"\n          :rows=\"10\"\n          type=\"textarea\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <div>\n        <el-button @click.prevent=\"dialogVisible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit(limitFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </div>\n    </template>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport { useRoute, useRouter } from 'vue-router'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport { copyClick } from '@/utils/clipboard'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  params: { id },\n} = route\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst emit = defineEmits(['refresh'])\nconst auth_list = ref<Array<{ label: string; value: string }>>([])\nconst limitFormRef = ref()\nconst form = ref<any>({\n  access_num: 0,\n  white_active: true,\n  white_list: '',\n  authentication_value: {\n    type: 'password',\n    max_attempts: 1,\n  },\n  authentication: false,\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      access_num: 0,\n      white_active: true,\n      white_list: '',\n    }\n  }\n})\nconst open = (data: any) => {\n  form.value.access_num = data.access_num\n  form.value.white_active = data.white_active\n  form.value.white_list = data.white_list?.length ? data.white_list?.join('\\n') : ''\n  form.value.authentication_value = data.authentication_value || {\n    type: 'password',\n  }\n  if (\n    form.value.authentication_value.type === 'password' &&\n    !form.value.authentication_value.password_value\n  ) {\n    refreshAuthentication()\n  }\n  if (!form.value.authentication_value.max_attempts) {\n    form.value.authentication_value.max_attempts = 1\n  }\n  form.value.authentication = data.authentication\n  dialogVisible.value = true\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getChatUserAuthType()\n    .then((ok: any) => {\n      auth_list.value = ok.data\n    })\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      const obj = {\n        white_list: form.value.white_list ? form.value.white_list.split('\\n') : [],\n        white_active: form.value.white_active,\n        access_num: form.value.access_num,\n        authentication: form.value.authentication,\n        authentication_value: form.value.authentication_value,\n      }\n      loadSharedApi({ type: 'application', systemType: apiType.value })\n        .putAccessToken(id as string, obj, loading)\n        .then(() => {\n          emit('refresh')\n          MsgSuccess(t('common.settingSuccess'))\n          dialogVisible.value = false\n        })\n    }\n  })\n}\n\nfunction generateAuthenticationValue(length: number = 10) {\n  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'\n  const randomValues = new Uint8Array(length)\n  window.crypto.getRandomValues(randomValues)\n  return Array.from(randomValues)\n    .map((value) => chars[value % chars.length])\n    .join('')\n}\n\nfunction refreshAuthentication() {\n  form.value.authentication_value.password_value = generateAuthenticationValue()\n}\n\nfunction firstGeneration() {\n  if (form.value.authentication && !form.value.authentication_value.password_value) {\n    form.value.authentication_value = {\n      type: 'password',\n      password_value: generateAuthenticationValue(),\n    }\n    if (!form.value.authentication_value.max_attempts) {\n      form.value.authentication_value.max_attempts = 1\n    }\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/application-workflow/component/PublishHistory.vue",
    "content": "<template>\n  <div class=\"workflow-publish-history border-l white-bg\">\n    <h4 class=\"border-b p-16-24\">{{ $t('workflow.setting.releaseHistory') }}</h4>\n    <div class=\"list-height pt-0\">\n      <el-scrollbar>\n        <div class=\"p-8 pt-0\">\n          <common-list\n            :data=\"LogData\"\n            class=\"mt-8\"\n            v-loading=\"loading\"\n            @click=\"clickListHandle\"\n            @mouseenter=\"mouseenter\"\n            @mouseleave=\"mouseId = ''\"\n          >\n            <template #default=\"{ row, index }\">\n              <div class=\"flex-between\">\n                <div style=\"max-width: 80%\">\n                  <h5 :class=\"index === 0 ? 'primary' : ''\" class=\"flex align-center\">\n                    <ReadWrite\n                      @change=\"editName($event, row)\"\n                      :data=\"row.name || datetimeFormat(row.update_time)\"\n                      trigger=\"manual\"\n                      :write=\"row.writeStatus\"\n                      @close=\"closeWrite(row)\"\n                    />\n                    <el-tag v-if=\"index === 0\" size=\"small\" class=\"default-tag ml-4\">{{\n                      $t('workflow.setting.latestRelease')\n                    }}</el-tag>\n                  </h5>\n                  <el-text type=\"info\" class=\"color-secondary flex align-center mt-8\">\n                    <el-avatar :size=\"20\" class=\"avatar-grey mr-4\">\n                      <el-icon><UserFilled /></el-icon>\n                    </el-avatar>\n                    {{ row.publish_user_name }}\n                  </el-text>\n                </div>\n\n                <div @click.stop v-show=\"mouseId === row.id\">\n                  <el-dropdown trigger=\"click\" :teleported=\"false\">\n                    <el-button text>\n                      <AppIcon iconName=\"app-more\"></AppIcon>\n                    </el-button>\n                    <template #dropdown>\n                      <el-dropdown-menu>\n                        <el-dropdown-item @click.stop=\"openEditVersion(row)\">\n                          <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n                          {{ $t('common.edit') }}\n                        </el-dropdown-item>\n                        <el-dropdown-item @click=\"refreshVersion(row)\">\n                          <el-icon class=\"color-secondary\"><RefreshLeft /></el-icon>\n                          {{ $t('workflow.setting.restoreCurrentVersion') }}\n                        </el-dropdown-item>\n                      </el-dropdown-menu>\n                    </template>\n                  </el-dropdown>\n                </div>\n              </div>\n            </template>\n\n            <template #empty>\n              <div class=\"text-center\">\n                <el-text type=\"info\"> {{ $t('chat.noHistory') }}</el-text>\n              </div>\n            </template>\n          </common-list>\n        </div>\n      </el-scrollbar>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { datetimeFormat } from '@/utils/time'\nimport { MsgSuccess, MsgError } from '@/utils/message'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst emit = defineEmits(['click', 'refreshVersion'])\nconst loading = ref(false)\nconst LogData = ref<any[]>([])\n\nconst mouseId = ref('')\n\nfunction mouseenter(row: any) {\n  mouseId.value = row.id\n}\n\nfunction clickListHandle(item: any) {\n  emit('click', item)\n}\n\nfunction refreshVersion(item: any) {\n  emit('refreshVersion', item)\n}\n\nfunction openEditVersion(item: any) {\n  item['writeStatus'] = true\n}\n\nfunction closeWrite(item: any) {\n  item['writeStatus'] = false\n}\n\nfunction editName(val: string, item: any) {\n  if (val) {\n    const obj = {\n      name: val,\n    }\n    loadSharedApi({ type: 'workflowVersion', systemType: apiType.value })\n      .putWorkFlowVersion(id as string, item.id, obj, loading)\n      .then(() => {\n        MsgSuccess(t('common.modifySuccess'))\n        item['writeStatus'] = false\n        getList()\n      })\n  } else {\n    MsgError(t('workflow.tip.nameMessage'))\n  }\n}\n\nfunction getList() {\n  loadSharedApi({ type: 'workflowVersion', systemType: apiType.value })\n    .getWorkFlowVersion(id, loading)\n    .then((res: any) => {\n      LogData.value = res.data\n    })\n}\n\nonMounted(() => {\n  getList()\n})\n</script>\n<style lang=\"scss\" scoped>\n.workflow-publish-history {\n  width: 320px;\n  position: absolute;\n  right: 0;\n  top: 57px;\n  height: calc(100vh - 57px);\n  z-index: 9;\n  .list-height {\n    height: calc(100vh - 120px);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/application-workflow/index.vue",
    "content": "<template>\n  <div class=\"application-workflow\" v-loading=\"loading\">\n    <div class=\"header border-b flex-between p-12-24 white-bg\">\n      <div class=\"flex align-center\">\n        <back-button @click=\"back\"></back-button>\n        <h4 class=\"ellipsis\" style=\"max-width: 300px\" :title=\"detail?.name\">{{ detail?.name }}</h4>\n        <div v-if=\"showHistory && disablePublic\">\n          <el-text type=\"info\" class=\"ml-16 color-secondary\"\n            >{{ $t('workflow.info.previewVersion') }}\n            {{ currentVersion.name || datetimeFormat(currentVersion.update_time) }}</el-text\n          >\n        </div>\n        <el-text type=\"info\" class=\"ml-16 color-secondary\" v-else-if=\"saveTime\"\n          >{{ $t('workflow.info.saveTime') }}{{ datetimeFormat(saveTime) }}</el-text\n        >\n      </div>\n      <div v-if=\"showHistory && disablePublic\">\n        <el-button type=\"primary\" class=\"mr-8\" @click=\"refreshVersion()\">\n          {{ $t('workflow.setting.restoreVersion') }}\n        </el-button>\n        <el-divider direction=\"vertical\" />\n        <el-button text @click=\"closeHistory\">\n          <el-icon>\n            <Close />\n          </el-icon>\n        </el-button>\n      </div>\n      <div v-else>\n        <el-button\n          class=\"ml-8\"\n          v-if=\"permissionPrecise.edit(id)\"\n          @click=\"openTemplateStoreDialog()\"\n        >\n          <AppIcon iconName=\"app-template-center\" class=\"mr-4\" />\n          {{ $t('workflow.setting.templateCenter') }}\n        </el-button>\n        <el-button @click=\"showPopover = !showPopover\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\" />\n          {{ $t('workflow.setting.addComponent') }}\n        </el-button>\n        <el-button @click=\"clickShowDebug\" :disabled=\"showDebug\" v-if=\"permissionPrecise.debug(id)\">\n          <AppIcon iconName=\"app-debug-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.debug') }}\n        </el-button>\n        <el-button @click=\"saveApplication(true)\" v-if=\"permissionPrecise.edit(id)\">\n          <AppIcon iconName=\"app-save-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.save') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"publish\" v-if=\"permissionPrecise.edit(id)\">\n          {{ $t('common.publish') }}\n        </el-button>\n\n        <el-dropdown trigger=\"click\">\n          <el-button text @click.stop class=\"ml-8 mt-4\">\n            <AppIcon iconName=\"app-more\" class=\"rotate-90\"></AppIcon>\n          </el-button>\n          <template #dropdown>\n            <el-dropdown-menu>\n              <a :href=\"shareUrl\" target=\"_blank\">\n                <el-dropdown-item>\n                  <AppIcon iconName=\"app-create-chat\" class=\"color-secondary\"></AppIcon>\n                  {{ $t('views.application.operation.toChat') }}\n                </el-dropdown-item>\n              </a>\n\n              <el-dropdown-item @click=\"openHistory\">\n                <AppIcon iconName=\"app-history-outlined\" class=\"color-secondary\"></AppIcon>\n                {{ $t('workflow.setting.releaseHistory') }}\n              </el-dropdown-item>\n              <el-dropdown-item>\n                <AppIcon iconName=\"app-save-outlined\" class=\"color-secondary\"></AppIcon>\n                {{ $t('workflow.setting.autoSave') }}\n                <div class=\"ml-4\">\n                  <el-switch size=\"small\" v-model=\"isSave\" @change=\"changeSave\" />\n                </div>\n              </el-dropdown-item>\n            </el-dropdown-menu>\n          </template>\n        </el-dropdown>\n      </div>\n    </div>\n    <!-- 下拉框 -->\n    <el-collapse-transition>\n      <DropdownMenu\n        :show=\"showPopover\"\n        :id=\"id\"\n        v-click-outside=\"clickoutside\"\n        @clickNodes=\"clickNodes\"\n        @onmousedown=\"onmousedown\"\n        :workflowRef=\"workflowRef\"\n      />\n    </el-collapse-transition>\n    <!-- 主画布 -->\n    <div class=\"workflow-main\" ref=\"workflowMainRef\">\n      <workflow ref=\"workflowRef\" v-if=\"detail\" :data=\"detail?.work_flow\" />\n    </div>\n    <!-- 调试 -->\n    <el-collapse-transition>\n      <div class=\"workflow-debug-container\" :class=\"enlarge ? 'enlarge' : ''\" v-if=\"showDebug\">\n        <div class=\"workflow-debug-header\" :class=\"!isDefaultTheme ? 'custom-header' : ''\">\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <div class=\"mr-12 ml-24 flex\">\n                <el-avatar\n                  v-if=\"isAppIcon(detail?.icon)\"\n                  shape=\"square\"\n                  :size=\"32\"\n                  style=\"background: none\"\n                >\n                  <img :src=\"resetUrl(detail?.icon)\" alt=\"\" />\n                </el-avatar>\n                <LogoIcon v-else height=\"32px\" />\n              </div>\n\n              <h4 class=\"ellipsis\" style=\"max-width: 270px\" :title=\"detail?.name\">\n                {{ detail?.name || $t('common.name') }}\n              </h4>\n            </div>\n            <div class=\"mr-16\">\n              <el-button link @click=\"enlarge = !enlarge\">\n                <AppIcon\n                  :iconName=\"enlarge ? 'app-minify' : 'app-magnify'\"\n                  class=\"color-secondary\"\n                  style=\"font-size: 20px\"\n                >\n                </AppIcon>\n              </el-button>\n              <el-button link @click=\"showDebug = false\">\n                <el-icon :size=\"20\" class=\"color-secondary\">\n                  <Close />\n                </el-icon>\n              </el-button>\n            </div>\n          </div>\n        </div>\n        <div class=\"scrollbar-height\">\n          <AiChat :application-details=\"detail\" :type=\"'debug-ai-chat'\"></AiChat>\n        </div>\n      </div>\n    </el-collapse-transition>\n    <!-- 发布历史 -->\n    <PublishHistory\n      v-if=\"showHistory\"\n      @click=\"checkVersion\"\n      v-click-outside=\"clickoutsideHistory\"\n      @refreshVersion=\"refreshVersion\"\n    />\n     <TemplateStoreDialog\n      ref=\"templateStoreDialogRef\"\n      :api-type=\"apiType\"\n      source=\"work_flow\"\n      @refresh=\"getDetail\"\n    />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount, computed, nextTick, provide } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport type { Action } from 'element-plus'\nimport Workflow from '@/workflow/index.vue'\nimport DropdownMenu from '@/components/workflow-dropdown-menu/index.vue'\nimport PublishHistory from '@/views/application-workflow/component/PublishHistory.vue'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport { MsgSuccess, MsgError, MsgConfirm } from '@/utils/message'\nimport { datetimeFormat } from '@/utils/time'\nimport { mapToUrlParams } from '@/utils/application'\nimport useStore from '@/stores'\nimport { WorkFlowInstance } from '@/workflow/common/validate'\nimport { hasPermission } from '@/utils/permission'\nimport { t } from '@/locales'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { WorkflowMode } from '@/enums/application'\nimport TemplateStoreDialog from \"@/views/application/template-store/TemplateStoreDialog.vue\";\nprovide('getResourceDetail', () => detail)\nprovide('workflowMode', WorkflowMode.Application)\nprovide('loopWorkflowMode', WorkflowMode.ApplicationLoop)\nconst { theme } = useStore()\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  params: { id, from },\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['application'][apiType.value]\n})\n\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\n\nlet interval: any\nconst workflowRef = ref()\nconst workflowMainRef = ref()\nconst loading = ref(false)\nconst detail = ref<any>(null)\n\nconst showPopover = ref(false)\nconst showDebug = ref(false)\nconst enlarge = ref(false)\nconst saveTime = ref<any>('')\nconst isSave = ref(false)\nconst showHistory = ref(false)\nconst disablePublic = ref(false)\nconst currentVersion = ref<any>({})\nconst cloneWorkFlow = ref(null)\n\nconst apiInputParams = ref([])\n\nconst urlParams = computed(() =>\n  mapToUrlParams(apiInputParams.value) ? '?' + mapToUrlParams(apiInputParams.value) : '',\n)\nconst shareUrl = computed(\n  () =>\n    `${window.location.origin}${window.MaxKB.chatPrefix}/` +\n    detail.value?.access_token +\n    urlParams.value,\n)\n\nfunction back() {\n  if (JSON.stringify(cloneWorkFlow.value) !== JSON.stringify(getGraphData())) {\n    MsgConfirm(t('common.tip'), t('workflow.tip.saveMessage'), {\n      confirmButtonText: t('workflow.setting.exitSave'),\n      cancelButtonText: t('workflow.setting.exit'),\n      distinguishCancelAndClose: true,\n    })\n      .then(() => {\n        saveApplication(true, true)\n      })\n      .catch((action: Action) => {\n        if (action === 'cancel') {\n          go()\n        }\n      })\n  } else {\n    go()\n  }\n}\nfunction clickoutsideHistory() {\n  if (!disablePublic.value) {\n    showHistory.value = false\n    disablePublic.value = false\n  }\n}\n\nfunction refreshVersion(item?: any) {\n  if (item) {\n    renderGraphData(item)\n  }\n  // if (hasPermission(`APPLICATION:MANAGE:${id}`, 'AND') && isSave.value) {\n  //   initInterval()\n  // }\n  showHistory.value = false\n  disablePublic.value = false\n}\n\nfunction checkVersion(item: any) {\n  disablePublic.value = true\n  currentVersion.value = item\n  renderGraphData(item)\n  closeInterval()\n}\n\nfunction renderGraphData(item: any) {\n  item.work_flow['nodes'].map((v: any) => {\n    v['properties']['noRender'] = true\n  })\n  detail.value.work_flow = item.work_flow\n  saveTime.value = item?.update_time\n  workflowRef.value?.clearGraphData()\n  nextTick(() => {\n    workflowRef.value?.render(item.work_flow)\n  })\n}\n\nfunction closeHistory() {\n  getDetail()\n  if (isSave.value) {\n    initInterval()\n  }\n  showHistory.value = false\n  disablePublic.value = false\n}\n\nfunction openHistory() {\n  showHistory.value = true\n}\n\nfunction changeSave(bool: boolean) {\n  if (bool) {\n    initInterval()\n  } else {\n    closeInterval()\n  }\n  localStorage.setItem('workflowAutoSave', bool.toString())\n}\n\nfunction clickNodes(item: any) {\n  showPopover.value = false\n}\n\nfunction onmousedown(item: any) {\n  showPopover.value = false\n}\n\nfunction clickoutside() {\n  showPopover.value = false\n}\nconst publish = () => {\n  workflowRef.value\n    ?.validate()\n    .then(() => {\n      const workflow = getGraphData()\n      const workflowInstance = new WorkFlowInstance(workflow)\n      try {\n        workflowInstance.is_valid()\n      } catch (e: any) {\n        MsgError(e.toString())\n        return\n      }\n      loadSharedApi({ type: 'application', systemType: apiType.value })\n        .putApplication(id, { work_flow: workflow }, loading)\n        .then(() => {\n          return loadSharedApi({ type: 'application', systemType: apiType.value }).publish(\n            id,\n            {},\n            loading,\n          )\n        })\n        .then((ok: any) => {\n          detail.value.name = ok.data.name\n          ok.data.work_flow?.nodes\n            ?.filter((v: any) => v.id === 'base-node')\n            .map((v: any) => {\n              apiInputParams.value = v.properties.api_input_field_list\n                ? v.properties.api_input_field_list.map((v: any) => {\n                    return {\n                      name: v.variable,\n                      value: v.default_value,\n                    }\n                  })\n                : v.properties.input_field_list\n                  ? v.properties.input_field_list\n                      .filter((v: any) => v.assignment_method === 'api_input')\n                      .map((v: any) => {\n                        return {\n                          name: v.variable,\n                          value: v.default_value,\n                        }\n                      })\n                  : []\n            })\n          MsgSuccess(t('views.application.tip.publishSuccess'))\n        })\n        .catch((res: any) => {\n          const node = res.node\n          const err_message = res.errMessage\n          if (typeof err_message == 'string') {\n            MsgError(\n              res.node.properties?.stepName +\n                ` ${t('workflow.node').toLowerCase()} ` +\n                err_message.toLowerCase(),\n            )\n          } else {\n            const keys = Object.keys(err_message)\n            MsgError(\n              node.properties?.stepName +\n                ` ${t('workflow.node').toLowerCase()} ` +\n                err_message[keys[0]]?.[0]?.message.toLowerCase(),\n            )\n          }\n        })\n    })\n    .catch((res: any) => {\n      const node = res.node\n      const err_message = res.errMessage\n      if (typeof err_message == 'string') {\n        MsgError(res.node.properties?.stepName + ` ${t('workflow.node')}，` + err_message)\n      } else {\n        const keys = Object.keys(err_message)\n        MsgError(\n          node.properties?.stepName +\n            ` ${t('workflow.node')}，` +\n            err_message[keys[0]]?.[0]?.message,\n        )\n      }\n    })\n}\n\nconst clickShowDebug = () => {\n  workflowRef.value\n    ?.validate()\n    .then(() => {\n      const graphData = getGraphData()\n      const workflow = new WorkFlowInstance(graphData)\n      try {\n        workflow.is_valid()\n        detail.value = {\n          ...detail.value,\n          type: 'WORK_FLOW',\n          ...workflow.get_base_node()?.properties.node_data,\n          work_flow: getGraphData(),\n        }\n\n        showDebug.value = true\n      } catch (e: any) {\n        MsgError(e.toString())\n      }\n    })\n    .catch((res: any) => {\n      const node = res.node\n      const err_message = res.errMessage\n      if (typeof err_message == 'string') {\n        MsgError(res.node.properties?.stepName + ` ${t('workflow.node')}，` + err_message)\n      } else {\n        const keys = Object.keys(err_message)\n        MsgError(\n          node.properties?.stepName +\n            ` ${t('workflow.node')}，` +\n            err_message[keys[0]]?.[0]?.message,\n        )\n      }\n    })\n}\nfunction getGraphData() {\n  return workflowRef.value?.getGraphData()\n}\n\nfunction getDetail() {\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getApplicationDetail(id)\n    .then((res: any) => {\n      res.data?.work_flow['nodes'].map((v: any) => {\n        v['properties']['noRender'] = true\n      })\n      detail.value = res.data\n      detail.value.stt_model_id = res.data.stt_model\n      detail.value.tts_model_id = res.data.tts_model\n      detail.value.tts_type = res.data.tts_type\n      saveTime.value = res.data?.update_time\n      detail.value.work_flow?.nodes\n        ?.filter((v: any) => v.id === 'base-node')\n        .map((v: any) => {\n          apiInputParams.value = v.properties.api_input_field_list\n            ? v.properties.api_input_field_list.map((v: any) => {\n                return {\n                  name: v.variable,\n                  value: v.default_value,\n                }\n              })\n            : v.properties.input_field_list\n              ? v.properties.input_field_list\n                  .filter((v: any) => v.assignment_method === 'api_input')\n                  .map((v: any) => {\n                    return {\n                      name: v.variable,\n                      value: v.default_value,\n                    }\n                  })\n              : []\n        })\n      loadSharedApi({ type: 'application', systemType: apiType.value })\n        .getAccessToken(id, loading)\n        .then((res: any) => {\n          detail.value = { ...detail.value, ...res.data }\n        })\n      workflowRef.value?.clearGraphData()\n      nextTick(() => {\n        workflowRef.value?.render(detail.value.work_flow)\n        cloneWorkFlow.value = getGraphData()\n      })\n      // 企业版和专业版\n      if (hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')) {\n        loadSharedApi({ type: 'application', systemType: apiType.value })\n          .getApplicationSetting(id)\n          .then((ok: any) => {\n            detail.value = { ...detail.value, ...ok.data }\n          })\n      }\n    })\n}\n\nfunction saveApplication(bool?: boolean, back?: boolean) {\n  const obj = {\n    work_flow: getGraphData(),\n  }\n  loading.value = back || false\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .putApplication(id, obj)\n    .then(() => {\n      saveTime.value = new Date()\n      if (bool) {\n        cloneWorkFlow.value = getGraphData()\n        MsgSuccess(t('common.saveSuccess'))\n        if (back) {\n          go()\n        }\n      }\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\nconst go = () => {\n  if (route.path.includes('workspace')) {\n    return router.push({ path: get_route() })\n  } else {\n    return router.push({ path: get_resource_management_route() })\n  }\n}\n\nconst get_resource_management_route = () => {\n  if (hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_APPLICATION_OVERVIEW_READ], 'OR')) {\n    return `/application/${from}/${id}/WORK_FLOW/overview`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_APPLICATION_ACCESS_READ], 'OR')\n  ) {\n    return `/application/${from}/${id}/WORK_FLOW/access`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_APPLICATION_CHAT_USER_READ], 'OR')\n  ) {\n    return `/application/${from}/${id}/WORK_FLOW/chat-user`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_APPLICATION_CHAT_LOG_READ], 'OR')\n  ) {\n    return `/application/${from}/${id}/WORK_FLOW/chat-log`\n  } else {\n    return `/system/resource-management/application`\n  }\n}\n\nconst get_route = () => {\n  if (\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.APPLICATION_OVERVIEW_READ.getWorkspacePermissionWorkspaceManageRole,\n        PermissionConst.APPLICATION_OVERVIEW_READ.getApplicationWorkspaceResourcePermission(id),\n      ],\n      'OR',\n    )\n  ) {\n    return `/application/${from}/${id}/WORK_FLOW/overview`\n  } else if (\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(id)],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'AND',\n        ),\n        new ComplexPermission(\n          [RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n          [PermissionConst.APPLICATION_ACCESS_READ.getWorkspacePermissionWorkspaceManageRole],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'OR',\n        ),\n        new ComplexPermission(\n          [],\n          [PermissionConst.APPLICATION_ACCESS_READ.getApplicationWorkspaceResourcePermission(id)],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'OR',\n        ),\n      ],\n      'OR',\n    )\n  ) {\n    return `/application/${from}/${id}/WORK_FLOW/access`\n  } else if (\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(id)],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'AND',\n        ),\n        new ComplexPermission(\n          [RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n          [PermissionConst.APPLICATION_CHAT_USER_READ.getWorkspacePermissionWorkspaceManageRole],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'OR',\n        ),\n        new ComplexPermission(\n          [],\n          [\n            PermissionConst.APPLICATION_CHAT_USER_READ.getApplicationWorkspaceResourcePermission(\n              id,\n            ),\n          ],\n          [EditionConst.IS_EE, EditionConst.IS_PE],\n          'OR',\n        ),\n      ],\n      'OR',\n    )\n  ) {\n    return `/application/${from}/${id}/WORK_FLOW/chat-user`\n  } else if (\n    hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.APPLICATION.getApplicationWorkspaceResourcePermission(id)],\n          [],\n          'AND',\n        ),\n        PermissionConst.APPLICATION_CHAT_LOG_READ.getWorkspacePermissionWorkspaceManageRole,\n        PermissionConst.APPLICATION_CHAT_LOG_READ.getApplicationWorkspaceResourcePermission(id),\n      ],\n      'OR',\n    )\n  ) {\n    return `/application/${from}/${id}/WORK_FLOW/chat-log`\n  } else return `/application`\n}\n\n/**\n * 定时保存\n */\nconst initInterval = () => {\n  interval = setInterval(() => {\n    saveApplication()\n  }, 60000)\n}\n\n/**\n * 关闭定时\n */\nconst closeInterval = () => {\n  if (interval) {\n    clearInterval(interval)\n  }\n}\n\nconst templateStoreDialogRef = ref()\nfunction openTemplateStoreDialog() {\n  templateStoreDialogRef.value?.open()\n}\n\nonMounted(() => {\n  getDetail()\n  const workflowAutoSave = localStorage.getItem('workflowAutoSave')\n  isSave.value = workflowAutoSave === 'true' ? true : false\n  // 初始化定时任务\n  if (isSave.value) {\n    initInterval()\n  }\n})\n\nonBeforeUnmount(() => {\n  // 清除定时任务\n  closeInterval()\n  workflowRef.value?.clearGraphData()\n})\n</script>\n<style lang=\"scss\">\n.application-workflow {\n  background: var(--app-layout-bg-color);\n  height: 100%;\n\n  .workflow-main {\n    height: calc(100vh - 62px);\n    box-sizing: border-box;\n  }\n\n  .workflow-dropdown-tabs {\n    .el-tabs__nav-wrap {\n      padding: 0 16px;\n    }\n  }\n}\n\n.workflow-debug-container {\n  z-index: 2000;\n  position: relative;\n  border-radius: 8px;\n  border: 1px solid #ffffff;\n  background: var(--dialog-bg-gradient-color);\n  box-shadow: 0px 4px 8px 0px rgba(var(--el-text-color-primary-rgb), 0.1);\n  position: fixed;\n  bottom: 16px;\n  right: 16px;\n  overflow: hidden;\n  width: 460px;\n  height: 680px;\n\n  .workflow-debug-header {\n    background: var(--app-header-bg-color);\n    height: var(--app-header-height);\n    line-height: var(--app-header-height);\n    box-sizing: border-box;\n    border-bottom: 1px solid var(--el-border-color);\n  }\n\n  .scrollbar-height {\n    height: calc(100% - var(--app-header-height) - 24px);\n    padding-top: 24px;\n  }\n\n  &.enlarge {\n    width: 50% !important;\n    height: 100% !important;\n    bottom: 0 !important;\n    right: 0 !important;\n  }\n\n  .chat-width {\n    max-width: 100% !important;\n    margin: 0 auto;\n  }\n}\n\n@media only screen and (max-height: 680px) {\n  .workflow-debug-container {\n    height: 600px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/Share.vue",
    "content": "<template>\n  <div class=\"chat-share\">\n    <div class=\"chat-width\">\n      <div class=\"p-16-24 flex-between\">\n        <h4 class=\"ellipsis-1\" style=\"width: 66%\">\n          {{ currentChatName }}\n        </h4>\n      </div>\n      <div class=\"chat-share__main\">\n        <AiChat ref=\"AiChatRef\" :record=\"currentRecordList\" type=\"share\"> </AiChat>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onBeforeMount } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { t } from '@/locales'\nimport chatAPI from '@/api/chat/chat'\n\nconst route = useRoute()\nconst {\n  params: { link },\n} = route as any\n\nconst currentChatName = ref(t('chat.createChat'))\nconst currentRecordList = ref<any>([])\n\nfunction getShareChat() {\n  chatAPI.getShareLink(link).then((res) => {\n    if (res.data) {\n      currentChatName.value = res.data.abstract\n      currentRecordList.value = res.data.chat_record_list\n    }\n  })\n}\nonBeforeMount(() => {\n  getShareChat()\n})\n</script>\n<style lang=\"scss\" scoped>\n.chat-share {\n  background: #eef1f4;\n  &__main {\n    height: calc(100vh - 60px);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/auth/component/password.vue",
    "content": "<template>\n  <el-form ref=\"FormRef\" :model=\"form\" @submit.prevent=\"validator\">\n    <el-form-item prop=\"value\" :rules=\"rules.password\">\n      <el-input show-password v-model=\"form.password\" />\n    </el-form-item>\n    <el-button class=\"w-full mt-8\" type=\"primary\" @click=\"validator\" :loading=\"loading\">\n      {{ $t('common.confirm') }}</el-button\n    >\n  </el-form>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport useStore from '@/stores'\nimport { t } from '@/locales'\nimport { useRoute, useRouter } from 'vue-router'\nconst FormRef = ref()\n\nconst { chatUser } = useStore()\nconst loading = ref<boolean>(false)\nconst router = useRouter()\nconst route = useRoute()\n\nconst auth = () => {\n  return chatUser.passwordAuthentication(form.value.password).then((ok) => {\n    router.push({ name: 'chat', params: { accessToken: chatUser.accessToken }, query: route.query })\n  })\n}\nconst validator_auth = (rule: any, value: string, callback: any) => {\n  if (value === '') {\n    callback(new Error(t('chat.passwordValidator.errorMessage1')))\n  } else {\n    auth().catch(() => {\n      callback(new Error(t('chat.passwordValidator.errorMessage2')))\n    })\n  }\n}\nconst validator = () => {\n  FormRef.value.validate()\n}\n\nconst rules = {\n  password: [{ required: true, validator: validator_auth, trigger: 'manual' }],\n}\n\nconst form = ref({\n  password: '',\n})\n</script>\n<style lang=\"scss\">\n.positioned-mask {\n  top: var(--app-header-height);\n  height: calc(100% - var(--app-header-height));\n  .el-overlay-dialog {\n    top: var(--app-header-height);\n    height: calc(100% - var(--app-header-height));\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/auth/index.vue",
    "content": "<template>\n  <component\n    :is=\"auth_components[`/src/views/chat/auth/component/${auth_type}.vue`].default\"\n    v-model=\"is_auth\"\n    :applicationProfile=\"application_profile\"\n  />\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst auth_components: any = import.meta.glob('@/views/chat/auth/component/*.vue', {\n  eager: true,\n})\n\nconst emit = defineEmits(['update:modelValue'])\n\nconst props = withDefaults(\n  defineProps<{ modelValue: boolean; application_profile: any; auth_type?: string; style?: any }>(),\n  {\n    auth_type: 'password',\n    style: {},\n  },\n)\nconst is_auth = computed({\n  get: () => {\n    return props.modelValue\n  },\n  set: (v) => {\n    emit('update:modelValue', v)\n  },\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/chat/component/EditTitleDialog.vue",
    "content": "<template>\n  <el-dialog class=\"responsive-dialog\" :title=\"$t('chat.editTitle')\" v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\" :close-on-press-escape=\"false\" :destroy-on-close=\"true\" append-to-body>\n    <el-form label-position=\"top\" ref=\"fieldFormRef\" :model=\"form\" require-asterisk-position=\"right\">\n      <el-form-item prop=\"abstract\" :rules=\"[\n        {\n          required: true,\n          message: $t('common.inputPlaceholder'),\n          trigger: 'blur'\n        }\n      ]\">\n        <el-input v-model=\"form.abstract\" maxlength=\"1024\" show-word-limit type=\"textarea\"\n          @blur=\"form.abstract = form.abstract.trim()\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport chatAPI from '@/api/chat/chat'\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst applicationId = ref<string>('')\nconst chatId = ref<string>('')\n\nconst form = ref<any>({\n  abstract: ''\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any, id: string) => {\n  applicationId.value = id\n  chatId.value = row.id\n  form.value.abstract = row.abstract\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      chatAPI.modifyChat(chatId.value, form.value, loading).then(() => {\n        emit('refresh', chatId.value, form.value.abstract)\n        dialogVisible.value = false\n      })\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/chat/component/HistoryPanel.vue",
    "content": "<template>\n  <div class=\"history-component h-full\">\n    <el-menu\n      :default-active=\"currentChatId\"\n      :collapse=\"isPcCollapse\"\n      :collapse-transition=\"false\"\n      popper-class=\"chat-pc-popper\"\n      class=\"h-full\"\n    >\n      <div style=\"padding: 16px 18px 0 18px\">\n        <div class=\"flex align-center mb-16\">\n          <div class=\"flex mr-8\">\n            <el-avatar\n              v-if=\"isAppIcon(applicationDetail?.icon)\"\n              shape=\"square\"\n              :size=\"32\"\n              style=\"background: none\"\n            >\n              <img :src=\"applicationDetail?.icon\" alt=\"\" />\n            </el-avatar>\n            <LogoIcon v-else height=\"32px\" />\n          </div>\n          <h4\n            v-show=\"!isPcCollapse\"\n            :style=\"{ color: applicationDetail?.custom_theme?.header_font_color }\"\n            class=\"ellipsis\"\n            style=\"max-width: 185px\"\n            :title=\"applicationDetail?.name\"\n          >\n            {{ applicationDetail?.name }}\n          </h4>\n        </div>\n        <el-button\n          type=\"primary\"\n          plain\n          v-show=\"!isPcCollapse\"\n          class=\"add-button primary medium w-full\"\n          @click=\"newChat\"\n        >\n          <AppIcon iconName=\"app-create-chat\"></AppIcon>\n          <span class=\"ml-4\">{{ $t('chat.createChat') }}</span>\n        </el-button>\n        <div\n          v-show=\"!isPcCollapse\"\n          class=\"flex-between p-8 pb-0 color-secondary mt-8\"\n          v-if=\"showHistory\"\n        >\n          <span>{{ $t('chat.history') }}</span>\n          <el-tooltip effect=\"dark\" :content=\"$t('chat.clearChat')\" placement=\"right\">\n            <el-button text @click.stop=\"clearChat\">\n              <AppIcon iconName=\"app-delete\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </div>\n      </div>\n      <div v-show=\"!isPcCollapse\" class=\"left-height\" v-if=\"showHistory\">\n        <el-scrollbar>\n          <InfiniteScroll\n            :size=\"chatLogData.length\"\n            :total=\"_chatLogPagination?.total || 0\"\n            :page_size=\"_chatLogPagination?.page_size || 20\"\n            v-model:current_page=\"_chatLogPagination.current_page\"\n            @load=\"scrollData\"\n            :loading=\"leftLoading\"\n          >\n            <div class=\"p-16 pt-0\">\n              <common-list\n                :data=\"chatLogData\"\n                class=\"mt-8\"\n                v-loading=\"leftLoading\"\n                :defaultActive=\"currentChatId\"\n                @click=\"handleClickList\"\n                @mouseenter=\"mouseenter\"\n                @mouseleave=\"mouseId = ''\"\n              >\n                <template #default=\"{ row }\">\n                  <div class=\"flex-between\">\n                    <span :title=\"row.abstract\" class=\"ellipsis\" style=\"max-width: 180px\">\n                      {{ row.abstract }}\n                    </span>\n                    <div @click.stop v-show=\"mouseId === row.id && row.id !== 'new'\">\n                      <el-dropdown trigger=\"click\" :teleported=\"false\">\n                        <el-button text>\n                          <AppIcon iconName=\"app-more\"></AppIcon>\n                        </el-button>\n\n                        <template #dropdown>\n                          <el-dropdown-menu>\n                            <el-dropdown-item\n                              @click.stop=\"shareHandle()\"\n                              :disabled=\"currentChatId !== row.id || chat_loading\"\n                            >\n                              <AppIcon iconName=\"app-share\" class=\"color-secondary\"></AppIcon>\n                              {{ $t('chat.share') }}\n                            </el-dropdown-item>\n                            <el-dropdown-item @click.stop=\"editLogTitle(row)\">\n                              <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n                              {{ $t('common.edit') }}\n                            </el-dropdown-item>\n                            <el-dropdown-item @click.stop=\"deleteChatLog(row)\">\n                              <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                              {{ $t('common.delete') }}\n                            </el-dropdown-item>\n                          </el-dropdown-menu>\n                        </template>\n                      </el-dropdown>\n                    </div>\n                  </div>\n                </template>\n\n                <template #empty>\n                  <div class=\"text-center\">\n                    <el-text type=\"info\">{{ $t('chat.noHistory') }}</el-text>\n                  </div>\n                </template>\n              </common-list>\n            </div>\n          </InfiniteScroll>\n          <!-- <div v-if=\"chatLogData?.length\" class=\"text-center lighter color-secondary\">\n            <span>{{ $t('chat.only20history') }}</span>\n          </div> -->\n        </el-scrollbar>\n      </div>\n      <el-menu-item index=\"1\" v-show=\"isPcCollapse\" @click=\"newChat\">\n        <AppIcon iconName=\"app-create-chat\"></AppIcon>\n        <template #title>{{ $t('chat.createChat') }}</template>\n      </el-menu-item>\n\n      <el-sub-menu v-show=\"isPcCollapse\" index=\"2\" v-if=\"showHistory\" :teleported=\"false\">\n        <template #title>\n          <AppIcon iconName=\"app-history-outlined\" />\n        </template>\n        <div class=\"flex-between p-8 ml-8\">\n          <span>{{ $t('chat.history') }}</span>\n          <el-tooltip effect=\"dark\" :content=\"$t('chat.clearChat')\" placement=\"right\">\n            <el-button text @click.stop=\"clearChat\">\n              <AppIcon\n                iconName=\"app-delete\"\n                class=\"color-secondary\"\n                style=\"font-size: 16px\"\n              ></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </div>\n\n        <div class=\"left-height\">\n          <el-scrollbar>\n            <InfiniteScroll\n              :size=\"chatLogData.length\"\n              :total=\"_chatLogPagination?.total || 0\"\n              :page_size=\"_chatLogPagination?.page_size || 20\"\n              v-model:current_page=\"_chatLogPagination.current_page\"\n              @load=\"scrollData\"\n              :loading=\"leftLoading\"\n            >\n              <div v-loading=\"leftLoading\">\n                <el-menu-item\n                  v-for=\"row in chatLogData\"\n                  :index=\"row.id\"\n                  :key=\"row.id\"\n                  @click=\"handleClickList(row)\"\n                  @mouseenter=\"mouseenter(row)\"\n                  @mouseleave=\"mouseId = ''\"\n                >\n                  <div class=\"flex-between w-full lighter\">\n                    <span :title=\"row.abstract\" class=\"ellipsis\">\n                      {{ row.abstract }}\n                    </span>\n                    <div @click.stop class=\"flex\" v-show=\"mouseId === row.id && row.id !== 'new'\">\n                      <el-dropdown trigger=\"click\" :teleported=\"false\">\n                        <el-button text class=\"lighter\" style=\"padding: 1px !important\">\n                          <AppIcon iconName=\"app-more\" style=\"margin-right: 0\"></AppIcon>\n                        </el-button>\n\n                        <template #dropdown>\n                          <el-dropdown-menu>\n                            <el-dropdown-item @click.stop=\"editLogTitle(row)\">\n                              <AppIcon\n                                iconName=\"app-edit\"\n                                style=\"color: var(--app-text-color-secondary)\"\n                                class=\"mr-4\"\n                              ></AppIcon>\n                              {{ $t('common.edit') }}\n                            </el-dropdown-item>\n                            <el-dropdown-item @click.stop=\"deleteChatLog(row)\">\n                              <AppIcon\n                                iconName=\"app-delete\"\n                                style=\"color: var(--app-text-color-secondary)\"\n                                class=\"mr-4\"\n                              ></AppIcon>\n                              {{ $t('common.delete') }}\n                            </el-dropdown-item>\n                          </el-dropdown-menu>\n                        </template>\n                      </el-dropdown>\n                    </div>\n                  </div>\n                </el-menu-item>\n              </div>\n            </InfiniteScroll>\n          </el-scrollbar>\n        </div>\n        <div v-if=\"!chatLogData?.length\" class=\"text-center\">\n          <el-text type=\"info\">{{ $t('chat.noHistory') }}</el-text>\n        </div>\n      </el-sub-menu>\n    </el-menu>\n    <slot></slot>\n    <EditTitleDialog ref=\"EditTitleDialogRef\" @refresh=\"refreshFieldTitle\" />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, inject, type Ref } from 'vue'\nimport { isAppIcon } from '@/utils/common'\nimport EditTitleDialog from './EditTitleDialog.vue'\n\nconst scrollData = inject('scrollData') as any\n// 子组件\nconst chatLogPagination = inject('chatLogPagination') as any\nconst _chatLogPagination = chatLogPagination()\n\nconst props = defineProps<{\n  applicationDetail: any\n  chatLogData: any[]\n  leftLoading?: boolean\n  currentChatId: string\n  isPcCollapse?: boolean\n  chat_loading?: boolean\n}>()\nconst emit = defineEmits([\n  'newChat',\n  'clickLog',\n  'deleteLog',\n  'refreshFieldTitle',\n  'clearChat',\n  'clickShare',\n])\n\nconst showHistory = computed(() => {\n  return props.applicationDetail?.show_history != null || undefined\n    ? props.applicationDetail?.show_history\n    : true\n})\n\n// 更新页码的方法\nconst updateCurrentPage = (page: number) => {\n  if (chatLogPagination) {\n    chatLogPagination.current_page = page\n  }\n}\n\nconst EditTitleDialogRef = ref()\n\nconst mouseId = ref('')\n\nfunction mouseenter(row: any) {\n  mouseId.value = row.id\n}\n\nconst shareHandle = () => {\n  emit('clickShare')\n}\nconst newChat = () => {\n  emit('newChat')\n}\n\nconst handleClickList = (item: any) => {\n  emit('clickLog', item)\n}\n\nconst deleteChatLog = (row: any) => {\n  emit('deleteLog', row)\n}\n\nconst clearChat = () => {\n  emit('clearChat')\n}\n\nfunction editLogTitle(row: any) {\n  EditTitleDialogRef.value.open(row, props.applicationDetail.id)\n}\n\nfunction refreshFieldTitle(chatId: string, abstract: string) {\n  emit('refreshFieldTitle', chatId, abstract)\n}\n</script>\n<style lang=\"scss\" scoped>\n.history-component {\n  display: flex;\n  flex-direction: column;\n  border-right: 1px solid var(--el-menu-border-color);\n  background: var(--el-color-primary-light-06) !important;\n  :deep(.el-menu) {\n    background: none;\n    border: none;\n    &:not(.el-menu--collapse) {\n      width: 280px;\n    }\n    &.el-menu--collapse {\n      .el-sub-menu.is-active .el-sub-menu__title {\n        color: var(--el-text-color-primary) !important;\n      }\n    }\n    .el-sub-menu__title:hover {\n      background-color: var(--el-color-primary-light-9) !important;\n    }\n  }\n\n  .left-height {\n    height: calc(100vh - 210px);\n  }\n\n  :deep(.common-list li.active) {\n    background-color: #ffffff;\n    font-weight: 500;\n    color: var(--el-text-color-primary);\n    &:hover {\n      background-color: #ffffff;\n    }\n  }\n\n  .add-button {\n    border: 1px solid var(--el-color-primary-light-6);\n    background-color: var(--el-color-primary-light-9);\n    color: var(--el-color-primary);\n  }\n}\n</style>\n<style lang=\"scss\">\n.chat-pc-popper {\n  background: #eef1f4;\n  .el-menu {\n    background: var(--el-color-primary-light-06) !important;\n  }\n  .el-menu-item-group__title {\n    padding: 8px 8px 8px 16px;\n    font-weight: 500;\n    color: var(--app-text-color-secondary);\n  }\n  .el-menu-item {\n    border-radius: 6px;\n    height: 40px;\n    margin: 0 8px;\n    padding-left: 8px;\n    padding-right: 8px;\n    &:hover {\n      background-color: rgba(var(--el-text-color-primary-rgb), 0.1);\n    }\n    &.is-active {\n      background-color: #ffffff;\n      color: var(--el-text-color-primary);\n      // & > div {\n      //   font-weight: 500;\n      // }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/embed/component/ChatHistoryDrawer.vue",
    "content": "<template>\n  <div>\n    <el-drawer\n      v-model=\"show\"\n      :with-header=\"false\"\n      class=\"chat-history-drawer\"\n      direction=\"ltr\"\n      :size=\"280\"\n      style=\"--el-drawer-padding-primary: 0\"\n    >\n      <el-button class=\"collapse cursor\" circle @click=\"show = !show\">\n        <el-icon>\n          <component :is=\"!show ? 'ArrowRightBold' : 'ArrowLeftBold'\" />\n        </el-icon>\n      </el-button>\n      <HistoryPanel\n        :application-detail=\"applicationDetail\"\n        :chat-log-data=\"chatLogData\"\n        :left-loading=\"leftLoading\"\n        :currentChatId=\"currentChatId\"\n        @new-chat=\"newChat\"\n        @clickLog=\"handleClickList\"\n        @delete-log=\"deleteChatLog\"\n        @refreshFieldTitle=\"refreshFieldTitle\"\n        @clear-chat=\"clearChat\"\n        @clickShare=\"clickShareHandle\"\n      >\n        <div class=\"user-info p-16 cursor\">\n          <el-avatar\n            :size=\"32\"\n            v-if=\"\n              !chatUser.chat_profile?.authentication ||\n              chatUser.chat_profile.authentication_type === 'password'\n            \"\n          >\n            <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n          </el-avatar>\n          <el-dropdown v-else trigger=\"click\" type=\"primary\" class=\"w-full\">\n            <div class=\"flex align-center\">\n              <el-avatar :size=\"32\">\n                <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n              </el-avatar>\n              <span class=\"ml-8 color-text-primary\">{{ chatUser.chatUserProfile?.nick_name }}</span>\n            </div>\n\n            <template #dropdown>\n              <el-dropdown-menu style=\"min-width: 260px\">\n                <div class=\"flex align-center p-8\">\n                  <div class=\"mr-8 flex align-center\">\n                    <el-avatar :size=\"40\">\n                      <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n                    </el-avatar>\n                  </div>\n                  <div>\n                    <h4 class=\"medium mb-4\">{{ chatUser.chatUserProfile?.nick_name }}</h4>\n                    <div class=\"color-secondary\">\n                      {{ `${$t('common.username')}: ${chatUser.chatUserProfile?.username}` }}\n                    </div>\n                  </div>\n                </div>\n                <el-dropdown-item\n                  v-if=\"chatUser.chatUserProfile?.source === 'LOCAL'\"\n                  class=\"border-t\"\n                  style=\"padding-top: 8px; padding-bottom: 8px\"\n                  @click=\"openResetPassword\"\n                >\n                  <AppIcon iconName=\"app-key\" class=\"color-secondary\"></AppIcon>\n                  {{ $t('views.login.resetPassword') }}\n                </el-dropdown-item>\n                <el-dropdown-item\n                  class=\"border-t\"\n                  style=\"padding-top: 8px; padding-bottom: 8px\"\n                  @click=\"logout\"\n                >\n                  <AppIcon iconName=\"app-export\" class=\"color-secondary\" />\n                  {{ $t('layout.logout') }}\n                </el-dropdown-item>\n              </el-dropdown-menu>\n            </template>\n          </el-dropdown>\n        </div>\n      </HistoryPanel>\n    </el-drawer>\n\n    <ResetPassword\n      ref=\"resetPasswordRef\"\n      emitConfirm\n      @confirm=\"handleResetPassword\"\n    ></ResetPassword>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport useStore from '@/stores'\nimport HistoryPanel from '@/views/chat/component/HistoryPanel.vue'\nimport ResetPassword from '@/layout/layout-header/avatar/ResetPassword.vue'\nimport type { ResetCurrentUserPasswordRequest } from '@/api/type/user'\nimport chatAPI from '@/api/chat/chat'\nimport { useRoute, useRouter } from 'vue-router'\n\nconst router = useRouter()\nconst route = useRoute()\nconst show = defineModel<boolean>('show')\n\nconst props = defineProps<{\n  applicationDetail: any\n  chatLogData: any[]\n  leftLoading: boolean\n  currentChatId: string\n}>()\n\nconst emit = defineEmits([\n  'newChat',\n  'clickLog',\n  'deleteLog',\n  'refreshFieldTitle',\n  'clearChat',\n  'clickShare',\n])\n\nconst { chatUser } = useStore()\n\nconst clickShareHandle = () => {\n  emit('clickShare')\n}\nconst clearChat = () => {\n  emit('clearChat')\n}\n\nconst newChat = () => {\n  emit('newChat')\n}\n\nconst handleClickList = (item: any) => {\n  emit('clickLog', item)\n}\n\nconst deleteChatLog = (row: any) => {\n  emit('deleteLog', row)\n}\n\nfunction refreshFieldTitle(chatId: string, abstract: string) {\n  emit('refreshFieldTitle', chatId, abstract)\n}\n\nconst resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()\nconst openResetPassword = () => {\n  resetPasswordRef.value?.open()\n}\n\nconst handleResetPassword = (param: ResetCurrentUserPasswordRequest) => {\n  chatAPI.resetCurrentPassword(param).then(() => {\n    router.push({ name: 'login' })\n  })\n}\n\nconst logout = () => {\n  chatUser.logout().then(() => {\n    router.push({\n      name: 'login',\n      params: { accessToken: chatUser.accessToken },\n      query: route.query,\n    })\n  })\n}\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(.chat-history-drawer) {\n  overflow: visible;\n\n  .el-drawer__body {\n    padding: 0 !important;\n\n    .collapse {\n      position: absolute;\n      top: 20px;\n      right: -13px;\n      box-shadow: 0px 5px 10px 0px rgba(var(--el-text-color-primary-rgb), 0.1);\n      z-index: 1;\n      width: 24px;\n      height: 24px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/embed/index.vue",
    "content": "<template>\n  <div\n    class=\"chat-embed layout-bg chat-background\"\n    v-loading=\"loading\"\n    :style=\"{\n      '--el-color-primary': applicationDetail?.custom_theme?.theme_color,\n      '--el-color-primary-light-9': hexToRgba(\n        applicationDetail?.custom_theme?.theme_color || '#3370FF',\n        0.1,\n      ),\n      '--el-color-primary-light-6': hexToRgba(\n        applicationDetail?.custom_theme?.theme_color || '#3370FF',\n        0.4,\n      ),\n      '--el-color-primary-light-06': hexToRgba(\n        applicationDetail?.custom_theme?.theme_color || '#3370FF',\n        0.04,\n      ),\n      backgroundImage: `url(${applicationDetail?.chat_background})`,\n    }\"\n  >\n    <div class=\"chat-embed__header\" :style=\"customStyle\">\n      <div class=\"flex-between\">\n        <div class=\"flex align-center\">\n          <AppIcon\n            iconName=\"app-mobile-open-history\"\n            style=\"font-size: 20px\"\n            class=\"ml-16 cursor\"\n            @click.prevent.stop=\"show = true\"\n          />\n          <div class=\"mr-12 ml-16 flex\">\n            <el-avatar\n              v-if=\"isAppIcon(applicationDetail?.icon)\"\n              shape=\"square\"\n              :size=\"32\"\n              style=\"background: none\"\n            >\n              <img :src=\"applicationDetail?.icon\" alt=\"\" />\n            </el-avatar>\n            <LogoIcon v-else height=\"32px\" />\n          </div>\n\n          <h4 class=\"ellipsis\" style=\"max-width: 270px\" :title=\"applicationDetail?.name\">\n            {{ applicationDetail?.name }}\n          </h4>\n        </div>\n        <div style=\"margin-right: 85px\">\n          <el-button\n            text\n            @click=\"newChat\"\n            v-if=\"!showSelection\"\n            :style=\"{ color: applicationDetail?.custom_theme?.header_font_color }\"\n          >\n            <AppIcon iconName=\"app-create-chat\" style=\"font-size: 20px\"></AppIcon>\n          </el-button>\n          <el-tooltip\n            v-if=\"!showSelection && currentChatId !== 'new'\"\n            effect=\"dark\"\n            :content=\"$t('chat.share')\"\n            placement=\"top\"\n          >\n            <el-button text @click=\"clickShareHandle\" :disabled=\"AiChatRef?.loading\">\n              <AppIcon iconName=\"app-share\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </div>\n      </div>\n    </div>\n    <div>\n      <div class=\"chat-embed__main\">\n        <AiChat\n          ref=\"AiChatRef\"\n          v-model:applicationDetails=\"applicationDetail\"\n          :available=\"applicationAvailable\"\n          :appId=\"applicationDetail?.id\"\n          :record=\"currentRecordList\"\n          :chatId=\"currentChatId\"\n          type=\"ai-chat\"\n          @refresh=\"refresh\"\n          @scroll=\"handleScroll\"\n          class=\"AiChat-embed\"\n          v-model:selection=\"showSelection\"\n        >\n        </AiChat>\n      </div>\n\n      <ChatHistoryDrawer\n        v-model:show=\"show\"\n        :application-detail=\"applicationDetail\"\n        :chat-log-data=\"chatLogData\"\n        :left-loading=\"left_loading\"\n        :currentChatId=\"currentChatId\"\n        @new-chat=\"newChat\"\n        @clickLog=\"clickListHandle\"\n        @delete-log=\"deleteLog\"\n        @refreshFieldTitle=\"refreshFieldTitle\"\n        @clear-chat=\"clearChat\"\n        @clickShare=\"clickShareHandle\"\n      />\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, reactive, nextTick, computed, provide } from 'vue'\nimport { isAppIcon } from '@/utils/common'\nimport { hexToRgba } from '@/utils/theme'\nimport { t } from '@/locales'\nimport ChatHistoryDrawer from './component/ChatHistoryDrawer.vue'\nimport chatAPI from '@/api/chat/chat'\n\nprovide('scrollData', loadInfiniteScroll)\nprovide('chatLogPagination', () => chatLogPagination)\n\nconst AiChatRef = ref()\nconst loading = ref(false)\nconst left_loading = ref(false)\nconst chatLogData = ref<any[]>([])\nconst show = ref(false)\nconst props = defineProps<{\n  application_profile: any\n  applicationAvailable: boolean\n}>()\nconst applicationDetail = computed({\n  get: () => {\n    return props.application_profile\n  },\n  set: (v) => {},\n})\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst currentRecordList = ref<any>([])\nconst currentChatId = ref('new') // 当前历史记录Id 默认为'new'\n\nconst customStyle = computed(() => {\n  return {\n    background: applicationDetail.value?.custom_theme?.theme_color,\n    color: applicationDetail.value?.custom_theme?.header_font_color,\n  }\n})\n\nconst showSelection = ref(false)\nconst clickShareHandle = () => {\n  showSelection.value = true\n  show.value = false\n}\n\nfunction clearChat() {\n  chatAPI.clearChat(left_loading).then(() => {\n    currentChatId.value = 'new'\n    paginationConfig.current_page = 1\n    paginationConfig.total = 0\n    currentRecordList.value = []\n    getChatLog()\n  })\n}\n\nfunction deleteLog(row: any) {\n  chatAPI.deleteChat(row.id).then(() => {\n    if (currentChatId.value === row.id) {\n      currentChatId.value = 'new'\n      paginationConfig.current_page = 1\n      paginationConfig.total = 0\n      currentRecordList.value = []\n    }\n    chatLogData.value = chatLogData.value.filter((item) => item.id !== row.id)\n  })\n}\n\nfunction handleScroll(event: any) {\n  if (\n    currentChatId.value !== 'new' &&\n    event.scrollTop === 0 &&\n    paginationConfig.total > currentRecordList.value.length\n  ) {\n    const history_height = event.dialogScrollbar.offsetHeight\n    paginationConfig.current_page += 1\n    getChatRecord().then(() => {\n      event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)\n    })\n  }\n}\n\nconst newObj = {\n  id: 'new',\n  abstract: t('chat.createChat'),\n}\n\nfunction newChat() {\n  paginationConfig.current_page = 1\n  currentRecordList.value = []\n  if (!chatLogData.value.some((v) => v.id === 'new')) {\n    chatLogData.value.unshift(newObj)\n  }\n  currentChatId.value = 'new'\n  show.value = false\n}\n\nconst chatLogPagination = ref({\n  total: 0,\n  page_size: 20,\n  current_page: 1,\n})\nfunction getChatLog(refresh?: boolean) {\n  chatAPI\n    .pageChat(chatLogPagination.value.current_page, chatLogPagination.value.page_size, left_loading)\n    .then((res: any) => {\n      chatLogPagination.value.total = res.data.total\n      chatLogData.value = [...chatLogData.value, ...res.data.records]\n      if (!refresh) {\n        paginationConfig.current_page = 1\n        paginationConfig.total = 0\n        currentRecordList.value = []\n        currentChatId.value = 'new'\n      }\n    })\n}\n\nfunction loadInfiniteScroll() {\n  getChatLog(true)\n}\n\nfunction getChatRecord() {\n  return chatAPI\n    .pageChatRecord(\n      currentChatId.value,\n      paginationConfig.current_page,\n      paginationConfig.page_size,\n      loading,\n    )\n    .then((res: any) => {\n      paginationConfig.total = res.data.total\n      const list = res.data.records\n      list.map((v: any) => {\n        v['write_ed'] = true\n        v['record_id'] = v.id\n      })\n      currentRecordList.value = [...list, ...currentRecordList.value].sort((a, b) =>\n        a.create_time.localeCompare(b.create_time),\n      )\n      if (paginationConfig.current_page === 1) {\n        nextTick(() => {\n          // 将滚动条滚动到最下面\n          AiChatRef.value.setScrollBottom()\n        })\n      }\n    })\n}\n\nconst clickListHandle = (item: any) => {\n  if (item.id !== currentChatId.value) {\n    showSelection.value = false\n    paginationConfig.current_page = 1\n    currentRecordList.value = []\n    currentChatId.value = item.id\n    if (currentChatId.value !== 'new') {\n      getChatRecord()\n    }\n    show.value = false\n  }\n}\n\nfunction refreshFieldTitle(chatId: string, abstract: string) {\n  const find = chatLogData.value.find((item: any) => item.id == chatId)\n  if (find) {\n    find.abstract = abstract\n  }\n}\n\nfunction refresh(id: string) {\n  currentChatId.value = id\n  chatLogPagination.value.current_page = 1\n  chatLogData.value = []\n  getChatLog(true)\n}\n/**\n *初始化历史对话记录\n */\nconst init = () => {\n  getChatLog()\n}\n\nonMounted(() => {\n  init()\n})\n</script>\n<style lang=\"scss\">\n.chat-embed {\n  overflow: hidden;\n  &__header {\n    background: var(--app-header-bg-color);\n    position: fixed;\n    width: 100%;\n    left: 0;\n    top: 0;\n    z-index: 100;\n    height: var(--app-header-height);\n    line-height: var(--app-header-height);\n    box-sizing: border-box;\n    border-bottom: 1px solid var(--el-border-color);\n  }\n  &__main {\n    padding-top: calc(var(--app-header-height) + 16px);\n    height: calc(100vh - var(--app-header-height) - 16px);\n    overflow: hidden;\n  }\n\n  &.chat-embed--popup {\n    .chat-popover-button {\n      right: 85px;\n    }\n  }\n  .chat-popover-mask {\n    background-color: var(--el-overlay-color-lighter);\n    bottom: 0;\n    height: 100%;\n    left: 0;\n    overflow: auto;\n    position: fixed;\n    right: 0;\n    top: var(--app-header-height);\n    z-index: 2008;\n  }\n\n  .AiChat-embed {\n    .ai-chat__operate {\n      padding-top: 12px;\n    }\n  }\n}\n</style>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/chat/index.vue",
    "content": "<template>\n  <component\n    :applicationAvailable=\"applicationAvailable\"\n    :is=\"currentTemplate\"\n    :application_profile=\"chatUser.application\"\n    :key=\"route.fullPath\"\n  />\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onBeforeMount } from 'vue'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport { useI18n } from 'vue-i18n'\nconst { locale } = useI18n({ useScope: 'global' })\nconst route = useRoute()\nconst { chatUser, common } = useStore()\n\nconst components: any = import.meta.glob('@/views/chat/**/index.vue', {\n  eager: true,\n})\n\nconst {\n  query: { mode },\n} = route as any\n\nconst currentTemplate = computed(() => {\n  let modeName = ''\n  if (chatUser.application) {\n    if (!mode || mode === 'pc') {\n      modeName = common.isMobile() ? 'mobile' : 'pc'\n    } else {\n      modeName = mode\n    }\n  } else {\n    modeName = 'no-service'\n  }\n\n  const name = `/src/views/chat/${modeName}/index.vue`\n  return components[name].default\n})\n\nconst applicationAvailable = ref<boolean>(true)\nonBeforeMount(() => {\n  locale.value = chatUser.getLanguage()\n})\n</script>\n"
  },
  {
    "path": "ui/src/views/chat/mobile/component/ChatHistoryDrawer.vue",
    "content": "<template>\n  <div>\n    <el-drawer\n      v-model=\"show\"\n      :with-header=\"false\"\n      class=\"chat-history-drawer\"\n      direction=\"ltr\"\n      :size=\"280\"\n      style=\"--el-drawer-padding-primary: 0\"\n    >\n      <HistoryPanel\n        :application-detail=\"applicationDetail\"\n        :chat-log-data=\"chatLogData\"\n        :left-loading=\"leftLoading\"\n        :currentChatId=\"currentChatId\"\n        @new-chat=\"newChat\"\n        @clickLog=\"handleClickList\"\n        @delete-log=\"deleteChatLog\"\n        @refreshFieldTitle=\"refreshFieldTitle\"\n        @clear-chat=\"clearChat\"\n        @clickShare=\"clickShareHandle\"\n      >\n        <div class=\"flex align-center user-info p-16\" @click=\"toUserCenter\">\n          <el-avatar\n            :size=\"32\"\n            :class=\"`${!chatUser.chat_profile?.authentication || chatUser.chat_profile.authentication_type === 'password' ? 'cursor-default' : ''}`\"\n          >\n            <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n          </el-avatar>\n          <span v-if=\"chatUser.chat_profile?.authentication\" class=\"ml-8 color-text-primary\">\n            {{ chatUser.chatUserProfile?.nick_name }}\n          </span>\n        </div>\n      </HistoryPanel>\n    </el-drawer>\n\n    <UserCenterDrawer v-model:show=\"userCenterDrawerShow\" />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport useStore from '@/stores'\nimport UserCenterDrawer from './UserCenterDrawer.vue'\nimport HistoryPanel from '@/views/chat/component/HistoryPanel.vue'\n\nconst show = defineModel<boolean>('show')\n\nconst props = defineProps<{\n  applicationDetail: any\n  chatLogData: any[]\n  leftLoading?: boolean\n  currentChatId: string\n}>()\n\nconst emit = defineEmits([\n  'newChat',\n  'clickLog',\n  'deleteLog',\n  'refreshFieldTitle',\n  'clearChat',\n  'clickShare',\n])\n\nconst { chatUser } = useStore()\n\nconst clickShareHandle = () => {\n  emit('clickShare')\n}\n\nconst clearChat = () => {\n  emit('clearChat')\n}\n\nconst newChat = () => {\n  emit('newChat')\n}\n\nconst handleClickList = (item: any) => {\n  emit('clickLog', item)\n}\n\nconst deleteChatLog = (row: any) => {\n  emit('deleteLog', row)\n}\nfunction refreshFieldTitle(chatId: string, abstract: string) {\n  emit('refreshFieldTitle', chatId, abstract)\n}\n\nconst userCenterDrawerShow = ref(false)\nfunction toUserCenter() {\n  if (\n    !chatUser.chat_profile?.authentication ||\n    chatUser.chat_profile.authentication_type === 'password'\n  )\n    return\n  userCenterDrawerShow.value = true\n}\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(.chat-history-drawer) {\n  .el-drawer__body {\n    padding: 0 !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/mobile/component/ResetPasswordDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"show\" :with-header=\"false\" class=\"reset-password-drawer\" size=\"100%\">\n    <div class=\"navigation flex align-center mb-16\">\n      <el-icon size=\"16\" @click=\"show = false\">\n        <ArrowLeftBold />\n      </el-icon>\n    </div>\n    <h2 class=\"mb-16\">{{ $t('views.login.resetPassword') }}</h2>\n\n    <el-form ref=\"resetPasswordFormRef\" :model=\"resetPasswordForm\" :rules=\"rules\">\n      <el-form-item prop=\"password\">\n        <el-input\n          type=\"password\"\n          size=\"large\"\n          v-model=\"resetPasswordForm.password\"\n          :placeholder=\"$t('views.login.loginForm.new_password.placeholder')\"\n          show-password\n        >\n        </el-input>\n      </el-form-item>\n      <el-form-item prop=\"re_password\">\n        <el-input\n          type=\"password\"\n          size=\"large\"\n          v-model=\"resetPasswordForm.re_password\"\n          :placeholder=\"$t('views.login.loginForm.re_password.placeholder')\"\n          show-password\n        >\n        </el-input>\n      </el-form-item>\n    </el-form>\n    <el-button type=\"primary\" size=\"large\" class=\"w-full\" @click=\"resetPassword\">{{\n      $t('chat.confirmModification')\n    }}</el-button>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { t } from '@/locales'\nimport type { ResetCurrentUserPasswordRequest } from '@/api/type/user'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport useStore from '@/stores'\nimport chatAPI from '@/api/chat/chat'\nimport { useRouter } from 'vue-router'\nimport { MsgSuccess } from '@/utils/message'\n\nconst router = useRouter()\nconst { chatUser } = useStore()\n\nconst show = defineModel<boolean>('show', {\n  required: true,\n})\n\nconst resetPasswordFormRef = ref<FormInstance>()\n\nconst resetPasswordForm = ref<ResetCurrentUserPasswordRequest>({\n  password: '',\n  re_password: '',\n})\n\nconst rules = ref<FormRules<ResetCurrentUserPasswordRequest>>({\n  password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.new_password.placeholder'),\n      trigger: 'blur',\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur',\n    },\n  ],\n  re_password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.re_password.requiredMessage'),\n      trigger: 'blur',\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur',\n    },\n    {\n      validator: (rule, value, callback) => {\n        if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {\n          callback(new Error(t('views.login.loginForm.re_password.validatorMessage')))\n        } else {\n          callback()\n        }\n      },\n      trigger: 'blur',\n    },\n  ],\n})\n\nfunction resetPassword() {\n  resetPasswordFormRef.value?.validate().then(() => {\n    chatAPI.resetCurrentPassword(resetPasswordForm.value).then(() => {\n      MsgSuccess(t('common.modifySuccess'))\n      router.push({name: 'login'})\n    })\n  })\n}\n</script>\n\n<style lang=\"scss\">\n.reset-password-drawer {\n  .el-drawer__body {\n    padding: 16px;\n    padding-top: 0;\n    background: #ffffff !important;\n\n    .navigation {\n      height: 44px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/mobile/component/UserCenterDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"show\" :with-header=\"false\" class=\"user-center-drawer\" size=\"100%\">\n    <div class=\"flex-center navigation mb-8\">\n      <el-icon size=\"16\" @click=\"show = false\">\n        <ArrowLeftBold />\n      </el-icon>\n      <h4 class=\"medium\">{{ $t('chat.mine') }}</h4>\n    </div>\n    <div class=\"card-item info p-16\">\n      <el-avatar :size=\"64\">\n        <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n      </el-avatar>\n      <h2 class=\"mt-12 mb-4\">{{ chatUser.chatUserProfile?.nick_name }}</h2>\n      <div class=\"color-secondary lighter\">\n        {{ `${$t('common.username')}: ${chatUser.chatUserProfile?.username}` }}\n      </div>\n    </div>\n\n    <div\n      class=\"card-item reset-password flex-between\"\n      v-if=\"chatUser.chatUserProfile?.source === 'LOCAL'\"\n      @click=\"resetPassword\"\n    >\n      <div class=\"flex align-center\">\n        <AppIcon iconName=\"app-key\" class=\"mr-12\"></AppIcon>\n        <h4 class=\"lighter\">{{ $t('views.login.resetPassword') }}</h4>\n      </div>\n      <el-icon size=\"16\">\n        <ArrowRight />\n      </el-icon>\n    </div>\n\n    <div\n      class=\"card-item logout\"\n      @click=\"logout\"\n    >\n      <h4 class=\"lighter\">{{ $t('layout.logout') }}</h4>\n    </div>\n\n    <ResetPasswordDrawer v-model:show=\"resetPasswordDrawerShow\" />\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport useStore from '@/stores'\nimport { MsgConfirm } from '@/utils/message'\nimport { t } from '@/locales'\nimport { useRouter } from 'vue-router'\nimport ResetPasswordDrawer from './ResetPasswordDrawer.vue'\n\nconst router = useRouter()\nconst { chatUser } = useStore()\n\nconst show = defineModel<boolean>('show', {\n  required: true,\n})\n\nconst resetPasswordDrawerShow = ref(false)\nfunction resetPassword() {\n  resetPasswordDrawerShow.value = true\n}\n\nfunction logout() {\n  MsgConfirm(t('layout.logout'), t('chat.logoutContent'), {\n    confirmButtonText: t('layout.logout'),\n    confirmButtonClass: 'danger',\n  }).then(() => {\n    chatUser.logout().then(() => {\n      router.push({ name: 'login' })\n    })\n  })\n}\n</script>\n\n<style lang=\"scss\">\n.user-center-drawer {\n  .el-drawer__body {\n    padding: 16px;\n    padding-top: 0;\n    background:\n      linear-gradient(187.61deg, rgba(235, 241, 255, 0.2) 39.6%, rgba(231, 249, 255, 0.2) 94.3%),\n      #eff0f1;\n\n    .navigation {\n      height: 44px;\n      position: relative;\n\n      i {\n        position: absolute;\n        left: 0;\n      }\n    }\n\n    .card-item {\n      background-color: #ffffff;\n      border-radius: 8px;\n      margin-bottom: 16px;\n\n      &.info {\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n      }\n\n      &.logout,\n      &.reset-password {\n        padding: 13px 16px;\n      }\n\n      &.logout {\n        color: #f54a45;\n        display: flex;\n        justify-content: center;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/mobile/index.vue",
    "content": "<template>\n  <div\n    class=\"chat-mobile layout-bg chat-background\"\n    :style=\"{\n      '--el-color-primary': applicationDetail?.custom_theme?.theme_color,\n      '--el-color-primary-light-9': hexToRgba(\n        applicationDetail?.custom_theme?.theme_color || '#3370FF',\n        0.1,\n      ),\n      '--el-color-primary-light-6': hexToRgba(\n        applicationDetail?.custom_theme?.theme_color || '#3370FF',\n        0.4,\n      ),\n      '--el-color-primary-light-06': hexToRgba(\n        applicationDetail?.custom_theme?.theme_color || '#3370FF',\n        0.04,\n      ),\n      backgroundImage: `url(${applicationDetail?.chat_background})`,\n    }\"\n  >\n    <div class=\"chat-mobile__header\" :style=\"customStyle\">\n      <div class=\"flex-between\">\n        <div class=\"flex align-center\">\n          <AppIcon\n            iconName=\"app-mobile-open-history\"\n            style=\"font-size: 20px\"\n            class=\"ml-16 cursor\"\n            @click.prevent.stop=\"show = true\"\n          />\n          <div class=\"mr-12 ml-16 flex\">\n            <el-avatar\n              v-if=\"isAppIcon(applicationDetail?.icon)\"\n              shape=\"square\"\n              :size=\"32\"\n              style=\"background: none\"\n            >\n              <img :src=\"applicationDetail?.icon\" alt=\"\" />\n            </el-avatar>\n            <LogoIcon v-else height=\"32px\" />\n          </div>\n\n          <h4 class=\"ellipsis\" style=\"max-width: 270px\" :title=\"applicationDetail?.name\">\n            {{ applicationDetail?.name }}\n          </h4>\n        </div>\n        <div>\n          <el-button\n            text\n            @click=\"newChat\"\n            :class=\"currentChatId === 'new' ? 'mr-16' : ''\"\n            v-if=\"!showSelection\"\n            :style=\"{ color: applicationDetail?.custom_theme?.header_font_color }\"\n          >\n            <AppIcon iconName=\"app-create-chat\" style=\"font-size: 20px\"></AppIcon>\n          </el-button>\n          <el-tooltip\n            effect=\"dark\"\n            :content=\"$t('chat.share')\"\n            placement=\"top\"\n            v-if=\"!showSelection && currentChatId !== 'new'\"\n          >\n            <el-button class=\"mr-16\" text @click=\"clickShareHandle\" :disabled=\"AiChatRef?.loading\">\n              <AppIcon iconName=\"app-share\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </div>\n      </div>\n    </div>\n    <div>\n      <div class=\"chat-mobile__main\">\n        <AiChat\n          ref=\"AiChatRef\"\n          v-model:applicationDetails=\"applicationDetail\"\n          :available=\"applicationAvailable\"\n          :appId=\"applicationDetail?.id\"\n          :record=\"currentRecordList\"\n          :chatId=\"currentChatId\"\n          type=\"ai-chat\"\n          @refresh=\"refresh\"\n          @scroll=\"handleScroll\"\n          v-model:selection=\"showSelection\"\n        >\n        </AiChat>\n      </div>\n    </div>\n    <ChatHistoryDrawer\n      v-model:show=\"show\"\n      :application-detail=\"applicationDetail\"\n      :chat-log-data=\"chatLogData\"\n      :left-loading=\"left_loading\"\n      :currentChatId=\"currentChatId\"\n      @new-chat=\"newChat\"\n      @clickLog=\"clickListHandle\"\n      @delete-log=\"deleteLog\"\n      @refreshFieldTitle=\"refreshFieldTitle\"\n      @clear-chat=\"clearChat\"\n      @clickShare=\"clickShareHandle\"\n    />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, reactive, nextTick, computed, provide } from 'vue'\nimport { isAppIcon } from '@/utils/common'\nimport { hexToRgba } from '@/utils/theme'\nimport useStore from '@/stores'\nimport { t } from '@/locales'\nimport ChatHistoryDrawer from './component/ChatHistoryDrawer.vue'\nimport chatAPI from '@/api/chat/chat'\n\nprovide('scrollData', loadInfiniteScroll)\nprovide('chatLogPagination', () => chatLogPagination)\n\nconst { common } = useStore()\n\nconst AiChatRef = ref()\nconst loading = ref(false)\nconst left_loading = ref(false)\nconst chatLogData = ref<any[]>([])\nconst show = ref(false)\nconst props = defineProps<{\n  application_profile: any\n  applicationAvailable: boolean\n}>()\nconst applicationDetail = computed({\n  get: () => {\n    return props.application_profile\n  },\n  set: (v) => {},\n})\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst currentRecordList = ref<any>([])\nconst currentChatId = ref('new') // 当前历史记录Id 默认为'new'\n\nconst customStyle = computed(() => {\n  return {\n    background: applicationDetail.value?.custom_theme?.theme_color,\n    color: applicationDetail.value?.custom_theme?.header_font_color,\n  }\n})\n\nconst showSelection = ref(false)\nconst clickShareHandle = () => {\n  showSelection.value = true\n  show.value = false\n}\n\nfunction clearChat() {\n  chatAPI.clearChat(left_loading).then(() => {\n    currentChatId.value = 'new'\n    paginationConfig.current_page = 1\n    paginationConfig.total = 0\n    currentRecordList.value = []\n    chatLogPagination.value.current_page = 1\n    chatLogData.value = []\n    getChatLog()\n  })\n}\n\nfunction deleteLog(row: any) {\n  chatAPI.deleteChat(row.id).then(() => {\n    if (currentChatId.value === row.id) {\n      currentChatId.value = 'new'\n      paginationConfig.current_page = 1\n      paginationConfig.total = 0\n      currentRecordList.value = []\n    }\n    chatLogData.value = chatLogData.value.filter((item) => item.id !== row.id)\n  })\n}\nfunction handleScroll(event: any) {\n  if (\n    currentChatId.value !== 'new' &&\n    event.scrollTop === 0 &&\n    paginationConfig.total > currentRecordList.value.length\n  ) {\n    const history_height = event.dialogScrollbar.offsetHeight\n    paginationConfig.current_page += 1\n    getChatRecord().then(() => {\n      event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)\n    })\n  }\n}\n\nconst newObj = {\n  id: 'new',\n  abstract: t('chat.createChat'),\n}\nfunction newChat() {\n  paginationConfig.current_page = 1\n  currentRecordList.value = []\n  if (!chatLogData.value.some((v) => v.id === 'new')) {\n    chatLogData.value.unshift(newObj)\n  }\n  currentChatId.value = 'new'\n  show.value = false\n}\n\nconst chatLogPagination = ref({\n  total: 0,\n  page_size: 20,\n  current_page: 1,\n})\nfunction getChatLog(refresh?: boolean) {\n  chatAPI\n    .pageChat(chatLogPagination.value.current_page, chatLogPagination.value.page_size, left_loading)\n    .then((res: any) => {\n      chatLogPagination.value.total = res.data.total\n      chatLogData.value = [...chatLogData.value, ...res.data.records]\n      if (!refresh) {\n        paginationConfig.current_page = 1\n        paginationConfig.total = 0\n        currentRecordList.value = []\n        currentChatId.value = 'new'\n      }\n    })\n}\nfunction loadInfiniteScroll() {\n  getChatLog(true)\n}\n\nfunction getChatRecord() {\n  return chatAPI\n    .pageChatRecord(\n      currentChatId.value,\n      paginationConfig.current_page,\n      paginationConfig.page_size,\n      loading,\n    )\n    .then((res: any) => {\n      paginationConfig.total = res.data.total\n      const list = res.data.records\n      list.map((v: any) => {\n        v['write_ed'] = true\n        v['record_id'] = v.id\n      })\n      currentRecordList.value = [...list, ...currentRecordList.value].sort((a, b) =>\n        a.create_time.localeCompare(b.create_time),\n      )\n      if (paginationConfig.current_page === 1) {\n        nextTick(() => {\n          // 将滚动条滚动到最下面\n          AiChatRef.value.setScrollBottom()\n        })\n      }\n    })\n}\n\nconst clickListHandle = (item: any) => {\n  if (item.id !== currentChatId.value) {\n    showSelection.value = false\n    paginationConfig.current_page = 1\n    currentRecordList.value = []\n    currentChatId.value = item.id\n    if (currentChatId.value !== 'new') {\n      getChatRecord()\n    }\n    show.value = false\n  }\n}\n\nfunction refreshFieldTitle(chatId: string, abstract: string) {\n  const find = chatLogData.value.find((item: any) => item.id == chatId)\n  if (find) {\n    find.abstract = abstract\n  }\n}\n\nfunction refresh(id: string) {\n  currentChatId.value = id\n  chatLogPagination.value.current_page = 1\n  chatLogData.value = []\n  getChatLog(true)\n}\n/**\n *初始化历史对话记录\n */\nconst init = () => {\n  getChatLog()\n}\n\nonMounted(() => {\n  init()\n})\n</script>\n<style lang=\"scss\" scoped>\n.chat-mobile {\n  overflow: hidden;\n  &__header {\n    background: var(--app-header-bg-color);\n    position: fixed;\n    width: 100%;\n    left: 0;\n    top: 0;\n    z-index: 100;\n    height: var(--app-header-height);\n    line-height: var(--app-header-height);\n    box-sizing: border-box;\n    border-bottom: 1px solid var(--el-border-color);\n  }\n  &__main {\n    padding-top: calc(var(--app-header-height) + 16px);\n    height: calc(100vh - var(--app-header-height) - 16px);\n    overflow: hidden;\n  }\n}\n</style>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/chat/no-service/index.vue",
    "content": "<template>\n  <div class=\"not-found-container flex-center\">\n    <div>\n      <img src=\"@/assets/500.png\" width=\"250\" alt=\"\" />\n      <h4 class=\"text-center\">{{ $t('common.notFound.NoService') }}</h4>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { useRouter } from 'vue-router'\nconst router = useRouter()\n</script>\n<style lang=\"scss\" scoped>\n.not-found-container {\n  height: 100vh;\n  width: 100vw;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/pc/index.vue",
    "content": "<template>\n  <div\n    class=\"chat-pc\"\n    :class=\"classObj\"\n    v-loading=\"loading\"\n    :style=\"{\n      '--el-color-primary': applicationDetail?.custom_theme?.theme_color,\n      '--el-color-primary-light-9': hexToRgba(\n        applicationDetail?.custom_theme?.theme_color || '#3370FF',\n        0.1,\n      ),\n      '--el-color-primary-light-6': hexToRgba(\n        applicationDetail?.custom_theme?.theme_color || '#3370FF',\n        0.4,\n      ),\n      '--el-color-primary-light-06': hexToRgba(\n        applicationDetail?.custom_theme?.theme_color || '#3370FF',\n        0.04,\n      ),\n    }\"\n  >\n    <div class=\"flex h-full w-full\">\n      <div class=\"chat-pc__left\">\n        <HistoryPanel\n          :application-detail=\"applicationDetail\"\n          :chat-log-data=\"chatLogData\"\n          :left-loading=\"left_loading\"\n          :currentChatId=\"currentChatId\"\n          @new-chat=\"newChat\"\n          @clickLog=\"clickListHandle\"\n          @delete-log=\"deleteLog\"\n          @clear-chat=\"clearChat\"\n          @refreshFieldTitle=\"refreshFieldTitle\"\n          @clickShare=\"clickShareHandle\"\n          :isPcCollapse=\"isPcCollapse\"\n          :chat-loading=\"AiChatRef?.loading\"\n        >\n          <div class=\"user-info p-16 cursor\">\n            <el-avatar\n              :size=\"32\"\n              v-if=\"\n                !chatUser.chat_profile?.authentication ||\n                chatUser.chat_profile.authentication_type === 'password'\n              \"\n            >\n              <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n            </el-avatar>\n            <el-dropdown v-else trigger=\"click\" type=\"primary\" class=\"w-full\">\n              <div class=\"flex align-center\">\n                <el-avatar :size=\"32\">\n                  <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n                </el-avatar>\n                <span v-show=\"!isPcCollapse\" class=\"ml-8 color-text-primary\">{{\n                  chatUser.chatUserProfile?.nick_name\n                }}</span>\n              </div>\n\n              <template #dropdown>\n                <el-dropdown-menu style=\"min-width: 260px\">\n                  <div class=\"flex align-center p-8\">\n                    <div class=\"mr-8 flex align-center\">\n                      <el-avatar :size=\"40\">\n                        <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n                      </el-avatar>\n                    </div>\n                    <div>\n                      <h4 class=\"medium mb-4\">{{ chatUser.chatUserProfile?.nick_name }}</h4>\n                      <div class=\"color-secondary\">\n                        {{ `${t('common.username')}: ${chatUser.chatUserProfile?.username}` }}\n                      </div>\n                    </div>\n                  </div>\n                  <el-dropdown-item\n                    v-if=\"chatUser.chatUserProfile?.source === 'LOCAL'\"\n                    class=\"border-t\"\n                    style=\"padding-top: 8px; padding-bottom: 8px\"\n                    @click=\"openResetPassword\"\n                  >\n                    <AppIcon iconName=\"app-key\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.login.resetPassword') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    class=\"border-t\"\n                    style=\"padding-top: 8px; padding-bottom: 8px\"\n                    @click=\"logout\"\n                  >\n                    <AppIcon iconName=\"app-export\" class=\"color-secondary\" />\n                    {{ $t('layout.logout') }}\n                  </el-dropdown-item>\n                </el-dropdown-menu>\n              </template>\n            </el-dropdown>\n          </div>\n        </HistoryPanel>\n        <el-button\n          v-if=\"!common.isMobile()\"\n          class=\"pc-collapse cursor\"\n          circle\n          @click=\"isPcCollapse = !isPcCollapse\"\n        >\n          <el-icon>\n            <component :is=\"isPcCollapse ? 'ArrowRightBold' : 'ArrowLeftBold'\" />\n          </el-icon>\n        </el-button>\n      </div>\n      <div\n        class=\"chat-pc__right chat-background\"\n        :style=\"{\n          backgroundImage: `url(${applicationDetail?.chat_background})`,\n          '--execution-detail-panel-width': rightPanelSize + 'px',\n        }\"\n      >\n        <div style=\"flex: 1; width: calc(100% - var(--execution-detail-panel-width))\">\n          <div class=\"p-16-24 flex-between\">\n            <h4 class=\"ellipsis-1\" style=\"width: 66%\">\n              {{ currentChatName }}\n            </h4>\n\n            <span class=\"flex align-center\" v-if=\"currentRecordList.length\">\n              <AppIcon\n                v-if=\"paginationConfig.total\"\n                iconName=\"app-chat-record\"\n                class=\"color-secondary mr-8\"\n                style=\"font-size: 16px\"\n              ></AppIcon>\n              <span v-if=\"paginationConfig.total\" class=\"lighter\">\n                {{ paginationConfig.total }} {{ $t('chat.question_count') }}\n              </span>\n              <el-tooltip\n                effect=\"dark\"\n                :content=\"$t('chat.share')\"\n                placement=\"top\"\n                v-if=\"!showSelection\"\n              >\n                <el-button\n                  text\n                  class=\"ml-12\"\n                  @click=\"clickShareHandle\"\n                  :disabled=\"AiChatRef?.loading\"\n                >\n                  <AppIcon iconName=\"app-share\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n              <el-dropdown class=\"ml-8\" v-if=\"!showSelection\">\n                <el-button text>\n                  <AppIcon iconName=\"app-export\" :title=\"$t('chat.exportRecords')\"></AppIcon>\n                </el-button>\n                <template #dropdown>\n                  <el-dropdown-menu>\n                    <el-dropdown-item @click=\"exportMarkdown\"\n                      >{{ $t('common.export') }} Markdown</el-dropdown-item\n                    >\n                    <el-dropdown-item @click=\"exportHTML\"\n                      >{{ $t('common.export') }} HTML</el-dropdown-item\n                    >\n                    <el-dropdown-item @click=\"openPDFExport\"\n                      >{{ $t('common.export') }} PDF</el-dropdown-item\n                    >\n                  </el-dropdown-menu>\n                </template>\n              </el-dropdown>\n            </span>\n          </div>\n          <div class=\"right-height chat-width\">\n            <AiChat\n              ref=\"AiChatRef\"\n              v-model:applicationDetails=\"applicationDetail\"\n              :available=\"applicationAvailable\"\n              type=\"ai-chat\"\n              :appId=\"applicationDetail?.id\"\n              :record=\"currentRecordList\"\n              :chatId=\"currentChatId\"\n              executionIsRightPanel\n              @refresh=\"refresh\"\n              @scroll=\"handleScroll\"\n              @open-execution-detail=\"openExecutionDetail\"\n              @openParagraph=\"openKnowledgeSource\"\n              @openParagraphDocument=\"openParagraphDocument\"\n              v-model:selection=\"showSelection\"\n            >\n            </AiChat>\n          </div>\n        </div>\n        <div class=\"execution-detail-panel\" :resizable=\"false\" collapsible>\n          <div class=\"p-16 flex-between border-b\">\n            <h4 class=\"medium ellipsis\" style=\"max-width: 300px\" :title=\"rightPanelTitle\">\n              {{ rightPanelTitle }}\n            </h4>\n\n            <div class=\"flex align-center\">\n              <span v-if=\"rightPanelType === 'paragraphDocument'\" class=\"mr-4\">\n                <a\n                  :href=\"\n                    getFileUrl(rightPanelDetail?.meta?.source_file_id) ||\n                    rightPanelDetail?.meta?.source_url\n                  \"\n                  target=\"_blank\"\n                  class=\"ellipsis-1\"\n                  :title=\"rightPanelDetail?.document_name?.trim()\"\n                >\n                  <el-button text>\n                    <AppIcon iconName=\"app-pdf-export\" class=\"cursor\"></AppIcon>\n                  </el-button>\n                </a>\n              </span>\n              <!-- <span v-if=\"rightPanelType === 'paragraphDocument'\">\n                <el-button text> <app-icon iconName=\"app-export\" size=\"20\" /></el-button>\n              </span> -->\n              <span>\n                <el-button text @click=\"closeExecutionDetail\">\n                  <el-icon size=\"20\"><Close /></el-icon\n                ></el-button>\n              </span>\n            </div>\n          </div>\n\n          <div class=\"execution-detail-content mb-8\" v-loading=\"rightPanelLoading\">\n            <el-scrollbar>\n              <ParagraphSourceContent\n                v-if=\"rightPanelType === 'knowledgeSource'\"\n                :detail=\"rightPanelDetail\"\n              />\n              <ExecutionDetailContent\n                v-if=\"rightPanelType === 'executionDetail'\"\n                :detail=\"executionDetail\"\n                :appType=\"applicationDetail?.type\"\n              />\n              <ParagraphDocumentContent :detail=\"rightPanelDetail\" v-else />\n            </el-scrollbar>\n          </div>\n        </div>\n      </div>\n    </div>\n\n    <ResetPassword\n      ref=\"resetPasswordRef\"\n      emitConfirm\n      @confirm=\"handleResetPassword\"\n    ></ResetPassword>\n    <PdfExport ref=\"pdfExportRef\"></PdfExport>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, nextTick, computed, watch, provide } from 'vue'\nimport { marked } from 'marked'\nimport { saveAs } from 'file-saver'\nimport chatAPI from '@/api/chat/chat'\nimport useStore from '@/stores'\nimport useResize from '@/layout/hooks/useResize'\nimport { hexToRgba } from '@/utils/theme'\nimport { useRoute, useRouter } from 'vue-router'\nimport ResetPassword from '@/layout/layout-header/avatar/ResetPassword.vue'\nimport { t } from '@/locales'\nimport type { ResetCurrentUserPasswordRequest } from '@/api/type/user'\nimport ExecutionDetailContent from '@/components/ai-chat/component/knowledge-source-component/ExecutionDetailContent.vue'\nimport ParagraphSourceContent from '@/components/ai-chat/component/knowledge-source-component/ParagraphSourceContent.vue'\nimport ParagraphDocumentContent from '@/components/ai-chat/component/knowledge-source-component/ParagraphDocumentContent.vue'\nimport HistoryPanel from '@/views/chat/component/HistoryPanel.vue'\nimport { cloneDeep } from 'lodash'\nimport { getFileUrl } from '@/utils/common'\nimport PdfExport from '@/components/pdf-export/index.vue'\n\nuseResize()\n\nprovide('scrollData', loadInfiniteScroll)\nprovide('chatLogPagination', () => chatLogPagination)\nconst pdfExportRef = ref<InstanceType<typeof PdfExport>>()\nconst { common, chatUser } = useStore()\nconst router = useRouter()\nconst openPDFExport = () => {\n  pdfExportRef.value?.open(document.getElementById('chatListId'))\n}\nconst route = useRoute()\nconst isPcCollapse = ref(false)\n// watch(\n//   () => common.device,\n//   () => {\n//     if (common.isMobile()) {\n//       isPcCollapse.value = false\n//     }\n//   },\n// )\n\nconst logout = () => {\n  chatUser.logout().then(() => {\n    router.push({\n      name: 'login',\n      query: route.query,\n    })\n  })\n}\nconst showSelection = ref(false)\nconst clickShareHandle = () => {\n  showSelection.value = true\n}\n\nconst resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()\nconst openResetPassword = () => {\n  resetPasswordRef.value?.open()\n}\n\nconst handleResetPassword = (param: ResetCurrentUserPasswordRequest) => {\n  chatAPI.resetCurrentPassword(param).then(() => {\n    router.push({ name: 'login' })\n  })\n}\n\nconst classObj = computed(() => {\n  return {\n    hideLeft: isPcCollapse.value,\n    openLeft: !isPcCollapse.value,\n  }\n})\n\nconst newObj = {\n  id: 'new',\n  abstract: t('chat.createChat'),\n}\nconst props = defineProps<{\n  application_profile: any\n  applicationAvailable: boolean\n}>()\nconst AiChatRef = ref()\nconst loading = ref(false)\nconst left_loading = ref(false)\n\nconst applicationDetail = computed({\n  get: () => {\n    return props.application_profile\n  },\n  set: (v) => {},\n})\n\nconst chatLogData = ref<any[]>([])\n\nconst paginationConfig = ref({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst currentRecordList = ref<any>([])\nconst currentChatId = ref('new') // 当前历史记录Id 默认为'new'\nconst currentChatName = ref(t('chat.createChat'))\n\nfunction refreshFieldTitle(chatId: string, abstract: string) {\n  const find = chatLogData.value.find((item: any) => item.id == chatId)\n  if (find) {\n    find.abstract = abstract\n  }\n}\n\nfunction deleteLog(row: any) {\n  chatAPI.deleteChat(row.id).then(() => {\n    if (currentChatId.value === row.id) {\n      currentChatId.value = 'new'\n      currentChatName.value = t('chat.createChat')\n      paginationConfig.value.current_page = 1\n      paginationConfig.value.total = 0\n      currentRecordList.value = []\n    }\n    chatLogData.value = chatLogData.value.filter((item) => item.id !== row.id)\n  })\n}\n\nfunction clearChat() {\n  chatAPI.clearChat(left_loading).then(() => {\n    currentChatId.value = 'new'\n    currentChatName.value = t('chat.createChat')\n    paginationConfig.value.current_page = 1\n    paginationConfig.value.total = 0\n    currentRecordList.value = []\n    chatLogPagination.value.current_page = 1\n    chatLogData.value = []\n    getChatLog()\n  })\n}\n\nfunction handleScroll(event: any) {\n  if (\n    currentChatId.value !== 'new' &&\n    event.scrollTop === 0 &&\n    paginationConfig.value.total > currentRecordList.value.length\n  ) {\n    const history_height = event.dialogScrollbar.offsetHeight\n    paginationConfig.value.current_page += 1\n    getChatRecord().then(() => {\n      event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)\n    })\n  }\n}\n\nfunction newChat() {\n  showSelection.value = false\n  if (!chatLogData.value.some((v) => v.id === 'new')) {\n    paginationConfig.value.current_page = 1\n    paginationConfig.value.total = 0\n    currentRecordList.value = []\n    chatLogData.value.unshift(newObj)\n  } else {\n    paginationConfig.value.current_page = 1\n    paginationConfig.value.total = 0\n    currentRecordList.value = []\n  }\n  closeExecutionDetail()\n  currentChatId.value = 'new'\n  currentChatName.value = t('chat.createChat')\n}\n\nconst chatLogPagination = ref({\n  total: 0,\n  page_size: 20,\n  current_page: 1,\n})\nfunction getChatLog(refresh?: boolean) {\n  chatAPI\n    .pageChat(chatLogPagination.value.current_page, chatLogPagination.value.page_size, left_loading)\n    .then((res: any) => {\n      chatLogPagination.value.total = res.data.total\n      chatLogData.value = [...chatLogData.value, ...res.data.records]\n      if (refresh) {\n        currentChatName.value = chatLogData.value?.[0]?.abstract\n      } else {\n        paginationConfig.value.current_page = 1\n        paginationConfig.value.total = 0\n        currentRecordList.value = []\n        currentChatId.value = 'new'\n        currentChatName.value = t('chat.createChat')\n      }\n    })\n}\n\nfunction loadInfiniteScroll() {\n  getChatLog(true)\n}\n\nfunction getChatRecord() {\n  return chatAPI\n    .pageChatRecord(\n      currentChatId.value,\n      paginationConfig.value.current_page,\n      paginationConfig.value.page_size,\n      loading,\n    )\n    .then((res: any) => {\n      paginationConfig.value.total = res.data.total\n      const list = res.data.records\n      list.map((v: any) => {\n        v['write_ed'] = true\n        v['record_id'] = v.id\n      })\n      currentRecordList.value = [...list, ...currentRecordList.value].sort((a, b) =>\n        a.create_time.localeCompare(b.create_time),\n      )\n      if (paginationConfig.value.current_page === 1) {\n        nextTick(() => {\n          // 将滚动条滚动到最下面\n          AiChatRef.value.setScrollBottom()\n        })\n      }\n    })\n}\n\nconst clickListHandle = (item: any) => {\n  if (item.id !== currentChatId.value) {\n    showSelection.value = false\n    paginationConfig.value.current_page = 1\n    paginationConfig.value.total = 0\n    currentRecordList.value = []\n    currentChatId.value = item.id\n    currentChatName.value = item.abstract\n    closeExecutionDetail()\n    if (currentChatId.value !== 'new') {\n      getChatRecord()\n\n      // 切换对话后，取消暂停的浏览器播放\n      if (window.speechSynthesis.paused && window.speechSynthesis.speaking) {\n        window.speechSynthesis.resume()\n        nextTick(() => {\n          window.speechSynthesis.cancel()\n        })\n      }\n    }\n  }\n}\n\nfunction refresh(id: string) {\n  currentChatId.value = id\n  chatLogPagination.value.current_page = 1\n  chatLogData.value = []\n  getChatLog(true)\n}\n\nasync function exportMarkdown(): Promise<void> {\n  const suggestedName: string = `${currentChatId.value}.md`\n  const markdownContent: string = currentRecordList.value\n    .map((record: any) => {\n      let answerText = ''\n      if (Array.isArray(record.answer_text_list)) {\n        answerText = record.answer_text_list\n          .flat()\n          .map((item: any) => item?.content || '')\n          .join('\\n\\n')\n      } else {\n        answerText = record.answer_text || ''\n      }\n      return `# ${record.problem_text}\\n\\n${answerText}\\n\\n`\n    })\n    .join('\\n')\n\n  const blob: Blob = new Blob([markdownContent], { type: 'text/markdown;charset=utf-8' })\n  saveAs(blob, suggestedName)\n}\n\nasync function exportHTML(): Promise<void> {\n  const suggestedName: string = `${currentChatId.value}.html`\n  const markdownContent: string = currentRecordList.value\n    .map((record: any) => {\n      let answerText = ''\n      if (Array.isArray(record.answer_text_list)) {\n        answerText = record.answer_text_list\n          .flat()\n          .map((item: any) => item?.content || '')\n          .join('\\n\\n')\n      } else {\n        answerText = record.answer_text || ''\n      }\n      return `# ${record.problem_text}\\n\\n${answerText}\\n\\n`\n    })\n    .join('\\n')\n  const htmlContent: any = marked(markdownContent)\n\n  const blob: Blob = new Blob([htmlContent], { type: 'text/html;charset=utf-8' })\n  saveAs(blob, suggestedName)\n}\n\n/**\n *初始化历史对话记录\n */\nconst init = () => {\n  getChatLog()\n}\nonMounted(() => {\n  init()\n})\n\nconst rightPanelSize = ref(0)\nconst rightPanelTitle = ref('')\nconst rightPanelType = ref('')\nconst rightPanelLoading = ref(false)\nconst executionDetail = ref<any[]>([])\nconst rightPanelDetail = ref<any>()\n\nasync function openExecutionDetail(row: any) {\n  rightPanelSize.value = 400\n  rightPanelTitle.value = t('chat.executionDetails.title')\n  rightPanelType.value = 'executionDetail'\n  if (row.execution_details) {\n    executionDetail.value = cloneDeep(row.execution_details)\n  } else {\n    const res = await chatAPI.getChatRecord(row.chat_id, row.record_id, rightPanelLoading)\n    executionDetail.value = cloneDeep(res.data.execution_details)\n  }\n}\n\nasync function openKnowledgeSource(row: any) {\n  rightPanelTitle.value = t('chat.KnowledgeSource.title')\n  rightPanelType.value = 'knowledgeSource'\n  rightPanelDetail.value = row\n  rightPanelSize.value = 400\n}\n\nfunction openParagraphDocument(detail: any, row: any) {\n  rightPanelTitle.value = row.document_name\n  rightPanelType.value = 'paragraphDocument'\n  rightPanelSize.value = 400\n  rightPanelDetail.value = row\n}\n\nfunction closeExecutionDetail() {\n  rightPanelSize.value = 0\n}\n</script>\n<style lang=\"scss\" scoped>\n.chat-pc {\n  height: 100%;\n  overflow: hidden;\n  background: #eef1f4;\n\n  &__left {\n    position: relative;\n    z-index: 11;\n\n    .pc-collapse {\n      position: absolute;\n      top: 20px;\n      right: -13px;\n      box-shadow: 0px 5px 10px 0px rgba(var(--el-text-color-primary-rgb), 0.1);\n      z-index: 1;\n      width: 24px;\n      height: 24px;\n    }\n  }\n\n  &__right {\n    flex: 1;\n    overflow: hidden;\n    position: relative;\n    box-sizing: border-box;\n    display: flex;\n\n    .right-height {\n      height: calc(100vh - 60px);\n    }\n\n    :deep(.execution-detail-panel) {\n      transition: width 0.4s;\n      background: #ffffff;\n      height: 100%;\n      overflow: hidden;\n\n      .execution-detail-content {\n        flex: 1;\n        overflow: hidden;\n        height: calc(100% - 45px);\n\n        .execution-details {\n          padding: 16px;\n          word-break: break-all;\n        }\n      }\n    }\n  }\n}\n\n.chat-pc__right {\n  width: calc(100vw - 280px);\n  --execution-detail-panel-width: 400px;\n\n  .execution-detail-panel {\n    width: var(--execution-detail-panel-width, 400px);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/user-login/index.vue",
    "content": "<template>\n  <UserLoginLayout v-if=\"!loading\" v-loading=\"loading\">\n    <div class=\"user-login-container p-24\">\n      <div v-if=\"isPc\" class=\"flex-center\" style=\"margin-bottom: 32px\">\n        <el-avatar\n          v-if=\"isAppIcon(chatUser.chat_profile?.icon)\"\n          shape=\"square\"\n          :size=\"32\"\n          class=\"mr-8\"\n          style=\"background: none\"\n        >\n          <img :src=\"chatUser.chat_profile?.icon\" alt=\"\"/>\n        </el-avatar>\n        <LogoIcon v-else height=\"32px\" class=\"mr-8\"/>\n        <h1>{{ chatUser.chat_profile?.application_name }}</h1>\n      </div>\n      <!-- 移动端头部标题-->\n      <div v-else class=\"user-login__header\">\n        <div class=\"flex-between\">\n          <div class=\"flex align-center\">\n            <div class=\"mr-12 ml-16 flex\">\n              <el-avatar\n                v-if=\"isAppIcon(chatUser.chat_profile?.icon)\"\n                shape=\"square\"\n                :size=\"32\"\n                style=\"background: none\"\n              >\n                <img :src=\"chatUser.chat_profile?.icon\" alt=\"\"/>\n              </el-avatar>\n              <LogoIcon v-else height=\"32px\"/>\n            </div>\n\n            <h4\n              class=\"ellipsis\"\n              style=\"max-width: 270px\"\n              :title=\"chatUser.chat_profile?.application_name\"\n            >\n              {{ chatUser.chat_profile?.application_name }}\n            </h4>\n          </div>\n        </div>\n      </div>\n\n      <el-card class=\"login-card\" v-if=\"chatUser.chat_profile?.authentication_type == 'password'\">\n        <h2 class=\"mb-24\">\n          {{ $t('views.applicationOverview.appInfo.LimitDialog.authenticationValue') }}\n        </h2>\n        <PasswordAuth></PasswordAuth>\n      </el-card>\n\n      <el-card class=\"login-card\" v-else style=\"--el-card-padding: 0\">\n        <h2 class=\"mb-24\" v-if=\"!showQrCodeTab && (loginMode === 'LDAP' || loginMode === 'LOCAL')\">\n          {{ loginMode == 'LOCAL' ? $t('views.login.title') : loginMode }}\n        </h2>\n        <div v-if=\"!showQrCodeTab && (loginMode === 'LDAP' || loginMode === 'LOCAL')\">\n          <el-form\n            class=\"login-form\"\n            :rules=\"rules\"\n            :model=\"loginForm\"\n            ref=\"loginFormRef\"\n            @keyup.enter=\"loginHandle\"\n          >\n            <div class=\"mb-24\">\n              <el-form-item prop=\"username\">\n                <el-input\n                  size=\"large\"\n                  class=\"input-item\"\n                  v-model=\"loginForm.username\"\n                  @blur=\"handleUsernameBlur(loginForm.username)\"\n                  :placeholder=\"$t('views.login.loginForm.username.placeholder')\"\n                >\n                </el-input>\n              </el-form-item>\n            </div>\n            <div class=\"mb-24\">\n              <el-form-item prop=\"password\">\n                <el-input\n                  type=\"password\"\n                  size=\"large\"\n                  class=\"input-item\"\n                  v-model=\"loginForm.password\"\n                  :placeholder=\"$t('views.login.loginForm.password.placeholder')\"\n                  show-password\n                >\n                </el-input>\n              </el-form-item>\n            </div>\n            <div class=\"mb-24\" v-if=\"loginMode !== 'LDAP'&& identifyCode\">\n              <el-form-item prop=\"captcha\">\n                <div class=\"flex-between w-full\">\n                  <el-input\n                    size=\"large\"\n                    class=\"input-item\"\n                    v-model=\"loginForm.captcha\"\n                    :placeholder=\"$t('views.login.loginForm.captcha.placeholder')\"\n                  >\n                  </el-input>\n\n                  <img\n                    :src=\"identifyCode\"\n                    alt=\"\"\n                    height=\"38\"\n                    class=\"ml-8 cursor border border-r-6\"\n                    @click=\"makeCode(loginForm.username)\"\n                  />\n                </div>\n              </el-form-item>\n            </div>\n          </el-form>\n\n          <el-button\n            size=\"large\"\n            type=\"primary\"\n            class=\"w-full\"\n            @click=\"loginHandle\"\n            :loading=\"loading\"\n          >\n            {{ $t('views.login.buttons.login') }}\n          </el-button>\n        </div>\n        <div v-if=\"showQrCodeTab\">\n          <QrCodeTab :tabs=\"orgOptions\"/>\n        </div>\n        <div class=\"login-gradient-divider lighter mt-24\" v-if=\"modeList.length > 1\">\n          <span>{{ $t('views.login.moreMethod') }}</span>\n        </div>\n        <div class=\"text-center mt-16\">\n          <template v-for=\"item in modeList\">\n            <el-button\n              v-if=\"item !== 'LOCAL' && loginMode !== item && item !== 'QR_CODE'\"\n              circle\n              :key=\"item\"\n              class=\"login-button-circle color-secondary\"\n              @click=\"changeMode(item)\"\n            >\n              <span\n                :style=\"{\n                  'font-size': item === 'OAUTH2' ? '8px' : '10px',\n                  color: theme.themeInfo?.theme,\n                }\"\n              >{{ item }}</span\n              >\n            </el-button>\n            <el-button\n              v-if=\"item === 'QR_CODE' && loginMode !== item\"\n              circle\n              :key=\"item\"\n              class=\"login-button-circle color-secondary\"\n              @click=\"changeMode('QR_CODE')\"\n            >\n              <img src=\"@/assets/icon_qr_outlined.svg\" width=\"25px\"/>\n            </el-button>\n            <el-button\n              v-if=\"item === 'LOCAL' && loginMode != 'LOCAL'\"\n              circle\n              :key=\"item\"\n              class=\"login-button-circle color-secondary\"\n              style=\"font-size: 24px\"\n              icon=\"UserFilled\"\n              @click=\"changeMode('LOCAL')\"\n            />\n          </template>\n        </div>\n      </el-card>\n    </div>\n  </UserLoginLayout>\n</template>\n<script setup lang=\"ts\">\nimport {onMounted, ref, onBeforeMount, computed} from 'vue'\nimport {useRoute, useRouter} from 'vue-router'\nimport type {FormInstance, FormRules} from 'element-plus'\nimport type {LoginRequest} from '@/api/type/login'\nimport UserLoginLayout from '@/layout/login-layout/UserLoginLayout.vue'\nimport loginApi from '@/api/chat/chat.ts'\nimport {t} from '@/locales'\nimport useResize from '@/layout/hooks/useResize'\nimport useStore from '@/stores'\nimport {useI18n} from 'vue-i18n'\nimport QrCodeTab from '@/views/chat/user-login/scanCompinents/QrCodeTab.vue'\nimport {MsgConfirm, MsgError} from '@/utils/message.ts'\nimport PasswordAuth from '@/views/chat/auth/component/password.vue'\nimport {isAppIcon, loadScript} from '@/utils/common'\nimport forge from \"node-forge\";\nimport * as dd from \"dingtalk-jsapi\";\n\nuseResize()\nconst router = useRouter()\n\nconst {theme, chatUser, common} = useStore()\nconst {locale} = useI18n({useScope: 'global'})\nconst loading = ref<boolean>(false)\nconst route = useRoute()\nconst identifyCode = ref<string>('')\nconst {\n  params: {accessToken},\n  query: {mode},\n} = route as any\n\nconst isPc = computed(() => {\n  console.log(common.isMobile())\n  let modeName = ''\n  if (!mode || mode === 'pc') {\n    modeName = common.isMobile() ? 'mobile' : 'pc'\n  } else {\n    modeName = mode\n  }\n  console.log(modeName)\n  return modeName === 'pc'\n})\n\nconst loginFormRef = ref<FormInstance>()\nconst loginForm = ref<LoginRequest>({\n  username: '',\n  password: '',\n  captcha: '',\n})\n\nconst max_attempts = ref<number>(1)  // 声明为 ref\nconst rules = ref<FormRules<LoginRequest>>({\n  username: [\n    {\n      required: true,\n      message: t('views.login.loginForm.username.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.password.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  captcha: [\n    {\n      required: false,\n      message: t('views.login.loginForm.captcha.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst loginHandle = () => {\n  loginFormRef.value?.validate().then(() => {\n    if (loginMode.value === 'LDAP') {\n      chatUser.ldapLogin(loginForm.value).then((ok) => {\n        router.push({\n          name: 'chat',\n          params: {accessToken: chatUser.accessToken},\n          query: route.query,\n        })\n      })\n    } else {\n      const publicKey = forge.pki.publicKeyFromPem(chatUser?.chat_profile?.rasKey as any);\n      const jsonData = JSON.stringify(loginForm.value);\n      const utf8Bytes = forge.util.encodeUtf8(jsonData);\n      const encrypted = publicKey.encrypt(utf8Bytes, 'RSAES-PKCS1-V1_5');\n      const encryptedBase64 = forge.util.encode64(encrypted);\n      chatUser.login({\n        encryptedData: encryptedBase64,\n        username: loginForm.value.username\n      }).then((ok) => {\n        router.push({\n          name: 'chat',\n          params: {accessToken: chatUser.accessToken},\n          query: route.query,\n        })\n      }).catch(() => {\n        makeCode(loginForm.value.username)\n      })\n    }\n  })\n}\n\nfunction makeCode(username?: string) {\n  loginApi.getCaptcha(username, accessToken).then((res: any) => {\n    identifyCode.value = res.data.captcha\n  })\n}\n\nonBeforeMount(() => {\n  locale.value = chatUser.getLanguage()\n})\n\nconst modeList = ref<string[]>([])\nconst QrList = ref<any[]>([''])\nconst loginMode = ref('')\nconst showQrCodeTab = ref(false)\n\ninterface qrOption {\n  key: string\n  value: string\n}\n\nconst orgOptions = ref<qrOption[]>([])\n\nfunction uuidv4() {\n  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n    const r = (Math.random() * 16) | 0\n    const v = c === 'x' ? r : (r & 0x3) | 0x8\n    return v.toString(16)\n  })\n}\n\nfunction redirectAuth(authType: string, needMessage: boolean = false) {\n  if (authType === 'LDAP' || authType === '' || authType === 'password') {\n    return\n  }\n  loginApi.getAuthSetting(authType, loading).then((res: any) => {\n    if (!res.data || !res.data.config) {\n      return\n    }\n\n    const config = res.data.config\n    const queryParams = new URLSearchParams(route.query as any).toString()\n    // 构造带查询参数的redirectUrl\n    let redirectUrl = `${config.redirectUrl}/${accessToken}`\n    if (queryParams) {\n      redirectUrl += `?${queryParams}`\n    }\n    let url\n    if (authType === 'CAS') {\n      url = config.ldpUri\n      url +=\n        url.indexOf('?') !== -1\n          ? `&service=${encodeURIComponent(redirectUrl)}`\n          : `?service=${encodeURIComponent(redirectUrl)}`\n    } else if (authType === 'OIDC') {\n      const scope = config.scope || 'openid+profile+email'\n      url = `${config.authEndpoint}?client_id=${config.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}`\n      if (config.state) {\n        url += `&state=${config.state}`\n      }\n    } else if (authType === 'OAuth2') {\n      url = `${config.authEndpoint}?client_id=${config.clientId}&response_type=code&redirect_uri=${redirectUrl}&state=${uuidv4()}`\n      if (config.scope) {\n        url += `&scope=${config.scope}`\n      }\n    }\n    if (!url) {\n      return\n    }\n    if (needMessage) {\n      MsgConfirm(t('views.login.jump_tip'), '', {\n        confirmButtonText: t('views.login.jump'),\n        cancelButtonText: t('common.cancel'),\n        confirmButtonClass: '',\n      })\n        .then(() => {\n          window.location.href = url\n        })\n        .catch(() => {\n        })\n    } else {\n      console.log('url', url)\n      window.location.href = url\n    }\n  })\n}\n\nfunction changeMode(val: string) {\n  loginMode.value = val === 'LDAP' ? val : 'LOCAL'\n  if (val !== 'LOCAL') {\n    loginMode.value = val\n  }\n  if (val === 'QR_CODE') {\n    loginMode.value = val\n    showQrCodeTab.value = true\n    return\n  }\n  showQrCodeTab.value = false\n  loginForm.value = {\n    username: '',\n    password: '',\n    captcha: '',\n  }\n  redirectAuth(val)\n  loginFormRef.value?.clearValidate()\n}\n\nfunction handleUsernameBlur(username: string) {\n  makeCode(username)\n}\n\nonBeforeMount(() => {\n  if (chatUser.chat_profile?.max_attempts) {\n    max_attempts.value = chatUser.chat_profile.max_attempts\n  }\n  if (chatUser.chat_profile?.login_value) {\n    modeList.value = chatUser.chat_profile.login_value\n    if (modeList.value.includes('LOCAL')) {\n      modeList.value = ['LOCAL', ...modeList.value.filter((item) => item !== 'LOCAL')]\n    } else if (modeList.value.includes('LDAP')) {\n      modeList.value = ['LDAP', ...modeList.value.filter((item) => item !== 'LDAP')]\n    }\n    loginMode.value = modeList.value[0] || 'LOCAL'\n    if (!modeList.value.includes('LOCAL') && !modeList.value.includes('LDAP')) {\n      loginMode.value = ''\n    }\n    if (modeList.value.length == 1 && ['CAS', 'OIDC', 'OAuth2'].includes(modeList.value[0])) {\n      redirectAuth(modeList.value[0])\n    }\n    // 这里的modeList 是oauth2 cas ldap oidc 这四个 还会有 lark wecom dingtalk\n    // 获取到的 modeList中除'CAS', 'OIDC', 'OAuth2' LOCAL之外的登录方式\n    QrList.value = modeList.value.filter(\n      (item) => !['CAS', 'OIDC', 'OAuth2', 'LOCAL', 'LDAP'].includes(item),\n    )\n    // modeList需要去掉lark wecom dingtalk\n    modeList.value = modeList.value.filter((item) => !['lark', 'wecom', 'dingtalk'].includes(item))\n    if (QrList.value.length > 0) {\n      QrList.value.forEach((item) => {\n        orgOptions.value.push({\n          key: item,\n          value:\n            item === 'wecom'\n              ? t('views.system.authentication.scanTheQRCode.wecom')\n              : item === 'dingtalk'\n                ? t('views.system.authentication.scanTheQRCode.dingtalk')\n                : t('views.system.authentication.scanTheQRCode.lark'),\n        })\n      })\n      if (!modeList.value.includes('LOCAL') && !modeList.value.includes('LDAP')) {\n        showQrCodeTab.value = true\n      }\n      modeList.value = ['QR_CODE', ...modeList.value]\n    }\n  }\n})\ndeclare const window: any\nonBeforeMount(() => {\n  const route = useRoute()\n  const currentUrl = ref(route.fullPath)\n  const params = new URLSearchParams(currentUrl.value.split('?')[1])\n  const client = params.get('client')\n\n  const handleDingTalk = () => {\n    const code = params.get('corpId')\n    if (code) {\n      dd.runtime.permission.requestAuthCode({corpId: code}).then((res) => {\n        console.log('DingTalk client request success:', res)\n        chatUser.dingOauth2Callback(res.code, accessToken).then(() => {\n          router.push({\n            name: 'chat',\n            params: {accessToken: accessToken},\n            query: route.query,\n          })\n        })\n      })\n    }\n  }\n\n  const handleLark = () => {\n    const appId = params.get('appId')\n    const callRequestAuthCode = () => {\n      window.tt?.requestAuthCode({\n        appId: appId,\n        success: (res: any) => {\n          chatUser.larkCallback(res.code, accessToken).then(() => {\n            router.push({\n              name: 'chat',\n              params: {accessToken: accessToken},\n              query: route.query,\n            })\n          })\n        },\n        fail: (error: any) => {\n          MsgError(error)\n        },\n      })\n    }\n\n    loadScript('https://lf-scm-cn.feishucdn.com/lark/op/h5-js-sdk-1.5.44.js', {\n      jsId: 'lark-sdk',\n      forceReload: true,\n    })\n      .then(() => {\n        if (window.tt) {\n          window.tt?.requestAccess({\n            appID: appId,\n            scopeList: [],\n            success: (res: any) => {\n              chatUser.larkCallback(res.code, accessToken).then(() => {\n                router.push({\n                  name: 'chat',\n                  params: {accessToken: accessToken},\n                  query: route.query,\n                })\n              })\n            },\n            fail: (error: any) => {\n              const {errno} = error\n              if (errno === 103) {\n                callRequestAuthCode()\n              }\n            },\n          })\n        } else {\n          callRequestAuthCode()\n        }\n      })\n      .catch((error) => {\n        console.error('SDK 加载失败:', error)\n      })\n  }\n\n  switch (client) {\n    case 'dingtalk':\n      handleDingTalk()\n      break\n    case 'lark':\n      handleLark()\n      break\n    default:\n      break\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.user-login {\n  &__header {\n    background: var(--app-header-bg-color);\n    position: fixed;\n    width: 100%;\n    left: 0;\n    top: 0;\n    z-index: 100;\n    height: var(--app-header-height);\n    line-height: var(--app-header-height);\n    box-sizing: border-box;\n    border-bottom: 1px solid var(--el-border-color);\n  }\n}\n\n.user-login-container {\n  width: 480px;\n\n  .login-card {\n    padding: 18px;\n  }\n}\n\n.login-gradient-divider {\n  position: relative;\n  text-align: center;\n  color: var(--el-color-info);\n\n  ::before {\n    content: '';\n    width: 25%;\n    height: 1px;\n    background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);\n    position: absolute;\n    left: 16px;\n    top: 50%;\n  }\n\n  ::after {\n    content: '';\n    width: 25%;\n    height: 1px;\n    background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);\n    position: absolute;\n    right: 16px;\n    top: 50%;\n  }\n}\n\n.login-button-circle {\n  padding: 20px !important;\n  margin: 0 4px;\n  width: 32px;\n  height: 32px;\n  text-align: center;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/user-login/scanCompinents/QrCodeTab.vue",
    "content": "<template>\n  <el-tabs v-model=\"activeKey\" @tab-change=\"selectTab\">\n    <template v-for=\"item in tabs\" :key=\"item.key\">\n      <el-tab-pane :label=\"item.value\" :name=\"item.key\">\n        <div class=\"text-center\" v-if=\"item.key === activeKey\">\n          <component\n            :is=\"defineAsyncComponent(() => import(`./${item.key}QrCode.vue`))\"\n            :config=\"config\"\n          />\n        </div>\n      </el-tab-pane>\n    </template>\n  </el-tabs>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref, defineAsyncComponent } from 'vue'\nimport useStore from \"@/stores\";\nconst { chatUser } = useStore()\n\ninterface Tab {\n  key: string\n  value: string\n}\n\ninterface PlatformConfig {\n  app_key: string\n  app_secret: string\n  auth_type: string\n  config: any\n}\n\ninterface Config {\n  app_key: string\n  app_secret: string\n  corpId?: string\n  agentId?: string\n}\n\nconst props = defineProps<{ tabs: Tab[] }>()\nconst activeKey = ref('')\nconst allConfigs = ref<PlatformConfig[]>([])\nconst config = ref<Config>({ app_key: '', app_secret: '' })\nasync function getPlatformInfo() {\n  try {\n    return await chatUser.getQrSource()\n  } catch (error) {\n    return []\n  }\n}\n\nonMounted(async () => {\n  if (props.tabs.length > 0) {\n    activeKey.value = props.tabs[0].key\n  }\n  allConfigs.value = await getPlatformInfo()\n  updateConfig(activeKey.value)\n})\n\nconst updateConfig = (key: string) => {\n  const selectedConfig = allConfigs.value.find((item) => item.auth_type === key)\n  if (selectedConfig && selectedConfig.config) {\n    config.value = selectedConfig.config\n  }\n}\n\nconst selectTab = (key: string) => {\n  activeKey.value = key\n  updateConfig(key)\n}\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/chat/user-login/scanCompinents/dingtalkQrCode.vue",
    "content": "<template>\n  <div class=\"flex-center mb-16\">\n    <img src=\"@/assets/logo/logo_dingtalk.svg\" alt=\"\" width=\"24px\" class=\"mr-4\"/>\n    <h2>{{ $t('views.system.authentication.scanTheQRCode.dingtalkQrCode') }}</h2>\n  </div>\n  <div class=\"ding-talk-qrName\">\n    <div id=\"ding-talk-qr\"></div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {useRoute, useRouter} from 'vue-router'\nimport {useScriptTag} from '@vueuse/core'\nimport {ref, watch} from 'vue'\nimport useStore from '@/stores'\nimport {MsgError} from '@/utils/message'\n// 声明 DTFrameLogin 和 QRLogin 的类型\ndeclare global {\n  interface Window {\n    DTFrameLogin: (\n      frameParams: IDTLoginFrameParams,\n      loginParams: IDTLoginLoginParams,\n      successCbk: (result: IDTLoginSuccess) => void,\n      errorCbk?: (errorMsg: string) => void\n    ) => void\n    QRLogin: (QRLogin: qrLogin) => Record<any, any>\n  }\n}\n\n// 定义接口类型\ninterface IDTLoginFrameParams {\n  id: string\n  width?: number\n  height?: number\n}\n\ninterface IDTLoginLoginParams {\n  redirect_uri: string\n  response_type: string\n  client_id: string\n  scope: string\n  prompt: string\n  state?: string\n  org_type?: string\n  corpId?: string\n  exclusiveLogin?: string\n  exclusiveCorpId?: string\n}\n\ninterface IDTLoginSuccess {\n  redirectUrl: string\n  authCode: string\n  state?: string\n}\n\ninterface qrLogin {\n  id: string\n  goto: string\n  width: string\n  height: string\n  style?: string\n}\n\nconst props = defineProps<{\n  config: {\n    app_secret: string\n    app_key: string\n    corp_id: string\n  }\n}>()\n\nconst router = useRouter()\nconst {chatUser} = useStore()\nconst {load} = useScriptTag('https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js')\nconst isConfigReady = ref(false)\n\nconst route = useRoute()\nconst {\n  params: {accessToken},\n} = route as any\n\nconst initActive = async () => {\n  try {\n    await load(true)\n    if (!isConfigReady.value) {\n      return\n    }\n\n    const data = {\n      appKey: props.config.app_key,\n      appSecret: props.config.app_secret,\n      corp_id: props.config.corp_id\n    }\n\n    const redirectUri = encodeURIComponent(window.location.origin)\n    window.DTFrameLogin(\n      {\n        id: 'ding-talk-qr',\n        width: 280,\n        height: 280\n      },\n      {\n        redirect_uri: redirectUri,\n        client_id: data.appKey,\n        scope: 'openid corpid',\n        response_type: 'code',\n        state: 'fit2cloud-ding-chat-qr',\n        prompt: 'consent',\n        corpId: data.corp_id\n      },\n      (loginResult) => {\n        const authCode = loginResult.authCode\n        chatUser.dingCallback(authCode, accessToken).then(() => {\n          router.push({\n            name: 'chat',\n            params: {accessToken: accessToken},\n            query: route.query,\n          })\n        })\n      },\n      (errorMsg: string) => {\n        MsgError(errorMsg)\n      }\n    )\n  } catch (error) {\n  }\n}\n\nwatch(\n  () => props.config,\n  (newConfig) => {\n    if (newConfig.app_key && newConfig.corp_id) {\n      isConfigReady.value = true\n      initActive()\n    }\n  },\n  {immediate: true}\n)\n</script>\n\n<style lang=\"scss\">\n.ding-talk-qrName {\n  border: 1px solid #e8e8e8;\n  border-radius: 8px;\n  height: 280px;\n  width: 280px;\n  margin: 0 auto;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat/user-login/scanCompinents/larkQrCode.vue",
    "content": "<template>\n  <div class=\"flex-center mb-16\">\n    <img src=\"@/assets/logo/logo_lark.svg \" alt=\"\" width=\"24px\" class=\"mr-4\"/>\n    <h2>{{ $t('views.system.authentication.scanTheQRCode.larkQrCode') }}</h2>\n  </div>\n  <div id=\"lark-qr\" class=\"lark-qrName\"></div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {useScriptTag} from '@vueuse/core'\nimport {onMounted} from 'vue'\nimport {useRoute} from \"vue-router\";\n\nconst route = useRoute()\nconst {\n  params: {accessToken},\n} = route as any\n\nconst {load} = useScriptTag(\n  'https://lf-package-cn.feishucdn.com/obj/feishu-static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.3.js'\n)\n\nconst props = defineProps<{\n  config: {\n    app_secret: string\n    app_key: string\n  }\n}>()\n\nconst initActive = async () => {\n  const scriptLoaded = await load(true)\n  if (!scriptLoaded) {\n    console.error('飞书二维码 SDK 加载失败')\n    return\n  }\n\n  const data = {\n    agentId: props.config.app_key,\n    appSecret: props.config.app_secret\n  }\n  const queryParams = new URLSearchParams(route.query as any).toString()\n  const redirectUrl = encodeURIComponent(`${window.location.origin}/chat/api/auth/lark?accessToken=${accessToken}&${queryParams}`)\n  //const redirectUrl = encodeURIComponent(`http://127.0.0.1:8080/chat/api/auth/lark?accessToken=${accessToken}&${queryParams}`)\n  const url = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${data.agentId}&redirect_uri=${redirectUrl}&response_type=code&state=fit2cloud-lark-qr`\n\n  const QRLoginObj = window.QRLogin({\n    id: 'lark-qr',\n    goto: url,\n    width: '266',\n    height: '266',\n    style: 'width:280px;height:280px;border:1px solid #e8e8e8;margin:0 auto;border-radius:8px;'\n  })\n\n  window.addEventListener('message', async (event: any) => {\n    if (QRLoginObj.matchOrigin(event.origin) && QRLoginObj.matchData(event.data)) {\n      const loginTmpCode = event.data.tmp_code\n      window.location.replace(`${url}&tmp_code=${loginTmpCode}`)\n    }\n  })\n}\n\nonMounted(() => {\n  initActive()\n})\n</script>\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/chat/user-login/scanCompinents/wecomQrCode.vue",
    "content": "<template>\n  <div id=\"wecom-qr\" class=\"wecom-qr flex\"></div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {nextTick, defineProps, onBeforeUnmount} from 'vue'\nimport {useRoute, useRouter} from 'vue-router'\nimport {getBrowserLang} from '@/locales'\nimport useStore from '@/stores'\n\nconst props = defineProps<{\n  config: {\n    app_secret: string\n    app_key: string\n    corp_id?: string\n    agent_id?: string\n    callback_url: string\n    qr_url: string\n  }\n}>()\n\nconst router = useRouter()\nconst route = useRoute()\nconst {chatUser} = useStore()\n\nconst {\n  params: {accessToken},\n} = route as any\n\nlet iframe: HTMLIFrameElement | null = null\n\nfunction createTransparentIFrame(el: string) {\n  const container = document.querySelector(el)\n  if (!container) return null\n\n  const iframeEl = document.createElement('iframe')\n  iframeEl.style.cssText = `\n    display: block;\n    border: none;\n    background: transparent;\n  `\n  iframeEl.referrerPolicy = 'origin'\n  iframeEl.setAttribute('frameborder', '0')\n  iframeEl.setAttribute('allowtransparency', 'true')\n  iframeEl.setAttribute('allow', 'local-network-access')\n  container.appendChild(iframeEl)\n  return iframeEl\n}\n\nfunction getLang() {\n  const lang = localStorage.getItem('MaxKB-locale') || getBrowserLang()\n  return lang === 'en-US' ? 'en' : 'zh'\n}\n\nfunction cleanup() {\n  iframe?.remove()\n  iframe = null\n}\n\nconst init = async () => {\n  await nextTick()\n\n  iframe = createTransparentIFrame('#wecom-qr')\n  if (!iframe) return\n\n  const redirectUri = encodeURIComponent(props.config.callback_url)\n\n  iframe.src =\n    `${props.config.qr_url}` +\n    `?login_type=CorpApp` +\n    `&appid=${props.config.corp_id}` +\n    `&agentid=${props.config.agent_id}` +\n    `&redirect_uri=${redirectUri}` +\n    `&state=${accessToken}` +\n    `&lang=${getLang()}` +\n    `&panel_size=small` +\n    `&redirect_type=self`\n  iframe.addEventListener('load', (e) => {\n    if (iframe?.contentWindow) {\n      iframe.contentWindow.postMessage('getToken', '*')\n    }\n  })\n  window.addEventListener('message', (event) => {\n    if (event.data.type === 'token') {\n      chatUser.setToken(event.data.value)\n      router.push({\n        name: 'chat',\n        params: {accessToken},\n        query: route.query,\n      })\n    }\n  })\n}\n\nonBeforeUnmount(cleanup)\n\ninit()\n</script>\n\n<style scoped lang=\"scss\">\n#wecom-qr {\n  margin-top: -20px;\n  height: 360px;\n  justify-content: center;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat-log/component/ChatRecordDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"60%\" @close=\"closeHandle\" class=\"chat-record-drawer\">\n    <template #header>\n      <h4 class=\"single-line\">{{ currentAbstract }}</h4>\n    </template>\n    <div\n      v-loading=\"paginationConfig.current_page === 1 && loading\"\n      class=\"h-full\"\n      style=\"padding: 24px 0\"\n    >\n      <AiChat\n        ref=\"AiChatRef\"\n        :application-details=\"application\"\n        type=\"log\"\n        :record=\"recordList\"\n        @scroll=\"handleScroll\"\n      >\n      </AiChat>\n    </div>\n    <template #footer>\n      <div>\n        <el-button @click=\"pre\" :disabled=\"pre_disable || loading\">{{\n          $t('common.pages.prev')\n        }}</el-button>\n        <el-button @click=\"next\" :disabled=\"next_disable || loading\">{{\n          $t('common.pages.next')\n        }}</el-button>\n      </div>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive, watch, nextTick, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { type ApplicationFormType, type chatType } from '@/api/type/application'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst AiChatRef = ref()\nconst props = withDefaults(\n  defineProps<{\n    /**\n     * 应用信息\n     */\n    application?: ApplicationFormType\n    /**\n     * 对话 记录id\n     */\n    chatId: string\n    currentAbstract: string\n    /**\n     * 下一条\n     */\n    next: () => void\n    /**\n     * 上一条\n     */\n    pre: () => void\n\n    pre_disable: boolean\n\n    next_disable: boolean\n  }>(),\n  {},\n)\n\nconst emit = defineEmits(['update:chatId', 'update:currentAbstract', 'refresh'])\n\nconst route = useRoute()\nconst {\n  params: { id },\n} = route\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst loading = ref(false)\nconst visible = ref(false)\nconst recordList = ref<chatType[]>([])\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nfunction closeHandle() {\n  recordList.value = []\n  paginationConfig.total = 0\n  paginationConfig.current_page = 1\n}\n\nfunction getChatRecord() {\n  return loadSharedApi({ type: 'chatLog', systemType: apiType.value })\n    .getChatRecordLog(id as string, props.chatId, paginationConfig, loading)\n    .then((res: any) => {\n      paginationConfig.total = res.data.total\n      const list = res.data.records\n      recordList.value = [...list, ...recordList.value].sort((a, b) =>\n        a.create_time.localeCompare(b.create_time),\n      )\n      if (paginationConfig.current_page === 1) {\n        nextTick(() => {\n          // 将滚动条滚动到最下面\n          AiChatRef.value.setScrollBottom()\n        })\n      }\n    })\n}\n\nwatch(\n  () => props.chatId,\n  () => {\n    recordList.value = []\n    paginationConfig.total = 0\n    paginationConfig.current_page = 1\n    if (props.chatId) {\n      getChatRecord()\n    }\n  },\n)\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    emit('update:chatId', '')\n    emit('update:currentAbstract', '')\n    emit('refresh')\n  }\n})\n\nfunction handleScroll(event: any) {\n  if (\n    props.chatId !== 'new' &&\n    event.scrollTop === 0 &&\n    paginationConfig.total > recordList.value.length\n  ) {\n    const history_height = event.dialogScrollbar.offsetHeight\n    paginationConfig.current_page += 1\n    getChatRecord().then(() => {\n      event.scrollDiv.setScrollTop(event.dialogScrollbar.offsetHeight - history_height)\n    })\n  }\n}\n\nconst open = () => {\n  visible.value = true\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\">\n.single-line {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.chat-record-drawer {\n  .el-drawer__body {\n    background: var(--app-layout-bg-color);\n    padding: 0;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat-log/component/EditContentDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.chatLog.editContent')\"\n    v-model=\"dialogVisible\"\n    width=\"600\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form\n      ref=\"formRef\"\n      :model=\"form\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      :rules=\"rules\"\n      @submit.prevent\n    >\n      <el-form-item :label=\"$t('views.paragraph.relatedProblem.title')\">\n        <el-input\n          v-model=\"form.problem_text\"\n          :placeholder=\"$t('views.paragraph.relatedProblem.title')\"\n          maxlength=\"256\"\n          show-word-limit\n        >\n        </el-input>\n      </el-form-item>\n      <el-form-item :label=\"$t('common.content')\" prop=\"content\">\n        <MdEditor\n          v-model=\"form.content\"\n          :placeholder=\"$t('views.chatLog.form.content.placeholder')\"\n          :maxLength=\"100000\"\n          :preview=\"false\"\n          :toolbars=\"toolbars\"\n          style=\"height: 300px\"\n          @onUploadImg=\"onUploadImg\"\n          :footers=\"footers\"\n        >\n          <template #defFooters>\n            <span style=\"margin-left: -6px\">/ 100000</span>\n          </template>\n        </MdEditor>\n      </el-form-item>\n      <el-form-item :label=\"$t('common.title')\">\n        <el-input\n          show-word-limit\n          v-model=\"form.title\"\n          :placeholder=\"$t('views.chatLog.form.title.placeholder')\"\n          maxlength=\"256\"\n        >\n        </el-input>\n      </el-form-item>\n    </el-form>\n    <SelectKnowledgeDocument\n      v-if=\"detail.workspace_id\"\n      ref=\"SelectKnowledgeDocumentRef\"\n      :post-knowledge-handler=\"postKnowledgeHandler\"\n      :apiType=\"apiType\"\n      @changeKnowledge=\"changeKnowledge\"\n      @changeDocument=\"changeDocument\"\n      :isApplication=\"true\"\n      :workspace-id=\"detail.workspace_id\"\n    />\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submitForm(formRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive, computed, onMounted } from 'vue'\nimport { useRoute } from 'vue-router'\nimport SelectKnowledgeDocument from '@/components/select-knowledge-document/index.vue'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport imageApi from '@/api/image'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { Permission } from '@/utils/permission/type'\nimport { hasPermission } from '@/utils/permission'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\n\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst postKnowledgeHandler = (knowledgeList: Array<any>) => {\n  return knowledgeList.filter((item) => {\n    if (apiType.value === 'workspace') {\n      return hasPermission(\n        [\n          RoleConst.WORKSPACE_MANAGE.getWorkspaceRole(),\n          new Permission('KNOWLEDGE_DOCUMENT:READ+EDIT').getWorkspacePermissionWorkspaceManageRole,\n          new Permission('KNOWLEDGE_DOCUMENT:READ+EDIT').getWorkspaceResourcePermission(\n            'KNOWLEDGE',\n            item.id,\n          ),\n        ],\n        'OR',\n      )\n    } else if (apiType.value === 'systemManage') {\n      return hasPermission(\n        [RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_EDIT],\n        'OR',\n      )\n    }\n  })\n}\n\nconst emit = defineEmits(['refresh'])\n\nconst formRef = ref()\n\nconst toolbars = [\n  'bold',\n  'underline',\n  'italic',\n  '-',\n  'title',\n  'strikeThrough',\n  'sub',\n  'sup',\n  'quote',\n  'unorderedList',\n  'orderedList',\n  'task',\n  '-',\n  'codeRow',\n  'code',\n  'link',\n  'image',\n  'table',\n  'mermaid',\n  'katex',\n  '-',\n  'revoke',\n  'next',\n  '=',\n  'pageFullscreen',\n  'preview',\n  'htmlPreview',\n] as any[]\n\nconst footers = ['markdownTotal', 0, '=', 1, 'scrollSwitch']\n\nconst SelectKnowledgeDocumentRef = ref()\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\nconst detail = ref<any>({})\nconst form = ref<any>({\n  chat_id: '',\n  record_id: '',\n  problem_text: '',\n  title: '',\n  content: '',\n})\n\nconst rules = reactive<FormRules>({\n  content: [\n    { required: true, message: t('views.chatLog.form.content.placeholder'), trigger: 'blur' },\n  ],\n})\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      chat_id: '',\n      record_id: '',\n      problem_text: '',\n      title: '',\n      content: '',\n    }\n    formRef.value?.clearValidate()\n    SelectKnowledgeDocumentRef.value?.clearValidate()\n  }\n})\n\nconst onUploadImg = async (files: any, callback: any) => {\n  const res = await Promise.all(\n    files.map((file: any) => {\n      return new Promise((rev, rej) => {\n        const fd = new FormData()\n        fd.append('file', file)\n\n        imageApi\n          .postImage(fd)\n          .then((res: any) => {\n            rev(res)\n          })\n          .catch((error) => rej(error))\n      })\n    }),\n  )\n\n  callback(res.map((item) => item.data))\n}\n\nfunction changeKnowledge(knowledge_id: string) {\n  localStorage.setItem(id + 'chat_knowledge_id', knowledge_id)\n}\n\nfunction changeDocument(document_id: string) {\n  localStorage.setItem(id + 'chat_document_id', document_id)\n}\n\nconst open = (data: any) => {\n  getDetail()\n  form.value.chat_id = data.chat_id\n  form.value.record_id = data.id\n  form.value.problem_text = data.problem_text ? data.problem_text.substring(0, 256) : ''\n  form.value.content = data.answer_text\n  formRef.value?.clearValidate()\n  dialogVisible.value = true\n}\nconst submitForm = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate(async (valid) => {\n    if (valid) {\n      if (await SelectKnowledgeDocumentRef.value?.validate()) {\n        const obj = {\n          title: form.value.title,\n          content: form.value.content,\n          problem_text: form.value.problem_text,\n        }\n        loadSharedApi({ type: 'chatLog', systemType: apiType.value })\n          .putChatRecordLog(\n            id,\n            form.value.chat_id,\n            form.value.record_id,\n            SelectKnowledgeDocumentRef.value.form.knowledge_id,\n            SelectKnowledgeDocumentRef.value.form.document_id,\n            obj,\n            loading,\n          )\n          .then((res: any) => {\n            emit('refresh', res.data)\n            dialogVisible.value = false\n          })\n      }\n    }\n  })\n}\n\nfunction getDetail(isLoading = false) {\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getApplicationDetail(id as string, isLoading ? loading : undefined)\n    .then((res: any) => {\n      detail.value = res.data\n    })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/chat-log/component/EditMarkDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.chatLog.editMark')\"\n    v-model=\"dialogVisible\"\n    width=\"600\"\n    class=\"edit-mark-dialog\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <template #header=\"{ titleId, titleClass }\">\n      <div class=\"flex-between\">\n        <h4 :id=\"titleId\" :class=\"titleClass\">{{ $t('views.chatLog.editMark') }}</h4>\n        <div class=\"text-right\">\n          <el-button text @click=\"isEdit = true\" v-if=\"!isEdit\">\n            <AppIcon iconName=\"app-edit\"></AppIcon>\n          </el-button>\n          <el-button text style=\"margin-left: 4px\" @click=\"deleteMark\">\n            <AppIcon iconName=\"app-delete\"></AppIcon>\n          </el-button>\n          <el-divider direction=\"vertical\" />\n        </div>\n      </div>\n    </template>\n\n    <el-scrollbar>\n      <div style=\"min-height: 250px; max-height: 350px\" v-loading=\"loading\">\n        <el-form\n          v-if=\"isEdit\"\n          ref=\"formRef\"\n          :model=\"form\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n          :rules=\"rules\"\n          @submit.prevent\n        >\n          <el-form-item prop=\"content\">\n            <el-input\n              v-model=\"form.content\"\n              :placeholder=\"$t('views.chatLog.form.content.placeholder')\"\n              :maxlength=\"100000\"\n              show-word-limit\n              :rows=\"15\"\n              type=\"textarea\"\n            >\n            </el-input>\n          </el-form-item>\n        </el-form>\n        <span v-else class=\"pre-wrap\">{{ form?.content }}</span>\n      </div>\n    </el-scrollbar>\n\n    <template #footer>\n      <span class=\"dialog-footer\" v-if=\"isEdit\">\n        <el-button @click.prevent=\"isEdit = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(formRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { t } from '@/locales'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\n\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst emit = defineEmits(['refresh'])\n\nconst formRef = ref()\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\n\nconst form = ref<any>({})\nconst isEdit = ref(false)\nconst detail = ref<any>({})\n\nconst rules = reactive<FormRules>({\n  content: [\n    { required: true, message: t('views.chatLog.form.content.placeholder'), trigger: 'blur' },\n  ],\n})\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {}\n    isEdit.value = false\n  }\n})\n\nfunction deleteMark() {\n  loadSharedApi({ type: 'chatLog', systemType: apiType.value })\n    .delMarkChatRecord(\n      id as string,\n      detail.value.chat_id,\n      detail.value.id,\n      form.value.knowledge,\n      form.value.document,\n      form.value.id,\n      loading,\n    )\n    .then(() => {\n      emit('refresh')\n      MsgSuccess(t('common.deleteSuccess'))\n      dialogVisible.value = false\n    })\n}\n\nfunction getMark(data: any) {\n  loadSharedApi({ type: 'chatLog', systemType: apiType.value })\n    .getMarkChatRecord(id as string, data.chat_id, data.id, loading)\n    .then((res: any) => {\n      if (res.data.length > 0) {\n        form.value = res.data[0]\n      }\n    })\n}\n\nconst open = (data: any) => {\n  detail.value = data\n  getMark(data)\n  dialogVisible.value = true\n}\nconst submit = async (formEl: FormInstance) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      loadSharedApi({ type: 'paragraph', systemType: apiType.value })\n        .putParagraph(\n          form.value.knowledge,\n          form.value.document,\n          form.value.id,\n          {\n            content: form.value.content,\n          },\n          loading,\n        )\n        .then(() => {\n          dialogVisible.value = false\n        })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped>\n.edit-mark-dialog {\n  .el-dialog__header.show-close {\n    padding-right: 15px;\n  }\n\n  .el-dialog__headerbtn {\n    top: 13px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat-log/index.vue",
    "content": "<template>\n  <div class=\"p-16-24\">\n    <h2 class=\"mb-16\">{{ $t('views.chatLog.title') }}</h2>\n\n    <el-card style=\"--el-card-padding: 24px\">\n      <div class=\"mb-16 flex-between\">\n        <div class=\"flex align-center\">\n          <div class=\"flex-between complex-search\">\n            <el-select\n              v-model=\"search_type\"\n              class=\"complex-search__left\"\n              @change=\"search_type_change\"\n              style=\"width: 75px\"\n            >\n              <el-option :label=\"$t('views.chatLog.table.abstract')\" value=\"abstract\" />\n              <el-option :label=\"$t('views.chatLog.table.username')\" value=\"username\" />\n            </el-select>\n            <el-input\n              v-model=\"search_form[search_type]\"\n              @change=\"getList\"\n              :placeholder=\"$t('common.search')\"\n              class=\"w-240\"\n              clearable\n            />\n          </div>\n          <el-select v-model=\"history_day\" class=\"ml-12 w-180\" @change=\"changeDayHandle\">\n            <el-option\n              v-for=\"item in dayOptions\"\n              :key=\"item.value\"\n              :label=\"item.label\"\n              :value=\"item.value\"\n            />\n          </el-select>\n          <el-date-picker\n            v-if=\"history_day === 'other'\"\n            v-model=\"daterangeValue\"\n            type=\"daterange\"\n            :start-placeholder=\"$t('views.applicationOverview.monitor.startDatePlaceholder')\"\n            :end-placeholder=\"$t('views.applicationOverview.monitor.endDatePlaceholder')\"\n            format=\"YYYY-MM-DD\"\n            value-format=\"YYYY-MM-DD\"\n            @change=\"changeDayRangeHandle\"\n            style=\"width: 240px\"\n            class=\"mr-12\"\n          />\n        </div>\n        <div style=\"display: flex; align-items: center\" class=\"float-right\">\n          <el-button @click=\"dialogVisible = true\" v-if=\"permissionPrecise.chat_log_clear(id)\">\n            {{ $t('views.chatLog.buttons.clearStrategy') }}\n          </el-button>\n          <el-button @click=\"exportLog\" v-if=\"permissionPrecise.chat_log_export(id)\">\n            {{ $t('common.export') }}\n          </el-button>\n          <el-button\n            @click=\"openDocumentDialog\"\n            :disabled=\"multipleSelection.length === 0\"\n            v-if=\"permissionPrecise.chat_log_add_knowledge(id)\"\n            >{{ $t('views.chatLog.addToKnowledge') }}\n          </el-button>\n        </div>\n      </div>\n\n      <app-table\n        :data=\"tableData\"\n        :pagination-config=\"paginationConfig\"\n        @sizeChange=\"getList\"\n        @changePage=\"getList\"\n        @row-click=\"rowClickHandle\"\n        v-loading=\"loading\"\n        :row-class-name=\"setRowClass\"\n        @selection-change=\"handleSelectionChange\"\n        class=\"log-table\"\n        ref=\"multipleTableRef\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column\n          prop=\"abstract\"\n          :label=\"$t('views.chatLog.table.abstract')\"\n          show-overflow-tooltip\n        />\n        <el-table-column\n          prop=\"chat_record_count\"\n          :label=\"$t('views.chatLog.table.chat_record_count')\"\n          align=\"right\"\n        />\n        <el-table-column prop=\"star_num\" align=\"right\">\n          <template #header>\n            <div>\n              <span>{{ $t('views.chatLog.table.feedback.label') }}</span>\n              <el-popover\n                :width=\"200\"\n                trigger=\"click\"\n                :visible=\"popoverVisible\"\n                :persistent=\"false\"\n              >\n                <template #reference>\n                  <el-button\n                    style=\"margin-top: -2px\"\n                    :type=\"filter.min_star || filter.min_trample ? 'primary' : ''\"\n                    link\n                    @click=\"popoverVisible = !popoverVisible\"\n                  >\n                    <el-icon>\n                      <Filter />\n                    </el-icon>\n                  </el-button>\n                </template>\n                <div class=\"filter\">\n                  <div class=\"form-item mb-16\">\n                    <div @click.stop>\n                      {{ $t('views.chatLog.table.feedback.star') }} >=\n                      <el-input-number\n                        v-model=\"filter.min_star\"\n                        :min=\"0\"\n                        :step=\"1\"\n                        :value-on-clear=\"0\"\n                        controls-position=\"right\"\n                        style=\"width: 80px\"\n                        size=\"small\"\n                        step-strictly\n                      />\n                    </div>\n                  </div>\n                  <div class=\"form-item mb-16\">\n                    <div @click.stop>\n                      {{ $t('views.chatLog.table.feedback.trample') }} >=\n                      <el-input-number\n                        v-model=\"filter.min_trample\"\n                        :min=\"0\"\n                        :step=\"1\"\n                        :value-on-clear=\"0\"\n                        controls-position=\"right\"\n                        style=\"width: 80px\"\n                        size=\"small\"\n                        step-strictly\n                      />\n                    </div>\n                  </div>\n                </div>\n                <div class=\"text-right\">\n                  <el-button size=\"small\" @click=\"filterChange('clear')\"\n                    >{{ $t('common.clear') }}\n                  </el-button>\n                  <el-button type=\"primary\" @click=\"filterChange\" size=\"small\"\n                    >{{ $t('common.confirm') }}\n                  </el-button>\n                </div>\n              </el-popover>\n            </div>\n          </template>\n          <template #default=\"{ row }\">\n            <span class=\"mr-8\" v-if=\"!row.trample_num && !row.star_num\"> - </span>\n            <span class=\"mr-8\" v-else>\n              <span v-if=\"row.star_num\">\n                <AppIcon iconName=\"app-like-color\"></AppIcon>\n                {{ row.star_num }}\n              </span>\n              <span v-if=\"row.trample_num\" class=\"ml-8\">\n                <AppIcon iconName=\"app-oppose-color\"></AppIcon>\n                {{ row.trample_num }}\n              </span>\n            </span>\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"mark_sum\" :label=\"$t('views.chatLog.table.mark')\" align=\"right\" />\n        <el-table-column prop=\"asker\" :label=\"$t('views.chatLog.table.user')\">\n          <template #default=\"{ row }\">\n            {{ row.asker?.username }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"ip_address\"\n          :label=\"$t('views.operateLog.table.ip_address')\"\n          width=\"120\"\n        >\n          <template #default=\"{ row }\">\n            {{ row.ip_address || '-' }}\n          </template>\n        </el-table-column>\n\n        <el-table-column prop=\"source\" :label=\"$t('views.tool.form.source.label')\">\n          <template #default=\"{ row }\">\n            {{ getSourceTypeName(row.source?.type) }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('views.chatLog.table.recenTimes')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.update_time) }}\n          </template>\n        </el-table-column>\n      </app-table>\n    </el-card>\n    <ChatRecordDrawer\n      :next=\"nextChatRecord\"\n      :pre=\"preChatRecord\"\n      ref=\"ChatRecordRef\"\n      v-model:chatId=\"currentChatId\"\n      v-model:currentAbstract=\"currentAbstract\"\n      :application=\"detail\"\n      :pre_disable=\"pre_disable\"\n      :next_disable=\"next_disable\"\n      @refresh=\"refresh\"\n    />\n    <el-dialog\n      :title=\"$t('views.chatLog.buttons.clearStrategy')\"\n      v-model=\"dialogVisible\"\n      width=\"25%\"\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n    >\n      <el-row :gutter=\"20\">\n        <el-col :span=\"24\">\n          <div class=\"mb-16\">\n            <span>{{ $t('common.delete') }}</span>\n            <el-input-number\n              v-model=\"days\"\n              controls-position=\"right\"\n              :min=\"1\"\n              :max=\"100000\"\n              :value-on-clear=\"0\"\n              step-strictly\n              style=\"width: 110px; margin-left: 8px; margin-right: 8px\"\n            ></el-input-number>\n            <span>{{ $t('views.chatLog.daysText') }}</span>\n          </div>\n        </el-col>\n        <el-col :span=\"24\">\n          <div>\n            <span>{{ $t('common.delete') }}</span>\n            <el-input-number\n              v-model=\"file_days\"\n              controls-position=\"right\"\n              :min=\"1\"\n              :max=\"days\"\n              :value-on-clear=\"0\"\n              step-strictly\n              style=\"width: 110px; margin-left: 8px; margin-right: 8px\"\n            ></el-input-number>\n            <span>{{ $t('views.chatLog.fileDaysText') }}</span>\n          </div>\n        </el-col>\n      </el-row>\n\n      <template #footer>\n        <div class=\"dialog-footer\" style=\"margin-top: 16px\">\n          <el-button @click=\"dialogVisible = false\">{{ $t('common.cancel') }}</el-button>\n          <el-button type=\"primary\" @click=\"saveCleanTime\">\n            {{ $t('common.save') }}\n          </el-button>\n        </div>\n      </template>\n    </el-dialog>\n\n    <el-dialog\n      :title=\"$t('views.chatLog.addToKnowledge')\"\n      v-model=\"documentDialogVisible\"\n      width=\"50%\"\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n    >\n      <SelectKnowledgeDocument\n        :post-knowledge-handler=\"postKnowledgeHandler\"\n        ref=\"SelectKnowledgeDocumentRef\"\n        :apiType=\"apiType\"\n        :workspace-id=\"detail.workspace_id\"\n      />\n      <template #footer>\n        <span class=\"dialog-footer\">\n          <el-button @click.prevent=\"documentDialogVisible = false\">\n            {{ $t('common.cancel') }}\n          </el-button>\n          <el-button type=\"primary\" @click=\"submitForm\" :loading=\"documentLoading\">\n            {{ $t('common.save') }}\n          </el-button>\n        </span>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, type Ref, onMounted, reactive, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { cloneDeep } from 'lodash'\nimport ChatRecordDrawer from './component/ChatRecordDrawer.vue'\nimport SelectKnowledgeDocument from '@/components/select-knowledge-document/index.vue'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { beforeDay, datetimeFormat, nowDate } from '@/utils/time'\nimport type { Dict } from '@/api/type/common'\nimport { t } from '@/locales'\nimport { ElTable } from 'element-plus'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { Permission } from '@/utils/permission/type'\nimport { hasPermission } from '@/utils/permission'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['application'][apiType.value]\n})\n\nconst {\n  params: { id },\n} = route as any\n\nconst emit = defineEmits(['refresh'])\n\nconst search_type = ref('abstract')\nconst search_form = ref<any>({\n  abstract: '',\n  username: '',\n})\n\nconst search_type_change = () => {\n  search_form.value = { abstract: '', username: '' }\n}\n\n// 定义源类型枚举\nenum SourceType {\n  ONLINE = 'ONLINE',\n  API_CALL = 'API_CALL',\n  ENTERPRISE_WECHAT = 'ENTERPRISE_WECHAT',\n  WECHAT_PUBLIC_ACCOUNT = 'WECHAT_PUBLIC_ACCOUNT',\n  LARK = 'LARK',\n  DINGTALK = 'DINGTALK',\n  ENTERPRISE_WECHAT_ROBOT = 'ENTERPRISE_WECHAT_ROBOT',\n  TRIGGER = 'TRIGGER',\n  SLACK = 'SLACK',\n}\n\n// 创建国际化键值映射\nconst SOURCE_TYPE_TRANSLATIONS: Record<SourceType, string> = {\n  [SourceType.ONLINE]: 'views.chatLog.online',\n  [SourceType.API_CALL]: 'views.chatLog.apiCall',\n  [SourceType.ENTERPRISE_WECHAT]: 'views.chatLog.enterpriseWeChat',\n  [SourceType.WECHAT_PUBLIC_ACCOUNT]: 'views.chatLog.wechatPublicAccount',\n  [SourceType.LARK]: 'views.chatLog.lark',\n  [SourceType.DINGTALK]: 'views.chatLog.dingtalk',\n  [SourceType.ENTERPRISE_WECHAT_ROBOT]: 'views.chatLog.enterpriseWeChatRobot',\n  [SourceType.TRIGGER]: 'views.trigger.title',\n  [SourceType.SLACK]: 'views.chatLog.slack',\n}\n\nconst dayOptions = [\n  {\n    value: 7,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past7Days'), // 使用 t 方法来国际化显示文本\n  },\n  {\n    value: 30,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past30Days'),\n  },\n  {\n    value: 90,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past90Days'),\n  },\n  {\n    value: 183,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past183Days'),\n  },\n  {\n    value: 'other',\n    label: t('common.custom'),\n  },\n]\nconst daterangeValue = ref('')\n// 提交日期时间\nconst daterange = ref({\n  start_time: '',\n  end_time: '',\n})\n\nconst multipleTableRef = ref<InstanceType<typeof ElTable>>()\nconst multipleSelection = ref<any[]>([])\n\nconst ChatRecordRef = ref()\nconst loading = ref(false)\nconst documentLoading = ref(false)\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\nconst dialogVisible = ref(false)\nconst documentDialogVisible = ref(false)\nconst days = ref<number>(180)\nconst file_days = ref<number>(180)\nconst tableData = ref<any[]>([])\nconst tableIndexMap = computed<Dict<number>>(() => {\n  return tableData.value\n    .map((row, index) => ({\n      [row.id]: index,\n    }))\n    .reduce((pre, next) => ({ ...pre, ...next }), {})\n})\nconst history_day = ref<number | string>(7)\n\nconst detail = ref<any>(null)\n\nconst currentChatId = ref<string>('')\nconst currentAbstract = ref<string>('')\nconst popoverVisible = ref(false)\nconst defaultFilter = {\n  min_star: 0,\n  min_trample: 0,\n  comparer: 'and',\n}\nconst filter = ref<any>({\n  min_star: 0,\n  min_trample: 0,\n  comparer: 'and',\n})\nconst postKnowledgeHandler = (knowledgeList: Array<any>) => {\n  return knowledgeList.filter((item) => {\n    if (apiType.value === 'workspace') {\n      if (item.resource_type === 'folder') {\n        return true\n      }\n      if (item.resource_type === 'knowledge') {\n        return hasPermission(\n          [\n            RoleConst.WORKSPACE_MANAGE.getWorkspaceRole(),\n            new Permission('KNOWLEDGE_DOCUMENT:READ+EDIT')\n              .getWorkspacePermissionWorkspaceManageRole,\n            new Permission('KNOWLEDGE_DOCUMENT:READ+EDIT').getWorkspaceResourcePermission(\n              'KNOWLEDGE',\n              item.id,\n            ),\n          ],\n          'OR',\n        )\n      }\n    } else if (apiType.value === 'systemManage') {\n      return hasPermission(\n        [RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_EDIT],\n        'OR',\n      )\n    }\n  })\n}\n\nfunction filterChange(val: string) {\n  if (val === 'clear') {\n    filter.value = cloneDeep(defaultFilter)\n  }\n  getList()\n  popoverVisible.value = false\n}\n\n/**\n * 下一页\n */\nconst nextChatRecord = () => {\n  let index = tableIndexMap.value[currentChatId.value] + 1\n  if (index >= tableData.value.length) {\n    if (\n      index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=\n      paginationConfig.total - 1\n    ) {\n      return\n    }\n    paginationConfig.current_page = paginationConfig.current_page + 1\n    getList().then(() => {\n      index = 0\n      currentChatId.value = tableData.value[index].id\n      currentAbstract.value = tableData.value[index].abstract\n    })\n  } else {\n    currentChatId.value = tableData.value[index].id\n    currentAbstract.value = tableData.value[index].abstract\n  }\n}\nconst pre_disable = computed(() => {\n  const index = tableIndexMap.value[currentChatId.value] - 1\n  return index < 0 && paginationConfig.current_page <= 1\n})\n\nconst next_disable = computed(() => {\n  const index = tableIndexMap.value[currentChatId.value] + 1\n  return (\n    index >= tableData.value.length &&\n    index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=\n      paginationConfig.total - 1\n  )\n})\n/**\n * 上一页\n */\nconst preChatRecord = () => {\n  let index = tableIndexMap.value[currentChatId.value] - 1\n\n  if (index < 0) {\n    if (paginationConfig.current_page <= 1) {\n      return\n    }\n    paginationConfig.current_page = paginationConfig.current_page - 1\n    getList().then(() => {\n      index = paginationConfig.page_size - 1\n      currentChatId.value = tableData.value[index].id\n      currentAbstract.value = tableData.value[index].abstract\n    })\n  } else {\n    currentChatId.value = tableData.value[index].id\n    currentAbstract.value = tableData.value[index].abstract\n  }\n}\n\nfunction rowClickHandle(row: any, column?: any) {\n  if (column && column.type === 'selection') {\n    return\n  }\n  currentChatId.value = row.id\n  currentAbstract.value = row.abstract\n  ChatRecordRef.value.open()\n}\n\nconst setRowClass = ({ row }: any) => {\n  return currentChatId.value === row?.id ? 'highlight' : ''\n}\n\nconst handleSelectionChange = (val: any[]) => {\n  multipleSelection.value = val\n}\n\nfunction getList() {\n  const obj: any = {\n    start_time: daterange.value.start_time,\n    end_time: daterange.value.end_time,\n    ...filter.value,\n  }\n  if (search_form.value[search_type.value]) {\n    obj[search_type.value] = search_form.value[search_type.value]\n  }\n  return loadSharedApi({ type: 'chatLog', systemType: apiType.value })\n    .getChatLog(id as string, paginationConfig, obj, loading)\n    .then((res: any) => {\n      tableData.value = res.data.records\n      if (currentChatId.value) {\n        currentChatId.value = tableData.value[0]?.id\n      }\n      paginationConfig.total = res.data.total\n    })\n}\n\nfunction getDetail(isLoading = false) {\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .getApplicationDetail(id as string, isLoading ? loading : undefined)\n    .then((res: any) => {\n      detail.value = res.data\n      days.value = res.data.clean_time\n      file_days.value = res.data.file_clean_time\n    })\n}\n\nconst exportLog = () => {\n  const arr: string[] = []\n  multipleSelection.value.map((v) => {\n    if (v) {\n      arr.push(v.id)\n    }\n  })\n  if (detail.value) {\n    const obj: any = {\n      start_time: daterange.value.start_time,\n      end_time: daterange.value.end_time,\n      ...filter.value,\n    }\n    if (search_form.value[search_type.value]) {\n      obj[search_type.value] = search_form.value[search_type.value]\n    }\n    loadSharedApi({ type: 'chatLog', systemType: apiType.value }).postExportChatLog(\n      detail.value.id,\n      detail.value.name,\n      obj,\n      { select_ids: arr },\n      loading,\n    )\n  }\n}\n\nfunction refresh() {\n  getList()\n}\n\nfunction changeDayRangeHandle(val: string) {\n  daterange.value.start_time = val[0]\n  daterange.value.end_time = val[1]\n  getList()\n}\n\nfunction changeDayHandle(val: number | string) {\n  if (val !== 'other') {\n    daterange.value.start_time = beforeDay(val)\n    daterange.value.end_time = nowDate\n    getList()\n  }\n}\n\nfunction saveCleanTime() {\n  if (file_days.value > days.value) {\n    file_days.value = days.value\n  }\n  const obj = {\n    clean_time: days.value,\n    file_clean_time: file_days.value,\n  }\n  loadSharedApi({ type: 'application', systemType: apiType.value })\n    .putApplication(id as string, obj, loading)\n    .then(() => {\n      MsgSuccess(t('common.saveSuccess'))\n      dialogVisible.value = false\n      getDetail(true)\n    })\n    .catch(() => {\n      dialogVisible.value = false\n    })\n}\n\nconst SelectKnowledgeDocumentRef = ref()\nconst submitForm = async () => {\n  if (await SelectKnowledgeDocumentRef.value?.validate()) {\n    const arr: string[] = []\n    multipleSelection.value.map((v) => {\n      if (v) {\n        arr.push(v.id)\n      }\n    })\n    const obj = {\n      ...SelectKnowledgeDocumentRef.value.form,\n      chat_ids: arr,\n    }\n    loadSharedApi({ type: 'chatLog', systemType: apiType.value })\n      .postChatLogAddKnowledge(id, obj, documentLoading)\n      .then((res: any) => {\n        multipleTableRef.value?.clearSelection()\n        documentDialogVisible.value = false\n      })\n  }\n}\n\nfunction openDocumentDialog() {\n  SelectKnowledgeDocumentRef.value?.clearValidate()\n  documentDialogVisible.value = true\n}\n\nconst getSourceTypeName = (sourceType: SourceType | undefined) => {\n  if (!sourceType) return '-'\n\n  const translationKey = SOURCE_TYPE_TRANSLATIONS[sourceType]\n  return translationKey ? t(translationKey) : '-'\n}\n\nonMounted(() => {\n  changeDayHandle(history_day.value)\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped>\n.log-table {\n  :deep(tr) {\n    cursor: pointer;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/chat-user/index.vue",
    "content": "<template>\n  <div class=\"group p-16-24\">\n    <div class=\"mb-16\">\n      <h2>{{ $t('views.chatUser.title') }}</h2>\n      <div class=\"color-secondary\">\n        {{\n          resource.resource_type === SourceTypeEnum.APPLICATION\n            ? $t('views.chatUser.applicationTitleTip')\n            : $t('views.chatUser.knowledgeTitleTip')\n        }}\n      </div>\n    </div>\n    <el-card style=\"--el-card-padding: 0\">\n      <div class=\"flex\">\n        <div class=\"user-left border-r\">\n          <div class=\"p-24 pb-0\">\n            <h4 class=\"medium mb-12\">{{ $t('views.chatUser.group.title') }}</h4>\n            <el-input v-model=\"filterText\" :placeholder=\"$t('common.search')\" prefix-icon=\"Search\" clearable />\n          </div>\n          <div class=\"list-height-left\">\n            <el-scrollbar v-loading=\"loading\">\n              <div class=\"p-16\">\n                <common-list :data=\"filterList\" @click=\"clickUserGroup\" :default-active=\"current?.id\">\n                  <template #default=\"{ row }\">\n                    <span class=\"ellipsis-1\" :title=\"row.name\">{{ row.name }}</span>\n                  </template>\n                  <template #empty>\n                    <span></span>\n                  </template>\n                </common-list>\n              </div>\n            </el-scrollbar>\n          </div>\n        </div>\n\n        <!-- 右边 -->\n        <div class=\"user-right\" v-loading=\"rightLoading\">\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <h4 class=\"medium ellipsis\" :title=\"current?.name\">{{ current?.name || '-' }}</h4>\n              <el-divider direction=\"vertical\" class=\"mr-8 ml-8\" />\n\n              <el-icon class=\"color-input-placeholder\">\n                <UserFilled />\n              </el-icon>\n              <span class=\"color-input-placeholder ml-4\">\n                {{ paginationConfig.total }}\n              </span>\n            </div>\n\n            <div class=\"flex align-center\" v-if=\"\n              route.path.includes('share/')\n                ? false\n                : permissionObj[currentPermissionKey]\n            \">\n              <div class=\"color-secondary mr-8\">{{ $t('views.chatUser.autoAuthorization') }}</div>\n              <el-switch size=\"small\" :model-value=\"current?.is_auth\" @click=\"changeAuth\"\n                :loading=\"loading\"></el-switch>\n            </div>\n          </div>\n\n          <div class=\"flex-between mb-16\" style=\"margin-top: 18px\">\n            <div class=\"flex complex-search\">\n              <el-select class=\"complex-search__left\" v-model=\"searchType\" style=\"width: 120px\">\n                <el-option :label=\"$t('views.login.loginForm.username.label')\" value=\"username\" />\n                <el-option :label=\"$t('views.userManage.userForm.nick_name.label')\" value=\"nick_name\" />\n                <el-option :label=\"$t('views.userManage.source.label')\" value=\"source\" />\n              </el-select>\n              <el-input v-if=\"searchType === 'username'\" v-model=\"searchForm.username\" @change=\"getList\"\n                :placeholder=\"$t('common.inputPlaceholder')\" style=\"width: 220px\" clearable />\n              <el-input v-else-if=\"searchType === 'nick_name'\" v-model=\"searchForm.nick_name\" @change=\"getList\"\n                :placeholder=\"$t('common.inputPlaceholder')\" style=\"width: 220px\" clearable />\n              <el-select v-else-if=\"searchType === 'source'\" v-model=\"searchForm.source\" @change=\"getList\"\n                :placeholder=\"$t('common.selectPlaceholder')\" style=\"width: 220px\" clearable>\n                <el-option :label=\"$t('views.userManage.source.local')\" value=\"LOCAL\" />\n                <el-option label=\"CAS\" value=\"CAS\" />\n                <el-option label=\"LDAP\" value=\"LDAP\" />\n                <el-option label=\"OIDC\" value=\"OIDC\" />\n                <el-option label=\"OAuth2\" value=\"OAuth2\" />\n                <el-option :label=\"$t('views.userManage.source.wecom')\" value=\"wecom\" />\n                <el-option :label=\"$t('views.userManage.source.lark')\" value=\"lark\" />\n                <el-option :label=\"$t('views.userManage.source.dingtalk')\" value=\"dingtalk\" />\n              </el-select>\n            </div>\n            <el-button type=\"primary\" :disabled=\"current?.is_auth\" @click=\"handleSave\" v-if=\"\n              route.path.includes('share/')\n                ? false\n                : permissionObj[currentPermissionKey]\n            \">\n              {{ t('common.save') }}\n            </el-button>\n          </div>\n\n          <app-table :data=\"tableData\" :pagination-config=\"paginationConfig\" @sizeChange=\"handleSizeChange\"\n            @changePage=\"getList\" :maxTableHeight=\"350\">\n            <el-table-column prop=\"nick_name\" :label=\"$t('views.userManage.userForm.nick_name.label')\" />\n            <el-table-column prop=\"username\" :label=\"$t('views.login.loginForm.username.label')\" />\n            <el-table-column prop=\"source\" :label=\"$t('views.userManage.source.label')\">\n              <template #default=\"{ row }\">\n                {{\n                  row.source === 'LOCAL'\n                    ? $t('views.userManage.source.local')\n                    : row.source === 'wecom'\n                      ? $t('views.userManage.source.wecom')\n                      : row.source === 'lark'\n                        ? $t('views.userManage.source.lark')\n                        : row.source === 'dingtalk'\n                          ? $t('views.userManage.source.dingtalk')\n                          : row.source === 'OAUTH2' || row.source === 'OAuth2'\n                            ? 'OAuth2'\n                            : row.source\n                }}\n              </template>\n            </el-table-column>\n            <el-table-column :width=\"140\" align=\"center\">\n              <template #header>\n                <el-checkbox :model-value=\"allChecked\" :indeterminate=\"allIndeterminate\" :disabled=\"current?.is_auth\"\n                  @change=\"handleCheckAll\">{{ $t('views.chatUser.authorization') }}\n                </el-checkbox>\n              </template>\n              <template #default=\"{ row }\">\n                <el-checkbox v-model=\"row.is_auth\" :indeterminate=\"row.indeterminate\" :disabled=\"current?.is_auth\"\n                  @change=\"(value: boolean) => handleRowChange(value, row)\" />\n              </template>\n            </el-table-column>\n          </app-table>\n        </div>\n      </div>\n    </el-card>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, watch, reactive, computed } from 'vue'\n\nimport { t } from '@/locales'\nimport type { ChatUserGroupItem, ChatUserGroupUserItem } from '@/api/type/workspaceChatUser'\nimport { useRoute } from 'vue-router'\nimport { SourceTypeEnum } from '@/enums/common'\nimport { MsgSuccess } from '@/utils/message'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { RoleConst, PermissionConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\n\nconst route = useRoute()\n\nconst {\n  params: { id, folderId },\n} = route as any\n\nconst permissionObj = ref<any>({\n  APPLICATION: permissionMap['application']['workspace'].application_chat_user_edit(id),\n  KNOWLEDGE: permissionMap['knowledge']['workspace'].chat_user_edit(id),\n  RESOURCE_APPLICATION: hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_APPLICATION_CHAT_USER_EDIT], 'OR'),\n  RESOURCE_KNOWLEDGE: hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_CHAT_USER_EDIT], 'OR'),\n  SHAREDKNOWLEDGE: hasPermission(new ComplexPermission(\n    [RoleConst.ADMIN],\n    [PermissionConst.SHARED_KNOWLEDGE_CHAT_USER_EDIT],\n    [],\n    'OR',\n  ), 'OR')\n})\n\nconst currentPermissionKey = computed(() => {\n  if (route.path.includes('resource-management')) {\n    if (route.meta?.resourceType === 'KNOWLEDGE') {\n      return 'RESOURCE_KNOWLEDGE'\n    } else if (route.meta?.resourceType === 'APPLICATION') {\n      return 'RESOURCE_APPLICATION'\n    }\n  }\n  else if (route.path.includes('shared')) {return 'SHAREDKNOWLEDGE'}\n  else {\n    if (route.path.includes('knowledge/')) return 'KNOWLEDGE'\n    if (route.path.includes('application/')) return 'APPLICATION'\n  }\n  return route.meta?.resourceType as string\n})\n\nconst resource = reactive({\n  resource_id: route.params.id as string,\n  resource_type: route.meta.resourceType as string,\n})\n\nconst filterText = ref('')\nconst loading = ref(false)\nconst list = ref<ChatUserGroupItem[]>([])\nconst filterList = ref<ChatUserGroupItem[]>([]) // 搜索过滤后列表\nconst current = ref<ChatUserGroupItem>()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nasync function getUserGroupList() {\n  try {\n    const res = await loadSharedApi({\n      type: 'chatUser',\n      isShared: isShared.value,\n      systemType: apiType.value,\n    }).getUserGroupList(resource, loading)\n    list.value = res.data\n    filterList.value = filter(list.value, filterText.value)\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nonMounted(async () => {\n  await getUserGroupList()\n  current.value = list.value[0]\n})\n\nfunction filter(list: ChatUserGroupItem[], filterText: string) {\n  if (!filterText.length) {\n    return list\n  }\n  return list.filter((v: ChatUserGroupItem) =>\n    v.name.toLowerCase().includes(filterText.toLowerCase()),\n  )\n}\n\nwatch(filterText, (val: string) => {\n  filterList.value = filter(list.value, val)\n})\n\nconst checkedMap = reactive<Record<string, boolean>>({}) // 选中的\n\nfunction clickUserGroup(item: ChatUserGroupItem) {\n  // 清空跨组勾选缓存\n  for (const key in checkedMap) delete checkedMap[key]\n  current.value = item\n}\n\nasync function changeAuth() {\n  const params = [{ user_group_id: current.value?.id as string, is_auth: !current.value?.is_auth }]\n  try {\n    await loadSharedApi({\n      type: 'chatUser',\n      systemType: apiType.value,\n    }).editUserGroupList(resource, params, loading)\n    await getUserGroupList()\n    current.value = {\n      name: current.value?.name as string,\n      id: current.value?.id as string,\n      is_auth: !current.value?.is_auth,\n    }\n    getList()\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nconst rightLoading = ref(false)\n\nconst searchType = ref('username')\nconst searchForm = ref<Record<string, any>>({\n  username: '',\n  nick_name: '',\n  source: '',\n})\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst tableData = ref<ChatUserGroupUserItem[]>([])\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nasync function getList() {\n  if (!current.value?.id) return\n  const params: any = {}\n  const searchValue = searchForm.value[searchType.value as keyof typeof searchForm.value]\n  if (searchValue !== undefined && searchValue !== null && searchValue !== '') {\n    params[searchType.value] = searchValue\n  }\n  try {\n    const res = await loadSharedApi({\n      type: 'chatUser',\n      isShared: isShared.value,\n      systemType: apiType.value,\n    }).getUserGroupUserList(resource, current.value?.id, paginationConfig, params, rightLoading)\n    // 更新缓存和回显状态\n    res.data.records.forEach((item: any) => {\n      if (checkedMap[item.id] === undefined) {\n        checkedMap[item.id] = item.is_auth\n      }\n      item.is_auth = checkedMap[item.id]\n    })\n\n    tableData.value = res.data.records\n    paginationConfig.total = res.data.total\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getList()\n}\n\nwatch(\n  () => current.value?.id,\n  () => {\n    paginationConfig.current_page = 1\n    getList()\n  },\n)\n\nconst allChecked = computed(\n  () => tableData.value.length > 0 && tableData.value.every((item) => checkedMap[item.id]),\n)\n\nconst allIndeterminate = computed(\n  () => !allChecked.value && tableData.value.some((item) => checkedMap[item.id]),\n)\n\nconst handleCheckAll = (checked: boolean) => {\n  tableData.value.forEach((item) => {\n    item.is_auth = checked\n    checkedMap[item.id] = checked\n  })\n}\n\nconst handleRowChange = (value: boolean, row: ChatUserGroupUserItem) => {\n  row.is_auth = value\n  checkedMap[row.id] = value\n}\n\nasync function handleSave() {\n  try {\n    const params = Object.entries(checkedMap).map(([id, is_auth]) => ({\n      chat_user_id: id,\n      is_auth,\n    }))\n    await loadSharedApi({\n      type: 'chatUser',\n      systemType: apiType.value,\n    }).putUserGroupUser(resource, current.value?.id as string, params, rightLoading)\n    MsgSuccess(t('common.saveSuccess'))\n  } catch (error) {\n    console.error(error)\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.user-left {\n  box-sizing: border-box;\n  width: var(--setting-left-width);\n  min-width: var(--setting-left-width);\n\n  .list-height-left {\n    height: calc(100vh - 251px);\n  }\n}\n\n.user-right {\n  flex: 1;\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n  padding: 24px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/demo/index.vue",
    "content": "<template>\n  <div style=\"height: 500px\">\n    <DemoVue />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport DemoVue from '@/components/dynamics-form/Demo.vue'\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/document/ImportLarkDocument.vue",
    "content": "<template>\n  <div class=\"upload-document p-12-24\">\n    <div class=\"flex align-center mb-16\">\n      <back-button to=\"-1\" style=\"margin-left: -4px\"></back-button>\n      <h3 style=\"display: inline-block\">{{ $t('views.document.importDocument') }}</h3>\n    </div>\n    <el-card style=\"--el-card-padding: 0\">\n      <div class=\"upload-document__main flex\" v-loading=\"loading\">\n        <div class=\"upload-document__component main-calc-height\">\n          <div class=\"upload-component p-24\" style=\"min-width: 850px\">\n            <h4 class=\"title-decoration-1 mb-8\">\n              {{ $t('views.document.feishu.selectDocument') }}\n            </h4>\n            <el-form\n              ref=\"FormRef\"\n              :model=\"form\"\n              :rules=\"rules\"\n              label-position=\"top\"\n              require-asterisk-position=\"right\"\n            >\n              <div class=\"mt-16 mb-16\">\n                <el-radio-group v-model=\"form.fileType\" class=\"app-radio-button-group\">\n                  <el-radio-button value=\"txt\"\n                    >{{ $t('views.document.fileType.txt.label') }}\n                  </el-radio-button>\n                </el-radio-group>\n              </div>\n              <div class=\"update-info flex p-8-12 border-r-6 mb-16\">\n                <div class=\"mt-4\">\n                  <AppIcon iconName=\"app-warning-colorful\" style=\"font-size: 16px\"></AppIcon>\n                </div>\n                <div class=\"ml-16 lighter\">\n                  <p>{{ $t('views.document.feishu.tip1') }}</p>\n                  <p>{{ $t('views.document.feishu.tip2') }}</p>\n                </div>\n              </div>\n              <div class=\"card-never border-r-6 mb-16\">\n                <el-checkbox\n                  v-model=\"allCheck\"\n                  :label=\"$t('common.allCheck')\"\n                  size=\"large\"\n                  class=\"ml-24\"\n                  @change=\"handleAllCheckChange\"\n                />\n              </div>\n              <div style=\"height: calc(100vh - 450px)\">\n                <el-scrollbar>\n                  <el-tree\n                    :props=\"props\"\n                    :load=\"loadNode\"\n                    lazy\n                    show-checkbox\n                    node-key=\"token\"\n                    ref=\"treeRef\"\n                  >\n                    <template #default=\"{ node, data }\">\n                      <div class=\"flex align-center lighter\">\n                        <img\n                          src=\"@/assets/fileType/file-icon.svg\"\n                          alt=\"\"\n                          height=\"20\"\n                          v-if=\"data.type === 'folder'\"\n                        />\n                        <img\n                          src=\"@/assets/fileType/docx-icon.svg\"\n                          alt=\"\"\n                          height=\"22\"\n                          v-else-if=\"data.type === 'docx' || data.name.endsWith('.docx')\"\n                        />\n                        <img\n                          src=\"@/assets/fileType/xlsx-icon.svg\"\n                          alt=\"\"\n                          height=\"22\"\n                          v-else-if=\"data.type === 'sheet' || data.name.endsWith('.xlsx')\"\n                        />\n                        <img\n                          src=\"@/assets/fileType/xls-icon.svg\"\n                          alt=\"\"\n                          height=\"22\"\n                          v-else-if=\"data.name.endsWith('xls')\"\n                        />\n                        <img\n                          src=\"@/assets/fileType/csv-icon.svg\"\n                          alt=\"\"\n                          height=\"22\"\n                          v-else-if=\"data.name.endsWith('csv')\"\n                        />\n                        <img\n                          src=\"@/assets/fileType/pdf-icon.svg\"\n                          alt=\"\"\n                          height=\"22\"\n                          v-else-if=\"data.name.endsWith('.pdf')\"\n                        />\n                        <img\n                          src=\"@/assets/fileType/html-icon.svg\"\n                          alt=\"\"\n                          height=\"22\"\n                          v-else-if=\"data.name.endsWith('.html')\"\n                        />\n                        <img\n                          src=\"@/assets/fileType/txt-icon.svg\"\n                          alt=\"\"\n                          height=\"22\"\n                          v-else-if=\"data.name.endsWith('.txt')\"\n                        />\n                        <img\n                          src=\"@/assets/fileType/zip-icon.svg\"\n                          alt=\"\"\n                          height=\"22\"\n                          v-else-if=\"data.name.endsWith('.zip')\"\n                        />\n                        <img\n                          src=\"@/assets/fileType/md-icon.svg\"\n                          alt=\"\"\n                          height=\"22\"\n                          v-else-if=\"data.name.endsWith('.md')\"\n                        />\n\n                        <span class=\"ml-4\">{{ node.label }}</span>\n                      </div>\n                    </template>\n                  </el-tree>\n                </el-scrollbar>\n              </div>\n            </el-form>\n          </div>\n        </div>\n      </div>\n    </el-card>\n    <div class=\"upload-document__footer text-right border-t\">\n      <el-button @click=\"back\">{{ $t('common.cancel') }}</el-button>\n\n      <el-button @click=\"submit\" type=\"primary\" :disabled=\"disabled\">\n        {{ $t('views.document.buttons.import') }}\n      </el-button>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, computed, onUnmounted } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport { MsgConfirm, MsgSuccess, MsgWarning } from '@/utils/message'\nimport { t } from '@/locales'\nimport type { LoadFunction } from 'element-plus'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  params: { folderId },\n  query: { id, folder_token }, // id为knowledgeID，有id的是上传文档 folder_token为飞书文件夹token\n} = route\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst loading = ref(false)\nconst disabled = ref(false)\nconst allCheck = ref(false)\nconst treeRef = ref<any>(null)\n\ninterface Tree {\n  name: string\n  leaf?: boolean\n  type: string\n  token: string\n  is_exist: boolean\n}\n\nconst form = ref({\n  fileType: 'txt',\n  fileList: [] as any,\n})\n\nconst rules = reactive({\n  fileList: [\n    { required: true, message: t('views.document.upload.requiredMessage'), trigger: 'change' },\n  ],\n})\n\nconst props = {\n  label: 'name',\n  children: 'zones',\n  isLeaf: (data: any) => data.type !== 'folder',\n  disabled: (data: any) => data.is_exist,\n}\n\nconst loadNode: LoadFunction = (node, resolve) => {\n  const token = node.level === 0 ? folder_token : node.data.token // 根节点使用 folder_token，其他节点使用 node.data.token\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .getLarkDocumentList(id, token, {}, loading)\n    .then((res: any) => {\n      const nodes = res.data.files as Tree[]\n      resolve(nodes)\n      nodes.forEach((childNode) => {\n        if (childNode.is_exist) {\n          treeRef.value?.setChecked(childNode.token, true, false)\n        }\n      })\n    })\n\n    .catch((err: any) => {\n      console.error('Failed to load tree nodes:', err)\n    })\n}\n\nconst handleAllCheckChange = (checked: boolean) => {\n  if (checked) {\n    // 获取所有已加载的节点\n    const nodes = Object.values(treeRef.value?.store.nodesMap || {}) as any[]\n    nodes.forEach((node) => {\n      // 只选择未禁用且是文件的节点\n      if (!node.disabled) {\n        treeRef.value?.setChecked(node.data, true, false)\n      }\n    })\n  } else {\n    treeRef.value?.setCheckedKeys([])\n  }\n}\n\nfunction submit() {\n  loading.value = true\n  disabled.value = true\n  // 选中的节点的token\n  const checkedNodes = treeRef.value?.getCheckedNodes() || []\n  const filteredNodes = checkedNodes.filter((node: any) => !node.is_exist)\n  const newList = filteredNodes.map((node: any) => {\n    return {\n      name: node.name,\n      token: node.token,\n      type: node.type,\n    }\n  })\n  if (newList.length === 0) {\n    disabled.value = false\n    MsgWarning(t('views.document.feishu.errorMessage1'))\n    loading.value = false\n    return\n  }\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .importLarkDocument(id, newList, loading)\n    .then(() => {\n      MsgSuccess(t('views.document.tip.importMessage'))\n      disabled.value = false\n      back()\n    })\n    .catch((err: any) => {\n      console.error('Failed to load tree nodes:', err)\n    })\n    .finally(() => {\n      disabled.value = false\n    })\n  loading.value = false\n}\n\nfunction back() {\n  router.go(-1)\n}\n</script>\n<style lang=\"scss\">\n@use './index.scss';\n</style>\n"
  },
  {
    "path": "ui/src/views/document/ImportWorkflowDocument.vue",
    "content": "<template>\n  <div class=\"upload-document p-12-24\">\n    <div class=\"flex align-center mb-16\">\n      <back-button @click=\"back\" style=\"margin-left: -4px\"></back-button>\n      <h3 style=\"display: inline-block\">{{ $t('views.document.importDocument') }}</h3>\n    </div>\n    <el-card style=\"--el-card-padding: 0\">\n      <div class=\"upload-document__main flex\" v-loading=\"loading\">\n        <div class=\"upload-document__component main-calc-height\">\n          <el-scrollbar>\n            <div class=\"upload-component p-24\" style=\"min-width: 850px\">\n              <keep-alive :key=\"key\" :include=\"['data_source', 'knowledge_base']\">\n                <component\n                  ref=\"ActionRef\"\n                  :is=\"ak[active]\"\n                  v-model:loading=\"loading\"\n                  :workflow=\"_workflow\"\n                  :knowledge_id=\"id\"\n                  :id=\"action_id\"\n                ></component>\n              </keep-alive>\n            </div>\n          </el-scrollbar>\n        </div>\n      </div>\n    </el-card>\n    <div class=\"upload-document__footer text-right border-t\">\n      <el-button v-if=\"active == 'result'\" @click=\"continueImporting\">\n        {{ $t('views.document.buttons.continueImporting') }}\n      </el-button>\n      <el-button\n        v-if=\"base_form_list.length > 0 && active == 'knowledge_base'\"\n        :loading=\"loading\"\n        @click=\"up\"\n      >\n        {{ $t('common.steps.prev') }}</el-button\n      >\n      <el-button\n        v-if=\"base_form_list.length > 0 && active == 'data_source'\"\n        :disabled=\"loading\"\n        @click=\"next\"\n      >\n        {{ $t('common.steps.next') }}\n      </el-button>\n      <el-button\n        v-if=\"base_form_list.length > 0 ? active == 'knowledge_base' : active == 'data_source'\"\n        @click=\"upload\"\n        type=\"primary\"\n        :disabled=\"loading\"\n      >\n        {{ $t('views.document.buttons.import') }}\n      </el-button>\n      <el-button v-if=\"active == 'result'\" type=\"primary\" @click=\"goDocument\">{{\n        $t('views.knowledge.ResultSuccess.buttons.toDocument')\n      }}</el-button>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, provide, type Ref, onMounted, nextTick } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport DataSource from '@/views/knowledge-workflow/component/action/DataSource.vue'\nimport Result from '@/views/knowledge-workflow/component/action/Result.vue'\nimport applicationApi from '@/api/application/application'\nimport KnowledgeBase from '@/views/knowledge-workflow/component/action/KnowledgeBase.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { WorkflowType } from '@/enums/application'\nimport { ComplexPermission, Permission } from '@/utils/permission/type'\nimport { hasPermission } from '@/utils/permission'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nprovide('upload', (file: any, loading?: Ref<boolean>) => {\n  return applicationApi.postUploadFile(file, id as string, 'KNOWLEDGE', loading)\n})\nconst router = useRouter()\nconst route = useRoute()\nconst key = ref<number>(0)\nconst {\n  params: { folderId },\n  query: { id },\n  /*\n  id为knowledgeID\n  folderId 可以区分 resource-management shared还是 workspace\n  */\n} = route\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst ak = {\n  data_source: DataSource,\n  knowledge_base: KnowledgeBase,\n  result: Result,\n}\n\nconst loading = ref(false)\nconst ActionRef = ref()\nconst action_id = ref<string>()\nconst form_data = ref<any>({})\nconst active = ref<'data_source' | 'knowledge_base' | 'result'>('data_source')\nconst _workflow = ref<any>(null)\n\nconst base_form_list = computed(() => {\n  const kBase = _workflow.value?.nodes?.find((n: any) => n.type === WorkflowType.KnowledgeBase)\n  if (kBase) {\n    return kBase.properties.user_input_field_list\n  }\n  return []\n})\nconst next = () => {\n  ActionRef.value.validate().then(() => {\n    form_data.value[active.value] = ActionRef.value.get_data()\n    active.value = 'knowledge_base'\n  })\n}\nconst up = () => {\n  ActionRef.value.validate().then(() => {\n    active.value = 'data_source'\n  })\n}\nconst upload = () => {\n  ActionRef.value.validate().then(() => {\n    form_data.value[active.value] = ActionRef.value.get_data()\n    loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n      .workflowUpload(id, form_data.value, loading)\n      .then((ok: any) => {\n        action_id.value = ok.data.id\n        active.value = 'result'\n      })\n  })\n}\nfunction getDetail() {\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .getKnowledgeDetail(id, loading)\n    .then((res: any) => {\n      _workflow.value = res.data.work_flow\n    })\n}\nconst continueImporting = () => {\n  active.value = 'data_source'\n  key.value++\n  action_id.value = undefined\n  const c_workflow = _workflow.value\n  _workflow.value = null\n  form_data.value = {}\n  nextTick(() => {\n    _workflow.value = c_workflow\n  })\n}\nconst goDocument = () => {\n  router.push({ path: `/knowledge/${id}/${folderId}/4/document` })\n}\n\nconst back = () => {\n  if (route.path.includes('resource-management')) {\n    return router.push({ path: get_resource_management_route() })\n  } else if (route.path.includes('shared')) {\n    return router.push({ path: get_shared_route() })\n  } else {\n    return router.push({ path: get_route() })\n  }\n}\n\nconst get_shared_route = () => {\n  if (hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_READ], 'OR')) {\n    return `/knowledge/${id}/shared/4/document`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_PROBLEM_READ], 'OR')\n  ) {\n    return `/knowledge/${id}/shared/4/problem`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_HIT_TEST_READ], 'OR')\n  ) {\n    return `/knowledge/${id}/shared/4/hit-test`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_CHAT_USER_READ], 'OR')\n  ) {\n    return `/knowledge/${id}/shared/4/chat-user`\n  } else if (hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_EDIT], 'OR')) {\n    return `/knowledge/${id}/shared/4/setting`\n  } else {\n    return `/system/shared/knowledge`\n  }\n}\n\nconst get_resource_management_route = () => {\n  if (hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_READ], 'OR')) {\n    return `/knowledge/${id}/resource-management/4/document`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_PROBLEM_READ], 'OR')\n  ) {\n    return `/knowledge/${id}/resource-management/4/problem`\n  } else if (hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_HIT_TEST], 'OR')) {\n    return `/knowledge/${id}/resource-management/4/hit-test`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_CHAT_USER_READ], 'OR')\n  ) {\n    return `/knowledge/${id}/resource-management/4/chat-user`\n  } else if (hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_EDIT], 'OR')) {\n    return `/knowledge/${id}/resource-management/4/setting`\n  } else {\n    return `/system/resource-management/knowledge`\n  }\n}\n\nconst get_route = () => {\n  const checkPermission = (permissionConst: Permission) => {\n    return hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(id as string)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        permissionConst.getWorkspacePermissionWorkspaceManageRole,\n        permissionConst.getKnowledgeWorkspaceResourcePermission(id as string),\n      ],\n      'OR',\n    )\n  }\n  if (checkPermission(PermissionConst.KNOWLEDGE_DOCUMENT_READ)) {\n    return `/knowledge/${id}/${folderId}/4/document`\n  } else if (checkPermission(PermissionConst.KNOWLEDGE_PROBLEM_READ)) {\n    return `/knowledge/${id}/${folderId}/4/problem`\n  } else if (checkPermission(PermissionConst.KNOWLEDGE_HIT_TEST_READ)) {\n    return `/knowledge/${id}/${folderId}/4/hit-test`\n  } else if (checkPermission(PermissionConst.KNOWLEDGE_CHAT_USER_READ)) {\n    return `/knowledge/${id}/${folderId}/4/chat-user`\n  } else if (checkPermission(PermissionConst.KNOWLEDGE_EDIT)) {\n    return `/knowledge/${id}/${folderId}/4/setting`\n  } else {\n    return `/knowledge`\n  }\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\">\n@use './index.scss';\n</style>\n"
  },
  {
    "path": "ui/src/views/document/UploadDocument.vue",
    "content": "<template>\n  <div class=\"upload-document p-12-24\">\n    <div class=\"flex align-center mb-16\">\n      <back-button @click=\"back\" style=\"margin-left: -4px\"></back-button>\n      <h3 style=\"display: inline-block\">{{ $t('views.document.uploadDocument') }}</h3>\n    </div>\n    <el-card style=\"--el-card-padding: 0\">\n      <div class=\"upload-document__main flex\" v-loading=\"loading\">\n        <div class=\"upload-document__component main-calc-height\">\n          <el-scrollbar>\n            <template v-if=\"active === 0\">\n              <div class=\"upload-component p-24\">\n                <!-- 上传文档 -->\n                <UploadComponent ref=\"UploadComponentRef\" />\n              </div>\n            </template>\n            <template v-else-if=\"active === 1\">\n              <SetRules ref=\"SetRulesRef\" />\n            </template>\n            <template v-else-if=\"active === 2\">\n              <ResultSuccess :data=\"successInfo\" />\n            </template>\n          </el-scrollbar>\n        </div>\n      </div>\n    </el-card>\n    <div class=\"upload-document__footer text-right border-t\" v-if=\"active !== 2\">\n      <el-button @click=\"router.go(-1)\" :disabled=\"SetRulesRef?.loading || loading\">{{\n        $t('common.cancel')\n      }}</el-button>\n      <el-button @click=\"prev\" v-if=\"active === 1\" :disabled=\"SetRulesRef?.loading || loading\">{{\n        $t('common.steps.prev')\n      }}</el-button>\n      <el-button\n        @click=\"next\"\n        type=\"primary\"\n        v-if=\"active === 0\"\n        :disabled=\"SetRulesRef?.loading || loading\"\n      >\n        {{\n          documentsType === 'txt'\n            ? $t('common.steps.next')\n            : $t('views.document.buttons.import')\n        }}\n      </el-button>\n      <el-button\n        @click=\"submit\"\n        type=\"primary\"\n        v-if=\"active === 1\"\n        :disabled=\"SetRulesRef?.loading || loading\"\n      >\n        {{ $t('views.document.buttons.import') }}\n      </el-button>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onUnmounted } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport SetRules from './upload/SetRules.vue'\nimport ResultSuccess from './upload/ResultSuccess.vue'\nimport UploadComponent from './upload/UploadComponent.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { MsgConfirm, MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nconst { knowledge } = useStore()\nconst documentsFiles = computed(() => knowledge.documentsFiles)\nconst documentsType = computed(() => knowledge.documentsType)\n\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  params: { folderId, type },\n  query: { id },\n  /*\n  id为knowledgeID，有id的是上传文档; type为知识库类型的类型\n  folderId 可以区分 resource-management shared还是 workspace\n  */\n} = route\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst SetRulesRef = ref()\nconst UploadComponentRef = ref()\n\nconst loading = ref(false)\nconst disabled = ref(false)\nconst active = ref(0)\nconst successInfo = ref<any>(null)\nasync function next() {\n  disabled.value = true\n  if (await UploadComponentRef.value.validate()) {\n    if (documentsType.value === 'QA') {\n      const fd = new FormData()\n      documentsFiles.value.forEach((item: any) => {\n        if (item?.raw) {\n          fd.append('file', item?.raw)\n        }\n      })\n      if (id) {\n        // QA文档上传\n        loadSharedApi({ type: 'document', systemType: apiType.value })\n          .postQADocument(id as string, fd, loading)\n          .then(() => {\n            MsgSuccess(t('common.submitSuccess'))\n            clearStore()\n            router.push({\n              path: `/knowledge/${id}/${folderId}/${type}/document`,\n            })\n          })\n      }\n    } else if (documentsType.value === 'table') {\n      const fd = new FormData()\n      documentsFiles.value.forEach((item: any) => {\n        if (item?.raw) {\n          fd.append('file', item?.raw)\n        }\n      })\n      if (id) {\n        // table文档上传\n        loadSharedApi({ type: 'document', systemType: apiType.value })\n          .postTableDocument(id as string, fd, loading)\n          .then(() => {\n            MsgSuccess(t('common.submitSuccess'))\n            clearStore()\n            router.push({\n              path: `/knowledge/${id}/${folderId}/${type}/document`,\n            })\n          })\n      }\n    } else {\n      if (active.value++ > 2) active.value = 0\n    }\n  } else {\n    disabled.value = false\n  }\n}\nconst prev = () => {\n  active.value = 0\n}\n\nfunction clearStore() {\n  knowledge.saveDocumentsFile([])\n  knowledge.saveDocumentsType('')\n}\nfunction submit() {\n  loading.value = true\n  const documents = [] as any\n  SetRulesRef.value?.paragraphList.map((item: any) => {\n    if (!SetRulesRef.value?.checkedConnect) {\n      item.content.map((v: any) => {\n        delete v['problem_list']\n      })\n    }\n    documents.push({\n      name: item.name,\n      paragraphs: item.content,\n      source_file_id: item.source_file_id,\n    })\n  })\n\n  if (id) {\n    // 上传文档\n    loadSharedApi({ type: 'document', systemType: apiType.value })\n      .putMulDocument(id as string, documents)\n      .then(() => {\n        MsgSuccess(t('common.submitSuccess'))\n        clearStore()\n        router.push({\n          path: `/knowledge/${id}/${folderId}/${type}/document`,\n        })\n      })\n      .catch(() => {\n        loading.value = false\n      })\n  }\n}\nfunction back() {\n  if (documentsFiles.value?.length > 0) {\n    MsgConfirm(t('common.tip'), t('views.document.tip.saveMessage'), {\n      confirmButtonText: t('common.confirm'),\n    })\n      .then(() => {\n        router.go(-1)\n        clearStore()\n      })\n      .catch(() => {})\n  } else {\n    router.go(-1)\n  }\n}\nonUnmounted(() => {\n  clearStore()\n})\n</script>\n<style lang=\"scss\">\n@use './index.scss';\n</style>\n"
  },
  {
    "path": "ui/src/views/document/component/EmbeddingContentDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    :title=\"$t('components.selectParagraph.title')\"\n    :before-close=\"close\"\n    width=\"450\"\n  >\n    <el-radio-group v-model=\"state\" class=\"radio-block\">\n      <el-radio value=\"error\" size=\"large\">{{\n        $t('components.selectParagraph.error')\n      }}</el-radio>\n      <el-radio value=\"all\" size=\"large\">{{ $t('components.selectParagraph.all') }}</el-radio>\n    </el-radio-group>\n    <template #footer>\n      <div class=\"dialog-footer\">\n        <el-button @click=\"close\">{{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit\"> {{ $t('common.submit') }} </el-button>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nconst dialogVisible = ref<boolean>(false)\nconst state = ref<'all' | 'error'>('error')\nconst stateMap = {\n  all: ['0', '1', '2', '3', '4', '5', 'n'],\n  error: ['0', '1', '3', '4', '5', 'n']\n}\nconst submit_handle = ref<(stateList: Array<string>) => void>()\nconst submit = () => {\n  if (submit_handle.value) {\n    submit_handle.value(stateMap[state.value])\n  }\n  close()\n}\n\nconst open = (handle: (stateList: Array<string>) => void) => {\n  submit_handle.value = handle\n  dialogVisible.value = true\n}\nconst close = () => {\n  submit_handle.value = undefined\n  dialogVisible.value = false\n}\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/document/component/ImportDocumentDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"title\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    width=\"550\"\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"webFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item\n        :label=\"$t('views.document.form.source_url.label')\"\n        prop=\"source_url\"\n        v-if=\"isImport\"\n      >\n        <el-input\n          v-model=\"form.source_url\"\n          :placeholder=\"$t('views.document.form.source_url.placeholder')\"\n          :rows=\"10\"\n          type=\"textarea\"\n        />\n      </el-form-item>\n      <el-form-item\n        v-else-if=\"!isImport && documentType === 1\"\n        :label=\"$t('views.document.form.source_url.label')\"\n        prop=\"source_url\"\n      >\n        <el-input\n          v-model=\"form.source_url\"\n          :placeholder=\"$t('views.document.form.source_url.requiredMessage')\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('views.document.form.selector.label')\" v-if=\"documentType === 1\">\n        <el-input\n          v-model=\"form.selector\"\n          :placeholder=\"$t('views.document.form.selector.placeholder')\"\n        />\n      </el-form-item>\n      <el-form-item v-if=\"!isImport\">\n        <template #label>\n          <div class=\"flex align-center\">\n            <span class=\"mr-4\">{{ $t('views.document.form.hit_handling_method.label') }}</span>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('views.document.form.hit_handling_method.tooltip')\"\n              placement=\"right\"\n            >\n              <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n            </el-tooltip>\n          </div>\n        </template>\n        <el-radio-group v-model=\"form.hit_handling_method\" class=\"radio-block mt-4\">\n          <template v-for=\"(value, key) of hitHandlingMethod\" :key=\"key\">\n            <el-radio :value=\"key\">{{ $t(value) }} </el-radio>\n          </template>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item\n        prop=\"directly_return_similarity\"\n        v-if=\"!isImport && form.hit_handling_method === 'directly_return'\"\n      >\n        <div class=\"lighter w-full\" style=\"margin-top: -20px\">\n          <span>{{ $t('views.document.form.similarity.label') }}</span>\n          <el-input-number\n            v-model=\"form.directly_return_similarity\"\n            :min=\"0\"\n            :max=\"1\"\n            :precision=\"3\"\n            :step=\"0.1\"\n            :value-on-clear=\"0\"\n            controls-position=\"right\"\n            size=\"small\"\n            class=\"ml-4 mr-4\"\n          /><span>{{ $t('views.document.form.similarity.placeholder') }}</span>\n        </div>\n      </el-form-item>\n      <el-form-item prop=\"allow_download\">\n        <el-checkbox v-model=\"form.allow_download\">\n          {{ $t('views.document.form.allow_download.label') }}\n        </el-checkbox>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(webFormRef)\" :loading=\"loading\">\n          {{ $t('common.confirm') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { hitHandlingMethod } from '@/enums/document'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { t } from '@/locales'\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\n\nconst props = defineProps({\n  title: String,\n})\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst emit = defineEmits(['refresh'])\nconst webFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isImport = ref<boolean>(false)\nconst form = ref<any>({\n  source_url: '',\n  selector: '',\n  hit_handling_method: 'optimization',\n  directly_return_similarity: 0.9,\n  allow_download: true,\n})\n\n// 文档设置\nconst documentId = ref('')\nconst documentType = ref<string | number>('') //文档类型：1: web文档；0:普通文档\n\n// 批量设置\nconst documentList = ref<Array<string>>([])\n\nconst rules = reactive({\n  source_url: [\n    {\n      required: true,\n      message: t('views.document.form.source_url.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  directly_return_similarity: [\n    {\n      required: true,\n      message: t('views.document.form.similarity.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      source_url: '',\n      selector: '',\n      hit_handling_method: 'optimization',\n      directly_return_similarity: 0.9,\n      allow_download: true,\n    }\n    isImport.value = false\n    documentType.value = ''\n    documentId.value = ''\n    documentList.value = []\n  }\n})\n\nconst open = (row: any, list: Array<string>) => {\n  if (row) {\n    documentType.value = row.type\n    documentId.value = row.id\n    form.value = {\n      hit_handling_method: row.hit_handling_method,\n      directly_return_similarity: row.directly_return_similarity,\n      ...row.meta,\n      meta: row.meta,\n    }\n    isImport.value = false\n  } else if (list) {\n    // 批量设置\n    documentList.value = list\n  } else {\n    // 导入 只有web文档类型\n    documentType.value = 1\n    isImport.value = true\n  }\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      if (isImport.value) {\n        const obj = {\n          source_url_list: form.value.source_url.split('\\n'),\n          selector: form.value.selector,\n          allow_download: form.value.allow_download,\n        }\n        loadSharedApi({ type: 'document', systemType: apiType.value })\n          .postWebDocument(id, obj, loading)\n          .then(() => {\n            MsgSuccess(t('views.document.tip.importMessage'))\n            emit('refresh')\n            dialogVisible.value = false\n          })\n      } else {\n        if (documentId.value) {\n          const obj = {\n            hit_handling_method: form.value.hit_handling_method,\n            directly_return_similarity: form.value.directly_return_similarity,\n            // 飞书文档需要传递meta信息，不能被页面上的form覆盖\n            meta: {\n              ...form.value.meta,\n              ...{\n                source_url: form.value.source_url,\n                selector: form.value.selector,\n                allow_download: form.value.allow_download,\n              },\n            },\n          }\n          loadSharedApi({ type: 'document', systemType: apiType.value })\n            .putDocument(id, documentId.value, obj, loading)\n            .then(() => {\n              MsgSuccess(t('common.settingSuccess'))\n              emit('refresh')\n              dialogVisible.value = false\n            })\n        } else if (documentList.value.length > 0) {\n          // 批量设置\n          const obj = {\n            hit_handling_method: form.value.hit_handling_method,\n            directly_return_similarity: form.value.directly_return_similarity,\n            id_list: documentList.value,\n            allow_download: form.value.allow_download,\n          }\n          loadSharedApi({ type: 'document', systemType: apiType.value })\n            .putBatchEditHitHandling(id, obj, loading)\n            .then(() => {\n              MsgSuccess(t('common.settingSuccess'))\n              emit('refresh')\n              dialogVisible.value = false\n            })\n        }\n      }\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/document/component/SelectKnowledgeDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"`${$t('views.document.migrateDocument')}`\"\n    v-model=\"dialogVisible\"\n    width=\"600\"\n    class=\"select-knowledge-dialog\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form ref=\"FormRef\" :model=\"form\" label-position=\"top\" require-asterisk-position=\"right\">\n      <el-form-item :label=\"$t('views.chatLog.selectKnowledge')\" required>\n        <el-tree-select\n          v-model=\"form.selectKnowledge\"\n          :props=\"defaultProps\"\n          node-key=\"id\"\n          lazy\n          :load=\"loadTree\"\n          :placeholder=\"$t('views.chatLog.selectKnowledgePlaceholder')\"\n        >\n          <template #default=\"{ data }\">\n            <div class=\"flex align-center\">\n              <KnowledgeIcon\n                class=\"mr-12\"\n                :size=\"20\"\n                v-if=\"data.resource_type !== 'folder'\"\n                :type=\"data.type\"\n              />\n              <el-avatar v-else class=\"mr-12\" shape=\"square\" :size=\"20\" style=\"background: none\">\n                <img\n                  src=\"@/assets/knowledge/icon_file-folder_colorful.svg\"\n                  style=\"width: 100%\"\n                  alt=\"\"\n                />\n              </el-avatar>\n\n              {{ data.name }}\n            </div>\n          </template>\n        </el-tree-select>\n      </el-form-item>\n    </el-form>\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button\n          type=\"primary\"\n          @click=\"submitHandle\"\n          :disabled=\"!form.selectKnowledge || loading\"\n        >\n          {{ $t('common.confirm') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport useStore from '@/stores'\nconst route = useRoute()\nconst {\n  params: { id }, // id为knowledgeID\n} = route as any\n\nconst { user } = useStore()\n\nconst props = defineProps({\n  workspaceId: {\n    type: String,\n  },\n})\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst emit = defineEmits(['refresh'])\n\nconst loading = ref<boolean>(false)\n\nconst dialogVisible = ref<boolean>(false)\nconst knowledgeList = ref<any>([])\nconst documentList = ref<any>([])\nconst form = ref<any>({\n  selectKnowledge: '',\n})\n\nconst defaultProps = {\n  children: 'children',\n  label: 'name',\n  isLeaf: (data: any) =>\n    data.resource_type ? data.resource_type !== 'folder' : data.workspace_id === 'None',\n  disabled: (data: any, node: any) => {\n    return data.id === id || (data.resource_type === 'folder' && node?.isLeaf)\n  },\n}\n\nconst loadTree = async (node: any, resolve: any) => {\n  if (node.isLeaf) return resolve([])\n  const folder_id = node.level === 0 ? user.getWorkspaceId() : node.data.id\n  const obj =\n   apiType.value === 'systemManage'\n      ? {\n          workspace_id: props.workspaceId,\n          folder_id:  node.level === 0 ? props.workspaceId : node.data.id,\n        }\n      : {\n          folder_id: folder_id,\n        }\n  await loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .getKnowledgeList(obj, loading)\n    .then((res: any) => {\n      resolve(res.data)\n    })\n}\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value.selectKnowledge = ''\n    knowledgeList.value = []\n    documentList.value = []\n  }\n})\n\nconst open = (list: any) => {\n  documentList.value = list\n  dialogVisible.value = true\n}\n\nconst submitHandle = () => {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .putMigrateMulDocument(id, form.value.selectKnowledge, documentList.value, loading)\n    .then(() => {\n      emit('refresh')\n      dialogVisible.value = false\n    })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/document/component/Status.vue",
    "content": "<template>\n  <el-popover\n    v-model:visible=\"visible\"\n    placement=\"top\"\n    trigger=\"hover\"\n    :popper-style=\"{ width: 'auto' }\"\n    :persistent=\"false\"\n  >\n    <template #default\n      ><StatusTable\n        v-if=\"visible\"\n        :status=\"status\"\n        :statusMeta=\"statusMeta\"\n        :taskTypeMap=\"taskTypeMap\"\n        :stateMap=\"stateMap\"\n      ></StatusTable>\n    </template>\n    <template #reference>\n      <el-text\n        class=\"color-text-primary\"\n        v-if=\"aggStatus?.value === State.SUCCESS || aggStatus?.value === State.REVOKED\"\n      >\n        <el-icon class=\"color-success\"><SuccessFilled /></el-icon>\n        {{ stateMap[aggStatus.value](aggStatus.key) }}\n      </el-text>\n      <el-text class=\"color-text-primary\" v-else-if=\"aggStatus?.value === State.FAILURE\">\n        <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n        {{ stateMap[aggStatus.value](aggStatus.key) }}\n      </el-text>\n      <el-text class=\"color-text-primary\" v-else-if=\"aggStatus?.value === State.STARTED\">\n        <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n        {{ stateMap[aggStatus.value](aggStatus.key) }}\n      </el-text>\n      <el-text class=\"color-text-primary\" v-else-if=\"aggStatus?.value === State.PENDING\">\n        <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n        {{ stateMap[aggStatus.value](aggStatus.key) }}\n      </el-text>\n      <el-text class=\"color-text-primary\" v-else-if=\"aggStatus?.value === State.REVOKE\">\n        <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n        {{ stateMap[aggStatus.value](aggStatus.key) }}\n      </el-text>\n    </template>\n  </el-popover>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { TaskType, State } from '@/utils/status'\nimport StatusTable from '@/views/document/component/StatusTable.vue'\nimport { t } from '@/locales'\nconst props = defineProps<{ status: string; statusMeta: any }>()\nconst visible = ref<boolean>(false)\nconst checkList: Array<string> = [\n  State.REVOKE,\n  State.STARTED,\n  State.PENDING,\n  State.FAILURE,\n  State.REVOKED,\n  State.SUCCESS,\n]\nconst aggStatus = computed(() => {\n  let obj = { key: 0, value: '' }\n  for (const i in checkList) {\n    const state = checkList[i]\n    const index = props.status.indexOf(state)\n    if (index > -1) {\n      obj = { key: props.status.length - index, value: state }\n      break\n    }\n  }\n  return obj\n})\nconst startedMap = {\n  [TaskType.EMBEDDING]: t('views.document.fileStatus.EMBEDDING'),\n  [TaskType.GENERATE_PROBLEM]: t('views.document.fileStatus.GENERATE'),\n  [TaskType.SYNC]: t('views.document.fileStatus.SYNC'),\n}\nconst taskTypeMap = {\n  [TaskType.EMBEDDING]: t('views.knowledge.setting.vectorization'),\n  [TaskType.GENERATE_PROBLEM]: t('views.document.generateQuestion.title'),\n  [TaskType.SYNC]: t('views.knowledge.setting.sync'),\n}\nconst stateMap: any = {\n  [State.PENDING]: (type: number) => t('views.document.fileStatus.PENDING'),\n  [State.STARTED]: (type: number) => startedMap[type],\n  [State.REVOKE]: (type: number) => t('common.status.REVOKE'),\n  [State.REVOKED]: (type: number) => t('common.status.success'),\n  [State.FAILURE]: (type: number) => t('common.status.fail'),\n  [State.SUCCESS]: (type: number) => t('common.status.success'),\n}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/document/component/StatusTable.vue",
    "content": "<template>\n  <div v-for=\"status in statusTable\" :key=\"status.type\">\n    <span> {{ taskTypeMap[status.type] }}：</span>\n    <span>\n      <el-text\n        class=\"color-text-primary\"\n        v-if=\"status.state === State.SUCCESS || status.state === State.REVOKED\"\n      >\n        <el-icon class=\"color-success\"><SuccessFilled /></el-icon>\n        {{ stateMap[status.state](status.type) }}\n      </el-text>\n      <el-text class=\"color-text-primary\" v-else-if=\"status.state === State.FAILURE\">\n        <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n        {{ stateMap[status.state](status.type) }}\n      </el-text>\n      <el-text class=\"color-text-primary\" v-else-if=\"status.state === State.STARTED\">\n        <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n        {{ stateMap[status.state](status.type) }}\n      </el-text>\n      <el-text class=\"color-text-primary\" v-else-if=\"status.state === State.PENDING\">\n        <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n        {{ stateMap[status.state](status.type) }}\n      </el-text>\n      <el-text class=\"color-text-primary\" v-else-if=\"status.state === State.REVOKE\">\n        <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n        {{ stateMap[status.state](status.type) }}\n      </el-text>\n    </span>\n    <span\n      class=\"ml-8 lighter\"\n      :style=\"{ color: [State.FAILURE, State.REVOKED].includes(status.state) ? '#F54A45' : '' }\"\n    >\n      {{ $t('views.document.fileStatus.finish') }}\n      {{\n        Object.keys(status.aggs ? status.aggs : {})\n          .filter((k) => k == State.SUCCESS)\n          .map((k) => status.aggs[k])\n          .reduce((x: any, y: any) => x + y, 0)\n      }}/{{\n        Object.values(status.aggs ? status.aggs : {}).reduce((x: any, y: any) => x + y, 0)\n      }}</span\n    >\n    <el-text type=\"info\" class=\"ml-12\">\n      {{\n        status.time\n          ? status.time[status.state == State.REVOKED ? State.REVOKED : State.PENDING]?.substring(\n              0,\n              19,\n            )\n          : undefined\n      }}\n    </el-text>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { Status, TaskType, State, type TaskTypeInterface } from '@/utils/status'\nimport { mergeWith } from 'lodash'\nconst props = defineProps<{ status: string; statusMeta: any; stateMap: any; taskTypeMap: any }>()\n\nconst parseAgg = (agg: { count: number; status: string }) => {\n  const status = new Status(agg.status)\n  return Object.keys(TaskType)\n    .map((key) => {\n      const value = TaskType[key as keyof TaskTypeInterface]\n      return { [value]: { [status.task_status[value]]: agg.count } }\n    })\n    .reduce((x, y) => ({ ...x, ...y }), {})\n}\n\nconst customizer: (x: any, y: any) => any = (objValue: any, srcValue: any) => {\n  if (objValue == undefined && srcValue) {\n    return srcValue\n  }\n  if (srcValue == undefined && objValue) {\n    return objValue\n  }\n  // 如果是数组，我们将元素进行聚合\n  if (typeof objValue === 'object' && typeof srcValue === 'object') {\n    // 若是object类型的对象，我们进行递归\n    return mergeWith(objValue, srcValue, customizer)\n  } else {\n    // 否则，单纯的将值进行累加\n    return objValue + srcValue\n  }\n}\nconst aggs = computed(() => {\n  return (props.statusMeta.aggs ? props.statusMeta.aggs : [])\n    .map((agg: any) => {\n      return parseAgg(agg)\n    })\n    .reduce((x: any, y: any) => {\n      return mergeWith(x, y, customizer)\n    }, {})\n})\n\nconst statusTable = computed(() => {\n  return Object.keys(TaskType)\n    .map((key) => {\n      const value = TaskType[key as keyof TaskTypeInterface]\n      const parseStatus = new Status(props.status)\n      return {\n        type: value,\n        state: parseStatus.task_status[value],\n        aggs: aggs.value[value],\n        time: props.statusMeta.state_time[value],\n      }\n    })\n    .filter((item) => item.state !== State.IGNORED)\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/document/index.scss",
    "content": ".upload-document {\n  &__component {\n    width: 100%;\n    margin: 0 auto;\n    overflow: hidden;\n  }\n  &__footer {\n    padding: 16px 24px;\n    position: fixed;\n    bottom: 0;\n    left: 0;\n    background: #ffffff;\n    width: 100%;\n    box-sizing: border-box;\n  }\n  .upload-component {\n    width: 70%;\n    margin: 0 auto;\n    margin-bottom: 20px;\n  }\n}\n"
  },
  {
    "path": "ui/src/views/document/index.vue",
    "content": "<template>\n  <div class=\"document p-16-24\">\n    <h2 class=\"mb-16\">{{ $t('common.fileUpload.document') }}</h2>\n    <el-card style=\"--el-card-padding: 0\">\n      <div class=\"main-calc-height\">\n        <div class=\"p-24\">\n          <div class=\"flex-between\">\n            <div>\n              <template v-if=\"!isShared\">\n                <el-button\n                  v-if=\"knowledgeDetail?.type === 0 && permissionPrecise.doc_create(id)\"\n                  type=\"primary\"\n                  @click=\"\n                    router.push({\n                      path: `/knowledge/document/upload/${folderId}/${type}`,\n                      query: { id: id },\n                    })\n                  \"\n                  >{{ $t('views.document.uploadDocument') }}\n                </el-button>\n                <el-button\n                  v-if=\"knowledgeDetail?.type === 1 && permissionPrecise.doc_create(id)\"\n                  type=\"primary\"\n                  @click=\"importDoc\"\n                  >{{ $t('views.document.importDocument') }}\n                </el-button>\n                <el-button\n                  v-if=\"knowledgeDetail?.type === 2 && permissionPrecise.doc_create(id)\"\n                  type=\"primary\"\n                  @click=\"\n                    router.push({\n                      path: `/knowledge/import/lark/${folderId}`,\n                      query: {\n                        id: id,\n                        folder_token: knowledgeDetail?.meta.folder_token,\n                      },\n                    })\n                  \"\n                  >{{ $t('views.document.importDocument') }}\n                </el-button>\n                <el-button\n                  v-if=\"knowledgeDetail?.type === 4 && permissionPrecise.doc_create(id)\"\n                  type=\"primary\"\n                  @click=\"toImportWorkflow\"\n                  >{{ $t('views.document.importDocument') }}\n                </el-button>\n                <el-button\n                  @click=\"batchRefresh\"\n                  :disabled=\"multipleSelection.length === 0\"\n                  v-if=\"permissionPrecise.doc_vector(id)\"\n                  >{{ $t('views.knowledge.setting.vectorization') }}\n                </el-button>\n                <el-button\n                  @click=\"openGenerateDialog()\"\n                  :disabled=\"multipleSelection.length === 0\"\n                  v-if=\"permissionPrecise.doc_generate(id)\"\n                  >{{ $t('views.document.generateQuestion.title') }}\n                </el-button>\n                <el-button\n                  @click=\"openBatchEditDocument\"\n                  :disabled=\"multipleSelection.length === 0\"\n                  v-if=\"permissionPrecise.doc_edit(id)\"\n                >\n                  {{ $t('common.setting') }}\n                </el-button>\n\n                <el-dropdown v-if=\"MoreFilledPermission0(id)\">\n                  <el-button class=\"ml-12 mr-12\">\n                    <AppIcon iconName=\"app-more\"></AppIcon>\n                  </el-button>\n                  <template #dropdown>\n                    <el-dropdown-menu>\n                      <el-dropdown-item\n                        @click=\"openknowledgeDialog()\"\n                        :disabled=\"multipleSelection.length === 0\"\n                        v-if=\"permissionPrecise.doc_migrate(id)\"\n                      >\n                        {{ $t('views.document.setting.migration') }}\n                      </el-dropdown-item>\n                      <el-dropdown-item\n                        @click=\"openAddTagDialog()\"\n                        :disabled=\"multipleSelection.length === 0\"\n                        v-if=\"permissionPrecise.doc_tag(id)\"\n                        >{{ $t('views.document.tag.addTag') }}\n                      </el-dropdown-item>\n                      <el-dropdown-item\n                        divided\n                        @click=\"syncMulDocument\"\n                        :disabled=\"multipleSelection.length === 0\"\n                        v-if=\"knowledgeDetail?.type === 1 && permissionPrecise.doc_sync(id)\"\n                        >{{ $t('views.document.syncDocument') }}\n                      </el-dropdown-item>\n                      <el-dropdown-item\n                        divided\n                        @click=\"syncLarkMulDocument\"\n                        :disabled=\"multipleSelection.length === 0\"\n                        v-if=\"knowledgeDetail?.type === 2 && permissionPrecise.doc_sync(id)\"\n                        >{{ $t('views.document.syncDocument') }}\n                      </el-dropdown-item>\n                      <el-dropdown-item\n                        @click=\"exportMulDocument\"\n                        :disabled=\"multipleSelection.length === 0\"\n                        v-if=\"permissionPrecise.doc_export(id)\"\n                      >\n                        {{ $t('views.document.setting.export') }} Excel\n                      </el-dropdown-item>\n                      <el-dropdown-item\n                        @click=\"exportMulDocumentZip\"\n                        :disabled=\"multipleSelection.length === 0\"\n                        v-if=\"permissionPrecise.doc_export(id)\"\n                      >\n                        {{ $t('views.document.setting.export') }} Zip\n                      </el-dropdown-item>\n\n                      <el-dropdown-item\n                        divided\n                        @click=\"deleteMulDocument\"\n                        :disabled=\"multipleSelection.length === 0\"\n                        v-if=\"permissionPrecise.doc_delete(id)\"\n                        >{{ $t('common.delete') }}\n                      </el-dropdown-item>\n                    </el-dropdown-menu>\n                  </template>\n                </el-dropdown>\n              </template>\n            </div>\n            <div class=\"flex\">\n              <div class=\"flex-between complex-search\">\n                <el-select\n                  class=\"complex-search__left\"\n                  v-model=\"search_type\"\n                  style=\"width: 120px\"\n                  @change=\"search_type_change\"\n                >\n                  <el-option :label=\"$t('common.name')\" value=\"name\" />\n                </el-select>\n                <el-input\n                  v-if=\"search_type === 'name'\"\n                  v-model=\"search_form.name\"\n                  @change=\"refresh\"\n                  :placeholder=\"$t('common.searchBar.placeholder')\"\n                  style=\"width: 220px\"\n                  clearable\n                />\n              </div>\n\n              <el-tooltip\n                effect=\"dark\"\n                :content=\"$t('common.ExecutionRecord.title')\"\n                placement=\"top\"\n                v-if=\"knowledgeDetail?.type === 4 && permissionPrecise.doc_create(id)\"\n              >\n                <el-button @click=\"openListAction\" class=\"ml-12\">\n                  <AppIcon iconName=\"app-execution-record\" class=\"color-secondary\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n              <el-button @click=\"openTagDrawer\" class=\"ml-12\" v-if=\"permissionPrecise.tag_read(id)\">\n                {{ $t('views.document.tag.label') }}\n              </el-button>\n            </div>\n          </div>\n          <app-table\n            ref=\"multipleTableRef\"\n            class=\"mt-16 document-table\"\n            :data=\"documentData\"\n            :pagination-config=\"paginationConfig\"\n            :quick-create=\"\n              knowledgeDetail?.type === 0 && permissionPrecise.doc_create(id) && !isShared\n            \"\n            @sizeChange=\"handleSizeChange\"\n            @changePage=\"getList\"\n            @cell-mouse-enter=\"cellMouseEnter\"\n            @cell-mouse-leave=\"cellMouseLeave\"\n            @creatQuick=\"creatQuickHandle\"\n            @row-click=\"rowClickHandle\"\n            @selection-change=\"handleSelectionChange\"\n            @sort-change=\"handleSortChange\"\n            v-loading=\"loading\"\n            :row-key=\"(row: any) => row.id\"\n            :storeKey=\"storeKey\"\n            @cell-click=\"cellClickHandle\"\n          >\n            <el-table-column\n              type=\"selection\"\n              width=\"55\"\n              :reserve-selection=\"true\"\n              v-if=\"!isShared\"\n            />\n            <el-table-column prop=\"name\" :label=\"$t('views.document.table.name')\" min-width=\"280\">\n              <template #default=\"{ row }\">\n                <ReadWrite\n                  v-if=\"!isShared\"\n                  @change=\"editName($event, row.id)\"\n                  :data=\"row.name\"\n                  :showEditIcon=\"row.id === currentMouseId\"\n                />\n                <span v-else>{{ row.name }}</span>\n              </template>\n            </el-table-column>\n            <el-table-column\n              prop=\"status\"\n              :label=\"$t('views.document.fileStatus.label')\"\n              width=\"120\"\n            >\n              <template #header>\n                <div>\n                  <span>{{ $t('views.document.fileStatus.label') }}</span>\n                  <el-dropdown trigger=\"click\" @command=\"dropdownHandle\">\n                    <el-button\n                      style=\"margin-top: 1px\"\n                      link\n                      :type=\"filterMethod['status'] ? 'primary' : ''\"\n                    >\n                      <el-icon>\n                        <Filter />\n                      </el-icon>\n                    </el-button>\n                    <template #dropdown>\n                      <el-dropdown-menu style=\"width: 100px\">\n                        <el-dropdown-item\n                          :class=\"filterMethod['status'] ? '' : 'is-active'\"\n                          :command=\"beforeCommand('status', '')\"\n                          class=\"justify-center\"\n                          >{{ $t('common.status.all') }}\n                        </el-dropdown-item>\n                        <el-dropdown-item\n                          :class=\"filterMethod['status'] === State.SUCCESS ? 'is-active' : ''\"\n                          class=\"justify-center\"\n                          :command=\"beforeCommand('status', State.SUCCESS)\"\n                          >{{ $t('common.status.success') }}\n                        </el-dropdown-item>\n                        <el-dropdown-item\n                          :class=\"filterMethod['status'] === State.FAILURE ? 'is-active' : ''\"\n                          class=\"justify-center\"\n                          :command=\"beforeCommand('status', State.FAILURE)\"\n                          >{{ $t('common.status.fail') }}\n                        </el-dropdown-item>\n                        <el-dropdown-item\n                          :class=\"\n                            filterMethod['status'] === State.STARTED &&\n                            filterMethod['task_type'] == TaskType.EMBEDDING\n                              ? 'is-active'\n                              : ''\n                          \"\n                          class=\"justify-center\"\n                          :command=\"beforeCommand('status', State.STARTED, TaskType.EMBEDDING)\"\n                          >{{ $t('views.document.fileStatus.EMBEDDING') }}\n                        </el-dropdown-item>\n                        <el-dropdown-item\n                          :class=\"filterMethod['status'] === State.PENDING ? 'is-active' : ''\"\n                          class=\"justify-center\"\n                          :command=\"beforeCommand('status', State.PENDING)\"\n                          >{{ $t('views.document.fileStatus.PENDING') }}\n                        </el-dropdown-item>\n                        <el-dropdown-item\n                          :class=\"\n                            filterMethod['status'] === State.STARTED &&\n                            filterMethod['task_type'] === TaskType.GENERATE_PROBLEM\n                              ? 'is-active'\n                              : ''\n                          \"\n                          class=\"justify-center\"\n                          :command=\"\n                            beforeCommand('status', State.STARTED, TaskType.GENERATE_PROBLEM)\n                          \"\n                          >{{ $t('views.document.fileStatus.GENERATE') }}\n                        </el-dropdown-item>\n                      </el-dropdown-menu>\n                    </template>\n                  </el-dropdown>\n                </div>\n              </template>\n              <template #default=\"{ row }\">\n                <StatusValue :status=\"row.status\" :status-meta=\"row.status_meta\"></StatusValue>\n              </template>\n            </el-table-column>\n            <el-table-column\n              prop=\"char_length\"\n              :label=\"$t('views.document.table.char_length')\"\n              align=\"right\"\n              min-width=\"120\"\n              sortable\n            >\n              <template #default=\"{ row }\">\n                {{ numberFormat(row.char_length) }}\n              </template>\n            </el-table-column>\n            <el-table-column\n              prop=\"paragraph_count\"\n              :label=\"$t('views.document.table.paragraph')\"\n              align=\"right\"\n              min-width=\"120\"\n              sortable\n            />\n\n            <el-table-column width=\"110\">\n              <template #header>\n                <div>\n                  <span>{{ $t('views.document.enableStatus.label') }}</span>\n                  <el-dropdown trigger=\"click\" @command=\"dropdownHandle\">\n                    <el-button\n                      style=\"margin-top: 1px\"\n                      link\n                      :type=\"filterMethod['is_active'] ? 'primary' : ''\"\n                    >\n                      <el-icon>\n                        <Filter />\n                      </el-icon>\n                    </el-button>\n                    <template #dropdown>\n                      <el-dropdown-menu style=\"width: 100px\">\n                        <el-dropdown-item\n                          :class=\"filterMethod['is_active'] === '' ? 'is-active' : ''\"\n                          :command=\"beforeCommand('is_active', '')\"\n                          class=\"justify-center\"\n                          >{{ $t('common.status.all') }}\n                        </el-dropdown-item>\n                        <el-dropdown-item\n                          :class=\"filterMethod['is_active'] === true ? 'is-active' : ''\"\n                          class=\"justify-center\"\n                          :command=\"beforeCommand('is_active', true)\"\n                          >{{ $t('common.status.enabled') }}\n                        </el-dropdown-item>\n                        <el-dropdown-item\n                          :class=\"filterMethod['is_active'] === false ? 'is-active' : ''\"\n                          class=\"justify-center\"\n                          :command=\"beforeCommand('is_active', false)\"\n                          >{{ $t('common.status.disabled') }}\n                        </el-dropdown-item>\n                      </el-dropdown-menu>\n                    </template>\n                  </el-dropdown>\n                </div>\n              </template>\n              <template #default=\"{ row }\">\n                <div v-if=\"row.is_active\" class=\"flex align-center\">\n                  <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n                    <SuccessFilled />\n                  </el-icon>\n                  <span class=\"color-text-primary\">\n                    {{ $t('common.status.enabled') }}\n                  </span>\n                </div>\n                <div v-else class=\"flex align-center\">\n                  <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n                  <span class=\"color-text-primary\">\n                    {{ $t('common.status.disabled') }}\n                  </span>\n                </div>\n              </template>\n            </el-table-column>\n            <el-table-column width=\"150\" prop=\"tag\">\n              <template #header>\n                <div>\n                  <span>{{ $t('dynamicsForm.tag.label') }}</span>\n\n                  <el-dropdown trigger=\"click\" @visible-change=\"handleTagVisibleChange\">\n                    <el-button\n                      style=\"margin-top: 1px\"\n                      link\n                      :type=\"filterMethod['tags']?.length > 0 ? 'primary' : ''\"\n                    >\n                      <el-icon>\n                        <Filter />\n                      </el-icon>\n                    </el-button>\n                    <template #dropdown>\n                      <div>\n                        <el-cascader-panel\n                          v-model=\"tagFilterValue\"\n                          :options=\"tagFilterOptions\"\n                          :props=\"{\n                            multiple: true,\n                            checkStrictly: true,\n                            emitPath: false,\n                            showPrefix: false,\n                          }\"\n                          @change=\"(val: any) => dropdownHandle({ attr: 'tags', command: val })\"\n                        />\n                      </div>\n                    </template>\n                  </el-dropdown>\n                </div>\n              </template>\n              <template #default=\"{ row }\">\n                <el-popover\n                  trigger=\"hover\"\n                  placement=\"bottom-start\"\n                  :disabled=\"!row.tag_count\"\n                  :popper-style=\"{ width: 'auto', maxWidth: '300px' }\"\n                >\n                  <div\n                    v-for=\"tag in row.tags\"\n                    :key=\"tag.id\"\n                    class=\"flex align-center lighter color-text-primary mt-4 mb-4\"\n                  >\n                    <span class=\"color-secondary ellipsis-1\" style=\"width: 40%\" :title=\"tag.key\">{{\n                      tag.key\n                    }}</span>\n                    <span class=\"ml-4 ellipsis-1\" :title=\"tag.value\"> {{ tag.value }}</span>\n                  </div>\n\n                  <template #reference>\n                    <el-tag v-if=\"row.tag_count\" type=\"info\" effect=\"plain\" class=\"never mr-4\">\n                      <div class=\"flex align-center color-text-primary\">\n                        <AppIcon iconName=\"app-tag\"></AppIcon>\n                        <span class=\"ml-4\">{{ row.tag_count }}</span>\n                      </div>\n                    </el-tag>\n                  </template>\n                </el-popover>\n                <el-button\n                  class=\"button-new-tag\"\n                  size=\"small\"\n                  :disabled=\"!permissionPrecise.doc_tag(id)\"\n                  @click.stop=\"openAddTagDialog(row.id)\"\n                >\n                  <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                  {{ $t('views.document.tag.key') }}\n                </el-button>\n              </template>\n            </el-table-column>\n            <el-table-column width=\"165\">\n              <template #header>\n                <div>\n                  <span>{{ $t('views.document.form.hit_handling_method.label') }}</span>\n                  <el-dropdown trigger=\"click\" @command=\"dropdownHandle\">\n                    <el-button\n                      style=\"margin-top: 1px\"\n                      link\n                      :type=\"filterMethod['hit_handling_method'] ? 'primary' : ''\"\n                    >\n                      <el-icon>\n                        <Filter />\n                      </el-icon>\n                    </el-button>\n                    <template #dropdown>\n                      <el-dropdown-menu style=\"width: 150px\">\n                        <el-dropdown-item\n                          :class=\"filterMethod['hit_handling_method'] ? '' : 'is-active'\"\n                          :command=\"beforeCommand('hit_handling_method', '')\"\n                          class=\"justify-center\"\n                          >{{ $t('common.status.all') }}\n                        </el-dropdown-item>\n                        <template v-for=\"(value, key) of hitHandlingMethod\" :key=\"key\">\n                          <el-dropdown-item\n                            :class=\"filterMethod['hit_handling_method'] === key ? 'is-active' : ''\"\n                            class=\"justify-center\"\n                            :command=\"beforeCommand('hit_handling_method', key)\"\n                            >{{ $t(value) }}\n                          </el-dropdown-item>\n                        </template>\n                      </el-dropdown-menu>\n                    </template>\n                  </el-dropdown>\n                </div>\n              </template>\n              <template #default=\"{ row }\">\n                {{\n                  $t(hitHandlingMethod[row.hit_handling_method as keyof typeof hitHandlingMethod])\n                }}\n              </template>\n            </el-table-column>\n            <el-table-column\n              prop=\"create_time\"\n              :label=\"$t('common.createTime')\"\n              width=\"175\"\n              sortable\n            >\n              <template #default=\"{ row }\">\n                {{ datetimeFormat(row.create_time) }}\n              </template>\n            </el-table-column>\n            <el-table-column\n              prop=\"update_time\"\n              :label=\"$t('views.document.table.updateTime')\"\n              width=\"175\"\n              sortable\n            >\n              <template #default=\"{ row }\">\n                {{ datetimeFormat(row.update_time) }}\n              </template>\n            </el-table-column>\n            <el-table-column\n              :label=\"$t('common.operation')\"\n              align=\"left\"\n              width=\"160\"\n              fixed=\"right\"\n              v-if=\"!isShared\"\n            >\n              <template #default=\"{ row }\">\n                <span @click.stop>\n                  <el-switch\n                    :loading=\"loading\"\n                    size=\"small\"\n                    v-model=\"row.is_active\"\n                    :before-change=\"() => changeState(row)\"\n                    v-if=\"permissionPrecise.doc_edit(id)\"\n                  />\n                </span>\n                <el-divider direction=\"vertical\" />\n                <template v-if=\"knowledgeDetail?.type === 0 || knowledgeDetail?.type === 4\">\n                  <el-tooltip\n                    effect=\"dark\"\n                    :content=\"$t('views.document.setting.cancelVectorization')\"\n                    placement=\"top\"\n                    v-if=\"\n                      ([State.STARTED, State.PENDING] as Array<string>).includes(\n                        getTaskState(row.status, TaskType.EMBEDDING),\n                      )\n                    \"\n                  >\n                    <span class=\"mr-4\">\n                      <el-button\n                        type=\"primary\"\n                        text\n                        @click.stop=\"cancelTask(row, TaskType.EMBEDDING)\"\n                        v-if=\"permissionPrecise.doc_vector(id)\"\n                      >\n                        <el-icon><Close /></el-icon>\n                      </el-button>\n                    </span>\n                  </el-tooltip>\n                  <el-tooltip\n                    effect=\"dark\"\n                    :content=\"$t('views.knowledge.setting.vectorization')\"\n                    placement=\"top\"\n                    v-else\n                  >\n                    <span class=\"mr-4\" v-if=\"permissionPrecise.doc_vector(id)\">\n                      <el-button type=\"primary\" text @click.stop=\"refreshDocument(row)\">\n                        <AppIcon iconName=\"app-document-refresh\" style=\"font-size: 16px\"></AppIcon>\n                      </el-button>\n                    </span>\n                  </el-tooltip>\n                  <el-tooltip\n                    effect=\"dark\"\n                    :content=\"$t('common.setting')\"\n                    placement=\"top\"\n                    v-if=\"permissionPrecise.doc_edit(id)\"\n                  >\n                    <span class=\"mr-4\">\n                      <el-button type=\"primary\" text @click.stop=\"settingDoc(row)\">\n                        <AppIcon iconName=\"app-setting\"></AppIcon>\n                      </el-button>\n                    </span>\n                  </el-tooltip>\n                  <span @click.stop>\n                    <el-dropdown trigger=\"click\" v-if=\"MoreFilledPermission1(id)\">\n                      <el-button text type=\"primary\">\n                        <AppIcon iconName=\"app-more\"></AppIcon>\n                      </el-button>\n                      <template #dropdown>\n                        <el-dropdown-menu>\n                          <el-dropdown-item\n                            v-if=\"\n                              ([State.STARTED, State.PENDING] as Array<string>).includes(\n                                getTaskState(row.status, TaskType.GENERATE_PROBLEM),\n                              ) && permissionPrecise.doc_generate(id)\n                            \"\n                            @click=\"cancelTask(row, TaskType.GENERATE_PROBLEM)\"\n                          >\n                            <el-icon class=\"color-secondary\"><Close /></el-icon>\n                            {{ $t('views.document.setting.cancelGenerateQuestion') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click=\"openGenerateDialog(row)\"\n                            v-else-if=\"permissionPrecise.doc_generate(id)\"\n                          >\n                            <AppIcon\n                              iconName=\"app-generate-question\"\n                              class=\"color-secondary\"\n                            ></AppIcon>\n                            {{ $t('views.document.generateQuestion.title') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click=\"openTagSettingDrawer(row)\"\n                            v-if=\"permissionPrecise.doc_tag(id)\"\n                          >\n                            <AppIcon iconName=\"app-tag\" class=\"color-secondary\"></AppIcon>\n\n                            {{ $t('views.document.tag.setting') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click=\"openknowledgeDialog(row)\"\n                            v-if=\"permissionPrecise.doc_migrate(id)\"\n                          >\n                            <AppIcon iconName=\"app-migrate\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.document.setting.migration') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click=\"exportDocument(row)\"\n                            v-if=\"permissionPrecise.doc_export(id)\"\n                          >\n                            <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.document.setting.export') }} Excel\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click=\"exportDocumentZip(row)\"\n                            v-if=\"permissionPrecise.doc_export(id)\"\n                          >\n                            <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.document.setting.export') }} Zip\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click.stop=\"downloadDocument(row)\"\n                            v-if=\"permissionPrecise.doc_download(id)\"\n                          >\n                            <AppIcon iconName=\"app-download\" class=\"color-secondary\" />\n                            {{ $t('views.document.setting.download') }}\n                          </el-dropdown-item>\n                          <el-upload\n                            v-if=\"permissionPrecise.doc_replace(id)\"\n                            ref=\"elUploadRef\"\n                            :file-list=\"[]\"\n                            action=\"#\"\n                            :auto-upload=\"false\"\n                            :show-file-list=\"false\"\n                            :on-change=\"(file: any, fileList: any) => replaceDocument(file, row)\"\n                          >\n                            <el-dropdown-item>\n                              <AppIcon iconName=\"app-upload\" class=\"color-secondary\" />\n                              {{ $t('views.document.setting.replace') }}\n                            </el-dropdown-item>\n                          </el-upload>\n                          <el-dropdown-item\n                            @click.stop=\"deleteDocument(row)\"\n                            v-if=\"permissionPrecise.doc_delete(id)\"\n                          >\n                            <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.delete') }}</el-dropdown-item\n                          >\n                        </el-dropdown-menu>\n                      </template>\n                    </el-dropdown>\n                  </span>\n                </template>\n                <template v-if=\"knowledgeDetail?.type === 1 || knowledgeDetail?.type === 2\">\n                  <el-tooltip\n                    effect=\"dark\"\n                    :content=\"$t('views.document.setting.cancelVectorization')\"\n                    placement=\"top\"\n                    v-if=\"\n                      ([State.STARTED, State.PENDING] as Array<string>).includes(\n                        getTaskState(row.status, TaskType.EMBEDDING),\n                      ) && permissionPrecise.doc_vector(id)\n                    \"\n                  >\n                    <span class=\"mr-4\">\n                      <el-button\n                        type=\"primary\"\n                        text\n                        @click.stop=\"cancelTask(row, TaskType.EMBEDDING)\"\n                      >\n                        <el-icon><Close /></el-icon>\n                      </el-button>\n                    </span>\n                  </el-tooltip>\n                  <el-tooltip\n                    effect=\"dark\"\n                    :content=\"$t('views.knowledge.setting.vectorization')\"\n                    placement=\"top\"\n                    v-if=\"permissionPrecise.vector(id)\"\n                  >\n                    <span class=\"mr-4\">\n                      <el-button type=\"primary\" text @click.stop=\"refreshDocument(row)\">\n                        <AppIcon iconName=\"app-document-refresh\" style=\"font-size: 16px\"></AppIcon>\n                      </el-button>\n                    </span>\n                  </el-tooltip>\n                  <el-tooltip\n                    effect=\"dark\"\n                    :content=\"$t('common.setting')\"\n                    placement=\"top\"\n                    v-if=\"permissionPrecise.doc_edit(id)\"\n                  >\n                    <span class=\"mr-4\">\n                      <el-button type=\"primary\" text @click.stop=\"settingDoc(row)\">\n                        <AppIcon iconName=\"app-setting\"></AppIcon>\n                      </el-button>\n                    </span>\n                  </el-tooltip>\n                  <span @click.stop>\n                    <el-dropdown trigger=\"click\" v-if=\"MoreFilledPermission2(id)\">\n                      <el-button text type=\"primary\">\n                        <AppIcon iconName=\"app-more\"></AppIcon>\n                      </el-button>\n                      <template #dropdown>\n                        <el-dropdown-menu>\n                          <el-dropdown-item\n                            @click=\"syncDocument(row)\"\n                            v-if=\"permissionPrecise.sync(id)\"\n                          >\n                            <AppIcon iconName=\"app-sync\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.knowledge.setting.sync') }}</el-dropdown-item\n                          >\n                          <el-dropdown-item\n                            @click=\"openTagSettingDrawer(row)\"\n                            v-if=\"permissionPrecise.doc_tag(id)\"\n                          >\n                            <AppIcon iconName=\"app-tag\" class=\"color-secondary\"></AppIcon>\n\n                            {{ $t('views.document.tag.setting') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            v-if=\"\n                              permissionPrecise.doc_generate(id) &&\n                              ([State.STARTED, State.PENDING] as Array<string>).includes(\n                                getTaskState(row.status, TaskType.GENERATE_PROBLEM),\n                              )\n                            \"\n                            @click=\"cancelTask(row, TaskType.GENERATE_PROBLEM)\"\n                          >\n                            <el-icon class=\"color-secondary\"><Close /></el-icon>\n                            {{ $t('views.document.setting.cancelGenerateQuestion') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click=\"openGenerateDialog(row)\"\n                            v-else-if=\"permissionPrecise.doc_generate(id)\"\n                          >\n                            <AppIcon\n                              iconName=\"app-generate-question\"\n                              class=\"color-secondary\"\n                            ></AppIcon>\n                            {{ $t('views.document.generateQuestion.title') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click=\"openknowledgeDialog(row)\"\n                            v-if=\"permissionPrecise.doc_migrate(id)\"\n                          >\n                            <AppIcon iconName=\"app-migrate\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.document.setting.migration') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click=\"exportDocument(row)\"\n                            v-if=\"permissionPrecise.doc_export(id)\"\n                          >\n                            <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.document.setting.export') }} Excel\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click=\"exportDocumentZip(row)\"\n                            v-if=\"permissionPrecise.doc_export(id)\"\n                          >\n                            <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.document.setting.export') }} Zip\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click.stop=\"deleteDocument(row)\"\n                            v-if=\"permissionPrecise.doc_delete(id)\"\n                          >\n                            <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.delete') }}\n                          </el-dropdown-item>\n                        </el-dropdown-menu>\n                      </template>\n                    </el-dropdown>\n                  </span>\n                </template>\n              </template>\n            </el-table-column>\n          </app-table>\n        </div>\n      </div>\n    </el-card>\n    <div class=\"mul-operation w-full flex\" v-if=\"multipleSelection.length !== 0\">\n      <el-button\n        :disabled=\"multipleSelection.length === 0\"\n        @click=\"cancelTaskHandle(1)\"\n        v-if=\"permissionPrecise.doc_vector(id)\"\n      >\n        {{ $t('views.document.setting.cancelVectorization') }}\n      </el-button>\n      <el-button\n        :disabled=\"multipleSelection.length === 0\"\n        @click=\"cancelTaskHandle(2)\"\n        v-if=\"permissionPrecise.doc_generate(id)\"\n      >\n        {{ $t('views.document.setting.cancelGenerate') }}\n      </el-button>\n      <el-text type=\"info\" class=\"secondary ml-24\">\n        {{ $t('common.selected') }} {{ multipleSelection.length }}\n        {{ $t('views.document.items') }}\n      </el-text>\n      <el-button class=\"ml-16\" type=\"primary\" link @click=\"clearSelection\">\n        {{ $t('common.clear') }}\n      </el-button>\n    </div>\n\n    <EmbeddingContentDialog ref=\"embeddingContentDialogRef\"></EmbeddingContentDialog>\n\n    <ImportDocumentDialog ref=\"ImportDocumentDialogRef\" :title=\"title\" @refresh=\"refresh\" />\n    <!-- 选择知识库 -->\n    <SelectKnowledgeDialog\n      ref=\"selectKnowledgeDialogRef\"\n      @refresh=\"refreshMigrate\"\n      :workspaceId=\"knowledgeDetail?.workspace_id\"\n    />\n    <GenerateRelatedDialog ref=\"GenerateRelatedDialogRef\" @refresh=\"getList\" :apiType=\"apiType\" />\n    <TagDrawer ref=\"tagDrawerRef\" @tag-changed=\"onTagChanged\" />\n    <TagSettingDrawer\n      ref=\"tagSettingDrawerRef\"\n      @refresh=\"\n        () => {\n          onTagChanged()\n          getList()\n        }\n      \"\n    />\n    <AddTagDialog ref=\"addTagDialogRef\" @addTags=\"addTags\" :apiType=\"apiType\" />\n    <!-- 执行详情 -->\n    <ExecutionRecord ref=\"ListActionRef\"></ExecutionRecord>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, onBeforeUnmount, computed, reactive } from 'vue'\nimport { useRouter, useRoute, onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'\nimport type { ElTable } from 'element-plus'\nimport ImportDocumentDialog from './component/ImportDocumentDialog.vue'\nimport SelectKnowledgeDialog from './component/SelectKnowledgeDialog.vue'\nimport { numberFormat } from '@/utils/common'\nimport { datetimeFormat } from '@/utils/time'\nimport { hitHandlingMethod } from '@/enums/document'\nimport { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'\nimport useStore from '@/stores'\nimport StatusValue from '@/views/document/component/Status.vue'\nimport GenerateRelatedDialog from '@/components/generate-related-dialog/index.vue'\nimport EmbeddingContentDialog from '@/views/document/component/EmbeddingContentDialog.vue'\nimport { TaskType, State } from '@/utils/status'\nimport { t } from '@/locales'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport TagDrawer from './tag/TagDrawer.vue'\nimport TagSettingDrawer from './tag/TagSettingDrawer.vue'\nimport AddTagDialog from '@/views/document/tag/MulAddTagDialog.vue'\nimport ExecutionRecord from '@/views/knowledge-workflow/component/execution-record/ExecutionRecordDrawer.vue'\n\nconst route = useRoute()\nconst router = useRouter()\nconst {\n  params: { id, folderId, type }, // id为knowledgeID\n} = route as any\nconst { common } = useStore()\nconst storeKey = 'documents'\nonBeforeRouteUpdate(() => {\n  common.savePage(storeKey, null)\n  common.saveCondition(storeKey, null)\n})\nonBeforeRouteLeave((to: any) => {\n  if (to.name !== 'ParagraphIndex') {\n    common.savePage(storeKey, null)\n    common.saveCondition(storeKey, null)\n  } else {\n    common.saveCondition(storeKey, {\n      search_type: search_type.value,\n      search_form: search_form.value,\n      filterMethod: filterMethod.value,\n    })\n  }\n})\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('share/')) {\n    return 'workspaceShare'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst MoreFilledPermission0 = (id: string) => {\n  return (\n    permissionPrecise.value.doc_migrate(id) ||\n    (knowledgeDetail?.value.type === 1 && permissionPrecise.value.doc_sync(id)) ||\n    (knowledgeDetail?.value.type === 2 && permissionPrecise.value.doc_sync(id)) ||\n    permissionPrecise.value.doc_delete(id) ||\n    permissionPrecise.value.doc_tag(id)\n  )\n}\n\nconst MoreFilledPermission1 = (id: string) => {\n  return (\n    permissionPrecise.value.doc_generate(id) ||\n    permissionPrecise.value.doc_migrate(id) ||\n    permissionPrecise.value.doc_export(id) ||\n    permissionPrecise.value.doc_download(id) ||\n    permissionPrecise.value.doc_delete(id) ||\n    permissionPrecise.value.doc_tag(id) ||\n    permissionPrecise.value.doc_replace(id)\n  )\n}\n\nconst MoreFilledPermission2 = (id: string) => {\n  return (\n    permissionPrecise.value.sync(id) ||\n    permissionPrecise.value.doc_generate(id) ||\n    permissionPrecise.value.doc_migrate(id) ||\n    permissionPrecise.value.doc_export(id) ||\n    permissionPrecise.value.doc_delete(id)\n  )\n}\n\nconst getTaskState = (status: string, taskType: number) => {\n  const statusList = status.split('').reverse()\n  return taskType - 1 > statusList.length + 1 ? 'n' : statusList[taskType - 1]\n}\n\nconst search_type = ref('name')\nconst search_form = ref<any>({\n  name: '',\n  tag: '',\n})\n\nconst beforePagination = computed(() => common.paginationConfig[storeKey])\nconst beforeSearch = computed(() => common.search[storeKey])\nconst embeddingContentDialogRef = ref<InstanceType<typeof EmbeddingContentDialog>>()\nconst ListActionRef = ref<InstanceType<typeof ExecutionRecord>>()\nconst loading = ref(false)\nlet interval: any\n\nconst filterMethod = ref<any>({})\nconst orderBy = ref<string>('')\nconst documentData = ref<any[]>([])\nconst currentMouseId = ref(null)\nconst knowledgeDetail = ref<any>({})\n\nconst paginationConfig = ref({\n  current_page: 1,\n  page_size: 10,\n  total: 0,\n})\n\nconst ImportDocumentDialogRef = ref()\nconst multipleTableRef = ref<InstanceType<typeof ElTable>>()\nconst multipleSelection = ref<any[]>([])\nconst title = ref('')\n\nconst selectKnowledgeDialogRef = ref()\n\nconst openListAction = () => {\n  ListActionRef.value?.open(id)\n}\n\nconst toImportWorkflow = () => {\n  if (knowledgeDetail.value.is_publish) {\n    router.push({\n      path: `/knowledge/import/workflow/${folderId}`,\n      query: {\n        id: id,\n      },\n    })\n  } else {\n    MsgConfirm(t('common.tip'), t('views.document.tip.toImportDocConfirm'), {\n      cancelButtonText: t('common.close'),\n      showConfirmButton: false,\n      type: 'warning',\n    })\n      .then(() => {})\n      .catch(() => {})\n  }\n}\n\nconst exportDocument = (document: any) => {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .exportDocument(document.name, document.knowledge_id, document.id, loading)\n    .then(() => {\n      MsgSuccess(t('common.exportSuccess'))\n    })\n}\nconst exportDocumentZip = (document: any) => {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .exportDocumentZip(document.name, document.knowledge_id, document.id, loading)\n    .then(() => {\n      MsgSuccess(t('common.exportSuccess'))\n    })\n}\n\nfunction cancelTaskHandle(val: any) {\n  const arr: string[] = []\n  multipleSelection.value.map((v) => {\n    if (v) {\n      arr.push(v.id)\n    }\n  })\n  const obj = {\n    id_list: arr,\n    type: val,\n  }\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .putBatchCancelTask(id, obj, loading)\n    .then(() => {\n      MsgSuccess(t('views.document.tip.cancelSuccess'))\n      multipleTableRef.value?.clearSelection()\n    })\n}\n\nfunction clearSelection() {\n  multipleTableRef.value?.clearSelection()\n}\n\nfunction openknowledgeDialog(row?: any) {\n  const arr: string[] = []\n  if (row) {\n    arr.push(row.id)\n  } else {\n    multipleSelection.value.map((v) => {\n      if (v) {\n        arr.push(v.id)\n      }\n    })\n  }\n\n  selectKnowledgeDialogRef.value.open(arr)\n}\n\nfunction dropdownHandle(obj: any) {\n  filterMethod.value[obj.attr] = obj.command\n  if (obj.attr == 'status') {\n    filterMethod.value['task_type'] = obj.task_type\n  }\n\n  getList()\n}\n\nfunction beforeCommand(attr: string, val: any, task_type?: number) {\n  return {\n    attr: attr,\n    command: val,\n    task_type,\n  }\n}\n\nconst cancelTask = (row: any, task_type: number) => {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .putCancelTask(id, row.id, { type: task_type })\n    .then(() => {\n      MsgSuccess(t('views.document.tip.sendMessage'))\n    })\n}\n\nfunction importDoc() {\n  title.value = t('views.document.importDocument')\n  ImportDocumentDialogRef.value.open()\n}\n\nfunction settingDoc(row: any) {\n  title.value = t('common.setting')\n  ImportDocumentDialogRef.value.open(row)\n}\n\nconst handleSelectionChange = (val: any[]) => {\n  multipleSelection.value = val\n}\n\nfunction openBatchEditDocument() {\n  title.value = t('common.setting')\n  const arr: string[] = multipleSelection.value.map((v) => v.id)\n  ImportDocumentDialogRef.value.open(null, arr)\n}\n\n/**\n * 初始化轮询\n */\nconst initInterval = () => {\n  interval = setInterval(() => {\n    getList(true)\n  }, 6000)\n}\n\n/**\n * 关闭轮询\n */\nconst closeInterval = () => {\n  if (interval) {\n    clearInterval(interval)\n  }\n}\n\nfunction syncDocument(row: any) {\n  if (+row.type === 1) {\n    syncWebDocument(row)\n  } else {\n    syncLarkDocument(row)\n  }\n}\n\nfunction syncLarkDocument(row: any) {\n  MsgConfirm(t('views.document.sync.confirmTitle'), t('views.document.sync.confirmMessage1'), {\n    confirmButtonText: t('views.document.sync.label'),\n    confirmButtonClass: 'danger',\n  })\n    .then(() => {\n      loadSharedApi({ type: 'document', systemType: apiType.value })\n        .putLarkDocumentSync(id, row.id)\n        .then(() => {\n          getList()\n        })\n    })\n    .catch(() => {})\n}\n\nfunction syncWebDocument(row: any) {\n  if (row.meta?.source_url) {\n    MsgConfirm(t('views.document.sync.confirmTitle'), t('views.document.sync.confirmMessage1'), {\n      confirmButtonText: t('views.document.sync.label'),\n      confirmButtonClass: 'danger',\n    })\n      .then(() => {\n        loadSharedApi({ type: 'document', systemType: apiType.value })\n          .putDocumentSync(row.knowledge_id, row.id)\n          .then(() => {\n            getList()\n          })\n      })\n      .catch(() => {})\n  } else {\n    MsgConfirm(t('common.tip'), t('views.document.sync.confirmMessage2'), {\n      confirmButtonText: t('common.confirm'),\n      type: 'warning',\n    })\n      .then(() => {})\n      .catch(() => {})\n  }\n}\n\nfunction refreshDocument(row: any) {\n  const embeddingDocument = (stateList: Array<string>) => {\n    return loadSharedApi({ type: 'document', systemType: apiType.value })\n      .putDocumentRefresh(row.knowledge_id, row.id, stateList)\n      .then(() => {\n        getList()\n      })\n  }\n  embeddingContentDialogRef.value?.open(embeddingDocument)\n}\n\nfunction rowClickHandle(row: any, column: any) {\n  console.log(column)\n  if (column && (column.type === 'selection' || column.property === 'tag')) {\n    return\n  }\n  router.push({\n    path: `/paragraph/${id}/${row.id}`,\n    query: { from: apiType.value, isShared: isShared.value ? 'true' : 'false' },\n  })\n}\n\n/*\n  快速创建空白文档\n*/\nfunction creatQuickHandle(val: string) {\n  loading.value = true\n  const obj = [{ name: val }]\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .putMulDocument(id, obj)\n    .then(() => {\n      getList()\n      MsgSuccess(t('common.createSuccess'))\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nfunction syncMulDocument() {\n  const arr: string[] = []\n  multipleSelection.value.map((v) => {\n    if (v) {\n      arr.push(v.id)\n    }\n  })\n  MsgConfirm(t('views.document.sync.confirmTitle'), t('views.document.sync.confirmMessage1'), {\n    confirmButtonText: t('views.document.sync.label'),\n    confirmButtonClass: 'danger',\n  })\n    .then(() => {\n      loadSharedApi({ type: 'document', systemType: apiType.value })\n        .putMulSyncDocument(id, arr, loading)\n        .then(() => {\n          MsgSuccess(t('views.document.sync.successMessage'))\n          getList()\n        })\n    })\n    .catch(() => {})\n}\n\nfunction syncLarkMulDocument() {\n  const arr: string[] = []\n  multipleSelection.value.map((v) => {\n    if (v) {\n      arr.push(v.id)\n    }\n  })\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .putMulLarkSyncDocument(id, arr, loading)\n    .then(() => {\n      MsgSuccess(t('views.document.sync.successMessage'))\n      getList()\n    })\n}\n\nfunction exportMulDocument() {\n  const arr: string[] = []\n  multipleSelection.value.map((v) => {\n    if (v) {\n      arr.push(v.id)\n    }\n  })\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .exportMulDocument(knowledgeDetail.value.name, id, arr, loading)\n    .then(() => {\n      MsgSuccess(t('common.exportSuccess'))\n    })\n}\n\nfunction exportMulDocumentZip() {\n  const arr: string[] = []\n  multipleSelection.value.map((v) => {\n    if (v) {\n      arr.push(v.id)\n    }\n  })\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .exportMulDocumentZip(knowledgeDetail.value.name, id, arr, loading)\n    .then(() => {\n      MsgSuccess(t('common.exportSuccess'))\n    })\n}\n\nfunction deleteMulDocument() {\n  MsgConfirm(\n    `${t('views.document.delete.confirmTitle1')} ${multipleSelection.value.length} ${t('views.document.delete.confirmTitle2')}`,\n    t('views.document.delete.confirmMessage'),\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      const arr: string[] = []\n      multipleSelection.value.map((v) => {\n        if (v) {\n          arr.push(v.id)\n        }\n      })\n      loadSharedApi({ type: 'document', systemType: apiType.value })\n        .delMulDocument(id, arr, loading)\n        .then(() => {\n          MsgSuccess(t('views.document.delete.successMessage'))\n          multipleTableRef.value?.clearSelection()\n          getList()\n        })\n    })\n    .catch(() => {})\n}\n\nfunction batchRefresh() {\n  const arr: string[] = multipleSelection.value.map((v) => v.id)\n  const embeddingBatchDocument = (stateList: Array<string>) => {\n    loadSharedApi({ type: 'document', systemType: apiType.value })\n      .putBatchRefresh(id, arr, stateList, loading)\n      .then(() => {\n        MsgSuccess(t('views.document.tip.vectorizationSuccess'))\n        multipleTableRef.value?.clearSelection()\n      })\n  }\n  embeddingContentDialogRef.value?.open(embeddingBatchDocument)\n}\n\nfunction downloadDocument(row: any) {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .getDownloadSourceFile(id, row.id, row.name)\n    .then(() => {\n      getList()\n    })\n}\n\nconst elUploadRef = ref()\n\nfunction replaceDocument(file: any, row: any) {\n  const formData = new FormData()\n  formData.append('file', file.raw, file.name)\n  elUploadRef.value.clearFiles()\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .postReplaceSourceFile(id, row.id, formData, loading)\n    .then(() => {\n      MsgSuccess(t('views.document.tip.replaceSuccess'))\n      getList()\n    })\n    .catch((e: any) => {})\n}\n\nfunction deleteDocument(row: any) {\n  MsgConfirm(\n    `${t('views.document.delete.confirmTitle3')} ${row.name} ?`,\n    `${t('views.document.delete.confirmMessage1')} ${row.paragraph_count} ${t('views.document.delete.confirmMessage2')}`,\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loadSharedApi({ type: 'document', systemType: apiType.value })\n        .delDocument(id, row.id, loading)\n        .then(() => {\n          MsgSuccess(t('common.deleteSuccess'))\n          getList()\n        })\n    })\n    .catch(() => {})\n}\n\n/*\n  更新名称或状态\n*/\nfunction updateData(documentId: string, data: any, msg: string) {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .putDocument(id, documentId, data, loading)\n    .then((res: any) => {\n      const index = documentData.value.findIndex((v) => v.id === documentId)\n      documentData.value.splice(index, 1, res.data)\n      MsgSuccess(msg)\n      return true\n    })\n    .catch(() => {\n      return false\n    })\n}\n\nasync function changeState(row: any) {\n  const obj = {\n    is_active: !row.is_active,\n  }\n  const str = !row.is_active ? t('common.status.enableSuccess') : t('common.status.disableSuccess')\n  await updateData(row.id, obj, str)\n}\n\nfunction editName(val: string, id: string) {\n  if (val) {\n    const obj = {\n      name: val,\n    }\n    updateData(id, obj, t('common.modifySuccess'))\n  } else {\n    MsgError(t('views.document.tip.nameMessage'))\n  }\n}\n\nfunction cellMouseEnter(row: any, column: any) {\n  if (column && column.property === 'name') {\n    currentMouseId.value = row.id\n  }\n}\n\nfunction cellMouseLeave() {\n  currentMouseId.value = null\n}\n\nfunction handleSizeChange() {\n  paginationConfig.value.current_page = 1\n  getList()\n}\n\nfunction handleSortChange({ prop, order }: { prop: string; order: string }) {\n  orderBy.value = order === 'ascending' ? prop : `-${prop}`\n  getList()\n}\n\nfunction getList(bool?: boolean) {\n  const param = {\n    ...filterMethod.value,\n    order_by: orderBy.value,\n    folder_id: folderId,\n  }\n  if (search_form.value[search_type.value]) {\n    param[search_type.value] = search_form.value[search_type.value]\n  }\n  loadSharedApi({ type: 'document', isShared: isShared.value, systemType: apiType.value })\n    .getDocumentPage(id as string, paginationConfig.value, param, bool ? undefined : loading)\n    .then((res: any) => {\n      documentData.value = res.data.records\n      paginationConfig.value.total = res.data.total\n    })\n}\n\nconst search_type_change = () => {\n  search_form.value = { name: '', tag: '' }\n}\n\nfunction getDetail() {\n  loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value })\n    .getKnowledgeDetail(id, loading)\n    .then((res: any) => {\n      knowledgeDetail.value = res.data\n    })\n}\n\nfunction refreshMigrate() {\n  multipleTableRef.value?.clearSelection()\n  getList()\n}\n\nfunction refresh() {\n  paginationConfig.value.current_page = 1\n  getList()\n}\n\nconst GenerateRelatedDialogRef = ref()\n\nfunction openGenerateDialog(row?: any) {\n  const arr: string[] = []\n  if (row) {\n    arr.push(row.id)\n  } else {\n    multipleSelection.value.map((v) => {\n      if (v) {\n        arr.push(v.id)\n      }\n    })\n  }\n\n  GenerateRelatedDialogRef.value.open(arr, 'document')\n}\n\nfunction cellClickHandle(row: any, column: any, cell: any, event: any) {\n  if (column.property === 'tag' && permissionPrecise.value.doc_tag(id)) {\n    event.stopPropagation()\n    openTagSettingDrawer(row)\n  }\n}\nconst tagFilterValue = ref<string[]>([])\nconst tagFilterDirty = ref(false)\nconst tagFilterOptions = ref<any[]>([])\nconst tagFilterLoaded = ref(false)\nconst tagFilterLoading = ref(false)\n\nfunction buildTagCascaderOptions(tags: any[]) {\n  const options = tags.map((group: any) => ({\n    label: group.key,\n    value: group.key,\n    children: (group.values || []).map((item: any) => ({\n      label: item.value,\n      value: item.id, // 叶子节点 tag.id\n    })),\n  }))\n\n  options.push({\n    label: t('views.document.tag.noTag'),\n    value: 'NO_TAG',\n    children: [],\n  })\n\n  return options\n}\n\nasync function ensureTagFilterOptions(needRefresh = false) {\n  // 非刷新 && 已加载 && 非脏数据\n  if (!needRefresh && tagFilterLoaded.value && !tagFilterDirty.value) return\n\n  try {\n    tagFilterLoading.value = true\n    const params = {}\n    const res: any = await loadSharedApi({\n      type: 'knowledge',\n      systemType: apiType.value,\n      isShared: isShared.value,\n    }).getTags(id, params, tagFilterLoading)\n\n    tagFilterOptions.value = buildTagCascaderOptions(res?.data || [])\n    tagFilterLoaded.value = true\n    tagFilterDirty.value = false\n  } finally {\n    tagFilterLoading.value = false\n  }\n}\n\nasync function handleTagVisibleChange(visible: boolean) {\n  if (!visible) return\n  await ensureTagFilterOptions()\n}\n\nfunction onTagChanged() {\n  tagFilterDirty.value = true\n}\n\nconst tagDrawerRef = ref()\nfunction openTagDrawer() {\n  tagDrawerRef.value.open()\n}\n\nconst tagSettingDrawerRef = ref()\nfunction openTagSettingDrawer(doc: any) {\n  tagSettingDrawerRef.value.open(doc)\n}\n\nconst addTagDialogRef = ref()\n\nfunction openAddTagDialog(rowId?: string) {\n  addTagDialogRef.value?.open(rowId)\n}\n\nfunction addTags(tags: any, rowId?: string) {\n  const arr: string[] = multipleSelection.value.length\n    ? multipleSelection.value.map((v) => v.id)\n    : [rowId]\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .postMulDocumentTags(id, { tag_ids: tags, document_ids: arr }, loading)\n    .then(() => {\n      addTagDialogRef.value?.close()\n      getList()\n      clearSelection()\n    })\n}\n\nonMounted(() => {\n  getDetail()\n  if (beforePagination.value) {\n    paginationConfig.value = beforePagination.value\n  }\n  if (beforeSearch.value) {\n    filterMethod.value = beforeSearch.value['filterMethod']\n    search_type.value = beforeSearch.value['search_type']\n    search_form.value = beforeSearch.value['search_form']\n  }\n  getList()\n  // 初始化定时任务\n  initInterval()\n})\n\nonBeforeUnmount(() => {\n  // 清除定时任务\n  closeInterval()\n})\n</script>\n<style lang=\"scss\" scoped>\n.document {\n  .mul-operation {\n    position: fixed;\n    margin-left: var(--sidebar-width);\n    bottom: 0;\n    right: 24px;\n    width: calc(100% - var(--sidebar-width) - 48px);\n    padding: 16px 24px;\n    box-sizing: border-box;\n    background: #ffffff;\n    z-index: 22;\n    box-shadow: 0px -2px 4px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n  }\n  .document-table {\n    :deep(.el-table__row) {\n      cursor: pointer;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/document/tag/CreateTagDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    :title=\"currentTagKey ? $t('views.document.tag.addValue') : $t('views.document.tag.create')\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      ref=\"FormRef\"\n      :model=\"{ tags }\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-scrollbar>\n        <el-row :gutter=\"8\" style=\"margin-right: 10px\" class=\"tag-list-max-list\">\n          <template v-for=\"(tag, index) in tags\" :key=\"tag\">\n            <el-col :span=\"12\">\n              <el-form-item\n                :label=\"index === 0 ? $t('views.document.tag.key') : ''\"\n                :prop=\"`tags.${index}.key`\"\n                :rules=\"{\n                  required: true,\n                  message: $t('views.document.tag.requiredMessage1'),\n                  trigger: 'blur',\n                }\"\n              >\n                <el-input\n                  v-model=\"tag.key\"\n                  :disabled=\"currentTagKey? true : false\"\n                  class=\"w-full\"\n                  :placeholder=\"$t('views.document.tag.requiredMessage1')\"\n                ></el-input>\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"11\">\n              <el-form-item\n                :label=\"index === 0 ? $t('views.document.tag.value') : ''\"\n                :prop=\"`tags.${index}.value`\"\n                :rules=\"{\n                  required: true,\n                  message: $t('views.document.tag.requiredMessage2'),\n                  trigger: 'blur',\n                }\"\n                class=\"w-full\"\n              >\n                <el-input\n                  v-model=\"tag.value\"\n                  :placeholder=\"$t('views.document.tag.requiredMessage2')\"\n                ></el-input>\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"1\">\n              <el-button\n                :disabled=\"tags.length === 1\"\n                link\n                type=\"info\"\n                @click=\"deleteTag(index)\"\n                :style=\"{ marginTop: index === 0 ? '35px' : '5px' }\"\n              >\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </el-col>\n          </template>\n        </el-row>\n      </el-scrollbar>\n    </el-form>\n\n    <el-button link type=\"primary\" @click=\"add\">\n      <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\" />\n      {{ $t('common.add') }}\n    </el-button>\n\n    <template #footer>\n      <div class=\"dialog-footer\">\n        <el-button @click=\"close\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit\">{{ $t('common.confirm') }}</el-button>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport { cloneDeep } from 'lodash'\n\nconst route = useRoute()\nconst {\n  params: { id }, // id为knowledgeID\n} = route as any\nconst emit = defineEmits(['refresh'])\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst FormRef = ref()\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\nconst currentTagKey = ref(null)\nconst tags = ref<Array<any>>([])\n\nconst add = () => {\n  if (currentTagKey.value) {\n    tags.value.push({ key: currentTagKey.value })\n  } else {\n    tags.value.push({})\n  }\n}\nconst deleteTag = (index: number) => {\n  tags.value.splice(index, 1)\n}\n\nconst submit = () => {\n  FormRef.value.validate((valid: boolean) => {\n    if (!valid) return\n    loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n      .postTags(id, tags.value, loading)\n      .then((res: any) => {\n        close()\n        emit('refresh', currentTagKey.value)\n      })\n  })\n}\n\nconst open = (row?: any) => {\n  const currentRow = cloneDeep(row)\n  dialogVisible.value = true\n  currentTagKey.value = currentRow ? currentRow.key : null\n  tags.value = currentRow ? [{ ...{ key: currentRow.key } }] : [{}]\n}\n\nconst close = () => {\n  dialogVisible.value = false\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped>\n.tag-list-max-list {\n  max-height: calc(100vh - 260px);\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/document/tag/EditTagDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    :title=\"isEditKey ? $t('views.document.tag.edit') : $t('views.document.tag.editValue')\"\n    :before-close=\"close\"\n    :width=\"isEditKey ? '500px' : '50%'\"\n  >\n    <el-form\n      ref=\"FormRef\"\n      :model=\"form\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-form-item\n        :label=\"$t('views.document.tag.key')\"\n        v-if=\"isEditKey\"\n        :rules=\"{\n          required: true,\n          message: $t('views.document.tag.requiredMessage1'),\n          trigger: 'blur',\n        }\"\n        prop=\"key\"\n      >\n        <el-input v-model=\"form.key\"></el-input>\n      </el-form-item>\n      <el-row :gutter=\"8\" align=\"bottom\" v-else>\n        <el-col :span=\"12\">\n          <el-form-item\n            :label=\"$t('views.document.tag.key')\"\n            prop=\"key\"\n            :rules=\"{\n              required: true,\n              message: $t('views.document.tag.requiredMessage1'),\n              trigger: 'blur',\n            }\"\n          >\n            <el-input v-model=\"form.key\" :disabled=\"true\"></el-input>\n          </el-form-item>\n        </el-col>\n        <el-col :span=\"12\">\n          <el-form-item\n            :label=\"$t('views.document.tag.value')\"\n            prop=\"value\"\n            :rules=\"{\n              required: true,\n              message: $t('views.document.tag.requiredMessage2'),\n              trigger: 'blur',\n            }\"\n          >\n            <el-input v-model=\"form.value\"></el-input>\n          </el-form-item>\n        </el-col>\n      </el-row>\n    </el-form>\n    <template #footer>\n      <div class=\"dialog-footer\">\n        <el-button @click=\"close\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit\">{{ $t('common.confirm') }}</el-button>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\n\nconst route = useRoute()\nconst {\n  params: { id }, // id为knowledgeID\n} = route as any\nconst emit = defineEmits(['refresh'])\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst FormRef = ref()\nconst isEditKey = ref(false)\nconst form = ref({\n  id: '',\n  key: '',\n  value: '',\n})\n\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\n\nconst submit = () => {\n  FormRef.value.validate((valid: boolean) => {\n    if (valid) {\n      loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n        .putTag(id, form.value.id, form.value, loading)\n        .then((res: any) => {\n          close()\n          emit('refresh')\n        })\n    }\n  })\n}\n\nconst open = (row: any, isKey: boolean) => {\n  dialogVisible.value = true\n  form.value.id = row.id\n  form.value.key = row.key\n  form.value.value = row.value\n  isEditKey.value = isKey\n}\n\nconst close = () => {\n  dialogVisible.value = false\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/document/tag/MulAddTagDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    :title=\"$t('views.document.tag.addTag')\"\n    :before-close=\"close\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form\n      ref=\"FormRef\"\n      :model=\"{ tagList }\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-scrollbar>\n        <el-row :gutter=\"8\" style=\"margin-right: 10px\" class=\"tag-list-max-list\">\n          <template v-for=\"(tag, index) in tagList\" :key=\"tag\">\n            <el-col :span=\"12\">\n              <el-form-item\n                :label=\"index === 0 ? $t('views.document.tag.key') : ''\"\n                :prop=\"`tagList.${index}.key`\"\n                :rules=\"{\n                  required: true,\n                  message: $t('views.document.tag.requiredMessage1'),\n                  trigger: 'blur',\n                }\"\n              >\n                <el-select\n                  v-model=\"tag.key\"\n                  @change=\"tagKeyChange(tag)\"\n                  filterable\n                  :filter-method=\"filterMethod\"\n                  :placeholder=\"$t('views.document.tag.requiredMessage1')\"\n                  :loading=\"optionLoading\"\n                >\n                  <el-option\n                    v-for=\"op in keyOptions\"\n                    :key=\"op\"\n                    :value=\"op.key\"\n                    :label=\"op.key\"\n                  ></el-option>\n                  <template #footer>\n                    <slot name=\"footer\">\n                      <div class=\"w-full text-left cursor\">\n                        <el-button type=\"primary\" link @click=\"openCreateTagDialog()\">\n                          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                          {{ $t('views.document.tag.create') }}\n                        </el-button>\n                      </div>\n                    </slot>\n                  </template>\n                </el-select>\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"11\">\n              <el-form-item\n                :label=\"index === 0 ? $t('views.document.tag.value') : ''\"\n                :prop=\"`tagList.${index}.value`\"\n                :rules=\"{\n                  required: true,\n                  message: $t('views.document.tag.requiredMessage2'),\n                  trigger: 'blur',\n                }\"\n              >\n                <el-select\n                  v-model=\"tag.value\"\n                  filterable\n                  :placeholder=\"$t('views.document.tag.requiredMessage2')\"\n                >\n                  <el-option\n                    v-for=\"op in getValueOptions(tag)\"\n                    :key=\"op\"\n                    :value=\"op.id\"\n                    :label=\"op.value\"\n                  ></el-option>\n                  <template #footer>\n                    <slot name=\"footer\">\n                      <div class=\"w-full text-left cursor\">\n                        <el-button type=\"primary\" link @click=\"openCreateTagDialog(tag)\">\n                          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                          {{ $t('views.document.tag.createValue') }}\n                        </el-button>\n                      </div>\n                    </slot>\n                  </template>\n                </el-select>\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"1\">\n              <el-button\n                :disabled=\"tagList.length === 1\"\n                text\n                @click=\"deleteTag(index)\"\n                :style=\"{ marginTop: index === 0 ? '35px' : '5px' }\"\n              >\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </el-col>\n          </template>\n        </el-row>\n      </el-scrollbar>\n    </el-form>\n\n    <el-button link type=\"primary\" @click=\"add\">\n      <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\" />\n      {{ $t('common.add') }}\n    </el-button>\n\n    <template #footer>\n      <div class=\"dialog-footer\">\n        <el-button @click=\"close\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit\">{{ $t('common.confirm') }}</el-button>\n      </div>\n    </template>\n    <CreateTagDialog ref=\"createTagDialogRef\" @refresh=\"getTags\" />\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport CreateTagDialog from './CreateTagDialog.vue'\n\nconst emit = defineEmits(['addTags'])\nconst props = defineProps<{\n  apiType: 'systemShare' | 'workspace' | 'systemManage' | 'workspaceShare'\n}>()\n\nconst route = useRoute()\nconst {\n  params: { id, folderId }, // id为knowledgeID\n} = route as any\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nconst optionLoading = ref<boolean>(false)\nconst FormRef = ref()\nconst dialogVisible = ref<boolean>(false)\nconst tagList = ref<Array<any>>([])\nconst keyOptions = ref<Array<any>>([])\nconst allKeyOptions = ref([])\n\nconst add = () => {\n  tagList.value.push({})\n}\nconst deleteTag = (index: number) => {\n  tagList.value.splice(index, 1)\n}\n\nfunction tagKeyChange(tag: any) {\n  tag.value = null\n}\n\nfunction getValueOptions(tag?: any) {\n  let currentKeyOption = null\n  if (tag && tag.key) {\n    currentKeyOption = keyOptions.value.find((op: any) => op.key === tag.key)\n  }\n  return currentKeyOption ? currentKeyOption.values : []\n}\n\nconst submit = () => {\n  FormRef.value.validate((valid: boolean) => {\n    if (!valid) return\n    emit(\n      'addTags',\n      tagList.value.map((tag) => tag.value),\n      currentDocId.value,\n    )\n  })\n}\n\nfunction getTags(Key?: string) {\n  loadSharedApi({ type: 'knowledge', systemType: props.apiType, isShared: isShared.value })\n    .getTags(id, {}, optionLoading)\n    .then((res: any) => {\n      keyOptions.value = res.data.slice(0, 100)\n      allKeyOptions.value = res.data\n    })\n}\n\nfunction filterMethod(val: string) {\n  keyOptions.value = allKeyOptions.value\n    .filter((item: any) => item.key.indexOf(val) > -1)\n    .slice(0, 100)\n}\n\nconst createTagDialogRef = ref()\n\nfunction openCreateTagDialog(row?: any) {\n  createTagDialogRef.value?.open(row)\n}\n\nconst currentDocId = ref<string | undefined>()\nconst open = (rowId?: string) => {\n  getTags()\n  currentDocId.value = rowId\n  dialogVisible.value = true\n  tagList.value = [{}]\n}\n\nconst close = () => {\n  dialogVisible.value = false\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/document/tag/TagDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"debugVisible\" size=\"60%\" :append-to-body=\"true\">\n    <template #header>\n      <h4>{{ $t('views.document.tag.label') }}</h4>\n    </template>\n    <div class=\"flex-between mb-16\">\n      <div>\n        <el-button\n          type=\"primary\"\n          @click=\"openCreateTagDialog()\"\n          v-if=\"permissionPrecise.tag_create(id)\"\n          >{{ $t('views.document.tag.create') }}\n        </el-button>\n        <el-button\n          :disabled=\"multipleSelection.length === 0\"\n          @click=\"batchDelete\"\n          v-if=\"permissionPrecise.tag_delete(id)\"\n        >\n          {{ $t('common.delete') }}\n        </el-button>\n      </div>\n      <el-input\n        v-model=\"filterText\"\n        prefix-icon=\"Search\"\n        class=\"w-240\"\n        @change=\"getList\"\n        clearable\n        :placeholder=\"$t('common.search')\"\n      />\n    </div>\n    <el-table\n      ref=\"tableRef\"\n      :data=\"pagedTableData\"\n      :span-method=\"spanMethod\"\n      v-loading=\"loading\"\n      :max-height=\"tableMaxHeight\"\n      @selection-change=\"handleSelectionChange\"\n      @cell-mouse-enter=\"cellMouseEnter\"\n      @cell-mouse-leave=\"cellMouseLeave\"\n    >\n      <el-table-column type=\"selection\" width=\"55\" />\n      <el-table-column\n        prop=\"key\"\n        :label=\"\n          multipleSelection.length === 0\n            ? $t('views.document.tag.key')\n            : `${$t('common.selected')} ${multipleSelection.length} ${$t('views.document.items')}`\n        \"\n      >\n        <template #default=\"{ row }\">\n          <div class=\"flex-between\">\n            {{ row.key }}\n            <div v-if=\"currentMouseId === row.id\">\n              <span class=\"mr-4\">\n                <el-tooltip effect=\"dark\" :content=\"$t('views.document.tag.addValue')\">\n                  <el-button\n                    type=\"primary\"\n                    text\n                    @click.stop=\"openCreateTagDialog(row)\"\n                    v-if=\"permissionPrecise.tag_create(id)\"\n                  >\n                    <AppIcon iconName=\"app-add-outlined\" />\n                  </el-button>\n                </el-tooltip>\n              </span>\n              <span class=\"mr-4\">\n                <el-tooltip effect=\"dark\" :content=\"$t('views.document.tag.edit')\">\n                  <el-button\n                    type=\"primary\"\n                    text\n                    @click.stop=\"editTagKey(row)\"\n                    v-if=\"permissionPrecise.tag_edit(id)\"\n                  >\n                    <AppIcon iconName=\"app-edit\" />\n                  </el-button>\n                </el-tooltip>\n              </span>\n              <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click.stop=\"delTag(row)\"\n                  v-if=\"permissionPrecise.tag_delete(id)\"\n                >\n                  <AppIcon iconName=\"app-delete\" />\n                </el-button>\n              </el-tooltip>\n            </div>\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('views.document.tag.value')\" class-name=\"border-l\">\n        <template #default=\"{ row }\">\n          <div class=\"flex-between\">\n            {{ row.value }}\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('views.document.tag.relatedDoc')\" align=\"right\">\n        <template #default=\"{ row }\">\n          <el-link type=\"primary\" underline @click=\"openTagLinkedDocumentDialog(row)\">\n            {{ row.doc_count }}\n          </el-link>\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"100\" fixed=\"right\">\n        <template #default=\"{ row }\">\n          <span class=\"mr-4\">\n            <el-tooltip effect=\"dark\" :content=\"$t('views.document.tag.editValue')\">\n              <el-button\n                type=\"primary\"\n                text\n                @click.stop=\"editTagValue(row)\"\n                v-if=\"permissionPrecise.tag_edit(id)\"\n              >\n                <AppIcon iconName=\"app-edit\" />\n              </el-button>\n            </el-tooltip>\n          </span>\n          <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\">\n            <el-button\n              type=\"primary\"\n              text\n              @click.stop=\"delTagValue(row)\"\n              v-if=\"permissionPrecise.tag_delete(id)\"\n            >\n              <AppIcon iconName=\"app-delete\" />\n            </el-button>\n          </el-tooltip>\n        </template>\n      </el-table-column>\n    </el-table>\n    <div class=\"mt-16 flex justify-end\">\n      <el-pagination\n        v-model:current-page=\"pageNum\"\n        v-model:page-size=\"pageSize\"\n        :total=\"groupedByKey.length\"\n        layout=\"total, prev, pager, next, sizes\"\n        :page-sizes=\"[10, 20, 50, 100]\"\n      />\n    </div>\n  </el-drawer>\n  <CreateTagDialog ref=\"createTagDialogRef\" @refresh=\"handleDialogRefresh\" />\n  <EditTagDialog ref=\"editTagDialogRef\" @refresh=\"handleDialogRefresh\" />\n  <TaglinkedDocumentDialog ref=\"taglinkedDocumentDialogRef\" @refresh=\"handleDialogRefresh\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref, nextTick } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport CreateTagDialog from './CreateTagDialog.vue'\nimport TaglinkedDocumentDialog from './TaglinkedDocumentDialog.vue'\nimport { MsgConfirm } from '@/utils/message.ts'\nimport { t } from '@/locales'\nimport EditTagDialog from '@/views/document/tag/EditTagDialog.vue'\nimport permissionMap from '@/permission'\n\nconst emit = defineEmits(['refresh', 'tag-changed'])\n\nfunction notifyTagChanged() {\n  emit('tag-changed')\n}\n\nfunction handleDialogRefresh() {\n  getList()\n  notifyTagChanged()\n}\n\nconst route = useRoute()\nconst {\n  params: { id, folderId }, // id为knowledgeID\n} = route as any\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst loading = ref(false)\nconst debugVisible = ref(false)\nconst filterText = ref('')\nconst tags = ref<Array<any>>([])\nconst currentMouseId = ref<number | null>(null)\nconst pageNum = ref(1)\nconst pageSize = ref(20)\nconst tableMaxHeight = computed(() => `calc(100vh - 200px)`)\n\nfunction cellMouseEnter(row: any, column: any) {\n  if (column && column.property === 'key') {\n    currentMouseId.value = row.id\n  }\n}\n\nfunction cellMouseLeave() {\n  currentMouseId.value = null\n}\n\n// 1) 仍然把后端全量 tags 转成“扁平行”，每行带上 keyIndex\nconst tableData = computed(() => {\n  const result: any[] = []\n  tags.value.forEach((tag: any) => {\n    if (tag.values && tag.values.length > 0) {\n      tag.values.forEach((value: any, index: number) => {\n        result.push({\n          id: value.id,\n          key: tag.key,\n          value: value.value,\n          doc_count: value.doc_count,\n          keyIndex: index, // 同一个 key 下第几行\n        })\n      })\n    }\n  })\n  return result\n})\n\n// 2) 按 key 分组（保持 key 的出现顺序）\nconst groupedByKey = computed(() => {\n  const map = new Map<string, any[]>()\n  for (const row of tableData.value) {\n    if (!map.has(row.key)) map.set(row.key, [])\n    map.get(row.key)!.push(row)\n  }\n  // 每个元素代表一个 key 分组\n  return Array.from(map.entries()).map(([key, rows]) => ({ key, rows }))\n})\n\n// 3) 按“key 分组”做分页：每页 pageSize 个 key\nconst pagedGroups = computed(() => {\n  const start = (pageNum.value - 1) * pageSize.value\n  const end = start + pageSize.value\n  return groupedByKey.value.slice(start, end)\n})\n\n// 4) 当前页表格数据：把当前页的若干个 key 分组展开为行\nconst pagedTableData = computed(() => {\n  return pagedGroups.value.flatMap((g) => g.rows)\n})\n\n// 5) 合并单元格：只在当前页内合并，同一个 key 的第一行 rowspan=该 key 在当前页的行数\nconst spanMethod = ({ row, columnIndex }: any) => {\n  // 注意：你现在有 selection 列，所以 key 列索引是 1；如需同时合并 value 列按需调整\n  if (columnIndex === 0 || columnIndex === 1) {\n    if (row.keyIndex === 0) {\n      const sameKeyCount = pagedTableData.value.filter((item) => item.key === row.key).length\n      return { rowspan: sameKeyCount, colspan: 1 }\n    }\n    return { rowspan: 0, colspan: 0 }\n  }\n}\n\nconst multipleSelection = ref<any[]>([])\nconst tableRef = ref<any>(null)\nconst syncingSelection = ref(false)\n\nconst handleSelectionChange = async (val: any[]) => {\n  if (syncingSelection.value) return\n\n  // 当前已选中的 id 集合（用于判断哪些行刚刚被取消）\n  const selectedIds = new Set(val.map((r) => r.id))\n\n  // 找出“刚被取消选中的行”\n  const deselectedRows = multipleSelection.value.filter((r) => !selectedIds.has(r.id))\n  if (deselectedRows.length === 0) {\n    multipleSelection.value = val\n    return\n  }\n\n  // 取消选中时：把同 key 分组里其它行也一并取消\n  syncingSelection.value = true\n  await nextTick()\n\n  for (const dr of deselectedRows) {\n    const sameGroupRows = pagedTableData.value.filter((r) => r.key === dr.key)\n    for (const r of sameGroupRows) {\n      if (!selectedIds.has(r.id)) continue\n      tableRef.value?.toggleRowSelection?.(r, false)\n    }\n  }\n\n  await nextTick()\n  syncingSelection.value = false\n\n  // 以表格最终状态为准更新缓存（这里直接用传入 val 可能已过期）\n  // 简化：重新从表格取 selection（Element Plus 有 store，没暴露就用 val\\+补丁）\n  multipleSelection.value = pagedTableData.value.filter((r) =>\n    tableRef.value?.getSelectionRows\n      ? tableRef.value.getSelectionRows().some((s: any) => s.id === r.id)\n      : selectedIds.has(r.id),\n  )\n}\n\nconst createTagDialogRef = ref()\n\nfunction openCreateTagDialog(row?: any) {\n  createTagDialogRef.value?.open(row)\n}\n\nfunction batchDelete() {\n  MsgConfirm(t('views.document.tag.deleteConfirm'), t('views.document.tag.deleteTip'), {\n    confirmButtonText: t('common.delete'),\n    confirmButtonClass: 'danger',\n  })\n    .then(() => {\n      const tagsToDelete = multipleSelection.value.map((item) => item.id)\n      loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n        .delMulTag(id, tagsToDelete)\n        .then(() => {\n          getList()\n          notifyTagChanged()\n        })\n    })\n    .catch(() => {})\n}\n\nconst editTagDialogRef = ref()\n\nfunction editTagKey(row: any) {\n  editTagDialogRef.value?.open(row, true)\n}\n\nfunction delTag(row: any) {\n  MsgConfirm(t('views.document.tag.deleteConfirm') + row.key, t('views.document.tag.deleteTip'), {\n    confirmButtonText: t('common.delete'),\n    confirmButtonClass: 'danger',\n  })\n    .then(() => {\n      loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n        .delTag(id, row.id, 'key')\n        .then(() => {\n          getList()\n          notifyTagChanged()\n        })\n    })\n    .catch(() => {})\n}\n\nconst taglinkedDocumentDialogRef = ref()\n\nconst openTagLinkedDocumentDialog = (row: any) => {\n  taglinkedDocumentDialogRef.value?.open(row)\n}\n\nfunction editTagValue(row: any) {\n  editTagDialogRef.value?.open(row, false)\n}\n\nfunction delTagValue(row: any) {\n  MsgConfirm(t('views.document.tag.deleteConfirm') + row.value, t('views.document.tag.deleteTip'), {\n    confirmButtonText: t('common.delete'),\n    confirmButtonClass: 'danger',\n  })\n    .then(() => {\n      loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n        .delTag(id, row.id, 'one')\n        .then(() => {\n          getList()\n          notifyTagChanged()\n        })\n    })\n    .catch(() => {})\n}\n\nfunction getList() {\n  const params = {\n    ...(filterText.value && { name: filterText.value }),\n  }\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value, isShared: isShared.value })\n    .getTags(id, params, loading)\n    .then((res: any) => {\n      tags.value = res.data\n      pageNum.value = 1\n    })\n}\n\nconst open = () => {\n  filterText.value = ''\n  debugVisible.value = true\n  pageNum.value = 1\n  getList()\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/document/tag/TagSettingDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"debugVisible\" size=\"60%\" :append-to-body=\"true\">\n    <template #header>\n      <h4>{{ $t('views.document.tag.setting') }}</h4>\n    </template>\n    <div class=\"flex-between mb-16\">\n      <div>\n        <el-button type=\"primary\" @click=\"openAddTagDialog()\">\n          {{ $t('views.document.tag.addTag') }}\n        </el-button>\n        <el-button :disabled=\"multipleSelection.length === 0\" @click=\"batchDelete\">\n          {{ $t('common.delete') }}\n        </el-button>\n      </div>\n      <el-input\n        v-model=\"filterText\"\n        prefix-icon=\"Search\"\n        class=\"w-240\"\n        @change=\"getList\"\n        clearable\n        :placeholder=\"$t('common.search')\"\n      />\n    </div>\n    <el-table\n      :data=\"tableData\"\n      :span-method=\"spanMethod\"\n      v-loading=\"loading\"\n      @selection-change=\"handleSelectionChange\"\n    >\n      <el-table-column type=\"selection\" width=\"55\" />\n      <el-table-column :label=\"$t('views.document.tag.key')\">\n        <template #default=\"{ row }\">\n          <div class=\"flex-between\">\n            {{ row.key }}\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('views.document.tag.value')\" class-name=\"border-l\">\n        <template #default=\"{ row }\">\n          {{ row.value }}\n        </template>\n      </el-table-column>\n      <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"100\" fixed=\"right\">\n        <template #default=\"{ row }\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\">\n            <el-button type=\"primary\" text @click.stop=\"delTagValue(row)\">\n              <AppIcon iconName=\"app-delete\" />\n            </el-button>\n          </el-tooltip>\n        </template>\n      </el-table-column>\n    </el-table>\n  </el-drawer>\n  <AddTagDialog ref=\"addTagDialogRef\" @addTags=\"addTags\" :apiType=\"apiType\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport { MsgConfirm } from '@/utils/message.ts'\nimport { t } from '@/locales'\nimport AddTagDialog from '@/views/document/tag/MulAddTagDialog.vue'\n\nconst emit = defineEmits(['refresh'])\nconst route = useRoute()\nconst {\n  params: { id, folderId }, // id为knowledgeID\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nconst document_id = ref('')\nconst loading = ref(false)\nconst debugVisible = ref(false)\nconst filterText = ref('')\nconst tags = ref<Array<any>>([])\n\n// 将原始数据转换为表格数据\nconst tableData = computed(() => {\n  const result: any[] = []\n  tags.value.forEach((tag: any) => {\n    if (tag.values && tag.values.length > 0) {\n      tag.values.forEach((value: any, index: number) => {\n        result.push({\n          id: value.id,\n          key: tag.key,\n          value: value.value,\n          keyIndex: index, // 用于判断是否为第一行\n        })\n      })\n    }\n  })\n  return result\n})\n\n// 合并单元格方法\nconst spanMethod = ({ row, column, rowIndex, columnIndex }: any) => {\n  if (columnIndex === 0 || columnIndex === 1) {\n    // key列 (由于添加了选择列，索引变为1)\n    if (row.keyIndex === 0) {\n      // 计算当前key有多少个值\n      const sameKeyCount = tableData.value.filter((item) => item.key === row.key).length\n      return {\n        rowspan: sameKeyCount,\n        colspan: 1,\n      }\n    } else {\n      return {\n        rowspan: 0,\n        colspan: 0,\n      }\n    }\n  }\n}\n\nconst multipleSelection = ref<any[]>([])\nconst handleSelectionChange = (val: any[]) => {\n  multipleSelection.value = val\n}\n\nfunction batchDelete() {\n  const tagsToDelete = multipleSelection.value.reduce((acc, item) => {\n    // 找出当前选中项的key对应的所有value id\n    const sameKeyItems = tableData.value.filter((data) => data.key === item.key)\n    const sameKeyIds = sameKeyItems.map((data) => data.id)\n    return [...acc, ...sameKeyIds]\n  }, [] as string[])\n\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .delMulDocumentTag(id, document_id.value, tagsToDelete, loading)\n    .then(() => {\n      getList()\n      emit('refresh')\n    })\n}\n\nfunction delTagValue(row: any) {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .delMulDocumentTag(id, document_id.value, [row.id], loading)\n    .then(() => {\n      getList()\n      emit('refresh')\n    })\n}\n\nfunction getList() {\n  const params = {\n    ...(filterText.value && { name: filterText.value }),\n  }\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .getDocumentTags(id, document_id.value, params, loading)\n    .then((res: any) => {\n      tags.value = res.data\n    })\n}\n\nconst addTagDialogRef = ref()\n\nfunction openAddTagDialog() {\n  addTagDialogRef.value?.open()\n}\n\nfunction addTags(tags: any) {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .postDocumentTags(id, document_id.value, tags, loading)\n    .then(() => {\n      addTagDialogRef.value?.close()\n      getList()\n      emit('refresh')\n    })\n}\n\nconst open = (doc: any) => {\n  filterText.value = ''\n  debugVisible.value = true\n  document_id.value = doc.id\n\n  getList()\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/document/tag/TaglinkedDocumentDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    width=\"1000\"\n    align-center\n    :before-close=\"close\"\n    append-to-body\n    destroy-on-close\n  >\n    <template #header>\n      <h4>{{ currentTag.key }}: {{ currentTag.value }}</h4>\n    </template>\n    <div>\n      <el-tabs v-model=\"activeTab\" @tab-change=\"handleTabChange\">\n        <el-tab-pane :label=\"$t('views.document.tag.relatedDoc')\" name=\"linked\" />\n        <el-tab-pane :label=\"$t('views.document.tag.unrelatedDoc')\" name=\"unlinked\" />\n      </el-tabs>\n    </div>\n    <div class=\"flex-between\">\n      <el-button :disabled=\"multipleSelection.length === 0 || loading\" @click=\"batchOperate\">{{\n        activeTab === 'linked' ? $t('views.document.tag.unrelate') : $t('views.document.tag.relate')\n      }}</el-button>\n\n      <el-input\n        v-model=\"filterText\"\n        prefix-icon=\"Search\"\n        class=\"w-240\"\n        @change=\"handleSearch\"\n        clearable\n        :placeholder=\"$t('common.search')\"\n      />\n    </div>\n    <app-table\n      ref=\"multipleTableRef\"\n      :pagination-config=\"paginationConfig\"\n      @sizeChange=\"handleSizeChange\"\n      @changePage=\"getList\"\n      :data=\"tableData\"\n      :row-key=\"(row: any) => row.id\"\n      class=\"mt-16\"\n      @selection-change=\"handleSelectionChange\"\n      style=\"min-height: 400px\"\n      v-loading=\"loading\"\n    >\n      <el-table-column type=\"selection\" width=\"55\" :reserve-selection=\"true\" v-if=\"!isShared\" />\n      <el-table-column\n        prop=\"name\"\n        :label=\"\n          multipleSelection.length === 0\n            ? $t('views.document.table.name')\n            : `${$t('common.selected')} ${multipleSelection.length} ${$t('views.document.items')}`\n        \"\n        min-width=\"280\"\n        show-overflow-tooltip\n      >\n        <template #default=\"{ row }\">\n          <el-space :size=\"8\">\n            <img :src=\"getImgUrl(row && row?.name)\" alt=\"\" width=\"24\" />\n            <span class=\"ellipsis\" style=\"max-width: 450px\">{{ row.name }}</span>\n          </el-space>\n        </template>\n      </el-table-column>\n      <el-table-column width=\"130\">\n        <template #header>\n          <div>\n            <span>{{ $t('views.document.enableStatus.label') }}</span>\n            <el-dropdown trigger=\"click\" @command=\"dropdownHandle\">\n              <el-button\n                style=\"margin-top: 1px\"\n                link\n                :type=\"filterMethod['is_active'] ? 'primary' : ''\"\n              >\n                <el-icon>\n                  <Filter />\n                </el-icon>\n              </el-button>\n              <template #dropdown>\n                <el-dropdown-menu style=\"width: 100px\">\n                  <el-dropdown-item\n                    :class=\"filterMethod['is_active'] === '' ? 'is-active' : ''\"\n                    :command=\"beforeCommand('is_active', '')\"\n                    class=\"justify-center\"\n                    >{{ $t('common.status.all') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    :class=\"filterMethod['is_active'] === true ? 'is-active' : ''\"\n                    class=\"justify-center\"\n                    :command=\"beforeCommand('is_active', true)\"\n                    >{{ $t('common.status.enabled') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    :class=\"filterMethod['is_active'] === false ? 'is-active' : ''\"\n                    class=\"justify-center\"\n                    :command=\"beforeCommand('is_active', false)\"\n                    >{{ $t('common.status.disabled') }}\n                  </el-dropdown-item>\n                </el-dropdown-menu>\n              </template>\n            </el-dropdown>\n          </div>\n        </template>\n        <template #default=\"{ row }\">\n          <div v-if=\"row.is_active\" class=\"flex align-center\">\n            <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n              <SuccessFilled />\n            </el-icon>\n            <span class=\"color-text-primary\">\n              {{ $t('common.status.enabled') }}\n            </span>\n          </div>\n          <div v-else class=\"flex align-center\">\n            <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n            <span class=\"color-text-primary\">\n              {{ $t('common.status.disabled') }}\n            </span>\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column prop=\"create_time\" :label=\"$t('common.createTime')\" width=\"175\" sortable>\n        <template #default=\"{ row }\">\n          {{ datetimeFormat(row.create_time) }}\n        </template>\n      </el-table-column>\n      <el-table-column\n        :label=\"$t('common.operation')\"\n        align=\"left\"\n        width=\"80\"\n        fixed=\"right\"\n        v-if=\"!isShared\"\n      >\n        <template #default=\"{ row }\">\n          <el-tooltip\n            v-if=\"activeTab === 'linked'\"\n            effect=\"dark\"\n            :content=\"$t('views.document.tag.unrelate')\"\n            placement=\"top\"\n          >\n            <span class=\"mr-4\">\n              <el-button type=\"primary\" text @click.stop=\"rowOperate(row)\">\n                <AppIcon iconName=\"app-unlink\"></AppIcon>\n              </el-button>\n            </span>\n          </el-tooltip>\n          <el-tooltip\n            v-else\n            effect=\"dark\"\n            :content=\"$t('views.document.tag.relate')\"\n            placement=\"top\"\n          >\n            <span class=\"mr-4\">\n              <el-button type=\"primary\" text @click.stop=\"rowOperate(row)\">\n                <AppIcon iconName=\"app-generate-question\"></AppIcon>\n              </el-button>\n            </span>\n          </el-tooltip>\n        </template>\n      </el-table-column>\n    </app-table>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport { cloneDeep } from 'lodash'\nimport type { ElTable } from 'element-plus'\nimport { datetimeFormat } from '@/utils/time'\nimport { getImgUrl } from '@/utils/common'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport type { TabPaneName } from 'element-plus'\n\nconst route = useRoute()\nconst {\n  params: { id, folderId, type }, // id为knowledgeID\n} = route as any\nconst emit = defineEmits(['refresh'])\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\n\ntype TabType = 'linked' | 'unlinked'\n\nconst activeTab = ref<TabType>('linked')\n\nfunction handleTabChange(tabName: TabPaneName) {\n  activeTab.value = (tabName as TabType) || 'linked'\n  resetPage()\n  multipleSelection.value = []\n  multipleTableRef.value?.clearSelection()\n  filterMethod.value = {}\n  filterText.value = ''\n  getList()\n}\n\nfunction handleSearch() {\n  resetPage()\n  getList()\n}\n\nconst paginationConfig = ref({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\nconst filterText = ref<string>('')\nconst currentTag = ref<any>({})\nconst tableData = ref<Array<any>>([])\nconst multipleSelection = ref<any[]>([])\nconst multipleTableRef = ref<InstanceType<typeof ElTable>>()\n\nconst filterMethod = ref<any>({})\nconst orderBy = ref<string>('')\n\nfunction dropdownHandle(obj: any) {\n  filterMethod.value = {\n    ...filterMethod.value,\n    [obj.attr]: obj.command,\n  }\n  resetPage()\n  getList()\n}\n\nfunction beforeCommand(attr: string, val: any, task_type?: number) {\n  return {\n    attr: attr,\n    command: val,\n    task_type,\n  }\n}\n\nconst handleSelectionChange = (val: any[]) => {\n  multipleSelection.value = val\n}\n\nfunction afterOperateSuccess() {\n  multipleSelection.value = []\n  multipleTableRef.value?.clearSelection()\n  resetPage()\n  getList()\n  emit('refresh')\n}\n\nfunction operate(docIds: string[]) {\n  if (!currentTag.value?.id || docIds.length === 0) return\n\n  const res = activeTab.value === 'linked' ? unrelateDocuments(docIds) : relateDocuments(docIds)\n  res.then(() => {\n    MsgSuccess(t('common.settingSuccess'))\n    afterOperateSuccess()\n  })\n}\n\nfunction handleSizeChange() {\n  paginationConfig.value.current_page = 1\n  getList()\n}\n\nfunction batchOperate() {\n  if (!currentTag.value.id || multipleSelection.value.length === 0) return\n\n  const docIds = multipleSelection.value.map((item: any) => item.id)\n  operate(docIds)\n}\n\nfunction rowOperate(row: any) {\n  if (!currentTag.value?.id) return\n  operate([row.id])\n}\n\nfunction relateDocuments(doc_ids: string[]) {\n  return loadSharedApi({\n    type: 'document',\n    isShared: isShared.value,\n    systemType: apiType.value,\n  }).postMulDocumentTags(\n    id as string,\n    { tag_ids: [currentTag.value.id], document_ids: doc_ids },\n    loading,\n  )\n}\n\nfunction unrelateDocuments(doc_ids: string[]) {\n  return loadSharedApi({\n    type: 'document',\n    isShared: isShared.value,\n    systemType: apiType.value,\n  }).delDocsTag(id as string, currentTag.value.id, doc_ids, loading)\n}\n\nfunction resetPage() {\n  paginationConfig.value.current_page = 1\n}\n\nfunction getList() {\n  if (!currentTag.value?.id) {\n    tableData.value = []\n    paginationConfig.value.total = 0\n    return\n  }\n  multipleSelection.value = []\n  const params = {\n    ...filterMethod.value,\n    folder_id: folderId,\n    order_by: orderBy.value,\n    'tags[]': [currentTag.value.id],\n  }\n  if (filterText.value) {\n    params.name = filterText.value\n  }\n  if (activeTab.value === 'unlinked') {\n    params.tag_exclude = true\n  }\n\n  loadSharedApi({ type: 'document', isShared: isShared.value, systemType: apiType.value })\n    .getDocumentPage(id as string, paginationConfig.value, params, loading)\n    .then((res: any) => {\n      tableData.value = res?.data?.records || []\n      paginationConfig.value.total = res?.data?.total || 0\n    })\n}\n\nconst open = (row?: any) => {\n  filterText.value = ''\n  filterMethod.value = {}\n  activeTab.value = 'linked'\n  orderBy.value = ''\n  tableData.value = []\n  multipleSelection.value = []\n  multipleTableRef.value?.clearSelection()\n  paginationConfig.value = {\n    current_page: 1,\n    page_size: 10,\n    total: 0,\n  }\n  currentTag.value = cloneDeep(row || {})\n  dialogVisible.value = true\n  getList()\n}\n\nconst close = () => {\n  multipleSelection.value = []\n  multipleTableRef.value?.clearSelection()\n  dialogVisible.value = false\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped>\n.tag-list-max-list {\n  max-height: calc(100vh - 260px);\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/document/upload/ResultSuccess.vue",
    "content": "<template>\n  <el-scrollbar>\n    <el-result icon=\"color-success\" :title=\"`🎉 ${$t('views.knowledge.ResultSuccess.title')} 🎉`\">\n      <template #sub-title>\n        <div class=\"mt-8\">\n          <span class=\"bold\">{{ data?.document_list.length || 0 }}</span>\n          <el-text type=\"info\" class=\"ml-4\">{{ $t('common.fileUpload.document') }}</el-text>\n          <el-divider direction=\"vertical\" />\n          <span class=\"bold\">{{ paragraph_count || 0 }}</span>\n          <el-text type=\"info\" class=\"ml-4\">{{\n            $t('views.knowledge.ResultSuccess.paragraph')\n          }}</el-text>\n          <el-divider direction=\"vertical\" />\n          <span class=\"bold\">{{ numberFormat(char_length) || 0 }}</span>\n          <el-text type=\"info\" class=\"ml-4\">{{ $t('common.character') }} </el-text>\n        </div>\n      </template>\n      <template #extra>\n        <el-button\n          v-if=\"apiType === 'workspace'\"\n          @click=\"\n            router.push({\n              path: `/knowledge`,\n            })\n          \"\n          >{{ $t('views.knowledge.ResultSuccess.buttons.toknowledge') }}</el-button\n        >\n        <el-button\n          v-else\n          @click=\"\n            router.push({\n              path: `/system/${folderId}/knowledge`,\n            })\n          \"\n          >{{ $t('views.knowledge.ResultSuccess.buttons.toknowledge') }}</el-button\n        >\n        <el-button\n          type=\"primary\"\n          @click=\"\n            router.push({\n              path: `/knowledge/${data?.id}/${folderId}/${type}/document`,\n            })\n          \"\n          >{{ $t('views.knowledge.ResultSuccess.buttons.toDocument') }}</el-button\n        >\n      </template>\n    </el-result>\n    <div class=\"result-success\">\n      <p class=\"bolder\">{{ $t('views.knowledge.ResultSuccess.documentList') }}</p>\n      <el-card\n        shadow=\"never\"\n        class=\"mt-8\"\n        style=\"--el-card-padding: 8px 12px; line-height: normal\"\n        v-for=\"(item, index) in data?.document_list\"\n        :key=\"index\"\n      >\n        <div class=\"flex-between\">\n          <div class=\"flex\">\n            <img :src=\"getImgUrl(item && item?.name)\" alt=\"\" width=\"40\" />\n            <div class=\"ml-8\">\n              <p>{{ item && item?.name }}</p>\n              <el-text type=\"info\" size=\"small\">{{ filesize(item && item?.char_length) }}</el-text>\n            </div>\n          </div>\n          <div>\n            <el-text type=\"info\" class=\"mr-16\"\n              >{{ item && item?.paragraph_count }}\n              {{ $t('views.knowledge.ResultSuccess.paragraph_count') }}</el-text\n            >\n            <el-text v-if=\"item.status === '1'\">\n              <el-icon class=\"color-success\"><SuccessFilled /></el-icon>\n            </el-text>\n            <el-text v-else-if=\"item.status === '2'\">\n              <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n            </el-text>\n            <el-text v-else-if=\"item.status === '0'\">\n              <el-icon class=\"is-loading primary\"><Loading /></el-icon>\n              {{ $t('views.knowledge.ResultSuccess.loading') }}...\n            </el-text>\n          </div>\n        </div>\n      </el-card>\n    </div>\n  </el-scrollbar>\n</template>\n<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport { getImgUrl, filesize, numberFormat } from '@/utils/common'\n\nconst props = defineProps({\n  data: {\n    type: Object,\n    default: () => {},\n  },\n})\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  params: { id, folderId, type }, // id为knowledgeID\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst paragraph_count = computed(() =>\n  props.data?.document_list.reduce((sum: number, obj: any) => (sum += obj.paragraph_count), 0),\n)\n\nconst char_length = computed(\n  () =>\n    props.data?.document_list.reduce((sum: number, obj: any) => (sum += obj.char_length), 0) || 0,\n)\n</script>\n<style scoped lang=\"scss\">\n.result-success {\n  width: 70%;\n  margin: 0 auto;\n  margin-bottom: 30px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/document/upload/SetRules.vue",
    "content": "<template>\n  <div class=\"set-rules\">\n    <el-row>\n      <el-col :span=\"10\" class=\"p-24\">\n        <h4 class=\"title-decoration-1 mb-16\">{{ $t('views.document.setRules.title.setting') }}</h4>\n        <div class=\"set-rules__right\">\n          <el-scrollbar>\n            <div class=\"left-height\" @click.stop>\n              <el-radio-group v-model=\"radio\" class=\"card__radio\">\n                <el-card shadow=\"never\" class=\"mb-16\" :class=\"radio === '1' ? 'border-active' : ''\">\n                  <el-radio value=\"1\" size=\"large\">\n                    <p class=\"mb-4\">{{ $t('views.document.setRules.intelligent.label') }}</p>\n                    <el-text type=\"info\">{{\n                      $t('views.document.setRules.intelligent.text')\n                    }}</el-text>\n                  </el-radio>\n                </el-card>\n                <el-card shadow=\"never\" class=\"mb-16\" :class=\"radio === '2' ? 'border-active' : ''\">\n                  <el-radio value=\"2\" size=\"large\">\n                    <p class=\"mb-4\">{{ $t('views.document.setRules.advanced.label') }}</p>\n                    <el-text type=\"info\">\n                      {{ $t('views.document.setRules.advanced.text') }}\n                    </el-text>\n                  </el-radio>\n\n                  <el-card\n                    v-if=\"radio === '2'\"\n                    shadow=\"never\"\n                    class=\"card-never mt-16\"\n                    style=\"margin-left: 30px\"\n                  >\n                    <div class=\"set-rules__form\">\n                      <div class=\"form-item mb-16\">\n                        <div class=\"title flex align-center mb-8\">\n                          <span style=\"margin-right: 4px\">{{\n                            $t('views.document.setRules.patterns.label')\n                          }}</span>\n                          <el-tooltip\n                            effect=\"dark\"\n                            :content=\"$t('views.document.setRules.patterns.tooltip')\"\n                            placement=\"right\"\n                          >\n                            <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                          </el-tooltip>\n                        </div>\n                        <div @click.stop>\n                          <el-select\n                            v-model=\"form.patterns\"\n                            multiple\n                            :reserve-keyword=\"false\"\n                            allow-create\n                            default-first-option\n                            filterable\n                            :placeholder=\"$t('views.document.setRules.patterns.placeholder')\"\n                          >\n                            <el-option\n                              v-for=\"(item, index) in splitPatternList\"\n                              :key=\"index\"\n                              :label=\"item.key\"\n                              :value=\"item.value\"\n                            >\n                            </el-option>\n                          </el-select>\n                        </div>\n                      </div>\n                      <div class=\"form-item mb-16\">\n                        <div class=\"title mb-8\">\n                          {{ $t('views.document.setRules.limit.label') }}\n                        </div>\n                        <el-slider\n                          v-model=\"form.limit\"\n                          show-input\n                          :show-input-controls=\"false\"\n                          :min=\"50\"\n                          :max=\"100000\"\n                        />\n                      </div>\n                      <div class=\"form-item mb-16\">\n                        <div class=\"title mb-8\">\n                          {{ $t('views.document.setRules.with_filter.label') }}\n                        </div>\n                        <el-switch size=\"small\" v-model=\"form.with_filter\" />\n                        <div style=\"margin-top: 4px\">\n                          <el-text type=\"info\">\n                            {{ $t('views.document.setRules.with_filter.text') }}</el-text\n                          >\n                        </div>\n                      </div>\n                    </div>\n                  </el-card>\n                </el-card>\n              </el-radio-group>\n            </div>\n          </el-scrollbar>\n          <div>\n            <el-checkbox\n              v-model=\"checkedConnect\"\n              @change=\"changeHandle\"\n              style=\"white-space: normal\"\n            >\n              {{ $t('views.document.setRules.checkedConnect.label') }}\n            </el-checkbox>\n          </div>\n          <div class=\"text-right mt-8\">\n            <el-button @click=\"splitDocument\">\n              {{ $t('views.document.buttons.preview') }}</el-button\n            >\n          </div>\n        </div>\n      </el-col>\n\n      <el-col :span=\"14\" class=\"p-24 border-l\">\n        <div v-loading=\"loading\">\n          <h4 class=\"title-decoration-1 mb-8\">{{ $t('views.document.setRules.title.preview') }}</h4>\n\n          <ParagraphPreview v-model:data=\"paragraphList\" :isConnect=\"checkedConnect\" :knowledge-id=\"id\"/>\n        </div>\n      </el-col>\n    </el-row>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted, reactive, watch } from 'vue'\nimport ParagraphPreview from '@/views/knowledge/component/ParagraphPreview.vue'\nimport { useRoute } from 'vue-router'\nimport { cutFilename } from '@/utils/common'\nimport useStore from '@/stores'\nimport type { KeyValue } from '@/api/type/common'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst { knowledge } = useStore()\nconst documentsFiles = computed(() => knowledge.documentsFiles)\nconst splitPatternList = ref<Array<KeyValue<string, string>>>([])\nconst route = useRoute()\nconst {\n  query: { id}, // id为knowledgeID\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst radio = ref('1')\nconst loading = ref(false)\nconst paragraphList = ref<any[]>([])\nconst patternLoading = ref<boolean>(false)\nconst checkedConnect = ref<boolean>(false)\n\nconst firstChecked = ref(true)\n\nconst form = reactive<{\n  patterns: Array<string>\n  limit: number\n  with_filter: boolean\n  [propName: string]: any\n}>({\n  patterns: [],\n  limit: 500,\n  with_filter: true,\n})\n\nfunction changeHandle(val: boolean) {\n  if (val && firstChecked.value) {\n    paragraphList.value = paragraphList.value.map((item: any) => ({\n      ...item,\n      content: item.content.map((v: any) => ({\n        ...v,\n        problem_list: v.title.trim()\n          ? [\n              {\n                content: v.title.trim(),\n              },\n            ]\n          : [],\n      })),\n    }))\n    firstChecked.value = false\n  }\n}\nfunction splitDocument() {\n  loading.value = true\n  const fd = new FormData()\n  documentsFiles.value.forEach((item) => {\n    if (item?.raw) {\n      fd.append('file', item?.raw)\n    }\n  })\n  if (radio.value === '2') {\n    Object.keys(form).forEach((key) => {\n      if (key == 'patterns') {\n        form.patterns.forEach((item) => fd.append('patterns', item))\n      } else {\n        fd.append(key, form[key])\n      }\n    })\n  }\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .postSplitDocument(id, fd)\n    .then((res: any) => {\n      const list = res.data\n\n      list.map((item: any) => {\n        if (item.name.length > 128) {\n          item.name = cutFilename(item.name, 128)\n        }\n        if (checkedConnect.value) {\n          item.content.map((v: any) => {\n            v['problem_list'] = v.title.trim()\n              ? [\n                  {\n                    content: v.title.trim(),\n                  },\n                ]\n              : []\n          })\n        }\n      })\n\n      paragraphList.value = list\n      loading.value = false\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nconst initSplitPatternList = () => {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .listSplitPattern(id, patternLoading)\n    .then((ok: any) => {\n      splitPatternList.value = ok.data\n    })\n}\n\nwatch(radio, () => {\n  if (radio.value === '2') {\n    initSplitPatternList()\n  }\n})\n\nonMounted(() => {\n  splitDocument()\n})\n\ndefineExpose({\n  paragraphList,\n  checkedConnect,\n  loading,\n})\n</script>\n<style scoped lang=\"scss\">\n.set-rules {\n  width: 100%;\n\n  .left-height {\n    max-height: calc(var(--create-knowledge-height) - 110px);\n    overflow-x: hidden;\n  }\n  &__form {\n    .title {\n      font-size: 14px;\n      font-weight: 400;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/document/upload/UploadComponent.vue",
    "content": "<template>\n  <h4 class=\"title-decoration-1 mb-8\">{{ $t('views.document.uploadDocument') }}</h4>\n  <el-form\n    ref=\"FormRef\"\n    :model=\"form\"\n    :rules=\"rules\"\n    label-position=\"top\"\n    require-asterisk-position=\"right\"\n    v-loading=\"loading\"\n  >\n    <div class=\"mt-16 mb-16\">\n      <el-radio-group v-model=\"form.fileType\" @change=\"radioChange\" class=\"app-radio-button-group\">\n        <el-radio-button value=\"txt\">{{ $t('views.document.fileType.txt.label') }}</el-radio-button>\n        <el-radio-button value=\"table\">{{\n          $t('views.document.fileType.table.label')\n        }}</el-radio-button>\n        <el-radio-button value=\"QA\">{{ $t('views.document.fileType.QA.label') }}</el-radio-button>\n      </el-radio-group>\n    </div>\n\n    <el-form-item prop=\"fileList\" v-if=\"form.fileType === 'QA'\">\n      <div class=\"update-info flex p-8-12 border-r-6 mb-16 w-full\">\n        <div class=\"mt-4\">\n          <AppIcon iconName=\"app-warning-colorful\" style=\"font-size: 16px\"></AppIcon>\n        </div>\n        <div class=\"ml-16 lighter\">\n          <p>\n            {{ $t('views.document.fileType.QA.tip1') }}\n            <el-button type=\"primary\" link @click=\"downloadTemplate('excel')\">\n              {{ $t('views.document.upload.download') }} Excel\n              {{ $t('views.document.upload.template') }}\n            </el-button>\n            <el-button type=\"primary\" link @click=\"downloadTemplate('csv')\">\n              {{ $t('views.document.upload.download') }} CSV\n              {{ $t('views.document.upload.template') }}\n            </el-button>\n          </p>\n          <p>{{ $t('views.document.fileType.QA.tip2') }}</p>\n          <p>\n            3. {{ $t('views.document.tip.fileLimitCountTip1') }} {{ file_count_limit }}\n            {{ $t('views.document.tip.fileLimitCountTip2') }},\n            {{ $t('views.document.tip.fileLimitSizeTip1') }} {{ file_size_limit }} MB\n          </p>\n        </div>\n      </div>\n      <el-upload\n        :webkitdirectory=\"false\"\n        class=\"w-full mb-4\"\n        drag\n        multiple\n        v-model:file-list=\"form.fileList\"\n        action=\"#\"\n        :auto-upload=\"false\"\n        :show-file-list=\"false\"\n        accept=\".xlsx, .xls, .csv,.zip\"\n        :limit=\"file_count_limit\"\n        :on-exceed=\"onExceed\"\n        :on-change=\"fileHandleChange\"\n        @click.prevent=\"handlePreview(false)\"\n      >\n        <img src=\"@/assets/upload-icon.svg\" alt=\"\" />\n        <div class=\"el-upload__text\">\n          <p>\n            {{ $t('views.document.upload.uploadMessage') }}\n            <em class=\"hover\" @click.prevent=\"handlePreview(false)\">\n              {{ $t('views.document.upload.selectFile') }}\n            </em>\n            <em class=\"hove ml-4\" @click.prevent=\"handlePreview(true)\">\n              {{ $t('views.document.upload.selectFiles') }}\n            </em>\n          </p>\n          <div class=\"upload__decoration\">\n            <p>{{ $t('views.document.upload.formats') }}XLS、XLSX、CSV、ZIP</p>\n          </div>\n        </div>\n      </el-upload>\n    </el-form-item>\n    <el-form-item prop=\"fileList\" v-else-if=\"form.fileType === 'table'\">\n      <div class=\"update-info flex p-8-12 border-r-6 mb-16 w-full\">\n        <div class=\"mt-4\">\n          <AppIcon iconName=\"app-warning-colorful\" style=\"font-size: 16px\"></AppIcon>\n        </div>\n        <div class=\"ml-16 lighter\">\n          <p>\n            {{ $t('views.document.fileType.table.tip1') }}\n            <el-button type=\"primary\" link @click=\"downloadTableTemplate('excel')\">\n              {{ $t('views.document.upload.download') }} Excel\n              {{ $t('views.document.upload.template') }}\n            </el-button>\n            <el-button type=\"primary\" link @click=\"downloadTableTemplate('csv')\">\n              {{ $t('views.document.upload.download') }} CSV\n              {{ $t('views.document.upload.template') }}\n            </el-button>\n          </p>\n          <p>{{ $t('views.document.fileType.table.tip2') }}</p>\n          <p>{{ $t('views.document.fileType.table.tip3') }}</p>\n          <p>\n            4. {{ $t('views.document.tip.fileLimitCountTip1') }} {{ file_count_limit }}\n            {{ $t('views.document.tip.fileLimitCountTip2') }},\n            {{ $t('views.document.tip.fileLimitSizeTip1') }} {{ file_size_limit }} MB\n          </p>\n        </div>\n      </div>\n      <el-upload\n        :webkitdirectory=\"false\"\n        class=\"w-full mb-4\"\n        drag\n        multiple\n        v-model:file-list=\"form.fileList\"\n        action=\"#\"\n        :auto-upload=\"false\"\n        :show-file-list=\"false\"\n        accept=\".xlsx, .xls, .csv\"\n        :limit=\"file_count_limit\"\n        :on-exceed=\"onExceed\"\n        :on-change=\"fileHandleChange\"\n        @click.prevent=\"handlePreview(false)\"\n      >\n        <img src=\"@/assets/upload-icon.svg\" alt=\"\" />\n        <div class=\"el-upload__text\">\n          <p>\n            {{ $t('views.document.upload.uploadMessage') }}\n            <em class=\"hover\" @click.prevent=\"handlePreview(false)\">\n              {{ $t('views.document.upload.selectFile') }}\n            </em>\n            <em class=\"hover ml-4\" @click.prevent=\"handlePreview(true)\">\n              {{ $t('views.document.upload.selectFiles') }}\n            </em>\n          </p>\n          <div class=\"upload__decoration\">\n            <p>{{ $t('views.document.upload.formats') }}XLS、XLSX、CSV</p>\n          </div>\n        </div>\n      </el-upload>\n    </el-form-item>\n    <el-form-item prop=\"fileList\" v-else>\n      <div class=\"update-info flex p-8-12 border-r-6 mb-16 w-full\">\n        <div class=\"mt-4\">\n          <AppIcon iconName=\"app-warning-colorful\" style=\"font-size: 16px\"></AppIcon>\n        </div>\n        <div class=\"ml-16 lighter\">\n          <p>{{ $t('views.document.fileType.txt.tip1') }}</p>\n          <p>\n            2. {{ $t('views.document.tip.fileLimitCountTip1') }} {{ file_count_limit }}\n            {{ $t('views.document.tip.fileLimitCountTip2') }},\n            {{ $t('views.document.tip.fileLimitSizeTip1') }} {{ file_size_limit }} MB\n          </p>\n        </div>\n      </div>\n      <el-upload\n        :webkitdirectory=\"false\"\n        class=\"w-full\"\n        drag\n        multiple\n        v-model:file-list=\"form.fileList\"\n        action=\"#\"\n        :auto-upload=\"false\"\n        :show-file-list=\"false\"\n        accept=\".txt, .md, .log, .docx, .pdf, .html,.zip,.xlsx,.xls,.csv\"\n        :limit=\"file_count_limit\"\n        :on-exceed=\"onExceed\"\n        :on-change=\"fileHandleChange\"\n        @click.prevent=\"handlePreview(false)\"\n      >\n        <img src=\"@/assets/upload-icon.svg\" alt=\"\" />\n        <div class=\"el-upload__text\">\n          <p>\n            {{ $t('views.document.upload.uploadMessage') }}\n            <em class=\"hover\" @click.prevent=\"handlePreview(false)\">\n              {{ $t('views.document.upload.selectFile') }}\n            </em>\n            <em class=\"hover ml-4\" @click.prevent=\"handlePreview(true)\">\n              {{ $t('views.document.upload.selectFiles') }}\n            </em>\n          </p>\n          <div class=\"upload__decoration\">\n            <p>\n              {{\n                $t('views.document.upload.formats')\n              }}TXT、Markdown、PDF、DOCX、HTML、XLS、XLSX、CSV、ZIP\n            </p>\n          </div>\n        </div>\n      </el-upload>\n    </el-form-item>\n  </el-form>\n  <el-row :gutter=\"8\" v-if=\"form.fileList?.length\">\n    <template v-for=\"(item, index) in form.fileList\" :key=\"index\">\n      <el-col :span=\"12\" class=\"mb-8\">\n        <el-card shadow=\"never\" style=\"--el-card-padding: 8px 12px; line-height: normal\">\n          <div class=\"flex-between\">\n            <div class=\"flex\">\n              <img :src=\"getImgUrl(item && item?.name)\" alt=\"\" width=\"40\" />\n              <div class=\"ml-8\">\n                <p class=\"ellipsis-1\" :title=\"item && item?.name\">{{ item && item?.name }}</p>\n                <el-text type=\"info\" size=\"small\">{{\n                  filesize(item && item?.size) || '0K'\n                }}</el-text>\n              </div>\n            </div>\n            <el-button text @click=\"deleteFile(index)\">\n              <AppIcon iconName=\"app-delete\"></AppIcon>\n            </el-button>\n          </div>\n        </el-card>\n      </el-col>\n    </template>\n  </el-row>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, onUnmounted, onMounted, computed, watch, nextTick } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { UploadFiles } from 'element-plus'\nimport { filesize, getImgUrl, isRightType } from '@/utils/common'\nimport { MsgError } from '@/utils/message'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport useStore from '@/stores'\nimport { t } from '@/locales'\n\nconst route = useRoute()\nconst {\n  query: { id }, // id为knowledgeID，有id的是上传文档\n} = route\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst { knowledge } = useStore()\nconst documentsFiles = computed(() => knowledge.documentsFiles)\nconst documentsType = computed(() => knowledge.documentsType)\n\nconst FormRef = ref()\nconst loading = ref(false)\n\nconst form = ref({\n  fileType: 'txt',\n  fileList: [] as any,\n})\n\nconst rules = reactive({\n  fileList: [\n    { required: true, message: t('views.document.upload.requiredMessage'), trigger: 'change' },\n  ],\n})\n\nconst file_count_limit = ref(50)\nconst file_size_limit = ref(100)\n\nwatch(form.value, (value) => {\n  knowledge.saveDocumentsType(value.fileType)\n  knowledge.saveDocumentsFile(value.fileList)\n})\n\nfunction downloadTemplate(type: string) {\n  loadSharedApi({ type: 'document', systemType: apiType.value }).exportQATemplate(\n    `${type}${t('views.document.upload.template')}.${type == 'csv' ? type : 'xlsx'}`,\n    type,\n  )\n}\n\nfunction downloadTableTemplate(type: string) {\n  loadSharedApi({ type: 'document', systemType: apiType.value }).exportTableTemplate(\n    `${type}${t('views.document.upload.template')}.${type == 'csv' ? type : 'xlsx'}`,\n    type,\n  )\n}\n\nfunction radioChange() {\n  form.value.fileList = []\n}\n\nfunction deleteFile(index: number | string) {\n  form.value.fileList.splice(index, 1)\n}\n\n// 上传on-change事件\nconst fileHandleChange = (file: any, fileList: UploadFiles) => {\n  //1、判断文件大小是否合法，文件限制不能大于100M\n  const isLimit = file?.size / 1024 / 1024 < file_size_limit.value\n  if (!isLimit) {\n    MsgError(t('views.document.tip.fileLimitSizeTip1') + file_size_limit.value + 'MB')\n    fileList.splice(-1, 1) //移除当前超出大小的文件\n    return false\n  }\n\n  if (!isRightType(file?.name, form.value.fileType)) {\n    if (file?.name !== '.DS_Store') {\n      MsgError(t('views.document.upload.errorMessage2'))\n    }\n    fileList.splice(-1, 1)\n    return false\n  }\n  if (file?.size === 0) {\n    MsgError(t('views.document.upload.errorMessage3'))\n    fileList.splice(-1, 1)\n    return false\n  }\n}\n\nconst onExceed = () => {\n  MsgError(\n    t('views.document.tip.fileLimitCountTip1') +\n      file_count_limit.value +\n      t('views.document.tip.fileLimitCountTip2'),\n  )\n}\n\nconst handlePreview = (bool: boolean) => {\n  let inputDom: any = null\n  nextTick(() => {\n    if (document.querySelector('.el-upload__input') != null) {\n      inputDom = document.querySelector('.el-upload__input')\n      inputDom.webkitdirectory = bool\n    }\n  })\n}\n\n/*\n  表单校验\n*/\nfunction validate() {\n  if (!FormRef.value) return\n  return FormRef.value.validate((valid: any) => {\n    return valid\n  })\n}\n\nfunction getDetail() {\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .getKnowledgeDetail(id, loading)\n    .then((res: any) => {\n      file_count_limit.value = res.data.file_count_limit\n      file_size_limit.value = res.data.file_size_limit\n    })\n}\n\nonMounted(() => {\n  if (documentsType.value) {\n    form.value.fileType = documentsType.value\n  }\n  if (documentsFiles.value) {\n    form.value.fileList = documentsFiles.value\n  }\n  getDetail()\n})\nonUnmounted(() => {\n  form.value = {\n    fileType: 'txt',\n    fileList: [],\n  }\n})\n\ndefineExpose({\n  validate,\n  form,\n})\n</script>\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/error/404.vue",
    "content": "<template>\n  <div class=\"not-found-container flex-center\">\n    <div>\n      <img src=\"@/assets/404.png\" width=\"250\" alt=\"\" />\n      <h4 class=\"text-center\">{{ $t('common.notFound.title') }}</h4>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { useRouter } from 'vue-router'\nconst router = useRouter()\n</script>\n<style lang=\"scss\" scoped>\n.not-found-container {\n  height: 100vh;\n  width: 100vw;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/error/NoPermission.vue",
    "content": "<template>\n  <div class=\"flex-center mt-24\">\n    <div>\n      <img src=\"@/assets/500.png\" width=\"250\" alt=\"\" />\n      <h4 class=\"text-center\">{{ $t('common.notFound.NoPermission') }}</h4>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { useRouter } from 'vue-router'\nconst router = useRouter()\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/error/NoService.vue",
    "content": "<template>\n  <div class=\"not-found-container flex-center\">\n    <div>\n      <img src=\"@/assets/500.png\" width=\"250\" alt=\"\" />\n      <h4 class=\"text-center\">{{ $t('common.notFound.NoService') }}</h4>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { useRouter } from 'vue-router'\nconst router = useRouter()\n</script>\n<style lang=\"scss\" scoped>\n.not-found-container {\n  height: 100vh;\n  width: 100vw;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/hit-test/index.vue",
    "content": "<template>\n  <div class=\"hit-test p-16-24\">\n    <h4>\n      {{ $t('views.application.hitTest.title') }}\n      <el-text type=\"info\" class=\"ml-4\"> {{ $t('views.application.hitTest.text') }}</el-text>\n    </h4>\n    <el-card\n      style=\"--el-card-padding: 0\"\n      class=\"hit-test__main p-16 mt-16 mb-16\"\n      v-loading=\"loading\"\n    >\n      <div class=\"question-title\" :style=\"{ visibility: questionTitle ? 'visible' : 'hidden' }\">\n        <div class=\"avatar\">\n          <el-avatar>\n            <img src=\"@/assets/user-icon.svg\" style=\"width: 54%\" alt=\"\" />\n          </el-avatar>\n        </div>\n        <div class=\"content ml-12\">\n          <h4 class=\"text break-all ellipsis-1\" style=\"width: 66%\" :title=\"questionTitle\">\n            {{ questionTitle }}\n          </h4>\n        </div>\n      </div>\n      <el-scrollbar>\n        <div :style=\"{ height: user.isExpire() ? 'calc(100vh - 340px)' : 'calc(100vh - 300px)' }\">\n          <el-empty\n            v-if=\"first\"\n            :image=\"emptyImg\"\n            :description=\"$t('views.application.hitTest.emptyMessage1')\"\n            style=\"padding-top: 160px\"\n            :image-size=\"125\"\n          />\n          <el-empty\n            v-else-if=\"paragraphDetail.length == 0\"\n            :description=\"$t('views.application.hitTest.emptyMessage2')\"\n            style=\"padding-top: 160px\"\n            :image-size=\"125\"\n          />\n          <el-row v-else>\n            <el-col\n              :xs=\"24\"\n              :sm=\"12\"\n              :md=\"12\"\n              :lg=\"8\"\n              :xl=\"6\"\n              v-for=\"(item, index) in paragraphDetail\"\n              :key=\"index\"\n              class=\"p-8\"\n            >\n              <CardBox\n                shadow=\"hover\"\n                :title=\"item.title || '-'\"\n                :description=\"item.content\"\n                class=\"document-card layout-bg layout-bg cursor\"\n                :class=\"item.is_active ? '' : 'disabled'\"\n                @click=\"editParagraph(item)\"\n              >\n                <template #icon>\n                  <el-avatar class=\"avatar-light\" :size=\"22\"> {{ index + 1 + '' }}</el-avatar>\n                </template>\n                <template #tag>\n                  <div class=\"primary\">{{ item.similarity?.toFixed(3) }}</div>\n                </template>\n                <template #footer>\n                  <div class=\"footer-content flex-between\">\n                    <el-text>\n                      <el-icon>\n                        <Document />\n                      </el-icon>\n                      {{ item?.document_name }}\n                    </el-text>\n                    <div v-if=\"item.trample_num || item.star_num\">\n                      <span v-if=\"item.star_num\">\n                        <AppIcon iconName=\"app-like-color\"></AppIcon>\n                        {{ item.star_num }}\n                      </span>\n                      <span v-if=\"item.trample_num\" class=\"ml-4\">\n                        <AppIcon iconName=\"app-oppose-color\"></AppIcon>\n                        {{ item.trample_num }}\n                      </span>\n                    </div>\n                  </div>\n                </template>\n              </CardBox>\n            </el-col>\n          </el-row>\n        </div>\n      </el-scrollbar>\n    </el-card>\n    <ParagraphDialog\n      ref=\"ParagraphDialogRef\"\n      :title=\"title\"\n      @refresh=\"refresh\"\n      :apiType=\"apiType\"\n    />\n\n    <div class=\"hit-test__operate\">\n      <el-popover\n        :visible=\"popoverVisible\"\n        placement=\"right-end\"\n        :width=\"500\"\n        trigger=\"click\"\n        :persistent=\"false\"\n      >\n        <template #reference>\n          <el-button\n            class=\"mb-8\"\n            @click=\"settingChange('open')\"\n            v-if=\"!route.path.includes('share/')\"\n          >\n            <AppIcon iconName=\"app-setting\"></AppIcon>\n            {{ $t('common.paramSetting') }}</el-button\n          >\n        </template>\n        <div class=\"mb-16\">\n          <div class=\"title mb-8\">\n            {{ $t('views.application.dialog.selectSearchMode') }}\n          </div>\n          <el-radio-group\n            v-model=\"cloneForm.search_mode\"\n            class=\"card__radio\"\n            @change=\"changeHandle\"\n          >\n            <el-card\n              shadow=\"never\"\n              class=\"mb-16\"\n              :class=\"cloneForm.search_mode === 'embedding' ? 'border-active' : ''\"\n            >\n              <el-radio value=\"embedding\" size=\"large\">\n                <p class=\"mb-4\">\n                  {{ $t('views.application.dialog.vectorSearch') }}\n                </p>\n                <el-text type=\"info\">{{\n                  $t('views.application.dialog.vectorSearchTooltip')\n                }}</el-text>\n              </el-radio>\n            </el-card>\n            <el-card\n              shadow=\"never\"\n              class=\"mb-16\"\n              :class=\"cloneForm.search_mode === 'keywords' ? 'border-active' : ''\"\n            >\n              <el-radio value=\"keywords\" size=\"large\">\n                <p class=\"mb-4\">\n                  {{ $t('views.application.dialog.fullTextSearch') }}\n                </p>\n                <el-text type=\"info\">{{\n                  $t('views.application.dialog.fullTextSearchTooltip')\n                }}</el-text>\n              </el-radio>\n            </el-card>\n            <el-card\n              shadow=\"never\"\n              class=\"mb-16\"\n              :class=\"cloneForm.search_mode === 'blend' ? 'border-active' : ''\"\n            >\n              <el-radio value=\"blend\" size=\"large\">\n                <p class=\"mb-4\">\n                  {{ $t('views.application.dialog.hybridSearch') }}\n                </p>\n                <el-text type=\"info\">{{\n                  $t('views.application.dialog.hybridSearchTooltip')\n                }}</el-text>\n              </el-radio>\n            </el-card>\n          </el-radio-group>\n        </div>\n        <el-row :gutter=\"20\">\n          <el-col :span=\"12\">\n            <div class=\"mb-16\">\n              <div class=\"title mb-8\">\n                {{ $t('views.application.dialog.similarityThreshold') }}\n              </div>\n              <el-input-number\n                v-model=\"cloneForm.similarity\"\n                :min=\"0\"\n                :max=\"cloneForm.search_mode === 'blend' ? 2 : 1\"\n                :precision=\"3\"\n                :step=\"0.1\"\n                :value-on-clear=\"0\"\n                controls-position=\"right\"\n                class=\"w-full\"\n              />\n            </div>\n          </el-col>\n          <el-col :span=\"12\">\n            <div class=\"mb-16\">\n              <div class=\"title mb-8\">\n                {{ $t('views.application.dialog.topReferences') }}\n              </div>\n              <el-input-number\n                v-model=\"cloneForm.top_number\"\n                :min=\"1\"\n                :max=\"10000\"\n                controls-position=\"right\"\n                class=\"w-full\"\n              />\n            </div>\n          </el-col>\n        </el-row>\n\n        <div class=\"text-right\">\n          <el-button @click=\"popoverVisible = false\">{{ $t('common.cancel') }}</el-button>\n          <el-button type=\"primary\" @click=\"settingChange('close')\">{{\n            $t('common.confirm')\n          }}</el-button>\n        </div>\n      </el-popover>\n      <div class=\"operate-textarea flex\" v-if=\"!route.path.includes('share/')\">\n        <el-input\n          ref=\"quickInputRef\"\n          v-model=\"inputValue\"\n          type=\"textarea\"\n          :placeholder=\"$t('common.inputPlaceholder')\"\n          :autosize=\"{ minRows: 1, maxRows: 1 }\"\n          @keydown.enter=\"sendChatHandle($event)\"\n        />\n        <div class=\"operate\">\n          <el-button\n            text\n            class=\"sent-button\"\n            :disabled=\"isDisabledChart || loading\"\n            @click=\"sendChatHandle\"\n          >\n            <img v-show=\"isDisabledChart || loading\" src=\"@/assets/chat/icon_send.svg\" alt=\"\" />\n            <img\n              v-show=\"!isDisabledChart && !loading\"\n              src=\"@/assets/chat/icon_send_colorful.svg\"\n              alt=\"\"\n            />\n          </el-button>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { nextTick, ref, onMounted, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport { cloneDeep } from 'lodash'\nimport ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue'\nimport { arraySort } from '@/utils/array'\nimport emptyImg from '@/assets/hit-test-empty.png'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\nconst { user } = useStore()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst quickInputRef = ref()\nconst ParagraphDialogRef = ref()\nconst loading = ref(false)\nconst paragraphDetail = ref<any[]>([])\nconst title = ref('')\nconst inputValue = ref('')\nconst formInline = ref({\n  similarity: 0.6,\n  top_number: 5,\n  search_mode: 'embedding',\n})\n\n// 第一次加载\nconst first = ref(true)\n\nconst cloneForm = ref<any>({})\n\nconst popoverVisible = ref(false)\nconst questionTitle = ref('')\n\nconst isDisabledChart = computed(() => !inputValue.value)\n\nfunction changeHandle(val: string) {\n  if (val === 'keywords') {\n    cloneForm.value.similarity = 0\n  } else {\n    cloneForm.value.similarity = 0.6\n  }\n}\n\nfunction settingChange(val: string) {\n  if (val === 'open') {\n    popoverVisible.value = true\n    cloneForm.value = cloneDeep(formInline.value)\n  } else if (val === 'close') {\n    popoverVisible.value = false\n    formInline.value = cloneDeep(cloneForm.value)\n  }\n}\n\nfunction editParagraph(row: any) {\n  title.value = t('views.paragraph.paragraphDetail')\n  ParagraphDialogRef.value.open(row)\n}\n\nfunction sendChatHandle(event: any) {\n  if (!event?.ctrlKey && !event?.shiftKey && !event?.altKey && !event?.metaKey) {\n    // 如果没有按下组合键，则会阻止默认事件\n    event.preventDefault()\n    if (!isDisabledChart.value && !loading.value) {\n      getHitTestList()\n    }\n  } else {\n    // 如果同时按下ctrl/shift/cmd/opt +enter，则会换行\n    insertNewlineAtCursor(event)\n  }\n}\nconst insertNewlineAtCursor = (event?: any) => {\n  const textarea = quickInputRef.value.$el.querySelector(\n    '.el-textarea__inner',\n  ) as HTMLTextAreaElement\n  const startPos = textarea.selectionStart\n  const endPos = textarea.selectionEnd\n  // 阻止默认行为（避免额外的换行符）\n  event.preventDefault()\n  // 在光标处插入换行符\n  inputValue.value = inputValue.value.slice(0, startPos) + '\\n' + inputValue.value.slice(endPos)\n  nextTick(() => {\n    textarea.setSelectionRange(startPos + 1, startPos + 1) // 光标定位到换行后位置\n  })\n}\n\nfunction getHitTestList() {\n  const obj = {\n    query_text: inputValue.value,\n    ...formInline.value,\n  }\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .putKnowledgeHitTest(id, obj, loading)\n    .then((res: any) => {\n      paragraphDetail.value = res.data && arraySort(res.data, 'comprehensive_score', true)\n      questionTitle.value = inputValue.value\n      inputValue.value = ''\n      first.value = false\n    })\n}\n\nfunction refresh(data: any) {\n  if (data) {\n    const obj = paragraphDetail.value.filter((v) => v.id === data.id)[0]\n    obj.content = data.content\n    obj.title = data.title\n  } else {\n    paragraphDetail.value = []\n    getHitTestList()\n  }\n}\n\nonMounted(() => {})\n</script>\n<style lang=\"scss\" scoped>\n.hit-test {\n  .question-title {\n    .avatar {\n      float: left;\n    }\n    .content {\n      padding-left: 40px;\n      .text {\n        padding: 6px 0;\n        height: 34px;\n        box-sizing: border-box;\n      }\n    }\n  }\n\n  &__operate {\n    .operate-textarea {\n      box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n      background-color: #ffffff;\n      border-radius: 8px;\n      border: 1px solid #ffffff;\n      box-sizing: border-box;\n\n      &:has(.el-textarea__inner:focus) {\n        border: 1px solid var(--el-color-primary);\n      }\n\n      :deep(.el-textarea__inner) {\n        border-radius: 8px !important;\n        box-shadow: none;\n        resize: none;\n        padding: 12px 16px;\n      }\n      .operate {\n        padding: 6px 10px;\n        .sent-button {\n          max-height: none;\n          .el-icon {\n            font-size: 24px;\n          }\n        }\n        :deep(.el-loading-spinner) {\n          margin-top: -15px;\n          .circular {\n            width: 31px;\n            height: 31px;\n          }\n        }\n      }\n    }\n  }\n}\n.hit-test {\n  &__header {\n    position: absolute;\n    right: calc(var(--app-base-px) * 3);\n  }\n  .document-card {\n    height: 210px;\n    border: 1px solid var(--app-layout-bg-color);\n    &:hover {\n      background: #ffffff;\n      border: 1px solid var(--el-border-color);\n    }\n    &.disabled {\n      background: var(--app-layout-bg-color);\n      border: 1px solid var(--app-layout-bg-color);\n      :deep(.description) {\n        color: var(--app-border-color-dark);\n      }\n      :deep(.title) {\n        color: var(--app-border-color-dark);\n      }\n    }\n    :deep(.description) {\n      -webkit-line-clamp: 5 !important;\n      height: 110px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/knowledge/KnowledgeSetting.vue",
    "content": "<template>\n  <div class=\"p-16-24\">\n    <h2 class=\"mb-16\">{{ $t('common.setting') }}</h2>\n    <el-card style=\"--el-card-padding: 0\">\n      <div class=\"knowledge-setting main-calc-height\">\n        <el-scrollbar>\n          <div class=\"p-24\" v-loading=\"loading\">\n            <h4 class=\"title-decoration-1 mb-16\">\n              {{ $t('common.info') }}\n            </h4>\n            <BaseForm ref=\"BaseFormRef\" :data=\"detail\" :apiType=\"apiType\" />\n\n            <el-form\n              ref=\"webFormRef\"\n              :rules=\"rules\"\n              :model=\"form\"\n              label-position=\"top\"\n              require-asterisk-position=\"right\"\n            >\n              <el-form-item :label=\"$t('views.knowledge.knowledgeType.label')\" required>\n                <el-card\n                  shadow=\"never\"\n                  class=\"mb-8 w-full layout-bg\"\n                  style=\"line-height: 22px\"\n                  v-if=\"detail?.type === 0\"\n                >\n                  <div class=\"flex align-center\">\n                    <el-avatar class=\"mr-8 avatar-blue\" shape=\"square\" :size=\"32\">\n                      <img src=\"@/assets/knowledge/icon_document.svg\" style=\"width: 58%\" alt=\"\" />\n                    </el-avatar>\n                    <div>\n                      <div>{{ $t('views.knowledge.knowledgeType.generalKnowledge') }}</div>\n                      <el-text type=\"info\"\n                        >{{ $t('views.knowledge.knowledgeType.generalInfo') }}\n                      </el-text>\n                    </div>\n                  </div>\n                </el-card>\n                <el-card\n                  shadow=\"never\"\n                  class=\"mb-8 w-full layout-bg\"\n                  style=\"line-height: 22px\"\n                  v-if=\"detail?.type === 1\"\n                >\n                  <div class=\"flex align-center\">\n                    <el-avatar class=\"mr-8 avatar-purple\" shape=\"square\" :size=\"32\">\n                      <img src=\"@/assets/knowledge/icon_web.svg\" style=\"width: 58%\" alt=\"\" />\n                    </el-avatar>\n                    <div>\n                      <div>{{ $t('views.knowledge.knowledgeType.webKnowledge') }}</div>\n                      <el-text type=\"info\">\n                        {{ $t('views.knowledge.knowledgeType.webInfo') }}\n                      </el-text>\n                    </div>\n                  </div>\n                </el-card>\n                <el-card\n                  shadow=\"never\"\n                  class=\"mb-8 w-full layout-bg\"\n                  style=\"line-height: 22px\"\n                  v-if=\"detail?.type === 2\"\n                >\n                  <div class=\"flex align-center\">\n                    <el-avatar\n                      class=\"mr-8\"\n                      shape=\"square\"\n                      :size=\"32\"\n                      style=\"background: none\"\n                    >\n                      <img src=\"@/assets/knowledge/logo_lark.svg\" style=\"width: 100%\" alt=\"\" />\n                    </el-avatar>\n                    <div>\n                      <p>\n                        <el-text>{{ $t('views.knowledge.knowledgeType.larkKnowledge') }}</el-text>\n                      </p>\n                      <el-text type=\"info\"\n                        >{{ $t('views.knowledge.knowledgeType.larkInfo') }}\n                      </el-text>\n                    </div>\n                  </div>\n                </el-card>\n                <el-card\n                  shadow=\"never\"\n                  class=\"mb-8 w-full layout-bg\"\n                  style=\"line-height: 22px\"\n                  v-if=\"detail?.type === 4\"\n                >\n                  <div class=\"flex align-center\">\n                    <el-avatar class=\"mr-8 avatar-orange\" shape=\"square\" :size=\"32\">\n                      <img src=\"@/assets/knowledge/logo_workflow.svg\" style=\"width: 60%\" alt=\"\" />\n                    </el-avatar>\n                    <div>\n                      <p>\n                        <el-text>{{\n                          $t('views.knowledge.knowledgeType.workflowKnowledge')\n                        }}</el-text>\n                      </p>\n                      <el-text type=\"info\">\n                        {{ $t('views.knowledge.knowledgeType.workflowInfo') }}\n                      </el-text>\n                    </div>\n                  </div>\n                </el-card>\n              </el-form-item>\n              <el-form-item\n                :label=\"$t('views.knowledge.form.source_url.label')\"\n                prop=\"source_url\"\n                v-if=\"detail?.type === 1\"\n              >\n                <el-input\n                  v-model=\"form.source_url\"\n                  :placeholder=\"$t('views.knowledge.form.source_url.placeholder')\"\n                  @blur=\"form.source_url = form.source_url.trim()\"\n                />\n              </el-form-item>\n              <el-form-item\n                :label=\"$t('views.knowledge.form.selector.label')\"\n                v-if=\"detail?.type === 1\"\n              >\n                <el-input\n                  v-model=\"form.selector\"\n                  :placeholder=\"$t('views.knowledge.form.selector.placeholder')\"\n                  @blur=\"form.selector = form.selector.trim()\"\n                />\n              </el-form-item>\n              <el-form-item label=\"App ID\" prop=\"app_id\" v-if=\"detail?.type === 2\">\n                <el-input\n                  v-model=\"form.app_id\"\n                  :placeholder=\"\n                    $t('views.application.applicationAccess.larkSetting.appIdPlaceholder')\n                  \"\n                />\n              </el-form-item>\n              <el-form-item label=\"App Secret\" prop=\"app_id\" v-if=\"detail?.type === 2\">\n                <el-input\n                  v-model=\"form.app_secret\"\n                  type=\"password\"\n                  show-password\n                  :placeholder=\"\n                    $t('views.application.applicationAccess.larkSetting.appSecretPlaceholder')\n                  \"\n                />\n              </el-form-item>\n              <el-form-item label=\"Folder Token\" prop=\"folder_token\" v-if=\"detail?.type === 2\">\n                <el-input\n                  v-model=\"form.folder_token\"\n                  :placeholder=\"\n                    $t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder')\n                  \"\n                />\n              </el-form-item>\n\n              <div v-if=\"detail?.type === 0\">\n                <h4 class=\"title-decoration-1 mb-16\">\n                  {{ $t('common.otherSetting') }}\n                </h4>\n                <el-form-item :label=\"$t('views.knowledge.form.file_count_limit.label')\">\n                  <el-slider\n                    v-model=\"form.file_count_limit\"\n                    show-input\n                    :show-input-controls=\"false\"\n                    :min=\"1\"\n                    :max=\"1000\"\n                    class=\"custom-slider\"\n                  />\n                </el-form-item>\n                <el-form-item>\n                  <template #label>\n                    <div class=\"flex align-center\">\n                      <span class=\"mr-4\"\n                        >{{ $t('views.knowledge.form.file_size_limit.label') }}\n                      </span>\n                      <el-tooltip\n                        effect=\"dark\"\n                        :content=\"$t('views.knowledge.form.file_size_limit.placeholder')\"\n                        placement=\"right\"\n                      >\n                        <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                      </el-tooltip>\n                    </div>\n                  </template>\n                  <el-slider\n                    v-model=\"form.file_size_limit\"\n                    show-input\n                    :show-input-controls=\"false\"\n                    :min=\"1\"\n                    :max=\"1000\"\n                    class=\"custom-slider\"\n                  />\n                </el-form-item>\n              </div>\n            </el-form>\n            <div class=\"text-right\">\n              <el-button\n                @click=\"submit\"\n                type=\"primary\"\n                v-if=\"!route.path.includes('share/') && permissionPrecise.edit(id)\"\n              >\n                {{ $t('common.save') }}\n              </el-button>\n            </div>\n          </div>\n        </el-scrollbar>\n      </div>\n    </el-card>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, reactive, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport BaseForm from '@/views/knowledge/component/BaseForm.vue'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { t } from '@/locales'\nimport permissionMap from '@/permission'\n\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\nconst {\n  params: { id, folderId },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nconst webFormRef = ref()\nconst BaseFormRef = ref()\nconst loading = ref(false)\nconst detail = ref<any>({})\nconst cloneModelId = ref('')\n\nconst form = ref<any>({\n  source_url: '',\n  selector: '',\n  app_id: '',\n  app_secret: '',\n  folder_token: '',\n  file_count_limit: 50,\n  file_size_limit: 100,\n})\n\nconst rules = reactive({\n  source_url: [\n    {\n      required: true,\n      message: t('views.knowledge.form.source_url.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  app_id: [\n    {\n      required: true,\n      message: t('views.application.applicationAccess.larkSetting.appIdPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  app_secret: [\n    {\n      required: true,\n      message: t('views.application.applicationAccess.larkSetting.appSecretPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  folder_token: [\n    {\n      required: true,\n      message: t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nasync function submit() {\n  if (await BaseFormRef.value?.validate()) {\n    await webFormRef.value.validate((valid: any) => {\n      if (valid) {\n        const obj =\n          detail.value.type === 1 || detail.value.type === 2\n            ? {\n                meta: form.value,\n                file_count_limit: form.value.file_count_limit,\n                file_size_limit: form.value.file_size_limit,\n                ...BaseFormRef.value.form,\n              }\n            : {\n                file_count_limit: form.value.file_count_limit,\n                file_size_limit: form.value.file_size_limit,\n                ...BaseFormRef.value.form,\n              }\n\n        if (cloneModelId.value !== BaseFormRef.value.form.embedding_model_id) {\n          MsgConfirm(t('common.tip'), t('views.knowledge.tip.updateModeMessage'), {\n            confirmButtonText: t('views.knowledge.setting.vectorization'),\n          })\n            .then(() => {\n              if (detail.value.type === 2) {\n                loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n                  .putLarkKnowledge(id, obj, loading)\n                  .then(() => {\n                    loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n                      .putReEmbeddingKnowledge(id)\n                      .then(() => {\n                        MsgSuccess(t('common.saveSuccess'))\n                      })\n                  })\n              } else {\n                loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n                  .putKnowledge(id, obj, loading)\n                  .then(() => {\n                    loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n                      .putReEmbeddingKnowledge(id)\n                      .then(() => {\n                        MsgSuccess(t('common.saveSuccess'))\n                      })\n                  })\n              }\n            })\n            .catch(() => {})\n        } else {\n          if (detail.value.type === 2) {\n            loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n              .putLarkKnowledge(id, obj, loading)\n              .then(() => {\n                MsgSuccess(t('common.saveSuccess'))\n              })\n          } else {\n            loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n              .putKnowledge(id, obj, loading)\n              .then(() => {\n                MsgSuccess(t('common.saveSuccess'))\n              })\n          }\n        }\n      }\n    })\n  }\n}\n\nfunction getDetail() {\n  loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value })\n    .getKnowledgeDetail(id, loading)\n    .then((res: any) => {\n      detail.value = res.data\n      cloneModelId.value = res.data?.embedding_model_id\n      if (detail.value?.type === 0) {\n        form.value.file_count_limit = res.data.file_count_limit\n        form.value.file_size_limit = res.data.file_size_limit\n      }\n      if (detail.value?.type === 1 || detail.value?.type === 2) {\n        form.value = res.data.meta\n      }\n    })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped>\n.knowledge-setting {\n  width: 70%;\n  margin: 0 auto;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/knowledge/WorkflowTransform.vue",
    "content": "<template>\n  <div class=\"p-16-24\">\n    <el-card style=\"height: calc(var(--app-main-height) + 50px)\">\n      <div class=\"flex-center h-full\">\n        <div style=\"margin-left: 20px\">\n          <h1 class=\"mb-12\">{{ $t('views.knowledge.transform.title') }}</h1>\n          <div class=\"color-secondary lighter line-height-22\">\n            {{ $t('views.knowledge.transform.message1') }}<br />{{\n              $t('views.knowledge.transform.message2')\n            }}\n          </div>\n          <p class=\"mt-24 mb-8 color-organe\">{{ $t('views.knowledge.transform.tip') }}</p>\n          <el-button type=\"primary\" @click=\"transformHandle\">{{\n            $t('views.knowledge.transform.button')\n          }}</el-button>\n        </div>\n        <img class=\"ml-24\" src=\"@/assets/workflow-demo.png\" width=\"708\" alt=\"\" />\n      </div>\n    </el-card>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, reactive, computed } from 'vue'\nimport { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport { useRoute, useRouter } from 'vue-router'\nimport { knowledgeTemplate } from '@/workflow/common/template.ts'\nconst route = useRoute()\nconst {\n  params: { id, folderId },\n} = route as any\n\nconst router = useRouter()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst workflowDefault = ref(knowledgeTemplate.default)\n\nconst loading = ref(false)\nfunction transformHandle() {\n  MsgConfirm(t('common.tip'), t('views.knowledge.transform.comfirm'), {\n    cancelButtonText: t('common.close'),\n    type: 'warning',\n  })\n    .then(() => {\n      loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n        .postTransformWorkflow(id as string, { work_flow: workflowDefault.value }, loading)\n        .then(() => {\n          MsgSuccess(t('common.submitSuccess'))\n          router.push({ path: `/knowledge/${id}/${folderId}/workflow` })\n        })\n        .catch(() => {\n          loading.value = false\n        })\n    })\n    .catch(() => {})\n}\n\nonMounted(() => {})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/component/BaseForm.vue",
    "content": "<template>\n  <el-form\n    ref=\"FormRef\"\n    :model=\"form\"\n    :rules=\"rules\"\n    label-position=\"top\"\n    require-asterisk-position=\"right\"\n    v-loading=\"loading\"\n  >\n    <el-form-item :label=\"$t('views.knowledge.form.knowledgeName.label')\" prop=\"name\">\n      <el-input\n        v-model=\"form.name\"\n        :placeholder=\"$t('views.knowledge.form.knowledgeName.placeholder')\"\n        maxlength=\"64\"\n        show-word-limit\n        @blur=\"form.name = form.name.trim()\"\n      />\n    </el-form-item>\n    <el-form-item :label=\"$t('views.knowledge.form.knowledgeDescription.label')\" prop=\"desc\">\n      <el-input\n        v-model=\"form.desc\"\n        type=\"textarea\"\n        :placeholder=\"$t('views.knowledge.form.knowledgeDescription.placeholder')\"\n        maxlength=\"256\"\n        show-word-limit\n        :autosize=\"{ minRows: 3 }\"\n        @blur=\"form.desc = form.desc.trim()\"\n      />\n    </el-form-item>\n    <el-form-item\n      :label=\"$t('views.knowledge.form.EmbeddingModel.label')\"\n      prop=\"embedding_model_id\"\n    >\n      <ModelSelect\n        v-model=\"form.embedding_model_id\"\n        :placeholder=\"$t('views.knowledge.form.EmbeddingModel.placeholder')\"\n        :options=\"modelOptions\"\n        @submit-model=\"getSelectModel\"\n        :model-type=\"'EMBEDDING'\"\n        showFooter\n      ></ModelSelect>\n    </el-form-item>\n  </el-form>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, onMounted, onUnmounted, computed, watch } from 'vue'\nimport { groupBy } from 'lodash'\nimport type { knowledgeData } from '@/api/type/knowledge'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst props = defineProps<{\n  data?: {\n    type: object\n    default: () => null\n  }\n  apiType: 'systemShare' | 'workspace' | 'systemManage'\n}>()\nconst form = ref<knowledgeData>({\n  name: '',\n  desc: '',\n  embedding_model_id: '',\n})\nconst workspace_id = ref('')\n\nconst rules = reactive({\n  name: [\n    {\n      required: true,\n      message: t('views.knowledge.form.knowledgeName.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  desc: [\n    {\n      required: true,\n      message: t('views.knowledge.form.knowledgeDescription.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  embedding_model_id: [\n    {\n      required: true,\n      message: t('views.knowledge.form.EmbeddingModel.requiredMessage'),\n      trigger: 'change',\n    },\n  ],\n})\n\nconst FormRef = ref()\nconst loading = ref(false)\nconst modelOptions = ref<any>([])\n\nwatch(\n  () => props.data,\n  (value: any) => {\n    if (value && JSON.stringify(value) !== '{}') {\n      form.value.name = value.name\n      form.value.desc = value.desc\n      form.value.embedding_model_id = value.embedding_model_id\n      workspace_id.value = value.workspace_id || ''\n      // 重新刷新模型列表\n      getSelectModel()\n    }\n  },\n  {\n    immediate: true,\n  },\n)\n\n/*\n  表单校验\n*/\nfunction validate() {\n  if (!FormRef.value) return\n  return FormRef.value.validate((valid: any) => {\n    return valid\n  })\n}\n\nfunction getSelectModel() {\n  loading.value = true\n  const obj =\n    props.apiType === 'systemManage'\n      ? {\n          model_type: 'EMBEDDING',\n          workspace_id: workspace_id.value,\n        }\n      : {\n          model_type: 'EMBEDDING',\n        }\n  loadSharedApi({ type: 'model', systemType: props.apiType })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n      loading.value = false\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nonMounted(() => {\n  if (props.apiType !== 'systemManage') {\n    getSelectModel()\n  }\n})\n\nonUnmounted(() => {\n  form.value = {\n    name: '',\n    desc: '',\n    embedding_model_id: '',\n  }\n  FormRef.value?.clearValidate()\n})\n\ndefineExpose({\n  validate,\n  form,\n})\n</script>\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/component/EditParagraphDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.paragraph.editParagraph')\"\n    v-model=\"dialogVisible\"\n    width=\"80%\"\n    destroy-on-close\n    class=\"paragraph-dialog\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-row v-if=\"isConnect\">\n      <el-col :span=\"18\" class=\"p-24\">\n        <ParagraphForm\n          ref=\"paragraphFormRef\"\n          :data=\"detail\"\n          :isEdit=\"true\"\n          :knowledge-id=\"knowledgeId\"\n        />\n      </el-col>\n      <el-col :span=\"6\" class=\"border-l\" style=\"width: 300px\">\n        <p class=\"bold title p-24\" style=\"padding-bottom: 0\">\n          <span class=\"flex align-center\">\n            <span>{{ $t('views.paragraph.relatedProblem.title') }}</span>\n            <el-divider direction=\"vertical\" class=\"mr-4\" />\n            <el-button text @click=\"addProblem\">\n              <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n            </el-button>\n          </span>\n        </p>\n        <el-scrollbar height=\"500px\">\n          <div class=\"p-24\" style=\"padding-top: 16px\">\n            <el-input\n              v-if=\"isAddProblem\"\n              v-model=\"problemValue\"\n              :placeholder=\"$t('views.paragraph.relatedProblem.placeholder')\"\n              @change=\"addProblemHandle\"\n              @blur=\"isAddProblem = false\"\n              ref=\"inputRef\"\n              class=\"mb-8\"\n            />\n\n            <template v-for=\"(item, index) in detail.problem_list\" :key=\"index\">\n              <TagEllipsis\n                @close=\"delProblemHandle(item, index)\"\n                class=\"question-tag\"\n                type=\"info\"\n                effect=\"plain\"\n                closable\n              >\n                <auto-tooltip :content=\"item.content\">\n                  {{ item.content }}\n                </auto-tooltip>\n              </TagEllipsis>\n            </template>\n          </div>\n        </el-scrollbar>\n      </el-col>\n    </el-row>\n    <div v-else class=\"p-24\">\n      <ParagraphForm\n        ref=\"paragraphFormRef\"\n        :data=\"detail\"\n        :isEdit=\"true\"\n        :knowledge-id=\"knowledgeId\"\n      />\n    </div>\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle\"> {{ $t('common.save') }} </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, nextTick } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport ParagraphForm from '@/views/paragraph/component/ParagraphForm.vue'\n\nconst props = defineProps({\n  isConnect: Boolean,\n  knowledgeId: String,\n})\n\nconst emit = defineEmits(['updateContent'])\n\nconst dialogVisible = ref<boolean>(false)\n\nconst detail = ref<any>({})\n\nconst paragraphFormRef = ref()\nconst inputRef = ref()\n\nconst isAddProblem = ref(false)\n\nconst problemValue = ref('')\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    detail.value = {}\n  }\n})\n\nconst open = (data: any) => {\n  detail.value = cloneDeep(data)\n  dialogVisible.value = true\n}\n\nfunction delProblemHandle(item: any, index: number) {\n  detail.value.problem_list.splice(index, 1)\n}\nfunction addProblemHandle() {\n  if (problemValue.value.trim()) {\n    if (\n      !detail.value?.problem_list.some((item: any) => item.content === problemValue.value.trim())\n    ) {\n      detail.value?.problem_list?.push({\n        content: problemValue.value.trim(),\n      })\n    }\n\n    problemValue.value = ''\n    isAddProblem.value = false\n  }\n}\nfunction addProblem() {\n  isAddProblem.value = true\n  nextTick(() => {\n    inputRef.value?.focus()\n  })\n}\n\nconst submitHandle = async () => {\n  if (await paragraphFormRef.value?.validate()) {\n    emit('updateContent', {\n      problem_list: detail.value.problem_list,\n      ...paragraphFormRef.value?.form,\n    })\n    dialogVisible.value = false\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/component/KnowledgeListContainer.vue",
    "content": "<template>\n  <ContentContainer>\n    <template #header>\n      <slot name=\"header\"> </slot>\n    </template>\n    <template #search>\n      <div class=\"flex\">\n        <div class=\"complex-search\">\n          <el-select\n            class=\"complex-search__left\"\n            v-model=\"search_type\"\n            style=\"width: 120px\"\n            @change=\"search_type_change\"\n          >\n            <el-option :label=\"$t('common.creator')\" value=\"create_user\" />\n\n            <el-option :label=\"$t('common.name')\" value=\"name\" />\n          </el-select>\n          <el-input\n            v-if=\"search_type === 'name'\"\n            v-model=\"search_form.name\"\n            @change=\"searchHandle\"\n            :placeholder=\"$t('common.searchBar.placeholder')\"\n            style=\"width: 220px\"\n            clearable\n          />\n          <el-select\n            v-else-if=\"search_type === 'create_user'\"\n            v-model=\"search_form.create_user\"\n            @change=\"searchHandle\"\n            filterable\n            clearable\n            style=\"width: 220px\"\n          >\n            <el-option v-for=\"u in user_options\" :key=\"u.id\" :value=\"u.id\" :label=\"u.nick_name\" />\n          </el-select>\n        </div>\n        <el-button\n          class=\"ml-8\"\n          v-if=\"!isShared && permissionPrecise.create()\"\n          @click=\"openTemplateStoreDialog()\"\n        >\n          <AppIcon iconName=\"app-template-center\" class=\"mr-4\" />\n          {{ $t('workflow.setting.templateCenter') }}\n        </el-button>\n        <el-dropdown trigger=\"click\" v-if=\"!isShared && permissionPrecise.create()\">\n          <el-button type=\"primary\" class=\"ml-8\">\n            {{ $t('common.create') }}\n            <el-icon class=\"el-icon--right\">\n              <arrow-down />\n            </el-icon>\n          </el-button>\n          <template #dropdown>\n            <el-dropdown-menu class=\"create-dropdown\">\n              <el-dropdown-item @click=\"openCreateDialog(CreateKnowledgeDialog)\">\n                <div class=\"flex\">\n                  <el-avatar class=\"avatar-blue mt-4\" shape=\"square\" :size=\"32\">\n                    <img src=\"@/assets/knowledge/icon_document.svg\" style=\"width: 58%\" alt=\"\" />\n                  </el-avatar>\n                  <div class=\"pre-wrap ml-8\">\n                    <div class=\"lighter\">\n                      {{ $t('views.knowledge.knowledgeType.generalKnowledge') }}\n                    </div>\n                    <el-text type=\"info\" size=\"small\" class=\"color-secondary\"\n                      >{{ $t('views.knowledge.knowledgeType.generalInfo') }}\n                    </el-text>\n                  </div>\n                </div>\n              </el-dropdown-item>\n              <el-dropdown-item @click=\"openCreateDialog(CreateWebKnowledgeDialog)\">\n                <div class=\"flex\">\n                  <el-avatar class=\"avatar-purple mt-4\" shape=\"square\" :size=\"32\">\n                    <img src=\"@/assets/knowledge/icon_web.svg\" style=\"width: 58%\" alt=\"\" />\n                  </el-avatar>\n                  <div class=\"pre-wrap ml-8\">\n                    <div class=\"lighter\">\n                      {{ $t('views.knowledge.knowledgeType.webKnowledge') }}\n                    </div>\n                    <el-text type=\"info\" size=\"small\" class=\"color-secondary\"\n                      >{{ $t('views.knowledge.knowledgeType.webInfo') }}\n                    </el-text>\n                  </div>\n                </div>\n              </el-dropdown-item>\n              <el-dropdown-item\n                @click=\"openCreateDialog(CreateLarkKnowledgeDialog)\"\n                v-if=\"user.isPE() || user.isEE()\"\n              >\n                <div class=\"flex\">\n                  <el-avatar\n                    class=\"avatar-purple mt-4\"\n                    shape=\"square\"\n                    :size=\"32\"\n                    style=\"background: none\"\n                  >\n                    <img src=\"@/assets/knowledge/logo_lark.svg\" alt=\"\" />\n                  </el-avatar>\n                  <div class=\"pre-wrap ml-8\">\n                    <div class=\"lighter\">\n                      {{ $t('views.knowledge.knowledgeType.larkKnowledge') }}\n                    </div>\n                    <el-text type=\"info\" size=\"small\" class=\"color-secondary\"\n                      >{{ $t('views.knowledge.knowledgeType.larkInfo') }}\n                    </el-text>\n                  </div>\n                </div>\n              </el-dropdown-item>\n              <el-dropdown-item @click=\"openCreateDialog(CreateWorkflowKnowledgeDialog)\">\n                <div class=\"flex\">\n                  <el-avatar class=\"avatar-orange mt-4\" shape=\"square\" :size=\"32\">\n                    <img src=\"@/assets/knowledge/logo_workflow.svg\" style=\"width: 60%\" alt=\"\" />\n                  </el-avatar>\n                  <div class=\"pre-wrap ml-8\">\n                    <div class=\"lighter\">\n                      {{ $t('views.knowledge.knowledgeType.workflowKnowledge') }}\n                    </div>\n                    <el-text type=\"info\" size=\"small\" class=\"color-secondary\"\n                      >{{ $t('views.knowledge.knowledgeType.workflowInfo') }}\n                    </el-text>\n                  </div>\n                </div>\n              </el-dropdown-item>\n              <el-dropdown-item @click=\"openCreateFolder\" divided v-if=\"apiType === 'workspace'\">\n                <div class=\"flex align-center\">\n                  <AppIcon iconName=\"app-folder\" style=\"font-size: 32px\"></AppIcon>\n                  <div class=\"pre-wrap ml-4\">\n                    <div class=\"lighter\">\n                      {{ $t('components.folder.addFolder') }}\n                    </div>\n                  </div>\n                </div>\n              </el-dropdown-item>\n            </el-dropdown-menu>\n          </template>\n        </el-dropdown>\n      </div>\n    </template>\n\n    <div\n      v-loading.fullscreen.lock=\"paginationConfig.current_page === 1 && loading\"\n      style=\"max-height: calc(100vh - 120px)\"\n    >\n      <InfiniteScroll\n        :size=\"knowledge.knowledgeList.length\"\n        :total=\"paginationConfig.total\"\n        :page_size=\"paginationConfig.page_size\"\n        v-model:current_page=\"paginationConfig.current_page\"\n        @load=\"getList\"\n        :loading=\"loading\"\n      >\n        <el-row v-if=\"knowledge.knowledgeList.length > 0\" :gutter=\"15\" class=\"w-full\">\n          <template v-for=\"(item, index) in knowledge.knowledgeList\" :key=\"index\">\n            <el-col :xs=\"24\" :sm=\"12\" :md=\"12\" :lg=\"8\" :xl=\"6\" class=\"mb-16\">\n              <CardBox\n                :title=\"item.name\"\n                :description=\"item.desc\"\n                class=\"cursor\"\n                @click=\"\n                  router.push({\n                    path: `/knowledge/${item.id}/${folder.currentFolder.id ? (folder.currentFolder.id !== 'share' ? item.folder_id : 'share') : 'shared'}/${item.type}/document`,\n                  })\n                \"\n              >\n                <template #icon>\n                  <KnowledgeIcon :type=\"item.type\" />\n                </template>\n                <template #subTitle>\n                  <el-text class=\"color-secondary lighter flex align-center\" size=\"small\">\n                    <span\n                      :title=\"i18n_name(item.nick_name)\"\n                      class=\"ellipsis\"\n                      style=\"max-width: 90px\"\n                    >\n                      {{ i18n_name(item.nick_name) }}\n                    </span>\n                    <span class=\"ml-4 mr-4\"> {{ $t('common.createdIn') }}</span>\n                    <span> {{ dateFormat(item.create_time) }}</span>\n                  </el-text>\n                </template>\n                <template #tag>\n                  <el-tag\n                    v-if=\"isShared || isSystemShare\"\n                    size=\"small\"\n                    type=\"info\"\n                    class=\"info-tag\"\n                  >\n                    {{ $t('views.shared.title') }}\n                  </el-tag>\n                </template>\n                <template #footer>\n                  <div class=\"footer-content flex-between\">\n                    <div>\n                      <span class=\"bold mr-4\">{{ item?.document_count || 0 }}</span>\n                      <span class=\"color-secondary\">{{\n                        $t('views.knowledge.document_count')\n                      }}</span>\n                      <el-divider direction=\"vertical\" />\n                      <span class=\"bold mr-4\">{{ numberFormat(item?.char_length) || 0 }}</span>\n                      <span class=\"color-secondary\">{{ $t('common.character') }}</span>\n                    </div>\n                  </div>\n                </template>\n                <template #mouseEnter>\n                  <div @click.stop v-if=\"!isShared\">\n                    <el-dropdown trigger=\"click\">\n                      <el-button text @click.stop v-if=\"MoreFilledPermission(item)\">\n                        <AppIcon iconName=\"app-more\"></AppIcon>\n                      </el-button>\n                      <template #dropdown>\n                        <el-dropdown-menu>\n                          <el-dropdown-item\n                            @click.stop=\"syncKnowledge(item)\"\n                            v-if=\"item.type === 1 && permissionPrecise.sync(item.id)\"\n                          >\n                            <AppIcon iconName=\"app-sync\" class=\"color-secondary\"></AppIcon>\n\n                            {{ $t('views.knowledge.setting.sync') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click.stop=\"reEmbeddingKnowledge(item)\"\n                            v-if=\"permissionPrecise.vector(item.id)\"\n                          >\n                            <AppIcon iconName=\"app-vectorization\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.knowledge.setting.vectorization') }}\n                          </el-dropdown-item>\n\n                          <el-dropdown-item\n                            @click.stop=\"openGenerateDialog(item)\"\n                            v-if=\"permissionPrecise.generate(item.id)\"\n                          >\n                            <AppIcon\n                              iconName=\"app-generate-question\"\n                              class=\"color-secondary\"\n                            ></AppIcon>\n\n                            {{ $t('views.document.generateQuestion.title') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            v-if=\"isSystemShare\"\n                            @click.stop=\"openAuthorizedWorkspaceDialog(item)\"\n                          >\n                            <AppIcon iconName=\"app-lock\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.shared.authorized_workspace') }}</el-dropdown-item\n                          >\n                          <el-dropdown-item\n                            @click.stop=\"openAuthorization(item)\"\n                            v-if=\"apiType === 'workspace' && permissionPrecise.auth(item.id)\"\n                          >\n                            <AppIcon\n                              iconName=\"app-resource-authorization\"\n                              class=\"color-secondary\"\n                            ></AppIcon>\n                            {{ $t('views.system.resourceAuthorization.title') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            text\n                            @click.stop=\"openResourceMappingDrawer(item)\"\n                            v-if=\"permissionPrecise.relate_map(item.id)\"\n                          >\n                            <AppIcon\n                              iconName=\"app-resource-mapping\"\n                              class=\"color-secondary\"\n                            ></AppIcon>\n                            {{ $t('views.system.resourceMapping.title') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click.stop=\"openMoveToDialog(item)\"\n                            v-if=\"permissionPrecise.edit(item.id) && apiType === 'workspace'\"\n                          >\n                            <AppIcon iconName=\"app-migrate\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.moveTo') }}\n                          </el-dropdown-item>\n\n                          <el-dropdown-item\n                            @click.stop=\"\n                              router.push({\n                                path: `/knowledge/${item.id}/${folder.currentFolder.id || 'shared'}/${item.type}/setting`,\n                              })\n                            \"\n                            v-if=\"permissionPrecise.edit(item.id)\"\n                          >\n                            <AppIcon iconName=\"app-setting\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.setting') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click.stop=\"exportKnowledge(item)\"\n                            v-if=\"permissionPrecise.export(item.id)\"\n                          >\n                            <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon\n                            >{{ $t('views.document.setting.export') }} Excel\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click.stop=\"exportZipKnowledge(item)\"\n                            v-if=\"permissionPrecise.export(item.id)\"\n                          >\n                            <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon\n                            >{{ $t('views.document.setting.export') }} ZIP</el-dropdown-item\n                          >\n                          <el-dropdown-item\n                            type=\"danger\"\n                            @click.stop=\"deleteKnowledge(item)\"\n                            v-if=\"permissionPrecise.delete(item.id)\"\n                          >\n                            <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.delete') }}</el-dropdown-item\n                          >\n                        </el-dropdown-menu>\n                      </template>\n                    </el-dropdown>\n                  </div>\n                </template>\n              </CardBox>\n            </el-col>\n          </template>\n        </el-row>\n        <el-empty :description=\"$t('common.noData')\" v-else />\n      </InfiniteScroll>\n    </div>\n  </ContentContainer>\n\n  <component :is=\"currentCreateDialog\" ref=\"CreateKnowledgeDialogRef\" v-if=\"!isShared\" />\n  <CreateFolderDialog ref=\"CreateFolderDialogRef\" v-if=\"!isShared\" @refresh=\"refreshFolder\" />\n  <GenerateRelatedDialog ref=\"GenerateRelatedDialogRef\" :apiType=\"apiType\" />\n  <SyncWebDialog ref=\"SyncWebDialogRef\" v-if=\"!isShared\" />\n  <AuthorizedWorkspace\n    ref=\"AuthorizedWorkspaceDialogRef\"\n    v-if=\"isSystemShare\"\n  ></AuthorizedWorkspace>\n  <MoveToDialog\n    ref=\"MoveToDialogRef\"\n    :source=\"SourceTypeEnum.KNOWLEDGE\"\n    @refresh=\"refreshKnowledgeList\"\n    v-if=\"apiType === 'workspace'\"\n  />\n  <ResourceAuthorizationDrawer\n    :type=\"SourceTypeEnum.KNOWLEDGE\"\n    ref=\"ResourceAuthorizationDrawerRef\"\n    v-if=\"apiType === 'workspace'\"\n  />\n  <TemplateStoreDialog ref=\"templateStoreDialogRef\" :api-type=\"apiType\" @refresh=\"getList\" />\n  <ResourceMappingDrawer ref=\"resourceMappingDrawerRef\"></ResourceMappingDrawer>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, shallowRef, nextTick, computed, watch } from 'vue'\nimport { useRouter, useRoute, onBeforeRouteLeave } from 'vue-router'\nimport { cloneDeep, get } from 'lodash'\nimport CreateKnowledgeDialog from '@/views/knowledge/create-component/CreateKnowledgeDialog.vue'\nimport CreateWebKnowledgeDialog from '@/views/knowledge/create-component/CreateWebKnowledgeDialog.vue'\nimport CreateLarkKnowledgeDialog from '@/views/knowledge/create-component/CreateLarkKnowledgeDialog.vue'\nimport CreateWorkflowKnowledgeDialog from '@/views/knowledge/create-component/CreateWorkflowKnowledgeDialog.vue'\nimport SyncWebDialog from '@/views/knowledge/component/SyncWebDialog.vue'\nimport CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'\nimport MoveToDialog from '@/components/folder-tree/MoveToDialog.vue'\nimport GenerateRelatedDialog from '@/components/generate-related-dialog/index.vue'\nimport AuthorizedWorkspace from '@/views/system-shared/AuthorizedWorkspaceDialog.vue'\nimport ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'\nimport TemplateStoreDialog from '@/views/knowledge/template-store/TemplateStoreDialog.vue'\nimport ResourceMappingDrawer from '@/components/resource_mapping/index.vue'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { numberFormat, i18n_name } from '@/utils/common'\nimport { dateFormat } from '@/utils/time'\nimport { SourceTypeEnum } from '@/enums/common'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\nimport useStore from '@/stores'\nimport { t } from '@/locales'\nconst resourceMappingDrawerRef = ref<InstanceType<typeof ResourceMappingDrawer>>()\n\nconst openResourceMappingDrawer = (knowledge: any) => {\n  resourceMappingDrawerRef.value?.open('KNOWLEDGE', knowledge)\n}\nconst router = useRouter()\nconst route = useRoute()\nconst { folder, user, knowledge } = useStore()\nonBeforeRouteLeave((to, from) => {\n  knowledge.setKnowledgeList([])\n})\n\nconst emit = defineEmits(['refreshFolder'])\n\nconst apiType = computed(() => {\n  // 工作空间普通用户的共享是share。系统的共享是shared\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst isShared = computed(() => {\n  return folder.currentFolder.id === 'share'\n})\nconst isSystemShare = computed(() => {\n  return apiType.value === 'systemShare'\n})\n\nconst MoreFilledPermission = (item: any) => {\n  return (\n    (item.type === 1 && permissionPrecise.value.sync(item.id)) ||\n    permissionPrecise.value.vector(item.id) ||\n    permissionPrecise.value.generate(item.id) ||\n    (permissionPrecise.value.edit(item.id) && apiType.value) === 'workspace' ||\n    permissionPrecise.value.export(item.id) ||\n    permissionPrecise.value.auth(item.id) ||\n    permissionPrecise.value.delete(item.id) ||\n    permissionPrecise.value.relate_map(item.id) ||\n    isSystemShare.value\n  )\n}\n\nconst loading = ref(false)\n\nconst search_type = ref('name')\nconst search_form = ref<any>({\n  name: '',\n  create_user: '',\n})\n\nconst user_options = ref<any[]>([])\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 30,\n  total: 0,\n})\n\nconst ResourceAuthorizationDrawerRef = ref()\nfunction openAuthorization(item: any) {\n  ResourceAuthorizationDrawerRef.value.open(item.id)\n}\n\nconst MoveToDialogRef = ref()\nfunction openMoveToDialog(data: any) {\n  // 仅2个参数就行\n  const obj = {\n    id: data.id,\n    folder_id: data.folder,\n  }\n  MoveToDialogRef.value?.open(obj)\n}\n\nfunction refreshKnowledgeList(row: any) {\n  // 不是根目录才会移除\n  if (folder.currentFolder?.parent_id) {\n    const list = cloneDeep(knowledge.knowledgeList)\n    const index = list.findIndex((v) => v.id === row.id)\n    list.splice(index, 1)\n    knowledge.setKnowledgeList(list)\n  }\n}\n\nconst CreateKnowledgeDialogRef = ref()\nconst currentCreateDialog = shallowRef<any>(null)\n\nfunction openCreateDialog(data: any) {\n  currentCreateDialog.value = data\n  nextTick(() => {\n    CreateKnowledgeDialogRef.value.open(folder.currentFolder)\n  })\n}\n\nfunction reEmbeddingKnowledge(row: any) {\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .putReEmbeddingKnowledge(row.id)\n    .then(() => {\n      MsgSuccess(t('common.submitSuccess'))\n    })\n}\n\nconst SyncWebDialogRef = ref()\n\nfunction syncKnowledge(row: any) {\n  SyncWebDialogRef.value.open(row.id)\n}\n\nconst search_type_change = () => {\n  search_form.value = { name: '', create_user: '' }\n}\n\nconst GenerateRelatedDialogRef = ref<InstanceType<typeof GenerateRelatedDialog>>()\nfunction openGenerateDialog(row: any) {\n  if (GenerateRelatedDialogRef.value) {\n    GenerateRelatedDialogRef.value.open([], 'knowledge', row)\n  }\n}\n\nconst exportKnowledge = (item: any) => {\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .exportKnowledge(item.name, item.id, loading)\n    .then(() => {\n      MsgSuccess(t('common.exportSuccess'))\n    })\n}\nconst exportZipKnowledge = (item: any) => {\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .exportZipKnowledge(item.name, item.id, loading)\n    .then(() => {\n      MsgSuccess(t('common.exportSuccess'))\n    })\n}\n\nfunction deleteKnowledge(row: any) {\n  MsgConfirm(\n    `${t('views.knowledge.delete.confirmTitle')}${row.name} ?`,\n    row.resource_count > 0\n      ? t('views.knowledge.delete.resourceCountMessage', row.resource_count)\n      : '',\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n        .delKnowledge(row.id, loading)\n        .then(() => {\n          const list = cloneDeep(knowledge.knowledgeList)\n          const index = list.findIndex((v) => v.id === row.id)\n          list.splice(index, 1)\n          knowledge.setKnowledgeList(list)\n\n          MsgSuccess(t('common.deleteSuccess'))\n        })\n    })\n    .catch(() => {})\n}\n\nconst AuthorizedWorkspaceDialogRef = ref()\nfunction openAuthorizedWorkspaceDialog(row: any) {\n  if (AuthorizedWorkspaceDialogRef.value) {\n    AuthorizedWorkspaceDialogRef.value.open(row)\n  }\n}\n\n// 文件夹相关\nconst CreateFolderDialogRef = ref()\nfunction openCreateFolder() {\n  CreateFolderDialogRef.value.open(SourceTypeEnum.KNOWLEDGE, folder.currentFolder.id)\n}\nwatch(\n  () => folder.currentFolder,\n  (newValue) => {\n    if (newValue && newValue.id && !isSystemShare.value) {\n      paginationConfig.current_page = 1\n      knowledge.setKnowledgeList([])\n      getList()\n    }\n  },\n  { deep: true, immediate: true },\n)\n\nfunction getList() {\n  const params: any = {\n    folder_id: folder.currentFolder?.id || user.getWorkspaceId(),\n    scope: apiType.value === 'systemShare' ? 'SHARED' : 'WORKSPACE',\n  }\n  if (search_form.value[search_type.value]) {\n    params[search_type.value] = search_form.value[search_type.value]\n  }\n  loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value })\n    .getKnowledgeListPage(paginationConfig, params, loading)\n    .then((res: any) => {\n      paginationConfig.total = res.data?.total\n      knowledge.setKnowledgeList([...knowledge.knowledgeList, ...res.data.records])\n    })\n}\n\nfunction clickFolder(item: any) {\n  folder.setCurrentFolder(item)\n}\n\nfunction searchHandle() {\n  paginationConfig.current_page = 1\n  knowledge.setKnowledgeList([])\n  getList()\n}\n\nfunction refreshFolder() {\n  emit('refreshFolder')\n}\n\nconst templateStoreDialogRef = ref()\nfunction openTemplateStoreDialog() {\n  templateStoreDialogRef.value?.open(folder.currentFolder.id)\n}\n\nonMounted(() => {\n  if (apiType.value !== 'workspace') {\n    folder.setCurrentFolder({\n      id: '',\n    })\n    getList()\n  }\n  loadSharedApi({ type: 'workspace', isShared: isShared.value, systemType: apiType.value })\n    .getAllMemberList(user.getWorkspaceId(), loading)\n    .then((res: any) => {\n      user_options.value = res.data\n    })\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/component/ParagraphList.vue",
    "content": "<template>\n  <div>\n    <InfiniteScroll\n      :size=\"paragraph_list.length\"\n      :total=\"modelValue.length\"\n      :page_size=\"page_size\"\n      v-model:current_page=\"current_page\"\n      @load=\"next()\"\n      :loading=\"loading\"\n    >\n      <el-card\n        v-for=\"(child, cIndex) in paragraph_list\"\n        :key=\"cIndex\"\n        shadow=\"never\"\n        class=\"card-never mb-16\"\n      >\n        <div class=\"flex-between\">\n          <span>{{ child.title || '-' }}</span>\n          <div>\n            <!-- 编辑分段按钮 -->\n            <el-button link @click=\"editHandle(child, cIndex)\">\n              <AppIcon iconName=\"app-edit\"></AppIcon>\n            </el-button>\n            <!-- 删除分段按钮  -->\n            <el-button link @click=\"deleteHandle(child, cIndex)\">\n              <AppIcon iconName=\"app-delete\"></AppIcon>\n            </el-button>\n          </div>\n        </div>\n        <div class=\"lighter mt-12\">\n          {{ child.content }}\n        </div>\n        <div class=\"lighter mt-12\">\n          <el-text type=\"info\">\n            {{ child.content.length }} {{ $t('views.paragraph.character_count') }}\n          </el-text>\n        </div>\n      </el-card>\n    </InfiniteScroll>\n\n    <EditParagraphDialog\n      ref=\"EditParagraphDialogRef\"\n      @updateContent=\"updateContent\"\n      :isConnect=\"isConnect\"\n      :knowledge-id=\"knowledgeId\"\n    />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { cloneDeep } from 'lodash'\nimport { ref, computed, watchEffect } from 'vue'\nimport EditParagraphDialog from './EditParagraphDialog.vue'\nimport { MsgConfirm } from '@/utils/message'\nimport { t } from '@/locales'\n\nconst page_size = ref<number>(30)\nconst current_page = ref<number>(1)\nconst currentCIndex = ref<number>(0)\nconst EditParagraphDialogRef = ref()\nconst emit = defineEmits(['update:modelValue'])\nconst loading = ref<boolean>(false)\nconst localParagraphList = ref<any[]>([])\n\nconst props = defineProps({\n  modelValue: {\n    type: Array<any>,\n    default: () => [],\n  },\n  isConnect: Boolean,\n  knowledgeId: String,\n})\n\n// 初始化加载数据\nwatchEffect(() => {\n  if (props.modelValue && props.modelValue.length > 0) {\n    const end = page_size.value * current_page.value\n    localParagraphList.value = props.modelValue.slice(0, Math.min(end, props.modelValue.length))\n  }\n})\n\n// 监听分页变化，只加载需要的数据\nwatchEffect(() => {\n  const start = 0\n  const end = page_size.value * current_page.value\n  // 不管数据量多少，都确保获取所有应该显示的数据\n  localParagraphList.value = props.modelValue.slice(start, Math.min(end, props.modelValue.length))\n})\n\nconst paragraph_list = computed(() => {\n  return localParagraphList.value\n})\n\nconst next = () => {\n  if (loading.value) return\n  loading.value = true\n  setTimeout(() => {\n    loading.value = false\n  }, 100)\n}\n\nconst editHandle = (item: any, cIndex: number) => {\n  // 计算实际索引，考虑分页\n  currentCIndex.value = cIndex\n  // currentCIndex.value = cIndex + page_size.value * (current_page.value - 1)\n  // console.log('Edit index:', cIndex, page_size.value, current_page.value, currentCIndex.value)\n  EditParagraphDialogRef.value.open(item)\n}\n\nconst updateContent = (data: any) => {\n  const new_value = [...props.modelValue]\n  if (\n    props.isConnect &&\n    data.title &&\n    !data?.problem_list.some((item: any) => item.content === data.title.trim())\n  ) {\n    data['problem_list'].push({\n      content: data.title.trim(),\n    })\n  }\n  new_value[currentCIndex.value] = cloneDeep(data)\n  emit('update:modelValue', new_value)\n\n  // 更新本地列表\n  const localIndex = currentCIndex.value - page_size.value * (current_page.value - 1)\n  if (localIndex >= 0 && localIndex < localParagraphList.value.length) {\n    localParagraphList.value[localIndex] = cloneDeep(data)\n  }\n}\n\nconst deleteHandle = (item: any, cIndex: number) => {\n  MsgConfirm(\n    `${t('views.paragraph.delete.confirmTitle')}${item.title || '-'} ?`,\n    t('views.paragraph.delete.confirmMessage'),\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      const new_value = [...props.modelValue]\n      new_value.splice(cIndex, 1)\n      emit('update:modelValue', new_value)\n\n      // 更新本地列表\n      localParagraphList.value.splice(cIndex, 1)\n      // 如果当前页删除完了，从总数据中再取一条添加到末尾\n      if (props.modelValue.length > localParagraphList.value.length * current_page.value) {\n        const nextItem = props.modelValue[localParagraphList.value.length * current_page.value]\n        if (nextItem) {\n          localParagraphList.value.push(nextItem)\n        }\n      }\n    })\n    .catch(() => {})\n}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/component/ParagraphPreview.vue",
    "content": "<template>\n  <el-tabs v-model=\"activeName\" class=\"paragraph-tabs\">\n    <template v-for=\"(item, index) in data\" :key=\"index\">\n      <el-tab-pane :label=\"item.name\" :name=\"index\">\n        <template #label>\n          <div class=\"flex-center\">\n            <img :src=\"getImgUrl(item && item?.name)\" alt=\"\" height=\"16\" />\n            <span class=\"ml-4\">{{ item?.name }}</span>\n          </div>\n        </template>\n        <div class=\"mb-16\">\n          <el-text type=\"info\">{{ item.content.length }} {{ $t('views.paragraph.title') }}</el-text>\n        </div>\n        <div class=\"paragraph-list\" v-if=\"activeName == index\">\n          <el-scrollbar>\n            <ParagraphList\n              v-model=\"item.content\"\n              :isConnect=\"isConnect\"\n              :knowledge-id=\"knowledgeId\"\n            />\n          </el-scrollbar>\n        </div>\n      </el-tab-pane>\n    </template>\n  </el-tabs>\n</template>\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { getImgUrl } from '@/utils/common'\nimport ParagraphList from './ParagraphList.vue'\n\ndefineProps({\n  data: {\n    type: Array<any>,\n    default: () => [],\n  },\n  isConnect: Boolean,\n  knowledgeId: String,\n})\n\nconst activeName = ref(0)\n</script>\n<style scoped lang=\"scss\">\n.paragraph-list {\n  height: calc(100vh - 319px);\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/knowledge/component/SyncWebDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.knowledge.syncWeb.title')\"\n    v-model=\"dialogVisible\"\n    width=\"600px\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n  >\n    <p class=\"mb-8\">{{ $t('views.knowledge.syncWeb.syncMethod') }}</p>\n    <el-radio-group v-model=\"method\" class=\"card__radio\">\n      <el-card shadow=\"never\" class=\"mb-16\" :class=\"method === 'replace' ? 'border-active' : ''\">\n        <el-radio value=\"replace\" size=\"large\">\n          <p class=\"mb-4\">{{ $t('views.knowledge.syncWeb.replace') }}</p>\n          <el-text type=\"info\">{{ $t('views.knowledge.syncWeb.replaceText') }}</el-text>\n        </el-radio>\n      </el-card>\n\n      <el-card shadow=\"never\" class=\"mb-16\" :class=\"method === 'complete' ? 'border-active' : ''\">\n        <el-radio value=\"complete\" size=\"large\">\n          <p class=\"mb-4\">{{ $t('views.knowledge.syncWeb.complete') }}</p>\n          <el-text type=\"info\">{{ $t('views.knowledge.syncWeb.completeText') }}</el-text>\n        </el-radio>\n      </el-card>\n    </el-radio-group>\n    <p class=\"color-danger\">{{ $t('views.knowledge.syncWeb.tip') }}</p>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit\" :loading=\"loading\">\n          {{ $t('common.confirm') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { MsgSuccess } from '@/utils/message'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { t } from '@/locales'\n\nconst route = useRoute()\nconst {\n  query: { from },\n} = route\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management') || from === 'systemManage') {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst emit = defineEmits(['refresh'])\nconst loading = ref<boolean>(false)\nconst method = ref('replace')\nconst knowledgeId = ref('')\n\nconst dialogVisible = ref<boolean>(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    method.value = 'replace'\n  }\n})\n\nconst open = (id: string) => {\n  knowledgeId.value = id\n  dialogVisible.value = true\n}\n\nconst submit = () => {\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .putSyncWebKnowledge(knowledgeId.value, method.value, loading)\n    .then((res: any) => {\n      emit('refresh', res.data)\n      MsgSuccess(t('views.knowledge.tip.syncSuccess'))\n      dialogVisible.value = false\n    })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped>\n.select-provider {\n  font-size: 16px;\n  color: rgba(100, 106, 115, 1);\n  font-weight: 400;\n  line-height: 24px;\n  cursor: pointer;\n  &:hover {\n    color: var(--el-color-primary);\n  }\n}\n.active-breadcrumb {\n  font-size: 16px;\n  color: var(--el-text-color-primary);\n  font-weight: 500;\n  line-height: 24px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/knowledge/create-component/CreateKnowledgeDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.knowledge.knowledgeType.createGeneralKnowledge')\"\n    v-model=\"dialogVisible\"\n    width=\"720\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <!-- 基本信息 -->\n    <BaseForm ref=\"BaseFormRef\" v-if=\"dialogVisible\" :apiType=\"apiType\" />\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\" :loading=\"loading\">\n          {{ $t('common.cancel') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle\" :loading=\"loading\">\n          {{ $t('common.create') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport BaseForm from '@/views/knowledge/component/BaseForm.vue'\nimport { MsgSuccess, MsgAlert } from '@/utils/message'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { t } from '@/locales'\nimport useStore from \"@/stores\";\nconst emit = defineEmits(['refresh'])\n\nconst { user } = useStore()\nconst router = useRouter()\nconst route = useRoute()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst BaseFormRef = ref()\n\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\nconst currentFolder = ref<any>(null)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    currentFolder.value = null\n  }\n})\n\nconst open = (folder: string) => {\n  currentFolder.value = folder\n  dialogVisible.value = true\n}\n\nconst submitHandle = async () => {\n  if (await BaseFormRef.value?.validate()) {\n    const obj = {\n      folder_id: currentFolder.value?.id,\n      ...BaseFormRef.value.form,\n    }\n    loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n      .postKnowledge(obj, loading)\n      .then(async (res: any) => {\n        await user.profile();\n        return res\n      })\n      .then((res: any) => {\n        MsgSuccess(t('common.createSuccess'))\n        router.push({\n          path: `/knowledge/${res.data.id}/${currentFolder.value.id || 'shared'}/0/document`,\n        })\n        emit('refresh')\n      })\n  } else {\n    return false\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/create-component/CreateLarkKnowledgeDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.knowledge.knowledgeType.createLarkKnowledge')\"\n    v-model=\"dialogVisible\"\n    width=\"720\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <!-- 基本信息 -->\n    <BaseForm ref=\"BaseFormRef\" v-if=\"dialogVisible\" :apiType=\"apiType\" />\n    <el-form\n      ref=\"knowledgeFormRef\"\n      :rules=\"rules\"\n      :model=\"knowledgeForm\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item label=\"App ID\" prop=\"app_id\">\n        <el-input\n          v-model=\"knowledgeForm.app_id\"\n          :placeholder=\"$t('views.application.applicationAccess.larkSetting.appIdPlaceholder')\"\n        />\n      </el-form-item>\n      <el-form-item label=\"App Secret\" prop=\"app_secret\">\n        <el-input\n          v-model=\"knowledgeForm.app_secret\"\n          type=\"password\"\n          show-password\n          :placeholder=\"$t('views.application.applicationAccess.larkSetting.appSecretPlaceholder')\"\n        />\n      </el-form-item>\n      <el-form-item label=\"Folder Token\" prop=\"folder_token\">\n        <el-input\n          v-model=\"knowledgeForm.folder_token\"\n          :placeholder=\"\n            $t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder')\n          \"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\" :loading=\"loading\">\n          {{ $t('common.cancel') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle\" :loading=\"loading\">\n          {{ $t('common.create') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive, computed } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport BaseForm from '@/views/knowledge/component/BaseForm.vue'\nimport { MsgSuccess, MsgAlert } from '@/utils/message'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport useStore from \"@/stores\";\nconst emit = defineEmits(['refresh'])\n\nconst { user } = useStore()\nconst router = useRouter()\nconst route = useRoute()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst BaseFormRef = ref()\nconst knowledgeFormRef = ref()\n\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\nconst currentFolder = ref<any>(null)\n\nconst knowledgeForm = ref<any>({\n  type: '0',\n  source_url: '',\n  selector: '',\n  app_id: '',\n  app_secret: '',\n  folder_token: '',\n})\n\nconst rules = reactive({\n  source_url: [\n    {\n      required: true,\n      message: t('views.knowledge.form.source_url.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  app_id: [\n    {\n      required: true,\n      message: t('views.application.applicationAccess.larkSetting.appIdPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  app_secret: [\n    {\n      required: true,\n      message: t('views.application.applicationAccess.larkSetting.appSecretPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  folder_token: [\n    {\n      required: true,\n      message: t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  user_id: [\n    {\n      required: true,\n      message: t('views.knowledge.form.user_id.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  token: [\n    {\n      required: true,\n      message: t('views.knowledge.form.token.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    knowledgeForm.value = {\n      type: '0',\n      source_url: '',\n      selector: '',\n    }\n    knowledgeFormRef.value?.clearValidate()\n  }\n})\n\nconst open = (folder: string) => {\n  currentFolder.value = folder\n  dialogVisible.value = true\n}\n\nconst submitHandle = async () => {\n  if (await BaseFormRef.value?.validate()) {\n    await knowledgeFormRef.value.validate((valid: any) => {\n      if (valid) {\n        const obj = {\n          folder_id: currentFolder.value?.id,\n          ...BaseFormRef.value.form,\n          ...knowledgeForm.value,\n        }\n        loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n          .postLarkKnowledge(obj, loading)\n          .then(async (res: any) => {\n            await user.profile();\n            return res\n          })\n          .then((res: any) => {\n            MsgSuccess(t('common.createSuccess'))\n            router.push({\n              path: `/knowledge/${res.data.id}/${currentFolder.value.id || 'shared'}/2/document`,\n              query: {\n                from: apiType.value,\n              },\n            })\n            emit('refresh')\n          })\n      } else {\n        return false\n      }\n    })\n  } else {\n    return false\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/create-component/CreateWebKnowledgeDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.knowledge.knowledgeType.createWebKnowledge')\"\n    v-model=\"dialogVisible\"\n    width=\"720\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <!-- 基本信息 -->\n    <BaseForm ref=\"BaseFormRef\" v-if=\"dialogVisible\" :apiType=\"apiType\" />\n    <el-form\n      ref=\"KnowledgeFormRef\"\n      :rules=\"rules\"\n      :model=\"knowledgeForm\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item :label=\"$t('views.knowledge.form.source_url.label')\" prop=\"source_url\">\n        <el-input\n          v-model=\"knowledgeForm.source_url\"\n          :placeholder=\"$t('views.knowledge.form.source_url.placeholder')\"\n          @blur=\"knowledgeForm.source_url = knowledgeForm.source_url.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('views.knowledge.form.selector.label')\">\n        <el-input\n          v-model=\"knowledgeForm.selector\"\n          :placeholder=\"$t('views.knowledge.form.selector.placeholder')\"\n          @blur=\"knowledgeForm.selector = knowledgeForm.selector.trim()\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\" :loading=\"loading\">\n          {{ $t('common.cancel') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle\" :loading=\"loading\">\n          {{ $t('common.create') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive, computed } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport BaseForm from '@/views/knowledge/component/BaseForm.vue'\nimport { MsgSuccess, MsgAlert } from '@/utils/message'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { t } from '@/locales'\nimport useStore from \"@/stores\";\nconst emit = defineEmits(['refresh'])\n\nconst { user } = useStore()\nconst router = useRouter()\nconst route = useRoute()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst BaseFormRef = ref()\nconst KnowledgeFormRef = ref()\n\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\n\nconst knowledgeForm = ref<any>({\n  source_url: '',\n  selector: '',\n})\n\nconst rules = reactive({\n  source_url: [\n    {\n      required: true,\n      message: t('views.knowledge.form.source_url.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst currentFolder = ref<any>(null)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    currentFolder.value = null\n    knowledgeForm.value = {\n      source_url: '',\n      selector: '',\n    }\n    KnowledgeFormRef.value?.clearValidate()\n  }\n})\n\nconst open = (folder: string) => {\n  currentFolder.value = folder\n  dialogVisible.value = true\n}\n\nconst submitHandle = async () => {\n  if (await BaseFormRef.value?.validate()) {\n    await KnowledgeFormRef.value.validate((valid: any) => {\n      if (valid) {\n        const obj = {\n          folder_id: currentFolder.value?.id,\n          ...BaseFormRef.value.form,\n          ...knowledgeForm.value,\n        }\n        loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n          .postWebKnowledge(obj, loading)\n          .then(async (res: any) => {\n            await user.profile();\n            return res\n          })\n          .then((res: any) => {\n            MsgSuccess(t('common.createSuccess'))\n            router.push({\n              path: `/knowledge/${res.data.id}/${currentFolder.value.id || 'shared'}/1/document`,\n              query: {\n                from: apiType.value,\n              },\n            })\n            emit('refresh')\n          })\n      } else {\n        return false\n      }\n    })\n  } else {\n    return false\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/create-component/CreateWorkflowKnowledgeDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.knowledge.knowledgeType.createWorkflowKnowledge')\"\n    v-model=\"dialogVisible\"\n    width=\"720\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <!-- 基本信息 -->\n    <BaseForm ref=\"BaseFormRef\" v-if=\"dialogVisible\" :apiType=\"apiType\" />\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\" :loading=\"loading\">\n          {{ $t('common.cancel') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle\" :loading=\"loading\">\n          {{ $t('common.create') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport BaseForm from '@/views/knowledge/component/BaseForm.vue'\nimport { MsgSuccess, MsgAlert } from '@/utils/message'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { knowledgeTemplate } from '@/workflow/common/template'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nconst emit = defineEmits(['refresh'])\n\nconst { user } = useStore()\nconst router = useRouter()\nconst route = useRoute()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst BaseFormRef = ref()\n\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\nconst currentFolder = ref<any>(null)\n\nconst workflowDefault = ref(knowledgeTemplate.default)\nconst workflowTemplate = ref()\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    currentFolder.value = null\n  }\n})\n\nconst open = (folder: string, workflow?: any) => {\n  currentFolder.value = folder\n  if (workflow) {\n    workflowTemplate.value = workflow\n  }\n  dialogVisible.value = true\n}\n\nconst submitHandle = async () => {\n  if (await BaseFormRef.value?.validate()) {\n    const obj = {\n      folder_id: currentFolder.value?.id,\n      work_flow: workflowDefault.value,\n      work_flow_template: workflowTemplate.value,\n      ...BaseFormRef.value.form,\n    }\n    loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n      .createWorkflowKnowledge(obj, loading)\n      .then(async (res: any) => {\n        await user.profile()\n        return res\n      })\n      .then((res: any) => {\n        MsgSuccess(t('common.createSuccess'))\n        router.push({\n          path: `/knowledge/${res.data.id}/${currentFolder.value.id || 'shared'}/workflow`,\n        })\n        emit('refresh')\n      })\n  } else {\n    return false\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/index.vue",
    "content": "<template>\n  <LayoutContainer showCollapse resizable class=\"knowledge-manage\">\n    <template #left>\n      <h4 class=\"p-12-16 pb-0 mt-12\">{{ $t('views.knowledge.title') }}</h4>\n\n      <folder-tree\n        :source=\"SourceTypeEnum.KNOWLEDGE\"\n        :data=\"folderList\"\n        :currentNodeKey=\"folder.currentFolder?.id\"\n        @handleNodeClick=\"folderClickHandle\"\n        :shareTitle=\"$t('views.shared.shared_knowledge')\"\n        :showShared=\"permissionPrecise['is_share']()\"\n        @refreshTree=\"refreshFolder\"\n        :draggable=\"true\"\n      />\n    </template>\n    <KnowledgeListContainer @refreshFolder=\"refreshFolder\">\n      <template #header>\n        <h2 v-if=\"folder.currentFolder?.id === 'share'\">\n          {{ $t('views.shared.shared_knowledge') }}\n        </h2>\n        <FolderBreadcrumb :folderList=\"folderList\" @click=\"folderClickHandle\" v-else />\n      </template>\n    </KnowledgeListContainer>\n  </LayoutContainer>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, shallowRef, nextTick, computed } from 'vue'\nimport KnowledgeListContainer from '@/views/knowledge/component/KnowledgeListContainer.vue'\nimport { SourceTypeEnum } from '@/enums/common'\nimport permissionMap from '@/permission'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nconst route = useRoute()\nconst { folder, knowledge } = useStore()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\nconst loading = ref(false)\n\nconst folderList = ref<any[]>([])\n\nfunction getFolder(bool?: boolean) {\n  const params = {}\n  folder\n    .asyncGetFolder(SourceTypeEnum.KNOWLEDGE, params, apiType.value, loading)\n    .then((res: any) => {\n      folderList.value = res.data\n      if (bool) {\n        // 初始化刷新\n        folder.setCurrentFolder(res.data?.[0] || {})\n      }\n    })\n}\n\nfunction folderClickHandle(row: any) {\n  if (row.id === folder.currentFolder?.id) {\n    return\n  }\n  folder.setCurrentFolder(row)\n  knowledge.setKnowledgeList([])\n}\n\nfunction refreshFolder() {\n  getFolder()\n}\n\nonMounted(() => {\n  getFolder(folder.currentFolder?.id ? false : true)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/template-store/InternalDescDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visibleInternalDesc\" size=\"60%\" :append-to-body=\"true\">\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <el-button class=\"cursor mr-4\" link @click.prevent=\"visibleInternalDesc = false\">\n          <el-icon :size=\"20\">\n            <Back />\n          </el-icon>\n        </el-button>\n        <h4>{{ $t('common.detail') }}</h4>\n      </div>\n    </template>\n\n    <div>\n      <div class=\"border-b\">\n        <div class=\"flex-between mb-24\">\n          <div class=\"title flex align-center\">\n            <el-avatar shape=\"square\" :size=\"64\" style=\"background: none\">\n              <img src=\"@/assets/knowledge/icon_basic_template.svg\" alt=\"\" />\n            </el-avatar>\n            <div class=\"ml-16\">\n              <h3 class=\"mb-8\">{{ toolDetail.name }}</h3>\n              <el-text type=\"info\" v-if=\"toolDetail?.desc\">\n                {{ toolDetail.desc }}\n              </el-text>\n              <span\n                class=\"color-secondary flex align-center mt-8\"\n                v-if=\"toolDetail?.downloads != undefined\"\n              >\n                <AppIcon iconName=\"app-download\" class=\"mr-4\" />\n                <span> {{ numberFormat(toolDetail.downloads || 0) }} </span>\n              </span>\n            </div>\n          </div>\n          <div @click.stop>\n            <el-button type=\"primary\" @click=\"addInternalTool(toolDetail)\">\n              {{ $t('common.use') }}\n            </el-button>\n          </div>\n        </div>\n      </div>\n      <MdPreview\n        ref=\"editorRef\"\n        editorId=\"preview-only\"\n        :modelValue=\"markdownContent\"\n        style=\"background: none\"\n        noImgZoomIn\n      />\n    </div>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport { isAppIcon, numberFormat } from '@/utils/common'\nconst emit = defineEmits(['refresh', 'addTool'])\n\nconst visibleInternalDesc = ref(false)\nconst markdownContent = ref('')\nconst toolDetail = ref<any>({})\n\nwatch(visibleInternalDesc, (bool) => {\n  if (!bool) {\n    markdownContent.value = ''\n  }\n})\n\nconst open = (data: any, detail: any) => {\n  toolDetail.value = detail\n  if (data) {\n    markdownContent.value = cloneDeep(data)\n  }\n  visibleInternalDesc.value = true\n}\n\nconst addInternalTool = (data: any) => {\n  emit('addTool', data)\n  visibleInternalDesc.value = false\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/knowledge/template-store/TemplateCard.vue",
    "content": "<template>\n  <CardBox :title=\"props.tool.name\" :description=\"props.tool.desc\" class=\"cursor tool-card\">\n    <template #icon>\n      <el-avatar shape=\"square\" :size=\"32\" style=\"background: none\">\n        <img src=\"@/assets/knowledge/icon_basic_template.svg\" alt=\"\" />\n      </el-avatar>\n    </template>\n    <template #title>\n      <span :title=\"props.tool?.name\" class=\"ellipsis\"> {{ props.tool?.name }}</span>\n    </template>\n    <template #footer>\n      <span class=\"card-footer-left color-secondary flex align-center\" v-if=\"props.tool?.downloads != undefined\">\n        <AppIcon iconName=\"app-download\" class=\"mr-4\" />\n        <span> {{ numberFormat(props.tool.downloads || 0) }} </span>\n      </span>\n\n      <div class=\"card-footer-operation mb-8\" @click.stop>\n        <el-button @click=\"emit('handleDetail')\">\n          {{ $t('common.detail') }}\n        </el-button>\n        <el-button type=\"primary\" :loading=\"props.addLoading\" @click=\"emit('handleAdd')\">\n          {{ $t('common.use') }}\n        </el-button>\n      </div>\n    </template>\n  </CardBox>\n</template>\n\n<script setup lang=\"ts\">\nimport { isAppIcon, numberFormat, resetUrl } from '@/utils/common'\n\nconst props = defineProps<{\n  tool: any\n  getSubTitle: (v: any) => string\n  addLoading: boolean\n}>()\n\nconst emit = defineEmits<{\n  (e: 'handleAdd'): void\n  (e: 'handleDetail'): void\n}>()\n</script>\n\n<style lang=\"scss\" scoped>\n.tool-card {\n  :deep(.card-footer) {\n    & > div:first-of-type {\n      flex: 1;\n    }\n\n    .card-footer-operation {\n      display: none;\n    }\n  }\n\n  &:hover {\n    .card-footer-left {\n      display: none;\n    }\n\n    .card-footer-operation {\n      display: flex !important;\n\n      .el-button {\n        flex: 1;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/knowledge/template-store/TemplateStoreDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    width=\"1000\"\n    append-to-body\n    class=\"tool-store-dialog\"\n    align-center\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <template #header=\"{ titleId }\">\n      <div class=\"dialog-header flex-between mb-8\">\n        <h4 :id=\"titleId\" class=\"medium w-240 mr-8\">\n          {{ $t('workflow.setting.templateCenter') }}\n        </h4>\n\n        <div class=\"flex align-center\" style=\"margin-right: 28px\">\n          <el-input\n            v-model=\"searchValue\"\n            :placeholder=\"$t('common.search')\"\n            prefix-icon=\"Search\"\n            class=\"w-240 mr-8\"\n            clearable\n            @change=\"getList\"\n          />\n          <el-divider direction=\"vertical\" />\n        </div>\n      </div>\n    </template>\n\n    <!-- <LayoutContainer v-loading=\"loading\" :minLeftWidth=\"204\">\n      <template #left>\n        <el-anchor\n          direction=\"vertical\"\n          :offset=\"130\"\n          type=\"default\"\n          container=\".category-scrollbar\"\n          @click=\"handleClick\"\n        >\n          <el-anchor-link\n            v-for=\"category in categories\"\n            :key=\"category.id\"\n            :href=\"`#category-${category.id}`\"\n            :title=\"category.title\"\n          />\n        </el-anchor>\n      </template> -->\n\n    <el-scrollbar class=\"layout-bg\" wrap-class=\"p-16-24 category-scrollbar\">\n      <template v-if=\"filterList === null\">\n        <div v-for=\"category in categories\" :key=\"category.id\">\n          <!-- <h4\n              class=\"title-decoration-1 mb-16 mt-8 color-text-primary\"\n              :id=\"`category-${category.id}`\"\n            >\n              {{ category.title }}\n            </h4> -->\n          <el-row :gutter=\"16\">\n            <el-col v-for=\"tool in category.tools\" :key=\"tool.id\" :span=\"8\" class=\"mb-16\">\n              <TemplateCard\n                :tool=\"tool\"\n                :addLoading=\"addLoading\"\n                :get-sub-title=\"getSubTitle\"\n                @handleAdd=\"handleOpenAdd(tool)\"\n                @handleDetail=\"handleDetail(tool)\"\n              >\n              </TemplateCard>\n            </el-col>\n          </el-row>\n        </div>\n      </template>\n      <div v-else>\n        <!-- <h4 class=\"color-text-primary medium mb-16\">\n            <span class=\"color-primary\">{{ searchValue }}</span>\n            {{ t('views.tool.toolStore.searchResult', { count: filterList.length }) }}\n          </h4> -->\n        <el-row :gutter=\"16\" v-if=\"filterList.length\">\n          <el-col v-for=\"tool in filterList\" :key=\"tool.id\" :span=\"8\" class=\"mb-16\">\n            <TemplateCard\n              :tool=\"tool\"\n              :addLoading=\"addLoading\"\n              :get-sub-title=\"getSubTitle\"\n              @handleAdd=\"handleOpenAdd(tool)\"\n              @handleDetail=\"handleDetail(tool)\"\n            />\n          </el-col>\n        </el-row>\n        <el-empty v-else :description=\"$t('common.noData')\" />\n      </div>\n    </el-scrollbar>\n    <!-- </LayoutContainer> -->\n  </el-dialog>\n  <InternalDescDrawer ref=\"internalDescDrawerRef\" @addTool=\"handleOpenAdd\" />\n  <CreateWorkflowKnowledgeDialog ref=\"CreateKnowledgeDialogRef\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport ToolStoreApi from '@/api/tool/store'\nimport { t } from '@/locales'\nimport TemplateCard from './TemplateCard.vue'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport InternalDescDrawer from './InternalDescDrawer.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport useStore from '@/stores'\nimport CreateWorkflowKnowledgeDialog from '@/views/knowledge/create-component/CreateWorkflowKnowledgeDialog.vue'\nimport { useRoute } from 'vue-router'\n\nconst { user } = useStore()\nconst route = useRoute()\nconst {\n  params: { id },\n  /*\n  folderId 可以区分 resource-management shared还是 workspace\n  */\n} = route as any\n\ninterface ToolCategory {\n  id: string\n  title: string\n  tools: any[]\n}\n\nconst props = defineProps({\n  apiType: {\n    type: String as () => 'workspace' | 'systemShare' | 'systemManage' | 'workspaceShare',\n    default: 'workspace',\n  },\n  source: {\n    type: String,\n    default: 'knowledge',\n  },\n})\nconst emit = defineEmits(['refresh'])\n\nconst dialogVisible = ref(false)\nconst loading = ref(false)\nconst searchValue = ref('')\nconst folderId = ref('')\nconst categories = ref<ToolCategory[]>([])\n\nconst filterList = ref<any>(null)\n\nfunction getSubTitle(tool: any) {\n  return categories.value.find((i) => i.id === tool.label)?.title ?? ''\n}\n\nfunction open(id: string) {\n  folderId.value = id\n  filterList.value = null\n  dialogVisible.value = true\n\n  getList()\n}\n\nasync function getList() {\n  filterList.value = null\n  const [v1] = await Promise.all([getStoreToolList()])\n\n  const merged = [...v1].reduce((acc, category) => {\n    const existing = acc.find((item: any) => item.id === category.id)\n    if (existing) {\n      existing.tools = [...existing.tools, ...category.tools]\n    } else {\n      acc.push({ ...category })\n    }\n    return acc\n  }, [] as ToolCategory[])\n\n  categories.value = merged.filter((item: any) => item.tools.length > 0)\n}\n\nasync function getStoreToolList() {\n  try {\n    const res = await ToolStoreApi.getStoreKBList({ name: searchValue.value }, loading)\n    const tags = res.data.additionalProperties.tags\n    const storeTools = res.data.apps\n    let categories = []\n    //\n    storeTools.forEach((tool: any) => {\n      tool.desc = tool.description\n    })\n    if (searchValue.value.length) {\n      filterList.value = [...res.data.apps, ...(filterList.value || [])]\n    } else {\n      filterList.value = null\n      categories = tags.map((tag: any) => ({\n        id: tag.key,\n        title: tag.name, // 国际化\n        tools: storeTools.filter((tool: any) => tool.label === tag.key),\n      }))\n    }\n    return categories\n  } catch (error) {\n    console.error(error)\n    return []\n  }\n}\n\nconst handleClick = (e: MouseEvent) => {\n  e.preventDefault()\n}\n\nconst internalDescDrawerRef = ref<InstanceType<typeof InternalDescDrawer>>()\n\nasync function handleDetail(tool: any) {\n  internalDescDrawerRef.value?.open(tool.readMe, tool)\n}\n\nconst CreateKnowledgeDialogRef = ref()\n\nfunction handleOpenAdd(data?: any, isEdit?: boolean) {\n  if (props.source === 'work_flow') {\n    MsgConfirm(\n      t('common.tip'),\n      `${t('views.application.tip.confirmUse')} ${data.name} ${t('views.application.tip.overwrite')}?`,\n      {\n        confirmButtonText: t('common.confirm'),\n        cancelButtonText: t('common.cancel'),\n      },\n    )\n      .then(() => {\n        handleStoreAdd(data)\n      })\n      .catch(() => {})\n  } else {\n    CreateKnowledgeDialogRef.value.open({ id: folderId.value }, data)\n  }\n}\n\nconst addLoading = ref(false)\n\nfunction handleStoreAdd(tool: any) {\n  try {\n    loadSharedApi({ type: 'knowledge', systemType: props.apiType })\n      .putKnowledgeWorkflow(id, { work_flow_template: tool })\n      .then(() => {\n        emit('refresh')\n        MsgSuccess(t('common.addSuccess'))\n      })\n    dialogVisible.value = false\n  } catch (error) {\n    console.error(error)\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\">\n.tool-store-dialog {\n  padding: 0;\n\n  .el-dialog__headerbtn {\n    top: 7px;\n  }\n\n  .el-dialog__header {\n    padding: 12px 20px 4px 24px;\n    border-bottom: 1px solid var(--el-border-color-light);\n\n    .dialog-header {\n      position: relative;\n\n      .store-type {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate(-50%, -50%);\n      }\n    }\n  }\n\n  .layout-container__left {\n    background-color: var(--app-layout-bg-color);\n    border-radius: 0 0 0 8px;\n  }\n\n  .layout-container__right {\n    background-color: var(--app-layout-bg-color);\n    border-radius: 0 0 8px 0;\n  }\n\n  .el-anchor {\n    background-color: var(--app-layout-bg-color);\n\n    .el-anchor__marker {\n      display: none;\n    }\n\n    .el-anchor__list {\n      padding: 8px;\n    }\n\n    .el-anchor__item {\n      .el-anchor__link {\n        padding: 8px 16px;\n        font-weight: 500;\n        font-size: 14px;\n        color: var(--el-text-color-primary);\n        border-radius: 6px;\n\n        &.is-active {\n          color: var(--el-color-primary);\n          background-color: #3370ff1a;\n        }\n      }\n    }\n  }\n\n  .category-scrollbar {\n    height: calc(100vh - 200px);\n    // min-height: 500px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/knowledge-workflow/component/DebugDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"drawerVisible\"\n    :title=\"$t('common.debug')\"\n    size=\"800px\"\n    direction=\"rtl\"\n    destroy-on-close\n    :before-close=\"close\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <div style=\"height: calc(100% - 57px)\" v-loading=\"loading\">\n      <keep-alive :key=\"key\" :include=\"['DataSource', 'KnowledgeBase']\">\n        <component\n          ref=\"ActionRef\"\n          :is=\"ak[active]\"\n          v-model:loading=\"loading\"\n          :workflow=\"_workflow\"\n          :knowledge_id=\"id\"\n          :id=\"action_id\"\n        ></component>\n      </keep-alive>\n    </div>\n    <template #footer>\n      <el-button v-if=\"active == 'result'\" @click=\"continueImporting\">\n        {{ $t('views.document.buttons.continueImporting') }}\n      </el-button>\n      <el-button\n        v-if=\"base_form_list.length > 0 && active == 'knowledge_base'\"\n        :loading=\"loading\"\n        @click=\"up\"\n      >\n        {{ $t('common.steps.prev') }}\n      </el-button\n      >\n      <el-button\n        v-if=\"base_form_list.length > 0 && active == 'data_source'\"\n        :loading=\"loading\"\n        @click=\"next\"\n      >\n        {{ $t('common.steps.next') }}\n      </el-button>\n      <el-button\n        v-if=\"base_form_list.length > 0 ? active == 'knowledge_base' : active == 'data_source'\"\n        @click=\"upload\"\n        type=\"primary\"\n        :loading=\"loading\"\n      >\n        {{ $t('views.document.buttons.import') }}\n      </el-button>\n      <el-button v-if=\"active == 'result'\" type=\"primary\" @click=\"goDocument\">{{\n          $t('views.knowledge.ResultSuccess.buttons.toDocument')\n        }}\n      </el-button>\n    </template>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport {computed, ref, provide, type Ref, nextTick} from 'vue'\nimport DataSource from '@/views/knowledge-workflow/component/action/DataSource.vue'\nimport Result from '@/views/knowledge-workflow/component/action/Result.vue'\nimport applicationApi from '@/api/application/application'\nimport KnowledgeBase from '@/views/knowledge-workflow/component/action/KnowledgeBase.vue'\nimport {WorkflowType} from '@/enums/application'\n\nimport {loadSharedApi} from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\nimport {MsgError} from '@/utils/message'\nimport {t} from '@/locales'\nimport {useRoute, useRouter} from 'vue-router'\n\nprovide('upload', (file: any, loading?: Ref<boolean>) => {\n  return applicationApi.postUploadFile(file, id, 'KNOWLEDGE', loading)\n})\nconst key = ref<number>(0)\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  params: {id, folderId},\n  /*\n  id 为 knowledge_id\n  */\n} = route as any\nconst ak = {\n  data_source: DataSource,\n  knowledge_base: KnowledgeBase,\n  result: Result,\n}\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst loading = ref<boolean>(false)\nconst action_id = ref<string>()\nconst ActionRef = ref()\nconst form_data = ref<any>({})\nconst active = ref<'data_source' | 'knowledge_base' | 'result'>('data_source')\nconst drawerVisible = ref<boolean>(false)\nconst _workflow = ref<any>(null)\nconst close = () => {\n  drawerVisible.value = false\n  _workflow.value = null\n  active.value = 'data_source'\n}\nconst open = (workflow: any) => {\n  drawerVisible.value = true\n  _workflow.value = workflow\n}\n\nconst base_form_list = computed(() => {\n  const kBase = _workflow.value?.nodes?.find((n: any) => n.type === WorkflowType.KnowledgeBase)\n  if (kBase) {\n    return kBase.properties.user_input_field_list\n  }\n  return []\n})\nconst next = () => {\n  ActionRef.value.validate().then(() => {\n    form_data.value[active.value] = ActionRef.value.get_data()\n    active.value = 'knowledge_base'\n  })\n}\nconst up = () => {\n  ActionRef.value.validate().then(() => {\n    active.value = 'data_source'\n  })\n}\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst upload = () => {\n  if (permissionPrecise.value.doc_create(id)) {\n    ActionRef.value.validate().then(() => {\n      form_data.value[active.value] = ActionRef.value.get_data()\n      loadSharedApi({type: 'knowledge', isShared: isShared.value, systemType: apiType.value})\n        .workflowAction(id, form_data.value, loading)\n        .then((ok: any) => {\n          action_id.value = ok.data.id\n          active.value = 'result'\n        })\n    })\n  } else {\n    MsgError(t('views.application.tip.noDocPermission'))\n  }\n}\nconst continueImporting = () => {\n  active.value = 'data_source'\n  key.value++\n  action_id.value = undefined\n  const c_workflow = _workflow.value\n  _workflow.value = null\n  form_data.value = {}\n  nextTick(() => {\n    _workflow.value = c_workflow\n  })\n}\nconst goDocument = () => {\n  const newUrl = router.resolve({\n    path: `/knowledge/${id}/${folderId}/4/document`,\n  }).href\n  window.open(newUrl)\n}\ndefineExpose({close, open})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge-workflow/component/PublishHistory.vue",
    "content": "<template>\n  <div class=\"workflow-publish-history border-l white-bg\">\n    <h4 class=\"border-b p-16-24\">{{ $t('workflow.setting.releaseHistory') }}</h4>\n    <div class=\"list-height pt-0\">\n      <el-scrollbar>\n        <div class=\"p-8 pt-0\">\n          <common-list\n            :data=\"LogData\"\n            class=\"mt-8\"\n            v-loading=\"loading\"\n            @click=\"clickListHandle\"\n            @mouseenter=\"mouseenter\"\n            @mouseleave=\"mouseId = ''\"\n          >\n            <template #default=\"{ row, index }\">\n              <div class=\"flex-between\">\n                <div style=\"max-width: 80%\">\n                  <h5 :class=\"index === 0 ? 'primary' : ''\" class=\"flex align-center\">\n                    <ReadWrite\n                      @change=\"editName($event, row)\"\n                      :data=\"row.name || datetimeFormat(row.update_time)\"\n                      trigger=\"manual\"\n                      :write=\"row.writeStatus\"\n                      @close=\"closeWrite(row)\"\n                    />\n                    <el-tag v-if=\"index === 0\" size=\"small\" class=\"default-tag ml-4\">{{\n                        $t('workflow.setting.latestRelease')\n                      }}\n                    </el-tag>\n                  </h5>\n                  <el-text type=\"info\" class=\"color-secondary flex align-center mt-8\">\n                    <el-avatar :size=\"20\" class=\"avatar-grey mr-4\">\n                      <el-icon>\n                        <UserFilled/>\n                      </el-icon>\n                    </el-avatar>\n                    {{ row.publish_user_name }}\n                  </el-text>\n                </div>\n\n                <div @click.stop v-show=\"mouseId === row.id\">\n                  <el-dropdown trigger=\"click\" :teleported=\"false\">\n                    <el-button text>\n                      <AppIcon iconName=\"app-more\"></AppIcon>\n                    </el-button>\n                    <template #dropdown>\n                      <el-dropdown-menu>\n                        <el-dropdown-item\n                          v-if=\"permissionPrecise.workflow_edit(id)\"\n                          @click.stop=\"openEditVersion(row)\"\n                        >\n                          <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n                          {{ $t('common.edit') }}\n                        </el-dropdown-item>\n                        <el-dropdown-item @click=\"refreshVersion(row)\">\n                          <el-icon class=\"color-secondary\">\n                            <RefreshLeft/>\n                          </el-icon>\n                          {{ $t('workflow.setting.restoreCurrentVersion') }}\n                        </el-dropdown-item>\n                      </el-dropdown-menu>\n                    </template>\n                  </el-dropdown>\n                </div>\n              </div>\n            </template>\n\n            <template #empty>\n              <div class=\"text-center\">\n                <el-text type=\"info\"> {{ $t('chat.noHistory') }}</el-text>\n              </div>\n            </template>\n          </common-list>\n        </div>\n      </el-scrollbar>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport {ref, onMounted, computed} from 'vue'\nimport {useRoute} from 'vue-router'\nimport {datetimeFormat} from '@/utils/time'\nimport {MsgSuccess, MsgError} from '@/utils/message'\nimport {t} from '@/locales'\nimport {loadSharedApi} from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\n\nconst route = useRoute()\nconst {\n  params: {id, folderId},\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst emit = defineEmits(['click', 'refreshVersion'])\nconst loading = ref(false)\nconst LogData = ref<any[]>([])\n\nconst mouseId = ref('')\n\nfunction mouseenter(row: any) {\n  mouseId.value = row.id\n}\n\nfunction clickListHandle(item: any) {\n  emit('click', item)\n}\n\nfunction refreshVersion(item: any) {\n  emit('refreshVersion', item)\n}\n\nfunction openEditVersion(item: any) {\n  item['writeStatus'] = true\n}\n\nfunction closeWrite(item: any) {\n  item['writeStatus'] = false\n}\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nfunction editName(val: string, item: any) {\n  if (val) {\n    const obj = {\n      name: val,\n    }\n    loadSharedApi({type: 'knowledge', isShared: isShared.value, systemType: apiType.value})\n      .updateKnowledgeVersion(id as string, item.id, obj, loading)\n      .then(() => {\n        MsgSuccess(t('common.modifySuccess'))\n        item['writeStatus'] = false\n        getList()\n      })\n  } else {\n    MsgError(t('workflow.tip.nameMessage'))\n  }\n}\n\nfunction getList() {\n  loadSharedApi({type: 'knowledge', isShared: isShared.value, systemType: apiType.value})\n    .listKnowledgeVersion(id, loading)\n    .then((res: any) => {\n      LogData.value = res.data\n    })\n}\n\nonMounted(() => {\n  getList()\n})\n</script>\n<style lang=\"scss\" scoped>\n.workflow-publish-history {\n  width: 320px;\n  position: absolute;\n  right: 0;\n  top: 57px;\n  height: calc(100vh - 57px);\n  z-index: 9;\n\n  .list-height {\n    height: calc(100vh - 120px);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/knowledge-workflow/component/action/DataSource.vue",
    "content": "<template>\n  <DynamicsForm\n    v-model=\"form_data\"\n    :render_data=\"model_form_field\"\n    :model=\"form_data\"\n    ref=\"dynamicsFormRef\"\n    label-position=\"top\"\n    require-asterisk-position=\"right\"\n    :other-params=\"{ current_workspace_id: workspace_id, current_knowledge_id: knowledge_id }\"\n  >\n    <template #default>\n      <h4 class=\"title-decoration-1 mb-16 mt-4\">\n        {{ $t('views.tool.dataSource.selectDataSource') }}\n      </h4>\n      <el-form-item\n        :label=\"$t('views.tool.dataSource.title')\"\n        prop=\"node_id\"\n        :rules=\"base_form_data_rule.node_id\"\n      >\n        <el-row class=\"w-full\" :gutter=\"8\">\n          <el-col :span=\"8\" v-for=\"node in source_node_list\" :key=\"node.id\">\n            <el-card\n              shadow=\"never\"\n              class=\"card-checkbox cursor w-full mb-8\"\n              :class=\"base_form_data.node_id === node.id ? 'border-active' : ''\"\n              style=\"--el-card-padding: 4px 12px\"\n              @click=\"sourceChange(node.id)\"\n            >\n              <div class=\"flex align-center\">\n                <component\n                  :is=\"iconComponent(`${node.type}-icon`)\"\n                  class=\"mr-8\"\n                  :size=\"20\"\n                  :item=\"node?.properties.node_data\"\n                />\n                {{ node.properties.stepName }}\n              </div>\n            </el-card>\n          </el-col>\n        </el-row>\n      </el-form-item>\n    </template>\n  </DynamicsForm>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, watch, provide } from 'vue'\nimport { WorkflowKind, WorkflowType } from '@/enums/application'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport { iconComponent } from '@/workflow/icons/utils'\nimport type { Dict } from '@/api/type/common'\nimport type { FormRules } from 'element-plus'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { useRoute } from 'vue-router'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nconst { user } = useStore()\nconst route = useRoute()\n\nconst props = defineProps<{\n  workflow: any\n  knowledge_id: string\n  loading: boolean\n}>()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst model_form_field = ref<Array<FormField>>([])\n\nconst workspace_id = computed(() => {\n  return user.getWorkspaceId()\n})\nconst emit = defineEmits(['update:loading'])\nconst _loading = computed({\n  get: () => {\n    return props.loading\n  },\n  set: (v: boolean) => {\n    emit('update:loading', v)\n  },\n})\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst base_form_data = ref<{ node_id: string }>({ node_id: '' })\nconst dynamics_form_data = ref<Dict<any>>({})\nconst form_data = computed({\n  get: () => {\n    return { ...dynamics_form_data.value, ...base_form_data.value }\n  },\n  set: (event: any) => {\n    dynamics_form_data.value = event\n  },\n})\nconst source_node_list = computed(() => {\n  return props.workflow?.nodes?.filter((n: any) => n.properties.kind === WorkflowKind.DataSource)\n})\nconst extra = ref<any>({\n  current_tool_id: undefined,\n})\nconst get_extra = () => {\n  return extra.value\n}\nprovide('get_extra', get_extra)\n\nconst sourceChange = (node_id: string) => {\n  base_form_data.value.node_id = node_id\n  const n = source_node_list.value.find((n: any) => n.id == node_id)\n  if (n.properties.node_data && n.properties.node_data.tool_lib_id) {\n    extra.value.current_tool_id = n.properties.node_data.tool_lib_id\n  }\n  node_id = n\n    ? [WorkflowType.DataSourceLocalNode, WorkflowType.DataSourceWebNode].includes(n.type)\n      ? n.type\n      : n.properties.node_data.tool_lib_id\n    : node_id\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .getKnowledgeWorkflowFormList(\n      props.knowledge_id,\n      [WorkflowType.DataSourceLocalNode, WorkflowType.DataSourceWebNode].includes(n.type)\n        ? 'local'\n        : 'tool',\n      node_id,\n      n,\n      _loading,\n    )\n    .then((ok: any) => {\n      dynamicsFormRef.value?.render(ok.data)\n    })\n}\nconst base_form_data_rule = ref<FormRules>({\n  node_id: {\n    required: true,\n    trigger: 'blur',\n    message: t('views.tool.dataSource.requiredMessage'),\n  },\n})\nconst validate = () => {\n  return dynamicsFormRef.value?.validate()\n}\nconst get_data = () => {\n  return form_data.value\n}\nwatch(\n  source_node_list,\n  () => {\n    if (!base_form_data.value.node_id) {\n      if (source_node_list.value && source_node_list.value.length > 0) {\n        sourceChange(source_node_list.value[0].id)\n      }\n    }\n  },\n  { immediate: true },\n)\n\ndefineExpose({ validate, get_data })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge-workflow/component/action/KnowledgeBase.vue",
    "content": "<template>\n  <DynamicsForm\n    v-loading=\"loading\"\n    v-model=\"form_data\"\n    :render_data=\"base_form_list\"\n    :model=\"form_data\"\n    ref=\"dynamicsFormRef\"\n    label-position=\"top\"\n    require-asterisk-position=\"right\"\n  >\n    <template #default>\n      <h4 class=\"title-decoration-1 mb-16 mt-4\">\n        {{ chat_title || $t('chat.userInput') }}\n      </h4>\n    </template>\n  </DynamicsForm>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport { WorkflowType } from '@/enums/application'\nconst props = defineProps<{ workflow: any }>()\nconst loading = ref<boolean>()\nconst form_data = ref<any>({})\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst validate = () => {\n  return dynamicsFormRef.value?.validate()\n}\nconst chat_title = computed(() => {\n  const kBase = props.workflow?.nodes?.find((n: any) => n.type === WorkflowType.KnowledgeBase)\n  return kBase.properties.user_input_config.title\n})\nconst base_form_list = computed(() => {\n  const kBase = props.workflow?.nodes?.find((n: any) => n.type === WorkflowType.KnowledgeBase)\n  if (kBase) {\n    return kBase.properties.user_input_field_list\n  }\n  return []\n})\nconst get_data = () => {\n  return form_data.value\n}\n\ndefineExpose({ validate, get_data })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge-workflow/component/action/Result.vue",
    "content": "<template>\n  <div>\n    <h4 class=\"title-decoration-1 mb-16 mt-4\">\n      {{ $t('chat.executionDetails.title') }}\n    </h4>\n    <div class=\"mb-16\" v-if=\"!isRecord\">\n      <!-- 执行结果 -->\n      <el-alert\n        v-if=\"state == 'SUCCESS'\"\n        :title=\"$t('common.status.success')\"\n        type=\"success\"\n        show-icon\n        :closable=\"false\"\n      />\n      <el-alert\n        v-if=\"state == 'FAILURE'\"\n        :title=\"$t('common.status.fail')\"\n        type=\"error\"\n        show-icon\n        :closable=\"false\"\n      />\n    </div>\n    <!-- <ExecutionDetailContent :detail=\"detail\" app-type=\"WORK_FLOW\"></ExecutionDetailContent> -->\n    <template v-for=\"(item, index) in arraySort(detail ?? [], 'index')\" :key=\"index\">\n      <ExecutionDetailCard :data=\"item\" type=\"knowledge\"> </ExecutionDetailCard>\n    </template>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { onUnmounted, ref, computed, watch } from 'vue'\nimport { arraySort } from '@/utils/array'\nimport ExecutionDetailCard from '@/components/execution-detail-card/index.vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nconst route = useRoute()\nconst props = defineProps<{ id: string; knowledge_id: string; isRecord: boolean }>()\nconst detail = computed(() => {\n  if (knowledge_action.value) {\n    return Object.values(knowledge_action.value.details)\n  }\n  return []\n})\nconst state = computed(() => {\n  if (knowledge_action.value) {\n    return knowledge_action.value.state\n  }\n  return 'PADDING'\n})\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst knowledge_action = ref<any>()\nlet pollingTimer: any = null\n\nconst getKnowledgeWorkflowAction = () => {\n  if (pollingTimer == null) {\n    return\n  }\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .getWorkflowAction(props.knowledge_id, props.id)\n    .then((ok: any) => {\n      knowledge_action.value = ok.data\n    })\n    .finally(() => {\n      if (['SUCCESS', 'FAILURE', 'REVOKED'].includes(state.value)) {\n        stopPolling()\n      } else {\n        // 请求完成后再设置下次轮询\n        pollingTimer = setTimeout(getKnowledgeWorkflowAction, 2000)\n      }\n    })\n}\n\nconst stopPolling = () => {\n  if (pollingTimer) {\n    clearTimeout(pollingTimer)\n    pollingTimer = null\n  }\n}\n\n// 启动轮询\npollingTimer = setTimeout(getKnowledgeWorkflowAction, 0)\n\nwatch(\n  () => props.id,\n  () => {\n    stopPolling()\n    pollingTimer = setTimeout(getKnowledgeWorkflowAction, 0)\n  },\n)\n\nonUnmounted(() => {\n  stopPolling()\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/knowledge-workflow/component/execution-record/ExecutionDetailDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"visible\"\n    size=\"800px\"\n    :modal=\"false\"\n    destroy-on-close\n    :before-close=\"closeHandle\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :show-close=\"false\"\n  >\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <el-button class=\"cursor mr-4\" link @click.prevent=\"visible = false\">\n          <el-icon :size=\"20\">\n            <Back />\n          </el-icon>\n        </el-button>\n        <h4>{{ $t('chat.executionDetails.title') }}</h4>\n      </div>\n    </template>\n    <div>\n      <el-scrollbar>\n        <h4 class=\"title-decoration-1 mb-16 mt-4\">\n          {{ $t('common.ExecutionRecord.title') }}\n        </h4>\n        <el-card class=\"mb-24\" shadow=\"never\" style=\"--el-card-padding: 12px 16px\">\n          <el-row :gutter=\"16\" class=\"lighter\">\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('workflow.initiator') }}</p>\n              <p>{{ props.currentContent?.meta?.user_name || '-' }}</p>\n            </el-col>\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('common.status.label') }}</p>\n              <p>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-if=\"props.currentContent?.state === 'SUCCESS'\"\n                >\n                  <el-icon class=\"color-success\"><SuccessFilled /></el-icon>\n                  {{ $t('common.status.success') }}\n                </el-text>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-else-if=\"props.currentContent?.state === 'FAILURE'\"\n                >\n                  <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n                  {{ $t('common.status.fail') }}\n                </el-text>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-else-if=\"props.currentContent?.state === 'REVOKED'\"\n                >\n                  <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n                  {{ $t('common.status.REVOKED') }}\n                </el-text>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-else-if=\"props.currentContent?.state === 'REVOKE'\"\n                >\n                  <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n                  {{ $t('common.status.REVOKE') }}\n                </el-text>\n                <el-text class=\"color-text-primary\" v-else>\n                  <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n                  {{ $t('common.status.STARTED') }}\n                </el-text>\n              </p>\n            </el-col>\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('chat.KnowledgeSource.consumeTime') }}</p>\n              <p>\n                {{\n                  props.currentContent?.run_time != undefined\n                    ? props.currentContent?.run_time?.toFixed(2) + 's'\n                    : '-'\n                }}\n              </p>\n            </el-col>\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('chat.executionDetails.createTime') }}</p>\n              <p>{{ datetimeFormat(props.currentContent?.create_time) }}</p>\n            </el-col>\n          </el-row>\n        </el-card>\n        <Result\n          :knowledge_id=\"props.currentContent.knowledge_id\"\n          :id=\"currentId\"\n          is-record\n          v-if=\"props.currentContent\"\n        />\n      </el-scrollbar>\n    </div>\n    <template #footer>\n      <div>\n        <el-button @click=\"pre\" :disabled=\"pre_disable || loading\">{{\n          $t('common.pages.prev')\n        }}</el-button>\n        <el-button @click=\"next\" :disabled=\"next_disable || loading\">{{\n          $t('common.pages.next')\n        }}</el-button>\n      </div>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport Result from '@/views/knowledge-workflow/component/action/Result.vue'\nimport { datetimeFormat } from '@/utils/time'\nconst props = withDefaults(\n  defineProps<{\n    /**\n     * 当前的action_id\n     */\n    currentId: string\n    currentContent: any\n    /**\n     * 下一条\n     */\n    next: () => void\n    /**\n     * 上一条\n     */\n    pre: () => void\n\n    pre_disable: boolean\n\n    next_disable: boolean\n  }>(),\n  {},\n)\n\nconst emit = defineEmits(['update:currentId', 'update:currentContent'])\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst loading = ref(false)\nconst visible = ref(false)\n\nfunction closeHandle() {}\n\nwatch(\n  () => props.currentId,\n  () => {},\n)\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    emit('update:currentId', '')\n    emit('update:currentContent', null)\n  }\n})\n\nconst open = () => {\n  visible.value = true\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/knowledge-workflow/component/execution-record/ExecutionRecordDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"drawer\"\n    :title=\"$t('common.ExecutionRecord.title')\"\n    direction=\"rtl\"\n    size=\"800px\"\n    :before-close=\"close\"\n    destroy-on-close\n  >\n    <div class=\"flex mb-16\">\n      <div class=\"flex-between complex-search\">\n        <el-select\n          v-model=\"filter_type\"\n          class=\"complex-search__left\"\n          @change=\"changeFilterHandle\"\n          style=\"width: 120px\"\n        >\n          <el-option :label=\"$t('workflow.initiator')\" value=\"user_name\" />\n          <el-option :label=\"$t('common.status.label')\" value=\"state\" />\n        </el-select>\n        <el-select\n          v-if=\"filter_type === 'state'\"\n          v-model=\"query.state\"\n          @change=\"getList(true)\"\n          style=\"width: 220px\"\n          clearable\n        >\n          <el-option :label=\"$t('common.status.success')\" value=\"SUCCESS\" />\n          <el-option :label=\"$t('common.status.fail')\" value=\"FAILURE\" />\n          <el-option :label=\"$t('common.status.STARTED')\" value=\"STARTED\" />\n          <el-option :label=\"$t('common.status.REVOKED')\" value=\"REVOKED\" />\n        </el-select>\n        <el-input\n          v-else\n          v-model=\"query.user_name\"\n          @change=\"getList(true)\"\n          :placeholder=\"$t('common.search')\"\n          prefix-icon=\"Search\"\n          style=\"width: 220px\"\n          clearable\n        />\n      </div>\n    </div>\n\n    <app-table\n      ref=\"multipleTableRef\"\n      class=\"mt-16 document-table\"\n      :data=\"tableData\"\n      :maxTableHeight=\"200\"\n      :pagination-config=\"paginationConfig\"\n      @sizeChange=\"changeSize\"\n      @changePage=\"getList(true)\"\n      v-loading=\"loading\"\n      :row-key=\"(row: any) => row.id\"\n    >\n      <el-table-column prop=\"user_name\" :label=\"$t('workflow.initiator')\">\n        <template #default=\"{ row }\">\n          {{ row.meta.user_name }}\n        </template>\n      </el-table-column>\n      <el-table-column prop=\"state\" :label=\"$t('common.status.label')\" width=\"180\">\n        <template #default=\"{ row }\">\n          <el-text class=\"color-text-primary\" v-if=\"row.state === 'SUCCESS'\">\n            <el-icon class=\"color-success\"><SuccessFilled /></el-icon>\n            {{ $t('common.status.success') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else-if=\"row.state === 'FAILURE'\">\n            <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n            {{ $t('common.status.fail') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else-if=\"row.state === 'REVOKED'\">\n            <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n            {{ $t('common.status.REVOKED') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else-if=\"row.state === 'REVOKE'\">\n            <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n            {{ $t('common.status.REVOKE') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else>\n            <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n            {{ $t('common.status.STARTED') }}\n          </el-text>\n        </template>\n      </el-table-column>\n      <el-table-column prop=\"run_time\" :label=\"$t('chat.KnowledgeSource.consumeTime')\">\n        <template #default=\"{ row }\">\n          {{ row.run_time != undefined ? row.run_time?.toFixed(2) + 's' : '-' }}\n        </template>\n      </el-table-column>\n      <el-table-column\n        prop=\"create_time\"\n        :label=\"$t('chat.executionDetails.createTime')\"\n        width=\"180\"\n      >\n        <template #default=\"{ row }\">\n          {{ datetimeFormat(row.create_time) }}\n        </template>\n      </el-table-column>\n\n      <el-table-column :label=\"$t('common.operation')\" width=\"90\">\n        <template #default=\"{ row }\">\n          <div class=\"flex\">\n            <el-tooltip effect=\"dark\" :content=\"$t('chat.executionDetails.title')\" placement=\"top\">\n              <el-button type=\"primary\" text @click.stop=\"toDetails(row)\">\n                <AppIcon iconName=\"app-operate-log\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('chat.executionDetails.cancel')\"\n              placement=\"top\"\n              v-if=\"['PADDING', 'STARTED'].includes(row.state)\"\n            >\n              <el-button type=\"danger\" text @click.stop=\"cancelExecution(row)\">\n                <el-icon><CircleCloseFilled /></el-icon>\n              </el-button>\n            </el-tooltip>\n          </div>\n        </template>\n      </el-table-column>\n    </app-table>\n\n    <ExecutionDetailDrawer\n      ref=\"ExecutionDetailDrawerRef\"\n      v-model:currentId=\"currentId\"\n      v-model:currentContent=\"currentContent\"\n      :next=\"nextRecord\"\n      :pre=\"preRecord\"\n      :pre_disable=\"pre_disable\"\n      :next_disable=\"next_disable\"\n    />\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport ExecutionDetailDrawer from './ExecutionDetailDrawer.vue'\nimport { computed, ref, reactive, onBeforeUnmount } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { datetimeFormat } from '@/utils/time'\nimport type { Dict } from '@/api/type/common'\nimport { MsgError, MsgConfirm } from '@/utils/message'\nimport { t } from '@/locales'\nconst drawer = ref<boolean>(false)\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 10,\n  total: 0,\n})\nconst query = ref<any>({\n  user_name: '',\n  state: '',\n})\nconst loading = ref(false)\nconst filter_type = ref<string>('user_name')\nconst active_knowledge_id = ref<string>('')\nconst tableData = ref<Array<any>>([])\n\nconst ExecutionDetailDrawerRef = ref<any>()\nconst currentId = ref<string>('')\nconst currentContent = ref<string>('')\n\nconst toDetails = (row: any) => {\n  currentContent.value = row\n  currentId.value = row.id\n\n  ExecutionDetailDrawerRef.value?.open()\n}\n\nconst cancelExecution = (row: any) => {\n  MsgConfirm(t('common.tip'), t('chat.executionDetails.cancelExecutionTip'), {\n    confirmButtonText: t('common.confirm'),\n    confirmButtonClass: 'danger',\n  }).then(() => {\n    loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n      .cancelWorkflowAction(active_knowledge_id.value, row.id, loading)\n      .then((ok: any) => {})\n  })\n}\nconst changeFilterHandle = () => {\n  query.value = { user_name: '', status: '' }\n}\nconst changeSize = () => {\n  paginationConfig.current_page = 1\n  getList()\n}\n\nconst getList = (isLoading?: boolean) => {\n  return loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .getWorkflowActionPage(\n      active_knowledge_id.value,\n      paginationConfig,\n      query.value,\n      isLoading ? loading : undefined,\n    )\n    .then((ok: any) => {\n      paginationConfig.total = ok.data?.total\n      tableData.value = ok.data.records\n    })\n}\n\n\nconst pre_disable = computed(() => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value)\n  return index === 0 && paginationConfig.current_page === 1\n})\n\nconst next_disable = computed(() => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value) + 1\n  return (\n    index >= tableData.value.length &&\n    index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=\n      paginationConfig.total - 1\n  )\n})\n\n\nconst interval = ref<any>()\n/**\n * 下一页\n */\nconst nextRecord = () => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value) + 1\n  if (index >= tableData.value.length) {\n    if (paginationConfig.current_page * paginationConfig.page_size >= paginationConfig.total) {\n      return\n    }\n    paginationConfig.current_page = paginationConfig.current_page + 1\n    getList(true).then(() => {\n      currentId.value = tableData.value[index].id\n      currentContent.value = tableData.value[index]\n    })\n    return\n  } else {\n    currentId.value = tableData.value[index].id\n    currentContent.value = tableData.value[index]\n  }\n}\n/**\n * 上一页\n */\nconst preRecord = () => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value) - 1\n  if (index < 0 && 1) {\n    if (paginationConfig.current_page === 1) {\n      return\n    }\n    paginationConfig.current_page = paginationConfig.current_page - 1\n    getList(true).then(() => {\n      currentId.value = tableData.value[tableData.value.length - 1].id\n      currentContent.value = tableData.value[tableData.value.length - 1]\n    })\n  } else {\n    currentId.value = tableData.value[index].id\n    currentContent.value = tableData.value[index]\n  }\n}\n\nconst open = (knowledge_id: string) => {\n  interval.value = setInterval(() => {\n    getList(false)\n  }, 6000)\n  active_knowledge_id.value = knowledge_id\n  getList(true)\n  drawer.value = true\n}\nconst close = () => {\n  paginationConfig.current_page = 1\n  paginationConfig.total = 0\n  tableData.value = []\n  drawer.value = false\n  if (interval.value) {\n    clearInterval(interval.value)\n  }\n}\nonBeforeUnmount(() => {\n  if (interval.value) {\n    clearInterval(interval.value)\n  }\n})\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/knowledge-workflow/index.vue",
    "content": "<template>\n  <div class=\"knowledge-workflow\" v-loading=\"loading\">\n    <div class=\"header border-b flex-between p-12-24 white-bg\">\n      <div class=\"flex align-center\">\n        <back-button @click=\"back\"></back-button>\n        <h4 class=\"ellipsis\" style=\"max-width: 300px\" :title=\"detail?.name\">{{ detail?.name }}</h4>\n        <div v-if=\"showHistory && disablePublic\">\n          <el-text type=\"info\" class=\"ml-16 color-secondary\"\n            >{{ $t('workflow.info.previewVersion') }}\n            {{ currentVersion.name || datetimeFormat(currentVersion.update_time) }}\n          </el-text>\n        </div>\n        <el-text type=\"info\" class=\"ml-16 color-secondary\" v-else-if=\"saveTime\"\n          >{{ $t('workflow.info.saveTime') }}{{ datetimeFormat(saveTime) }}\n        </el-text>\n      </div>\n      <div v-if=\"showHistory && disablePublic && !route.path.includes('share/')\">\n        <el-button type=\"primary\" class=\"mr-8\" @click=\"refreshVersion()\">\n          {{ $t('workflow.setting.restoreVersion') }}\n        </el-button>\n        <el-divider direction=\"vertical\" />\n        <el-button text @click=\"closeHistory\">\n          <el-icon>\n            <Close />\n          </el-icon>\n        </el-button>\n      </div>\n      <div v-else-if=\"!route.path.includes('share/')\">\n        <el-button\n          class=\"ml-8\"\n          v-if=\"permissionPrecise.create()\"\n          @click=\"openTemplateStoreDialog()\"\n        >\n          <AppIcon iconName=\"app-template-center\" class=\"mr-4\" />\n          {{ $t('workflow.setting.templateCenter') }}\n        </el-button>\n        <el-button @click=\"showPopover = !showPopover\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\" />\n          {{ $t('workflow.setting.addComponent') }}\n        </el-button>\n        <el-button @click=\"clickShowDebug\" :disabled=\"showDebug\" v-if=\"permissionPrecise.debug(id)\">\n          <AppIcon iconName=\"app-debug-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.debug') }}\n        </el-button>\n        <el-button v-if=\"permissionPrecise.workflow_edit(id)\" @click=\"saveknowledge(true)\">\n          <AppIcon iconName=\"app-save-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.save') }}\n        </el-button>\n        <el-button type=\"primary\" v-if=\"permissionPrecise.workflow_edit(id)\" @click=\"publish\">\n          {{ $t('common.publish') }}\n        </el-button>\n\n        <el-dropdown trigger=\"click\">\n          <el-button text @click.stop class=\"ml-8 mt-4\">\n            <AppIcon iconName=\"app-more\" class=\"rotate-90\"></AppIcon>\n          </el-button>\n          <template #dropdown>\n            <el-dropdown-menu>\n              <el-dropdown-item @click=\"toImportDoc\">\n                <AppIcon iconName=\"app-to-import-doc\" class=\"color-secondary\"></AppIcon>\n                {{ $t('workflow.operation.toImportDoc') }}\n              </el-dropdown-item>\n              <el-upload\n                class=\"import-button\"\n                ref=\"elUploadRef\"\n                accept=\".kbwf\"\n                :file-list=\"[]\"\n                action=\"#\"\n                multiple\n                :auto-upload=\"false\"\n                :show-file-list=\"false\"\n                :limit=\"1\"\n                :on-change=\"(file: any, fileList: any) => importKnowledgeWorkflow(file)\"\n                v-if=\"permissionPrecise.workflow_edit(id)\"\n              >\n                <el-dropdown-item>\n                  <AppIcon iconName=\"app-import\" class=\"color-secondary\"></AppIcon>\n                  {{ $t('workflow.operation.importWorkflow') }}\n                </el-dropdown-item>\n              </el-upload>\n              <el-dropdown-item\n                @click.stop=\"exportKnowledgeWorkflow(detail.name, detail.id)\"\n                v-if=\"permissionPrecise.workflow_export(id)\"\n              >\n                <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                {{ $t('workflow.operation.exportWorkflow') }}\n              </el-dropdown-item>\n\n              <el-dropdown-item\n                @click=\"openListAction\"\n                divided\n                v-if=\"permissionPrecise.doc_create(id)\"\n              >\n                <AppIcon iconName=\"app-execution-record\" class=\"color-secondary\"></AppIcon>\n                {{ $t('common.ExecutionRecord.title') }}\n              </el-dropdown-item>\n              <el-dropdown-item @click=\"openHistory\">\n                <AppIcon iconName=\"app-history-outlined\" class=\"color-secondary\"></AppIcon>\n                {{ $t('workflow.setting.releaseHistory') }}\n              </el-dropdown-item>\n              <el-dropdown-item v-if=\"permissionPrecise.workflow_edit(id)\">\n                <AppIcon iconName=\"app-save-outlined\" class=\"color-secondary\"></AppIcon>\n                {{ $t('workflow.setting.autoSave') }}\n                <div class=\"ml-4\">\n                  <el-switch size=\"small\" v-model=\"isSave\" @change=\"changeSave\" />\n                </div>\n              </el-dropdown-item>\n            </el-dropdown-menu>\n          </template>\n        </el-dropdown>\n      </div>\n    </div>\n    <!-- 下拉框 -->\n    <el-collapse-transition>\n      <DropdownMenu\n        :show=\"showPopover\"\n        :id=\"id\"\n        v-click-outside=\"clickoutside\"\n        @clickNodes=\"clickNodes\"\n        @onmousedown=\"onmousedown\"\n        :workflowRef=\"workflowRef\"\n      />\n    </el-collapse-transition>\n    <!-- 主画布 -->\n    <div class=\"workflow-main\" ref=\"workflowMainRef\">\n      <workflow ref=\"workflowRef\" v-if=\"detail\" :data=\"detail?.work_flow\" />\n    </div>\n    <!-- 调试 -->\n    <el-collapse-transition>\n      <div class=\"workflow-debug-container\" :class=\"enlarge ? 'enlarge' : ''\" v-if=\"showDebug\">\n        <div class=\"workflow-debug-header\" :class=\"!isDefaultTheme ? 'custom-header' : ''\">\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <div class=\"mr-12 ml-24 flex\">\n                <el-avatar\n                  v-if=\"isAppIcon(detail?.icon)\"\n                  shape=\"square\"\n                  :size=\"32\"\n                  style=\"background: none\"\n                >\n                  <img :src=\"resetUrl(detail?.icon)\" alt=\"\" />\n                </el-avatar>\n                <LogoIcon v-else height=\"32px\" />\n              </div>\n\n              <h4 class=\"ellipsis\" style=\"max-width: 270px\" :title=\"detail?.name\">\n                {{ detail?.name || $t('views.knowledge.form.appName.label') }}\n              </h4>\n            </div>\n            <div class=\"mr-16\">\n              <el-button link @click=\"enlarge = !enlarge\">\n                <AppIcon\n                  :iconName=\"enlarge ? 'app-minify' : 'app-magnify'\"\n                  class=\"color-secondary\"\n                  style=\"font-size: 20px\"\n                >\n                </AppIcon>\n              </el-button>\n              <el-button link @click=\"showDebug = false\">\n                <el-icon :size=\"20\" class=\"color-secondary\">\n                  <Close />\n                </el-icon>\n              </el-button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </el-collapse-transition>\n    <DebugVue ref=\"DebugRef\"></DebugVue>\n    <ExecutionRecord ref=\"ListActionRef\"></ExecutionRecord>\n    <!-- 发布历史 -->\n    <PublishHistory\n      v-if=\"showHistory\"\n      @click=\"checkVersion\"\n      v-click-outside=\"clickoutsideHistory\"\n      @refreshVersion=\"refreshVersion\"\n    />\n    <TemplateStoreDialog\n      ref=\"templateStoreDialogRef\"\n      :api-type=\"apiType\"\n      source=\"work_flow\"\n      @refresh=\"getDetail\"\n    />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onBeforeMount, onBeforeUnmount, computed, nextTick, provide } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport type { Action } from 'element-plus'\nimport Workflow from '@/workflow/index.vue'\nimport DropdownMenu from '@/components/workflow-dropdown-menu/index.vue'\nimport ExecutionRecord from '@/views/knowledge-workflow/component/execution-record/ExecutionRecordDrawer.vue'\nimport PublishHistory from '@/views/knowledge-workflow/component/PublishHistory.vue'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport { MsgSuccess, MsgError, MsgConfirm } from '@/utils/message'\nimport { datetimeFormat } from '@/utils/time'\nimport useStore from '@/stores'\nimport { KnowledgeWorkFlowInstance } from '@/workflow/common/validate'\nimport { hasPermission } from '@/utils/permission'\nimport DebugVue from './component/DebugDrawer.vue'\nimport { t } from '@/locales'\nimport { ComplexPermission, Permission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nimport permissionMap from '@/permission'\nimport { WorkflowMode } from '@/enums/application'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { knowledgeBaseNode } from '@/workflow/common/data'\nimport TemplateStoreDialog from '@/views/knowledge/template-store/TemplateStoreDialog.vue'\nprovide('getResourceDetail', () => detail)\nprovide('workflowMode', WorkflowMode.Knowledge)\nprovide('loopWorkflowMode', WorkflowMode.KnowledgeLoop)\nconst { theme } = useStore()\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  params: { id, folderId },\n  /*\n  folderId 可以区分 resource-management shared还是 workspace\n  */\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('share/')) {\n    return 'workspaceShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\nconst DebugRef = ref<InstanceType<typeof DebugVue>>()\nconst ListActionRef = ref<InstanceType<typeof ExecutionRecord>>()\nlet interval: any\nconst workflowRef = ref()\nconst loading = ref(false)\nconst detail = ref<any>(null)\n\nconst showPopover = ref(false)\nconst showDebug = ref(false)\nconst enlarge = ref(false)\nconst saveTime = ref<any>('')\nconst isSave = ref(false)\nconst showHistory = ref(false)\nconst disablePublic = ref(false)\nconst currentVersion = ref<any>({})\nconst cloneWorkFlow = ref(null)\n\nconst apiInputParams = ref([])\n\nconst isPublish = computed(() => detail.value?.is_publish)\n\nfunction back() {\n  if (JSON.stringify(cloneWorkFlow.value) !== JSON.stringify(getGraphData())) {\n    MsgConfirm(t('common.tip'), t('workflow.tip.saveMessage'), {\n      confirmButtonText: t('workflow.setting.exitSave'),\n      cancelButtonText: t('workflow.setting.exit'),\n      distinguishCancelAndClose: true,\n    })\n      .then(() => {\n        saveknowledge(true, true)\n      })\n      .catch((action: Action) => {\n        if (action === 'cancel') {\n          go()\n        }\n      })\n  } else {\n    go()\n  }\n}\n\nconst openListAction = () => {\n  ListActionRef.value?.open(id)\n}\n\nfunction clickoutsideHistory() {\n  if (!disablePublic.value) {\n    showHistory.value = false\n    disablePublic.value = false\n  }\n}\n\nfunction refreshVersion(item?: any) {\n  if (item) {\n    renderGraphData(item)\n  }\n  showHistory.value = false\n  disablePublic.value = false\n}\n\nfunction checkVersion(item: any) {\n  disablePublic.value = true\n  currentVersion.value = item\n  renderGraphData(item)\n  closeInterval()\n}\n\nfunction renderGraphData(item: any) {\n  item.work_flow['nodes'].map((v: any) => {\n    v['properties']['noRender'] = true\n  })\n  detail.value.work_flow = item.work_flow\n  saveTime.value = item?.update_time\n  workflowRef.value?.clearGraphData()\n  nextTick(() => {\n    workflowRef.value?.render(item.work_flow)\n  })\n}\n\nfunction closeHistory() {\n  getDetail()\n  if (isSave.value) {\n    initInterval()\n  }\n  showHistory.value = false\n  disablePublic.value = false\n}\n\nfunction openHistory() {\n  showHistory.value = true\n}\n\nfunction changeSave(bool: boolean) {\n  if (bool) {\n    initInterval()\n  } else {\n    closeInterval()\n  }\n  localStorage.setItem('workflowAutoSave', bool.toString())\n}\n\nfunction clickNodes(item: any) {\n  showPopover.value = false\n}\n\nfunction onmousedown(item: any) {\n  showPopover.value = false\n}\n\nfunction clickoutside() {\n  showPopover.value = false\n}\n\nconst publish = () => {\n  workflowRef.value\n    ?.validate()\n    .then(() => {\n      const workflow = getGraphData()\n      const workflowInstance = new KnowledgeWorkFlowInstance(workflow, WorkflowMode.Knowledge)\n      try {\n        workflowInstance.is_valid()\n      } catch (e: any) {\n        MsgError(e.toString())\n        return\n      }\n      loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value })\n        .putKnowledgeWorkflow(id, { work_flow: workflow })\n        .then(() => {\n          return loadSharedApi({\n            type: 'knowledge',\n            isShared: isShared.value,\n            systemType: apiType.value,\n          }).publish(id, {}, loading)\n        })\n        .then((ok: any) => {\n          detail.value.is_publish = true\n          MsgSuccess(t('views.application.tip.publishSuccess'))\n        })\n        .catch((res: any) => {\n          const node = res.node\n          const err_message = res.errMessage\n          if (typeof err_message == 'string') {\n            MsgError(\n              res.node.properties?.stepName +\n                ` ${t('workflow.node').toLowerCase()} ` +\n                err_message.toLowerCase(),\n            )\n          } else {\n            const keys = Object.keys(err_message)\n            MsgError(\n              node.properties?.stepName +\n                ` ${t('workflow.node').toLowerCase()} ` +\n                err_message[keys[0]]?.[0]?.message.toLowerCase(),\n            )\n          }\n        })\n    })\n    .catch((res: any) => {\n      const node = res.node\n      const err_message = res.errMessage\n      if (typeof err_message == 'string') {\n        MsgError(res.node.properties?.stepName + ` ${t('workflow.node')}，` + err_message)\n      } else {\n        const keys = Object.keys(err_message)\n        MsgError(\n          node.properties?.stepName +\n            ` ${t('workflow.node')}，` +\n            err_message[keys[0]]?.[0]?.message,\n        )\n      }\n    })\n}\n\nconst elUploadRef = ref()\nconst importKnowledgeWorkflow = (file: any) => {\n  const formData = new FormData()\n  formData.append('file', file.raw)\n  const name = file.name.replace('.kbwf', '')\n  elUploadRef.value.clearFiles()\n  MsgConfirm(\n    t('common.tip'),\n    `${t('views.application.tip.confirmUse')} ${name} ${t('views.application.tip.overwrite')}?`,\n    {\n      confirmButtonText: t('common.confirm'),\n      cancelButtonText: t('common.cancel'),\n    },\n  )\n    .then(() => {\n      loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value })\n        .importKnowledgeWorkflow(id, formData, loading)\n        .then(() => {\n          getDetail()\n        })\n        .catch((error: any) => {\n          if (error.code === 400) {\n            MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), {\n              cancelButtonText: t('common.confirm'),\n              confirmButtonText: t('common.professional'),\n            }).then(() => {\n              window.open('https://maxkb.cn/pricing.html', '_blank')\n            })\n          }\n        })\n    })\n    .catch(() => {})\n}\n\nfunction exportKnowledgeWorkflow(name: string, id: string) {\n  loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value })\n    .exportKnowledgeWorkflow(id, name, loading)\n    .catch((error: any) => {\n      if (error.response.status !== 403) {\n        error.response.data.text().then((res: string) => {\n          MsgError(`${t('views.application.tip.ExportError')}:${JSON.parse(res).message}`)\n        })\n      }\n    })\n}\n\nconst clickShowDebug = () => {\n  workflowRef.value\n    ?.validate()\n    .then(() => {\n      const graphData = getGraphData()\n      const workflow = new KnowledgeWorkFlowInstance(graphData, WorkflowMode.Knowledge)\n      try {\n        workflow.is_valid()\n        detail.value = {\n          ...detail.value,\n          type: 'WORK_FLOW',\n          ...workflow.get_base_node()?.properties.node_data,\n          work_flow: getGraphData(),\n        }\n        DebugRef.value?.open(graphData)\n      } catch (e: any) {\n        MsgError(e.toString())\n      }\n    })\n    .catch((res: any) => {\n      const node = res.node\n      const err_message = res.errMessage\n      if (typeof err_message == 'string') {\n        MsgError(res.node.properties?.stepName + ` ${t('workflow.node')}，` + err_message)\n      } else {\n        const keys = Object.keys(err_message)\n        MsgError(\n          node.properties?.stepName +\n            ` ${t('workflow.node')}，` +\n            err_message[keys[0]]?.[0]?.message,\n        )\n      }\n    })\n}\n\nfunction getGraphData() {\n  return workflowRef.value?.getGraphData()\n}\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nfunction getDetail() {\n  loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value })\n    .getKnowledgeDetail(id)\n    .then((res: any) => {\n      detail.value = res.data\n      detail.value.stt_model_id = res.data.stt_model\n      detail.value.tts_model_id = res.data.tts_model\n      detail.value.tts_type = res.data.tts_type\n      saveTime.value = res.data?.update_time\n      if (!detail.value.work_flow || !('nodes' in detail.value.work_flow)) {\n        detail.value.work_flow = { nodes: [knowledgeBaseNode] }\n      }\n      detail.value.work_flow?.nodes\n        ?.filter((v: any) => v.id === 'knowledge-base-node')\n        .map((v: any) => {\n          apiInputParams.value = v.properties.api_input_field_list\n            ? v.properties.api_input_field_list.map((v: any) => {\n                return {\n                  name: v.variable,\n                  value: v.default_value,\n                }\n              })\n            : v.properties.input_field_list\n              ? v.properties.input_field_list\n                  .filter((v: any) => v.assignment_method === 'api_input')\n                  .map((v: any) => {\n                    return {\n                      name: v.variable,\n                      value: v.default_value,\n                    }\n                  })\n              : []\n        })\n\n      workflowRef.value?.clearGraphData()\n      nextTick(() => {\n        workflowRef.value?.render(detail.value.work_flow)\n        cloneWorkFlow.value = getGraphData()\n      })\n    })\n}\n\nfunction saveknowledge(bool?: boolean, back?: boolean) {\n  const obj = {\n    work_flow: getGraphData(),\n  }\n  loading.value = back || false\n  loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value })\n    .putKnowledgeWorkflow(id, obj)\n    .then(() => {\n      saveTime.value = new Date()\n      if (bool) {\n        cloneWorkFlow.value = getGraphData()\n        MsgSuccess(t('common.saveSuccess'))\n        if (back) {\n          go()\n        }\n      }\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nconst go = () => {\n  if (route.path.includes('resource-management')) {\n    return router.push({ path: get_resource_management_route() })\n  } else if (route.path.includes('shared')) {\n    return router.push({ path: get_shared_route() })\n  } else {\n    return router.push({ path: get_route() })\n  }\n}\n\nconst get_shared_route = () => {\n  if (hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_DOCUMENT_READ], 'OR')) {\n    return `/knowledge/${id}/shared/4/document`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_PROBLEM_READ], 'OR')\n  ) {\n    return `/knowledge/${id}/shared/4/problem`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_HIT_TEST_READ], 'OR')\n  ) {\n    return `/knowledge/${id}/shared/4/hit-test`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_CHAT_USER_READ], 'OR')\n  ) {\n    return `/knowledge/${id}/shared/4/chat-user`\n  } else if (hasPermission([RoleConst.ADMIN, PermissionConst.SHARED_KNOWLEDGE_EDIT], 'OR')) {\n    return `/knowledge/${id}/shared/4/setting`\n  } else {\n    return `/system/shared/knowledge`\n  }\n}\n\nconst get_resource_management_route = () => {\n  if (hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_DOCUMENT_READ], 'OR')) {\n    return `/knowledge/${id}/resource-management/4/document`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_PROBLEM_READ], 'OR')\n  ) {\n    return `/knowledge/${id}/resource-management/4/problem`\n  } else if (hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_HIT_TEST], 'OR')) {\n    return `/knowledge/${id}/resource-management/4/hit-test`\n  } else if (\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_CHAT_USER_READ], 'OR')\n  ) {\n    return `/knowledge/${id}/resource-management/4/chat-user`\n  } else if (hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_EDIT], 'OR')) {\n    return `/knowledge/${id}/resource-management/4/setting`\n  } else {\n    return `/system/resource-management/knowledge`\n  }\n}\n\nconst get_route = () => {\n  const checkPermission = (permissionConst: Permission) => {\n    return hasPermission(\n      [\n        new ComplexPermission(\n          [RoleConst.USER],\n          [PermissionConst.KNOWLEDGE.getKnowledgeWorkspaceResourcePermission(id)],\n          [],\n          'AND',\n        ),\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        permissionConst.getWorkspacePermissionWorkspaceManageRole,\n        permissionConst.getKnowledgeWorkspaceResourcePermission(id),\n      ],\n      'OR',\n    )\n  }\n  if (checkPermission(PermissionConst.KNOWLEDGE_DOCUMENT_READ)) {\n    return `/knowledge/${id}/${folderId}/4/document`\n  } else if (checkPermission(PermissionConst.KNOWLEDGE_PROBLEM_READ)) {\n    return `/knowledge/${id}/${folderId}/4/problem`\n  } else if (checkPermission(PermissionConst.KNOWLEDGE_HIT_TEST_READ)) {\n    return `/knowledge/${id}/${folderId}/4/hit-test`\n  } else if (checkPermission(PermissionConst.KNOWLEDGE_CHAT_USER_READ)) {\n    return `/knowledge/${id}/${folderId}/4/chat-user`\n  } else if (checkPermission(PermissionConst.KNOWLEDGE_EDIT)) {\n    return `/knowledge/${id}/${folderId}/4/setting`\n  } else {\n    return `/knowledge`\n  }\n}\n\nconst toImportDoc = () => {\n  if (isPublish.value) {\n    const newUrl = router.resolve({\n      path: `/knowledge/import/workflow/${folderId}`,\n      query: {\n        id: id,\n      },\n    }).href\n\n    window.open(newUrl)\n  } else {\n    MsgConfirm(t('common.tip'), t('views.document.tip.toImportDocConfirm'), {\n      cancelButtonText: t('common.close'),\n      showConfirmButton: false,\n      type: 'warning',\n    })\n      .then(() => {})\n      .catch(() => {})\n  }\n}\n\nconst templateStoreDialogRef = ref()\nfunction openTemplateStoreDialog() {\n  templateStoreDialogRef.value?.open(folderId)\n}\n\n/**\n * 定时保存\n */\nconst initInterval = () => {\n  interval = setInterval(() => {\n    saveknowledge()\n  }, 60000)\n}\n\n/**\n * 关闭定时\n */\nconst closeInterval = () => {\n  if (interval) {\n    clearInterval(interval)\n  }\n}\n\nonBeforeMount(() => {\n  getDetail()\n  const workflowAutoSave = localStorage.getItem('workflowAutoSave')\n  isSave.value = workflowAutoSave === 'true' ? true : false\n  // 初始化定时任务\n  if (isSave.value) {\n    initInterval()\n  }\n})\n\nonBeforeUnmount(() => {\n  // 清除定时任务\n  closeInterval()\n  workflowRef.value?.clearGraphData()\n})\n</script>\n<style lang=\"scss\">\n.knowledge-workflow {\n  background: var(--app-layout-bg-color);\n  height: 100%;\n\n  .workflow-main {\n    height: calc(100vh - 62px);\n    box-sizing: border-box;\n  }\n\n  .workflow-dropdown-tabs {\n    .el-tabs__nav-wrap {\n      padding: 0 16px;\n    }\n  }\n}\n\n.workflow-debug-container {\n  z-index: 2000;\n  position: relative;\n  border-radius: 8px;\n  border: 1px solid #ffffff;\n  background: var(--dialog-bg-gradient-color);\n  box-shadow: 0px 4px 8px 0px rgba(var(--el-text-color-primary-rgb), 0.1);\n  position: fixed;\n  bottom: 16px;\n  right: 16px;\n  overflow: hidden;\n  width: 460px;\n  height: 680px;\n\n  .workflow-debug-header {\n    background: var(--app-header-bg-color);\n    height: var(--app-header-height);\n    line-height: var(--app-header-height);\n    box-sizing: border-box;\n    border-bottom: 1px solid var(--el-border-color);\n  }\n\n  .scrollbar-height {\n    height: calc(100% - var(--app-header-height) - 24px);\n    padding-top: 24px;\n  }\n\n  &.enlarge {\n    width: 50% !important;\n    height: 100% !important;\n    bottom: 0 !important;\n    right: 0 !important;\n  }\n\n  .chat-width {\n    max-width: 100% !important;\n    margin: 0 auto;\n  }\n}\n\n@media only screen and (max-height: 680px) {\n  .workflow-debug-container {\n    height: 600px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/login/ForgotPassword.vue",
    "content": "<template>\n  <login-layout v-if=\"!loading\" v-loading=\"loading || sendLoading\">\n    <LoginContainer\n      :subTitle=\"theme.themeInfo?.slogan ? theme.themeInfo?.slogan : $t('theme.defaultSlogan')\"\n    >\n      <h2 class=\"mb-24\">{{ $t('views.login.forgotPassword') }}</h2>\n      <el-form\n        class=\"register-form\"\n        ref=\"resetPasswordFormRef\"\n        :model=\"CheckEmailForm\"\n        :rules=\"rules\"\n      >\n        <div class=\"mb-24\">\n          <el-form-item prop=\"email\">\n            <el-input\n              size=\"large\"\n              class=\"input-item\"\n              v-model=\"CheckEmailForm.email\"\n              :placeholder=\"$t('views.login.loginForm.email.placeholder')\"\n            >\n            </el-input>\n          </el-form-item>\n        </div>\n        <div class=\"mb-24\">\n          <el-form-item prop=\"code\">\n            <div class=\"flex-between w-full\">\n              <el-input\n                size=\"large\"\n                class=\"code-input\"\n                v-model=\"CheckEmailForm.code\"\n                :placeholder=\"$t('views.login.verificationCode.placeholder')\"\n              >\n              </el-input>\n\n              <el-button\n                :disabled=\"isDisabled\"\n                size=\"large\"\n                class=\"send-email-button ml-12\"\n                @click=\"sendEmail\"\n                :loading=\"loading\"\n              >\n                {{\n                  isDisabled\n                    ? `${$t('views.login.verificationCode.resend')}（${time}s）`\n                    : $t('views.login.verificationCode.getVerificationCode')\n                }}\n              </el-button>\n            </div>\n          </el-form-item>\n        </div>\n      </el-form>\n      <el-button size=\"large\" type=\"primary\" class=\"w-full\" @click=\"checkCode\"\n      >{{ $t('views.login.buttons.checkCode') }}\n      </el-button>\n      <div class=\"operate-container mt-12\">\n        <el-button\n          class=\"register\"\n          @click=\"router.push('/login')\"\n          link\n          type=\"primary\"\n          icon=\"ArrowLeft\"\n        >\n          {{ $t('views.login.buttons.backLogin') }}\n        </el-button>\n      </div>\n    </LoginContainer>\n  </login-layout>\n</template>\n<script setup lang=\"ts\">\nimport {onBeforeMount, ref} from 'vue'\nimport LoginContainer from '@/layout/login-layout/LoginContainer.vue'\nimport LoginLayout from '@/layout/login-layout/LoginLayout.vue'\nimport type {CheckCodeRequest} from '@/api/type/user'\nimport {useRouter} from 'vue-router'\nimport type {FormInstance, FormRules} from 'element-plus'\nimport UserApi from '@/api/user/user'\nimport {MsgSuccess} from '@/utils/message'\nimport {t} from '@/locales'\nimport useStore from '@/stores'\n\nconst router = useRouter()\nconst {theme, user} = useStore()\n\nconst CheckEmailForm = ref<CheckCodeRequest>({\n  email: '',\n  code: '',\n  type: 'reset_password',\n})\n\nconst resetPasswordFormRef = ref<FormInstance>()\nconst rules = ref<FormRules<CheckCodeRequest>>({\n  email: [\n    {\n      required: true,\n      message: t('views.login.loginForm.email.requiredMessage'),\n      trigger: 'blur',\n    },\n    {\n      validator: (rule, value, callback) => {\n        const emailRegExp = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$/\n        if (!emailRegExp.test(value) && value != '') {\n          callback(new Error(t('views.login.loginForm.email.validatorEmail')))\n        } else {\n          callback()\n        }\n      },\n      trigger: 'blur',\n    },\n  ],\n  code: [{required: true, message: t('views.login.verificationCode.placeholder')}],\n})\nconst loading = ref<boolean>(false)\nconst isDisabled = ref<boolean>(false)\nconst time = ref<number>(60)\nconst sendLoading = ref<boolean>(false)\nconst checkCode = () => {\n  resetPasswordFormRef.value\n    ?.validate()\n    .then(() => UserApi.checkCode(CheckEmailForm.value, sendLoading))\n    .then(() => router.push({name: 'ResetPassword', params: CheckEmailForm.value}))\n}\n/**\n * 发送验证码\n */\nconst sendEmail = () => {\n  resetPasswordFormRef.value?.validateField('email', (v: boolean) => {\n    if (v) {\n      UserApi.sendEmit(CheckEmailForm.value.email, 'reset_password', sendLoading).then(() => {\n        MsgSuccess(t('views.login.verificationCode.successMessage'))\n        isDisabled.value = true\n        handleTimeChange()\n      })\n    }\n  })\n}\nconst handleTimeChange = () => {\n  if (time.value <= 0) {\n    isDisabled.value = false\n    time.value = 60\n  } else {\n    setTimeout(() => {\n      time.value--\n      handleTimeChange()\n    }, 1000)\n  }\n}\nonBeforeMount(() => {\n  loading.value = true\n  user.asyncGetProfile().then(() => {\n    loading.value = false\n  })\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/login/ResetPassword.vue",
    "content": "<template>\n  <login-layout>\n    <LoginContainer :subTitle=\"$t('theme.defaultSlogan')\">\n      <h2 class=\"mb-24\">{{ $t('views.login.resetPassword') }}</h2>\n      <el-form\n        class=\"reset-password-form\"\n        ref=\"resetPasswordFormRef\"\n        :model=\"resetPasswordForm\"\n        :rules=\"rules\"\n      >\n        <div class=\"mb-24\">\n          <el-form-item prop=\"password\">\n            <el-input\n              type=\"password\"\n              size=\"large\"\n              class=\"input-item\"\n              v-model=\"resetPasswordForm.password\"\n              :placeholder=\"$t('views.login.loginForm.password.placeholder')\"\n              show-password\n            >\n            </el-input>\n          </el-form-item>\n        </div>\n        <div class=\"mb-24\">\n          <el-form-item prop=\"re_password\">\n            <el-input\n              type=\"password\"\n              size=\"large\"\n              class=\"input-item\"\n              v-model=\"resetPasswordForm.re_password\"\n              :placeholder=\"$t('views.login.loginForm.re_password.placeholder')\"\n              show-password\n            >\n            </el-input>\n          </el-form-item>\n        </div>\n      </el-form>\n      <el-button size=\"large\" type=\"primary\" class=\"w-full\" @click=\"resetPassword\">{{\n        $t('common.confirm')\n      }}</el-button>\n      <div class=\"operate-container mt-12\">\n        <el-button\n          size=\"large\"\n          class=\"register\"\n          @click=\"router.push('/login')\"\n          link\n          type=\"primary\"\n          icon=\"ArrowLeft\"\n        >\n          {{ $t('views.login.buttons.backLogin') }}\n        </el-button>\n      </div>\n    </LoginContainer>\n  </login-layout>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted } from 'vue'\nimport LoginContainer from '@/layout/login-layout/LoginContainer.vue'\nimport LoginLayout from '@/layout/login-layout/LoginLayout.vue'\nimport type { ResetPasswordRequest } from '@/api/type/user'\nimport { useRouter, useRoute } from 'vue-router'\nimport { MsgSuccess } from '@/utils/message'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport UserApi from '@/api/user/user'\nimport { t } from '@/locales'\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  params: { code, email }\n} = route\nconst resetPasswordForm = ref<ResetPasswordRequest>({\n  password: '',\n  re_password: '',\n  email: '',\n  code: ''\n})\n\nonMounted(() => {\n  if (code && email) {\n    resetPasswordForm.value.code = code as string\n    resetPasswordForm.value.email = email as string\n  } else {\n    router.push('forgot_password')\n  }\n})\n\nconst rules = ref<FormRules<ResetPasswordRequest>>({\n  password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.re_password.requiredMessage'),\n      trigger: 'blur'\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur'\n    }\n  ],\n  re_password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.re_password.requiredMessage'),\n      trigger: 'blur'\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur'\n    },\n    {\n      validator: (rule, value, callback) => {\n        if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {\n          callback(new Error(t('views.login.loginForm.re_password.validatorMessage')))\n        } else {\n          callback()\n        }\n      },\n      trigger: 'blur'\n    }\n  ]\n})\nconst resetPasswordFormRef = ref<FormInstance>()\nconst loading = ref<boolean>(false)\nconst resetPassword = () => {\n  resetPasswordFormRef.value\n    ?.validate()\n    .then(() => UserApi.postResetPassword(resetPasswordForm.value, loading))\n    .then(() => {\n      MsgSuccess(t('common.modifySuccess'))\n      router.push({ name: 'login' })\n    })\n}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/login/index.vue",
    "content": "<template>\n  <login-layout v-if=\"!loading\" v-loading=\"loading\">\n    <LoginContainer :subTitle=\"newDefaultSlogan\">\n      <h2 class=\"mb-24\" v-if=\"!showQrCodeTab\">{{ loginMode || $t('views.login.title') }}</h2>\n      <div v-if=\"!showQrCodeTab\">\n        <el-form\n          class=\"login-form\"\n          :rules=\"rules\"\n          :model=\"loginForm\"\n          ref=\"loginFormRef\"\n          @keyup.enter=\"loginHandle\"\n        >\n          <div class=\"mb-24\">\n            <el-form-item prop=\"username\">\n              <el-input\n                size=\"large\"\n                class=\"input-item\"\n                v-model=\"loginForm.username\"\n                @blur=\"handleUsernameBlur(loginForm.username)\"\n                :placeholder=\"$t('views.login.loginForm.username.placeholder')\"\n              >\n              </el-input>\n            </el-form-item>\n          </div>\n          <div class=\"mb-24\">\n            <el-form-item prop=\"password\">\n              <el-input\n                type=\"password\"\n                size=\"large\"\n                class=\"input-item\"\n                v-model=\"loginForm.password\"\n                :placeholder=\"$t('views.login.loginForm.password.placeholder')\"\n                show-password\n              >\n              </el-input>\n            </el-form-item>\n          </div>\n          <div class=\"mb-24\" v-if=\"loginMode !== 'LDAP' && identifyCode\">\n            <el-form-item prop=\"captcha\">\n              <div class=\"flex-between w-full\">\n                <el-input\n                  size=\"large\"\n                  class=\"input-item\"\n                  v-model=\"loginForm.captcha\"\n                  :placeholder=\"$t('views.login.loginForm.captcha.placeholder')\"\n                >\n                </el-input>\n\n                <img\n                  :src=\"identifyCode\"\n                  alt=\"\"\n                  height=\"38\"\n                  class=\"ml-8 cursor border border-r-6\"\n                  @click=\"makeCode(loginForm.username)\"\n                />\n              </div>\n            </el-form-item>\n          </div>\n        </el-form>\n\n        <el-button\n          size=\"large\"\n          type=\"primary\"\n          class=\"w-full\"\n          @click=\"loginHandle\"\n          :loading=\"loading\"\n        >\n          {{ $t('views.login.buttons.login') }}\n        </el-button>\n        <div class=\"operate-container flex-between mt-12\">\n          <el-button\n            :loading=\"loading\"\n            class=\"forgot-password\"\n            @click=\"router.push('/forgot_password')\"\n            link\n            type=\"primary\"\n          >\n            {{ $t('views.login.forgotPassword') }}?\n          </el-button>\n        </div>\n      </div>\n      <div v-if=\"showQrCodeTab\">\n        <QrCodeTab :tabs=\"orgOptions\" :default-tab=\"defaultQrTab\"/>\n      </div>\n      <div class=\"login-gradient-divider lighter mt-24\" v-if=\"modeList.length > 1\">\n        <span>{{ $t('views.login.moreMethod') }}</span>\n      </div>\n      <div class=\"text-center mt-16\">\n        <template v-for=\"item in modeList\">\n          <el-button\n            v-if=\"item !== '' && loginMode !== item && item !== 'QR_CODE'\"\n            circle\n            :key=\"item\"\n            class=\"login-button-circle color-secondary\"\n            @click=\"changeMode(item)\"\n          >\n            <span\n              :style=\"{\n                'font-size': item === 'OAUTH2' ? '8px' : '10px',\n                color: theme.themeInfo?.theme,\n              }\"\n            >{{ item }}</span\n            >\n          </el-button>\n          <el-button\n            v-if=\"item === 'QR_CODE' && loginMode !== item\"\n            circle\n            :key=\"item\"\n            class=\"login-button-circle color-secondary\"\n            @click=\"changeMode('QR_CODE')\"\n          >\n            <img src=\"@/assets/icon_qr_outlined.svg\" width=\"25px\"/>\n          </el-button>\n          <el-button\n            v-if=\"item === '' && loginMode !== ''\"\n            circle\n            :key=\"item\"\n            class=\"login-button-circle color-secondary\"\n            style=\"font-size: 24px\"\n            icon=\"UserFilled\"\n            @click=\"changeMode('')\"\n          />\n        </template>\n      </div>\n    </LoginContainer>\n  </login-layout>\n</template>\n<script setup lang=\"ts\">\nimport {computed, onBeforeMount, onMounted, ref} from 'vue'\nimport {useRoute, useRouter} from 'vue-router'\nimport type {FormInstance, FormRules} from 'element-plus'\nimport type {LoginRequest} from '@/api/type/login'\nimport LoginContainer from '@/layout/login-layout/LoginContainer.vue'\nimport LoginLayout from '@/layout/login-layout/LoginLayout.vue'\nimport loginApi from '@/api/user/login'\nimport authApi from '@/api/system-settings/auth-setting'\nimport {getBrowserLang, t} from '@/locales'\nimport useStore from '@/stores'\nimport {useI18n} from 'vue-i18n'\nimport QrCodeTab from '@/views/login/scanCompinents/QrCodeTab.vue'\nimport {MsgConfirm, MsgError} from '@/utils/message.ts'\nimport * as dd from 'dingtalk-jsapi'\nimport {loadScript} from '@/utils/common'\nimport forge from 'node-forge';\n\nconst router = useRouter()\nconst {login, user, theme} = useStore()\nconst {locale} = useI18n({useScope: 'global'})\nconst loading = ref<boolean>(false)\nconst route = useRoute()\nconst identifyCode = ref<string>('')\nconst loginFormRef = ref<FormInstance>()\nconst authSetting = ref<any>(null)\nconst defaultQrTab = ref<string>('')\nconst loginForm = ref<LoginRequest>({\n  username: '',\n  password: '',\n  captcha: '',\n})\n\nconst rules = ref<FormRules<LoginRequest>>({\n  username: [\n    {\n      required: true,\n      message: t('views.login.loginForm.username.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.password.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  captcha: [\n    {\n      required: false,\n      message: t('views.login.loginForm.captcha.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst loginHandle = () => {\n  if (!loginFormRef.value) {\n    return\n  }\n  loginFormRef.value.validate((valid) => {\n    if (valid) {\n      loading.value = true\n      if (loginMode.value === 'LDAP') {\n        login\n          .asyncLdapLogin(loginForm.value)\n          .then(() => {\n            locale.value = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'\n            router.push({name: 'home'})\n          })\n          .catch(() => {\n            loading.value = false\n          })\n      } else {\n        const publicKey = forge.pki.publicKeyFromPem(user.rasKey);\n        // 转换为UTF-8编码后再加密\n        const jsonData = JSON.stringify(loginForm.value);\n        const utf8Bytes = forge.util.encodeUtf8(jsonData);\n        const encrypted = publicKey.encrypt(utf8Bytes, 'RSAES-PKCS1-V1_5');\n        const encryptedBase64 = forge.util.encode64(encrypted);\n        login\n          .asyncLogin({encryptedData: encryptedBase64, username: loginForm.value.username})\n          .then(() => {\n            locale.value = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'\n            localStorage.setItem('workspace_id', 'default')\n            router.push({name: 'home'})\n          })\n          .catch(() => {\n            const username = loginForm.value.username\n            loading.value = false\n            makeCode(username)\n          })\n      }\n    }\n  })\n}\n\nfunction makeCode(username?: string) {\n  loginApi.getCaptcha(username).then((res: any) => {\n    if (res && res.data && res.data.captcha) {\n      identifyCode.value = res.data.captcha\n    }\n  }).catch((error) => {\n    console.error('Failed to get captcha:', error)\n  })\n}\n\nfunction handleUsernameBlur(username: string) {\n  makeCode(username)\n}\n\nonBeforeMount(() => {\n  user.asyncGetProfile().then((res) => {\n    // 企业版和专业版：第三方登录\n    if (user.isPE() || user.isEE()) {\n      authApi.getLoginAuthSetting().then((res) => {\n        if (Object.keys(res.data).length > 0) {\n          authSetting.value = res.data;\n        } else {\n          authSetting.value = {\n            max_attempts: 1,\n            default_value: 'LOCAL',\n          }\n        }\n        const params = route.query\n        if (params.login_mode !== 'manual') {\n          if (authSetting.value?.login_methods) {\n            modeList.value = authSetting.value?.login_methods\n            if (modeList.value.includes('LOCAL')) {\n              modeList.value = ['LOCAL', ...modeList.value.filter((item) => item !== 'LOCAL')]\n            } else if (modeList.value.includes('LDAP')) {\n              modeList.value = ['LDAP', ...modeList.value.filter((item) => item !== 'LDAP')]\n            }\n            loginMode.value = modeList.value[0] || 'LOCAL'\n            if (!modeList.value.includes('LOCAL') && !modeList.value.includes('LDAP')) {\n              loginMode.value = ''\n            }\n            if (modeList.value.length == 1 && ['CAS', 'OIDC', 'OAuth2', 'SAML2'].includes(modeList.value[0])) {\n              redirectAuth(modeList.value[0])\n            }\n            // 这里的modeList 是oauth2 cas ldap oidc 这四个 还会有 lark wecom dingtalk\n            // 获取到的 modeList中除'CAS', 'OIDC', 'OAuth2' LOCAL之外的登录方式\n            QrList.value = modeList.value.filter(\n              (item) => !['CAS', 'OIDC', 'OAuth2', 'LOCAL', 'LDAP', 'SAML2'].includes(item),\n            )\n            // modeList需要去掉lark wecom dingtalk\n            modeList.value = modeList.value.filter((item) => !['lark', 'wecom', 'dingtalk'].includes(item))\n            if (QrList.value.length > 0) {\n              QrList.value.forEach((item) => {\n                orgOptions.value.push({\n                  key: item,\n                  value:\n                    item === 'wecom'\n                      ? t('views.system.authentication.scanTheQRCode.wecom')\n                      : item === 'dingtalk'\n                        ? t('views.system.authentication.scanTheQRCode.dingtalk')\n                        : t('views.system.authentication.scanTheQRCode.lark'),\n                })\n              })\n              if (!modeList.value.includes('LOCAL') && !modeList.value.includes('LDAP')) {\n                showQrCodeTab.value = true\n              }\n              modeList.value = ['QR_CODE', ...modeList.value]\n            }\n          }\n          const defaultMode = authSetting.value.default_value\n          if (['lark', 'wecom', 'dingtalk'].includes(defaultMode)) {\n            changeMode('QR_CODE', false)\n            defaultQrTab.value = defaultMode\n          } else {\n            changeMode(defaultMode, false)\n          }\n        }\n      })\n    } else {\n      authSetting.value = {\n        max_attempts: 1,\n        default_value: 'LOCAL',\n      }\n    }\n  })\n})\n\nconst modeList = ref<string[]>([''])\nconst QrList = ref<any[]>([''])\nconst loginMode = ref('')\nconst showQrCodeTab = ref(false)\n\ninterface qrOption {\n  key: string\n  value: string\n}\n\nconst orgOptions = ref<qrOption[]>([])\n\nfunction uuidv4() {\n  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n    const r = (Math.random() * 16) | 0\n    const v = c === 'x' ? r : (r & 0x3) | 0x8\n    return v.toString(16)\n  })\n}\n\nconst newDefaultSlogan = computed(() => {\n  const default_login = '强大易用的企业级智能体平台'\n  if (!theme.themeInfo?.slogan || default_login == theme.themeInfo?.slogan) {\n    return t('theme.defaultSlogan')\n  } else {\n    return theme.themeInfo?.slogan\n  }\n})\n\nfunction redirectAuth(authType: string, needMessage: boolean = true) {\n  if (authType === 'LDAP' || authType === '' || authType === 'LOCAL') {\n    return\n  }\n  authApi.getLoginViewAuthSetting(authType, loading).then((res: any) => {\n    if (!res.data || !res.data.config) {\n      return\n    }\n\n    const config = res.data.config\n    // 构造带查询参数的redirectUrl\n    const redirectUrl = `${config.redirectUrl}`\n    let url\n    if (authType === 'CAS') {\n      url = config.ldpUri\n      url +=\n        url.indexOf('?') !== -1\n          ? `&service=${encodeURIComponent(redirectUrl)}`\n          : `?service=${encodeURIComponent(redirectUrl)}`\n    } else if (authType === 'OIDC') {\n      const scope = config.scope || 'openid+profile+email'\n      url = `${config.authEndpoint}?client_id=${config.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}`\n      if (config.state) {\n        url += `&state=${config.state}`\n      }\n    } else if (authType === 'OAuth2') {\n      url = `${config.authEndpoint}?client_id=${config.clientId}&response_type=code&redirect_uri=${redirectUrl}&state=${uuidv4()}`\n      if (config.scope) {\n        url += `&scope=${config.scope}`\n      }\n    } else if (authType === 'SAML2') {\n      loginApi.samlLogin().then((res: any) => {\n        window.location.href = res.data\n      })\n    }\n    if (!url) {\n      return\n    }\n    if (needMessage) {\n      MsgConfirm(t('views.login.jump_tip'), '', {\n        confirmButtonText: t('views.login.jump'),\n        cancelButtonText: t('common.cancel'),\n        confirmButtonClass: '',\n      })\n        .then(() => {\n          window.location.href = url\n        })\n        .catch(() => {\n        })\n    } else {\n      console.log('url', url)\n      window.location.href = url\n    }\n  })\n}\n\nfunction changeMode(val: string, needMessage: boolean = true) {\n  loginMode.value = val === 'LDAP' ? val : ''\n  if (val === 'QR_CODE') {\n    loginMode.value = val\n    showQrCodeTab.value = true\n    return\n  }\n  showQrCodeTab.value = false\n  loginForm.value = {\n    username: '',\n    password: '',\n    captcha: '',\n  }\n  redirectAuth(val, needMessage)\n  loginFormRef.value?.clearValidate()\n}\n\n// onBeforeMount(() => {\n//   loading.value = true\n//   user.asyncGetProfile().then((res) => {\n//     // 企业版和专业版：第三方登录\n//     if (user.isPE() || user.isEE()) {\n//       login\n//         .getAuthType()\n//         .then((res) => {\n//           //如果结果包含LDAP，把LDAP放在第一个\n//           const ldapIndex = res.indexOf('LDAP')\n//           if (ldapIndex !== -1) {\n//             const [ldap] = res.splice(ldapIndex, 1)\n//             res.unshift(ldap)\n//           }\n//           modeList.value = [...modeList.value, ...res]\n//         })\n//         .finally(() => (loading.value = false))\n//       login\n//         .getQrType()\n//         .then((res) => {\n//           if (res.length > 0) {\n//             modeList.value = ['QR_CODE', ...modeList.value]\n//             QrList.value = res\n//             QrList.value.forEach((item) => {\n//               orgOptions.value.push({\n//                 key: item,\n//                 value:\n//                   item === 'wecom'\n//                     ? t('views.system.authentication.scanTheQRCode.wecom')\n//                     : item === 'dingtalk'\n//                       ? t('views.system.authentication.scanTheQRCode.dingtalk')\n//                       : t('views.system.authentication.scanTheQRCode.lark'),\n//               })\n//             })\n//           }\n//         })\n//         .finally(() => (loading.value = false))\n//     } else {\n//       loading.value = false\n//     }\n//   })\n// })\ndeclare const window: any\n\nonMounted(() => {\n  const route = useRoute()\n  const currentUrl = ref(route.fullPath)\n  const params = new URLSearchParams(currentUrl.value.split('?')[1])\n  const client = params.get('client')\n\n  const handleDingTalk = () => {\n    const code = params.get('corpId')\n    if (code) {\n      dd.runtime.permission.requestAuthCode({corpId: code}).then((res) => {\n        console.log('DingTalk client request success:', res)\n        login.dingOauth2Callback(res.code).then(() => {\n          router.push({name: 'home'})\n        })\n      })\n    }\n  }\n\n  const handleLark = () => {\n    const appId = params.get('appId')\n    const callRequestAuthCode = () => {\n      window.tt?.requestAuthCode({\n        appId: appId,\n        success: (res: any) => {\n          login.larkCallback(res.code).then(() => {\n            router.push({name: 'home'})\n          })\n        },\n        fail: (error: any) => {\n          MsgError(error)\n        },\n      })\n    }\n\n    loadScript('https://lf-scm-cn.feishucdn.com/lark/op/h5-js-sdk-1.5.35.js', {\n      jsId: 'lark-sdk',\n      forceReload: true,\n    })\n      .then(() => {\n        if (window.tt) {\n          window.tt.requestAccess({\n            appID: appId,\n            scopeList: [],\n            success: (res: any) => {\n              login.larkCallback(res.code).then(() => {\n                router.push({name: 'home'})\n              })\n            },\n            fail: (error: any) => {\n              const {errno} = error\n              if (errno === 103) {\n                callRequestAuthCode()\n              }\n            },\n          })\n        } else {\n          callRequestAuthCode()\n        }\n      })\n      .catch((error) => {\n        console.error('SDK 加载失败:', error)\n      })\n  }\n\n  switch (client) {\n    case 'dingtalk':\n      handleDingTalk()\n      break\n    case 'lark':\n      handleLark()\n      break\n    default:\n      break\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.login-gradient-divider {\n  position: relative;\n  text-align: center;\n  color: var(--el-color-info);\n\n  ::before {\n    content: '';\n    width: 25%;\n    height: 1px;\n    background: linear-gradient(90deg, rgba(222, 224, 227, 0) 0%, #dee0e3 100%);\n    position: absolute;\n    left: 16px;\n    top: 50%;\n  }\n\n  ::after {\n    content: '';\n    width: 25%;\n    height: 1px;\n    background: linear-gradient(90deg, #dee0e3 0%, rgba(222, 224, 227, 0) 100%);\n    position: absolute;\n    right: 16px;\n    top: 50%;\n  }\n}\n\n.login-button-circle {\n  padding: 20px !important;\n  margin: 0 4px;\n  width: 32px;\n  height: 32px;\n  text-align: center;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/login/scanCompinents/QrCodeTab.vue",
    "content": "<template>\n  <el-tabs v-model=\"activeKey\" @tab-change=\"selectTab\">\n    <template v-for=\"item in tabs\" :key=\"item.key\">\n      <el-tab-pane :label=\"item.value\" :name=\"item.key\">\n        <div class=\"text-center mt-16\" v-if=\"item.key === activeKey\">\n          <component\n            :is=\"defineAsyncComponent(() => import(`./${item.key}QrCode.vue`))\"\n            :config=\"config\"\n          />\n        </div>\n      </el-tab-pane>\n    </template>\n  </el-tabs>\n</template>\n\n<script setup lang=\"ts\">\nimport {onMounted, ref, defineAsyncComponent} from 'vue'\nimport useStore from '@/stores'\n\nconst {login} = useStore()\n\ninterface Tab {\n  key: string\n  value: string\n}\n\ninterface PlatformConfig {\n  app_key: string\n  app_secret: string\n  auth_type: string\n  config: any\n}\n\ninterface Config {\n  app_key: string\n  app_secret: string\n  corpId?: string\n  agentId?: string\n}\n\nconst props = defineProps<{ tabs: Tab[], defaultTab?: string }>()\nconst activeKey = ref('')\nconst allConfigs = ref<PlatformConfig[]>([])\nconst config = ref<Config>({app_key: '', app_secret: ''})\n\n\nasync function getPlatformInfo() {\n  try {\n    return await login.getQrSource()\n  } catch (error) {\n    return []\n  }\n}\n\nonMounted(async () => {\n  if (props.tabs.length > 0) {\n    activeKey.value = props.tabs[0].key\n  }\n  allConfigs.value = await getPlatformInfo()\n  updateConfig(activeKey.value)\n  console.log(props.defaultTab)\n  if (props.defaultTab) {\n    selectTab(props.defaultTab)\n  }\n})\n\nconst updateConfig = (key: string) => {\n  const selectedConfig = allConfigs.value.find((item) => item.auth_type === key)\n  if (selectedConfig && selectedConfig.config) {\n    config.value = selectedConfig.config\n  }\n}\n\nconst selectTab = (key: string) => {\n  activeKey.value = key\n  updateConfig(key)\n}\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/login/scanCompinents/dingtalkQrCode.vue",
    "content": "<template>\n  <div class=\"flex-center mb-16\">\n    <img src=\"@/assets/logo/logo_dingtalk.svg\" alt=\"\" width=\"24px\" class=\"mr-4\" />\n    <h2>{{ $t('views.system.authentication.scanTheQRCode.dingtalkQrCode') }}</h2>\n  </div>\n  <div class=\"ding-talk-qrName\">\n    <div id=\"ding-talk-qr\"></div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useRouter } from 'vue-router'\nimport { useScriptTag } from '@vueuse/core'\nimport { ref, watch } from 'vue'\nimport useStore from '@/stores'\nimport { MsgError } from '@/utils/message'\n// 声明 DTFrameLogin 和 QRLogin 的类型\ndeclare global {\n  interface Window {\n    DTFrameLogin: (\n      frameParams: IDTLoginFrameParams,\n      loginParams: IDTLoginLoginParams,\n      successCbk: (result: IDTLoginSuccess) => void,\n      errorCbk?: (errorMsg: string) => void\n    ) => void\n    QRLogin: (QRLogin: qrLogin) => Record<any, any>\n  }\n}\n\n// 定义接口类型\ninterface IDTLoginFrameParams {\n  id: string\n  width?: number\n  height?: number\n}\n\ninterface IDTLoginLoginParams {\n  redirect_uri: string\n  response_type: string\n  client_id: string\n  scope: string\n  prompt: string\n  state?: string\n  org_type?: string\n  corpId?: string\n  exclusiveLogin?: string\n  exclusiveCorpId?: string\n}\n\ninterface IDTLoginSuccess {\n  redirectUrl: string\n  authCode: string\n  state?: string\n}\n\ninterface qrLogin {\n  id: string\n  goto: string\n  width: string\n  height: string\n  style?: string\n}\n\nconst props = defineProps<{\n  config: {\n    app_secret: string\n    app_key: string\n    corp_id: string\n  }\n}>()\n\nconst router = useRouter()\nconst { login } = useStore()\nconst { load } = useScriptTag('https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js')\nconst isConfigReady = ref(false)\n\nconst initActive = async () => {\n  try {\n    await load(true)\n    if (!isConfigReady.value) {\n      return\n    }\n\n    const data = {\n      appKey: props.config.app_key,\n      appSecret: props.config.app_secret,\n      corp_id: props.config.corp_id\n    }\n\n    const redirectUri = encodeURIComponent(window.location.origin)\n    window.DTFrameLogin(\n      {\n        id: 'ding-talk-qr',\n        width: 280,\n        height: 280\n      },\n      {\n        redirect_uri: redirectUri,\n        client_id: data.appKey,\n        scope: 'openid corpid',\n        response_type: 'code',\n        state: 'fit2cloud-ding-qr',\n        prompt: 'consent',\n        corpId: data.corp_id\n      },\n      (loginResult) => {\n        const authCode = loginResult.authCode\n        login.dingCallback(authCode).then(() => {\n          router.push({ name: 'home' })\n        })\n      },\n      (errorMsg: string) => {\n        MsgError(errorMsg)\n      }\n    )\n  } catch (error) {\n  }\n}\n\nwatch(\n  () => props.config,\n  (newConfig) => {\n    if (newConfig.app_key && newConfig.corp_id) {\n      isConfigReady.value = true\n      initActive()\n    }\n  },\n  { immediate: true }\n)\n</script>\n\n<style lang=\"scss\">\n.ding-talk-qrName {\n  border: 1px solid #e8e8e8;\n  border-radius: 8px;\n  height: 280px;\n  width: 280px;\n  margin: 0 auto;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/login/scanCompinents/larkQrCode.vue",
    "content": "<template>\n  <div class=\"flex-center mb-16\">\n    <img src=\"@/assets/logo/logo_lark.svg \" alt=\"\" width=\"24px\" class=\"mr-4\"/>\n    <h2>{{ $t('views.system.authentication.scanTheQRCode.larkQrCode') }}</h2>\n  </div>\n  <div id=\"lark-qr\" class=\"lark-qrName\"></div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {useScriptTag} from '@vueuse/core'\nimport {onMounted} from 'vue'\n\nconst {load} = useScriptTag(\n  'https://lf-package-cn.feishucdn.com/obj/feishu-static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.3.js'\n)\nconst props = defineProps<{\n  config: {\n    app_secret: string\n    app_key: string\n  }\n}>()\n\nconst initActive = async () => {\n  const scriptLoaded = await load(true)\n  if (!scriptLoaded) {\n    console.error('飞书二维码 SDK 加载失败')\n    return\n  }\n  const data = {\n    agentId: props.config.app_key,\n    appSecret: props.config.app_secret\n  }\n\n  const redirectUrl = encodeURIComponent(`${window.location.origin}${window.MaxKB.prefix}/api/lark`)\n  const url = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${data.agentId}&redirect_uri=${redirectUrl}&response_type=code&state=fit2cloud-lark-qr`\n  const QRLoginObj = window.QRLogin({\n    id: 'lark-qr',\n    goto: url,\n    width: '266',\n    height: '266',\n    style: 'width:280px;height:280px;border:1px solid #e8e8e8;margin:0 auto;border-radius:8px;'\n  })\n\n  window.addEventListener('message', async (event: any) => {\n    if (QRLoginObj.matchOrigin(event.origin) && QRLoginObj.matchData(event.data)) {\n      const loginTmpCode = event.data.tmp_code\n      window.location.href = `${url}&tmp_code=${loginTmpCode}`\n    }\n  })\n}\n\nonMounted(() => {\n  initActive()\n})\n</script>\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/login/scanCompinents/wecomQrCode.vue",
    "content": "<template>\n  <iframe :src=\"iframeUrl\" width=\"100%\" height=\"380px\" frameborder=\"0\"\n          style=\"margin-top: -30px\"></iframe>\n</template>\n\n<script lang=\"ts\" setup>\nimport {ref, nextTick, defineProps} from 'vue'\nimport {getBrowserLang} from '@/locales'\n\nconst props = defineProps<{\n  config: {\n    app_secret: string\n    app_key: string\n    corp_id?: string\n    agent_id?: string\n    callback_url: string,\n    qr_url: string\n  }\n}>()\n\nconst iframeUrl = ref('')\nconst init = async () => {\n  await nextTick() // 确保DOM已更新\n  const data = {\n    corpId: props.config.corp_id,\n    agentId: props.config.agent_id,\n    redirectUri: props.config.callback_url,\n  }\n  let lang = localStorage.getItem('MaxKB-locale') || getBrowserLang() || 'en-US'\n  if (lang === 'en-US') {\n    lang = 'en'\n  } else {\n    lang = 'zh'\n  }\n  const redirectUri = encodeURIComponent(data.redirectUri)\n  console.log('redirectUri', data.redirectUri)\n  // 手动构建生成二维码的url\n  iframeUrl.value = `${props.config.qr_url}?login_type=CorpApp&appid=${data.corpId}&agentid=${data.agentId}&redirect_uri=${redirectUri}&state=fit2cloud-wecom-qr&lang=${lang}&panel_size=small`\n}\n\ninit()\n</script>\n\n<style scoped lang=\"scss\">\n#wecom-qr {\n  margin-top: -20px;\n  height: 331px;\n  justify-content: center;\n\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/model/component/AddParamDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"drawer\"\n    :direction=\"direction\"\n    size=\"600\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :before-close=\"cancelClick\"\n    destroy-on-close\n  >\n    <template #header>\n      <h4>\n        {{ isEdit ? $t('common.param.editParam') : $t('common.param.addParam') }}\n      </h4>\n    </template>\n    <template #default>\n      <DynamicsFormConstructor\n        v-model=\"currentItem\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        ref=\"DynamicsFormConstructorRef\"\n      ></DynamicsFormConstructor>\n    </template>\n    <template #footer>\n      <div style=\"flex: auto\">\n        <el-button @click=\"cancelClick\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"confirmClick()\">{{\n          isEdit ? $t('common.save') : $t('common.add')\n        }}</el-button>\n      </div>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport type { DrawerProps } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'\n\nconst drawer = ref(false)\nconst direction = ref<DrawerProps['direction']>('rtl')\nconst isEdit = ref(false)\nconst DynamicsFormConstructorRef = ref<InstanceType<typeof DynamicsFormConstructor>>()\n\nconst currentItem = ref(null)\nconst currentIndex = ref(null)\n\nconst emit = defineEmits(['refresh'])\n\nconst open = (row: any, index: any) => {\n  if (row) {\n    currentItem.value = cloneDeep(row)\n    currentIndex.value = index\n    isEdit.value = true\n  }\n  drawer.value = true\n}\n\nfunction cancelClick() {\n  drawer.value = false\n  isEdit.value = false\n  currentItem.value = null\n  currentIndex.value = null\n}\n\nfunction confirmClick() {\n  const formEl = DynamicsFormConstructorRef.value\n  formEl?.validate().then((valid) => {\n    if (valid) {\n      emit('refresh', formEl?.getData(), currentIndex.value)\n      drawer.value = false\n      isEdit.value = false\n      currentItem.value = null\n      currentIndex.value = null\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/model/component/CreateModelDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    width=\"600px\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <template #header>\n      <el-breadcrumb separator=\">\">\n        <el-breadcrumb-item>\n          <span @click=\"toSelectProvider\" class=\"select-provider\">\n            {{ $t('views.model.providerPlaceholder') }}\n          </span>\n        </el-breadcrumb-item>\n        <el-breadcrumb-item\n          ><span class=\"active-breadcrumb\">{{\n            `${$t('common.add')} ${providerValue?.name}`\n          }}</span></el-breadcrumb-item\n        >\n      </el-breadcrumb>\n    </template>\n    <el-tabs v-model=\"activeName\">\n      <el-tab-pane :label=\"$t('views.model.modelForm.title.baseInfo')\" name=\"base-info\">\n        <DynamicsForm\n          v-model=\"form_data\"\n          :render_data=\"model_form_field\"\n          :model=\"form_data\"\n          ref=\"dynamicsFormRef\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n          class=\"mb-24\"\n          label-width=\"auto\"\n        >\n          <template #default>\n            <el-form-item prop=\"name\" :rules=\"base_form_data_rule.name\">\n              <template #label>\n                <div class=\"flex align-center\" style=\"display: inline-flex\">\n                  <div class=\"mr-4\">\n                    <span> {{ $t('views.model.modelForm.modeName.label') }} </span>\n                  </div>\n                  <el-tooltip effect=\"dark\" placement=\"right\">\n                    <template #content>\n                      <p>{{ $t('views.model.modelForm.modeName.tooltip') }}</p>\n                    </template>\n                    <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                  </el-tooltip>\n                </div>\n              </template>\n              <el-input\n                v-model=\"base_form_data.name\"\n                maxlength=\"64\"\n                show-word-limit\n                :placeholder=\"$t('views.model.modelForm.modeName.placeholder')\"\n              />\n            </el-form-item>\n            <el-form-item prop=\"model_type\" :rules=\"base_form_data_rule.model_type\">\n              <template #label>\n                <div class=\"flex align-center\" style=\"display: inline-flex\">\n                  <span class=\"mr-4\">{{ $t('views.model.modelForm.model_type.label') }} </span>\n                  <el-tooltip effect=\"dark\" placement=\"right\">\n                    <template #content>\n                      <p>{{ $t('views.model.modelForm.model_type.tooltip1') }}</p>\n                      <p>{{ $t('views.model.modelForm.model_type.tooltip2') }}</p>\n                      <p>{{ $t('views.model.modelForm.model_type.tooltip3') }}</p>\n                      <p>{{ $t('views.model.modelForm.model_type.tooltip4') }}</p>\n                      <p>{{ $t('views.model.modelForm.model_type.tooltip5') }}</p>\n                      <p>{{ $t('views.model.modelForm.model_type.tooltip6') }}</p>\n                      <p>{{ $t('views.model.modelForm.model_type.tooltip7') }}</p>\n                      <p>{{ $t('views.model.modelForm.model_type.tooltip8') }}</p>\n                      <p>{{ $t('views.model.modelForm.model_type.tooltip9') }}</p>\n                    </template>\n                    <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                  </el-tooltip>\n                </div>\n              </template>\n              <el-select\n                v-loading=\"model_type_loading\"\n                @change=\"list_base_model($event, true)\"\n                v-model=\"base_form_data.model_type\"\n                class=\"w-full m-2\"\n                :placeholder=\"$t('views.model.modelForm.model_type.placeholder')\"\n              >\n                <el-option\n                  v-for=\"item in model_type_list\"\n                  :key=\"item.value\"\n                  :label=\"item.key\"\n                  :value=\"item.value\"\n                ></el-option>\n              </el-select>\n            </el-form-item>\n\n            <el-form-item prop=\"model_name\" :rules=\"base_form_data_rule.model_name\">\n              <template #label>\n                <div class=\"flex align-center\" style=\"display: inline-flex\">\n                  <div class=\"mr-4\">\n                    <span>{{ $t('views.model.modelForm.base_model.label') }} </span>\n                    <span class=\"color-danger ml-4\">{{\n                      $t('views.model.modelForm.base_model.tooltip')\n                    }}</span>\n                  </div>\n                </div>\n              </template>\n              <el-select\n                @change=\"getModelForm($event)\"\n                v-loading=\"base_model_loading\"\n                v-model=\"base_form_data.model_name\"\n                class=\"w-full m-2\"\n                :placeholder=\"$t('views.model.modelForm.base_model.placeholder')\"\n                filterable\n                allow-create\n                default-first-option\n              >\n                <el-option v-for=\"item in base_model_list\" :key=\"item.name\" :value=\"item.name\">\n                  <template #default>\n                    <div class=\"flex align-center\" style=\"display: inline-flex\">\n                      <div class=\"flex-between mr-4\">\n                        <span>{{ item.name }} </span>\n                      </div>\n                      <el-tooltip effect=\"dark\" placement=\"right\" v-if=\"item.desc\">\n                        <template #content>\n                          <p class=\"w-280\">{{ item.desc }}</p>\n                        </template>\n                        <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                      </el-tooltip>\n                    </div>\n                  </template>\n                </el-option>\n              </el-select>\n            </el-form-item>\n          </template>\n        </DynamicsForm>\n      </el-tab-pane>\n      <el-tab-pane :label=\"$t('views.model.modelForm.title.advancedInfo')\" name=\"advanced-info\">\n        <el-empty\n          v-if=\"!base_form_data.model_type || !base_form_data.model_name\"\n          :description=\"$t('views.model.tip.emptyMessage1')\"\n        />\n        <el-empty\n          v-else-if=\"base_form_data.model_type === 'RERANKER'\"\n          :description=\"$t('views.model.tip.emptyMessage2')\"\n        />\n        <div class=\"flex-between mb-8\" v-else>\n          <h5>{{ $t('views.model.modelForm.title.modelParams') }}</h5>\n          <el-button\n            type=\"text\"\n            @click.stop=\"openAddDrawer()\"\n            :disabled=\"\n              !['TTS', 'LLM', 'IMAGE', 'TTI', 'TTV', 'ITV', 'STT', 'EMBEDDING'].includes(\n                base_form_data.model_type,\n              )\n            \"\n          >\n            <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\" /> {{ $t('common.add') }}\n          </el-button>\n        </div>\n        <el-table\n          :data=\"base_form_data.model_params_form\"\n          v-if=\"base_form_data.model_params_form?.length > 0\"\n          class=\"mb-16\"\n        >\n          <el-table-column\n            prop=\"label\"\n            :label=\"$t('dynamicsForm.paramForm.name.label')\"\n            show-overflow-tooltip\n          >\n            <template #default=\"{ row }\">\n              <span v-if=\"row.label && row.label.input_type === 'TooltipLabel'\">{{\n                row.label.label\n              }}</span>\n              <span v-else>{{ row.label }}</span>\n            </template>\n          </el-table-column>\n          <el-table-column\n            prop=\"field\"\n            :label=\"$t('dynamicsForm.paramForm.field.label')\"\n            show-overflow-tooltip\n            width=\"95px\"\n          />\n          <el-table-column :label=\"$t('dynamicsForm.paramForm.input_type.label')\" width=\"110px\">\n            <template #default=\"{ row }\">\n              <el-tag size=\"small\" type=\"info\" class=\"info-tag\">{{\n                input_type_list.find((item) => item.value === row.input_type)?.label\n              }}</el-tag>\n            </template>\n          </el-table-column>\n          <el-table-column\n            prop=\"default_value\"\n            :label=\"$t('dynamicsForm.default.label')\"\n            show-overflow-tooltip\n          />\n          <el-table-column :label=\"$t('common.required')\">\n            <template #default=\"{ row }\">\n              <div @click.stop>\n                <el-switch disabled size=\"small\" v-model=\"row.required\" />\n              </div>\n            </template>\n          </el-table-column>\n\n          <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n            <template #default=\"{ row, $index }\">\n              <span class=\"mr-4\">\n                <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n                  <el-button type=\"primary\" text @click.stop=\"openAddDrawer(row, $index)\">\n                    <AppIcon iconName=\"app-edit\"></AppIcon>\n                  </el-button>\n                </el-tooltip>\n              </span>\n              <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n                <el-button type=\"primary\" text @click=\"deleteParam($index)\">\n                  <AppIcon iconName=\"app-delete\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n            </template>\n          </el-table-column>\n        </el-table>\n      </el-tab-pane>\n    </el-tabs>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click=\"close\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n  <AddParamDrawer ref=\"AddParamRef\" @refresh=\"refresh\" />\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { Provider, BaseModel } from '@/api/type/model'\nimport type { Dict, KeyValue } from '@/api/type/common'\nimport ProviderApi from '@/api/model/provider'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport AddParamDrawer from '@/views/model/component/AddParamDrawer.vue'\nimport { input_type_list } from '@/components/dynamics-form/constructor/data'\nimport type { FormRules } from 'element-plus'\nimport { MsgError, MsgSuccess, MsgWarning } from '@/utils/message'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport useStore from '@/stores'\n\nconst route = useRoute()\nconst { user } = useStore()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst providerValue = ref<Provider>()\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst emit = defineEmits(['change', 'submit'])\nconst loading = ref<boolean>(false)\nconst model_type_loading = ref<boolean>(false)\nconst base_model_loading = ref<boolean>(false)\nconst model_type_list = ref<Array<KeyValue<string, string>>>([])\n\nconst base_model_list = ref<Array<BaseModel>>()\nconst model_form_field = ref<Array<FormField>>([])\nconst dialogVisible = ref<boolean>(false)\nconst activeName = ref('base-info')\nconst AddParamRef = ref()\n\nconst base_form_data_rule = ref<FormRules>({\n  name: {\n    required: true,\n    trigger: 'blur',\n    message: t('views.model.modelForm.modeName.requiredMessage'),\n  },\n  model_type: {\n    required: true,\n    trigger: 'change',\n    message: t('views.model.modelForm.model_type.requiredMessage'),\n  },\n  model_name: {\n    required: true,\n    trigger: 'change',\n    message: t('views.model.modelForm.base_model.requiredMessage'),\n  },\n})\n\nconst base_form_data = ref<{\n  name: string\n  model_type: string\n  model_name: string\n  model_params_form: any\n}>({ name: '', model_type: '', model_name: '', model_params_form: [] })\n\nconst credential_form_data = ref<Dict<any>>({})\n\nconst form_data = computed({\n  get: () => {\n    return {\n      ...credential_form_data.value,\n      name: base_form_data.value.name,\n      model_type: base_form_data.value.model_type,\n      model_name: base_form_data.value.model_name,\n      model_params_form: base_form_data.value.model_params_form,\n    }\n  },\n  set: (event: any) => {\n    credential_form_data.value = event\n  },\n})\n\nconst getModelForm = (model_name: string) => {\n  if (!form_data.value.model_type) {\n    MsgWarning(t('views.model.modelForm.model_type.requiredMessage'))\n    base_form_data.value.model_name = ''\n    return\n  }\n  if (providerValue.value) {\n    ProviderApi.getModelCreateForm(\n      providerValue.value.provider,\n      form_data.value.model_type,\n      model_name,\n    ).then((ok) => {\n      model_form_field.value = ok.data\n      // 渲染动态表单\n      dynamicsFormRef.value?.render(model_form_field.value, undefined)\n    })\n\n    ProviderApi.listBaseModelParamsForm(\n      providerValue.value.provider,\n      form_data.value.model_type,\n      model_name,\n      base_model_loading,\n    ).then((ok) => {\n      base_form_data.value.model_params_form = ok.data\n    })\n  }\n}\n\nconst open = (provider: Provider, model_type?: string) => {\n  ProviderApi.listModelType(provider.provider, model_type_loading).then((ok) => {\n    model_type_list.value = ok.data\n  })\n  providerValue.value = provider\n  dialogVisible.value = true\n  base_form_data.value.model_type = model_type || ''\n  activeName.value = 'base-info'\n  if (model_type) {\n    list_base_model(model_type)\n  }\n}\n\nconst list_base_model = (model_type: any, change?: boolean) => {\n  if (change) {\n    base_form_data.value.model_name = ''\n    base_form_data.value.model_params_form = []\n  }\n  if (providerValue.value) {\n    ProviderApi.listBaseModel(providerValue.value.provider, model_type, base_model_loading).then(\n      (ok) => {\n        base_model_list.value = ok.data\n      },\n    )\n  }\n}\n\nconst close = () => {\n  base_form_data.value = {\n    name: '',\n    model_type: '',\n    model_name: '',\n    model_params_form: [],\n  }\n  credential_form_data.value = {}\n  model_form_field.value = []\n  base_model_list.value = []\n  loading.value = false\n  dialogVisible.value = false\n}\nconst submit = () => {\n  dynamicsFormRef.value\n    ?.validate()\n    .then(() => {\n      if (providerValue.value) {\n        loadSharedApi({ type: 'model', systemType: apiType.value })\n          .createModel(\n            {\n              ...base_form_data.value,\n              credential: credential_form_data.value,\n              provider: providerValue.value.provider,\n            },\n            loading,\n          )\n          .then((ok: any) => {\n            close()\n            MsgSuccess(t('views.model.tip.createSuccessMessage'))\n            emit('submit')\n            return user.profile()\n          })\n      }\n    })\n    .catch(() => {\n      MsgError(t('views.model.tip.createErrorMessage'))\n    })\n}\n\nfunction openAddDrawer(data?: any, index?: any) {\n  AddParamRef.value?.open(data, index)\n}\n\nfunction deleteParam(index: any) {\n  base_form_data.value.model_params_form.splice(index, 1)\n}\n\nfunction refresh(data: any, index: any) {\n  for (let i = 0; i < base_form_data.value.model_params_form.length; i++) {\n    const field = base_form_data.value.model_params_form[i].field\n    let label = base_form_data.value.model_params_form[i].label\n    if (label && label.input_type === 'TooltipLabel') {\n      label = label.label\n    }\n    let label2 = data.label\n    if (label2 && label2.input_type === 'TooltipLabel') {\n      label2 = label2.label\n    }\n\n    if (field === data.field && index !== i) {\n      MsgError(t('views.model.tip.errorMessage') + data.field)\n      return\n    }\n    if (label === label2 && index !== i) {\n      MsgError(t('views.model.tip.errorMessage') + label)\n      return\n    }\n  }\n  if (index !== null) {\n    base_form_data.value.model_params_form.splice(index, 1, data)\n  } else {\n    base_form_data.value.model_params_form.push(data)\n  }\n}\n\nconst toSelectProvider = () => {\n  close()\n  emit('change')\n}\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped>\n.select-provider {\n  font-size: 16px;\n  color: rgba(100, 106, 115, 1);\n  font-weight: 400;\n  line-height: 24px;\n  cursor: pointer;\n\n  &:hover {\n    color: var(--el-color-primary);\n  }\n}\n\n.active-breadcrumb {\n  font-size: 16px;\n  color: var(--el-text-color-primary);\n  font-weight: 500;\n  line-height: 24px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/model/component/EditModel.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    width=\"600px\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n  >\n    <template #header>\n      <el-breadcrumb separator=\">\">\n        <el-breadcrumb-item\n          ><span class=\"active-breadcrumb\">{{\n            `${$t('common.edit')} ${providerValue?.name}`\n          }}</span></el-breadcrumb-item\n        >\n      </el-breadcrumb>\n    </template>\n\n    <DynamicsForm\n      v-loading=\"formLoading\"\n      v-model=\"form_data\"\n      :render_data=\"model_form_field\"\n      :model=\"form_data\"\n      ref=\"dynamicsFormRef\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n    >\n      <template #default>\n        <el-form-item prop=\"name\" :rules=\"base_form_data_rule.name\">\n          <template #label>\n            <div class=\"flex align-center\" style=\"display: inline-flex\">\n              <div class=\"mr-4\">\n                <span>{{ $t('views.model.modelForm.modeName.label') }} </span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\">\n                <template #content>\n                  <p>{{ $t('views.model.modelForm.modeName.tooltip') }}</p>\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-input\n            v-model=\"base_form_data.name\"\n            maxlength=\"64\"\n            show-word-limit\n            :placeholder=\"$t('views.model.modelForm.modeName.placeholder')\"\n          />\n        </el-form-item>\n        <el-form-item prop=\"model_type\" :rules=\"base_form_data_rule.model_type\">\n          <template #label>\n            <span>{{ $t('views.model.modelForm.model_type.label') }}</span>\n          </template>\n          <el-select\n            disabled\n            v-loading=\"model_type_loading\"\n            @change=\"list_base_model($event, true)\"\n            v-model=\"base_form_data.model_type\"\n            class=\"w-full m-2\"\n            :placeholder=\"$t('views.model.modelForm.model_type.placeholder')\"\n          >\n            <el-option\n              v-for=\"item in model_type_list\"\n              :key=\"item.value\"\n              :label=\"item.key\"\n              :value=\"item.value\"\n            ></el-option>\n          </el-select>\n        </el-form-item>\n        <el-form-item prop=\"model_name\" :rules=\"base_form_data_rule.model_name\">\n          <template #label>\n            <div class=\"flex align-center\" style=\"display: inline-flex\">\n              <div class=\"mr-4\">\n                <span>{{ $t('views.model.modelForm.base_model.label') }} </span>\n                <span class=\"color-danger ml-4\" style=\"color: red\">{{\n                  $t('views.model.modelForm.base_model.tooltip')\n                }}</span>\n              </div>\n            </div>\n          </template>\n          <el-select\n            @change=\"getModelForm($event)\"\n            v-loading=\"base_model_loading\"\n            v-model=\"base_form_data.model_name\"\n            class=\"w-full m-2\"\n            :placeholder=\"$t('views.model.modelForm.base_model.requiredMessage')\"\n            filterable\n            allow-create\n            default-first-option\n          >\n            <el-option v-for=\"item in base_model_list\" :key=\"item.name\" :value=\"item.name\">\n              <template #default>\n                <div class=\"flex align-center\" style=\"display: inline-flex\">\n                  <div class=\"flex-between mr-4\">\n                    <span>{{ item.name }} </span>\n                  </div>\n                  <el-tooltip effect=\"dark\" placement=\"right\" v-if=\"item.desc\">\n                    <template #content>\n                      <p>{{ item.desc }}</p>\n                    </template>\n                    <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                  </el-tooltip>\n                </div>\n              </template>\n            </el-option>\n          </el-select>\n        </el-form-item>\n      </template>\n    </DynamicsForm>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click=\"close\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit\" :loading=\"loading\">\n          {{ $t('common.modify') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { Provider, BaseModel, Model } from '@/api/type/model'\nimport type { Dict, KeyValue } from '@/api/type/common'\nimport ProviderApi from '@/api/model/provider'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport type { FormRules } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst providerValue = ref<Provider>()\nconst dynamicsFormRef = ref<InstanceType<typeof DynamicsForm>>()\nconst emit = defineEmits(['change', 'submit'])\nconst loading = ref<boolean>(false)\nconst formLoading = ref<boolean>(false)\nconst model_type_loading = ref<boolean>(false)\nconst base_model_loading = ref<boolean>(false)\nconst model_type_list = ref<Array<KeyValue<string, string>>>([])\nconst modelValue = ref<Model>()\nconst base_model_list = ref<Array<BaseModel>>([])\nconst model_form_field = ref<Array<FormField>>([])\nconst dialogVisible = ref<boolean>(false)\n\nconst base_form_data_rule = ref<FormRules>({\n  name: {\n    required: true,\n    trigger: 'blur',\n    message: t('views.model.modelForm.modeName.requiredMessage'),\n  },\n  model_type: {\n    required: true,\n    trigger: 'change',\n    message: t('views.model.modelForm.model_type.requiredMessage'),\n  },\n  model_name: {\n    required: true,\n    trigger: 'change',\n    message: t('views.model.modelForm.base_model.requiredMessage'),\n  },\n})\n\nconst base_form_data = ref<{\n  name: string\n  model_type: string\n\n  model_name: string\n}>({ name: '', model_type: '', model_name: '' })\n\nconst credential_form_data = ref<Dict<any>>({})\n\nconst form_data = computed({\n  get: () => {\n    return { ...credential_form_data.value, ...base_form_data.value }\n  },\n  set: (event: any) => {\n    credential_form_data.value = event\n  },\n})\n\nconst getModelForm = (model_name: string) => {\n  if (providerValue.value) {\n    ProviderApi.getModelCreateForm(\n      providerValue.value.provider,\n      form_data.value.model_type,\n      model_name,\n    ).then((ok) => {\n      model_form_field.value = ok.data\n      if (modelValue.value) {\n        // 渲染动态表单\n        dynamicsFormRef.value?.render(model_form_field.value, modelValue.value.credential)\n      }\n    })\n  }\n}\nconst list_base_model = (model_type: any, change?: boolean) => {\n  if (change) {\n    base_form_data.value.model_name = ''\n  }\n  if (providerValue.value) {\n    ProviderApi.listBaseModel(providerValue.value.provider, model_type, base_model_loading).then(\n      (ok) => {\n        base_model_list.value = ok.data\n      },\n    )\n  }\n}\nconst open = (provider: Provider, model: Model) => {\n  modelValue.value = model\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getModelById(model.id, formLoading)\n    .then((ok: any) => {\n      modelValue.value = ok.data\n      ProviderApi.listModelType(model.provider, model_type_loading).then((ok) => {\n        model_type_list.value = ok.data\n        list_base_model(model.model_type)\n      })\n      providerValue.value = provider\n\n      base_form_data.value = {\n        name: model.name,\n        model_type: model.model_type,\n        model_name: model.model_name,\n      }\n      form_data.value = model.credential\n      getModelForm(model.model_name)\n    })\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  base_form_data.value = { name: '', model_type: '', model_name: '' }\n  dynamicsFormRef.value?.ruleFormRef?.resetFields()\n  credential_form_data.value = {}\n  model_form_field.value = []\n  base_model_list.value = []\n  dialogVisible.value = false\n}\n\nconst submit = () => {\n  dynamicsFormRef.value?.validate().then(() => {\n    if (modelValue.value) {\n      loadSharedApi({ type: 'model', systemType: apiType.value })\n        .updateModel(\n          modelValue.value.id,\n          {\n            ...base_form_data.value,\n            credential: credential_form_data.value,\n          },\n          loading,\n        )\n        .then((ok: any) => {\n          MsgSuccess(t('views.model.tip.updateSuccessMessage'))\n          close()\n          emit('submit')\n        })\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped>\n.select-provider {\n  font-size: 16px;\n  color: rgba(100, 106, 115, 1);\n  font-weight: 400;\n  line-height: 24px;\n  cursor: pointer;\n\n  &:hover {\n    color: var(--el-color-primary);\n  }\n}\n\n.active-breadcrumb {\n  font-size: 16px;\n  color: var(--el-text-color-primary);\n  font-weight: 500;\n  line-height: 24px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/model/component/ModelCard.vue",
    "content": "<template>\n  <card-box :title=\"model.name\" shadow=\"hover\" class=\"model-card\">\n    <template #icon>\n      <span style=\"height: 32px; width: 32px\" :innerHTML=\"icon\"></span>\n    </template>\n    <template #title>\n      <div class=\"flex\">\n        <span class=\"ellipsis-1\" :title=\"model.name\" style=\"max-width: 80%\">\n          {{ model.name }}\n        </span>\n        <span v-if=\"currentModel.status === 'ERROR'\">\n          <el-tooltip effect=\"dark\" :content=\"errMessage\" placement=\"top\">\n            <el-icon class=\"color-danger ml-4\" size=\"18\"><WarningFilled /></el-icon>\n          </el-tooltip>\n        </span>\n        <span v-if=\"currentModel.status === 'PAUSE_DOWNLOAD'\">\n          <el-tooltip\n            effect=\"dark\"\n            :content=\"`${$t('views.model.modelForm.base_model.label')}: ${props.model.model_name} ${$t('views.model.tip.downloadError')}`\"\n            placement=\"top\"\n          >\n            <el-icon class=\"color-danger ml-4\" size=\"18\"><WarningFilled /></el-icon>\n          </el-tooltip>\n        </span>\n      </div>\n    </template>\n    <template #subTitle>\n      <el-text class=\"color-secondary lighter flex align-center\" size=\"small\">\n        <span :title=\"i18n_name(model.nick_name)\" class=\"ellipsis\" style=\"max-width: 90px\">\n          {{ i18n_name(model.nick_name) }}\n        </span>\n        <span class=\"ml-4 mr-4\"> {{ $t('common.createdIn') }}</span>\n        <span> {{ dateFormat(model.create_time) }}</span>\n      </el-text>\n    </template>\n    <template #tag>\n      <el-tag v-if=\"isShared || isSystemShare\" size=\"small\" type=\"info\" class=\"info-tag\">\n        {{ t('views.shared.title') }}\n      </el-tag>\n    </template>\n    <ul>\n      <li class=\"flex mb-4\">\n        <el-text type=\"info\" class=\"color-secondary\"\n          >{{ $t('views.model.modelForm.model_type.label') }}\n        </el-text>\n        <span class=\"ellipsis ml-16\">\n          {{ $t(modelType[model.model_type as keyof typeof modelType]) }}</span\n        >\n      </li>\n      <li class=\"flex\">\n        <el-text type=\"info\" class=\"color-secondary\"\n          >{{ $t('views.model.modelForm.base_model.label') }}\n        </el-text>\n        <span class=\"ellipsis-1 ml-16\" style=\"height: 20px; width: 70%\">\n          {{ model.model_name }}</span\n        >\n      </li>\n    </ul>\n    <!-- progress -->\n    <div class=\"progress-mask\" v-if=\"currentModel.status === 'DOWNLOAD'\">\n      <DownloadLoading class=\"percentage\" />\n\n      <div class=\"percentage-label flex-center\">\n        {{ $t('views.model.download.downloading') }} <span class=\"dotting\"></span>\n        <el-button link type=\"primary\" class=\"ml-16\" @click.stop=\"cancelDownload\"\n          >{{ $t('views.model.download.cancelDownload') }}\n        </el-button>\n      </div>\n    </div>\n\n    <template #mouseEnter v-if=\"MoreFilledPermission(model.id)\">\n      <el-dropdown trigger=\"click\" v-if=\"!isShared\">\n        <el-button text @click.stop>\n          <AppIcon iconName=\"app-more\"></AppIcon>\n        </el-button>\n        <template #dropdown>\n          <el-dropdown-menu>\n            <el-dropdown-item\n              v-if=\"permissionPrecise.modify(model.id)\"\n              text\n              @click.stop=\"openEditModel\"\n            >\n              <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n              {{ $t('common.edit') }}\n            </el-dropdown-item>\n            <el-dropdown-item\n              v-if=\"isSystemShare\"\n              @click.stop=\"openAuthorizedWorkspaceDialog(model)\"\n            >\n              <AppIcon iconName=\"app-lock\" class=\"color-secondary\"></AppIcon>\n              {{ $t('views.shared.authorized_workspace') }}\n            </el-dropdown-item>\n\n            <el-dropdown-item\n              v-if=\"\n                (currentModel.model_type === 'TTS' ||\n                  currentModel.model_type === 'STT' ||\n                  currentModel.model_type === 'LLM' ||\n                  currentModel.model_type === 'IMAGE' ||\n                  currentModel.model_type === 'TTI' ||\n                  currentModel.model_type === 'ITV' ||\n                  currentModel.model_type === 'EMBEDDING' ||\n                  currentModel.model_type === 'TTV') &&\n                permissionPrecise.paramSetting(model.id)\n              \"\n              @click.stop=\"openParamSetting\"\n            >\n              <AppIcon iconName=\"app-setting\" class=\"color-secondary\"></AppIcon>\n              {{ $t('views.model.modelForm.title.paramSetting') }}\n            </el-dropdown-item>\n            <el-dropdown-item\n              @click.stop=\"openAuthorization(model)\"\n              v-if=\"apiType === 'workspace' && permissionPrecise.auth(model.id)\"\n            >\n              <AppIcon iconName=\"app-resource-authorization\" class=\"color-secondary\"></AppIcon>\n              {{ $t('views.system.resourceAuthorization.title') }}\n            </el-dropdown-item>\n            <el-dropdown-item\n              text\n              @click.stop=\"openResourceMappingDrawer(model)\"\n              v-if=\"permissionPrecise.relate_map(model.id)\"\n            >\n              <AppIcon iconName=\"app-resource-mapping\" class=\"color-secondary\"></AppIcon>\n              {{ $t('views.system.resourceMapping.title') }}\n            </el-dropdown-item>\n            <el-dropdown-item\n              divided\n              text\n              @click.stop=\"deleteModel\"\n              v-if=\"permissionPrecise.delete(model.id)\"\n            >\n              <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n              {{ $t('common.delete') }}\n            </el-dropdown-item>\n          </el-dropdown-menu>\n        </template>\n      </el-dropdown>\n    </template>\n    <EditModel ref=\"editModelRef\" @submit=\"emit('change')\"></EditModel>\n    <ParamSettingDialog ref=\"paramSettingRef\" />\n    <AuthorizedWorkspace\n      ref=\"AuthorizedWorkspaceDialogRef\"\n      v-if=\"isSystemShare\"\n    ></AuthorizedWorkspace>\n    <ResourceAuthorizationDrawer\n      :type=\"SourceTypeEnum.MODEL\"\n      ref=\"ResourceAuthorizationDrawerRef\"\n      v-if=\"apiType === 'workspace'\"\n    />\n    <ResourceMappingDrawer ref=\"resourceMappingDrawerRef\"></ResourceMappingDrawer>\n  </card-box>\n</template>\n<script setup lang=\"ts\">\nimport type { Provider, Model } from '@/api/type/model'\nimport { computed, ref, onMounted, onBeforeUnmount } from 'vue'\nimport EditModel from '@/views/model/component/EditModel.vue'\nimport DownloadLoading from '@/components/loading/DownloadLoading.vue'\nimport { MsgConfirm, MsgSuccess } from '@/utils/message'\nimport { modelType } from '@/enums/model'\nimport ParamSettingDialog from './ParamSettingDialog.vue'\nimport AuthorizedWorkspace from '@/views/system-shared/AuthorizedWorkspaceDialog.vue'\nimport ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'\nimport ResourceMappingDrawer from '@/components/resource_mapping/index.vue'\nimport { SourceTypeEnum } from '@/enums/common'\nimport { t } from '@/locales'\nimport { i18n_name } from '@/utils/common'\nimport { dateFormat } from '@/utils/time'\nimport permissionMap from '@/permission'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst resourceMappingDrawerRef = ref<InstanceType<typeof ResourceMappingDrawer>>()\nconst route = useRoute()\n\nconst props = defineProps<{\n  model: Model\n  provider_list: Array<Provider>\n  updateModelById: (model_id: string, model: Model) => void\n  isShared?: boolean | undefined\n  isSystemShare?: boolean | undefined\n  apiType: 'systemShare' | 'workspace' | 'systemManage'\n}>()\nconst openResourceMappingDrawer = (model: any) => {\n  resourceMappingDrawerRef.value?.open('MODEL', model)\n}\nconst isSystemShare = computed(() => {\n  return props.apiType === 'systemShare'\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['model'][props.apiType]\n})\n\nconst MoreFilledPermission = (id: any) => {\n  return (\n    permissionPrecise.value.modify(id) ||\n    permissionPrecise.value.delete(id) ||\n    permissionPrecise.value.auth(id) ||\n    permissionPrecise.value.relate_map(id) ||\n    isSystemShare.value\n  )\n}\n\nconst ResourceAuthorizationDrawerRef = ref()\n\nfunction openAuthorization(item: any) {\n  ResourceAuthorizationDrawerRef.value.open(item.id)\n}\n\nconst downModel = ref<Model>()\n\nconst currentModel = computed(() => {\n  if (downModel.value) {\n    return downModel.value\n  } else {\n    return props.model\n  }\n})\n\nconst errMessage = computed(() => {\n  if (currentModel.value.meta && currentModel.value.meta.message) {\n    if (currentModel.value.meta.message === 'pull model manifest: file does not exist') {\n      return `${currentModel.value.model_name} ${t('views.model.tip.noModel')}`\n    }\n    return currentModel.value.meta.message\n  }\n  return ''\n})\nconst emit = defineEmits(['change', 'update:model'])\nconst editModelRef = ref<InstanceType<typeof EditModel>>()\nlet interval: any\nconst deleteModel = () => {\n  MsgConfirm(\n    `${t('views.model.delete.confirmTitle')}${props.model.name} ?`,\n    props.model.resource_count > 0\n      ? t('views.model.delete.resourceCountMessage', { count: props.model.resource_count })\n      : '',\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loadSharedApi({ type: 'model', systemType: props.apiType })\n        .deleteModel(props.model.id)\n        .then(() => {\n          emit('change')\n          MsgSuccess(t('common.deleteSuccess'))\n        })\n    })\n    .catch(() => {})\n}\n\nconst cancelDownload = () => {\n  loadSharedApi({ type: 'model', systemType: props.apiType })\n    .pauseDownload(props.model.id)\n    .then(() => {\n      downModel.value = undefined\n      emit('change')\n    })\n}\nconst openEditModel = () => {\n  const provider = props.provider_list.find((p) => p.provider === props.model.provider)\n  if (provider) {\n    editModelRef.value?.open(provider, props.model)\n  }\n}\nconst icon = computed(() => {\n  return props.provider_list.find((p) => p.provider === props.model.provider)?.icon\n})\n\n/**\n * 初始化轮询\n */\nconst initInterval = () => {\n  interval = setInterval(() => {\n    if (currentModel.value.status === 'DOWNLOAD') {\n      loadSharedApi({ type: 'model', systemType: props.apiType })\n        .getModelMetaById(props.model.id)\n        .then((ok: any) => {\n          downModel.value = ok.data\n        })\n    } else {\n      if (downModel.value) {\n        props.updateModelById(props.model.id, downModel.value)\n        downModel.value = undefined\n      }\n    }\n  }, 6000)\n}\n\n/**\n * 关闭轮询\n */\nconst closeInterval = () => {\n  if (interval) {\n    clearInterval(interval)\n  }\n}\n\nconst paramSettingRef = ref<InstanceType<typeof ParamSettingDialog>>()\nconst openParamSetting = () => {\n  paramSettingRef.value?.open(props.model)\n}\n\nconst AuthorizedWorkspaceDialogRef = ref()\n\nfunction openAuthorizedWorkspaceDialog(row: any) {\n  if (AuthorizedWorkspaceDialogRef.value) {\n    AuthorizedWorkspaceDialogRef.value.open(row, 'Model')\n  }\n}\n\nonMounted(() => {\n  initInterval()\n})\nonBeforeUnmount(() => {\n  // 清除定时任务\n  closeInterval()\n})\n</script>\n<style lang=\"scss\" scoped>\n.model-card {\n  min-height: 135px;\n  min-width: auto;\n\n  .operation-button {\n    position: absolute;\n    right: 12px;\n    bottom: 12px;\n    height: auto;\n  }\n\n  .progress-mask {\n    position: absolute;\n    top: 0;\n    left: 0;\n    background-color: rgba(255, 255, 255, 0.9);\n    width: 100%;\n    height: 100%;\n    z-index: 99;\n    text-align: center;\n\n    .percentage {\n      margin-top: 55px;\n      margin-bottom: 16px;\n    }\n\n    .percentage-label {\n      margin-top: 50px;\n      margin-left: 10px;\n      font-size: 13px;\n      color: var(--app-text-color-secondary);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/model/component/ParamSettingDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.model.modelForm.title.paramSetting')\"\n    v-model=\"dialogVisible\"\n    width=\"800px\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n  >\n    <el-button type=\"primary\" @click=\"openAddDrawer()\" class=\"mb-12\">\n      {{ $t('common.param.addParam') }}\n    </el-button>\n    <el-table :data=\"modelParamsForm\" class=\"mb-16\">\n      <el-table-column\n        prop=\"label\"\n        :label=\"$t('dynamicsForm.paramForm.name.label')\"\n        show-overflow-tooltip\n      >\n        <template #default=\"{ row }\">\n          <span v-if=\"row.label && row.label.input_type === 'TooltipLabel'\">{{\n            row.label.label\n          }}</span>\n          <span v-else>{{ row.label }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column\n        prop=\"field\"\n        :label=\"$t('dynamicsForm.paramForm.field.label')\"\n        show-overflow-tooltip\n      />\n      <el-table-column :label=\"$t('dynamicsForm.paramForm.input_type.label')\" width=\"110px\">\n        <template #default=\"{ row }\">\n          <el-tag size=\"small\" type=\"info\" class=\"info-tag\">{{\n            input_type_list.find((item) => item.value === row.input_type)?.label\n          }}</el-tag>\n        </template>\n      </el-table-column>\n      <el-table-column\n        prop=\"default_value\"\n        :label=\"$t('dynamicsForm.default.label')\"\n        show-overflow-tooltip\n      />\n      <el-table-column :label=\"$t('common.required')\">\n        <template #default=\"{ row }\">\n          <div @click.stop>\n            <el-switch disabled size=\"small\" v-model=\"row.required\" />\n          </div>\n        </template>\n      </el-table-column>\n\n      <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n        <template #default=\"{ row, $index }\">\n          <span class=\"mr-4\">\n            <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n              <el-button type=\"primary\" text @click.stop=\"openAddDrawer(row, $index)\">\n                <AppIcon iconName=\"app-edit\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </span>\n          <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n            <el-button type=\"primary\" text @click=\"deleteParam($index)\">\n              <AppIcon iconName=\"app-delete\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </template>\n      </el-table-column>\n    </el-table>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click=\"close\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n  <AddParamDrawer ref=\"AddParamRef\" @refresh=\"refresh\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { Model } from '@/api/type/model'\nimport AddParamDrawer from './AddParamDrawer.vue'\nimport { MsgError, MsgSuccess } from '@/utils/message'\nimport { input_type_list } from '@/components/dynamics-form/constructor/data'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst loading = ref<boolean>(false)\nconst dialogVisible = ref<boolean>(false)\nconst modelParamsForm = ref<any[]>([])\nconst AddParamRef = ref()\nconst currentModel = ref<Model | null>(null)\n\nconst open = (model: Model) => {\n  currentModel.value = model\n  dialogVisible.value = true\n  loading.value = true\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getModelParamsForm(model.id, loading)\n    .then((ok: any) => {\n      loading.value = false\n      modelParamsForm.value = ok.data\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nconst close = () => {\n  dialogVisible.value = false\n}\n\nfunction openAddDrawer(data?: any, index?: any) {\n  AddParamRef.value?.open(data, index)\n}\n\nfunction deleteParam(index: any) {\n  modelParamsForm.value.splice(index, 1)\n}\n\nfunction refresh(data: any, index: any) {\n  for (let i = 0; i < modelParamsForm.value.length; i++) {\n    const field = modelParamsForm.value[i].field\n    let label = modelParamsForm.value[i].label\n    if (label && label.input_type === 'TooltipLabel') {\n      label = label.label\n    }\n    let label2 = data.label\n    if (label2 && label2.input_type === 'TooltipLabel') {\n      label2 = label2.label\n    }\n\n    if (field === data.field && index !== i) {\n      MsgError(t('views.model.tip.errorMessage') + data.field)\n      return\n    }\n    if (label === label2 && index !== i) {\n      MsgError(t('views.model.tip.errorMessage') + label)\n      return\n    }\n  }\n  if (index !== null) {\n    modelParamsForm.value.splice(index, 1, data)\n  } else {\n    modelParamsForm.value.push(data)\n  }\n}\n\nfunction submit() {\n  if (!currentModel.value) {\n    return\n  }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .updateModelParamsForm(currentModel.value.id, modelParamsForm.value, loading)\n    .then((ok: any) => {\n      MsgSuccess(t('views.model.tip.saveSuccessMessage'))\n      close()\n      // emit('submit')\n    })\n}\n\ndefineExpose({ open, close })\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/model/component/Provider.vue",
    "content": "<template>\n  <div class=\"provider-list\">\n    <el-scrollbar>\n      <div class=\"p-8\">\n        <div v-if=\"showShared && hasPermission(EditionConst.IS_EE, 'OR')\" class=\"border-b mb-4\">\n          <div\n            @click=\"handleSharedNodeClick\"\n            class=\"shared-button flex cursor\"\n            :class=\"active?.provider === 'share' && 'active'\"\n          >\n            <AppIcon\n              iconName=\"app-shared-active\"\n              style=\"font-size: 18px\"\n              class=\"color-primary\"\n            ></AppIcon>\n            <span class=\"ml-8\">{{ $t('views.shared.shared_model') }}</span>\n          </div>\n        </div>\n        <div\n          class=\"all-mode flex cursor\"\n          @click=\"clickListHandle(allObj as Provider)\"\n          :class=\"!active?.provider ? 'all-mode-active color-primary-1' : ''\"\n        >\n          <AppIcon\n            class=\"mr-8 color-primary\"\n            style=\"height: 20px; width: 20px\"\n            :iconName=\"'app-all-menu-active'\"\n          ></AppIcon>\n          <span>{{ $t('views.model.modelType.allModel') }}</span>\n        </div>\n\n        <el-collapse class=\"model-collapse\" expand-icon-position=\"left\">\n          <el-collapse-item\n            :title=\"$t('views.model.modelType.publicModel')\"\n            name=\"1\"\n            icon=\"CaretRight\"\n          >\n            <template #title>\n              <div class=\"flex align-center\">\n                <AppIcon iconName=\"app-folder\" style=\"font-size: 20px\"></AppIcon>\n                <span class=\"ml-8\">\n                  {{ $t('views.model.modelType.publicModel') }}\n                </span>\n              </div>\n            </template>\n            <common-list\n              :data=\"online_provider_list\"\n              v-loading=\"loading\"\n              @click=\"clickListHandle\"\n              value-key=\"provider\"\n              default-active=\"\"\n              ref=\"commonList1\"\n            >\n              <template #default=\"{ row }\">\n                <div class=\"flex align-center\">\n                  <span\n                    :innerHTML=\"row.icon\"\n                    alt=\"\"\n                    style=\"height: 20px; width: 20px\"\n                    class=\"mr-8\"\n                  />\n                  <span class=\"ellipsis-1\" :title=\"row.name\">{{ row.name }}</span>\n                </div>\n              </template>\n            </common-list>\n          </el-collapse-item>\n          <el-collapse-item\n            :title=\"$t('views.model.modelType.privateModel')\"\n            name=\"2\"\n            icon=\"CaretRight\"\n          >\n            <template #title>\n              <div class=\"flex align-center\">\n                <AppIcon iconName=\"app-folder\" style=\"font-size: 20px\"></AppIcon>\n                <span class=\"ml-8\">\n                  {{ $t('views.model.modelType.privateModel') }}\n                </span>\n              </div>\n            </template>\n            <common-list\n              :data=\"local_provider_list\"\n              v-loading=\"loading\"\n              @click=\"clickListHandle\"\n              value-key=\"provider\"\n              default-active=\"\"\n              ref=\"commonList2\"\n            >\n              <template #default=\"{ row }\">\n                <div class=\"flex align-center\">\n                  <span\n                    :innerHTML=\"row.icon\"\n                    alt=\"\"\n                    style=\"height: 20px; width: 20px\"\n                    class=\"mr-8\"\n                  />\n                  <span class=\"ellipsis-1\" :title=\"row.name\">{{ row.name }}</span>\n                </div>\n              </template>\n            </common-list>\n          </el-collapse-item>\n        </el-collapse>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { watch, ref } from 'vue'\nimport type { Provider, Model } from '@/api/type/model'\nimport { modelTypeList, allObj } from '@/views/model/component/data'\nimport { EditionConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport { t } from '@/locales'\nconst props = defineProps<{\n  data: Array<Provider>\n  loading: boolean\n  showShared?: boolean\n  active?: Provider\n}>()\nconst emit = defineEmits(['click'])\n\nconst online_provider_list = ref<Array<Provider>>([])\nconst local_provider_list = ref<Array<Provider>>([])\n\nwatch(\n  () => props.data,\n  (list) => {\n    const local_provider = [\n      'model_ollama_provider',\n      'model_local_provider',\n      'model_xinference_provider',\n      'model_vllm_provider',\n      'model_docker_ai_provider'\n    ]\n    list\n      .filter((v) => v.provider)\n      ?.forEach((item) => {\n        if (local_provider.indexOf(item.provider) > -1) {\n          local_provider_list.value.push(item)\n        } else {\n          online_provider_list.value.push(item)\n        }\n      })\n    online_provider_list.value.sort((a, b) => a.provider.localeCompare(b.provider))\n    local_provider_list.value.sort((a, b) => a.provider.localeCompare(b.provider))\n  },\n  { immediate: true },\n)\n\nconst clickListHandle = (item: Provider) => {\n  emit('click', item)\n}\n\nconst handleSharedNodeClick = () => {\n  emit('click', { provider: 'share', name: t('views.shared.shared_model') })\n}\n</script>\n<style lang=\"scss\" scoped>\n.provider-list {\n  height: calc(var(--app-main-height));\n  .all-mode {\n    padding: 10px 8px;\n    font-weight: 400;\n    &:hover {\n      border-radius: var(--app-border-radius-small);\n      background: rgba(var(--el-text-color-primary-rgb), 0.1);\n    }\n  }\n  .all-mode-active {\n    border-radius: var(--app-border-radius-small);\n    color: var(--el-color-primary);\n    font-weight: 500 !important;\n    background: var(--el-color-primary-light-9);\n    &:hover {\n      background: var(--el-color-primary-light-9);\n    }\n  }\n  .model-collapse {\n    border-top: none !important;\n    border-bottom: none !important;\n    :deep(.el-collapse-item__header) {\n      border-bottom: none !important;\n      padding-left: 8px;\n      font-size: 14px;\n      font-weight: 400;\n      height: 40px;\n      background: none;\n      &:hover {\n        background: rgba(var(--el-text-color-primary-rgb), 0.1);\n        border-radius: var(--app-border-radius-small);\n      }\n    }\n    :deep(.el-collapse-item) {\n      margin-top: 2px;\n    }\n    :deep(.common-list) {\n      li {\n        padding-left: 50px !important;\n      }\n    }\n    :deep(.el-collapse-item__wrap) {\n      border-bottom: none !important;\n      background: none !important;\n    }\n    :deep(.el-collapse-item__content) {\n      padding-bottom: 0 !important;\n    }\n  }\n  .shared-button {\n    padding: 10px 8px;\n    font-weight: 400;\n    font-size: 14px;\n    margin-bottom: 4px;\n    &.active {\n      background: var(--el-color-primary-light-9);\n      border-radius: var(--app-border-radius-small);\n      color: var(--el-color-primary);\n      font-weight: 500;\n      &:hover {\n        background: var(--el-color-primary-light-9);\n      }\n    }\n    &:hover {\n      border-radius: var(--app-border-radius-small);\n      background: rgba(var(--el-text-color-primary-rgb), 0.1);\n    }\n    &.is-active {\n      &:hover {\n        color: var(--el-color-primary);\n        background: var(--el-color-primary-light-9);\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/model/component/SelectProviderDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    width=\"600px\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <template #header>\n      <div class=\"flex-between\">\n        <h4>{{ $t('views.model.providerPlaceholder') }}</h4>\n        <el-dropdown>\n          <span class=\"cursor\">\n            {{ currentModelType || $t('views..model.modelType.allModel') }}\n            <el-icon class=\"el-icon--right\">\n              <arrow-down />\n            </el-icon>\n          </span>\n          <template #dropdown>\n            <el-dropdown-menu>\n              <el-dropdown-item\n                v-for=\"item in modelTypeOptions\"\n                :key=\"item.value\"\n                @click=\"checkModelType(item.value)\"\n                class=\"flex-between w-120\"\n                :class=\"currentModelType === item.text ? 'active' : ''\"\n              >\n                <span>{{ item.text }}</span>\n                <el-icon v-if=\"currentModelType === item.text\"><Check /></el-icon>\n              </el-dropdown-item>\n            </el-dropdown-menu>\n          </template>\n        </el-dropdown>\n      </div>\n    </template>\n    <el-row :gutter=\"12\" v-loading=\"loading\">\n      <el-col :span=\"12\" class=\"mb-16\" v-for=\"(data, index) in list_provider\" :key=\"index\">\n        <el-card shadow=\"hover\" @click=\"go_create(data)\">\n          <div class=\"flex align-center cursor\">\n            <span :innerHTML=\"data.icon\" alt=\"\" style=\"height: 24px; width: 24px\" class=\"mr-8\" />\n            <span>{{ data.name }}</span>\n          </div>\n        </el-card>\n      </el-col>\n    </el-row>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport ProviderApi from '@/api/model/provider'\nimport type { Provider } from '@/api/type/model'\nimport { modelTypeList } from './data'\nimport { t } from '@/locales'\n\nconst loading = ref<boolean>(false)\nconst dialogVisible = ref<boolean>(false)\nconst list_provider = ref<Array<Provider>>([])\nconst currentModelType = ref('')\nconst selectModelType = ref('')\nconst modelTypeOptions = [\n  { text: t('views.model.modelType.allModel'), value: '' },\n  ...modelTypeList,\n]\n\nconst open = (model_type?: string) => {\n  dialogVisible.value = true\n  const option = modelTypeOptions.find((item) => item.text === currentModelType.value)\n  checkModelType(model_type ? model_type : option ? option.value : '')\n}\n\nconst close = () => {\n  dialogVisible.value = false\n}\n\nconst checkModelType = (model_type: string) => {\n  selectModelType.value = model_type\n  currentModelType.value = modelTypeOptions.filter((item) => item.value === model_type)[0].text\n  ProviderApi.getProviderByModelType(model_type, loading).then((ok) => {\n    list_provider.value = ok.data\n    list_provider.value.sort((a, b) => a.provider.localeCompare(b.provider))\n  })\n}\n\nconst emit = defineEmits(['change'])\nconst go_create = (provider: Provider) => {\n  close()\n  emit('change', provider, selectModelType.value)\n}\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/model/component/data.ts",
    "content": "import { modelType } from '@/enums/model'\nimport { t } from '@/locales'\nexport const modelTypeList = [\n  { text: t(modelType['LLM']), value: 'LLM' },\n  { text: t(modelType['EMBEDDING']), value: 'EMBEDDING' },\n  { text: t(modelType['RERANKER']), value: 'RERANKER' },\n  { text: t(modelType['STT']), value: 'STT' },\n  { text: t(modelType['TTS']), value: 'TTS' },\n  { text: t(modelType['IMAGE']), value: 'IMAGE' },\n  { text: t(modelType['TTI']), value: 'TTI' },\n  { text: t(modelType['ITV']), value: 'ITV' },\n  { text: t(modelType['TTV']), value: 'TTV' },\n]\n\n\nexport const allObj = {\n  icon: '',\n  provider: '',\n  name: t('views.model.modelType.allModel'),\n}\n"
  },
  {
    "path": "ui/src/views/model/index.vue",
    "content": "<template>\n  <LayoutContainer showCollapse class=\"model-manage\">\n    <template #left>\n      <h4 class=\"p-12-16 pb-0 mt-12\">{{ $t('views.model.provider') }}</h4>\n      <ProviderComponent\n        :data=\"provider_list\"\n        @click=\"clickListHandle\"\n        :loading=\"loading\"\n        :showShared=\"permissionPrecise['is_share']()\"\n        :active=\"active_provider\"\n      />\n    </template>\n    <ContentContainer\n      :header=\"active_provider?.name\"\n      v-loading=\"list_model_loading\"\n      style=\"padding: 0\"\n    >\n      <template #search>\n        <div class=\"flex\">\n          <div class=\"complex-search\">\n            <el-select\n              class=\"complex-search__left\"\n              v-model=\"search_type\"\n              style=\"width: 120px\"\n              @change=\"search_type_change\"\n            >\n              <el-option :label=\"$t('common.creator')\" value=\"create_user\" />\n              <el-option :label=\"$t('views.model.modelForm.model_type.label')\" value=\"model_type\" />\n              <el-option :label=\"$t('views.model.modelForm.modeName.label')\" value=\"name\" />\n            </el-select>\n            <el-input\n              v-if=\"search_type === 'name'\"\n              v-model=\"model_search_form.name\"\n              @change=\"list_model\"\n              :placeholder=\"$t('common.searchBar.placeholder')\"\n              style=\"width: 220px\"\n              clearable\n            />\n            <el-select\n              v-else-if=\"search_type === 'create_user'\"\n              v-model=\"model_search_form.create_user\"\n              @change=\"list_model\"\n              filterable\n              clearable\n              style=\"width: 220px\"\n            >\n              <el-option v-for=\"u in user_options\" :key=\"u.id\" :value=\"u.id\" :label=\"u.nick_name\" />\n            </el-select>\n            <el-select\n              v-else-if=\"search_type === 'model_type'\"\n              v-model=\"model_search_form.model_type\"\n              clearable\n              @change=\"list_model\"\n              style=\"width: 220px\"\n            >\n              <template v-for=\"item in modelTypeList\" :key=\"item.value\">\n                <el-option :label=\"item.text\" :value=\"item.value\" />\n              </template>\n            </el-select>\n          </div>\n          <el-button\n            v-if=\"!isShared && permissionPrecise.create()\"\n            class=\"ml-16\"\n            type=\"primary\"\n            @click=\"openCreateModel(active_provider)\"\n          >\n            {{ $t('views.model.addModel') }}\n          </el-button>\n        </div>\n      </template>\n\n      <div class=\"model-list-height\">\n        <el-row v-if=\"model_split_list.length > 0\" :gutter=\"15\" class=\"w-full\">\n          <template v-for=\"(row, index) in model_split_list\" :key=\"index\">\n            <el-col\n              :xs=\"24\"\n              :sm=\"12\"\n              :md=\"isSystemShare ? 24 : 12\"\n              :lg=\"isSystemShare ? 12 : 8\"\n              :xl=\"isSystemShare ? 12 : 8\"\n              class=\"mb-16\"\n              v-for=\"(model, i) in row\"\n              :key=\"i\"\n            >\n              <ModelCard\n                @change=\"list_model\"\n                :updateModelById=\"updateModelById\"\n                :model=\"model\"\n                :provider_list=\"provider_list\"\n                :isShared=\"isShared\"\n                :isSystemShare=\"isSystemShare\"\n                :apiType=\"apiType\"\n              >\n              </ModelCard>\n            </el-col>\n          </template>\n        </el-row>\n        <el-empty :description=\"$t('common.noData')\" v-else />\n      </div>\n    </ContentContainer>\n\n    <CreateModelDialog\n      ref=\"createModelRef\"\n      @submit=\"list_model\"\n      @change=\"openCreateModel($event)\"\n      v-if=\"!isShared\"\n    ></CreateModelDialog>\n\n    <SelectProviderDialog\n      ref=\"selectProviderRef\"\n      @change=\"(provider, modelType) => openCreateModel(provider, modelType)\"\n      v-if=\"!isShared\"\n    ></SelectProviderDialog>\n  </LayoutContainer>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, computed } from 'vue'\nimport type { Provider, Model } from '@/api/type/model'\nimport ModelCard from '@/views/model/component/ModelCard.vue'\nimport ProviderComponent from '@/views/model/component/Provider.vue'\nimport { splitArray } from '@/utils/array'\nimport { modelTypeList, allObj } from '@/views/model/component/data'\nimport CreateModelDialog from '@/views/model/component/CreateModelDialog.vue'\nimport SelectProviderDialog from '@/views/model/component/SelectProviderDialog.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport useStore from '@/stores'\nimport { useRoute } from 'vue-router'\nimport permissionMap from '@/permission'\n\nconst route = useRoute()\nconst { model, user } = useStore()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['model'][apiType.value]\n})\nconst isSystemShare = computed(() => {\n  return apiType.value === 'systemShare'\n})\nconst commonList1 = ref()\nconst commonList2 = ref()\nconst loading = ref<boolean>(false)\n\nconst active_provider = ref<Provider>()\nconst search_type = ref('name')\nconst model_search_form = ref<{\n  name: string\n  create_user: string\n  model_type: string\n}>({\n  name: '',\n  create_user: '',\n  model_type: '',\n})\nconst user_options = ref<any[]>([])\nconst list_model_loading = ref<boolean>(false)\nconst provider_list = ref<Array<Provider>>([])\n\nconst model_list = ref<Array<Model>>([])\n\nconst isShared = computed(() => {\n  return active_provider.value && active_provider.value.provider === 'share'\n})\nconst updateModelById = (model_id: string, model: Model) => {\n  model_list.value\n    .filter((m) => m.id == model_id)\n    .forEach((m) => {\n      m.status = model.status\n    })\n}\nconst model_split_list = computed(() => {\n  return splitArray(model_list.value, 2)\n})\nconst createModelRef = ref<InstanceType<typeof CreateModelDialog>>()\nconst selectProviderRef = ref<InstanceType<typeof SelectProviderDialog>>()\n\nconst clickListHandle = (item: Provider) => {\n  active_provider.value = item\n  list_model()\n  if (active_provider.value.provider === '') {\n    commonList1.value?.clearCurrent()\n    commonList2.value?.clearCurrent()\n  }\n}\n\nconst openCreateModel = (provider?: Provider, model_type?: string) => {\n  if (provider && provider.provider) {\n    createModelRef.value?.open(provider, model_type)\n  } else {\n    selectProviderRef.value?.open()\n  }\n}\n\nconst list_model = () => {\n  const params = active_provider.value?.provider && active_provider.value?.provider !=='share' ? { provider: active_provider.value.provider } : {}\n  loadSharedApi({ type: 'model', isShared: isShared.value, systemType: apiType.value })\n    .getModelList({ ...model_search_form.value, ...params }, list_model_loading)\n    .then((ok: any) => {\n      model_list.value = ok.data\n    })\n  loadSharedApi({ type: 'workspace', isShared: isShared.value, systemType: apiType.value })\n    .getAllMemberList(user.getWorkspaceId(), loading)\n    .then((res: any) => {\n      user_options.value = res.data\n    })\n}\n\nconst search_type_change = () => {\n  model_search_form.value = { name: '', create_user: '', model_type: '' }\n}\n\nonMounted(() => {\n  model.asyncGetProvider(loading).then((ok: any) => {\n    active_provider.value = allObj\n    provider_list.value = [allObj, ...ok.data]\n    list_model()\n  })\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.model-manage {\n  .model-list-height {\n    height: calc(var(--app-main-height));\n    padding-right: 0 !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/paragraph/component/ParagraphCard.vue",
    "content": "<template>\n  <el-card\n    shadow=\"hover\"\n    class=\"paragraph-box cursor\"\n    :class=\"data.is_active ? '' : 'disabled'\"\n    @mouseenter=\"cardEnter()\"\n    @mouseleave=\"cardLeave()\"\n    @click.stop=\"handleClickCard(data)\"\n    v-loading=\"loading\"\n  >\n    <div v-show=\"show\" class=\"mk-sticky\" v-if=\"!disabled\">\n      <el-card\n        class=\"paragraph-box-operation mt-8 mr-8\"\n        shadow=\"always\"\n        style=\"--el-card-padding: 8px 12px; --el-card-border-radius: 8px\"\n        @click.stop\n        v-if=\"MoreFieldPermission(id)\"\n      >\n        <el-switch\n          :loading=\"changeStateloading\"\n          v-model=\"data.is_active\"\n          :before-change=\"() => changeState(data)\"\n          size=\"small\"\n          v-if=\"permissionPrecise.doc_edit(id)\"\n        />\n\n        <el-divider direction=\"vertical\" />\n        <span class=\"mr-8\">\n          <el-tooltip\n            effect=\"dark\"\n            :content=\"$t('views.paragraph.editParagraph')\"\n            placement=\"top\"\n            v-if=\"permissionPrecise.doc_edit(id)\"\n          >\n            <el-button text @click.stop=\"editParagraph(data)\">\n              <AppIcon iconName=\"app-edit\" :size=\"16\" class=\"color-secondary\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <span class=\"mr-8\">\n          <el-tooltip\n            effect=\"dark\"\n            :content=\"$t('views.paragraph.prevAddParagraph')\"\n            placement=\"top\"\n            v-if=\"permissionPrecise.doc_edit(id)\"\n          >\n            <el-button text @click.stop=\"addParagraph(data)\" v-if=\"permissionPrecise.doc_edit(id)\">\n              <AppIcon\n                iconName=\"app-add-circle-outlined\"\n                class=\"color-secondary\"\n                :size=\"16\"\n              ></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <el-dropdown trigger=\"click\" :teleported=\"false\" v-if=\"MoreFieldPermission(id)\">\n          <el-button text>\n            <AppIcon iconName=\"app-more\" class=\"color-secondary\"></AppIcon>\n          </el-button>\n          <template #dropdown>\n            <el-dropdown-menu style=\"min-width: 140px\">\n              <el-dropdown-item\n                @click.stop=\"openGenerateDialog(data)\"\n                v-if=\"permissionPrecise.doc_generate(id)\"\n              >\n                <AppIcon iconName=\"app-generate-question\" class=\"color-secondary\"></AppIcon>\n                {{ $t('views.document.generateQuestion.title') }}</el-dropdown-item\n              >\n              <el-dropdown-item\n                @click.stop=\"openSelectDocumentDialog(data)\"\n                v-if=\"permissionPrecise.doc_edit(id)\"\n              >\n                <AppIcon iconName=\"app-migrate\" class=\"color-secondary\"></AppIcon>\n                {{ $t('views.document.setting.migration') }}</el-dropdown-item\n              >\n              <el-dropdown-item v-if=\"permissionPrecise.doc_edit(id)\">\n                <el-dropdown\n                  class=\"w-full\"\n                  trigger=\"hover\"\n                  :show-arrow=\"false\"\n                  placement=\"right-start\"\n                  popper-class=\"move-position-popper\"\n                >\n                  <div class=\"w-full flex-between\" style=\"line-height: 22px\">\n                    <div class=\"flex align-center\">\n                      <AppIcon iconName=\"app-drag-outlined\" class=\"color-secondary\"></AppIcon>\n                      {{ $t('views.document.movePosition.title') }}\n                    </div>\n                    <el-icon class=\"color-input-placeholder\" :size=\"16\" style=\"margin-right: 0\"\n                      ><ArrowRight\n                    /></el-icon>\n                  </div>\n                  <template #dropdown>\n                    <el-dropdown-menu>\n                      <el-dropdown-item\n                        :disabled=\"!props.showMoveUp\"\n                        @click.stop=\"emit('move', 'top')\"\n                      >\n                        {{ $t('views.document.movePosition.moveTop') }}\n                      </el-dropdown-item>\n                      <el-dropdown-item\n                        :disabled=\"!props.showMoveUp\"\n                        @click.stop=\"emit('move', 'up')\"\n                      >\n                        {{ $t('views.document.movePosition.moveUp') }}\n                      </el-dropdown-item>\n                      <el-dropdown-item\n                        :disabled=\"!props.showMoveDown\"\n                        @click.stop=\"emit('move', 'down')\"\n                      >\n                        {{ $t('views.document.movePosition.moveDown') }}\n                      </el-dropdown-item>\n                      <el-dropdown-item\n                        :disabled=\"!props.showMoveDown\"\n                        @click.stop=\"emit('move', 'bottom')\"\n                      >\n                        {{ $t('views.document.movePosition.moveBottom') }}\n                      </el-dropdown-item>\n                    </el-dropdown-menu>\n                  </template>\n                </el-dropdown>\n              </el-dropdown-item>\n              <el-dropdown-item\n                @click.stop=\"deleteParagraph(data)\"\n                v-if=\"permissionPrecise.doc_edit(id)\"\n              >\n                <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                {{ $t('common.delete') }}</el-dropdown-item\n              >\n            </el-dropdown-menu>\n          </template>\n        </el-dropdown>\n      </el-card>\n    </div>\n    <h2 class=\"mb-16\">{{ data.title || '-' }}</h2>\n    <MdPreview\n      ref=\"editorRef\"\n      editorId=\"preview-only\"\n      :modelValue=\"data.content\"\n      class=\"maxkb-md\"\n      style=\"background: none\"\n      @clickPreview=\"handleClickCard(data)\"\n    />\n\n    <ParagraphDialog\n      ref=\"ParagraphDialogRef\"\n      :title=\"title\"\n      @refresh=\"refresh\"\n      :apiType=\"apiType\"\n    />\n    <SelectDocumentDialog\n      ref=\"SelectDocumentDialogRef\"\n      @refresh=\"refreshMigrateParagraph\"\n      :apiType=\"apiType\"\n    />\n    <GenerateRelatedDialog ref=\"GenerateRelatedDialogRef\" @refresh=\"refresh\" :apiType=\"apiType\" />\n  </el-card>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, watch, onMounted } from 'vue'\nimport { useRoute } from 'vue-router'\nimport GenerateRelatedDialog from '@/components/generate-related-dialog/index.vue'\nimport ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue'\nimport SelectDocumentDialog from '@/views/paragraph/component/SelectDocumentDialog.vue'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\nimport { t } from '@/locales'\nconst props = defineProps<{\n  data: any\n  disabled?: boolean\n  showMoveUp?: boolean\n  showMoveDown?: boolean\n}>()\n\nconst route = useRoute()\nconst {\n  params: { id, documentId },\n  query: { from, isShared },\n} = route as any\n\nconst shareDisabled = computed(() => {\n  return isShared === 'true'\n})\n\nconst apiType = computed(() => {\n  return from as 'systemShare' | 'workspace' | 'systemManage'\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst MoreFieldPermission = (id: any) => {\n  return permissionPrecise.value.doc_generate(id) || permissionPrecise.value.doc_edit(id)\n}\n\nconst emit = defineEmits([\n  'dialogVisibleChange',\n  'clickCard',\n  'changeState',\n  'deleteParagraph',\n  'refresh',\n  'refreshMigrateParagraph',\n  'move',\n])\nconst loading = ref(false)\nconst changeStateloading = ref(false)\nconst show = ref(false)\n// card上面存在dropdown菜单\nconst subHovered = ref(false)\nfunction cardEnter() {\n  show.value = true\n  subHovered.value = false\n}\n\nfunction cardLeave() {\n  show.value = subHovered.value\n}\n\nasync function changeState(row: any) {\n  const obj = {\n    is_active: !row.is_active,\n  }\n  await loadSharedApi({ type: 'paragraph', systemType: apiType.value })\n    .putParagraph(id, documentId, row.id, obj, changeStateloading)\n    .then(() => {\n      emit('changeState', row.id)\n      return true\n    })\n    .catch(() => {\n      return false\n    })\n}\n\nconst GenerateRelatedDialogRef = ref<InstanceType<typeof GenerateRelatedDialog>>()\nfunction openGenerateDialog(row: any) {\n  if (GenerateRelatedDialogRef.value) {\n    GenerateRelatedDialogRef.value.open([row.id], 'paragraph', row.id)\n  }\n}\nfunction deleteParagraph(row: any) {\n  MsgConfirm(\n    `${t('views.paragraph.delete.confirmTitle')} ${row.title || '-'} ?`,\n    t('views.paragraph.delete.confirmMessage'),\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loadSharedApi({ type: 'paragraph', systemType: apiType.value })\n        .delParagraph(id, documentId, row.id, loading)\n        .then(() => {\n          emit('deleteParagraph', row.id)\n          MsgSuccess(t('common.deleteSuccess'))\n        })\n    })\n    .catch(() => {})\n}\n\nconst ParagraphDialogRef = ref()\nconst title = ref('')\nfunction editParagraph(row: any) {\n  if (!props.disabled) {\n    title.value = t('views.paragraph.editParagraph')\n    ParagraphDialogRef.value.open(row, 'edit')\n  }\n}\n\nconst cardClick = permissionPrecise.value.doc_edit(id)\n\nfunction handleClickCard(row: any) {\n  if (!cardClick || dialogVisible.value) {\n    return\n  }\n  if (!props.disabled) {\n    title.value = t('views.paragraph.paragraphDetail')\n    ParagraphDialogRef.value.open(row)\n  } else {\n    emit('clickCard')\n  }\n}\n\nfunction addParagraph(row: any) {\n  title.value = t('views.paragraph.addParagraph')\n  ParagraphDialogRef.value.open(row, 'add')\n}\n\nconst SelectDocumentDialogRef = ref()\nfunction openSelectDocumentDialog(row?: any) {\n  SelectDocumentDialogRef.value.open([row.id])\n}\n\nfunction refresh(data?: any) {\n  emit('refresh', data)\n}\n\nfunction refreshMigrateParagraph() {\n  emit('refreshMigrateParagraph', props.data)\n}\n\nconst dialogVisible = computed(\n  () =>\n    ParagraphDialogRef.value?.dialogVisible ||\n    SelectDocumentDialogRef.value?.dialogVisible ||\n    GenerateRelatedDialogRef.value?.dialogVisible,\n)\n\nwatch(dialogVisible, (val: boolean) => {\n  emit('dialogVisibleChange', val)\n})\n</script>\n<style lang=\"scss\" scoped>\n.paragraph-box {\n  background: var(--app-layout-bg-color);\n  border: 1px solid #ffffff;\n  box-shadow: none !important;\n  position: relative;\n  overflow: inherit;\n  &:hover {\n    background: rgba(var(--el-text-color-primary-rgb), 0.1);\n    border: 1px solid #dee0e3;\n  }\n  &.disabled {\n    color: var(--app-text-color-disable) !important;\n    :deep(.md-editor-preview) {\n      color: var(--app-text-color-disable) !important;\n    }\n    &:hover {\n      background: var(--app-layout-bg-color);\n      border: 1px solid #ffffff;\n    }\n  }\n  .paragraph-box-operation {\n    position: absolute;\n    right: -10px;\n    top: -10px;\n    overflow: inherit;\n    border: 1px solid #dee0e3;\n    z-index: 10;\n  }\n\n  .mk-sticky {\n    height: 0;\n    position: sticky;\n    right: 0;\n    top: 0;\n    overflow: inherit;\n    z-index: 10;\n  }\n}\n</style>\n\n<style lang=\"scss\">\n.move-position-popper {\n  .el-popper__arrow {\n    display: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/paragraph/component/ParagraphDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"title\"\n    v-model=\"dialogVisible\"\n    width=\"80%\"\n    class=\"paragraph-dialog\"\n    destroy-on-close\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    append-to-body\n  >\n    <el-row v-loading=\"loading\">\n      <el-col :span=\"18\">\n        <el-scrollbar height=\"500\" wrap-class=\"paragraph-scrollbar\">\n          <div class=\"p-24\" style=\"padding-bottom: 8px\">\n            <div style=\"position: absolute; right: 20px; top: 20px\">\n              <el-button text @click=\"isEdit = true\" v-if=\"paragraphId && !isEdit\">\n                <AppIcon iconName=\"app-edit\"></AppIcon>\n              </el-button>\n            </div>\n\n            <ParagraphForm\n              ref=\"paragraphFormRef\"\n              :data=\"detail\"\n              :isEdit=\"isEdit\"\n              :knowledge-id=\"id\"\n            />\n          </div>\n        </el-scrollbar>\n        <div class=\"text-right p-24 pt-0\" v-if=\"paragraphId && isEdit\">\n          <el-button @click.prevent=\"cancelEdit\"> {{ $t('common.cancel') }} </el-button>\n          <el-button type=\"primary\" :disabled=\"loading\" @click=\"handleDebounceClick\">\n            {{ $t('common.save') }}\n          </el-button>\n        </div>\n      </el-col>\n      <el-col :span=\"6\" class=\"border-l\" style=\"width: 300px\">\n        <!-- 关联问题 -->\n        <ProblemComponent\n          v-if=\"permissionPrecise.problem_read(id)\"\n          :paragraphId=\"paragraphId\"\n          :docId=\"document_id\"\n          :knowledgeId=\"id\"\n          :apiType=\"apiType\"\n          ref=\"ProblemRef\"\n        />\n      </el-col>\n    </el-row>\n    <template #footer v-if=\"!paragraphId\">\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button :disabled=\"loading\" type=\"primary\" @click=\"handleDebounceClick\">\n          {{ $t('common.submit') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, nextTick, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { cloneDeep, debounce } from 'lodash'\nimport ParagraphForm from '@/views/paragraph/component/ParagraphForm.vue'\nimport ProblemComponent from '@/views/paragraph/component/ProblemComponent.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\n\nconst props = defineProps<{\n  title: string\n  apiType: 'systemShare' | 'workspace' | 'systemManage'\n}>()\n\nconst route = useRoute()\nconst {\n  params: { id, documentId },\n} = route as any\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][props.apiType]\n})\n\nconst emit = defineEmits(['refresh'])\n\nconst ProblemRef = ref()\nconst paragraphFormRef = ref<any>()\n\nconst dialogVisible = ref<boolean>(false)\n\nconst loading = ref(false)\nconst paragraphId = ref('')\nconst detail = ref<any>({})\nconst isEdit = ref(false)\nconst document_id = ref('')\nconst dataset_id = ref('')\nconst cloneData = ref(null)\nconst position = ref(null)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    paragraphId.value = ''\n    detail.value = {}\n    isEdit.value = false\n    document_id.value = ''\n    dataset_id.value = ''\n    cloneData.value = null\n  }\n})\n\nconst cancelEdit = () => {\n  isEdit.value = false\n  detail.value = cloneDeep(cloneData.value)\n}\n\nconst open = (data: any, str: any) => {\n  if (data && str === 'add') {\n    isEdit.value = true\n    position.value = data.position\n  } else if (data) {\n    detail.value.title = data.title\n    detail.value.content = data.content\n    cloneData.value = cloneDeep(detail.value)\n    paragraphId.value = data.id\n    document_id.value = data.document_id\n    dataset_id.value = data.dataset_id || id\n    if (str === 'edit') {\n      isEdit.value = true\n    } else {\n      isEdit.value = false\n    }\n  } else {\n    isEdit.value = true\n  }\n  dialogVisible.value = true\n}\nconst submitHandle = async () => {\n  if (await paragraphFormRef.value?.validate()) {\n    loading.value = true\n    if (paragraphId.value) {\n      loadSharedApi({ type: 'paragraph', systemType: props.apiType })\n        .putParagraph(\n          dataset_id.value,\n          documentId || document_id.value,\n          paragraphId.value,\n          paragraphFormRef.value?.form,\n          loading,\n        )\n        .then((res: any) => {\n          isEdit.value = false\n          emit('refresh', res.data)\n        })\n    } else {\n      const obj =\n        ProblemRef.value.problemList.length > 0\n          ? {\n              position: String(position.value) ? position.value : null,\n              problem_list: ProblemRef.value.problemList,\n              ...paragraphFormRef.value?.form,\n            }\n          : {\n              position: String(position.value) ? position.value : null,\n              ...paragraphFormRef.value?.form,\n            }\n      loadSharedApi({ type: 'paragraph', systemType: props.apiType })\n        .postParagraph(id, documentId, obj, loading)\n        .then(() => {\n          dialogVisible.value = false\n          emit('refresh')\n        })\n    }\n  }\n}\nconst handleDebounceClick = debounce(() => {\n  submitHandle()\n}, 200)\n\ndefineExpose({ open, dialogVisible })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/paragraph/component/ParagraphForm.vue",
    "content": "<template>\n  <el-form\n    ref=\"paragraphFormRef\"\n    :model=\"form\"\n    label-position=\"top\"\n    require-asterisk-position=\"right\"\n    :rules=\"rules\"\n    @submit.prevent\n  >\n    <el-form-item :label=\"$t('views.paragraph.form.paragraphTitle.label')\">\n      <el-input\n        v-if=\"isEdit\"\n        v-model=\"form.title\"\n        :placeholder=\"$t('views.paragraph.form.paragraphTitle.placeholder')\"\n        maxlength=\"256\"\n        show-word-limit\n      >\n      </el-input>\n      <span class=\"lighter\" v-else>{{ form.title || '-' }}</span>\n    </el-form-item>\n    <el-form-item :label=\"$t('views.paragraph.form.content.label')\" prop=\"content\">\n      <MdEditor\n        v-if=\"isEdit\"\n        v-model=\"form.content\"\n        :placeholder=\"$t('views.paragraph.form.content.placeholder')\"\n        :maxLength=\"100000\"\n        :preview=\"false\"\n        :toolbars=\"toolbars\"\n        style=\"height: 300px\"\n        @onUploadImg=\"onUploadImg\"\n        :footers=\"footers\"\n      >\n        <template #defFooters>\n          <span style=\"margin-left: -6px\">/ 100000</span>\n        </template>\n      </MdEditor>\n      <MdPreview\n        v-else\n        ref=\"editorRef\"\n        editorId=\"preview-only\"\n        :modelValue=\"form.content\"\n        class=\"maxkb-md\"\n      />\n    </el-form-item>\n  </el-form>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, onUnmounted, watch } from 'vue'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport imageApi from '@/api/image'\nimport { t } from '@/locales'\nconst props = defineProps({\n  data: {\n    type: Object,\n    default: () => {}\n  },\n  isEdit: Boolean,\n  knowledgeId: String\n})\n\nconst toolbars = [\n  'bold',\n  'underline',\n  'italic',\n  '-',\n  'title',\n  'strikeThrough',\n  'sub',\n  'sup',\n  'quote',\n  'unorderedList',\n  'orderedList',\n  'task',\n  '-',\n  'codeRow',\n  'code',\n  'link',\n  'image',\n  'table',\n  'mermaid',\n  'katex',\n  '-',\n  'revoke',\n  'next',\n  '=',\n  'pageFullscreen',\n  'preview',\n  'htmlPreview'\n] as any[]\n\nconst footers = ['markdownTotal', 0, '=', 1, 'scrollSwitch']\n\nconst editorRef = ref()\n\nconst form = ref<any>({\n  title: '',\n  content: ''\n})\n\nconst rules = reactive<FormRules>({\n  content: [\n    { required: true, message: t('views.paragraph.form.content.requiredMessage1'), trigger: 'blur' },\n    { max: 100000, message: t('views.paragraph.form.content.requiredMessage2'), trigger: 'blur' }\n  ]\n})\n\nconst paragraphFormRef = ref<FormInstance>()\n\nwatch(\n  () => props.data,\n  (value) => {\n    if (value && JSON.stringify(value) !== '{}') {\n      form.value.title = value.title\n      form.value.content = value.content\n    }\n  },\n  {\n    immediate: true\n  }\n)\nwatch(\n  () => props.isEdit,\n  (value) => {\n    if (!value) {\n      paragraphFormRef.value?.clearValidate()\n    }\n  },\n  {\n    immediate: true\n  }\n)\n\n/*\n  表单校验\n*/\nfunction validate() {\n  if (!paragraphFormRef.value) return\n  return paragraphFormRef.value.validate((valid: any) => {\n    return valid\n  })\n}\n\nconst onUploadImg = async (files: any, callback: any) => {\n  const res = await Promise.all(\n    files.map((file: any) => {\n      return new Promise((rev, rej) => {\n        const fd = new FormData()\n        fd.append('file', file)\n        fd.append('source_id', props.knowledgeId as string)\n        fd.append('source_type', 'KNOWLEDGE')\n\n        imageApi\n          .postImage(fd)\n          .then((res: any) => {\n            rev(res)\n          })\n          .catch((error) => rej(error))\n      })\n    })\n  )\n\n  callback(res.map((item) => item.data))\n}\n\nonUnmounted(() => {\n  form.value = {\n    title: '',\n    content: ''\n  }\n})\n\ndefineExpose({\n  validate,\n  form\n})\n</script>\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/paragraph/component/ProblemComponent.vue",
    "content": "<template>\n  <p class=\"bold title p-24\" style=\"padding-bottom: 0\">\n    <span class=\"flex align-center\">\n      <span>{{ $t('views.paragraph.relatedProblem.title') }}</span>\n      <el-divider direction=\"vertical\" class=\"mr-4\" />\n      <el-button text @click=\"addProblem\" v-if=\"permissionPrecise.problem_relate(id)\">\n        <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n      </el-button>\n    </span>\n  </p>\n  <div v-loading=\"loading\">\n    <el-scrollbar height=\"500px\">\n      <div class=\"p-24\" style=\"padding-top: 16px\">\n        <el-select\n          v-if=\"isAddProblem\"\n          v-model=\"problemValue\"\n          filterable\n          allow-create\n          default-first-option\n          :reserve-keyword=\"false\"\n          :placeholder=\"$t('views.paragraph.relatedProblem.placeholder')\"\n          remote\n          :remote-method=\"remoteMethod\"\n          :loading=\"optionLoading\"\n          @change=\"addProblemHandle\"\n          @blur=\"isAddProblem = false\"\n          class=\"mb-16\"\n          popper-class=\"select-popper\"\n          :popper-append-to-body=\"false\"\n        >\n          <el-option\n            v-for=\"item in problemOptions\"\n            :key=\"item.id\"\n            :label=\"item.content\"\n            :value=\"item.id\"\n          >\n            <span class=\"ellipsis\" :title=\"item.content\" style=\"max-width: 255px\" >\n              {{ item.content }}\n            </span>\n          </el-option>\n        </el-select>\n        <template v-for=\"(item, index) in problemList\" :key=\"index\">\n          <TagEllipsis\n            @close=\"delProblemHandle(item, index)\"\n            class=\"question-tag\"\n            type=\"info\"\n            effect=\"plain\"\n            v-bind=\"permissionPrecise.problem_relate(id) ? { closable: true } : {}\"\n          >\n            <auto-tooltip :content=\"item.content\">\n              {{ item.content }}\n            </auto-tooltip>\n          </TagEllipsis>\n        </template>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, nextTick, onMounted, onUnmounted, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\n\nconst props = defineProps<{\n  paragraphId: string\n  docId: string\n  knowledgeId: string\n  apiType: 'systemShare' | 'workspace' | 'systemManage'\n}>()\n\nconst route = useRoute()\nconst {\n  params: { id, documentId }, // id为knowledgeId\n} = route as any\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][props.apiType]\n})\n\nconst inputRef = ref()\nconst loading = ref(false)\nconst isAddProblem = ref(false)\n\nconst problemValue = ref('')\nconst problemList = ref<any[]>([])\n\nconst problemOptions = ref<any[]>([])\nconst optionLoading = ref(false)\n\nwatch(\n  () => props.paragraphId,\n  (value) => {\n    if (value) {\n      getProblemList()\n    }\n  },\n  {\n    immediate: true,\n  },\n)\n\nfunction delProblemHandle(item: any, index: number) {\n  if (item.id) {\n    const obj = {\n      paragraph_id: props.paragraphId || '',\n      problem_id: item.id,\n    }\n    loadSharedApi({ type: 'paragraph', systemType: props.apiType })\n      .putDisassociationProblem(props.knowledgeId || id, documentId || props.docId, obj, loading)\n      .then((res: any) => {\n        getProblemList()\n      })\n  } else {\n    problemList.value.splice(index, 1)\n  }\n}\n\nfunction getProblemList() {\n  loading.value = true\n  loadSharedApi({ type: 'paragraph', systemType: props.apiType })\n    .getParagraphProblem(\n      props.knowledgeId || id,\n      documentId || props.docId,\n      props.paragraphId || '',\n    )\n    .then((res: any) => {\n      problemList.value = res.data\n      loading.value = false\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nfunction addProblem() {\n  isAddProblem.value = true\n  nextTick(() => {\n    inputRef.value?.focus()\n  })\n}\nfunction addProblemHandle(val: string) {\n  if (props.paragraphId) {\n    const obj = {\n      paragraph_id: props.paragraphId,\n      problem_id: val,\n    }\n    const api = problemOptions.value.some((option) => option.id === val)\n      ? loadSharedApi({ type: 'paragraph', systemType: props.apiType }).putAssociationProblem(\n          props.knowledgeId || id,\n          documentId || props.docId,\n          obj,\n          loading,\n        )\n      : loadSharedApi({ type: 'paragraph', systemType: props.apiType }).postParagraphProblem(\n          props.knowledgeId || id,\n          documentId || props.docId,\n          props.paragraphId,\n          {\n            content: val,\n          },\n          loading,\n        )\n    api.then(() => {\n      getProblemList()\n      problemValue.value = ''\n      isAddProblem.value = false\n    })\n  } else {\n    const problem = problemOptions.value.find((option) => option.id === val)\n    const content = problem ? problem.content : val\n    if (!problemList.value.some((item) => item.content === content)) {\n      problemList.value.push({ content: content })\n    }\n\n    problemValue.value = ''\n    isAddProblem.value = false\n  }\n}\n\nconst remoteMethod = (query: string) => {\n  getProblemOption(query)\n}\n\nfunction getProblemOption(filterText?: string) {\n  return loadSharedApi({ type: 'problem', systemType: props.apiType })\n    .getProblemsPage(\n      props.knowledgeId || (id as string),\n      { current_page: 1, page_size: 100 },\n      filterText && { content: filterText },\n      optionLoading,\n    )\n    .then((res: any) => {\n      problemOptions.value = res.data.records\n    })\n}\n\nonMounted(() => {\n  getProblemOption()\n})\nonUnmounted(() => {\n  problemList.value = []\n  problemValue.value = ''\n  isAddProblem.value = false\n})\n\ndefineExpose({\n  problemList,\n})\n</script>\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/paragraph/component/SelectDocumentDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"`${$t('views.chatLog.selectKnowledge')}/${$t('common.fileUpload.document')}`\"\n    v-model=\"dialogVisible\"\n    width=\"500\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    @click.stop\n  >\n    <SelectKnowledgeDocument\n      ref=\"SelectKnowledgeDocumentRef\"\n      :apiType=\"apiType\"\n      @changeKnowledge=\"changeKnowledge\"\n      @changeDocument=\"changeDocument\"\n      :isApplication=\"true\"\n      :workspace-id=\"knowledgeDetail.workspace_id\"\n    />\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submitForm\" :loading=\"loading\">\n          {{ $t('views.document.setting.migration') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive, computed, onMounted } from 'vue'\nimport { useRoute } from 'vue-router'\nimport SelectKnowledgeDocument from '@/components/select-knowledge-document/index.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst props = defineProps<{\n  apiType: 'systemShare' | 'workspace' | 'systemManage'\n}>()\nconst route = useRoute()\nconst {\n  params: { id, documentId }, // id为knowledgeID\n  query: { from, isShared },\n} = route as any\n\nconst shareDisabled = computed(() => {\n  return isShared === 'true'\n})\n\nconst emit = defineEmits(['refresh'])\nconst SelectKnowledgeDocumentRef = ref()\nconst knowledgeDetail = ref<any>({})\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\n\nconst paragraphList = ref<string[]>([])\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    paragraphList.value = []\n    SelectKnowledgeDocumentRef.value?.clearValidate()\n  }\n})\n\nconst open = (list: any) => {\n  getDetail()\n  paragraphList.value = list\n  dialogVisible.value = true\n}\nconst submitForm = async () => {\n  if (await SelectKnowledgeDocumentRef.value?.validate()) {\n    const obj = {\n      id_list: paragraphList.value,\n    }\n    loadSharedApi({ type: 'paragraph', systemType: props.apiType })\n      .putMigrateMulParagraph(\n        id,\n        documentId,\n        SelectKnowledgeDocumentRef.value.form.knowledge_id,\n        SelectKnowledgeDocumentRef.value.form.document_id,\n        obj,\n        loading,\n      )\n      .then(() => {\n        emit('refresh')\n        dialogVisible.value = false\n      })\n  }\n}\n\nfunction getDetail() {\n  loadSharedApi({ type: 'knowledge', systemType: props.apiType, isShared: shareDisabled.value })\n    .getKnowledgeDetail(id, loading)\n    .then((res: any) => {\n      knowledgeDetail.value = res.data\n    })\n}\n\nfunction changeKnowledge(dataset_id: string) {\n  localStorage.setItem(id + 'chat_dataset_id', dataset_id)\n}\n\nfunction changeDocument(document_id: string) {\n  localStorage.setItem(id + 'chat_document_id', document_id)\n}\n\ndefineExpose({ open, dialogVisible })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/paragraph/index.vue",
    "content": "<template>\n  <div class=\"paragraph p-12-24\">\n    <div class=\"flex align-center\" style=\"width: 78%\">\n      <back-button to=\"-1\" style=\"margin-left: -4px\"></back-button>\n      <h3 style=\"display: inline-block\">{{ documentDetail?.name }}</h3>\n      <el-text type=\"info\" v-if=\"documentDetail?.type === '1'\"\n        >（{{ $t('views.document.form.source_url.label') }}：<el-link\n          :href=\"documentDetail?.meta?.source_url\"\n          target=\"_blank\"\n        >\n          <span class=\"break-all\">{{ documentDetail?.meta?.source_url }} </span></el-link\n        >）\n      </el-text>\n    </div>\n    <div class=\"header-button\" v-if=\"!shareDisabled && permissionPrecise.doc_edit(id)\">\n      <el-button @click=\"batchSelectedHandle(true)\" v-if=\"isBatch === false\">\n        {{ $t('views.paragraph.setting.batchSelected') }}\n      </el-button>\n      <el-button @click=\"batchSelectedHandle(false)\" v-if=\"isBatch === true\">\n        {{ $t('views.paragraph.setting.cancelSelected') }}\n      </el-button>\n      <el-button @click=\"addParagraph\" type=\"primary\" :disabled=\"loading\" v-if=\"isBatch === false\">\n        {{ $t('views.paragraph.addParagraph') }}\n      </el-button>\n    </div>\n    <el-card\n      style=\"--el-card-padding: 0\"\n      class=\"paragraph__main mt-16\"\n      v-loading=\"(paginationConfig.current_page === 1 && loading) || changeStateloading\"\n    >\n      <div class=\"flex-between p-12-16 border-b\">\n        <span>{{ paginationConfig.total }} {{ $t('views.paragraph.paragraph_count') }}</span>\n        <el-input\n          v-model=\"search\"\n          :placeholder=\"$t('common.search')\"\n          class=\"input-with-select\"\n          style=\"width: 260px\"\n          @change=\"searchHandle\"\n          clearable\n        >\n          <template #prepend>\n            <el-select\n              v-model=\"searchType\"\n              placeholder=\"Select\"\n              style=\"width: 80px\"\n              @change=\"searchTypeChange\"\n            >\n              <el-option :label=\"$t('common.title')\" value=\"title\" />\n              <el-option :label=\"$t('common.content')\" value=\"content\" />\n            </el-select>\n          </template>\n        </el-input>\n      </div>\n      <LayoutContainer showCollapse>\n        <template #left>\n          <div class=\"paragraph-sidebar p-16\">\n            <el-scrollbar class=\"paragraph-scrollbar\">\n              <el-anchor\n                direction=\"vertical\"\n                type=\"default\"\n                :offset=\"130\"\n                container=\".paragraph-scrollbar\"\n                @click=\"handleClick\"\n              >\n                <template v-for=\"item in paragraphDetail\" :key=\"item.id\">\n                  <el-anchor-link :href=\"`#m${item.id}`\" :title=\"item.title\" v-if=\"item.title\">\n                    <span :title=\"item.title\">\n                      {{ item.title }}\n                    </span>\n                  </el-anchor-link>\n                </template>\n              </el-anchor>\n            </el-scrollbar>\n          </div>\n        </template>\n        <div class=\"w-full\">\n          <el-empty v-if=\"paragraphDetail.length == 0\" :description=\"$t('common.noData')\" />\n          <div v-else>\n            <el-scrollbar class=\"paragraph-scrollbar\">\n              <div class=\"paragraph-detail\">\n                <el-checkbox-group v-model=\"multipleSelection\">\n                  <InfiniteScroll\n                    :size=\"paragraphDetail.length\"\n                    :total=\"paginationConfig.total\"\n                    :page_size=\"paginationConfig.page_size\"\n                    v-model:current_page=\"paginationConfig.current_page\"\n                    @load=\"getParagraphList\"\n                    :loading=\"loading\"\n                  >\n                    <VueDraggable\n                      ref=\"el\"\n                      v-model=\"paragraphDetail\"\n                      :disabled=\"\n                        isBatch === true ||\n                        shareDisabled ||\n                        dialogVisible ||\n                        !permissionPrecise.doc_edit(id)\n                      \"\n                      handle=\".handle\"\n                      :animation=\"150\"\n                      ghostClass=\"ghost\"\n                      @end=\"onEnd\"\n                    >\n                      <template v-for=\"(item, index) in paragraphDetail\" :key=\"item.id\">\n                        <div :id=\"`m${item.id}`\" class=\"flex mb-16\">\n                          <!-- 批量操作 -->\n                          <div class=\"paragraph-card flex w-full\" v-if=\"isBatch === true\">\n                            <el-checkbox :value=\"item.id\" />\n                            <ParagraphCard\n                              :data=\"item\"\n                              class=\"mb-8 w-full\"\n                              :class=\"{\n                                'is-selected': multipleSelection.includes(item.id),\n                              }\"\n                              :disabled=\"true\"\n                              @clickCard=\"toggleSelect(item.id)\"\n                            />\n                          </div>\n                          <!-- 非批量操作 -->\n                          <div class=\"handle paragraph-card flex w-full\" :id=\"item.id\" v-else>\n                            <img\n                              src=\"@/assets/sort.svg\"\n                              alt=\"\"\n                              height=\"15\"\n                              class=\"handle-img mr-8 mt-24 cursor\"\n                            />\n\n                            <ParagraphCard\n                              :data=\"item\"\n                              :showMoveUp=\"index !== 0\"\n                              :showMoveDown=\"index < paragraphDetail.length - 1\"\n                              class=\"mb-8 w-full\"\n                              @changeState=\"changeState\"\n                              @deleteParagraph=\"deleteParagraph\"\n                              @move=\"\n                                (val: string) =>\n                                  onEnd(\n                                    null,\n                                    {\n                                      paragraph_id: item.id,\n                                      new_position: setPosition(val, index),\n                                    },\n                                    index,\n                                  )\n                              \"\n                              @refresh=\"refresh\"\n                              @refreshMigrateParagraph=\"refreshMigrateParagraph\"\n                              :disabled=\"shareDisabled\"\n                              @dialogVisibleChange=\"dialogVisibleChange\"\n                            />\n                          </div>\n                        </div>\n                      </template>\n                    </VueDraggable>\n                  </InfiniteScroll>\n                </el-checkbox-group>\n              </div>\n            </el-scrollbar>\n          </div>\n        </div>\n      </LayoutContainer>\n\n      <div class=\"mul-operation border-t w-full flex align-center\" v-if=\"isBatch === true\">\n        <el-button :disabled=\"multipleSelection.length === 0\" @click=\"openGenerateDialog()\">\n          {{ $t('views.document.generateQuestion.title') }}\n        </el-button>\n        <el-button :disabled=\"multipleSelection.length === 0\" @click=\"openSelectDocumentDialog()\">\n          {{ $t('views.document.setting.migration') }}\n        </el-button>\n\n        <el-button :disabled=\"multipleSelection.length === 0\" @click=\"deleteMulParagraph\">\n          {{ $t('common.delete') }}\n        </el-button>\n        <span class=\"color-secondary ml-24 mr-16\">\n          {{ $t('common.selected') }} {{ multipleSelection.length }}\n          {{ $t('views.document.items') }}\n        </span>\n        <el-button\n          link\n          type=\"primary\"\n          v-if=\"multipleSelection.length > 0\"\n          @click=\"multipleSelection = []\"\n        >\n          {{ $t('common.clear') }}\n        </el-button>\n      </div>\n    </el-card>\n    <ParagraphDialog\n      ref=\"ParagraphDialogRef\"\n      :title=\"title\"\n      :apiType=\"apiType\"\n      @refresh=\"refresh\"\n    />\n    <SelectDocumentDialog\n      ref=\"SelectDocumentDialogRef\"\n      @refresh=\"refreshMigrateParagraph\"\n      :apiType=\"apiType\"\n      :workspaceId=\"knowledgeDetail.workspace_id\"\n    />\n    <GenerateRelatedDialog ref=\"GenerateRelatedDialogRef\" @refresh=\"refresh\" :apiType=\"apiType\" />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, onMounted, computed, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport ParagraphDialog from './component/ParagraphDialog.vue'\nimport ParagraphCard from './component/ParagraphCard.vue'\nimport SelectDocumentDialog from './component/SelectDocumentDialog.vue'\nimport GenerateRelatedDialog from '@/components/generate-related-dialog/index.vue'\nimport { VueDraggable } from 'vue-draggable-plus'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\nimport { t } from '@/locales'\nimport { cloneDeep } from 'lodash'\nconst route = useRoute()\nconst {\n  params: { id, documentId },\n  query: { from, isShared },\n} = route as any\n\nconst apiType = computed(() => {\n  return from as 'systemShare' | 'workspace' | 'systemManage'\n})\nconst shareDisabled = computed(() => {\n  return isShared === 'true'\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst SelectDocumentDialogRef = ref()\nconst ParagraphDialogRef = ref()\nconst loading = ref(false)\nconst changeStateloading = ref(false)\nconst documentDetail = ref<any>({})\nconst knowledgeDetail = ref<any>({})\nconst paragraphDetail = ref<any[]>([])\nconst title = ref('')\nconst search = ref('')\nconst searchType = ref('title')\n\nconst searchTypeChange = () => {\n  search.value = ''\n}\n\nconst dialogVisible = ref(false)\nwatch(\n  () => ParagraphDialogRef.value?.dialogVisible,\n  (val: boolean) => {\n    dialogVisible.value = val\n  },\n)\n\nfunction setPosition(val: string, index: number) {\n  if (val === 'top') {\n    return 0\n  } else if (val === 'bottom') {\n    return paginationConfig.total - 1\n  } else if (val === 'up') {\n    return index - 1\n  } else if (val === 'down') {\n    return index + 1\n  }\n}\nfunction dialogVisibleChange(val: boolean) {\n  dialogVisible.value = val\n}\n\nconst handleClick = (e: MouseEvent, ele: any) => {\n  e.preventDefault()\n  document.querySelector(`${ele}`)?.scrollIntoView({ behavior: 'smooth', block: 'start' })\n}\n\n// 批量操作\nconst isBatch = ref(false)\nconst multipleSelection = ref<any[]>([])\n\nfunction toggleSelect(id: number) {\n  const index = multipleSelection.value.indexOf(id)\n  if (index === -1) {\n    multipleSelection.value.push(id)\n  } else {\n    multipleSelection.value.splice(index, 1)\n  }\n}\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 30,\n  total: 0,\n})\n\nfunction deleteParagraph(id: string) {\n  const index = paragraphDetail.value.findIndex((v) => v.id === id)\n  paragraphDetail.value.splice(index, 1)\n}\n\nfunction changeState(id: string) {\n  const index = paragraphDetail.value.findIndex((v) => v.id === id)\n  paragraphDetail.value[index].is_active = !paragraphDetail.value[index].is_active\n}\n\nfunction refreshMigrateParagraph(data: any) {\n  if (data) {\n    multipleSelection.value = [data.id]\n  }\n  paragraphDetail.value = paragraphDetail.value.filter(\n    (v) => !multipleSelection.value.includes(v.id),\n  )\n  multipleSelection.value = []\n  MsgSuccess(t('views.document.tip.migrationSuccess'))\n}\n\nfunction openSelectDocumentDialog(row?: any) {\n  if (row) {\n    multipleSelection.value = [row.id]\n  }\n  SelectDocumentDialogRef.value.open(multipleSelection.value)\n}\nfunction deleteMulParagraph() {\n  MsgConfirm(\n    `${t('views.document.delete.confirmTitle1')} ${multipleSelection.value.length} ${t('views.document.delete.confirmTitle2')}`,\n    t('views.paragraph.delete.confirmMessage'),\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loadSharedApi({ type: 'paragraph', systemType: apiType.value })\n        .putMulParagraph(id, documentId, multipleSelection.value, changeStateloading)\n        .then(() => {\n          paragraphDetail.value = paragraphDetail.value.filter(\n            (v) => !multipleSelection.value.includes(v.id),\n          )\n          multipleSelection.value = []\n          MsgSuccess(t('views.document.delete.successMessage'))\n        })\n    })\n    .catch(() => {})\n}\n\nfunction batchSelectedHandle(bool: boolean) {\n  isBatch.value = bool\n  multipleSelection.value = []\n}\n\nfunction searchHandle() {\n  paginationConfig.current_page = 1\n  paragraphDetail.value = []\n  getParagraphList()\n}\n\nfunction addParagraph() {\n  title.value = t('views.paragraph.addParagraph')\n  ParagraphDialogRef.value.open()\n}\n\nfunction getDetail() {\n  loadSharedApi({ type: 'document', isShared: shareDisabled.value, systemType: apiType.value })\n    .getDocumentDetail(id, documentId, loading)\n    .then((res: any) => {\n      documentDetail.value = res.data\n    })\n\n  loadSharedApi({ type: 'knowledge', isShared: shareDisabled.value, systemType: apiType.value })\n    .getKnowledgeDetail(id, loading)\n    .then((res: any) => {\n      knowledgeDetail.value = res.data\n    })\n}\n\nfunction getParagraphList() {\n  loadSharedApi({ type: 'paragraph', isShared: shareDisabled.value, systemType: apiType.value })\n    .getParagraphPage(\n      id,\n      documentId,\n      paginationConfig,\n      search.value && { [searchType.value]: search.value },\n      loading,\n    )\n    .then((res: any) => {\n      paragraphDetail.value = [...paragraphDetail.value, ...res.data.records]\n      paginationConfig.total = res.data.total\n    })\n}\n\nfunction refresh(data: any) {\n  if (data) {\n    const index = paragraphDetail.value.findIndex((v) => v.id === data.id)\n    paragraphDetail.value.splice(index, 1, data)\n  } else {\n    paginationConfig.current_page = 1\n    paragraphDetail.value = []\n    getParagraphList()\n  }\n}\n\nconst GenerateRelatedDialogRef = ref()\nfunction openGenerateDialog(row?: any) {\n  const arr: string[] = []\n  if (row) {\n    arr.push(row.id)\n  } else {\n    multipleSelection.value.map((v) => {\n      if (v) {\n        arr.push(v)\n      }\n    })\n  }\n\n  GenerateRelatedDialogRef.value.open(arr, 'paragraph')\n}\n\nfunction onEnd(event?: any, params?: any, index?: number) {\n  // console.log('onEnd', event, params, index)\n  const p = cloneDeep(params)\n  if (p) {\n    p.new_position = p.new_position + 1 // 由于拖拽时会将当前段落位置作为新位置，所以需要加1\n  }\n  const obj = p ?? {\n    paragraph_id: paragraphDetail.value[event.newIndex].id, // 当前拖动的段落ID\n    new_position:\n      paragraphDetail.value[event.newIndex + 1]?.position || paragraphDetail.value.length, // 新位置的段落位置\n  }\n  // console.log(paragraphDetail.value[event.newIndex], obj)\n  loadSharedApi({ type: 'paragraph', systemType: apiType.value }).putAdjustPosition(\n    id,\n    documentId,\n    obj,\n    loading,\n  )\n  if (params) {\n    const movedItem = paragraphDetail.value.splice(index as number, 1)[0]\n    paragraphDetail.value.splice(params.new_position, 0, movedItem)\n  }\n}\n\nonMounted(() => {\n  getDetail()\n  getParagraphList()\n})\n</script>\n<style lang=\"scss\" scoped>\n.paragraph {\n  position: relative;\n  .header-button {\n    position: absolute;\n    right: calc(var(--app-base-px) * 3);\n    top: calc(var(--app-base-px) + 4px);\n  }\n  .paragraph-sidebar {\n    width: 100%;\n    height: calc(100vh - 215px);\n    box-sizing: border-box;\n  }\n\n  .paragraph-detail {\n    height: calc(100vh - 215px);\n    max-width: 1000px;\n    margin: 16px auto;\n\n    .el-checkbox-group {\n      font-size: inherit;\n      line-height: inherit;\n    }\n  }\n\n  &__main {\n    position: relative;\n    box-sizing: border-box;\n    .mul-operation {\n      position: absolute;\n      bottom: 0;\n      left: 0;\n      padding: 16px 24px;\n      box-sizing: border-box;\n      background: #ffffff;\n    }\n  }\n  .paragraph-card {\n    .is-selected {\n      border: 1px solid var(--el-color-primary);\n    }\n    &.handle {\n      .handle-img {\n        visibility: hidden;\n      }\n      &:hover {\n        .handle-img {\n          visibility: visible;\n        }\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/problem/component/CreateProblemDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.problem.createProblem')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"problemFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item :label=\"$t('views.problem.title')\" prop=\"data\">\n        <el-input\n          v-model=\"form.data\"\n          :placeholder=\"$t('views.problem.tip.placeholder')\"\n          :rows=\"10\"\n          type=\"textarea\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(problemFormRef)\" :loading=\"loading\">\n          {{ $t('common.confirm') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst emit = defineEmits(['refresh'])\nconst problemFormRef = ref()\nconst loading = ref<boolean>(false)\n\nconst form = ref<any>({\n  data: '',\n})\n\nconst rules = reactive({\n  data: [{ required: true, message: t('views.problem.tip.requiredMessage'), trigger: 'blur' }],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      data: '',\n    }\n  }\n})\n\nconst open = () => {\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      const arr = form.value.data.split('\\n').filter(function (item: string) {\n        return item !== ''\n      })\n      loadSharedApi({ type: 'problem', systemType: apiType.value })\n        .postProblems(id, arr, loading)\n        .then((res: any) => {\n          MsgSuccess(t('common.createSuccess'))\n          emit('refresh')\n          dialogVisible.value = false\n        })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/problem/component/DetailProblemDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"60%\" @close=\"closeHandle\">\n    <template #header>\n      <h4>{{ $t('views.problem.detailProblem') }}</h4>\n    </template>\n    <div>\n      <el-scrollbar>\n        <div class=\"p-8\">\n          <el-form label-position=\"top\" v-loading=\"loading\" @submit.prevent>\n            <el-form-item :label=\"$t('views.problem.title')\">\n              <ReadWrite\n                @change=\"editName\"\n                :data=\"currentContent\"\n                :showEditIcon=\"permissionPrecise.problem_edit(id as string)\"\n                :maxlength=\"256\"\n              />\n            </el-form-item>\n            <el-form-item :label=\"$t('views.problem.relateParagraph.title')\">\n              <template v-for=\"(item, index) in paragraphList\" :key=\"index\">\n                <CardBox\n                  :title=\"item.title || '-'\"\n                  class=\"cursor mb-8 w-full\"\n                  :showIcon=\"false\"\n                  @click.stop=\"permissionPrecise.doc_edit(id as string) && editParagraph(item)\"\n                  style=\"height: 210px\"\n                >\n                  <template #tag>\n                    <el-tooltip\n                      effect=\"dark\"\n                      :content=\"$t('views.problem.setting.cancelRelated')\"\n                      placement=\"top\"\n                    >\n                      <el-button\n                        type=\"primary\"\n                        text\n                        @click.stop=\"disassociation(item)\"\n                        v-if=\"permissionPrecise.problem_relate(id as string)\"\n                      >\n                        <AppIcon iconName=\"app-quxiaoguanlian\"></AppIcon>\n                      </el-button>\n                    </el-tooltip>\n                  </template>\n                  <el-scrollbar height=\"110\">\n                    {{ item.content }}\n                  </el-scrollbar>\n\n                  <template #footer>\n                    <div class=\"footer-content flex-between\">\n                      <el-text>\n                        <el-icon>\n                          <Document />\n                        </el-icon>\n                        {{ item?.document_name }}\n                      </el-text>\n                    </div>\n                  </template>\n                </CardBox>\n              </template>\n            </el-form-item>\n          </el-form>\n        </div>\n      </el-scrollbar>\n      <ParagraphDialog\n        ref=\"ParagraphDialogRef\"\n        :title=\"$t('views.paragraph.editParagraph')\"\n        :apiType=\"apiType\"\n        @refresh=\"refresh\"\n      />\n      <RelateProblemDialog ref=\"RelateProblemDialogRef\" @refresh=\"refresh\" />\n    </div>\n    <template #footer>\n      <div>\n        <el-button @click=\"relateProblem\" v-if=\"permissionPrecise.doc_edit(id as string)\">{{\n          $t('views.problem.relateParagraph.title')\n        }}</el-button>\n        <el-button @click=\"pre\" :disabled=\"pre_disable || loading\">{{\n          $t('common.pages.prev')\n        }}</el-button>\n        <el-button @click=\"next\" :disabled=\"next_disable || loading\">{{\n          $t('common.pages.next')\n        }}</el-button>\n      </div>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive, computed, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue'\nimport RelateProblemDialog from './RelateProblemDialog.vue'\nimport { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\nimport { t } from '@/locales'\nconst props = withDefaults(\n  defineProps<{\n    /**\n     * 当前的id\n     */\n    currentId: string\n    currentContent: string\n    /**\n     * 下一条\n     */\n    next: () => void\n    /**\n     * 上一条\n     */\n    pre: () => void\n\n    pre_disable: boolean\n\n    next_disable: boolean\n  }>(),\n  {},\n)\n\nconst emit = defineEmits(['update:currentId', 'update:currentContent', 'refresh'])\n\nconst route = useRoute()\nconst {\n  params: { id },\n} = route\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst RelateProblemDialogRef = ref()\nconst ParagraphDialogRef = ref()\nconst loading = ref(false)\nconst visible = ref(false)\nconst paragraphList = ref<any[]>([])\n\nfunction disassociation(item: any) {\n  const obj = {\n    paragraph_id: item.id,\n    problem_id: props.currentId,\n  }\n  loadSharedApi({ type: 'paragraph', systemType: apiType.value })\n    .putDisassociationProblem(item.knowledge_id, item.document_id, obj, loading)\n    .then(() => {\n      getRecord()\n    })\n}\n\nfunction relateProblem() {\n  RelateProblemDialogRef.value.open([props.currentId])\n}\n\nfunction editParagraph(row: any) {\n  ParagraphDialogRef.value.open(row, 'edit')\n}\n\nfunction editName(val: string) {\n  if (val) {\n    const obj = {\n      content: val,\n    }\n    loadSharedApi({ type: 'problem', systemType: apiType.value })\n      .putProblems(id as string, props.currentId, obj, loading)\n      .then(() => {\n        emit('update:currentContent', val)\n        MsgSuccess(t('common.modifySuccess'))\n      })\n  } else {\n    MsgError(t('views.problem.tip.errorMessage'))\n  }\n}\n\nfunction closeHandle() {\n  paragraphList.value = []\n}\n\nfunction getRecord() {\n  if (props.currentId && visible.value) {\n    loadSharedApi({ type: 'problem', systemType: apiType.value })\n      .getDetailProblems(id as string, props.currentId, loading)\n      .then((res: any) => {\n        paragraphList.value = res.data\n      })\n  }\n}\n\nfunction refresh() {\n  getRecord()\n}\n\nwatch(\n  () => props.currentId,\n  () => {\n    paragraphList.value = []\n    getRecord()\n  },\n)\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    emit('update:currentId', '')\n    emit('update:currentContent', '')\n    emit('refresh')\n  }\n})\n\nconst open = () => {\n  getRecord()\n  visible.value = true\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/problem/component/RelateProblemDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.problem.relateParagraph.title')\"\n    v-model=\"dialogVisible\"\n    width=\"80%\"\n    class=\"paragraph-dialog\"\n    destroy-on-close\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-row v-loading=\"loading\">\n      <el-col :span=\"6\">\n        <el-scrollbar height=\"500\" wrap-class=\"paragraph-scrollbar\">\n          <div class=\"bold title align-center p-24 pb-0\">\n            {{ $t('views.problem.relateParagraph.selectDocument') }}\n          </div>\n          <div class=\"p-8\" style=\"padding-bottom: 8px\">\n            <el-input\n              v-model=\"filterDoc\"\n              :placeholder=\"$t('views.problem.relateParagraph.placeholder')\"\n              prefix-icon=\"Search\"\n              clearable\n            />\n            <common-list\n              :data=\"documentList\"\n              class=\"mt-8\"\n              @click=\"clickDocumentHandle\"\n              :default-active=\"currentDocument\"\n            >\n              <template #default=\"{ row }\">\n                <span class=\"flex lighter align-center\">\n                  <auto-tooltip :content=\"row.name\">\n                    {{ row.name }}\n                  </auto-tooltip>\n                  <el-badge\n                    :value=\"associationCount(row.id)\"\n                    type=\"primary\"\n                    v-if=\"associationCount(row.id)\"\n                    class=\"paragraph-badge ml-4\"\n                  />\n                </span>\n              </template>\n            </common-list>\n          </div>\n        </el-scrollbar>\n      </el-col>\n      <el-col :span=\"18\" class=\"border-l\">\n        <el-scrollbar height=\"500\" wrap-class=\"paragraph-scrollbar\">\n          <div class=\"p-24\" style=\"padding-bottom: 8px; padding-top: 16px\">\n            <div class=\"flex-between mb-16\">\n              <div class=\"bold title align-center\">\n                {{ $t('components.selectParagraph.title') }}\n                <el-text>\n                  （{{ $t('views.problem.relateParagraph.selectedParagraph') }}：{{\n                    associationCount(currentDocument)\n                  }}\n                  {{ $t('views.problem.relateParagraph.count') }}）\n                </el-text>\n              </div>\n              <el-input\n                v-model=\"search\"\n                :placeholder=\"$t('common.search')\"\n                class=\"input-with-select\"\n                style=\"width: 260px\"\n                @change=\"searchHandle\"\n              >\n                <template #prepend>\n                  <el-select v-model=\"searchType\" placeholder=\"Select\" style=\"width: 80px\">\n                    <el-option :label=\"$t('common.title')\" value=\"title\" />\n                    <el-option :label=\"$t('common.content')\" value=\"content\" />\n                  </el-select>\n                </template>\n              </el-input>\n            </div>\n            <el-empty v-if=\"paragraphList.length == 0\" :description=\"$t('common.noData')\" />\n\n            <InfiniteScroll\n              v-else\n              :size=\"paragraphList.length\"\n              :total=\"paginationConfig.total\"\n              :page_size=\"paginationConfig.page_size\"\n              v-model:current_page=\"paginationConfig.current_page\"\n              @load=\"getParagraphList\"\n              :loading=\"loading\"\n            >\n              <template v-for=\"(item, index) in paragraphList\" :key=\"index\">\n                <CardBox\n                  shadow=\"hover\"\n                  :title=\"item.title || '-'\"\n                  :description=\"item.content\"\n                  class=\"paragraph-card cursor mb-16\"\n                  :class=\"isAssociation(item.id) ? 'selected' : ''\"\n                  :showIcon=\"false\"\n                  @click=\"associationClick(item)\"\n                >\n                </CardBox>\n              </template>\n            </InfiniteScroll>\n          </div>\n        </el-scrollbar>\n      </el-col>\n    </el-row>\n    <template #footer v-if=\"isMul\">\n      <div class=\"dialog-footer\">\n        <el-button @click=\"dialogVisible = false\"> {{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"mulAssociation\"> {{ $t('common.confirm') }} </el-button>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\n\nconst route = useRoute()\nconst {\n  params: { id }, // knowledgeId\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst emit = defineEmits(['refresh'])\n\nconst dialogVisible = ref<boolean>(false)\n\nconst loading = ref(false)\nconst documentList = ref<any[]>([])\nconst cloneDocumentList = ref<any[]>([])\nconst paragraphList = ref<any[]>([])\nconst currentProblemId = ref<string>('')\nconst currentMulProblemId = ref<string[]>([])\n\n// 回显\nconst associationParagraph = ref<any[]>([])\n\nconst currentDocument = ref<string>('')\nconst search = ref('')\nconst searchType = ref('title')\nconst filterDoc = ref('')\n// 批量\nconst isMul = ref(false)\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 50,\n  total: 0,\n})\n\nfunction mulAssociation() {\n  const data = {\n    problem_id_list: currentMulProblemId.value,\n    paragraph_list: associationParagraph.value.map((item) => ({\n      paragraph_id: item.id,\n      document_id: item.document_id,\n    })),\n  }\n  loadSharedApi({ type: 'problem', systemType: apiType.value })\n    .putMulAssociationProblem(id, data, loading)\n    .then(() => {\n      MsgSuccess(t('views.problem.tip.relatedSuccess'))\n      dialogVisible.value = false\n    })\n}\n\nfunction associationClick(item: any) {\n  if (isMul.value) {\n    if (isAssociation(item.id)) {\n      associationParagraph.value.splice(associationParagraph.value.indexOf(item.id), 1)\n    } else {\n      associationParagraph.value.push(item)\n    }\n  } else {\n    const obj = {\n      paragraph_id: item.id,\n      problem_id: currentProblemId.value as string,\n    }\n    if (isAssociation(item.id)) {\n      loadSharedApi({ type: 'paragraph', systemType: apiType.value })\n        .putDisassociationProblem(id, item.document_id, obj, loading)\n        .then(() => {\n          getRecord(currentProblemId.value)\n        })\n    } else {\n      loadSharedApi({ type: 'paragraph', systemType: apiType.value })\n        .putAssociationProblem(id, item.document_id, obj, loading)\n        .then(() => {\n          getRecord(currentProblemId.value)\n        })\n    }\n  }\n}\n\nfunction searchHandle() {\n  paginationConfig.current_page = 1\n  paragraphList.value = []\n  if (currentDocument.value) {\n    getParagraphList(currentDocument.value)\n  }\n}\n\nfunction clickDocumentHandle(item: any) {\n  paginationConfig.current_page = 1\n  paragraphList.value = []\n  currentDocument.value = item.id\n  getParagraphList(item.id)\n}\n\nfunction getDocument() {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .getDocumentList(id, loading)\n    .then((res: any) => {\n      cloneDocumentList.value = res.data\n      documentList.value = res.data\n      currentDocument.value =\n        cloneDocumentList.value?.length > 0 ? cloneDocumentList.value[0].id : ''\n\n      if (currentDocument.value) {\n        getParagraphList(currentDocument.value)\n      }\n    })\n}\n\nfunction getParagraphList(documentId: string) {\n  loadSharedApi({ type: 'paragraph', systemType: apiType.value })\n    .getParagraphPage(\n      id,\n      (documentId || currentDocument.value) as string,\n      paginationConfig,\n      search.value && { [searchType.value]: search.value },\n      loading,\n    )\n    .then((res: any) => {\n      paragraphList.value = [...paragraphList.value, ...res.data.records]\n      paginationConfig.total = res.data.total\n    })\n}\n\n// 已关联分段\nfunction getRecord(problemId: string) {\n  loadSharedApi({ type: 'problem', systemType: apiType.value })\n    .getDetailProblems(id as string, problemId as string, loading)\n    .then((res: any) => {\n      associationParagraph.value = res.data\n    })\n}\n\nfunction associationCount(documentId: string) {\n  return associationParagraph.value.filter((item) => item.document_id === documentId).length\n}\nfunction isAssociation(paragraphId: string) {\n  return associationParagraph.value.some((option) => option.id === paragraphId)\n}\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    documentList.value = []\n    cloneDocumentList.value = []\n    paragraphList.value = []\n    associationParagraph.value = []\n    isMul.value = false\n\n    currentDocument.value = ''\n    search.value = ''\n    searchType.value = 'title'\n    emit('refresh')\n  }\n})\n\nwatch(filterDoc, (val) => {\n  paragraphList.value = []\n  documentList.value = val\n    ? cloneDocumentList.value.filter((item) => item.name.includes(val))\n    : cloneDocumentList.value\n  currentDocument.value = documentList.value?.length > 0 ? documentList.value[0].id : ''\n})\n\nconst open = (problemId: any) => {\n  getDocument()\n  if (problemId.length == 1) {\n    currentProblemId.value = problemId[0]\n    getRecord(problemId)\n  } else if (problemId.length > 1) {\n    currentMulProblemId.value = problemId\n    isMul.value = true\n  }\n  dialogVisible.value = true\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped>\n.paragraph-card {\n  position: relative;\n  // card 选中样式\n  &.selected {\n    border: 1px solid var(--el-color-primary) !important;\n    &:before {\n      content: '';\n      position: absolute;\n      right: 0;\n      top: 0;\n      border: 14px solid var(--el-color-primary);\n      border-bottom-color: transparent;\n      border-left-color: transparent;\n    }\n\n    &:after {\n      content: '';\n      width: 3px;\n      height: 6px;\n      position: absolute;\n      right: 5px;\n      top: 2px;\n      border: 2px solid #fff;\n      border-top-color: transparent;\n      border-left-color: transparent;\n      transform: rotate(35deg);\n    }\n    &:hover {\n      border: 1px solid var(--el-color-primary);\n    }\n  }\n}\n.paragraph-badge {\n  .el-badge__content {\n    height: auto;\n    display: table;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/problem/index.vue",
    "content": "<template>\n  <div class=\"document p-16-24\">\n    <h2 class=\"mb-16\">{{ $t('views.problem.title') }}</h2>\n    <el-card style=\"--el-card-padding: 0\">\n      <div class=\"main-calc-height\">\n        <div class=\"p-24\">\n          <div class=\"flex-between\">\n            <div>\n              <el-button\n                type=\"primary\"\n                @click=\"createProblem\"\n                v-if=\"permissionPrecise.problem_create(id)\"\n              >\n                {{ $t('views.problem.createProblem') }}\n              </el-button>\n              <el-button\n                @click=\"relateProblem()\"\n                :disabled=\"multipleSelection.length === 0\"\n                v-if=\"permissionPrecise.problem_relate(id)\"\n              >\n                {{ $t('views.problem.relateParagraph.title') }}\n              </el-button>\n              <el-button\n                @click=\"deleteMulDocument\"\n                :disabled=\"multipleSelection.length === 0\"\n                v-if=\"permissionPrecise.problem_delete(id)\"\n              >\n                {{ $t('views.problem.setting.batchDelete') }}\n              </el-button>\n            </div>\n\n            <el-input\n              v-model=\"filterText\"\n              :placeholder=\"$t('common.searchBar.placeholder')\"\n              prefix-icon=\"Search\"\n              class=\"w-240\"\n              @change=\"getList\"\n              clearable\n            />\n          </div>\n          <app-table\n            ref=\"multipleTableRef\"\n            class=\"mt-16\"\n            :data=\"problemData\"\n            :pagination-config=\"paginationConfig\"\n            :quick-create=\"permissionPrecise.problem_create(id)\"\n            :quickCreateName=\"$t('views.problem.quickCreateName')\"\n            :quickCreatePlaceholder=\"$t('views.problem.quickCreateProblem')\"\n            :quickCreateMaxlength=\"256\"\n            @sizeChange=\"handleSizeChange\"\n            @changePage=\"getList\"\n            @cell-mouse-enter=\"cellMouseEnter\"\n            @cell-mouse-leave=\"cellMouseLeave\"\n            @creatQuick=\"creatQuickHandle\"\n            @row-click=\"rowClickHandle\"\n            @selection-change=\"handleSelectionChange\"\n            :row-class-name=\"setRowClass\"\n            v-loading=\"loading\"\n            :row-key=\"(row: any) => row.id\"\n          >\n            <el-table-column type=\"selection\" width=\"55\" :reserve-selection=\"true\" />\n            <el-table-column prop=\"content\" :label=\"$t('views.problem.title')\" min-width=\"280\">\n              <template #default=\"{ row }\">\n                <ReadWrite\n                  @change=\"editName($event, row.id)\"\n                  :data=\"row.content\"\n                  :showEditIcon=\"permissionPrecise.problem_edit(id) && row.id === currentMouseId\"\n                  :maxlength=\"256\"\n                />\n              </template>\n            </el-table-column>\n            <el-table-column\n              prop=\"paragraph_count\"\n              :label=\"$t('views.problem.table.paragraph_count')\"\n              align=\"right\"\n              min-width=\"100\"\n            >\n              <template #default=\"{ row }\">\n                <el-link\n                  type=\"primary\"\n                  @click.stop=\"rowClickHandle(row)\"\n                  v-if=\"row.paragraph_count\"\n                >\n                  {{ row.paragraph_count }}\n                </el-link>\n                <span v-else>\n                  {{ row.paragraph_count }}\n                </span>\n              </template>\n            </el-table-column>\n            <el-table-column prop=\"create_time\" :label=\"$t('common.createTime')\" width=\"170\">\n              <template #default=\"{ row }\">\n                {{ datetimeFormat(row.create_time) }}\n              </template>\n            </el-table-column>\n            <el-table-column\n              prop=\"update_time\"\n              :label=\"$t('views.problem.table.updateTime')\"\n              width=\"170\"\n            >\n              <template #default=\"{ row }\">\n                {{ datetimeFormat(row.update_time) }}\n              </template>\n            </el-table-column>\n            <el-table-column :label=\"$t('common.operation')\" align=\"left\" fixed=\"right\">\n              <template #default=\"{ row }\">\n                <div>\n                  <span class=\"mr-4\">\n                    <el-tooltip\n                      effect=\"dark\"\n                      :content=\"$t('views.problem.relateParagraph.title')\"\n                      placement=\"top\"\n                    >\n                      <el-button\n                        type=\"primary\"\n                        text\n                        @click.stop=\"relateProblem(row)\"\n                        v-if=\"permissionPrecise.problem_relate(id)\"\n                      >\n                        <AppIcon iconName=\"app-generate-question\"></AppIcon>\n                      </el-button>\n                    </el-tooltip>\n                  </span>\n                  <span>\n                    <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n                      <el-button\n                        type=\"primary\"\n                        text\n                        @click.stop=\"deleteProblem(row)\"\n                        v-if=\"permissionPrecise.problem_delete(id)\"\n                      >\n                        <AppIcon iconName=\"app-delete\"></AppIcon>\n                      </el-button>\n                    </el-tooltip>\n                  </span>\n                </div>\n              </template>\n            </el-table-column>\n          </app-table>\n        </div>\n      </div>\n    </el-card>\n    <CreateProblemDialog ref=\"CreateProblemDialogRef\" @refresh=\"refresh\" />\n    <DetailProblemDrawer\n      :next=\"nextChatRecord\"\n      :pre=\"preChatRecord\"\n      ref=\"DetailProblemRef\"\n      v-model:currentId=\"currentClickId\"\n      v-model:currentContent=\"currentContent\"\n      :pre_disable=\"pre_disable\"\n      :next_disable=\"next_disable\"\n      @refresh=\"refreshRelate\"\n    />\n    <RelateProblemDialog ref=\"RelateProblemDialogRef\" @refresh=\"refreshRelate\" />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, reactive, onBeforeUnmount, computed } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport { ElTable } from 'element-plus'\nimport CreateProblemDialog from './component/CreateProblemDialog.vue'\nimport DetailProblemDrawer from './component/DetailProblemDrawer.vue'\nimport RelateProblemDialog from './component/RelateProblemDialog.vue'\nimport { datetimeFormat } from '@/utils/time'\nimport { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'\nimport type { Dict } from '@/api/type/common'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\n\nconst route = useRoute()\nconst {\n  params: { id, folderId }, // 知识库id\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('share/')) {\n    return 'workspaceShare'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nconst RelateProblemDialogRef = ref()\nconst DetailProblemRef = ref()\nconst CreateProblemDialogRef = ref()\nconst loading = ref(false)\n\n// 当前需要修改问题的id\nconst currentMouseId = ref('')\n// 当前点击打开drawer的id\nconst currentClickId = ref('')\nconst currentContent = ref('')\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 10,\n  total: 0,\n  page_sizes: [10, 20, 50, 100, 1000],\n})\n\nconst filterText = ref('')\nconst problemData = ref<any[]>([])\nconst problemIndexMap = computed<Dict<number>>(() => {\n  return problemData.value\n    .map((row, index) => ({\n      [row.id]: index,\n    }))\n    .reduce((pre, next) => ({ ...pre, ...next }), {})\n})\n\nconst multipleTableRef = ref<InstanceType<typeof ElTable>>()\nconst multipleSelection = ref<any[]>([])\n\nfunction relateProblem(row?: any) {\n  const arr: string[] = []\n  if (row) {\n    arr.push(row.id)\n  } else {\n    multipleSelection.value.map((v) => {\n      if (v) {\n        arr.push(v.id)\n      }\n    })\n  }\n\n  RelateProblemDialogRef.value.open(arr)\n}\n\nfunction createProblem() {\n  CreateProblemDialogRef.value.open()\n}\n\nconst handleSelectionChange = (val: any[]) => {\n  multipleSelection.value = val\n}\n\n/*\n  快速创建空白文档\n*/\nfunction creatQuickHandle(val: string) {\n  loading.value = true\n  const obj = [val]\n  loadSharedApi({ type: 'problem', systemType: apiType.value })\n    .postProblems(id, obj)\n    .then(() => {\n      getList()\n      MsgSuccess(t('common.createSuccess'))\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nfunction deleteMulDocument() {\n  const arr: string[] = []\n  multipleSelection.value.map((v) => {\n    if (v) {\n      arr.push(v.id)\n    }\n  })\n  loadSharedApi({ type: 'problem', systemType: apiType.value })\n    .putMulProblem(id, arr, loading)\n    .then(() => {\n      MsgSuccess(t('views.document.delete.successMessage'))\n      multipleTableRef.value?.clearSelection()\n      getList()\n    })\n}\n\nfunction deleteProblem(row: any) {\n  loadSharedApi({ type: 'problem', systemType: apiType.value })\n    .delProblems(id, row.id, loading)\n    .then(() => {\n      MsgSuccess(t('common.deleteSuccess'))\n      getList()\n    })\n}\n\nfunction editName(val: string, problemId: string) {\n  if (val) {\n    const obj = {\n      content: val,\n    }\n    loadSharedApi({ type: 'problem', systemType: apiType.value })\n      .putProblems(id, problemId, obj, loading)\n      .then(() => {\n        getList()\n        MsgSuccess(t('common.modifySuccess'))\n      })\n  } else {\n    MsgError(t('views.problem.tip.errorMessage'))\n  }\n}\n\nfunction cellMouseEnter(row: any, column: any) {\n  if (column && column.property === 'content') {\n    currentMouseId.value = row.id\n  }\n}\n\nfunction cellMouseLeave() {\n  currentMouseId.value = ''\n}\n\n/**\n * 下一页\n */\nconst nextChatRecord = () => {\n  let index = problemIndexMap.value[currentClickId.value] + 1\n  if (index >= problemData.value.length) {\n    if (\n      index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=\n      paginationConfig.total - 1\n    ) {\n      return\n    }\n    paginationConfig.current_page = paginationConfig.current_page + 1\n    getList().then(() => {\n      index = 0\n      currentClickId.value = problemData.value[index].id\n      currentContent.value = problemData.value[index].content\n    })\n  } else {\n    currentClickId.value = problemData.value[index].id\n    currentContent.value = problemData.value[index].content\n  }\n}\nconst pre_disable = computed(() => {\n  const index = problemIndexMap.value[currentClickId.value] - 1\n  return index < 0 && paginationConfig.current_page <= 1\n})\n\nconst next_disable = computed(() => {\n  const index = problemIndexMap.value[currentClickId.value] + 1\n  return (\n    index >= problemData.value.length &&\n    index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=\n      paginationConfig.total - 1\n  )\n})\n/**\n * 上一页\n */\nconst preChatRecord = () => {\n  let index = problemIndexMap.value[currentClickId.value] - 1\n\n  if (index < 0) {\n    if (paginationConfig.current_page <= 1) {\n      return\n    }\n    paginationConfig.current_page = paginationConfig.current_page - 1\n    getList().then(() => {\n      index = paginationConfig.page_size - 1\n      currentClickId.value = problemData.value[index].id\n      currentContent.value = problemData.value[index].content\n    })\n  } else {\n    currentClickId.value = problemData.value[index].id\n    currentContent.value = problemData.value[index].content\n  }\n}\n\nfunction rowClickHandle(row: any, column?: any) {\n  if (column && column.type === 'selection') {\n    return\n  }\n  if (route.path.includes('share/')) {\n    return\n  }\n  if (row.paragraph_count) {\n    currentClickId.value = row.id\n    currentContent.value = row.content\n    DetailProblemRef.value.open()\n  }\n}\n\nconst setRowClass = ({ row }: any) => {\n  return currentClickId.value === row?.id ? 'highlight' : ''\n}\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getList()\n}\n\nfunction getList() {\n  return loadSharedApi({ type: 'problem', isShared: isShared.value, systemType: apiType.value })\n    .getProblemsPage(\n      id as string,\n      paginationConfig,\n      filterText.value && { content: filterText.value },\n      loading,\n    )\n    .then((res: any) => {\n      problemData.value = res.data.records\n      paginationConfig.total = res.data.total\n    })\n}\n\nfunction refreshRelate() {\n  getList()\n  multipleTableRef.value?.clearSelection()\n}\n\nfunction refresh() {\n  paginationConfig.current_page = 1\n  getList()\n}\n\nonMounted(() => {\n  getList()\n})\n\nonBeforeUnmount(() => {})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system/operate-log/component/DetailDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.operateLog.table.opt')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <div class=\"border border-r-6 mb-16\" style=\"white-space: pre-wrap; height: 400px\">\n      <el-scrollbar>\n        <div class=\"p-16\">\n          {{ details }}\n        </div>\n      </el-scrollbar>\n    </div>\n    <template #footer>\n      <span class=\"dialog-footer mt-16\">\n        <el-button @click.prevent=\"dialogVisible = false\">\n          {{ $t('common.close') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nconst dialogVisible = ref<boolean>(false)\nconst details = ref<string>()\n\nconst open = (data: any) => {\n  details.value = JSON.stringify(data.details, null, 4)\n  dialogVisible.value = true\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system/operate-log/index.vue",
    "content": "<template>\n  <div class=\"operate-log p-16-24\">\n    <h2 class=\"mb-16\">{{ $t('views.operateLog.title') }}</h2>\n    <el-card style=\"--el-card-padding: 0\" class=\"main-calc-height\">\n      <div class=\"p-24\">\n        <div class=\"flex-between\">\n          <div>\n            <el-select v-model=\"history_day\" class=\"mr-12 w-180\" @change=\"changeDayHandle\">\n              <el-option\n                v-for=\"item in dayOptions\"\n                :key=\"item.value\"\n                :label=\"item.label\"\n                :value=\"item.value\"\n              />\n            </el-select>\n            <el-date-picker\n              v-if=\"history_day === 'other'\"\n              v-model=\"daterangeValue\"\n              type=\"daterange\"\n              :start-placeholder=\"$t('views.applicationOverview.monitor.startDatePlaceholder')\"\n              :end-placeholder=\"$t('views.applicationOverview.monitor.endDatePlaceholder')\"\n              format=\"YYYY-MM-DD\"\n              value-format=\"YYYY-MM-DD\"\n              @change=\"changeDayRangeHandle\"\n            />\n          </div>\n\n          <div style=\"display: flex\">\n            <div class=\"flex-between complex-search\">\n              <el-select\n                v-model=\"filter_type\"\n                class=\"complex-search__left\"\n                @change=\"changeFilterHandle\"\n                style=\"width: 120px\"\n              >\n                <el-option\n                  v-for=\"item in filterOptions\"\n                  :key=\"item.value\"\n                  :label=\"item.label\"\n                  :value=\"item.value\"\n                />\n              </el-select>\n              <el-select\n                v-if=\"filter_type === 'status'\"\n                v-model=\"filter_status\"\n                @change=\"changeStatusHandle\"\n                style=\"width: 220px\"\n                clearable\n              >\n                <el-option\n                  v-for=\"item in statusOptions\"\n                  :key=\"item.value\"\n                  :label=\"item.label\"\n                  :value=\"item.value\"\n                />\n              </el-select>\n              <el-input\n                v-else\n                v-model=\"searchValue\"\n                @change=\"getList\"\n                :placeholder=\"$t('common.search')\"\n                prefix-icon=\"Search\"\n                style=\"width: 220px\"\n                clearable\n              />\n            </div>\n            <el-button\n              @click=\"exportLog\"\n              style=\"margin-left: 10px\"\n              v-hasPermission=\"\n                new ComplexPermission(\n                  [RoleConst.ADMIN],\n                  [PermissionConst.OPERATION_LOG_EXPORT],\n                  [EditionConst.IS_EE, EditionConst.IS_PE],\n                  'OR',\n                )\n              \"\n              >{{ $t('common.export') }}\n            </el-button>\n            <el-button\n              @click=\"dialogVisible = true\"\n              v-hasPermission=\"\n                new ComplexPermission(\n                  [RoleConst.ADMIN],\n                  [PermissionConst.OPERATION_LOG_CLEAR_POLICY],\n                  [EditionConst.IS_EE, EditionConst.IS_PE],\n                  'OR',\n                )\n              \"\n            >\n              {{ $t('views.chatLog.buttons.clearStrategy') }}\n            </el-button>\n          </div>\n        </div>\n\n        <app-table\n          class=\"mt-16 w-full\"\n          :data=\"tableData\"\n          :pagination-config=\"paginationConfig\"\n          @sizeChange=\"handleSizeChange\"\n          @changePage=\"getList\"\n          v-loading=\"loading\"\n          show-overflow-tooltip\n        >\n          <el-table-column prop=\"menu\" :label=\"$t('views.operateLog.table.menu')\" width=\"160\">\n            <template #header>\n              <div>\n                <span>{{ $t('views.operateLog.table.menu') }}</span>\n                <el-popover\n                  :width=\"200\"\n                  trigger=\"click\"\n                  :visible=\"popoverVisible\"\n                  :persistent=\"false\"\n                >\n                  <template #reference>\n                    <el-button\n                      style=\"margin-top: -2px\"\n                      :type=\"operateTypeArr && operateTypeArr.length > 0 ? 'primary' : ''\"\n                      link\n                      @click=\"popoverVisible = !popoverVisible\"\n                    >\n                      <el-icon>\n                        <Filter />\n                      </el-icon>\n                    </el-button>\n                  </template>\n                  <div class=\"filter\">\n                    <div class=\"form-item mb-16\">\n                      <div @click.stop>\n                        <el-scrollbar height=\"300\" style=\"margin: 0 0 0 10px\">\n                          <el-checkbox-group\n                            v-model=\"operateTypeArr\"\n                            style=\"display: flex; flex-direction: column\"\n                          >\n                            <el-checkbox\n                              v-for=\"item in operateOptions\"\n                              :key=\"item.value\"\n                              :label=\"item.label\"\n                              :value=\"item.value\"\n                            />\n                          </el-checkbox-group>\n                        </el-scrollbar>\n                      </div>\n                    </div>\n                  </div>\n                  <div class=\"text-right\">\n                    <el-button size=\"small\" @click=\"filterChange('clear')\"\n                      >{{ $t('common.clear') }}\n                    </el-button>\n                    <el-button type=\"primary\" @click=\"filterChange\" size=\"small\"\n                      >{{ $t('common.confirm') }}\n                    </el-button>\n                  </div>\n                </el-popover>\n              </div>\n            </template>\n          </el-table-column>\n          <el-table-column\n            prop=\"operate\"\n            :label=\"$t('views.operateLog.table.detail')\"\n            :tooltip-formatter=\"\n              ({ row }: any) =>\n                row.operate + (row.operation_object?.name ? `【${row.operation_object.name}】` : '')\n            \"\n          >\n            <template #default=\"{ row }\">\n              {{\n                row.operate + (row.operation_object?.name ? `【${row.operation_object.name}】` : '')\n              }}\n            </template>\n          </el-table-column>\n          <el-table-column\n            width=\"140\"\n            prop=\"user.username\"\n            :label=\"$t('views.operateLog.table.user')\"\n          />\n          <el-table-column\n            v-if=\"user.isEE()\"\n            width=\"200\"\n            prop=\"workspace_name\"\n            :label=\"$t('views.workspace.title')\"\n          >\n            <template #header>\n              <div>\n                <span>{{ $t('views.workspace.title') }}</span>\n                <el-popover\n                  :width=\"200\"\n                  trigger=\"click\"\n                  :visible=\"workspaceVisible\"\n                  :persistent=\"false\"\n                >\n                  <template #reference>\n                    <el-button\n                      style=\"margin-top: -2px\"\n                      :type=\"workspaceArr && workspaceArr.length > 0 ? 'primary' : ''\"\n                      link\n                      @click=\"workspaceVisible = !workspaceVisible\"\n                    >\n                      <el-icon>\n                        <Filter />\n                      </el-icon>\n                    </el-button>\n                  </template>\n                  <div class=\"filter\">\n                    <div class=\"form-item mb-16\">\n                      <div @click.stop>\n                        <el-scrollbar height=\"300\" style=\"margin: 0 0 0 10px\">\n                          <el-checkbox-group\n                            v-model=\"workspaceArr\"\n                            style=\"display: flex; flex-direction: column\"\n                          >\n                            <el-checkbox\n                              v-for=\"item in workspaceOptions\"\n                              :key=\"item.value\"\n                              :label=\"item.label\"\n                              :value=\"item.value\"\n                            />\n                          </el-checkbox-group>\n                        </el-scrollbar>\n                      </div>\n                    </div>\n                  </div>\n                  <div class=\"text-right\">\n                    <el-button size=\"small\" @click=\"filterWorkspaceChange('clear')\"\n                      >{{ $t('common.clear') }}\n                    </el-button>\n                    <el-button type=\"primary\" @click=\"filterWorkspaceChange\" size=\"small\"\n                      >{{ $t('common.confirm') }}\n                    </el-button>\n                  </div>\n                </el-popover>\n              </div>\n            </template>\n          </el-table-column>\n          <el-table-column prop=\"status\" :label=\"$t('common.status.label')\" width=\"100\">\n            <template #default=\"{ row }\">\n              <span v-if=\"row.status === 200\">{{ $t('common.status.success') }}</span>\n              <span v-else style=\"color: red\">{{ $t('common.status.fail') }}</span>\n            </template>\n          </el-table-column>\n          <el-table-column\n            prop=\"ip_address\"\n            :label=\"$t('views.operateLog.table.ip_address')\"\n            width=\"160\"\n          ></el-table-column>\n          <el-table-column :label=\"$t('views.operateLog.table.operateTime')\" width=\"180\">\n            <template #default=\"{ row }\">\n              {{ datetimeFormat(row.create_time) }}\n            </template>\n          </el-table-column>\n          <el-table-column :label=\"$t('common.operation')\" width=\"70\" align=\"left\" fixed=\"right\">\n            <template #default=\"{ row }\">\n              <!-- <span class=\"mr-4\"> -->\n              <el-tooltip effect=\"dark\" :content=\"$t('views.operateLog.table.opt')\" placement=\"top\">\n                <el-button type=\"primary\" text @click.stop=\"showDetails(row)\">\n                  <AppIcon iconName=\"app-operate-log\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n              <!-- </span> -->\n            </template>\n          </el-table-column>\n        </app-table>\n      </div>\n      <DetailDialog ref=\"DetailDialogRef\" />\n    </el-card>\n    <el-dialog\n      :title=\"$t('views.chatLog.buttons.clearStrategy')\"\n      v-model=\"dialogVisible\"\n      width=\"25%\"\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n    >\n      <span>{{ $t('common.delete') }}</span>\n      <el-input-number\n        v-model=\"days\"\n        controls-position=\"right\"\n        :min=\"1\"\n        :max=\"100000\"\n        :value-on-clear=\"0\"\n        step-strictly\n        style=\"width: 110px; margin-left: 8px; margin-right: 8px\"\n      ></el-input-number>\n      <span>{{ $t('views.chatLog.daysText') }}</span>\n      <template #footer>\n        <div class=\"dialog-footer\" style=\"margin-top: 16px\">\n          <el-button @click=\"dialogVisible = false\">{{ $t('common.cancel') }}</el-button>\n          <el-button type=\"primary\" @click=\"saveCleanTime\">\n            {{ $t('common.save') }}\n          </el-button>\n        </div>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, reactive } from 'vue'\nimport operateLog from '@/api/system/operate-log'\nimport DetailDialog from './component/DetailDialog.vue'\nimport { t } from '@/locales'\nimport { beforeDay, datetimeFormat, nowDate } from '@/utils/time'\nimport useStore from '@/stores'\nimport WorkspaceApi from '@/api/system/workspace.ts'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data.ts'\nimport { ComplexPermission } from '@/utils/permission/type.ts'\nimport { MsgSuccess } from '@/utils/message.ts'\n\nconst { user } = useStore()\nconst popoverVisible = ref(false)\nconst operateTypeArr = ref<any[]>([])\nconst workspaceVisible = ref(false)\nconst workspaceArr = ref<any[]>([])\nconst DetailDialogRef = ref()\nconst loading = ref(false)\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\nconst searchValue = ref('')\nconst tableData = ref<any[]>([])\nconst history_day = ref<number | string>(7)\nconst filter_type = ref<string>('user')\nconst filter_status = ref<string>('')\nconst daterange = ref({\n  start_time: '',\n  end_time: '',\n})\nconst daterangeValue = ref('')\nconst dialogVisible = ref(false)\nconst days = ref<number>(180)\nconst dayOptions = [\n  {\n    value: 7,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past7Days'), // 使用 t 方法来国际化显示文本\n  },\n  {\n    value: 30,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past30Days'),\n  },\n  {\n    value: 90,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past90Days'),\n  },\n  {\n    value: 183,\n    label: t('views.applicationOverview.monitor.pastDayOptions.past183Days'),\n  },\n  {\n    value: 'other',\n    label: t('common.custom'),\n  },\n]\nconst filterOptions = [\n  {\n    value: 'user',\n    label: t('views.operateLog.table.user'),\n  },\n  {\n    value: 'status',\n    label: t('common.status.label'),\n  },\n  {\n    value: 'ip_address',\n    label: t('views.operateLog.table.ip_address'),\n  },\n]\nconst statusOptions = [\n  {\n    value: '200',\n    label: t('common.status.success'),\n  },\n  {\n    value: '500',\n    label: t('common.status.fail'),\n  },\n]\nconst operateOptions = ref<any[]>([])\n\nconst workspaceOptions = ref<any[]>([])\n\nfunction filterChange(val: string) {\n  if (val === 'clear') {\n    operateTypeArr.value = []\n  }\n  getList()\n  popoverVisible.value = false\n}\n\nfunction filterWorkspaceChange(val: string) {\n  if (val === 'clear') {\n    workspaceArr.value = []\n  }\n  getList()\n  workspaceVisible.value = false\n}\n\nfunction changeStatusHandle(val: string) {\n  getList()\n}\n\nfunction changeFilterHandle(val: string) {\n  filter_type.value = val\n  if (searchValue.value) {\n    getList()\n  }\n}\n\nfunction changeDayHandle(val: number | string) {\n  if (val !== 'other') {\n    daterange.value.start_time = beforeDay(val)\n    daterange.value.end_time = ''\n    getList()\n  }\n}\n\nfunction changeDayRangeHandle(val: string) {\n  daterange.value.start_time = val[0]\n  daterange.value.end_time = val[1]\n  getList()\n}\n\nfunction showDetails(row: any) {\n  DetailDialogRef.value.open(row)\n}\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getList()\n}\n\nfunction getRequestParams() {\n  const obj: any = {\n    start_time: daterange.value.start_time,\n    end_time: daterange.value.end_time,\n  }\n  if (searchValue.value && filter_type.value !== 'status') {\n    obj[filter_type.value] = searchValue.value\n  }\n  if (filter_type.value === 'status') {\n    obj['status'] = filter_status.value\n  }\n  if (operateTypeArr.value.length > 0) {\n    obj['menu'] = JSON.stringify(operateTypeArr.value)\n  }\n  if (workspaceArr.value.length > 0) {\n    obj['workspace_ids'] = JSON.stringify(workspaceArr.value)\n  }\n  return obj\n}\n\nfunction getList() {\n  return operateLog.getOperateLog(paginationConfig, getRequestParams(), loading).then((res) => {\n    tableData.value = res.data.records\n    paginationConfig.total = res.data.total\n  })\n}\n\nfunction getMenuList() {\n  return operateLog.getMenuList().then((res) => {\n    const arr: any[] = res.data\n    arr\n      .filter((item, index, self) => index === self.findIndex((i) => i['menu'] === item['menu']))\n      .forEach((ele) => {\n        operateOptions.value.push({ label: ele.menu_label, value: ele.menu })\n      })\n  })\n}\n\nconst exportLog = () => {\n  operateLog.exportOperateLog(getRequestParams(), loading)\n}\n\nasync function getWorkspaceList() {\n  if (user.isEE()) {\n    const res = await WorkspaceApi.getSystemWorkspaceList(loading)\n    workspaceOptions.value = res.data.map((item: any) => ({\n      label: item.name,\n      value: item.id,\n    }))\n  }\n}\n\nfunction saveCleanTime() {\n  const obj = {\n    clean_time: days.value,\n  }\n  operateLog\n    .saveCleanTime(obj, loading)\n    .then(() => {\n      MsgSuccess(t('common.saveSuccess'))\n      dialogVisible.value = false\n      getCleanTime()\n    })\n    .catch(() => {\n      dialogVisible.value = false\n    })\n}\n\nfunction getCleanTime() {\n  operateLog.getCleanTime().then((res) => {\n    days.value = res.data\n  })\n}\n\nonMounted(() => {\n  getMenuList()\n  getCleanTime()\n  getWorkspaceList()\n  changeDayHandle(history_day.value)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system/resource-authorization/component/PermissionTable.vue",
    "content": "<template>\n  <div class=\"permission-setting p-24 flex\">\n    <div class=\"resource-authorization__table\">\n      <h4 class=\"mb-16\">{{ $t('views.system.resourceAuthorization.permissionSetting') }}</h4>\n      <div class=\"flex-between mb-16\">\n        <el-button\n          type=\"primary\"\n          v-if=\"\n            hasPermission(permissionObj[(route.meta?.resource as string) || 'APPLICATION'], 'OR')\n          \"\n          :disabled=\"multipleSelection.length === 0\"\n          @click=\"openMulConfigureDialog\"\n          >{{ $t('views.system.resourceAuthorization.setting.configure') }}</el-button\n        >\n\n        <div class=\"flex-between complex-search\">\n          <el-select\n            class=\"complex-search__left\"\n            v-model=\"searchType\"\n            style=\"width: 80px\"\n            @change=\"search_type_change\"\n          >\n            <el-option :label=\"$t('common.name')\" value=\"name\" />\n            <el-option\n              :label=\"$t('views.model.modelForm.permissionType.label')\"\n              value=\"permission\"\n            />\n          </el-select>\n          <el-input\n            v-if=\"searchType === 'name'\"\n            v-model=\"searchForm.name\"\n            :placeholder=\"$t('common.searchBar.placeholder')\"\n            style=\"width: 220px\"\n            clearable\n          />\n          <el-select\n            v-else-if=\"searchType === 'permission'\"\n            v-model=\"searchForm.permission\"\n            filterable\n            clearable\n            multiple\n            :reserve-keyword=\"false\"\n            collapse-tags\n            collapse-tags-tooltip\n            style=\"width: 220px\"\n          >\n            <template v-for=\"(item, index) in getPermissionOptions()\" :key=\"index\">\n              <el-option :label=\"item.label\" :value=\"item.value\" />\n            </template>\n          </el-select>\n        </div>\n      </div>\n\n      <app-table\n        ref=\"multipleTableRef\"\n        class=\"mt-16\"\n        :data=\"filteredData\"\n        @select=\"select\"\n        @select-all=\"selectAll\"\n        :maxTableHeight=\"260\"\n        :row-key=\"(row: any) => row.id\"\n        style=\"min-width: 600px\"\n        :expand-row-keys=\"defaultExpandKeys\"\n        :default-expand-all=\"searchForm.name || searchForm.permission?.length > 0\"\n        show-overflow-tooltip\n      >\n        <el-table-column type=\"selection\" width=\"55\" :reserve-selection=\"true\"> </el-table-column>\n        <el-table-column prop=\"name\" :label=\"$t('common.name')\">\n          <template #default=\"{ row }\">\n            <span style=\"vertical-align: sub\">\n              <!--  文件夹 icon -->\n              <AppIcon\n                v-if=\"row.resource_type === 'folder'\"\n                iconName=\"app-folder\"\n                style=\"font-size: 20px\"\n              ></AppIcon>\n              <!--  知识库 icon -->\n              <KnowledgeIcon :size=\"20\" v-else-if=\"isKnowledge\" :type=\"row.icon\" />\n              <!--  应用/工具 自定义 icon -->\n              <el-avatar\n                v-else-if=\"isAppIcon(row?.icon) && !isModel\"\n                style=\"background: none\"\n                shape=\"square\"\n                :size=\"20\"\n              >\n                <img :src=\"resetUrl(row?.icon)\" alt=\"\" />\n              </el-avatar>\n              <!--  应用 icon -->\n              <LogoIcon v-else-if=\"isApplication\" height=\"20px\" />\n              <!-- 工具 icon -->\n              <ToolIcon v-else-if=\"isTool\" :size=\"20\" :type=\"row?.tool_type\" />\n              <!-- 模型 icon -->\n              <span\n                v-else-if=\"isModel\"\n                style=\"width: 20px; height: 20px; display: inline-block\"\n                :innerHTML=\"getProviderIcon(row)\"\n              ></span>\n            </span>\n            {{ row?.name }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('views.model.modelForm.permissionType.label')\" align=\"left\">\n          <template #default=\"{ row }\">\n            <el-radio-group\n              v-model=\"row.permission\"\n              @change=\"(val: any) => submitPermissions(val, row)\"\n            >\n              <template v-for=\"(item, index) in getRowPermissionOptions(row)\" :key=\"index\">\n                <el-radio :value=\"item.value\" class=\"mr-16\">{{ item.label }}</el-radio>\n              </template>\n            </el-radio-group>\n          </template>\n        </el-table-column>\n      </app-table>\n    </div>\n\n    <!-- 批量配置 弹出层 -->\n    <el-dialog\n      v-model=\"dialogVisible\"\n      :title=\"$t('views.system.resourceAuthorization.setting.configure')\"\n      destroy-on-close\n      @close=\"closeDialog\"\n    >\n      <el-radio-group v-model=\"radioPermission\" class=\"radio-block\">\n        <template v-for=\"(item, index) in permissionOptions\" :key=\"index\">\n          <el-radio :value=\"item.value\" class=\"mr-16\">\n            <p class=\"color-text-primary lighter\">{{ item.label }}</p>\n            <el-text class=\"color-secondary lighter\">{{ item.desc }}</el-text>\n          </el-radio>\n        </template>\n      </el-radio-group>\n      <template #footer>\n        <div class=\"dialog-footer mt-24\">\n          <el-button @click=\"closeDialog\"> {{ $t('common.cancel') }}</el-button>\n          <el-button type=\"primary\" @click=\"submitDialog\"> {{ $t('common.confirm') }}</el-button>\n        </div>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport type { Provider } from '@/api/type/model'\nimport { SourceTypeEnum } from '@/enums/common'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport { RoleConst, PermissionConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { getPermissionOptions } from '@/views/system/resource-authorization/constant'\nimport useStore from '@/stores'\nimport { TreeToFlatten } from '@/utils/array'\n\nconst { model, user } = useStore()\nconst route = useRoute()\nconst props = defineProps<{\n  data: any[]\n  type: string\n  getData?: () => void\n}>()\nconst emit = defineEmits(['submitPermissions'])\n\nconst defaultExpandKeys = ref<Array<string>>([])\nconst isComputedFirst = ref(true) // 仅第一次获得数据的时候需要计算一次展开属性\n\nwatch(\n  () => props.data,\n  (newData) => {\n    if (newData && newData.length > 0 && isComputedFirst.value) {\n      defaultExpandKeys.value = props.data?.length > 0 ? [props.data[0]?.id] : []\n      isComputedFirst.value = false\n    }\n  },\n  { immediate: true },\n)\n\n// const defaultExpandKeys = computed(() => {\n// const searchName = searchForm.value.name || ''\n// const searchPermissions = searchForm.value.permission ?? []\n// if (!searchName && (!searchPermissions || searchPermissions.length === 0)) {\n// return props.data?.length > 0 ? [props.data[0]?.id] : []\n\n// }\n// const expandIds: string[] = []\n// // 传入过滤后的数据\n// const collectExpandIds = (nodes: any[]) => {\n//   nodes.forEach((node) => {\n//     if (node.children && node.children.length > 0) {\n//       expandIds.push(node.id)\n//       collectExpandIds(node.children)\n//     }\n//   })\n// }\n// collectExpandIds(filteredData.value)\n// return expandIds\n// })\n\nconst permissionOptionMap = computed(() => {\n  return {\n    rootFolder: getPermissionOptions(true, true),\n    folder: getPermissionOptions(false, false),\n    resource: getPermissionOptions(false, false),\n  }\n})\n\nconst getRowPermissionOptions = (row: any) => {\n  const isFolder = row.resource_type === 'folder'\n  const isRoot = isFolder && row.folder_id === null\n  if (isRoot) {\n    return permissionOptionMap.value.rootFolder\n  }\n  if (isFolder) {\n    return permissionOptionMap.value.folder\n  }\n  return permissionOptionMap.value.resource\n}\n\nconst permissionOptions = computed(() => {\n  if (\n    multipleSelection.value.some(\n      (item) => item.resource_type === 'folder' && item.folder_id == null,\n    )\n  ) {\n    return permissionOptionMap.value.rootFolder\n  } else if (multipleSelection.value.some((item) => item.resource_type === 'folder')) {\n    return permissionOptionMap.value.folder\n  } else {\n    return permissionOptionMap.value.resource\n  }\n})\nconst permissionObj = ref<any>({\n  APPLICATION: new ComplexPermission(\n    [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n    [\n      PermissionConst.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT,\n      PermissionConst.APPLICATION_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT\n        .getWorkspacePermissionWorkspaceManageRole,\n    ],\n    [],\n    'OR',\n  ),\n  KNOWLEDGE: new ComplexPermission(\n    [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n    [\n      PermissionConst.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT,\n      PermissionConst.KNOWLEDGE_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT\n        .getWorkspacePermissionWorkspaceManageRole,\n    ],\n    [],\n    'OR',\n  ),\n  TOOL: new ComplexPermission(\n    [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n    [\n      PermissionConst.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT,\n      PermissionConst.TOOL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT\n        .getWorkspacePermissionWorkspaceManageRole,\n    ],\n    [],\n    'OR',\n  ),\n  MODEL: new ComplexPermission(\n    [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n    [\n      PermissionConst.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT,\n      PermissionConst.MODEL_WORKSPACE_USER_RESOURCE_PERMISSION_EDIT\n        .getWorkspacePermissionWorkspaceManageRole,\n    ],\n    [],\n    'OR',\n  ),\n})\nconst isKnowledge = computed(() => props.type === SourceTypeEnum.KNOWLEDGE)\nconst isApplication = computed(() => props.type === SourceTypeEnum.APPLICATION)\nconst isTool = computed(() => props.type === SourceTypeEnum.TOOL)\nconst isModel = computed(() => props.type === SourceTypeEnum.MODEL)\n\nconst multipleTableRef = ref()\nconst searchType = ref('name')\nconst searchForm = ref<any>({\n  name: '',\n  permission: undefined,\n})\n\nconst search_type_change = () => {\n  searchForm.value = { name: '', permission: undefined }\n}\n\nconst filterTreeData = () => {\n  const searchName = searchForm.value.name || ''\n  const searchPermissions = searchForm.value.permission ?? []\n\n  if (!searchName && (!searchPermissions || searchPermissions.length === 0)) {\n    return props.data\n  }\n\n  const filterNodes = (treeData: any[], name: string, permissions: any[]): any[] => {\n    if (!treeData || treeData.length === 0) return []\n\n    const result: any[] = []\n\n    for (const node of treeData) {\n      const cloneNode = { ...node }\n\n      let isMatch = false\n      if (searchType.value === 'name') {\n        isMatch = node.name.toLowerCase().includes(name.toLowerCase())\n      } else if (searchType.value === 'permission') {\n        isMatch = node.permission && permissions.includes(node.permission)\n      }\n\n      let filteredChildren: any[] = []\n      if (node.children && node.children.length > 0) {\n        filteredChildren = filterNodes(node.children, name, permissions)\n      }\n      if (isMatch || filteredChildren.length > 0) {\n        cloneNode.children = filteredChildren\n        result.push(cloneNode)\n      }\n    }\n    return result\n  }\n  return filterNodes(props.data, searchName, searchPermissions)\n}\n\nconst filteredData = computed(() => {\n  return filterTreeData()\n})\n\nconst multipleSelection = ref<any[]>([])\nconst selectObj: any = {}\nconst selectAll = (selection: any[]) => {\n  multipleSelection.value = selection\n}\nconst select = (val: any[], active: any) => {\n  if (active.resource_type === 'folder') {\n    if (!val.some((item) => item.id == active.id)) {\n      if (selectObj[active.id] === undefined) {\n        selectObj[active.id] = 0\n      }\n      if (selectObj[active.id] % 2 == 0) {\n        TreeToFlatten([active])\n          .filter((item: any) => item.id != active.id)\n          .forEach((item: any) => {\n            if (multipleSelection.value.some((select) => item.id == select.id)) {\n              multipleTableRef.value?.toggleRowSelection(item, true)\n            }\n          })\n        multipleSelection.value = multipleTableRef.value.getSelectionRows()\n      } else {\n        multipleSelection.value = val\n      }\n      selectObj[active.id] = selectObj[active.id] + 1\n    } else {\n      multipleSelection.value = val\n    }\n  } else {\n    multipleSelection.value = val\n  }\n}\nconst dialogVisible = ref(false)\nconst radioPermission = ref('')\nfunction openMulConfigureDialog() {\n  if (multipleSelection.value.length === 0) {\n    return\n  }\n  dialogVisible.value = true\n}\nfunction submitDialog() {\n  if (multipleSelection.value.length === 0 || !radioPermission.value) {\n    return\n  }\n  const obj = multipleSelection.value.map((item) => ({\n    target_id: item.id,\n    permission: radioPermission.value,\n  }))\n  emit('submitPermissions', obj)\n  closeDialog()\n}\nfunction closeDialog() {\n  dialogVisible.value = false\n  radioPermission.value = ''\n  multipleSelection.value = []\n  multipleTableRef.value?.clearSelection()\n}\nfunction getResourcesByFolderId(treeData: any[], folderId: string): any[] {\n  const result: any[] = []\n  let target: any = null\n\n  function dfs(nodes: any[]) {\n    for (const node of nodes) {\n      if (node.id === folderId) {\n        target = node\n        return\n      }\n      if (node.children?.length) {\n        dfs(node.children)\n        if (target) return\n      }\n    }\n  }\n\n  function collect(node: any) {\n    if (!node?.children) return\n    for (const child of node.children) {\n      result.push(child)\n      collect(child)\n    }\n  }\n\n  dfs(treeData)\n\n  if (target) {\n    collect(target)\n  }\n\n  return result\n}\nfunction submitPermissions(value: string, row: any) {\n  const obj = [\n    {\n      target_id: row.id,\n      permission: value,\n    },\n  ]\n  const emitSubmitPermissions = (treeData: any[], ids: Array<string>, result: Array<any>) => {\n    if (!treeData || treeData.length === 0) return []\n    for (const node of treeData) {\n      const isRecursion = node.permission == 'NOT_AUTH' && ids.includes(node.id)\n      if (node.children && node.children.length > 0 && !isRecursion) {\n        emitSubmitPermissions(node.children, ids, result)\n      }\n      const isMatch = node.permission == 'NOT_AUTH' && ids.includes(node.id)\n      if (isMatch) {\n        ids.push(node.folder_id)\n        result.push({\n          target_id: node.id,\n          permission: 'VIEW',\n        })\n      }\n    }\n    return result\n  }\n\n  if (['VIEW', 'MANAGE', 'ROLE'].includes(value)) {\n    emitSubmitPermissions(props.data, [row.folder_id], obj)\n  }\n  if (['NOT_AUTH'].includes(value) && 'folder' == row.resource_type) {\n    getResourcesByFolderId(props.data, row.id).forEach((n) => {\n      obj.push({ target_id: n.id, permission: 'NOT_AUTH' })\n    })\n  }\n  emit('submitPermissions', obj)\n}\nconst provider_list = ref<Array<Provider>>([])\n\nfunction getProvider() {\n  model.asyncGetProvider().then((res: any) => {\n    provider_list.value = res?.data\n  })\n}\n\nconst getProviderIcon = computed(() => {\n  return (row: any) => {\n    return provider_list.value.find((p) => p.provider === row.icon)?.icon\n  }\n})\nonMounted(() => {\n  if (isModel.value) {\n    getProvider()\n  }\n})\n\ndefineExpose({\n  searchForm,\n  searchType,\n})\n</script>\n<style lang=\"scss\" scoped>\n.permission-setting {\n  flex: 1;\n  overflow: hidden;\n  box-sizing: border-box;\n  width: 100%;\n  flex-direction: column;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system/resource-authorization/constant.ts",
    "content": "import { AuthorizationEnum } from '@/enums/system'\nimport { t } from '@/locales'\nimport { hasPermission } from '@/utils/permission'\nimport { EditionConst } from '@/utils/permission/data'\n \n \nconst permissionOptions = [\n  {\n    label: t('views.system.resourceAuthorization.setting.notAuthorized'),\n    value: AuthorizationEnum.NOT_AUTH,\n    desc: '',\n  },\n  {\n    label: t('views.system.resourceAuthorization.setting.check'),\n    value: AuthorizationEnum.VIEW,\n    desc: t('views.system.resourceAuthorization.setting.checkDesc'),\n  },\n  {\n    label: t('views.system.resourceAuthorization.setting.management'),\n    value: AuthorizationEnum.MANAGE,\n    desc: t('views.system.resourceAuthorization.setting.managementDesc'),\n  },\n]\n\n \nconst getPermissionOptions = (isFolder = false, isRootFolder = false) => {\n  if (isFolder && isRootFolder) {\n    return permissionOptions.filter(\n      item => item.value === AuthorizationEnum.VIEW || item.value === AuthorizationEnum.MANAGE\n    )\n  }\n  if (isFolder) {\n    return permissionOptions\n  }\n  if (hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')) { \n    return [...permissionOptions,{\n    label: t('views.system.resourceAuthorization.setting.role'),\n    value: AuthorizationEnum.ROLE,\n    desc: t('views.system.resourceAuthorization.setting.roleDesc'),\n  },]\n  }\n  return permissionOptions;\n}\n\nexport {getPermissionOptions}"
  },
  {
    "path": "ui/src/views/system/resource-authorization/index.vue",
    "content": "<template>\n  <div class=\"resource-authorization p-16-24\">\n    <div class=\"flex align-center mb-16\">\n      <el-breadcrumb separator-icon=\"ArrowRight\">\n        <el-breadcrumb-item>{{ t('views.system.resourceAuthorization.title') }}</el-breadcrumb-item>\n        <el-breadcrumb-item>\n          <h5 class=\"ml-4 color-text-primary\">{{ activeData.label }}</h5>\n        </el-breadcrumb-item>\n      </el-breadcrumb>\n      <!-- 企业版: 工作空间下拉框-->\n      <el-divider\n        class=\"ml-24\"\n        direction=\"vertical\"\n        v-if=\"hasPermission(EditionConst.IS_EE, 'OR')\"\n      />\n      <WorkspaceDropdown\n        v-if=\"hasPermission(EditionConst.IS_EE, 'OR')\"\n        :data=\"workspaceList\"\n        :currentWorkspace=\"currentWorkspace\"\n        @changeWorkspace=\"changeWorkspace\"\n      />\n    </div>\n\n    <el-card style=\"--el-card-padding: 0; height: calc(100vh - 140px)\">\n      <div class=\"flex\">\n        <div class=\"resource-authorization__left border-r\">\n          <div class=\"p-24 pb-0\">\n            <h4 class=\"mb-12\">{{ $t('views.system.resourceAuthorization.member') }}</h4>\n            <el-input\n              v-model=\"filterText\"\n              :placeholder=\"$t('common.search')\"\n              prefix-icon=\"Search\"\n              clearable\n            />\n          </div>\n          <div class=\"list-height-left\">\n            <el-scrollbar>\n              <div class=\"p-8-16\">\n                <common-list\n                  :data=\"filterMember\"\n                  v-loading=\"loading\"\n                  @click=\"clickMemberHandle\"\n                  :default-active=\"currentUser\"\n                >\n                  <template #default=\"{ row }\">\n                    <div class=\"flex-between\">\n                      <div class=\"flex\">\n                        <span class=\"mr-8 ellipsis-1\" :title=\"row.nick_name\">{{\n                          i18n_name(row.nick_name)\n                        }}</span>\n                        <el-text\n                          class=\"color-input-placeholder ellipsis-1\"\n                          :title=\"row.roles.join('，')\"\n                          v-if=\"hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')\"\n                          >({{\n                            row.roles.map((item: any) => i18n_name(item))?.join('，')\n                          }})</el-text\n                        >\n                      </div>\n                    </div>\n                  </template>\n                </common-list>\n              </div>\n            </el-scrollbar>\n          </div>\n        </div>\n        <PermissionTable\n          :data=\"treeData\"\n          :type=\"activeData.type\"\n          ref=\"PermissionTableRef\"\n          :getData=\"getPermissionList\"\n          @submitPermissions=\"submitPermissions\"\n        />\n      </div>\n    </el-card>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport PermissionTable from '@/views/system/resource-authorization/component/PermissionTable.vue'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { SourceTypeEnum } from '@/enums/common'\nimport { t } from '@/locales'\nimport AuthorizationApi from '@/api/system/resource-authorization'\nimport { EditionConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport type { WorkspaceItem } from '@/api/type/workspace'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\n\nimport useStore from '@/stores'\nimport { i18n_name } from '@/utils/common'\n\nconst route = useRoute()\nconst { user } = useStore()\nconst loading = ref(false)\nconst rLoading = ref(false)\nconst memberList = ref<any[]>([]) // 全部成员\nconst filterMember = ref<any[]>([]) // 搜索过滤后列表\nconst currentUser = ref<string>('')\nconst currentType = ref<string>('')\nconst filterText = ref('')\nconst permissionData = ref<any[]>([])\n\nconst settingTags = reactive([\n  {\n    label: t('views.knowledge.title'),\n    type: SourceTypeEnum.KNOWLEDGE,\n  },\n  {\n    label: t('views.application.title'),\n    type: SourceTypeEnum.APPLICATION,\n  },\n  {\n    label: t('views.tool.title'),\n    type: SourceTypeEnum.TOOL,\n  },\n  {\n    label: t('views.model.title'),\n    type: SourceTypeEnum.MODEL,\n  },\n])\n// 当前激活的数据类型（应用/知识库/模型/工具）\nconst activeData = computed(() => {\n  const lastIndex = route.path.lastIndexOf('/')\n  const currentPathType = route.path.substring(lastIndex + 1).toUpperCase()\n  return settingTags.filter((item) => {\n    return item.type === currentPathType\n  })[0]\n})\n\nwatch(filterText, (val: any) => {\n  if (val) {\n    filterMember.value = memberList.value.filter((v: any) =>\n      v.nick_name.toLowerCase().includes(val.toLowerCase()),\n    )\n  } else {\n    filterMember.value = memberList.value\n  }\n})\n\nfunction submitPermissions(obj: any) {\n  const workspaceId = currentWorkspaceId.value || user.getWorkspaceId() || 'default'\n  AuthorizationApi.putResourceAuthorization(\n    workspaceId,\n    currentUser.value,\n    (route.meta?.resource as string) || 'APPLICATION',\n    obj,\n    rLoading,\n  ).then(() => {\n    MsgSuccess(t('common.submitSuccess'))\n    getPermissionList()\n  })\n}\n\nconst PermissionTableRef = ref()\n\nconst getPermissionList = () => {\n  const workspaceId = currentWorkspaceId.value || user.getWorkspaceId() || 'default'\n  const params: any = {}\n  AuthorizationApi.getResourceAuthorization(\n    workspaceId,\n    currentUser.value,\n    (route.meta?.resource as string) || 'APPLICATION',\n    params,\n    rLoading,\n  ).then((res) => {\n    const resourceType = (route.meta?.resource as string) || 'APPLICATION'\n\n    if (resourceType === 'MODEL') {\n      permissionData.value = res.data || []\n    } else {\n      permissionData.value =\n        res.data.map((item: any) => {\n          if (!item.folder_id && item.permission === 'NOT_AUTH') {\n            return { ...item, permission: 'VIEW' }\n          }\n          return item\n        }) || []\n    }\n  })\n}\n\nconst toTree = (nodeList: any, pField: any) => {\n  if (!nodeList || nodeList.length === 0) return []\n\n  const list = JSON.parse(JSON.stringify(nodeList))\n\n  if (!pField) {\n    pField = 'parentId'\n  }\n  const nodeMap = Object.fromEntries(list.map((item: any) => [item.id, item]))\n\n  for (let index = 0; index < nodeList.length; index++) {\n    const element = list[index]\n    if (!element.children) {\n      element.children = []\n    }\n    if (element[pField]) {\n      const pNode = nodeMap[element[pField]]\n      if (pNode) {\n        if (!pNode.children) {\n          pNode.children = []\n        }\n        pNode.children.push(element)\n      }\n    }\n  }\n  return list.filter((item: any) => !item[pField])\n}\n\nconst treeData = computed(() => {\n  const resourceType = (route.meta?.resource as string) || 'APPLICATION'\n  if (resourceType === 'MODEL') {\n    return permissionData.value\n  }\n  return toTree(permissionData.value, 'folder_id')\n})\n\nfunction clickMemberHandle(item: any) {\n  currentUser.value = item.id\n  currentType.value = item.type\n  getPermissionList()\n}\n\nfunction getMember(id?: string) {\n  const workspaceId = currentWorkspaceId.value || user.getWorkspaceId() || 'default'\n  AuthorizationApi.getUserMember(workspaceId, loading).then((res) => {\n    memberList.value = res.data\n    filterMember.value = res.data\n    if (memberList.value.length > 0) {\n      const member = (id && memberList.value.find((p: any) => p.user_id === id)) || null\n      currentUser.value = member ? member.id : memberList.value?.[0]?.id\n      currentType.value = member ? member.type : memberList.value?.[0]?.type\n      getPermissionList()\n    } else {\n      permissionData.value = []\n    }\n  })\n}\n\nconst workspaceList = ref<WorkspaceItem[]>([])\nconst currentWorkspaceId = ref<string | undefined>('')\nconst currentWorkspace = computed(() => {\n  return workspaceList.value.find((w) => w.id == currentWorkspaceId.value)\n})\nasync function getWorkspaceList() {\n  const res = await loadPermissionApi('workspace').getSystemWorkspaceList(loading)\n  workspaceList.value = res.data\n  currentWorkspaceId.value = (user.getWorkspaceId() as string) || 'default'\n}\n\nfunction changeWorkspace(item: WorkspaceItem) {\n  currentWorkspaceId.value = item.id\n  getMember()\n}\n\nonMounted(() => {\n  if (user.isEE()) {\n    getWorkspaceList()\n  }\n  getMember()\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.resource-authorization {\n  .resource-authorization__left {\n    box-sizing: border-box;\n    width: var(--setting-left-width);\n    min-width: var(--setting-left-width);\n  }\n  .list-height-left {\n    height: calc(100vh - 240px);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system/role/component/AddMemberDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"600\" :destroy-on-close=\"true\" :before-close=\"handleCancel\">\n    <template #header>\n      <h4>{{ $t('views.role.member.add') }}</h4>\n    </template>\n    <template #default>\n      <MemberFormContent\n        ref=\"memberFormContentRef\"\n        :models=\"formItemModel\"\n        v-model:form=\"list\"\n        v-loading=\"memberFormContentLoading\"\n        keepOneLine\n      />\n    </template>\n    <template #footer>\n      <el-button @click=\"handleCancel\">{{ $t('common.cancel') }}</el-button>\n      <el-button type=\"primary\" @click=\"handleAdd()\" :loading=\"loading\">\n        {{ $t('common.add') }}\n      </el-button>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport {onBeforeMount, ref} from 'vue'\nimport type {CreateMemberParamsItem, FormItemModel} from '@/api/type/role'\nimport UserApi from '@/api/user/user'\nimport WorkspaceApi from '@/api/workspace/workspace'\nimport MemberFormContent from './MemberFormContent.vue'\nimport {t} from '@/locales'\nimport type {RoleItem} from '@/api/type/role'\nimport {MsgSuccess} from '@/utils/message'\nimport {RoleTypeEnum} from '@/enums/system'\nimport {loadPermissionApi} from '@/utils/dynamics-api/permission-api'\nimport useStore from \"@/stores\";\n\nconst {user} = useStore()\nconst props = defineProps<{\n  currentRole?: RoleItem\n}>()\n\nconst emit = defineEmits<{\n  (e: 'refresh'): void\n}>()\n\nconst loading = ref(false)\nconst visible = ref(false)\nconst list = ref<CreateMemberParamsItem[]>([])\n\nconst memberFormContentLoading = ref(false)\nconst formItemModel = ref<FormItemModel[]>([])\nconst userFormItem = ref<FormItemModel[]>([])\nconst workspaceFormItem = ref<FormItemModel[]>([])\n\nasync function getUserFormItem() {\n  try {\n    const res = await UserApi.getUserList(memberFormContentLoading)\n    userFormItem.value = [\n      {\n        path: 'user_ids',\n        label: t('views.role.member.title'),\n        rules: [\n          {\n            required: true,\n            message: `${t('common.selectPlaceholder')}${t('views.role.member.title')}`,\n          },\n        ],\n        selectProps: {\n          options:\n            res.data?.map((item) => ({\n              label: item.nick_name,\n              value: item.id,\n            })) || [],\n          placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.title')}`,\n        },\n      },\n    ]\n  } catch (e) {\n    console.error(e)\n  }\n}\n\nasync function getWorkspaceFormItem() {\n  try {\n    const res = await loadPermissionApi('workspace').getWorkspaceList(memberFormContentLoading)\n    workspaceFormItem.value = [\n      {\n        path: 'workspace_ids',\n        label: t('views.role.member.workspace'),\n        rules: [\n          {\n            required: true,\n            message: `${t('common.selectPlaceholder')}${t('views.role.member.workspace')}`,\n          },\n        ],\n        selectProps: {\n          options:\n            res.data?.map((item: any) => ({\n              label: item.name,\n              value: item.id,\n            })) || [],\n          placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.workspace')}`,\n        },\n      },\n    ]\n  } catch (e) {\n    console.error(e)\n  }\n}\n\nfunction init() {\n  if (props.currentRole?.type !== RoleTypeEnum.ADMIN) {\n    formItemModel.value = [...userFormItem.value, ...workspaceFormItem.value]\n    list.value = [{user_ids: [], workspace_ids: []}]\n  } else {\n    formItemModel.value = [...userFormItem.value]\n    list.value = [{user_ids: []}]\n  }\n}\n\nonBeforeMount(async () => {\n  await getUserFormItem()\n  if (user.isEE()) {\n    await getWorkspaceFormItem()\n  }\n  init()\n})\n\nfunction open() {\n  init()\n  visible.value = true\n}\n\nfunction handleCancel() {\n  visible.value = false\n}\n\nconst memberFormContentRef = ref<InstanceType<typeof MemberFormContent>>()\n\nfunction handleAdd() {\n  memberFormContentRef.value?.validate().then(async (valid: any) => {\n    if (valid) {\n      let params\n      if (props.currentRole?.type === RoleTypeEnum.ADMIN) {\n        params = list.value.map((item) => ({user_ids: item.user_ids, workspace_ids: ['None']}))\n      } else if (user.isPE()) {\n        params = list.value.map((item) => ({user_ids: item.user_ids, workspace_ids: ['default']}))\n      }\n      await loadPermissionApi('role').CreateMember(\n        props.currentRole?.id as string,\n        {members: params ?? list.value},\n        loading,\n      )\n      MsgSuccess(t('common.addSuccess'))\n      handleCancel()\n      emit('refresh')\n    }\n  })\n}\n\ndefineExpose({open})\n</script>\n"
  },
  {
    "path": "ui/src/views/system/role/component/CreateOrUpdateRoleDialog.vue",
    "content": "<template>\n  <el-dialog :title=\"`${!form.role_id ? $t('common.create') : $t('common.rename')}${$t('views.role.customRole')}`\"\n    v-model=\"dialogVisible\" :close-on-click-modal=\"false\" :close-on-press-escape=\"false\" :destroy-on-close=\"true\">\n    <el-form label-position=\"top\" ref=\"formRef\" :rules=\"rules\" :model=\"form\" require-asterisk-position=\"right\">\n      <el-form-item :label=\"$t('views.role.roleName')\" prop=\"role_name\">\n        <el-input v-model=\"form.role_name\" maxlength=\"64\" show-word-limit\n          :placeholder=\"`${$t('common.inputPlaceholder')}${$t('views.role.roleName')}`\" />\n      </el-form-item>\n      <el-form-item v-if=\"!form.role_id\" :label=\"$t('views.role.inheritingRole')\" prop=\"role_type\">\n        <el-select v-model=\"form.role_type\"\n          :placeholder=\"`${$t('common.selectPlaceholder')}${$t('views.role.inheritingRole')}`\">\n          <el-option v-for=\"(label, value) in roleTypeMap\" :key=\"value\" :label=\"label\" :value=\"value\" />\n        </el-select>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(formRef)\" :loading=\"loading\">\n          {{ !form.role_id ? $t('common.create') : $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport type { RoleItem, CreateOrUpdateParams } from '@/api/type/role'\nimport RoleApi from '@/api/system/role'\nimport { roleTypeMap } from '../index'\nimport {loadPermissionApi} from \"@/utils/dynamics-api/permission-api.ts\";\n\nconst emit = defineEmits<{\n  (e: 'refresh', currentRole: RoleItem): void;\n}>();\n\nconst dialogVisible = ref<boolean>(false)\nconst defaultForm = {\n  role_name: ''\n}\nconst form = ref<CreateOrUpdateParams>({\n  ...defaultForm,\n})\nfunction open(item?: RoleItem) {\n  if (item) {\n    form.value = {\n      role_name: item.role_name,\n      role_type: item.type,\n      role_id: item.id,\n    }\n  } else {\n    form.value = { ...defaultForm }\n  }\n  dialogVisible.value = true\n}\n\nconst formRef = ref<FormInstance>();\n\nconst rules = reactive({\n  role_name: [{ required: true, message: `${t('common.inputPlaceholder')}${t('views.role.roleName')}`, trigger: 'blur' }],\n  role_type: [{ required: true, message: `${t('common.selectPlaceholder')}${t('views.role.inheritingRole')}`, trigger: 'blur' }]\n})\n\nconst loading = ref<boolean>(false)\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      loadPermissionApi('role').CreateOrUpdateRole(form.value, loading).then((res: any) => {\n        MsgSuccess(!form.value.role_id ? t('common.createSuccess') : t('common.renameSuccess'))\n        emit('refresh', res.data)\n        dialogVisible.value = false\n      })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n"
  },
  {
    "path": "ui/src/views/system/role/component/Member.vue",
    "content": "<template>\n  <div>\n    <div class=\"flex-between mb-16\">\n      <el-button\n        type=\"primary\"\n        @click=\"handleAdd\"\n        v-hasPermission=\"\n          new ComplexPermission(\n            [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n            [PermissionConst.WORKSPACE_ROLE_ADD_MEMBER, PermissionConst.ROLE_ADD_MEMBER],\n            [],\n            'OR',\n          )\n        \"\n      >\n        {{ $t('views.role.member.add') }}\n      </el-button>\n      <div class=\"flex complex-search\">\n        <el-select class=\"complex-search__left\" v-model=\"searchType\" style=\"width: 120px\">\n          <el-option :label=\"$t('views.login.loginForm.username.label')\" value=\"username\" />\n          <el-option :label=\"$t('views.userManage.userForm.nick_name.label')\" value=\"nick_name\" />\n        </el-select>\n        <el-input\n          v-if=\"searchType === 'username'\"\n          v-model=\"searchForm.username\"\n          @change=\"getList\"\n          :placeholder=\"$t('common.inputPlaceholder')\"\n          style=\"width: 220px\"\n          clearable\n        />\n        <el-input\n          v-else-if=\"searchType === 'nick_name'\"\n          v-model=\"searchForm.nick_name\"\n          @change=\"getList\"\n          :placeholder=\"$t('common.inputPlaceholder')\"\n          style=\"width: 220px\"\n          clearable\n        />\n      </div>\n    </div>\n    <app-table\n      :class=\"`${props.currentRole?.type !== RoleTypeEnum.ADMIN ? 'member-table' : ''} mt-16`\"\n      :data=\"tableData\"\n      :pagination-config=\"paginationConfig\"\n      @sizeChange=\"handleSizeChange\"\n      @changePage=\"getList\"\n      v-loading=\"loading\"\n      :span-method=\"objectSpanMethod\"\n      :maxTableHeight=\"330\"\n    >\n      <el-table-column prop=\"nick_name\" :label=\"$t('views.userManage.userForm.nick_name.label')\" />\n      <el-table-column prop=\"username\" :label=\"$t('views.login.loginForm.username.label')\" />\n      <el-table-column\n        v-if=\"props.currentRole?.type !== RoleTypeEnum.ADMIN\"\n        prop=\"workspace_name\"\n        :label=\"$t('views.role.member.workspace')\"\n      />\n      <el-table-column :label=\"$t('common.operation')\" width=\"100\" fixed=\"right\">\n        <template #default=\"{ row }\">\n          <el-tooltip\n            effect=\"dark\"\n            :content=\"`${$t('views.role.member.delete.button')}`\"\n            placement=\"top\"\n          >\n            <el-button\n              type=\"primary\"\n              text\n              @click.stop=\"handleDelete(row)\"\n              v-hasPermission=\"\n                new ComplexPermission(\n                  [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                  [\n                    PermissionConst.ROLE_REMOVE_MEMBER,\n                    PermissionConst.WORKSPACE_ROLE_REMOVE_MEMBER,\n                  ],\n                  [],\n                  'OR',\n                )\n              \"\n            >\n              <AppIcon iconName=\"app-delete-users\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </template>\n      </el-table-column>\n    </app-table>\n  </div>\n  <AddMemberDrawer ref=\"addMemberDrawerRef\" :currentRole=\"props.currentRole\" @refresh=\"getList\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref, reactive, watch } from 'vue'\nimport type { RoleItem, RoleMemberItem } from '@/api/type/role'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { t } from '@/locales'\nimport AddMemberDrawer from './AddMemberDrawer.vue'\nimport { RoleTypeEnum } from '@/enums/system'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\nconst props = defineProps<{\n  currentRole?: RoleItem\n}>()\n\nconst loading = ref(false)\n\nconst searchType = ref('username')\nconst searchForm = ref<Record<string, any>>({\n  username: '',\n  nick_name: '',\n})\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst tableData = ref<RoleMemberItem[]>([])\n\nasync function getList() {\n  try {\n    const params = {\n      [searchType.value]: searchForm.value[searchType.value],\n    }\n    const res = await loadPermissionApi('role').getRoleMemberList(\n      props.currentRole?.id as string,\n      paginationConfig,\n      params,\n      loading,\n    )\n    tableData.value = res.data.records\n    paginationConfig.total = res.data.total\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getList()\n}\n\nonMounted(() => {\n  getList()\n})\n\nwatch(\n  () => props.currentRole?.id,\n  () => {\n    getList()\n  },\n)\n\nconst objectSpanMethod = ({ row, column, rowIndex, columnIndex }: any) => {\n  if (column.property === 'nick_name' || column.property === 'username') {\n    const sameUserRows = tableData.value.filter((item) => item.user_id === row.user_id)\n    if (rowIndex === tableData.value.findIndex((item) => item.user_id === row.user_id)) {\n      return {\n        rowspan: sameUserRows.length,\n        colspan: 1,\n      }\n    } else {\n      return {\n        rowspan: 0,\n        colspan: 0,\n      }\n    }\n  }\n}\n\nconst addMemberDrawerRef = ref<InstanceType<typeof AddMemberDrawer>>()\n\nfunction handleAdd() {\n  addMemberDrawerRef.value?.open()\n}\n\nfunction handleDelete(row: RoleMemberItem) {\n  MsgConfirm(`${t('views.role.member.delete.confirmTitle')}${row.nick_name} ?`, '', {\n    confirmButtonText: t('common.confirm'),\n    confirmButtonClass: 'danger',\n  })\n    .then(() => {\n      loading.value = true\n      loadPermissionApi('role')\n        .deleteRoleMember(props.currentRole?.id as string, row.user_relation_id, loading)\n        .then(() => {\n          MsgSuccess(t('common.deleteSuccess'))\n          getList()\n        })\n    })\n    .catch(() => {})\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.member-table :deep(.el-table__cell):nth-child(2) {\n  border-right: 1px solid var(--el-table-border-color);\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system/role/component/MemberFormContent.vue",
    "content": "<template>\n  <el-form :model=\"form\" ref=\"formRef\" label-position=\"top\" require-asterisk-position=\"right\">\n    <el-scrollbar>\n      <div v-for=\"(element, index) in form\" :key=\"index\" class=\"flex w-full\">\n        <el-form-item\n          v-for=\"model of props.models\"\n          :key=\"model.path\"\n          :prop=\"`[${index}].${model.path}`\"\n          :rules=\"model.rules\"\n          :label=\"index === 0 && model.label ? model.label : ''\"\n          class=\"mr-8\"\n          style=\"flex: 1\"\n        >\n          <el-select\n            v-if=\"!model?.hidden?.(element)\"\n            v-model=\"element[model.path]\"\n            :placeholder=\"model.selectProps?.placeholder ?? $t('common.selectPlaceholder')\"\n            :clearable=\"\n              model.selectProps?.clearableFunction\n                ? model.selectProps?.clearableFunction?.(element)\n                : true\n            \"\n            filterable\n            multiple\n            :reserve-keyword=\"false\"\n            style=\"width: 100%\"\n            collapse-tags\n            collapse-tags-tooltip\n            v-bind=\"model.selectProps\"\n          >\n            <el-option\n              v-for=\"opt in model.selectProps?.options\"\n              :key=\"opt.value\"\n              :label=\"opt.label\"\n              :value=\"opt.value\"\n              :disabled=\"selectedRoles.includes(opt.value)\"\n            >\n              <el-tooltip effect=\"dark\" :content=\"opt.label\" placement=\"top\" :show-after=\"500\">\n                <div class=\"ellipsis\" style=\"max-width: 190px\">{{ opt.label }}</div>\n              </el-tooltip>\n            </el-option>\n          </el-select>\n        </el-form-item>\n        <!-- 删除按钮 -->\n        <el-button\n          :disabled=\"\n            (props.keepOneLine && form.length === 1) || props.deleteButtonDisabled?.(element)\n          \"\n          @click=\"handleDelete(index)\"\n          text\n          :style=\"{\n            'margin-top': index === 0 && props.models.some((item) => item.label) ? '32px' : '2px',\n          }\"\n        >\n          <AppIcon iconName=\"app-delete\"></AppIcon>\n        </el-button>\n      </div>\n    </el-scrollbar>\n    <!-- 添加按钮 -->\n    <el-button type=\"primary\" text class=\"mt-2\" @click=\"handleAdd\" v-if=\"needAddButton\">\n      <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n      {{ props.addText ?? $t('views.role.member.add') }}\n    </el-button>\n  </el-form>\n</template>\n\n<script setup lang=\"ts\">\nimport {ref, watch, computed} from 'vue'\nimport type {FormItemModel} from '@/api/type/role'\n\nconst props = withDefaults(defineProps<{\n  models: FormItemModel[]\n  addText?: string\n  keepOneLine?: boolean\n  deleteButtonDisabled?: (model: any) => boolean\n  needAddButton?: boolean\n}>(), {\n  needAddButton: true\n})\n\n\nconst formRef = ref()\nconst formItem: Record<string, any> = {}\nconst form = defineModel<Record<string, any>[]>('form', {\n  default: [],\n})\n\nconst selectedRoles = computed(() => {\n  return form.value.map((item) => item.role_id)\n})\n\nfunction handleAdd() {\n  form.value.push({...formItem})\n}\n\nwatch(\n  () => props.models,\n  () => {\n    props.models.forEach((e) => {\n      formItem[e.path] = []\n    })\n  },\n  {immediate: true},\n)\n\nfunction handleDelete(index: number) {\n  form.value.splice(index, 1)\n}\n\nconst validate = () => {\n  if (formRef.value) {\n    return formRef.value?.validate()\n  }\n  return Promise.resolve()\n}\n\nconst resetValidation = () => {\n  if (formRef.value) {\n    formRef.value.clearValidate()\n  }\n}\ndefineExpose({validate, resetValidation})\n</script>\n"
  },
  {
    "path": "ui/src/views/system/role/component/PermissionConfiguration.vue",
    "content": "<template>\n  <el-scrollbar v-loading=\"loading\">\n    <app-table :data=\"tableData\" border :span-method=\"objectSpanMethod\" :maxTableHeight=\"280\">\n      <el-table-column prop=\"module\" :width=\"150\" :label=\"$t('views.role.permission.moduleName')\"/>\n      <el-table-column\n        prop=\"name\"\n        :width=\"150\"\n        :label=\"$t('views.role.permission.operationTarget')\"\n      />\n      <el-table-column prop=\"permission\" :label=\"$t('views.model.modelForm.permissionType.label')\">\n        <template #default=\"{ row }\">\n          <el-checkbox\n            v-for=\"item in row.permission\"\n            :key=\"item.id\"\n            v-model=\"item.enable\"\n            :disabled=\"disabled\"\n            @change=\"(val: boolean) => handleCellChange(val, item, row)\"\n          >\n            <div class=\"ellipsis\" style=\"width: 96px\">{{ item.name }}</div>\n          </el-checkbox>\n        </template>\n      </el-table-column>\n      <el-table-column :width=\"40\">\n        <template #header>\n          <el-checkbox\n            :model-value=\"allChecked\"\n            :indeterminate=\"allIndeterminate\"\n            :disabled=\"disabled\"\n            @change=\"handleCheckAll\"\n          />\n        </template>\n        <template #default=\"{ row }\">\n          <el-checkbox\n            v-model=\"row.enable\"\n            :indeterminate=\"row.indeterminate\"\n            :disabled=\"disabled\"\n            @change=\"(value: boolean) => handleRowChange(value, row)\"\n          />\n        </template>\n      </el-table-column>\n    </app-table>\n  </el-scrollbar>\n  <div class=\"footer border-t\">\n    <el-button type=\"primary\" :disabled=\"disabled\" :loading=\"loading\" @click=\"handleSave\">\n      {{ $t('common.save') }}\n    </el-button>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport {ref, watch, computed} from 'vue'\nimport type {\n  RoleItem,\n  RolePermissionItem,\n  RoleTableDataItem,\n  ChildrenPermissionItem,\n} from '@/api/type/role'\nimport {loadPermissionApi} from '@/utils/dynamics-api/permission-api'\nimport RoleApi from '@/api/system/role'\nimport {MsgSuccess} from '@/utils/message'\nimport {t} from '@/locales'\nimport {hasPermission} from \"@/utils/permission\";\nimport {EditionConst, RoleConst} from \"@/utils/permission/data.ts\";\n\nconst props = defineProps<{\n  currentRole?: RoleItem\n}>()\n\nconst loading = ref(false)\nconst tableData = ref<RoleTableDataItem[]>([])\nconst needDisable = computed(() => {\n  const isEeOrPe = hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')\n  const isAdminOrExtendAdmin = hasPermission([RoleConst.ADMIN, RoleConst.EXTENDS_ADMIN], 'OR')\n  const isWorkspaceManage =\n    hasPermission(\n    [\n      RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n      RoleConst.EXTENDS_WORKSPACE_MANAGE.getWorkspaceRole,\n    ],\n      'OR'\n  )\n\n  if (!isEeOrPe) {\n    return false\n  }\n\n  if (isAdminOrExtendAdmin) {\n    return false\n  }\n\n  return isWorkspaceManage\n})\nconst disabled = computed(() => props.currentRole?.internal || needDisable.value)\n\nfunction transformData(data: RolePermissionItem[]) {\n  const transformedData: RoleTableDataItem[] = []\n  data.forEach((module) => {\n    module.children.forEach((feature) => {\n      const perChecked = feature.permission.filter((p) => p.enable).map((p) => p.id)\n\n      transformedData.push({\n        module: module.name,\n        name: feature.name,\n        permission: feature.permission,\n        enable: feature.enable,\n        perChecked,\n        indeterminate: perChecked.length > 0 && perChecked.length < feature.permission.length,\n      })\n    })\n  })\n  return transformedData\n}\n\nasync function getRolePermission() {\n  if (!props.currentRole?.id) return\n  try {\n    tableData.value = []\n    const res = await RoleApi.getRolePermissionList(props.currentRole.id, loading)\n    tableData.value = transformData(res.data)\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nfunction handleCellChange(\n  value: boolean,\n  item: ChildrenPermissionItem,\n  row: RoleTableDataItem,\n) {\n  item.enable = value\n  const readItem = row.permission.find((p) => /:READ$/.test(p.id))\n  // 如果勾选的不是 READ，则强制把 READ 也勾上\n  if (value && item.id !== readItem?.id && readItem && !readItem.enable) {\n    readItem.enable = true\n  } else if (!value && item.id === readItem?.id) {\n    // 取消 READ 整行其他权限全部取消\n    row.permission.forEach((p) => (p.enable = false))\n  }\n\n  const checkedIds = row.permission.filter((p) => p.enable).map((p) => p.id)\n  row.perChecked = checkedIds\n  row.enable = checkedIds.length === row.permission.length\n  row.indeterminate =\n    checkedIds.length > 0 && checkedIds.length < row.permission.length\n}\n\nfunction handleRowChange(checked: boolean, row: RoleTableDataItem) {\n  if (checked) {\n    row.permission.forEach((p) => (p.enable = true))\n  } else {\n    row.permission.forEach((p) => (p.enable = false))\n  }\n  row.perChecked = checked ? row.permission.map((p) => p.id) : []\n  row.indeterminate = false\n}\n\nconst allChecked = computed(() => {\n  return tableData.value.length > 0 && tableData.value.every((item) => item.enable)\n})\n\nconst allIndeterminate = computed(() => {\n  return !allChecked.value && tableData.value.some((item) => item.enable)\n})\n\nfunction handleCheckAll(checked: boolean) {\n  tableData.value.forEach((item) => {\n    item.enable = checked\n    item.perChecked = checked ? item.permission.map((p) => p.id) : []\n    item.indeterminate = false\n    item.permission.forEach((p) => (p.enable = checked))\n  })\n}\n\nconst objectSpanMethod = ({row, column, rowIndex, columnIndex}: any) => {\n  if (columnIndex === 0) {\n    const sameModuleRows = tableData.value.filter((item) => item.module === row.module)\n    const firstRowIndex = tableData.value.findIndex((item) => item.module === row.module)\n    if (rowIndex === firstRowIndex) {\n      return {\n        rowspan: sameModuleRows.length,\n        colspan: 1,\n      }\n    } else {\n      return {\n        rowspan: 0,\n        colspan: 0,\n      }\n    }\n  }\n}\n\nwatch(() => props.currentRole?.id, getRolePermission, {immediate: true})\n\nasync function handleSave() {\n  try {\n    const permissions = tableData.value.flatMap((row) =>\n      row.permission.map((p) => ({ id: p.id, enable: p.enable })),\n    )\n    await loadPermissionApi('role').saveRolePermission(props.currentRole?.id as string, permissions, loading)\n    MsgSuccess(t('common.saveSuccess'))\n  } catch (error) {\n    console.log(error)\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(.el-checkbox-group) {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 4px;\n}\n\n.footer {\n  width: 100%;\n  display: flex;\n  justify-content: flex-end;\n  padding: 16px 24px;\n  box-sizing: border-box;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system/role/index.ts",
    "content": "import {RoleTypeEnum} from '@/enums/system'\nimport {t} from '@/locales'\nimport useStore from '@/stores'\nimport {computed} from \"vue\";\n\nconst {user} = useStore()\nexport const roleTypeMap = computed(() => ({\n  ...(user.is_admin()\n    ? {\n      [RoleTypeEnum.ADMIN]: t('views.role.systemAdmin'),\n    }\n    : {}),\n  [RoleTypeEnum.USER]: t('views.role.user'),\n  [RoleTypeEnum.WORKSPACE_MANAGE]: t('views.role.workspaceAdmin'),\n}))\n"
  },
  {
    "path": "ui/src/views/system/role/index.vue",
    "content": "<template>\n  <div class=\"role-manage p-16-24\">\n    <h2 class=\"mb-16\">{{ $t('views.role.title') }}</h2>\n    <el-card style=\"--el-card-padding: 0\" class=\"main-calc-height\">\n      <div class=\"flex\">\n        <div class=\"role-left border-r\">\n          <div class=\"p-24 pb-0\">\n            <el-input\n              v-model=\"filterText\"\n              :placeholder=\"$t('common.search')\"\n              prefix-icon=\"Search\"\n              clearable\n            />\n          </div>\n          <div class=\"list-height-left\">\n            <el-scrollbar v-loading=\"loading\">\n              <div class=\"p-16\">\n                <div class=\"color-secondary lighter ml-8 mb-8\">\n                  <span>{{ $t('views.role.internalRole') }}</span>\n                </div>\n                <common-list\n                  :data=\"filterInternalRole\"\n                  @click=\"clickRole\"\n                  :default-active=\"currentRole?.id\"\n                  @mouseenter=\"mouseenter\"\n                  @mouseleave=\"mouseId = ''\"\n                >\n                  <template #default=\"{ row }\">\n                    <span class=\"ellipsis-1\" :title=\"row.role_name\">{{ i18n_name(row.role_name) }}</span>\n                  </template>\n                  <template #empty>\n                    <span></span>\n                  </template>\n                </common-list>\n\n                <div class=\"ml-8 border-t flex-between mb-8\" style=\"padding-top: 12px\">\n                  <span class=\"color-secondary lighter\">{{ $t('views.role.customRole') }}</span>\n                  <el-tooltip\n                    effect=\"dark\"\n                    :content=\"`${$t('common.create')}${$t('views.role.customRole')}`\"\n                    placement=\"top\"\n                  >\n                    <el-button\n                      type=\"primary\"\n                      text\n                      @click=\"createOrUpdateRole()\"\n                      v-hasPermission=\"\n                        new ComplexPermission(\n                          [RoleConst.ADMIN],\n                          [PermissionConst.ROLE_CREATE],\n                          [],\n                          'OR',\n                        )\n                      \"\n                    >\n                      <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n                    </el-button>\n                  </el-tooltip>\n                </div>\n                <common-list\n                  :data=\"filterCustomRole\"\n                  @click=\"clickRole\"\n                  :default-active=\"currentRole?.id\"\n                  @mouseenter=\"mouseenter\"\n                  @mouseleave=\"mouseId = ''\"\n                >\n                  <template #default=\"{ row }\">\n                    <div class=\"flex-between\">\n                      <span class=\"flex align-center mr-8\">\n                        <div class=\"ellipsis\" style=\"flex: 1\">{{ row.role_name }}</div>\n                        <span class=\"color-input-placeholder ml-4\"\n                          >({{ roleTypeMap[row.type as RoleTypeEnum] }})</span\n                        >\n                      </span>\n                      <div\n                        @click.stop\n                        v-show=\"mouseId === row.id\"\n                        v-if=\"editPermission() || delPermission()\"\n                      >\n                        <el-dropdown :teleported=\"false\" trigger=\"click\">\n                          <el-button text>\n                            <AppIcon iconName=\"app-more\"></AppIcon>\n                          </el-button>\n                          <template #dropdown>\n                            <el-dropdown-menu style=\"min-width: 80px\">\n                              <el-dropdown-item\n                                @click.stop=\"createOrUpdateRole(row)\"\n                                class=\"p-8\"\n                                v-if=\"editPermission()\"\n                              >\n                                <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n                                {{ $t('common.rename') }}\n                              </el-dropdown-item>\n                              <el-dropdown-item\n                                @click.stop=\"deleteRole(row)\"\n                                class=\"border-t p-8\"\n                                v-if=\"delPermission()\"\n                              >\n                                <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                                {{ $t('common.delete') }}\n                              </el-dropdown-item>\n                            </el-dropdown-menu>\n                          </template>\n                        </el-dropdown>\n                      </div>\n                    </div>\n                  </template>\n                  <template #empty>\n                    <span></span>\n                  </template>\n                </common-list>\n              </div>\n            </el-scrollbar>\n          </div>\n        </div>\n\n        <!-- 右边 -->\n        <div class=\"role-right p-24\" v-loading=\"loading\">\n          <div class=\"flex-between mb-16\">\n            <div class=\"flex align-center\">\n              <h4>\n                {{ i18n_name(currentRole?.role_name as string) }}\n              </h4>\n              <span\n                v-if=\"currentRole?.type && !currentRole.internal\"\n                class=\"color-input-placeholder ml-4\"\n                >({{ roleTypeMap[currentRole?.type as RoleTypeEnum] }})\n              </span>\n\n              <el-divider direction=\"vertical\" />\n              <el-icon class=\"color-input-placeholder\"><UserFilled /></el-icon>\n              <span class=\"color-input-placeholder ml-4\">\n                {{ currentRole?.user_count }}\n              </span>\n            </div>\n            <el-radio-group v-model=\"currentTab\" class=\"app-radio-button-group\">\n              <el-radio-button\n                v-for=\"item in tabList\"\n                :key=\"item.value\"\n                :label=\"item.label\"\n                :value=\"item.value\"\n              />\n            </el-radio-group>\n          </div>\n          <PermissionConfiguration v-if=\"currentTab === 'permission'\" :currentRole=\"currentRole\" />\n          <Member v-else :currentRole=\"currentRole\" />\n        </div>\n      </div>\n    </el-card>\n\n    <CreateOrUpdateRoleDialog ref=\"createOrUpdateRoleDialogRef\" @refresh=\"refresh\" />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, watch } from 'vue'\nimport { t } from '@/locales'\nimport { i18n_name } from '@/utils/common'\nimport PermissionConfiguration from './component/PermissionConfiguration.vue'\nimport Member from './component/Member.vue'\nimport CreateOrUpdateRoleDialog from './component/CreateOrUpdateRoleDialog.vue'\nimport type { RoleItem } from '@/api/type/role'\nimport { RoleTypeEnum } from '@/enums/system'\nimport { roleTypeMap } from './index'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { hasPermission } from '@/utils/permission/index'\n\nconst filterText = ref('')\nconst loading = ref(false)\nconst internalRoleList = ref<RoleItem[]>([])\nconst filterInternalRole = ref<RoleItem[]>([]) // 搜索过滤后列表\nconst customRoleList = ref<RoleItem[]>([])\nconst filterCustomRole = ref<RoleItem[]>([]) // 搜索过滤后列表\nconst currentRole = ref<RoleItem>()\n\nasync function getRole() {\n  try {\n    const res = await loadPermissionApi('role').getRoleList(loading)\n    internalRoleList.value = res.data.internal_role\n    customRoleList.value = res.data.custom_role\n    filterInternalRole.value = filter(internalRoleList.value, filterText.value)\n    filterCustomRole.value = filter(customRoleList.value, filterText.value)\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nconst editPermission = () => {\n  return hasPermission(\n    new ComplexPermission([RoleConst.ADMIN], [PermissionConst.ROLE_EDIT], [], 'OR'),\n    'OR',\n  )\n}\n\nconst delPermission = () => {\n  return hasPermission(\n    new ComplexPermission([RoleConst.ADMIN], [PermissionConst.ROLE_DELETE], [], 'OR'),\n    'OR',\n  )\n}\n\nonMounted(async () => {\n  await getRole()\n  currentRole.value = internalRoleList.value[0]\n})\n\nasync function refresh(role?: RoleItem) {\n  await getRole()\n  // 创建角色后选中新建的角色\n  if (role) {\n    currentRole.value = role\n  } else {\n    currentRole.value = customRoleList.value.find((item) => item.id === currentRole.value?.id)\n  }\n}\n\nfunction filter(list: RoleItem[], filterText: string) {\n  if (!filterText.length) {\n    return list\n  }\n  return list.filter((v: RoleItem) => v.role_name.toLowerCase().includes(filterText.toLowerCase()))\n}\n\nwatch(filterText, (val: string) => {\n  filterInternalRole.value = filter(internalRoleList.value, val)\n  filterCustomRole.value = filter(customRoleList.value, val)\n})\n\nfunction clickRole(item: RoleItem) {\n  currentRole.value = item\n}\n\nconst createOrUpdateRoleDialogRef = ref<InstanceType<typeof CreateOrUpdateRoleDialog>>()\n\nfunction createOrUpdateRole(item?: RoleItem) {\n  createOrUpdateRoleDialogRef.value?.open(item)\n}\n\nfunction deleteRole(item: RoleItem) {\n  MsgConfirm(\n    `${t('views.role.delete.confirmTitle')}${item.role_name} ?`,\n    t('views.role.delete.confirmMessage'),\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loadPermissionApi('role')\n        .deleteRole(item.id, loading)\n        .then(async () => {\n          MsgSuccess(t('common.deleteSuccess'))\n          await getRole()\n          currentRole.value =\n            item.id === currentRole.value?.id ? internalRoleList.value[0] : currentRole.value\n        })\n    })\n    .catch(() => {})\n}\n\nconst currentTab = ref('permission')\nconst tabList = [\n  {\n    value: 'permission',\n    label: t('views.role.permission.title'),\n  },\n  {\n    value: 'member',\n    label: t('views.role.member.title'),\n  },\n]\nconst mouseId = ref('')\n\nfunction mouseenter(row: any) {\n  mouseId.value = row.id\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.role-manage {\n  .role-left {\n    box-sizing: border-box;\n    width: var(--setting-left-width);\n    min-width: var(--setting-left-width);\n\n    .list-height-left {\n      height: calc(100vh - 200px);\n    }\n  }\n  .role-right {\n    flex: 1;\n    overflow: hidden;\n    display: flex;\n    flex-direction: column;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system/user-manage/component/SetUserRoleDialog.vue",
    "content": "<template>\n  <el-dialog\n    width=\"600\"\n    :title=\"$t('views.userManage.settingRole')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"formRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item :label=\"$t('views.chatUser.settingMethod')\">\n        <el-radio-group v-model=\"form.is_append\">\n          <el-radio :value=\"true\">{{ $t('views.chatUser.append') }}</el-radio>\n          <el-radio :value=\"false\"\n            >{{ $t('views.applicationOverview.SettingDisplayDialog.replace') }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n\n      <MemberFormContent\n        ref=\"memberFormContentRef\"\n        :models=\"formItemModel\"\n        v-model:form=\"list\"\n        v-loading=\"memberFormContentLoading\"\n        keepOneLine\n        :need-add-button=\"!user.isPE()\"\n        :addText=\"$t('views.userManage.addRole')\"\n        v-if=\"user.isEE() || user.isPE()\"\n      />\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(formRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive, onBeforeMount } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { t } from '@/locales'\n\nconst memberFormContentLoading = ref(false)\nimport userManageApi from '@/api/system/user-manage'\nimport MemberFormContent from '@/views/system/role/component/MemberFormContent.vue'\nimport type { FormItemModel } from '@/api/type/role.ts'\nimport WorkspaceApi from '@/api/workspace/workspace.ts'\nimport useStore from '@/stores'\nimport { RoleTypeEnum } from '@/enums/system.ts'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\nimport { MsgSuccess } from '@/utils/message.ts'\n\nconst list = ref<any[]>([])\nconst formItemModel = ref<FormItemModel[]>([])\nconst { user, common } = useStore()\nconst workspaceFormItem = ref<FormItemModel[]>([])\nconst roleFormItem = ref<FormItemModel[]>([])\n\nconst emit = defineEmits<{\n  (e: 'refresh'): void\n}>()\n\nconst dialogVisible = ref<boolean>(false)\nconst defaultForm = {\n  role_ids: [],\n  is_append: true,\n  ids: [],\n}\nconst form = ref<{\n  ids: string[]\n  role_ids: string[]\n  is_append: boolean\n}>({\n  ...defaultForm,\n})\n\nfunction open(ids: string[]) {\n  form.value = { ...defaultForm, ids }\n  list.value = [{ role_id: '', workspace_ids: [] }]\n   if (memberFormContentRef.value) {\n    memberFormContentRef.value.resetValidation()\n  }\n  dialogVisible.value = true\n}\n\nconst formRef = ref<FormInstance>()\nconst adminRoleList = ref<any[]>([])\nconst rules = reactive({\n  is_append: [{ required: true, message: t('common.selectPlaceholder'), trigger: 'blur' }],\n})\n\nconst memberFormContentRef = ref<InstanceType<typeof MemberFormContent>>()\nconst loading = ref<boolean>(false)\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate(async (valid) => {\n    if (valid) {\n      if (memberFormContentRef.value) {\n        await memberFormContentRef.value?.validate()\n      }\n      if (user.isPE()) {\n        const data = {\n          is_append: form.value.is_append,\n          ids: form.value.ids,\n          role_ids: list.value[0].role_id,\n        }\n        userManageApi.batchSetRolePE(data, loading).then(() => {\n          MsgSuccess(t('common.settingSuccess'))\n          emit('refresh')\n          dialogVisible.value = false\n        })\n      }\n      if (user.isEE()) {\n        list.value = list.value.map((item) => {\n          const isAdminRole = adminRoleList.value.find((item1) => item1.id === item.role_id)\n\n          // 如果是管理员角色，则设置为 ['None']\n          if (isAdminRole) {\n            return {...item, workspace_ids: ['None']}\n          }\n          return item\n        })\n        const data = {\n          is_append: form.value.is_append,\n          ids: form.value.ids,\n          role_setting: list.value,\n        }\n        userManageApi.batchSetRoleEE(data, loading).then(() => {\n          MsgSuccess(t('common.settingSuccess'))\n          emit('refresh')\n          dialogVisible.value = false\n        })\n      }\n    }\n  })\n}\n\nasync function getRoleFormItem() {\n  try {\n    const res = await WorkspaceApi.getWorkspaceRoleList(memberFormContentLoading)\n    roleFormItem.value = [\n      {\n        path: 'role_id',\n        label: t('views.role.member.role'),\n        rules: [\n          {\n            required: true,\n            message: `${t('common.selectPlaceholder')}${t('views.role.member.role')}`,\n          },\n        ],\n        selectProps: {\n          options:\n            res.data?.map((item) => ({\n              label: item.name,\n              value: item.id,\n            })) || [],\n          placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.role')}`,\n          multiple: !!user.isPE(),\n        },\n      },\n    ]\n    adminRoleList.value = res.data.filter((item) => item.type === RoleTypeEnum.ADMIN)\n  } catch (e) {\n    console.error(e)\n  }\n}\n\nasync function getWorkspaceFormItem() {\n  try {\n    const res = await WorkspaceApi.getWorkspaceList(memberFormContentLoading)\n    workspaceFormItem.value = [\n      {\n        path: 'workspace_ids',\n        label: t('views.role.member.workspace'),\n        hidden: (e) => adminRoleList.value.find((item) => item.id === e.role_id),\n        rules: [\n          {\n            validator: (rule, value, callback) => {\n              const match = rule.field?.match(/\\[(\\d+)\\]/)\n              const isAdmin = adminRoleList.value.some(\n                (role) => role.id === list.value[parseInt(match?.[1] ?? '', 10)].role_id,\n              )\n              if (!isAdmin && (!value || value.length === 0)) {\n                callback(\n                  new Error(`${t('common.selectPlaceholder')}${t('views.role.member.workspace')}`),\n                )\n              } else {\n                callback()\n              }\n            },\n            trigger: 'blur',\n          },\n        ],\n        selectProps: {\n          options:\n            res.data?.map((item) => ({\n              label: item.name,\n              value: item.id,\n            })) || [],\n          placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.workspace')}`,\n        },\n      },\n    ]\n  } catch (e) {\n    console.error(e)\n  }\n}\n\nonBeforeMount(async () => {\n  if (user.isEE() || user.isPE()) {\n    await getRoleFormItem()\n    if (user.isEE()) {\n      await getWorkspaceFormItem()\n    }\n    formItemModel.value = [...roleFormItem.value, ...workspaceFormItem.value]\n  }\n  list.value = [{ role_id: '', workspace_ids: [] }]\n})\n\ndefineExpose({ open })\n</script>\n"
  },
  {
    "path": "ui/src/views/system/user-manage/component/UserDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"600\">\n    <template #header>\n      <h4>{{ title }}</h4>\n    </template>\n    <h4 class=\"title-decoration-1 mb-16 mt-8\">{{ $t('common.info') }}</h4>\n    <el-form\n      ref=\"userFormRef\"\n      :model=\"userForm\"\n      :rules=\"rules\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n    >\n      <el-form-item\n        :prop=\"isEdit ? '' : 'username'\"\n        :label=\"$t('views.login.loginForm.username.label')\"\n      >\n        <el-input\n          v-model=\"userForm.username\"\n          :placeholder=\"$t('views.login.loginForm.username.placeholder')\"\n          maxlength=\"64\"\n          show-word-limit\n          :disabled=\"isEdit\"\n        >\n        </el-input>\n      </el-form-item>\n      <el-form-item :label=\"$t('views.userManage.userForm.nick_name.label')\" prop=\"nick_name\">\n        <el-input\n          v-model=\"userForm.nick_name\"\n          :placeholder=\"$t('views.userManage.userForm.nick_name.placeholder')\"\n          maxlength=\"64\"\n          show-word-limit\n        >\n        </el-input>\n      </el-form-item>\n      <el-form-item :label=\"$t('views.login.loginForm.email.label')\" prop=\"email\">\n        <el-input\n          type=\"email\"\n          v-model=\"userForm.email\"\n          :placeholder=\"$t('views.login.loginForm.email.placeholder')\"\n        >\n        </el-input>\n      </el-form-item>\n      <el-form-item :label=\"$t('views.userManage.userForm.phone.label')\" prop=\"phone\">\n        <el-input\n          v-model=\"userForm.phone\"\n          :placeholder=\"$t('views.userManage.userForm.phone.placeholder')\"\n        >\n        </el-input>\n      </el-form-item>\n      <el-form-item :label=\"$t('views.userManage.defaultPassword')\" v-if=\"!isEdit\">\n        <span>{{ userForm.password }}</span>\n      </el-form-item>\n    </el-form>\n    <h4 class=\"title-decoration-1 mb-16 mt-8\" v-if=\"user.isEE() || user.isPE()\">\n      {{ $t('views.userManage.roleSetting') }}\n    </h4>\n    <MemberFormContent\n      ref=\"memberFormContentRef\"\n      :models=\"formItemModel\"\n      v-model:form=\"list\"\n      v-loading=\"memberFormContentLoading\"\n      keepOneLine\n      :addText=\"$t('views.userManage.addRole')\"\n      v-if=\"user.isEE() || user.isPE()\"\n      :deleteButtonDisabled=\"deleteButtonDisabled\"\n    />\n    <template #footer>\n      <div style=\"display: flex; width: 100%;\">\n        <el-button @click=\"openDialog\" v-if=\"!isEdit && showPermission\">\n          {{ $t('views.system.resourceAuthorization.setting.defaultPermission') }}\n        </el-button>\n        <div style=\"margin-left: auto;\">\n          <el-button @click.prevent=\"visible = false\">{{ $t('common.cancel') }}</el-button>\n          <el-button type=\"primary\" @click=\"submit(userFormRef)\" :loading=\"loading\">\n            {{ $t('common.save') }}\n          </el-button>\n        </div>\n      </div>\n\n    </template>\n  </el-drawer>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    :title=\"$t('views.system.resourceAuthorization.setting.defaultPermission')\"\n    destroy-on-close\n    @close=\"closeDialog\"\n  >\n    <template #header=\"{ titleId, titleClass }\" v-if=\"user.isEE()\">\n      <div class=\"dialog-header\">\n        <h4 :id=\"titleId\" :class=\"titleClass\" style=\"margin: 0;\">\n          {{ $t('views.system.resourceAuthorization.setting.defaultPermission') }}\n          <span class=\"dialog-subtitle\">\n          {{ $t('views.system.resourceAuthorization.setting.defaultPermissionTip') }}\n        </span>\n        </h4>\n      </div>\n    </template>\n    <el-radio-group v-model=\"radioPermission\" class=\"radio-block\">\n      <template v-for=\"(item, index) in permissionOptions\" :key=\"index\">\n        <el-radio :value=\"item.value\" class=\"mr-16\">\n          <p class=\"color-text-primary lighter\">{{ item.label }}</p>\n          <el-text class=\"color-secondary lighter\">{{ item.desc }}</el-text>\n        </el-radio>\n      </template>\n    </el-radio-group>\n    <template #footer>\n      <div class=\"dialog-footer mt-24\">\n        <el-button @click=\"closeDialog\"> {{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submitDialog\"> {{ $t('common.confirm') }}</el-button>\n      </div>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport {ref, reactive, watch, onBeforeMount, computed} from 'vue'\nimport type {FormInstance} from 'element-plus'\nimport userManageApi from '@/api/system/user-manage'\nimport {MsgSuccess} from '@/utils/message'\nimport {t} from '@/locales'\nimport type {FormItemModel} from '@/api/type/role'\nimport WorkspaceApi from '@/api/workspace/workspace'\nimport MemberFormContent from '@/views/system/role/component/MemberFormContent.vue'\nimport {AuthorizationEnum, RoleTypeEnum} from '@/enums/system'\nimport useStore from '@/stores'\nimport {hasPermission} from \"@/utils/permission\";\nimport {EditionConst} from \"@/utils/permission/data.ts\";\nimport forge from \"node-forge\";\n\nconst {user} = useStore()\nconst props = defineProps({\n  title: String,\n})\n\nconst emit = defineEmits(['refresh'])\n\nconst userFormRef = ref()\nconst userForm = ref<any>({\n  username: '',\n  email: '',\n  password: '',\n  phone: '',\n  nick_name: '',\n})\n\nconst list = ref<any[]>([])\nconst memberFormContentLoading = ref(false)\nconst formItemModel = ref<FormItemModel[]>([])\nconst roleFormItem = ref<FormItemModel[]>([])\nconst adminRoleList = ref<any[]>([])\nconst userRoleList = ref<any[]>([])\nconst workspaceFormItem = ref<FormItemModel[]>([])\n\nconst isAdmin = computed(() => userForm.value['id'] === 'f0dd8f71-e4ee-11ee-8c84-a8a1595801ab')\nconst dialogVisible = ref(false)\nconst radioPermission = ref('NOT_AUTH')\nconst defaultPermission = ref('NOT_AUTH')\nconst permissionOptions = computed(() => {\n  const baseOptions = [\n    {\n      label: t('views.system.resourceAuthorization.setting.check'),\n      value: AuthorizationEnum.VIEW,\n      desc: t('views.system.resourceAuthorization.setting.checkDesc'),\n    },\n    {\n      label: t('views.system.resourceAuthorization.setting.management'),\n      value: AuthorizationEnum.MANAGE,\n      desc: t('views.system.resourceAuthorization.setting.managementDesc'),\n    },\n    {\n      label: t('views.system.resourceAuthorization.setting.notAuthorized'),\n      value: AuthorizationEnum.NOT_AUTH,\n      desc: '',\n    }\n  ];\n\n  if (hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')) {\n    baseOptions.splice(2, 0, {\n      label: t('views.system.resourceAuthorization.setting.role'),\n      value: AuthorizationEnum.ROLE,\n      desc: t('views.system.resourceAuthorization.setting.roleDesc'),\n    });\n  }\n\n  return baseOptions;\n});\n\nconst showPermission = computed(() => {\n  //社区版本的可以显示 别的版本 过期了 也可以显示\n  if (user.isCE() || user.isExpire()) {\n    return true\n  }\n  const hasUserRole = list.value.some((item) => userRoleList.value.includes(item.role_id));\n  return (user.isEE() || user.isPE()) && hasUserRole\n})\n\n\nfunction deleteButtonDisabled(element: any) {\n  return isAdmin.value && ['ADMIN', 'WORKSPACE_MANAGE', 'USER'].includes(element.role_id);\n\n}\n\nasync function getRoleFormItem() {\n  try {\n    const res = await WorkspaceApi.getWorkspaceRoleList(memberFormContentLoading)\n    roleFormItem.value = [\n      {\n        path: 'role_id',\n        label: t('views.role.member.role'),\n        rules: [\n          {\n            required: true,\n            message: `${t('common.selectPlaceholder')}${t('views.role.member.role')}`,\n          },\n        ],\n        selectProps: {\n          options:\n            res.data?.map((item) => ({\n              label: item.name,\n              value: item.id,\n            })) || [],\n          placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.role')}`,\n          multiple: false,\n        },\n      },\n    ]\n    adminRoleList.value = res.data.filter((item) => item.type === RoleTypeEnum.ADMIN)\n    userRoleList.value = res.data\n      .filter((item) => item.type === RoleTypeEnum.USER)\n      .map((item) => item.id)\n\n  } catch (e) {\n    console.error(e)\n  }\n}\n\nasync function getWorkspaceFormItem() {\n  try {\n    const res = await WorkspaceApi.getWorkspaceList(memberFormContentLoading)\n    workspaceFormItem.value = [\n      {\n        path: 'workspace_ids',\n        label: t('views.role.member.workspace'),\n        hidden: (e) => adminRoleList.value.find((item) => item.id === e.role_id),\n        rules: [\n          {\n            validator: (rule, value, callback) => {\n              const match = rule.field?.match(/\\[(\\d+)\\]/)\n              const isAdmin = adminRoleList.value.some(\n                (role) => role.id === list.value[parseInt(match?.[1] ?? '', 10)].role_id,\n              )\n              if (!isAdmin && (!value || value.length === 0)) {\n                callback(\n                  new Error(`${t('common.selectPlaceholder')}${t('views.role.member.workspace')}`),\n                )\n              } else {\n                callback()\n              }\n            },\n            trigger: 'blur',\n          },\n        ],\n        selectProps: {\n          options:\n            res.data?.map((item) => ({\n              label: item.name,\n              value: item.id,\n              disabledFunction: (e: any) =>\n                isAdmin.value &&\n                ['WORKSPACE_MANAGE', 'USER'].includes(e.role_id) &&\n                item.id === 'default',\n            })) || [],\n          placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.workspace')}`,\n          clearableFunction: (e) => {\n            return !(isAdmin.value && ['WORKSPACE_MANAGE', 'USER'].includes(e.role_id))\n          },\n        },\n      },\n    ]\n  } catch (e) {\n    console.error(e)\n  }\n}\n\nonBeforeMount(async () => {\n  if (user.isEE() || user.isPE()) {\n    await getRoleFormItem()\n    if (user.isEE()) {\n      await getWorkspaceFormItem()\n    }\n    formItemModel.value = [...roleFormItem.value, ...workspaceFormItem.value]\n  }\n  list.value = [{role_id: '', workspace_ids: []}]\n})\n\nconst rules = reactive({\n  username: [\n    {\n      required: true,\n      message: t('views.login.loginForm.username.requiredMessage'),\n      trigger: 'blur',\n    },\n    {\n      min: 4,\n      max: 64,\n      message: t('views.login.loginForm.username.lengthMessage'),\n      trigger: 'blur',\n    },\n  ],\n  nick_name: [\n    {\n      required: true,\n      message: t('views.userManage.userForm.nick_name.placeholder'),\n      trigger: 'blur',\n    },\n    {\n      min: 1,\n      max: 64,\n      message: t('views.userManage.userForm.nick_name.lengthMessage'),\n      trigger: 'blur',\n    },\n  ],\n  email: [\n    {\n      required: true,\n      message: t('views.login.loginForm.email.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.password.requiredMessage'),\n      trigger: 'blur',\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur',\n    },\n  ],\n  phone: [\n    {\n      pattern: /^1[3-9]\\d{9}$/,\n      message: t('views.userManage.userForm.phone.invalidMessage'),\n      trigger: 'blur',\n    },\n  ],\n})\nconst visible = ref<boolean>(false)\nconst loading = ref(false)\nconst isEdit = ref(false)\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    userForm.value = {\n      username: '',\n      email: '',\n      password: '',\n      phone: '',\n      nick_name: '',\n    }\n    isEdit.value = false\n    list.value = [{role_id: '', workspace_ids: []}]\n    userFormRef.value?.clearValidate()\n  }\n})\n\nconst open = (data: any) => {\n  if (data) {\n    userForm.value['id'] = data.id\n    userForm.value.username = data.username\n    userForm.value.email = data.email\n    userForm.value.password = data.password\n    userForm.value.phone = data.phone\n    userForm.value.nick_name = data.nick_name\n    list.value = data.role_setting?.map((item: any) => ({\n      ...item,\n      workspace_ids: item.workspace_ids.includes('None') ? [] : item.workspace_ids,\n    }))\n    isEdit.value = true\n  } else {\n    userManageApi.getSystemDefaultPassword().then((res: any) => {\n      userForm.value.password = res.data.password\n    })\n  }\n  if (memberFormContentRef.value) {\n    memberFormContentRef.value.resetValidation()\n  }\n\n  visible.value = true\n}\n\nconst memberFormContentRef = ref<InstanceType<typeof MemberFormContent>>()\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate(async (valid, fields) => {\n    if (valid) {\n      if (memberFormContentRef.value) {\n        await memberFormContentRef.value?.validate()\n      }\n      if (user.isPE() || user.isEE()) {\n        list.value = list.value.map((item) => {\n          const isAdminRole = adminRoleList.value.find((item1) => item1.id === item.role_id)\n\n          // 如果是管理员角色，则设置为 ['None']\n          if (isAdminRole) {\n            return {...item, workspace_ids: ['None']}\n          }\n\n          // 如果是普通用户且是 PE 类型，则设置为 ['default']\n          if (user.isPE()) {\n            return {...item, workspace_ids: ['default']}\n          }\n\n          // 其他情况保持原样\n          return item\n        })\n      }\n      const params = {\n        ...userForm.value,\n        role_setting: list.value,\n      }\n      if (isEdit.value) {\n        userManageApi\n          .putUserManage(userForm.value.id, params, loading)\n          .then((res) => {\n            return user.profile(loading).then(() => {\n              return res\n            })\n          })\n          .then((res) => {\n            emit('refresh')\n            MsgSuccess(t('common.editSuccess'))\n            visible.value = false\n          })\n      } else {\n        params.defaultPermission = defaultPermission.value\n        const publicKey = forge.pki.publicKeyFromPem(user.rasKey);\n        const utf8Bytes = forge.util.encodeUtf8(params.password);\n        const encrypted = publicKey.encrypt(utf8Bytes, 'RSAES-PKCS1-V1_5');\n        params.password = forge.util.encode64(encrypted);\n        params.encrypted = true;\n        userManageApi\n          .postUserManage(params, loading)\n          .then((res) => {\n            return user.profile(loading).then(() => {\n              return res\n            })\n          })\n          .then((res) => {\n            emit('refresh')\n            MsgSuccess(t('common.createSuccess'))\n            visible.value = false\n          })\n      }\n    }\n  })\n}\n\nconst openDialog = () => {\n  dialogVisible.value = true\n}\n\nconst closeDialog = () => {\n  dialogVisible.value = false\n}\n\nconst submitDialog = () => {\n  defaultPermission.value = radioPermission.value\n  closeDialog()\n}\n\n\ndefineExpose({open})\n</script>\n<style lang=\"scss\" scoped>\n.dialog-header {\n  h4 {\n    display: flex;\n    align-items: baseline;\n    gap: 8px;\n    margin: 0;\n  }\n\n  .dialog-subtitle {\n    font-size: 14px;\n    color: var(--el-text-color-secondary);\n  }\n}\n\n</style>\n"
  },
  {
    "path": "ui/src/views/system/user-manage/component/UserPwdDialog.vue",
    "content": "<template>\n  <el-dialog :title=\"$t('views.userManage.setting.updatePwd')\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"userFormRef\"\n      :model=\"userForm\"\n      :rules=\"rules\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n    >\n      <el-form-item :label=\"$t('views.login.loginForm.new_password.label')\" prop=\"password\">\n        <el-input\n          type=\"password\"\n          v-model=\"userForm.password\"\n          :placeholder=\"$t('views.login.loginForm.new_password.placeholder')\"\n          show-password\n        >\n        </el-input>\n      </el-form-item>\n      <el-form-item :label=\"$t('views.login.loginForm.re_password.label')\" prop=\"re_password\">\n        <el-input\n          type=\"password\"\n          v-model=\"userForm.re_password\"\n          :placeholder=\"$t('views.login.loginForm.re_password.placeholder')\"\n          show-password\n        >\n        </el-input>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(userFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, watch } from 'vue'\nimport useStore from '@/stores'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport type { ResetPasswordRequest } from '@/api/type/user'\nimport userManageApi from '@/api/system/user-manage'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst { user } = useStore()\n\nconst userFormRef = ref()\nconst userForm = ref<any>({\n  password: '',\n  re_password: ''\n})\n\nconst rules = reactive<FormRules<ResetPasswordRequest>>({\n  password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.new_password.requiredMessage'),\n      trigger: 'blur'\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur'\n    }\n  ],\n  re_password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.re_password.requiredMessage'),\n      trigger: 'blur'\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur'\n    },\n    {\n      validator: (rule, value, callback) => {\n        if (userFormRef.value.password != userFormRef.value.re_password) {\n          callback(new Error(t('views.login.loginForm.re_password.validatorMessage')))\n        } else {\n          callback()\n        }\n      },\n      trigger: 'blur'\n    }\n  ]\n})\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\nconst userId = ref('')\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    userForm.value = {\n      password: '',\n      re_password: ''\n    }\n  }\n})\n\nconst open = (data: any) => {\n  userId.value = data.id\n  dialogVisible.value = true\n  userFormRef.value?.clearValidate()\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      userManageApi.putUserManagePassword(userId.value, userForm.value, loading).then((res) => {\n        emit('refresh')\n        user.profile()\n        MsgSuccess(t('views.userManage.tip.updatePwdSuccess'))\n        dialogVisible.value = false\n      })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system/user-manage/index.vue",
    "content": "<template>\n  <div class=\"p-16-24\">\n    <h2 class=\"mb-16\">{{ $t('views.userManage.title') }}</h2>\n    <el-card class=\"main-calc-height\">\n      <div class=\"flex-between mb-16\">\n        <div>\n          <el-button\n            type=\"primary\"\n            @click=\"createUser\"\n            v-hasPermission=\"[RoleConst.ADMIN, PermissionConst.USER_CREATE]\"\n            >{{ $t('views.userManage.createUser') }}\n          </el-button>\n          <el-button\n            v-if=\"user.isPE() || user.isEE()\"\n            :disabled=\"multipleSelection.length === 0\"\n            @click=\"setUserRoles\"\n            v-hasPermission=\"[RoleConst.ADMIN, PermissionConst.USER_EDIT]\"\n          >\n            {{ $t('views.userManage.settingRole') }}\n          </el-button>\n          <el-button\n            :disabled=\"multipleSelection.length === 0\"\n            @click=\"handleBatchDelete\"\n            v-hasPermission=\"[RoleConst.ADMIN, PermissionConst.USER_DELETE]\"\n          >\n            {{ $t('common.delete') }}\n          </el-button>\n        </div>\n        <div class=\"flex-between complex-search\">\n          <el-select\n            class=\"complex-search__left\"\n            v-model=\"search_type\"\n            style=\"width: 120px\"\n            @change=\"search_type_change\"\n          >\n            <el-option :label=\"$t('views.login.loginForm.username.label')\" value=\"username\" />\n            <el-option :label=\"$t('views.userManage.userForm.nick_name.label')\" value=\"nick_name\" />\n            <el-option :label=\"$t('views.login.loginForm.email.label')\" value=\"email\" />\n            <el-option :label=\"$t('common.status.label')\" value=\"is_active\" />\n            <el-option\n              v-if=\"user.isEE() || user.isPE()\"\n              :label=\"$t('views.userManage.source.label')\"\n              value=\"source\"\n            />\n          </el-select>\n          <el-input\n            v-if=\"search_type === 'username'\"\n            v-model=\"search_form.username\"\n            @change=\"getList\"\n            style=\"width: 220px\"\n            clearable\n            :placeholder=\"$t('common.inputPlaceholder')\"\n          />\n          <el-input\n            v-else-if=\"search_type === 'nick_name'\"\n            v-model=\"search_form.nick_name\"\n            @change=\"getList\"\n            style=\"width: 220px\"\n            clearable\n            :placeholder=\"$t('common.inputPlaceholder')\"\n          />\n          <el-input\n            v-else-if=\"search_type === 'email'\"\n            v-model=\"search_form.email\"\n            @change=\"getList\"\n            style=\"width: 220px\"\n            clearable\n            :placeholder=\"$t('common.inputPlaceholder')\"\n          />\n          <el-select\n            v-else-if=\"search_type === 'is_active'\"\n            v-model=\"search_form.is_active\"\n            @change=\"getList\"\n            clearable\n            style=\"width: 220px\"\n          >\n            <el-option :label=\"$t('common.status.enabled')\" :value=\"true\" />\n            <el-option :label=\"$t('common.status.disabled')\" :value=\"false\" />\n          </el-select>\n          <el-select\n            v-else-if=\"search_type === 'source'\"\n            v-model=\"search_form.source\"\n            @change=\"getList\"\n            style=\"width: 220px\"\n            clearable\n            :placeholder=\"$t('common.inputPlaceholder')\"\n          >\n            <el-option :label=\"$t('views.userManage.source.local')\" value=\"LOCAL\" />\n            <el-option label=\"CAS\" value=\"CAS\" />\n            <el-option label=\"LDAP\" value=\"LDAP\" />\n            <el-option label=\"OIDC\" value=\"OIDC\" />\n            <el-option label=\"OAuth2\" value=\"OAuth2\" />\n            <el-option :label=\"$t('views.userManage.source.wecom')\" value=\"wecom\" />\n            <el-option :label=\"$t('views.userManage.source.lark')\" value=\"lark\" />\n            <el-option :label=\"$t('views.userManage.source.dingtalk')\" value=\"dingtalk\" />\n          </el-select>\n        </div>\n      </div>\n      <app-table\n        class=\"mt-16\"\n        :data=\"userTableData\"\n        :pagination-config=\"paginationConfig\"\n        @sizeChange=\"handleSizeChange\"\n        @changePage=\"getList\"\n        v-loading=\"loading\"\n        @selection-change=\"handleSelectionChange\"\n        :maxTableHeight=\"280\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column\n          prop=\"nick_name\"\n          :label=\"$t('views.userManage.userForm.nick_name.label')\"\n          min-width=\"180\"\n          show-overflow-tooltip\n        />\n        <el-table-column\n          prop=\"username\"\n          min-width=\"180\"\n          show-overflow-tooltip\n          :label=\"$t('views.login.loginForm.username.label')\"\n        />\n        <el-table-column width=\"100\" prop=\"is_active\" :label=\"$t('common.status.label')\">\n          <template #default=\"{ row }\">\n            <div v-if=\"row.is_active\" class=\"flex align-center\">\n              <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n                <SuccessFilled />\n              </el-icon>\n              <span class=\"color-text-primary\">\n                {{ $t('common.status.enabled') }}\n              </span>\n            </div>\n            <div v-else class=\"flex align-center\">\n              <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n              <span class=\"color-text-primary\">\n                {{ $t('common.status.disabled') }}\n              </span>\n            </div>\n          </template>\n        </el-table-column>\n\n        <el-table-column\n          prop=\"email\"\n          :label=\"$t('views.login.loginForm.email.label')\"\n          show-overflow-tooltip\n          min-width=\"180\"\n        >\n          <template #default=\"{ row }\">\n            {{ row.email || '-' }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"phone\"\n          width=\"120\"\n          :label=\"$t('views.userManage.userForm.phone.label')\"\n        >\n          <template #default=\"{ row }\">\n            {{ row.phone || '-' }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"role_name\"\n          :label=\"$t('views.role.member.role')\"\n          width=\"210\"\n          v-if=\"user.isEE() || user.isPE()\"\n        >\n          <template #default=\"{ row }\">\n            <el-popover :width=\"500\" :persistent=\"false\">\n              <template #reference>\n                <TagGroup class=\"cursor\" :tags=\"row.role_name\" tooltipDisabled />\n              </template>\n              <template #default>\n                <el-table\n                  :data=\"row.role_workspace\"\n                  :max-height=\"300\"\n                  :tooltip-options=\"{\n                    popperClass: 'max-w-350',\n                  }\"\n                >\n                  <el-table-column\n                    prop=\"role\"\n                    :label=\"$t('views.role.member.role')\"\n                    width=\"200\"\n                    show-overflow-tooltip\n                  >\n                  </el-table-column>\n                  <el-table-column\n                    prop=\"workspace\"\n                    :label=\"$t('views.workspace.title')\"\n                    show-overflow-tooltip\n                  >\n                  </el-table-column>\n                </el-table>\n              </template>\n            </el-popover>\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"source\" width=\"120\" :label=\"$t('views.userManage.source.label')\">\n          <template #default=\"{ row }\">\n            {{\n              row.source === 'LOCAL'\n                ? $t('views.userManage.source.local')\n                : row.source === 'wecom'\n                  ? $t('views.userManage.source.wecom')\n                  : row.source === 'lark'\n                    ? $t('views.userManage.source.lark')\n                    : row.source === 'dingtalk'\n                      ? $t('views.userManage.source.dingtalk')\n                      : row.source === 'OAUTH2' || row.source === 'OAuth2'\n                        ? 'OAuth2'\n                        : row.source\n            }}\n          </template>\n        </el-table-column>\n\n        <el-table-column :label=\"$t('common.createTime')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.create_time) }}\n          </template>\n        </el-table-column>\n\n        <el-table-column :label=\"$t('common.operation')\" width=\"160\" align=\"left\" fixed=\"right\">\n          <template #default=\"{ row }\">\n            <span @click.stop>\n              <el-switch\n                :disabled=\"row.role === 'ADMIN' || row.id === user.userInfo?.id\"\n                size=\"small\"\n                v-model=\"row.is_active\"\n                :before-change=\"() => changeState(row)\"\n                v-if=\"hasPermission([RoleConst.ADMIN, PermissionConst.USER_EDIT], 'OR')\"\n              />\n            </span>\n            <el-divider direction=\"vertical\" />\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('common.edit')\"\n              placement=\"top\"\n              v-if=\"hasPermission([RoleConst.ADMIN, PermissionConst.USER_EDIT], 'OR')\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click.stop=\"editUser(row)\"\n                  :title=\"$t('common.edit')\"\n                >\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('views.userManage.setting.updatePwd')\"\n              placement=\"top\"\n              v-if=\"hasPermission([RoleConst.ADMIN, PermissionConst.USER_EDIT], 'OR')\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click.stop=\"editPwdUser(row)\"\n                  :title=\"$t('views.userManage.setting.updatePwd')\"\n                >\n                  <AppIcon iconName=\"app-key\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('common.delete')\"\n              placement=\"top\"\n              v-if=\"hasPermission([RoleConst.ADMIN, PermissionConst.USER_DELETE], 'OR')\"\n            >\n              <el-button\n                :disabled=\"row.role === 'ADMIN' || row.id === user.userInfo?.id\"\n                type=\"primary\"\n                text\n                @click.stop=\"deleteUserManage(row)\"\n                :title=\"$t('common.delete')\"\n              >\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </template>\n        </el-table-column>\n      </app-table>\n    </el-card>\n    <UserDrawer :title=\"title\" ref=\"UserDrawerRef\" @refresh=\"refresh\" />\n    <UserPwdDialog ref=\"UserPwdDialogRef\" @refresh=\"refresh\" />\n    <SetUserRoleDialog ref=\"setUserRoleRef\" @refresh=\"refresh\" />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, watch, computed, onBeforeMount } from 'vue'\nimport UserDrawer from './component/UserDrawer.vue'\nimport UserPwdDialog from './component/UserPwdDialog.vue'\nimport SetUserRoleDialog from './component/SetUserRoleDialog.vue'\nimport userManageApi from '@/api/system/user-manage'\nimport { datetimeFormat } from '@/utils/time'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport { i18n_name } from '@/utils/common'\n\nconst { user, common } = useStore()\nconst search_type = ref('username')\nconst search_form = ref<{\n  username: string\n  nick_name?: string\n  email?: string\n  is_active?: boolean | null\n  source?: string | null\n}>({\n  username: '',\n  nick_name: '',\n  email: '',\n  is_active: null,\n  source: '',\n})\n\nconst UserDrawerRef = ref()\nconst UserPwdDialogRef = ref()\nconst loading = ref(false)\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\nconst userTableData = ref<any[]>([])\n\nconst search_type_change = () => {\n  search_form.value = { username: '', nick_name: '', email: '', is_active: null }\n}\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getList()\n}\n\nfunction getList() {\n  const params: any = {}\n  const searchValue = search_form.value[search_type.value as keyof typeof search_form.value]\n  if (searchValue !== undefined && searchValue !== null && searchValue !== '') {\n    params[search_type.value] = searchValue\n  }\n  return userManageApi.getUserManage(paginationConfig, params, loading).then((res) => {\n    userTableData.value = res.data.records.map((item: any) => ({\n      ...item,\n      nick_name: i18n_name(item.nick_name),\n      role_workspace: Object.entries(item.role_workspace ?? {}).map(([role, workspaces]) => ({\n        role: i18n_name(role),\n        workspace:\n          (workspaces as string[])?.[0] === 'None'\n            ? '-'\n            : (workspaces as string[])?.map((ws) => i18n_name(ws)).join(', '),\n      })),\n    }))\n    paginationConfig.total = res.data.total\n  })\n}\n\nasync function changeState(row: any) {\n  const obj = {\n    is_active: !row.is_active,\n  }\n  const str = obj.is_active ? t('common.status.enableSuccess') : t('common.status.disableSuccess')\n  await userManageApi\n    .putUserManage(row.id, obj, loading)\n    .then((res) => {\n      getList()\n      MsgSuccess(str)\n      return true\n    })\n    .catch(() => {\n      return false\n    })\n}\n\nconst title = ref('')\n\nfunction editUser(row: any) {\n  title.value = t('views.userManage.editUser')\n  UserDrawerRef.value.open(row)\n}\n\nfunction createUser() {\n  title.value = t('views.userManage.createUser')\n  UserDrawerRef.value.open()\n}\n\nfunction deleteUserManage(row: any) {\n  MsgConfirm(\n    `${t('views.userManage.delete.confirmTitle')}${row.nick_name} ?`,\n    t('views.userManage.delete.confirmMessage'),\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loading.value = true\n      userManageApi.delUserManage(row.id, loading).then(() => {\n        MsgSuccess(t('common.deleteSuccess'))\n        getList()\n      })\n    })\n    .catch(() => {})\n}\n\nfunction editPwdUser(row: any) {\n  UserPwdDialogRef.value.open(row)\n}\n\nfunction refresh() {\n  getList()\n}\n\nconst multipleSelection = ref<any[]>([])\n\nfunction handleSelectionChange(val: any[]) {\n  multipleSelection.value = val\n}\n\nfunction handleBatchDelete() {\n  MsgConfirm(t('views.chatUser.batchDeleteUser', { count: multipleSelection.value.length }), '', {\n    confirmButtonText: t('common.confirm'),\n    confirmButtonClass: 'danger',\n  })\n    .then(() => {\n      userManageApi\n        .batchDelete(\n          multipleSelection.value.map((item) => item.id),\n          loading,\n        )\n        .then(async () => {\n          MsgSuccess(t('common.deleteSuccess'))\n          await getList()\n        })\n    })\n    .catch(() => {})\n}\n\nconst setUserRoleRef = ref<InstanceType<typeof SetUserRoleDialog>>()\n\nfunction setUserRoles() {\n  setUserRoleRef.value?.open(multipleSelection.value.map((item) => item.id))\n}\n\nonMounted(() => {\n  getList()\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system/workspace/component/AddMemberDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"600\" :destroy-on-close=\"true\" :before-close=\"handleCancel\">\n    <template #header>\n      <h4>{{ $t('views.role.member.add') }}</h4>\n    </template>\n    <template #default>\n      <MemberFormContent\n        ref=\"memberFormContentRef\"\n        :models=\"formItemModel\"\n        v-model:form=\"list\"\n        v-loading=\"memberFormContentLoading\"\n        keepOneLine\n      />\n    </template>\n    <template #footer>\n      <div style=\"flex: auto\">\n        <el-button @click=\"handleCancel\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"handleAdd()\" :loading=\"loading\">\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { onBeforeMount, ref } from 'vue'\nimport UserApi from '@/api/user/user'\nimport WorkspaceApi from '@/api/workspace/workspace'\nimport MemberFormContent from '@/views/system/role/component/MemberFormContent.vue'\nimport { t } from '@/locales'\nimport { MsgSuccess } from '@/utils/message'\nimport type { CreateWorkspaceMemberParamsItem, WorkspaceItem } from '@/api/type/workspace'\nimport type { FormItemModel } from '@/api/type/role'\nimport { RoleTypeEnum } from '@/enums/system'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\nimport { i18n_name } from '@/utils/common'\n\nconst props = defineProps<{\n  currentWorkspace?: WorkspaceItem\n}>()\n\nconst emit = defineEmits<{\n  (e: 'refresh'): void\n}>()\n\nconst loading = ref(false)\nconst visible = ref(false)\nconst list = ref<CreateWorkspaceMemberParamsItem[]>([])\n\nconst memberFormContentLoading = ref(false)\nconst formItemModel = ref<FormItemModel[]>([])\nconst userFormItem = ref<FormItemModel[]>([])\nconst roleFormItem = ref<FormItemModel[]>([])\n\nasync function getUserFormItem() {\n  try {\n    const res = await UserApi.getUserList(memberFormContentLoading)\n    userFormItem.value = [\n      {\n        path: 'user_ids',\n        label: t('views.role.member.title'),\n        rules: [\n          {\n            required: true,\n            message: `${t('common.selectPlaceholder')}${t('views.role.member.title')}`,\n          },\n        ],\n        selectProps: {\n          options:\n            res.data?.map((item) => ({\n              label: item.nick_name,\n              value: item.id,\n            })) || [],\n          placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.title')}`,\n        },\n      },\n    ]\n  } catch (e) {\n    console.error(e)\n  }\n}\n\nasync function getRoleFormItem() {\n  try {\n    const res = await loadPermissionApi('workspace').getWorkspaceRoleList(memberFormContentLoading)\n    roleFormItem.value = [\n      {\n        path: 'role_ids',\n        label: t('views.role.member.role'),\n        rules: [\n          {\n            required: true,\n            message: `${t('common.selectPlaceholder')}${t('views.role.member.role')}`,\n          },\n        ],\n        selectProps: {\n          options:\n            res.data\n              .filter((item: any) => item.type !== RoleTypeEnum.ADMIN)\n              ?.map((item: any) => ({\n                label: i18n_name(item.name),\n                value: item.id,\n              })) || [],\n          placeholder: `${t('common.selectPlaceholder')}${t('views.role.member.role')}`,\n        },\n      },\n    ]\n  } catch (e) {\n    console.error(e)\n  }\n}\n\nfunction init() {\n  formItemModel.value = [...userFormItem.value, ...roleFormItem.value]\n  list.value = [{ user_ids: [], role_ids: [] }]\n}\n\nonBeforeMount(async () => {\n  await getUserFormItem()\n  await getRoleFormItem()\n  init()\n})\n\nfunction open() {\n  init()\n  visible.value = true\n}\n\nfunction handleCancel() {\n  visible.value = false\n}\n\nconst memberFormContentRef = ref<InstanceType<typeof MemberFormContent>>()\nfunction handleAdd() {\n  memberFormContentRef.value?.validate().then(async (valid: any) => {\n    if (valid) {\n      await loadPermissionApi('workspace').CreateWorkspaceMember(\n        props.currentWorkspace?.id as string,\n        list.value,\n        loading,\n      )\n      MsgSuccess(t('common.addSuccess'))\n      handleCancel()\n      emit('refresh')\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n"
  },
  {
    "path": "ui/src/views/system/workspace/component/CreateOrUpdateWorkspaceDialog.vue",
    "content": "<template>\n  <el-dialog :title=\"`${!form.id ? $t('common.create') : $t('common.rename')}${$t('views.workspace.title')}`\"\n    v-model=\"dialogVisible\" :close-on-click-modal=\"false\" :close-on-press-escape=\"false\" :destroy-on-close=\"true\">\n    <el-form label-position=\"top\" ref=\"formRef\" :rules=\"rules\" :model=\"form\" require-asterisk-position=\"right\" @submit.prevent>\n      <el-form-item :label=\"$t('views.workspace.name')\" prop=\"name\">\n        <el-input v-model=\"form.name\" maxlength=\"64\" show-word-limit\n          :placeholder=\"`${$t('common.inputPlaceholder')}${$t('views.workspace.name')}`\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(formRef)\" :loading=\"loading\">\n          {{ !form.id ? $t('common.create') : $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport type { WorkspaceItem } from '@/api/type/workspace'\nimport WorkspaceApi from '@/api/workspace/workspace'\nimport {loadPermissionApi} from \"@/utils/dynamics-api/permission-api.ts\";\n\nconst emit = defineEmits<{\n  (e: 'refresh', currentWorkspace: WorkspaceItem): void;\n}>();\n\nconst dialogVisible = ref<boolean>(false)\nconst defaultForm = {\n  name: ''\n}\nconst form = ref<WorkspaceItem>({\n  ...defaultForm,\n})\nfunction open(item?: WorkspaceItem) {\n  if (item) {\n    form.value = { id: item.id, name: item.name }\n  } else {\n    form.value = { ...defaultForm }\n  }\n  dialogVisible.value = true\n}\n\nconst formRef = ref<FormInstance>();\n\nconst rules = reactive({\n  name: [{ required: true, message: `${t('common.inputPlaceholder')}${t('views.workspace.name')}`, trigger: 'blur' }],\n})\n\nconst loading = ref<boolean>(false)\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      loadPermissionApi('workspace').CreateOrUpdateWorkspace(form.value, loading).then((res: any) => {\n        MsgSuccess(!form.value.id ? t('common.createSuccess') : t('common.renameSuccess'))\n        emit('refresh', res.data)\n        dialogVisible.value = false\n      })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n"
  },
  {
    "path": "ui/src/views/system/workspace/component/Member.vue",
    "content": "<template>\n  <div class=\"flex-between mb-16\">\n    <el-button\n      type=\"primary\"\n      @click=\"handleAdd\"\n      v-hasPermission=\"\n        new ComplexPermission(\n          [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n          [\n            PermissionConst.WORKSPACE_ADD_MEMBER,\n            PermissionConst.WORKSPACE_WORKSPACE_ADD_MEMBER\n              .getWorkspacePermissionWorkspaceManageRole,\n          ],\n          [],\n          'OR',\n        )\n      \"\n    >\n      {{ $t('views.role.member.add') }}\n    </el-button>\n    <div class=\"flex complex-search\">\n      <el-select class=\"complex-search__left\" v-model=\"searchType\" style=\"width: 120px\">\n        <el-option :label=\"$t('views.login.loginForm.username.label')\" value=\"username\" />\n        <el-option :label=\"$t('views.userManage.userForm.nick_name.label')\" value=\"nick_name\" />\n      </el-select>\n      <el-input\n        v-if=\"searchType === 'username'\"\n        v-model=\"searchForm.username\"\n        @change=\"getList\"\n        :placeholder=\"$t('common.inputPlaceholder')\"\n        style=\"width: 220px\"\n        clearable\n      />\n      <el-input\n        v-else-if=\"searchType === 'nick_name'\"\n        v-model=\"searchForm.nick_name\"\n        @change=\"getList\"\n        :placeholder=\"$t('common.inputPlaceholder')\"\n        style=\"width: 220px\"\n        clearable\n      />\n    </div>\n  </div>\n  <app-table\n    :data=\"tableData\"\n    :pagination-config=\"paginationConfig\"\n    @sizeChange=\"handleSizeChange\"\n    @changePage=\"getList\"\n    v-loading=\"loading\"\n    class=\"member-table\"\n    :span-method=\"objectSpanMethod\"\n    :maxTableHeight=\"320\"\n  >\n    <el-table-column prop=\"nick_name\" :label=\"$t('views.userManage.userForm.nick_name.label')\" />\n    <el-table-column prop=\"username\" :label=\"$t('views.login.loginForm.username.label')\" />\n    <el-table-column prop=\"role_name\" :label=\"$t('views.role.member.role')\" />\n    <el-table-column :label=\"$t('common.operation')\" width=\"100\" fixed=\"right\">\n      <template #default=\"{ row }\">\n        <el-tooltip\n          effect=\"dark\"\n          :content=\"`${$t('views.role.member.delete.button')}`\"\n          placement=\"top\"\n        >\n          <el-button\n            type=\"primary\"\n            text\n            @click.stop=\"handleDelete(row)\"\n            v-hasPermission=\"\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],\n                [\n                  PermissionConst.WORKSPACE_REMOVE_MEMBER,\n                  PermissionConst.WORKSPACE_WORKSPACE_REMOVE_MEMBER\n                    .getWorkspacePermissionWorkspaceManageRole,\n                ],\n                [],\n                'OR',\n              )\n            \"\n          >\n            <AppIcon iconName=\"app-delete-users\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </template>\n    </el-table-column>\n  </app-table>\n  <AddMemberDrawer\n    ref=\"addMemberDrawerRef\"\n    :currentWorkspace=\"props.currentWorkspace\"\n    @refresh=\"getList\"\n  />\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref, reactive, watch } from 'vue'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { t } from '@/locales'\nimport AddMemberDrawer from './AddMemberDrawer.vue'\nimport type { WorkspaceMemberItem, WorkspaceItem } from '@/api/type/workspace'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\nimport { i18n_name } from '@/utils/common'\n\nconst props = defineProps<{\n  currentWorkspace?: WorkspaceItem\n}>()\n\nconst loading = ref(false)\n\nconst searchType = ref('username')\nconst searchForm = ref<Record<string, any>>({\n  username: '',\n  nick_name: '',\n})\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst tableData = ref<WorkspaceMemberItem[]>([])\n\nasync function getList() {\n  if (!props.currentWorkspace?.id) return\n  try {\n    const params = {\n      [searchType.value]: searchForm.value[searchType.value],\n    }\n    const res = await loadPermissionApi('workspace').getWorkspaceMemberList(\n      props.currentWorkspace?.id,\n      paginationConfig,\n      params,\n      loading,\n    )\n    tableData.value = res.data.records.map((item: any) => ({\n      ...item,\n      nick_name: i18n_name(item.nick_name),\n      role_name: i18n_name(item.role_name),\n    }))\n    paginationConfig.total = res.data.total\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getList()\n}\n\nonMounted(() => {\n  getList()\n})\n\nwatch(\n  () => props.currentWorkspace?.id,\n  () => {\n    getList()\n  },\n)\n\nconst objectSpanMethod = ({ row, column, rowIndex, columnIndex }: any) => {\n  if (column.property === 'nick_name' || column.property === 'username') {\n    const sameUserRows = tableData.value.filter((item) => item.user_id === row.user_id)\n    if (rowIndex === tableData.value.findIndex((item) => item.user_id === row.user_id)) {\n      return {\n        rowspan: sameUserRows.length,\n        colspan: 1,\n      }\n    } else {\n      return {\n        rowspan: 0,\n        colspan: 0,\n      }\n    }\n  }\n}\n\nconst addMemberDrawerRef = ref<InstanceType<typeof AddMemberDrawer>>()\n\nfunction handleAdd() {\n  addMemberDrawerRef.value?.open()\n}\n\nfunction handleDelete(row: WorkspaceMemberItem) {\n  MsgConfirm(`${t('views.workspace.member.delete.confirmTitle')}${row.nick_name} ?`, '', {\n    confirmButtonText: t('common.confirm'),\n    confirmButtonClass: 'danger',\n  })\n    .then(() => {\n      loading.value = true\n      loadPermissionApi('workspace')\n        .deleteWorkspaceMember(props.currentWorkspace?.id as string, row.user_relation_id, loading)\n        .then(() => {\n          MsgSuccess(t('common.deleteSuccess'))\n          getList()\n        })\n    })\n    .catch(() => {})\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.member-table :deep(.el-table__cell):nth-child(2) {\n  border-right: 1px solid var(--el-table-border-color);\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system/workspace/index.vue",
    "content": "<template>\n  <div class=\"workspace-manage p-16-24\">\n    <h2 class=\"mb-16\">{{ $t('views.workspace.title') }}</h2>\n    <el-card style=\"--el-card-padding: 0\">\n      <div class=\"flex main-calc-height\">\n        <div class=\"workspace-left border-r\">\n          <div class=\"p-24 pb-0\">\n            <div class=\"flex-between mb-12\">\n              <h4 class=\"medium\">{{ $t('views.workspace.list') }}</h4>\n              <el-tooltip\n                effect=\"dark\"\n                :content=\"`${$t('common.create')}${$t('views.workspace.title')}`\"\n                placement=\"top\"\n              >\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click=\"createOrUpdateWorkspace()\"\n                  v-hasPermission=\"[RoleConst.ADMIN, PermissionConst.WORKSPACE_CREATE]\"\n                >\n                  <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n            </div>\n            <el-input\n              v-model=\"filterText\"\n              :placeholder=\"$t('common.search')\"\n              prefix-icon=\"Search\"\n              clearable\n            />\n          </div>\n          <div class=\"list-height-left\">\n            <el-scrollbar v-loading=\"loading\">\n              <div class=\"p-8-16\">\n                <common-list\n                  :data=\"filterList\"\n                  @click=\"clickWorkspace\"\n                  :default-active=\"currentWorkspace?.id\"\n                  @mouseenter=\"mouseenter\"\n                  @mouseleave=\"mouseId = ''\"\n                >\n                  <template #default=\"{ row }\">\n                    <div class=\"flex-between\">\n                      <span class=\"ellipsis\" :title=\"row.name\">{{ i18n_name(row.name) }}</span>\n                      <div @click.stop v-show=\"mouseId === row.id\">\n                        <el-dropdown\n                          :teleported=\"false\"\n                          trigger=\"click\"\n                          v-if=\"editPermission() || dlePermission()\"\n                        >\n                          <el-button text>\n                            <AppIcon iconName=\"app-more\"></AppIcon>\n                          </el-button>\n                          <template #dropdown>\n                            <el-dropdown-menu style=\"min-width: 80px\">\n                              <el-dropdown-item\n                                @click.stop=\"createOrUpdateWorkspace(row)\"\n                                class=\"p-8\"\n                                v-if=\"editPermission()\"\n                              >\n                                <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n                                {{ $t('common.rename') }}\n                              </el-dropdown-item>\n                              <el-dropdown-item\n                                @click.stop=\"deleteWorkspace(row)\"\n                                class=\"border-t p-8\"\n                                v-if=\"dlePermission()\"\n                              >\n                                <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                                {{ $t('common.delete') }}\n                              </el-dropdown-item>\n                            </el-dropdown-menu>\n                          </template>\n                        </el-dropdown>\n                      </div>\n                    </div>\n                  </template>\n                  <template #empty>\n                    <span></span>\n                  </template>\n                </common-list>\n              </div>\n            </el-scrollbar>\n          </div>\n        </div>\n\n        <!-- 右边 -->\n        <div class=\"workspace-right p-24\" v-loading=\"loading\">\n          <div class=\"flex align-center mb-16\">\n            <h4 class=\"medium\">{{ i18n_name(currentWorkspace?.name as string) }}</h4>\n            <el-divider direction=\"vertical\" class=\"mr-8 ml-8\" />\n            <el-icon class=\"color-input-placeholder\"><UserFilled /></el-icon>\n            <span class=\"color-input-placeholder ml-4\">\n              {{ currentWorkspace?.user_count }}\n            </span>\n          </div>\n          <Member :currentWorkspace=\"currentWorkspace\" />\n        </div>\n      </div>\n    </el-card>\n\n    <CreateOrUpdateWorkspaceDialog ref=\"createOrUpdateWorkspaceDialogRef\" @refresh=\"refresh\" />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, watch } from 'vue'\nimport { t } from '@/locales'\nimport { i18n_name } from '@/utils/common'\nimport Member from './component/Member.vue'\nimport CreateOrUpdateWorkspaceDialog from './component/CreateOrUpdateWorkspaceDialog.vue'\nimport type { WorkspaceItem } from '@/api/type/workspace'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { hasPermission } from '@/utils/permission/index'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\n\nconst filterText = ref('')\nconst loading = ref(false)\nconst list = ref<WorkspaceItem[]>([])\nconst filterList = ref<WorkspaceItem[]>([]) // 搜索过滤后列表\nconst currentWorkspace = ref<WorkspaceItem>()\n\nasync function getWorkspace() {\n  try {\n    const res = await loadPermissionApi('workspace').getSystemWorkspaceList(loading)\n    list.value = res.data\n    filterList.value = filter(list.value, filterText.value)\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nonMounted(async () => {\n  await getWorkspace()\n  currentWorkspace.value = list.value[0]\n})\n\nconst editPermission = () => {\n  return hasPermission([RoleConst.ADMIN, PermissionConst.WORKSPACE_EDIT], 'OR')\n}\n\nconst dlePermission = () => {\n  return hasPermission([RoleConst.ADMIN, PermissionConst.WORKSPACE_DELETE], 'OR')\n}\n\nasync function refresh(workspace?: WorkspaceItem) {\n  await getWorkspace()\n  // 创建后选中新建的\n  if (workspace) {\n    currentWorkspace.value = workspace\n  } else {\n    currentWorkspace.value = list.value.find((item) => item.id === currentWorkspace.value?.id)\n  }\n}\n\nfunction filter(list: WorkspaceItem[], filterText: string) {\n  if (!filterText.length) {\n    return list\n  }\n  return list.filter((v: WorkspaceItem) => v.name.toLowerCase().includes(filterText.toLowerCase()))\n}\n\nwatch(filterText, (val: string) => {\n  filterList.value = filter(list.value, val)\n})\n\nfunction clickWorkspace(item: WorkspaceItem) {\n  currentWorkspace.value = item\n}\n\nconst createOrUpdateWorkspaceDialogRef = ref<InstanceType<typeof CreateOrUpdateWorkspaceDialog>>()\n\nfunction createOrUpdateWorkspace(item?: WorkspaceItem) {\n  createOrUpdateWorkspaceDialogRef.value?.open(item)\n}\n\nasync function check(id: string) {\n  try {\n    return await loadPermissionApi('workspace').deleteWorkspaceCheck(id)\n  } catch (error) {\n    console.log(error)\n  }\n}\n\nasync function deleteWorkspace(item: WorkspaceItem) {\n  // 判断是否能删除\n  const res = await check(item.id as string)\n  const canDelete = res ? res.data.can_delete : true\n  if (canDelete) {\n    MsgConfirm(\n      `${t('views.workspace.delete.confirmTitle')}${item.name} ?`,\n      t('views.workspace.delete.confirmContent'),\n      {\n        confirmButtonText: t('common.confirm'),\n        confirmButtonClass: 'danger',\n      },\n    ).then(() => {\n      loadPermissionApi('workspace')\n        .deleteWorkspace(item.id as string, loading)\n        .then(async () => {\n          MsgSuccess(t('common.deleteSuccess'))\n          await getWorkspace()\n          currentWorkspace.value =\n            item.id === currentWorkspace.value?.id ? list.value[0] : currentWorkspace.value\n        })\n    })\n  } else {\n    MsgConfirm(\n      `${t('views.workspace.delete.confirmTitle')}${item.name} ?`,\n      res ? res.data.message : t('views.workspace.delete.confirmContent'),\n      {\n        showConfirmButton: false,\n        cancelButtonText: t('common.close'),\n      },\n    )\n  }\n}\nconst mouseId = ref('')\n\nfunction mouseenter(row: any) {\n  mouseId.value = row.id\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.workspace-manage {\n  .workspace-left {\n    box-sizing: border-box;\n    width: var(--setting-left-width);\n    min-width: var(--setting-left-width);\n\n    .list-height-left {\n      height: calc(100vh - 255px);\n    }\n  }\n\n  .workspace-right {\n    flex: 1;\n    overflow: hidden;\n    display: flex;\n    flex-direction: column;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/authentication/component/CAS.vue",
    "content": "<template>\n  <div class=\"authentication-setting__main main-calc-height\">\n    <el-scrollbar>\n      <div class=\"form-container p-24\" v-loading=\"loading\">\n        <el-form\n          ref=\"authFormRef\"\n          :rules=\"rules\"\n          :model=\"form\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n        >\n          <el-form-item :label=\"$t('views.system.authentication.cas.ldpUri')\" prop=\"config.ldpUri\">\n            <el-input\n              v-model=\"form.config.ldpUri\"\n              :placeholder=\"$t('views.system.authentication.cas.ldpUriPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.cas.validateUrl')\"\n            prop=\"config.validateUrl\"\n          >\n            <el-input\n              v-model=\"form.config.validateUrl\"\n              :placeholder=\"$t('views.system.authentication.cas.validateUrlPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.cas.redirectUrl')\"\n            prop=\"config.redirectUrl\"\n          >\n            <el-input\n              v-model=\"form.config.redirectUrl\"\n              :placeholder=\"$t('views.system.authentication.cas.redirectUrlPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item>\n            <el-checkbox v-model=\"form.is_active\"\n              >{{ $t('views.system.authentication.cas.enableAuthentication') }}\n            </el-checkbox>\n          </el-form-item>\n        </el-form>\n\n        <div>\n          <el-button\n            @click=\"submit(authFormRef)\"\n            type=\"primary\"\n            :disabled=\"loading\"\n            v-hasPermission=\"\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.CHAT_USER_AUTH_EDIT],\n                [],\n                'OR',\n              )\n            \"\n          >\n            {{ $t('common.save') }}\n          </el-button>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch, onMounted } from 'vue'\nimport authApi from '@/api/chat-user/auth-setting'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { t } from '@/locales'\nimport { MsgSuccess } from '@/utils/message'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\nconst form = ref<any>({\n  id: '',\n  auth_type: 'CAS',\n  config: {\n    ldpUri: '',\n    validateUrl: '',\n    redirectUrl: '',\n  },\n  is_active: true,\n})\n\nconst authFormRef = ref()\n\nconst loading = ref(false)\n\nconst rules = reactive<FormRules<any>>({\n  'config.ldpUri': [\n    {\n      required: true,\n      message: t('views.system.authentication.cas.ldpUriPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.validateUrl': [\n    {\n      required: true,\n      message: t('views.system.authentication.cas.validateUrlPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.redirectUrl': [\n    {\n      required: true,\n      message: t('views.system.authentication.cas.redirectUrlPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {\n        MsgSuccess(t('common.saveSuccess'))\n      })\n    }\n  })\n}\n\nfunction getDetail() {\n  authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {\n    if (res.data && JSON.stringify(res.data) !== '{}') {\n      if (!res.data.config.validateUrl) {\n        res.data.config.validateUrl = res.data.config.ldpUri\n      }\n      form.value = res.data\n    }\n    if (!form.value.config.redirectUrl) {\n      form.value.config.redirectUrl =\n        window.location.origin + window.MaxKB.chatPrefix + '/api/auth/cas'\n    }\n  })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/authentication/component/EditModal.vue",
    "content": "template\n<template>\n  <el-drawer\n    v-model=\"visible\"\n    size=\"60%\"\n    :append-to-body=\"true\"\n    :destroy-on-close=\"true\"\n    @close=\"handleClose\"\n  >\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <h4>\n          {{ currentPlatform.name + $t('views.system.authentication.scanTheQRCode.setting') }}\n        </h4>\n      </div>\n    </template>\n\n    <el-form\n      :model=\"currentPlatform.config\"\n      label-width=\"120px\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      ref=\"formRef\"\n    >\n      <el-form-item\n        v-for=\"(value, key) in currentPlatform.config\"\n        :key=\"key\"\n        :label=\"formatFieldName(key)\"\n        :prop=\"key\"\n        :rules=\"getValidationRules(key)\"\n      >\n        <el-input\n          v-model=\"currentPlatform.config[key]\"\n          :type=\"isPasswordField(key) ? 'password' : 'text'\"\n          :show-password=\"isPasswordField(key)\"\n        >\n        </el-input>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click=\"handleClose\">{{ $t('common.cancel') }}</el-button>\n        <el-button @click=\"validateConnection\">{{\n          $t('views.system.authentication.scanTheQRCode.validate')\n        }}</el-button>\n        <el-button type=\"primary\" @click=\"validateForm\">{{ $t('common.save') }}</el-button>\n      </span>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { reactive, ref } from 'vue'\nimport { ElForm } from 'element-plus'\nimport platformApi from '@/api/chat-user/auth-setting.ts'\nimport { MsgError, MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\n\nconst visible = ref(false)\nconst loading = ref(false)\nconst formRef = ref<InstanceType<typeof ElForm>>()\n\ninterface PlatformConfig {\n  [key: string]: string\n}\n\ninterface Platform {\n  key: string\n  logoSrc: string\n  name: string\n  isActive: boolean\n  isValid: boolean\n  config: PlatformConfig\n}\n\nconst currentPlatform = reactive<Platform>({\n  key: '',\n  logoSrc: '',\n  name: '',\n  isActive: false,\n  isValid: false,\n  config: {}\n})\n\nconst formatFieldName = (key?: any): string => {\n  const fieldNames: { [key: string]: string } = {\n    corp_id: 'Corp ID',\n    app_key: currentPlatform?.key != 'lark' ? 'APP Key' : 'App ID',\n    app_secret: 'APP Secret',\n    agent_id: 'Agent ID',\n    callback_url: t('views.application.applicationAccess.callback')\n  }\n  return (\n    fieldNames[key as keyof typeof fieldNames] ||\n    (key ? key.charAt(0).toUpperCase() + key.slice(1) : '')\n  )\n}\n\nconst getValidationRules = (key: any) => {\n  switch (key) {\n    case 'app_key':\n      return [\n        {\n          required: true,\n          message: t('views.system.authentication.scanTheQRCode.appKeyPlaceholder'),\n          trigger: ['blur', 'change']\n        }\n      ]\n    case 'app_secret':\n      return [\n        {\n          required: true,\n          message: t('views.system.authentication.scanTheQRCode.appSecretPlaceholder'),\n          trigger: ['blur', 'change']\n        }\n      ]\n    case 'corp_id':\n      return [\n        {\n          required: true,\n          message: t('views.system.authentication.scanTheQRCode.corpIdPlaceholder'),\n          trigger: ['blur', 'change']\n        }\n      ]\n    case 'agent_id':\n      return [\n        {\n          required: true,\n          message: t('views.system.authentication.scanTheQRCode.agentIdPlaceholder'),\n          trigger: ['blur', 'change']\n        }\n      ]\n    case 'callback_url':\n      return [\n        {\n          required: true,\n          message: t('views.application.applicationAccess.callbackTip'),\n          trigger: ['blur', 'change']\n        },\n        {\n          pattern: /^https?:\\/\\/.+/,\n          message: t('views.system.authentication.scanTheQRCode.callbackWarning'),\n          trigger: ['blur', 'change']\n        }\n      ]\n    default:\n      return []\n  }\n}\n\nconst open = async (platform: Platform) => {\n  visible.value = true\n  loading.value = true\n  Object.assign(currentPlatform, platform)\n\n  // 设置默认的 callback_url\n  const defaultCallbackUrl = window.location.origin + window.MaxKB.chatPrefix + '/api'\n  switch (platform.key) {\n    case 'wecom':\n      if (currentPlatform.config.app_key) {\n        currentPlatform.config.agent_id = currentPlatform.config.app_key\n        delete currentPlatform.config.app_key\n      }\n        currentPlatform.config.callback_url = `${defaultCallbackUrl}/auth/wecom`\n      break\n    case 'dingtalk':\n      if (currentPlatform.config.agent_id) {\n        currentPlatform.config.corp_id = currentPlatform.config.agent_id\n        delete currentPlatform.config.agent_id\n      }\n      currentPlatform.config = {\n        corp_id: currentPlatform.config.corp_id,\n        app_key: currentPlatform.config.app_key,\n        app_secret: currentPlatform.config.app_secret,\n        callback_url: defaultCallbackUrl\n      }\n      currentPlatform.config.callback_url = `${defaultCallbackUrl}/auth/dingtalk`\n      break\n    case 'lark':\n      currentPlatform.config.callback_url = `${defaultCallbackUrl}/auth/lark`\n      break\n    default:\n      break\n  }\n  formRef.value?.clearValidate()\n}\ndefineExpose({ open })\n\nconst validateForm = () => {\n  formRef.value?.validate((valid) => {\n    if (valid) {\n      saveConfig()\n    } else {\n      MsgError(t('views.system.authentication.scanTheQRCode.validateFailedTip'))\n    }\n  })\n}\n\nconst handleClose = () => {\n  visible.value = false\n  formRef.value?.clearValidate()\n  emit('refresh')\n}\n\nfunction validateConnection() {\n  platformApi.validateConnection(currentPlatform, loading).then((res: any) => {\n    if (res.data) {\n      MsgSuccess(t('views.system.authentication.scanTheQRCode.validateSuccess'))\n    } else {\n      MsgError(t('views.system.authentication.scanTheQRCode.validateFailed'))\n    }\n  })\n}\n\nconst passwordFields = new Set(['app_secret', 'client_secret', 'secret'])\n\nconst isPasswordField = (key: any) => passwordFields.has(key)\nconst emit = defineEmits(['refresh'])\n\nfunction saveConfig() {\n  platformApi.updateConfig(currentPlatform, loading).then((res: any) => {\n    MsgSuccess(t('common.saveSuccess'))\n    emit('refresh')\n    visible.value = false\n    formRef.value?.clearValidate()\n  })\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/authentication/component/LDAP.vue",
    "content": "<template>\n  <div class=\"authentication-setting__main main-calc-height\">\n    <el-scrollbar>\n      <div class=\"form-container p-24\" v-loading=\"loading\">\n        <el-form\n          ref=\"authFormRef\"\n          :rules=\"rules\"\n          :model=\"form\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n        >\n          <el-form-item\n            :label=\"$t('views.system.authentication.ldap.address')\"\n            prop=\"config.ldap_server\"\n          >\n            <el-input\n              v-model=\"form.config.ldap_server\"\n              :placeholder=\"$t('views.system.authentication.ldap.serverPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.ldap.bindDN')\"\n            prop=\"config.base_dn\"\n          >\n            <el-input\n              v-model=\"form.config.base_dn\"\n              :placeholder=\"$t('views.system.authentication.ldap.bindDNPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item :label=\"$t('views.system.password')\" prop=\"config.password\">\n            <el-input\n              v-model=\"form.config.password\"\n              :placeholder=\"$t('views.login.loginForm.password.placeholder')\"\n              show-password\n            />\n          </el-form-item>\n          <el-form-item :label=\"$t('views.system.authentication.ldap.ou')\" prop=\"config.ou\">\n            <el-input\n              v-model=\"form.config.ou\"\n              :placeholder=\"$t('views.system.authentication.ldap.ouPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.ldap.ldap_filter')\"\n            prop=\"config.ldap_filter\"\n          >\n            <el-input\n              v-model=\"form.config.ldap_filter\"\n              :placeholder=\"$t('views.system.authentication.ldap.ldap_filterPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.ldap.ldap_mapping')\"\n            prop=\"config.ldap_mapping\"\n          >\n            <el-input\n              v-model=\"form.config.ldap_mapping\"\n              placeholder='{\"name\":\"name\",\"email\":\"mail\",\"username\":\"cn\"}'\n            />\n          </el-form-item>\n          <el-form-item>\n            <el-checkbox v-model=\"form.is_active\">{{\n              $t('views.system.authentication.ldap.enableAuthentication')\n            }}</el-checkbox>\n          </el-form-item>\n        </el-form>\n\n        <div>\n          <span\n            v-hasPermission=\"\n              new ComplexPermission(\n                [RoleConst.ADMIN],\n                [PermissionConst.CHAT_USER_AUTH_EDIT],\n                [],\n                'OR',\n              )\n            \"\n            class=\"mr-12\"\n          >\n            <el-button @click=\"submit(authFormRef)\" type=\"primary\" :disabled=\"loading\">\n              {{ $t('common.save') }}\n            </el-button>\n          </span>\n          <span>\n            <el-button @click=\"submit(authFormRef, 'test')\" :disabled=\"loading\">\n              {{ $t('views.system.test') }}\n            </el-button>\n          </span>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch, onMounted } from 'vue'\nimport authApi from '@/api/chat-user/auth-setting'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { t } from '@/locales'\nimport { MsgSuccess } from '@/utils/message'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\nconst form = ref<any>({\n  id: '',\n  auth_type: 'LDAP',\n  config: {\n    ldap_server: '',\n    base_dn: '',\n    password: '',\n    ou: '',\n    ldap_filter: '',\n    ldap_mapping: '',\n  },\n  is_active: true,\n})\n\nconst authFormRef = ref()\n\nconst loading = ref(false)\n\nconst rules = reactive<FormRules<any>>({\n  'config.ldap_server': [\n    {\n      required: true,\n      message: t('views.system.authentication.ldap.serverPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.base_dn': [\n    {\n      required: true,\n      message: t('views.system.authentication.ldap.bindDNPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.password': [\n    {\n      required: true,\n      message: t('views.login.loginForm.password.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.ou': [\n    {\n      required: true,\n      message: t('views.system.authentication.ldap.ouPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.ldap_filter': [\n    {\n      required: true,\n      message: t('views.system.authentication.ldap.ldap_filterPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.ldap_mapping': [\n    {\n      required: true,\n      message: t('views.system.authentication.ldap.ldap_mappingPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst submit = async (formEl: FormInstance | undefined, test?: string) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      if (test) {\n        authApi.postAuthSetting(form.value, loading).then((res) => {\n          MsgSuccess(t('views.system.testSuccess'))\n        })\n      } else {\n        authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {\n          MsgSuccess(t('common.saveSuccess'))\n        })\n      }\n    }\n  })\n}\n\nfunction getDetail() {\n  authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {\n    if (res.data && JSON.stringify(res.data) !== '{}') {\n      form.value = res.data\n      if (res.data.config.ldap_mapping) {\n        form.value.config.ldap_mapping = JSON.stringify(JSON.parse(res.data.config.ldap_mapping))\n      }\n    }\n  })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/authentication/component/OAuth2.vue",
    "content": "<template>\n  <div class=\"authentication-setting__main main-calc-height\">\n    <el-scrollbar>\n      <div class=\"form-container p-24\" v-loading=\"loading\">\n        <el-form\n          ref=\"authFormRef\"\n          :rules=\"rules\"\n          :model=\"form\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n        >\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.authEndpoint')\"\n            prop=\"config.authEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.authEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oauth2.authEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.tokenEndpoint')\"\n            prop=\"config.tokenEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.tokenEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oauth2.tokenEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.userInfoEndpoint')\"\n            prop=\"config.userInfoEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.userInfoEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oauth2.userInfoEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.scope')\"\n            prop=\"config.scope\"\n          >\n            <el-input\n              v-model=\"form.config.scope\"\n              :placeholder=\"$t('views.system.authentication.oauth2.scopePlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.clientId')\"\n            prop=\"config.clientId\"\n          >\n            <el-input\n              v-model=\"form.config.clientId\"\n              :placeholder=\"$t('views.system.authentication.oauth2.clientIdPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.clientSecret')\"\n            prop=\"config.clientSecret\"\n          >\n            <el-input\n              v-model=\"form.config.clientSecret\"\n              :placeholder=\"$t('views.system.authentication.oauth2.clientSecretPlaceholder')\"\n              show-password\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.redirectUrl')\"\n            prop=\"config.redirectUrl\"\n          >\n            <el-input\n              v-model=\"form.config.redirectUrl\"\n              :placeholder=\"$t('views.system.authentication.oauth2.redirectUrlPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.filedMapping')\"\n            prop=\"config.fieldMapping\"\n          >\n            <el-input\n              v-model=\"form.config.fieldMapping\"\n              :placeholder=\"$t('views.system.authentication.oauth2.filedMappingPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item>\n            <el-checkbox v-model=\"form.is_active\"\n              >{{ $t('views.system.authentication.oauth2.enableAuthentication') }}\n            </el-checkbox>\n          </el-form-item>\n        </el-form>\n\n        <div>\n          <el-button @click=\"submit(authFormRef)\" type=\"primary\" :disabled=\"loading\"\n            v-hasPermission=\"\n                      new ComplexPermission(\n                        [RoleConst.ADMIN],\n                        [PermissionConst.CHAT_USER_AUTH_EDIT],\n                        [],'OR',)\"\n          >\n            {{ $t('common.save') }}\n          </el-button>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, onMounted } from 'vue'\nimport authApi from '@/api/chat-user/auth-setting'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { t } from '@/locales'\nimport { MsgSuccess } from '@/utils/message'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\nconst form = ref<any>({\n  id: '',\n  auth_type: 'OAuth2',\n  config: {\n    authEndpoint: '',\n    tokenEndpoint: '',\n    userInfoEndpoint: '',\n    scope: '',\n    clientId: '',\n    clientSecret: '',\n    redirectUrl: '',\n    fieldMapping: ''\n  },\n  is_active: true\n})\n\nconst authFormRef = ref()\n\nconst loading = ref(false)\n\nconst rules = reactive<FormRules<any>>({\n  'config.authEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.authEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.tokenEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.tokenEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.userInfoEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.userInfoEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.scope': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.scopePlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.clientId': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.clientIdPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.clientSecret': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.clientSecretPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.redirectUrl': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.redirectUrlPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.fieldMapping': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.filedMappingPlaceholder'),\n      trigger: 'blur'\n    }\n  ]\n})\n\nconst submit = async (formEl: FormInstance | undefined, test?: string) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {\n        MsgSuccess(t('common.saveSuccess'))\n      })\n    }\n  })\n}\n\nfunction getDetail() {\n  authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {\n    if (res.data && JSON.stringify(res.data) !== '{}') {\n      form.value = res.data\n    }\n    if (!form.value.config.redirectUrl) {\n      form.value.config.redirectUrl = window.location.origin + window.MaxKB.chatPrefix + '/api/auth/oauth2'\n    }\n  })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/authentication/component/OIDC.vue",
    "content": "<template>\n  <div class=\"authentication-setting__main main-calc-height\">\n    <el-scrollbar>\n      <div class=\"form-container p-24\" v-loading=\"loading\">\n        <el-form\n          ref=\"authFormRef\"\n          :rules=\"rules\"\n          :model=\"form\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n        >\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.authEndpoint')\"\n            prop=\"config.authEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.authEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oidc.authEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.tokenEndpoint')\"\n            prop=\"config.tokenEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.tokenEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oidc.tokenEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.userInfoEndpoint')\"\n            prop=\"config.userInfoEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.userInfoEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oidc.userInfoEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item label=\"Scope\" prop=\"config.scope\">\n            <el-input v-model=\"form.config.scope\" placeholder=\"openid+profile+email \" />\n          </el-form-item>\n          <el-form-item label=\"State\" prop=\"config.state\">\n            <el-input v-model=\"form.config.state\" placeholder=\"\" />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.clientId')\"\n            prop=\"config.clientId\"\n          >\n            <el-input\n              v-model=\"form.config.clientId\"\n              :placeholder=\"$t('views.system.authentication.oidc.clientIdPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.clientSecret')\"\n            prop=\"config.clientSecret\"\n          >\n            <el-input\n              v-model=\"form.config.clientSecret\"\n              :placeholder=\"$t('views.system.authentication.oidc.clientSecretPlaceholder')\"\n              show-password\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.filedMapping')\"\n            prop=\"config.fieldMapping\"\n          >\n            <el-input\n              v-model=\"form.config.fieldMapping\"\n              :placeholder=\"$t('views.system.authentication.oauth2.filedMappingPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.redirectUrl')\"\n            prop=\"config.redirectUrl\"\n          >\n            <el-input\n              v-model=\"form.config.redirectUrl\"\n              :placeholder=\"$t('views.system.authentication.oidc.redirectUrlPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item>\n            <el-checkbox v-model=\"form.is_active\"\n              >{{ $t('views.system.authentication.oidc.enableAuthentication') }}\n            </el-checkbox>\n          </el-form-item>\n        </el-form>\n\n        <div>\n          <el-button @click=\"submit(authFormRef)\" type=\"primary\" :disabled=\"loading\"\n            v-hasPermission=\"\n                      new ComplexPermission(\n                        [RoleConst.ADMIN],\n                        [PermissionConst.CHAT_USER_AUTH_EDIT],\n                        [],'OR',)\"\n          >\n            {{ $t('common.save') }}\n          </el-button>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch, onMounted } from 'vue'\nimport authApi from '@/api/chat-user/auth-setting'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { t } from '@/locales'\nimport { MsgSuccess } from '@/utils/message'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\nconst form = ref<any>({\n  id: '',\n  auth_type: 'OIDC',\n  config: {\n    authEndpoint: '',\n    tokenEndpoint: '',\n    userInfoEndpoint: '',\n    scope: '',\n    state: '',\n    clientId: '',\n    clientSecret: '',\n    fieldMapping: '{\"username\": \"preferred_username\", \"email\": \"email\"}',\n    redirectUrl: ''\n  },\n  is_active: true\n})\n\nconst authFormRef = ref()\n\nconst loading = ref(false)\n\nconst rules = reactive<FormRules<any>>({\n  'config.authEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.authEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.tokenEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.tokenEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.userInfoEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.userInfoEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.scope': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.scopePlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.clientId': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.clientIdPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.clientSecret': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.clientSecretPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.fieldMapping': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.filedMappingPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.redirectUrl': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.redirectUrlPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.logoutEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.logoutEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ]\n})\n\nconst submit = async (formEl: FormInstance | undefined, test?: string) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {\n        MsgSuccess(t('common.saveSuccess'))\n      })\n    }\n  })\n}\n\nfunction getDetail() {\n  authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {\n    if (res.data && JSON.stringify(res.data) !== '{}') {\n      form.value = res.data\n      if (\n        form.value.config.fieldMapping === '' ||\n        form.value.config.fieldMapping === undefined\n      ) {\n        form.value.config.fieldMapping = '{\"username\": \"preferred_username\", \"email\": \"email\"}'\n      }\n    }\n    if (!form.value.config.redirectUrl) {\n        form.value.config.redirectUrl = window.location.origin + window.MaxKB.chatPrefix + '/api/auth/oidc'\n      }\n  })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/authentication/component/SCAN.vue",
    "content": "<template>\n  <div v-loading=\"loading\" class=\"scan-height\">\n    <el-scrollbar>\n      <div v-for=\"item in platforms\" :key=\"item.key\" class=\"mb-16\">\n        <el-card class=\"border-none mb-16\" shadow=\"never\">\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <img :src=\"item.logoSrc\" alt=\"\" width=\"24px\" />\n              <h5 class=\"ml-8\">{{ item.name }}</h5>\n              <el-tag v-if=\"item.isValid\" size=\"small\" type=\"success\" class=\"ml-8\"\n                >{{ $t('views.system.authentication.scanTheQRCode.effective') }}\n              </el-tag>\n            </div>\n            <div>\n              <el-button type=\"primary\" v-if=\"!item.isValid\" @click=\"showDialog(item)\"\n                >{{ $t('views.system.authentication.scanTheQRCode.access') }}\n              </el-button>\n              <span v-if=\"item.isValid\">\n                <span class=\"mr-4\">{{\n                  item.isActive\n                    ? $t('views.system.authentication.scanTheQRCode.alreadyTurnedOn')\n                    : $t('views.system.authentication.scanTheQRCode.notEnabled')\n                }}</span>\n                <el-switch\n                  size=\"small\"\n                  v-model=\"item.isActive\"\n                  :disabled=\"!item.isValid\"\n                  @change=\"changeStatus(item)\"\n                />\n              </span>\n            </div>\n          </div>\n          <el-collapse-transition>\n            <div v-if=\"item.isValid\" class=\"border-t mt-16\">\n              <el-row :gutter=\"12\" class=\"mt-16\">\n                <el-col v-for=\"(value, key) in item.config\" :key=\"key\" :span=\"12\">\n                  <el-text class=\"color-secondary lighter\">{{ formatFieldName(key, item) }}</el-text>\n                  <div class=\"mt-4 mb-16 flex align-center\">\n                    <span\n                      v-if=\"key !== 'app_secret'\"\n                      class=\"vertical-middle lighter break-all ellipsis-1\"\n                      >{{ value }}</span\n                    >\n                    <span\n                      v-if=\"key === 'app_secret' && !showPassword[item.key]?.[key]\"\n                      class=\"vertical-middle lighter break-all ellipsis-1\"\n                      >************</span\n                    >\n                    <span\n                      v-if=\"key === 'app_secret' && showPassword[item.key]?.[key]\"\n                      class=\"vertical-middle lighter break-all ellipsis-1\"\n                      >{{ value }}</span\n                    >\n                    <span>\n                      <el-button type=\"primary\" text @click=\"() => copyClick(value)\">\n                        <AppIcon iconName=\"app-copy\" />\n                      </el-button>\n                    </span>\n\n                    <span class=\"ml-4\">\n                      <el-button\n                        v-if=\"key === 'app_secret'\"\n                        type=\"primary\"\n                        text\n                        @click=\"toggleShowPassword(item.key)\"\n                      >\n                        <AppIcon\n                          iconName=\"app-password-hide\"\n                          v-if=\"key === 'app_secret' && !showPassword[item.key]?.[key]\"\n                        />\n\n                        <el-icon v-if=\"key === 'app_secret' && showPassword[item.key]?.[key]\">\n                          <View />\n                        </el-icon> </el-button\n                    ></span>\n                  </div>\n                </el-col>\n              </el-row>\n              <el-button type=\"primary\" @click=\"showDialog(item)\">\n                {{ $t('common.edit') }}\n              </el-button>\n              <el-button @click=\"validateConnection(item)\">\n                {{ $t('views.system.authentication.scanTheQRCode.validate') }}\n              </el-button>\n            </div>\n          </el-collapse-transition>\n        </el-card>\n      </div>\n      <EditModel ref=\"EditModelRef\" @refresh=\"refresh\" />\n    </el-scrollbar>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { reactive, ref, onMounted } from 'vue'\nimport { copyClick } from '@/utils/clipboard'\nimport EditModel from './EditModal.vue'\nimport platformApi from '@/api/chat-user/auth-setting.ts'\nimport { MsgError, MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\n\ninterface PlatformConfig {\n  [key: string]: string\n}\n\ninterface Platform {\n  key: string\n  logoSrc: string\n  name: string\n  isActive: boolean\n  isValid: boolean\n  config: PlatformConfig\n}\n\nconst EditModelRef = ref()\nconst loading = ref(false)\nconst platforms = reactive<Platform[]>(initializePlatforms())\nconst showPassword = reactive<{ [platformKey: string]: { [key: string]: boolean } }>({})\n\nonMounted(() => {\n  getPlatformInfo()\n})\n\nfunction initializePlatforms(): Platform[] {\n  return [\n    createPlatform('wecom', t('views.system.authentication.scanTheQRCode.wecom')),\n    createPlatform('dingtalk', t('views.system.authentication.scanTheQRCode.dingtalk')),\n    createPlatform('lark', t('views.system.authentication.scanTheQRCode.lark')),\n  ]\n}\n\nfunction createPlatform(key: string, name: string): Platform {\n  let logo = ''\n  switch (key) {\n    case 'wecom':\n      logo = 'wechat-work'\n      break\n    case 'dingtalk':\n      logo = 'dingtalk'\n      break\n    case 'lark':\n      logo = 'lark'\n      break\n    default:\n      logo = '' // 默认值\n      break\n  }\n\n  const config = {\n    ...(key === 'wecom' ? { corp_id: '', agent_id: '' } : { app_key: '' }),\n    app_secret: '',\n    callback_url: '',\n  }\n\n  return {\n    key,\n    logoSrc: new URL(`../../../../assets/logo/logo_${logo}.svg`, import.meta.url).href,\n    name,\n    isActive: false,\n    isValid: false,\n    config,\n  }\n}\n\nfunction formatFieldName(key?: any, item?: Platform): string {\n  const fieldNames: { [key: string]: string } = {\n    corp_id: 'Corp ID',\n    app_key: item?.key != 'lark' ? 'APP Key' : 'App ID',\n    app_secret: 'APP Secret',\n    agent_id: 'Agent ID',\n    callback_url: t('views.application.applicationAccess.callback'),\n  }\n  return (\n    fieldNames[key as keyof typeof fieldNames] ||\n    (key ? key.charAt(0).toUpperCase() + key.slice(1) : '')\n  )\n}\n\nfunction getPlatformInfo() {\n  loading.value = true\n  platformApi.getPlatformInfo(loading).then((res: any) => {\n    if (res) {\n      platforms.forEach((platform) => {\n        const data = res.data.find((item: any) => item.auth_type === platform.key)\n        if (data) {\n          Object.assign(platform, {\n            isValid: data.is_valid,\n            isActive: data.is_active,\n            config: data.config,\n          })\n          if (platform.key === 'dingtalk') {\n            const { corp_id, app_key, app_secret } = platform.config\n            platform.config = {\n              corp_id,\n              app_key,\n              app_secret,\n              callback_url: platform.config.callback_url,\n            }\n          }\n          showPassword[platform.key] = {}\n          showPassword[platform.key]['app_secret'] = false\n        }\n      })\n    }\n  })\n}\n\nfunction validateConnection(currentPlatform: Platform) {\n  platformApi.validateConnection(currentPlatform, loading).then((res: any) => {\n    res.data\n      ? MsgSuccess(t('views.system.authentication.scanTheQRCode.validateSuccess'))\n      : MsgError(t('views.system.authentication.scanTheQRCode.validateFailed'))\n  })\n}\n\nfunction refresh() {\n  getPlatformInfo()\n}\n\nfunction changeStatus(currentPlatform: Platform) {\n  platformApi.updateConfig(currentPlatform, loading).then((res: any) => {\n    MsgSuccess(t('common.saveSuccess'))\n  })\n}\n\nfunction toggleShowPassword(platformKey: string) {\n  if (!showPassword[platformKey]) {\n    showPassword[platformKey] = {}\n  }\n  showPassword[platformKey]['app_secret'] = !showPassword[platformKey]['app_secret']\n}\n\nfunction showDialog(platform: Platform) {\n  EditModelRef.value?.open(platform)\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.scan-height {\n  height: calc(100vh - var(--app-header-height) - var(--app-view-padding) * 2 - 70px);\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/authentication/index.vue",
    "content": "<template>\n  <div class=\"authentication-setting p-16-24\">\n    <h4 class=\"mb-16\">{{ $t('views.system.authentication.title') }}</h4>\n\n    <el-tabs v-model=\"activeName\" class=\"mt-4\">\n      <template v-for=\"(item, index) in tabList\" :key=\"index\">\n        <el-tab-pane :label=\"item.label\" :name=\"item.name\">\n          <component :is=\"item.component\" />\n        </el-tab-pane>\n      </template>\n    </el-tabs>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted } from 'vue'\nimport { useRouter } from 'vue-router'\nimport LDAP from './component/LDAP.vue'\nimport CAS from './component/CAS.vue'\nimport OIDC from './component/OIDC.vue'\nimport SCAN from './component/SCAN.vue'\nimport OAuth2 from './component/OAuth2.vue'\nimport { t } from '@/locales'\n\nconst activeName = ref('LDAP')\nconst tabList = [\n  {\n    label: t('views.system.authentication.ldap.title'),\n    name: 'LDAP',\n    component: LDAP,\n  },\n  {\n    label: t('views.system.authentication.cas.title'),\n    name: 'CAS',\n    component: CAS,\n  },\n  {\n    label: t('views.system.authentication.oidc.title'),\n    name: 'OIDC',\n    component: OIDC,\n  },\n  {\n    label: t('views.system.authentication.oauth2.title'),\n    name: 'OAuth2',\n    component: OAuth2,\n  },\n  {\n    label: t('views.system.authentication.scanTheQRCode.title'),\n    name: 'SCAN',\n    component: SCAN,\n  },\n]\n\n\n\nonMounted(() => {\n  // if (user.isExpire()) {\n  //   router.push({ path: `/application` })\n  // }\n})\n</script>\n<style lang=\"scss\" scoped>\n.authentication-setting__main {\n  background-color: var(--app-view-bg-color);\n  box-sizing: border-box;\n  min-width: 700px;\n  height: calc(100vh - var(--app-header-height) - var(--app-view-padding) * 2 - 70px);\n  box-sizing: border-box;\n  :deep(.form-container) {\n    width: 70%;\n    margin: 0 auto;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/chat-user/component/SetUserGroupsDialog.vue",
    "content": "<template>\n  <el-dialog width=\"600\" :title=\"$t('views.chatUser.setUserGroups')\" v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\" :close-on-press-escape=\"false\" :destroy-on-close=\"true\">\n    <el-form label-position=\"top\" ref=\"formRef\" :rules=\"rules\" :model=\"form\" require-asterisk-position=\"right\">\n      <el-form-item :label=\"$t('views.chatUser.settingMethod')\" prop=\"is_append\">\n        <el-radio-group v-model=\"form.is_append\">\n          <el-radio :value=\"true\">{{ $t('views.chatUser.append') }}</el-radio>\n          <el-radio :value=\"false\">{{ $t('views.applicationOverview.SettingDisplayDialog.replace') }}</el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item :label=\"$t('views.chatUser.group.title')\" prop=\"user_group_ids\">\n        <el-select v-model=\"form.user_group_ids\" multiple filterable :placeholder=\"$t('common.selectPlaceholder')\"\n          :loading=\"props.optionLoading\">\n          <el-option v-for=\"item in props.chatGroupList\" :key=\"item.id\" :label=\"item.name\" :value=\"item.id\">\n          </el-option>\n        </el-select>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(formRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport userManageApi from '@/api/system/chat-user'\nimport type { ListItem } from '@/api/type/common'\nimport {loadPermissionApi} from \"@/utils/dynamics-api/permission-api.ts\";\n\nconst props = defineProps<{\n  optionLoading: boolean,\n  chatGroupList: ListItem[],\n}>()\n\nconst emit = defineEmits<{\n  (e: 'refresh'): void;\n}>();\n\nconst dialogVisible = ref<boolean>(false)\nconst defaultForm = {\n  user_group_ids: [],\n  is_append: true,\n  ids: []\n}\nconst form = ref<{\n  ids: string[], user_group_ids: string[], is_append: boolean\n}>({\n  ...defaultForm,\n})\n\nfunction open(ids: string[]) {\n  form.value = { ...defaultForm, ids }\n  dialogVisible.value = true\n}\n\nconst formRef = ref<FormInstance>();\n\nconst rules = reactive({\n  user_group_ids: [{ required: true, message: t('common.selectPlaceholder'), trigger: 'blur' }],\n  is_append: [{ required: true, message: t('common.selectPlaceholder'), trigger: 'blur' }],\n})\n\nconst loading = ref<boolean>(false)\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      loadPermissionApi('chatUser').batchAddGroup(form.value, loading).then(() => {\n        MsgSuccess(t('common.settingSuccess'))\n        emit('refresh')\n        dialogVisible.value = false\n      })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/chat-user/component/SyncUsersDialog.vue",
    "content": "<template>\n  <el-dialog v-model=\"dialogVisible\" :close-on-click-modal=\"false\" :close-on-press-escape=\"false\"\n             :destroy-on-close=\"true\" width=\"600\">\n    <template #header>\n      <h4 class=\"mb-8 medium\">{{ t('views.chatUser.syncUsers') }}</h4>\n      <div class=\"color-secondary lighter\">{{ t('views.chatUser.syncUsersTip') }}</div>\n    </template>\n    <el-form label-position=\"top\" ref=\"formRef\" :rules=\"rules\" :model=\"form\"\n             require-asterisk-position=\"right\">\n      <el-form-item :label=\"$t('views.userManage.source.label')\" prop=\"sync_type\">\n        <el-select v-model=\"form.sync_type\" :placeholder=\"$t('common.selectPlaceholder')\">\n          <el-option\n            v-for=\"option in syncTypeOptions\"\n            :key=\"option.value\"\n            :label=\"option.label\"\n            :value=\"option.value\"\n          />\n        </el-select>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(formRef)\" :loading=\"loading\">\n          {{ $t('common.sync') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport {ref, reactive} from 'vue'\nimport type {FormInstance} from 'element-plus'\nimport {MsgError, MsgSuccess} from '@/utils/message'\nimport {t} from '@/locales'\nimport userManageApi from '@/api/system/chat-user'\nimport systemChatUserApi from '@/api/system/chat-user'\n\nconst syncTypeOptions = ref<Array<{ label: string; value: string }>>([\n  {label: t('views.userManage.source.local'), value: 'LOCAL'},\n  {label: t('views.system.authentication.scanTheQRCode.wecom'), value: 'wecom'},\n  {label: 'LDAP', value: 'LDAP'},\n  {label: t('views.system.authentication.scanTheQRCode.lark'), value: 'lark'},\n])\n\nconst emit = defineEmits<{\n  (e: 'refresh'): void;\n}>();\n\nconst dialogVisible = ref<boolean>(false)\nconst defaultForm = {\n  sync_type: 'LOCAL',\n}\nconst form = ref<{\n  sync_type: string\n}>({\n  ...defaultForm,\n})\n\nfunction open() {\n  form.value = {...defaultForm}\n  getSyncType()\n  dialogVisible.value = true\n}\n\nasync function getSyncType() {\n  return systemChatUserApi.getSyncType().then((res) => {\n    if (res.data && res.data.length > 0) {\n      syncTypeOptions.value = syncTypeOptions.value.filter(option => res.data.includes(option.value))\n    }\n  })\n}\n\nconst formRef = ref<FormInstance>();\n\nconst rules = reactive({\n  sync_type: [{required: true, message: t('common.selectPlaceholder'), trigger: 'blur'}],\n})\n\nconst loading = ref<boolean>(false)\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      userManageApi.batchSync(form.value.sync_type, loading).then((res) => {\n        if (res.data) {\n          const count = res.data.success_count\n          let ErrorMsg = ''\n          if (res.data.conflict_users && res.data.conflict_users.length > 0) {\n            // 遍历res.data.conflict_users， 他是一个数组里面是对象\n            res.data.conflict_users.forEach((item: any) => {\n              if (item.type === 'username') {\n                ErrorMsg += '\\n\\n' + t('views.chatUser.syncMessage.usernameExist') + \" [ \" + item.users.join(',') + '\\n' + ' ]'\n              }\n              if (item.type === 'nick_name') {\n                ErrorMsg += '\\n\\n' + t('views.chatUser.syncMessage.nicknameExist') + \" [ \" + item.users.join(',') + '\\n' + ' ]'\n              }\n            })\n          }\n          MsgSuccess(t('views.chatUser.syncMessage.title', {count: count}) + ErrorMsg)\n          emit('refresh')\n          dialogVisible.value = false\n        }\n\n      })\n    }\n  })\n}\n\ndefineExpose({open})\n</script>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/chat-user/component/UserDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"60%\">\n    <template #header>\n      <h4>{{ props.title }}</h4>\n    </template>\n    <h4 class=\"title-decoration-1 mb-16 mt-8\">{{ $t('common.info') }}</h4>\n    <el-form ref=\"userFormRef\" :model=\"userForm\" :rules=\"rules\" label-position=\"top\"\n             require-asterisk-position=\"right\"\n             @submit.prevent :close-on-click-modal=\"false\" :close-on-press-escape=\"false\">\n      <el-form-item :prop=\"isEdit ? '' : 'username'\"\n                    :label=\"$t('views.login.loginForm.username.label')\">\n        <el-input v-model=\"userForm.username\"\n                  :placeholder=\"$t('views.login.loginForm.username.placeholder')\"\n                  maxlength=\"64\" show-word-limit :disabled=\"isEdit\">\n        </el-input>\n      </el-form-item>\n      <el-form-item prop=\"nick_name\" :label=\"$t('views.userManage.userForm.nick_name.label')\">\n        <el-input v-model=\"userForm.nick_name\"\n                  :placeholder=\"$t('views.userManage.userForm.nick_name.placeholder')\"\n                  maxlength=\"64\" show-word-limit>\n        </el-input>\n      </el-form-item>\n      <el-form-item :label=\"$t('views.login.loginForm.email.label')\" prop=\"email\">\n        <el-input type=\"email\" v-model=\"userForm.email\"\n                  :placeholder=\"$t('views.login.loginForm.email.placeholder')\">\n        </el-input>\n      </el-form-item>\n      <el-form-item :label=\"$t('views.userManage.userForm.phone.label')\" prop=\"phone\">\n        <el-input v-model=\"userForm.phone\"\n                  :placeholder=\"$t('views.userManage.userForm.phone.placeholder')\">\n        </el-input>\n      </el-form-item>\n      <el-form-item :label=\"$t('views.userManage.defaultPassword')\" v-if=\"!isEdit\">\n        <span class=\"mr-8\">{{ userForm.password }}</span>\n        <el-button type=\"primary\" link @click=\"copyClick(userForm.password)\">\n          <AppIcon iconName=\"app-copy\"></AppIcon>\n        </el-button>\n      </el-form-item>\n      <h4 class=\"title-decoration-1 mb-16 mt-8\">{{ $t('views.chatUser.group.title') }}</h4>\n      <el-form-item :label=\"$t('views.chatUser.group.title')\" prop=\"user_group_ids\">\n        <el-select v-model=\"userForm.user_group_ids\" multiple filterable\n                   :placeholder=\"`${$t('common.selectPlaceholder')}${$t('views.chatUser.group.title')}`\"\n                   :loading=\"props.optionLoading\">\n          <el-option v-for=\"item in props.chatGroupList\" :key=\"item.id\" :label=\"item.name\"\n                     :value=\"item.id\">\n          </el-option>\n        </el-select>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click.prevent=\"visible = false\"> {{ $t('common.cancel') }}</el-button>\n      <el-button type=\"primary\" @click=\"submit(userFormRef)\" :loading=\"loading\">\n        {{ $t('common.save') }}\n      </el-button>\n    </template>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport {ref, reactive, watch} from 'vue'\nimport type {FormInstance} from 'element-plus'\nimport userManageApi from '@/api/system/user-manage'\nimport {MsgSuccess} from '@/utils/message'\nimport {t} from '@/locales'\nimport type {ListItem} from '@/api/type/common'\nimport {copyClick} from '@/utils/clipboard'\nimport {loadPermissionApi} from \"@/utils/dynamics-api/permission-api.ts\";\n\nconst props = defineProps<{\n  title: string,\n  optionLoading: boolean,\n  chatGroupList: ListItem[],\n}>()\n\nconst emit = defineEmits(['refresh'])\n\nconst userFormRef = ref()\nconst userForm = ref<any>({\n  username: '',\n  email: '',\n  password: '',\n  phone: '',\n  nick_name: '',\n  user_group_ids: []\n})\n\nconst rules = reactive({\n  username: [\n    {\n      required: true,\n      message: t('views.login.loginForm.username.requiredMessage'),\n      trigger: 'blur',\n    },\n    {\n      min: 4,\n      max: 64,\n      message: t('views.login.loginForm.username.lengthMessage'),\n      trigger: 'blur',\n    },\n  ],\n  nick_name: [\n    {\n      required: true,\n      message: t('views.userManage.userForm.nick_name.placeholder'),\n      trigger: 'blur',\n    },\n    {\n      min: 1,\n      max: 64,\n      message: t('views.userManage.userForm.nick_name.lengthMessage'),\n      trigger: 'blur',\n    },\n  ],\n  phone: [\n    {\n      pattern: /^1[3-9]\\d{9}$/,\n      message: t('views.userManage.userForm.phone.invalidMessage'),\n      trigger: 'blur',\n    },\n  ],\n  user_group_ids: [\n    {\n      type: 'array',\n      required: true,\n      message: t('views.chatUser.group.requiredMessage'),\n      trigger: 'change',\n    },\n  ],\n})\nconst visible = ref<boolean>(false)\nconst loading = ref(false)\nconst isEdit = ref(false)\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    userForm.value = {\n      username: '',\n      email: '',\n      password: '',\n      phone: '',\n      nick_name: '',\n      user_group_ids: []\n    }\n    isEdit.value = false\n    userFormRef.value?.clearValidate()\n  }\n})\n\nconst open = (data: any) => {\n  if (data) {\n    userForm.value['id'] = data.id\n    userForm.value.username = data.username\n    userForm.value.email = data.email\n    userForm.value.phone = data.phone\n    userForm.value.nick_name = data.nick_name\n    userForm.value.user_group_ids = data.user_group_ids\n    isEdit.value = true\n  } else {\n    userManageApi.getSystemDefaultPassword().then((res: any) => {\n      userForm.value.password = res.data.password\n    })\n  }\n\n  visible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      if (isEdit.value) {\n        loadPermissionApi('chatUser').putUserManage(userForm.value.id, userForm.value, loading).then(() => {\n          emit('refresh')\n          MsgSuccess(t('common.editSuccess'))\n          visible.value = false\n        })\n      } else {\n        loadPermissionApi('chatUser').postUserManage(userForm.value, loading).then(() => {\n          emit('refresh')\n          MsgSuccess(t('common.createSuccess'))\n          visible.value = false\n        })\n      }\n    }\n  })\n}\n\ndefineExpose({open})\n</script>\n<style lang=\"scss\" scoped>\n\n</style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/chat-user/component/UserPwdDialog.vue",
    "content": "<template>\n  <el-dialog :title=\"$t('views.login.resetPassword')\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"userFormRef\"\n      :model=\"userForm\"\n      :rules=\"rules\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n    >\n      <el-form-item :label=\"$t('views.login.loginForm.new_password.label')\" prop=\"password\">\n        <el-input\n          type=\"password\"\n          v-model=\"userForm.password\"\n          :placeholder=\"$t('views.login.loginForm.new_password.placeholder')\"\n          show-password\n        >\n        </el-input>\n      </el-form-item>\n      <el-form-item :label=\"$t('views.login.loginForm.re_password.label')\" prop=\"re_password\">\n        <el-input\n          type=\"password\"\n          v-model=\"userForm.re_password\"\n          :placeholder=\"$t('views.login.loginForm.re_password.placeholder')\"\n          show-password\n        >\n        </el-input>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(userFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, watch } from 'vue'\nimport useStore from '@/stores'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport type { ResetPasswordRequest } from '@/api/type/user'\nimport userManageApi from '@/api/system/chat-user'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport {loadPermissionApi} from \"@/utils/dynamics-api/permission-api.ts\";\nconst emit = defineEmits(['refresh'])\n\nconst { user } = useStore()\n\nconst userFormRef = ref()\nconst userForm = ref<any>({\n  password: '',\n  re_password: ''\n})\n\nconst rules = reactive<FormRules<ResetPasswordRequest>>({\n  password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.new_password.requiredMessage'),\n      trigger: 'blur'\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur'\n    }\n  ],\n  re_password: [\n    {\n      required: true,\n      message: t('views.login.loginForm.re_password.requiredMessage'),\n      trigger: 'blur'\n    },\n    {\n      min: 6,\n      max: 20,\n      message: t('views.login.loginForm.password.lengthMessage'),\n      trigger: 'blur'\n    },\n    {\n      validator: (rule, value, callback) => {\n        if (userFormRef.value.password != userFormRef.value.re_password) {\n          callback(new Error(t('views.login.loginForm.re_password.validatorMessage')))\n        } else {\n          callback()\n        }\n      },\n      trigger: 'blur'\n    }\n  ]\n})\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\nconst userId = ref('')\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    userForm.value = {\n      password: '',\n      re_password: ''\n    }\n  }\n})\n\nconst open = (data: any) => {\n  userId.value = data.id\n  dialogVisible.value = true\n  userFormRef.value?.clearValidate()\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      loadPermissionApi('chatUser').putUserManagePassword(userId.value, userForm.value, loading).then(() => {\n        emit('refresh')\n        user.profile()\n        MsgSuccess(t('views.userManage.tip.updatePwdSuccess'))\n        dialogVisible.value = false\n      })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/chat-user/index.vue",
    "content": "<template>\n  <div class=\"chat-user p-24\">\n    <el-breadcrumb separator-icon=\"ArrowRight\" class=\"mb-16\">\n      <el-breadcrumb-item>{{ t('views.chatUser.title') }}</el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <h5 class=\"ml-4 color-text-primary\">{{ t('views.chatUser.title') }}</h5>\n      </el-breadcrumb-item>\n    </el-breadcrumb>\n    <el-card style=\"height: calc(var(--app-main-height) + 10px)\">\n      <div class=\"flex-between mb-16\">\n        <div>\n          <el-button\n            type=\"primary\"\n            @click=\"createUser()\"\n            v-hasPermission=\"\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                [PermissionConst.CHAT_USER_CREATE, PermissionConst.WORKSPACE_CHAT_USER_CREATE],\n                [],\n                'OR',\n              )\n            \"\n          >\n            {{ t('views.userManage.createUser') }}\n          </el-button>\n          <el-button\n            @click=\"syncUsers\"\n            v-hasPermission=\"\n              new ComplexPermission([RoleConst.ADMIN], [PermissionConst.CHAT_USER_SYNC], [], 'OR')\n            \"\n          >\n            {{ $t('views.chatUser.syncUsers') }}\n          </el-button>\n          <el-button\n            :disabled=\"multipleSelection.length === 0\"\n            @click=\"setUserGroups\"\n            v-hasPermission=\"\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                [PermissionConst.CHAT_USER_GROUP, PermissionConst.WORKSPACE_CHAT_USER_GROUP],\n                [],\n                'OR',\n              )\n            \"\n          >\n            {{ $t('views.chatUser.setUserGroups') }}\n          </el-button>\n          <el-button\n            :disabled=\"multipleSelection.length === 0\"\n            @click=\"handleBatchDelete\"\n            v-hasPermission=\"\n              new ComplexPermission(\n                [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                [PermissionConst.WORKSPACE_CHAT_USER_DELETE, PermissionConst.CHAT_USER_DELETE],\n                [],\n                'OR',\n              )\n            \"\n          >\n            {{ $t('common.delete') }}\n          </el-button>\n        </div>\n        <div class=\"flex-between complex-search\">\n          <el-select\n            class=\"complex-search__left\"\n            v-model=\"search_type\"\n            style=\"width: 120px\"\n            @change=\"search_type_change\"\n          >\n            <el-option :label=\"$t('views.login.loginForm.username.label')\" value=\"username\" />\n            <el-option :label=\"$t('views.userManage.userForm.nick_name.label')\" value=\"nick_name\" />\n            <el-option :label=\"$t('common.status.label')\" value=\"is_active\" />\n            <el-option :label=\"$t('views.userManage.source.label')\" value=\"source\" />\n          </el-select>\n          <el-input\n            v-if=\"search_type === 'username'\"\n            v-model=\"search_form.username\"\n            @change=\"getList\"\n            style=\"width: 220px\"\n            clearable\n          />\n          <el-input\n            v-if=\"search_type === 'nick_name'\"\n            v-model=\"search_form.nick_name\"\n            @change=\"getList\"\n            style=\"width: 220px\"\n            clearable\n          />\n          <el-select\n            v-else-if=\"search_type === 'is_active'\"\n            v-model=\"search_form.is_active\"\n            @change=\"getList\"\n            clearable\n            style=\"width: 220px\"\n          >\n            <el-option :label=\"$t('common.status.enabled')\" :value=\"true\" />\n            <el-option :label=\"$t('common.status.disabled')\" :value=\"false\" />\n          </el-select>\n          <el-select\n            v-else-if=\"search_type === 'source'\"\n            v-model=\"search_form.source\"\n            @change=\"getList\"\n            style=\"width: 220px\"\n            clearable\n            :placeholder=\"$t('common.inputPlaceholder')\"\n          >\n            <el-option :label=\"$t('views.userManage.source.local')\" value=\"LOCAL\" />\n            <el-option label=\"CAS\" value=\"CAS\" />\n            <el-option label=\"LDAP\" value=\"LDAP\" />\n            <el-option label=\"OIDC\" value=\"OIDC\" />\n            <el-option label=\"OAuth2\" value=\"OAuth2\" />\n            <el-option :label=\"$t('views.userManage.source.wecom')\" value=\"wecom\" />\n            <el-option :label=\"$t('views.userManage.source.lark')\" value=\"lark\" />\n            <el-option :label=\"$t('views.userManage.source.dingtalk')\" value=\"dingtalk\" />\n          </el-select>\n        </div>\n      </div>\n      <app-table\n        class=\"mt-16\"\n        :data=\"userTableData\"\n        :pagination-config=\"paginationConfig\"\n        @sizeChange=\"handleSizeChange\"\n        @changePage=\"getList\"\n        v-loading=\"loading\"\n        @selection-change=\"handleSelectionChange\"\n        @sort-change=\"handleSortChange\"\n        :maxTableHeight=\"270\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column\n          prop=\"nick_name\"\n          :label=\"$t('views.userManage.userForm.nick_name.label')\"\n          min-width=\"180\"\n          show-overflow-tooltip\n        />\n        <el-table-column\n          prop=\"username\"\n          :label=\"$t('common.username')\"\n          min-width=\"180\"\n          show-overflow-tooltip\n        />\n        <el-table-column prop=\"is_active\" :label=\"$t('common.status.label')\" width=\"100\">\n          <template #default=\"{ row }\">\n            <div v-if=\"row.is_active\" class=\"flex align-center\">\n              <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n                <SuccessFilled />\n              </el-icon>\n              <span class=\"color-text-primary\">\n                {{ $t('common.status.enabled') }}\n              </span>\n            </div>\n            <div v-else class=\"flex align-center\">\n              <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n              <span class=\"color-text-primary\">\n                {{ $t('common.status.disabled') }}\n              </span>\n            </div>\n          </template>\n        </el-table-column>\n\n        <el-table-column\n          prop=\"email\"\n          :label=\"$t('views.login.loginForm.email.label')\"\n          show-overflow-tooltip\n          min-width=\"180\"\n        >\n          <template #default=\"{ row }\">\n            {{ row.email || '-' }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"phone\"\n          :label=\"$t('views.userManage.userForm.phone.label')\"\n          width=\"120\"\n        >\n          <template #default=\"{ row }\">\n            {{ row.phone || '-' }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"user_group_names\"\n          :label=\"$t('views.chatUser.group.title')\"\n          min-width=\"150\"\n        >\n          <template #default=\"{ row }\">\n            <TagGroup :tags=\"row.user_group_names\" />\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"source\" :label=\"$t('views.userManage.source.label')\">\n          <template #default=\"{ row }\">\n            {{\n              row.source === 'LOCAL'\n                ? $t('views.userManage.source.localCreate')\n                : row.source === 'wecom'\n                  ? $t('views.userManage.source.wecom')\n                  : row.source === 'lark'\n                    ? $t('views.userManage.source.lark')\n                    : row.source === 'dingtalk'\n                      ? $t('views.userManage.source.dingtalk')\n                      : row.source === 'OAUTH2' || row.source === 'OAuth2'\n                        ? 'OAuth2'\n                        : row.source\n            }}\n          </template>\n        </el-table-column>\n\n        <el-table-column :label=\"$t('common.createTime')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.create_time) }}\n          </template>\n        </el-table-column>\n\n        <el-table-column :label=\"$t('common.operation')\" width=\"160\" align=\"left\" fixed=\"right\">\n          <template #default=\"{ row }\">\n            <span @click.stop>\n              <el-switch\n                size=\"small\"\n                v-model=\"row.is_active\"\n                :before-change=\"() => changeState(row)\"\n                v-if=\"\n                  hasPermission(\n                    new ComplexPermission(\n                      [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                      [PermissionConst.CHAT_USER_EDIT, PermissionConst.WORKSPACE_CHAT_USER_EDIT],\n                      [],\n                      'OR',\n                    ),\n                    'OR',\n                  )\n                \"\n              />\n            </span>\n            <el-divider direction=\"vertical\" />\n            <span class=\"mr-8\">\n              <el-button\n                type=\"primary\"\n                text\n                @click.stop=\"editUser(row)\"\n                :title=\"$t('common.edit')\"\n                v-if=\"\n                  hasPermission(\n                    new ComplexPermission(\n                      [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                      [PermissionConst.CHAT_USER_EDIT, PermissionConst.WORKSPACE_CHAT_USER_EDIT],\n                      [],\n                      'OR',\n                    ),\n                    'OR',\n                  )\n                \"\n              >\n                <AppIcon iconName=\"app-edit\"></AppIcon>\n              </el-button>\n            </span>\n\n            <span class=\"mr-8\">\n              <el-button\n                type=\"primary\"\n                text\n                @click.stop=\"editPwdUser(row)\"\n                :title=\"$t('views.userManage.setting.updatePwd')\"\n                v-if=\"\n                  hasPermission(\n                    new ComplexPermission(\n                      [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                      [PermissionConst.CHAT_USER_EDIT, PermissionConst.WORKSPACE_CHAT_USER_EDIT],\n                      [],\n                      'OR',\n                    ),\n                    'OR',\n                  )\n                \"\n              >\n                <AppIcon iconName=\"app-key\"></AppIcon>\n              </el-button>\n            </span>\n            <span>\n              <el-button\n                :disabled=\"row.role === 'ADMIN'\"\n                type=\"primary\"\n                text\n                @click.stop=\"deleteUserManage(row)\"\n                :title=\"$t('common.delete')\"\n                v-if=\"\n                  hasPermission(\n                    new ComplexPermission(\n                      [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                      [\n                        PermissionConst.CHAT_USER_DELETE,\n                        PermissionConst.WORKSPACE_CHAT_USER_DELETE,\n                      ],\n                      [],\n                      'OR',\n                    ),\n                    'OR',\n                  )\n                \"\n              >\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </span>\n          </template>\n        </el-table-column>\n      </app-table>\n    </el-card>\n\n    <UserDrawer\n      :title=\"title\"\n      :optionLoading=\"optionLoading\"\n      :chatGroupList=\"chatGroupList\"\n      ref=\"UserDrawerRef\"\n      @refresh=\"refresh\"\n    />\n    <UserPwdDialog ref=\"UserPwdDialogRef\" @refresh=\"refresh\" />\n    <SetUserGroupsDialog\n      :optionLoading=\"optionLoading\"\n      :chatGroupList=\"chatGroupList\"\n      ref=\"setUserGroupsRef\"\n      @refresh=\"refresh\"\n    />\n    <SyncUsersDialog ref=\"syncUsersDialogRef\" @refresh=\"refresh\" />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive } from 'vue'\nimport UserDrawer from './component/UserDrawer.vue'\nimport UserPwdDialog from './component/UserPwdDialog.vue'\nimport SetUserGroupsDialog from './component/SetUserGroupsDialog.vue'\nimport SyncUsersDialog from './component/SyncUsersDialog.vue'\nimport userManageApi from '@/api/system/chat-user'\nimport { datetimeFormat } from '@/utils/time'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { t } from '@/locales'\nimport type { ChatUserItem } from '@/api/type/systemChatUser'\nimport SystemGroupApi from '@/api/system/user-group'\nimport type { ListItem } from '@/api/type/common'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { hasPermission } from '@/utils/permission'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\n\nconst search_type = ref('username')\nconst search_form = ref<{\n  username: string\n  nick_name?: string\n  source?: string\n  is_active?: boolean | null\n}>({\n  username: '',\n  nick_name: '',\n  source: '',\n  is_active: null,\n})\nconst search_type_change = () => {\n  search_form.value = { username: '', nick_name: '', source: '', is_active: null }\n}\n\nconst loading = ref(false)\n\nconst multipleSelection = ref<any[]>([])\n\nfunction handleSelectionChange(val: any[]) {\n  multipleSelection.value = val\n}\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst userTableData = ref<ChatUserItem[]>([])\n\nfunction getList() {\n  const params: any = {}\n  const searchValue = search_form.value[search_type.value as keyof typeof search_form.value]\n  if (searchValue !== undefined && searchValue !== null && searchValue !== '') {\n    params[search_type.value] = searchValue\n  }\n  return loadPermissionApi('chatUser')\n    .getUserManage(paginationConfig, params, loading)\n    .then((res: any) => {\n      userTableData.value = res.data.records\n      paginationConfig.total = res.data.total\n    })\n}\n\nconst orderBy = ref<string>('')\n\nfunction handleSortChange({ prop, order }: { prop: string; order: string }) {\n  orderBy.value = order === 'ascending' ? prop : `-${prop}`\n  getList()\n}\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getList()\n}\n\nasync function changeState(row: ChatUserItem) {\n  const obj = {\n    ...row,\n    is_active: !row.is_active,\n  }\n  const str = obj.is_active ? t('common.status.enableSuccess') : t('common.status.disableSuccess')\n  await loadPermissionApi('chatUser')\n    .putUserManage(row.id, obj, loading)\n    .then(() => {\n      getList()\n      MsgSuccess(str)\n      return true\n    })\n    .catch(() => {\n      return false\n    })\n}\n\nconst title = ref('')\nconst UserDrawerRef = ref()\n\nfunction editUser(row: ChatUserItem) {\n  title.value = t('views.userManage.editUser')\n  UserDrawerRef.value.open(row)\n}\n\nfunction createUser() {\n  title.value = t('views.userManage.createUser')\n  UserDrawerRef.value.open()\n}\n\nfunction deleteUserManage(row: ChatUserItem) {\n  MsgConfirm(`${t('views.userManage.delete.confirmTitle')}${row.nick_name} ?`, '', {\n    confirmButtonText: t('common.confirm'),\n    confirmButtonClass: 'danger',\n  })\n    .then(() => {\n      loading.value = true\n      loadPermissionApi('chatUser')\n        .delUserManage(row.id, loading)\n        .then(() => {\n          MsgSuccess(t('common.deleteSuccess'))\n          getList()\n        })\n    })\n    .catch(() => {})\n}\n\nconst UserPwdDialogRef = ref()\n\nfunction editPwdUser(row: ChatUserItem) {\n  UserPwdDialogRef.value.open(row)\n}\n\nfunction refresh() {\n  getList()\n}\n\nonMounted(() => {\n  getChatGroupList()\n  getList()\n})\n\nconst optionLoading = ref(false)\nconst chatGroupList = ref<ListItem[]>([])\n\nasync function getChatGroupList() {\n  try {\n    const res = await loadPermissionApi('userGroup').getUserGroup(optionLoading)\n    chatGroupList.value = res.data\n  } catch (e) {\n    console.error(e)\n  }\n}\n\nfunction handleBatchDelete() {\n  MsgConfirm(t('views.chatUser.batchDeleteUser', { count: multipleSelection.value.length }), '', {\n    confirmButtonText: t('common.confirm'),\n    confirmButtonClass: 'danger',\n  })\n    .then(() => {\n      loadPermissionApi('chatUser')\n        .batchDelete(\n          multipleSelection.value.map((item) => item.id),\n          loading,\n        )\n        .then(async () => {\n          MsgSuccess(t('common.deleteSuccess'))\n          await getList()\n        })\n    })\n    .catch(() => {})\n}\n\nconst setUserGroupsRef = ref<InstanceType<typeof SetUserGroupsDialog>>()\n\nfunction setUserGroups() {\n  setUserGroupsRef.value?.open(multipleSelection.value.map((item) => item.id))\n}\n\nconst syncUsersDialogRef = ref<InstanceType<typeof SyncUsersDialog>>()\n\nfunction syncUsers() {\n  syncUsersDialogRef.value?.open()\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/group/component/CreateGroupUserDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.role.member.add')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"formRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item :label=\"$t('views.chatUser.group.usernameOrName')\" prop=\"user\">\n        <el-select\n          v-model=\"form.user\"\n          multiple\n          filterable\n          :reserve-keyword=\"false\"\n          :placeholder=\"$t('common.selectPlaceholder')\"\n          :loading=\"optionLoading\"\n          :filter-method=\"filterUser\"\n        >\n          <el-option\n            v-for=\"item in chatUserList\"\n            :key=\"item.id\"\n            :label=\"item.nick_name\"\n            :value=\"item.id\"\n          >\n          </el-option>\n        </el-select>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(formRef)\" :loading=\"loading\">\n          {{ $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive, onBeforeMount } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport SystemGroupApi from '@/api/system/user-group'\nimport userManageApi from '@/api/system/chat-user'\nimport type { ChatUserItem } from '@/api/type/systemChatUser'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\n\nconst emit = defineEmits<{\n  (e: 'refresh'): void\n}>()\n\nconst dialogVisible = ref<boolean>(false)\nconst defaultForm = {\n  user: [],\n}\nconst form = ref<{ user: string[] }>({\n  ...defaultForm,\n})\n\nconst optionLoading = ref(false)\nconst chatUserList = ref<ChatUserItem[]>([])\nconst originalChatUserList = ref<ChatUserItem[]>([])\nasync function getChatUserList() {\n  try {\n    const res = await loadPermissionApi('chatUser').getChatUserList(optionLoading)\n    originalChatUserList.value = res.data\n    chatUserList.value = [...res.data]\n  } catch (e) {\n    console.error(e)\n  }\n}\n\nconst filterUser = (query: string) => {\n  if (!query) {\n    chatUserList.value = originalChatUserList.value\n    return\n  }\n\n  const q = query.toLowerCase()\n  chatUserList.value = originalChatUserList.value.filter(\n    (item) => item.nick_name?.toLowerCase().includes(q) || item.username?.toLowerCase().includes(q),\n  )\n}\n\nonBeforeMount(() => {\n  getChatUserList()\n})\n\nconst groupId = ref('')\n\nfunction open(id: string) {\n  form.value = { ...defaultForm }\n  groupId.value = id\n  dialogVisible.value = true\n}\n\nconst formRef = ref<FormInstance>()\n\nconst rules = reactive({\n  user: [{ required: true, message: t('common.selectPlaceholder'), trigger: 'blur' }],\n})\n\nconst loading = ref<boolean>(false)\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      loadPermissionApi('userGroup')\n        .postAddMember(groupId.value, { user_ids: form.value.user }, loading)\n        .then(() => {\n          MsgSuccess(t('common.addSuccess'))\n          emit('refresh')\n          dialogVisible.value = false\n        })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/group/component/CreateOrUpdateGroupDialog.vue",
    "content": "<template>\n  <el-dialog :title=\"`${!form.id ? $t('common.create') : $t('common.rename')}${$t('views.chatUser.group.title')}`\"\n    v-model=\"dialogVisible\" :close-on-click-modal=\"false\" :close-on-press-escape=\"false\" :destroy-on-close=\"true\">\n    <el-form label-position=\"top\" ref=\"formRef\" :rules=\"rules\" :model=\"form\" require-asterisk-position=\"right\" @submit.prevent>\n      <el-form-item :label=\"$t('views.chatUser.group.name')\" prop=\"name\">\n        <el-input v-model=\"form.name\" maxlength=\"128\" show-word-limit\n          :placeholder=\"`${$t('common.inputPlaceholder')}${$t('views.chatUser.group.name')}`\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(formRef)\" :loading=\"loading\">\n          {{ !form.id ? $t('common.create') : $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport SystemGroupApi from '@/api/system/user-group'\nimport type { ListItem } from '@/api/type/common'\nimport {loadPermissionApi} from \"@/utils/dynamics-api/permission-api.ts\";\n\n\nconst emit = defineEmits<{\n  (e: 'refresh', current: ListItem): void;\n}>();\n\nconst dialogVisible = ref<boolean>(false)\nconst defaultForm = {\n  name: ''\n}\nconst form = ref<ListItem>({\n  ...defaultForm,\n})\nfunction open(item?: ListItem) {\n  if (item) {\n    form.value = { id: item.id, name: item.name }\n  } else {\n    form.value = { ...defaultForm }\n  }\n  dialogVisible.value = true\n}\n\nconst formRef = ref<FormInstance>();\n\nconst rules = reactive({\n  name: [{ required: true, message: `${t('common.inputPlaceholder')}${t('views.chatUser.group.name')}`, trigger: 'blur' }],\n})\n\nconst loading = ref<boolean>(false)\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      loadPermissionApi('userGroup').postUserGroup(form.value, loading).then((res: any) => {\n        MsgSuccess(!form.value.id ? t('common.createSuccess') : t('common.renameSuccess'))\n        emit('refresh', res.data)\n        dialogVisible.value = false\n      })\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n"
  },
  {
    "path": "ui/src/views/system-chat-user/group/index.vue",
    "content": "<template>\n  <div class=\"group p-24\">\n    <el-breadcrumb separator-icon=\"ArrowRight\" class=\"mb-16\">\n      <el-breadcrumb-item>{{ t('views.chatUser.title') }}</el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <h5 class=\"ml-4 color-text-primary\">{{ t('views.chatUser.group.title') }}</h5>\n      </el-breadcrumb-item>\n    </el-breadcrumb>\n    <el-card style=\"--el-card-padding: 0\">\n      <div class=\"flex\">\n        <div class=\"user-left border-r\">\n          <div class=\"p-24 pb-0\">\n            <div class=\"flex-between mb-12\">\n              <h4 class=\"medium\">{{ $t('views.chatUser.group.title') }}</h4>\n              <el-tooltip\n                effect=\"dark\"\n                :content=\"`${$t('common.create')}${$t('views.chatUser.group.title')}`\"\n                placement=\"top\"\n              >\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click=\"createOrUpdate()\"\n                  v-hasPermission=\"\n                    new ComplexPermission(\n                      [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                      [\n                        PermissionConst.WORKSPACE_USER_GROUP_CREATE,\n                        PermissionConst.USER_GROUP_CREATE,\n                      ],\n                      [],\n                      'OR',\n                    )\n                  \"\n                >\n                  <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n            </div>\n\n            <el-input\n              v-model=\"filterText\"\n              :placeholder=\"$t('common.search')\"\n              prefix-icon=\"Search\"\n              clearable\n              filterable\n            />\n          </div>\n\n          <div class=\"list-height-left\">\n            <el-scrollbar v-loading=\"loading\">\n              <div class=\"p-8-16\">\n                <common-list\n                  :data=\"filterList\"\n                  @click=\"clickUserGroup\"\n                  :default-active=\"current?.id\"\n                  @mouseenter=\"mouseenter\"\n                  @mouseleave=\"mouseId = ''\"\n                >\n                  <template #default=\"{ row }\">\n                    <div class=\"flex-between\">\n                      <span class=\"ellipsis\" :title=\"row.name\">{{ i18n_name(row.name) }}</span>\n                      <div @click.stop v-show=\"mouseId === row.id\">\n                        <el-dropdown\n                          :teleported=\"false\"\n                          trigger=\"click\"\n                          v-if=\"editPermission() || dlePermission()\"\n                        >\n                          <el-button text>\n                            <AppIcon iconName=\"app-more\"></AppIcon>\n                          </el-button>\n                          <template #dropdown>\n                            <el-dropdown-menu style=\"min-width: 80px\">\n                              <el-dropdown-item\n                                @click.stop=\"createOrUpdate(row)\"\n                                class=\"p-8\"\n                                v-if=\"editPermission()\"\n                              >\n                                <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n                                {{ $t('common.rename') }}\n                              </el-dropdown-item>\n                              <el-dropdown-item\n                                @click.stop=\"deleteGroup(row)\"\n                                class=\"border-t p-8\"\n                                v-if=\"dlePermission()\"\n                              >\n                                <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                                {{ $t('common.delete') }}\n                              </el-dropdown-item>\n                            </el-dropdown-menu>\n                          </template>\n                        </el-dropdown>\n                      </div>\n                    </div>\n                  </template>\n                  <template #empty>\n                    <span></span>\n                  </template>\n                </common-list>\n              </div>\n            </el-scrollbar>\n          </div>\n        </div>\n\n        <!-- 右边 -->\n        <div class=\"user-right\" v-loading=\"rightLoading\">\n          <div class=\"flex align-center\">\n            <h4 class=\"medium ellipsis\" :title=\"current?.name\">{{ i18n_name(current?.name as string) }}</h4>\n            <el-divider direction=\"vertical\" class=\"mr-8 ml-8\" />\n            <AppIcon\n              iconName=\"app-workspace\"\n              style=\"font-size: 16px\"\n              class=\"color-input-placeholder\"\n            ></AppIcon>\n            <span class=\"color-input-placeholder ml-4\">\n              {{ paginationConfig.total }}\n            </span>\n          </div>\n\n          <div class=\"flex-between mb-16\" style=\"margin-top: 20px\">\n            <div>\n              <el-button\n                type=\"primary\"\n                @click=\"createUser()\"\n                v-hasPermission=\"\n                  new ComplexPermission(\n                    [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                    [\n                      PermissionConst.WORKSPACE_USER_GROUP_ADD_MEMBER,\n                      PermissionConst.USER_GROUP_ADD_MEMBER,\n                    ],\n                    [],\n                    'OR',\n                  )\n                \"\n              >\n                {{ t('views.role.member.add') }}\n              </el-button>\n              <el-button\n                :disabled=\"multipleSelection.length === 0\"\n                @click=\"handleDeleteUser()\"\n                v-hasPermission=\"\n                  new ComplexPermission(\n                    [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                    [\n                      PermissionConst.WORKSPACE_USER_GROUP_REMOVE_MEMBER,\n                      PermissionConst.USER_GROUP_REMOVE_MEMBER,\n                    ],\n                    [],\n                    'OR',\n                  )\n                \"\n              >\n                {{ $t('common.remove') }}\n              </el-button>\n            </div>\n            <div class=\"flex-between complex-search\">\n              <el-select class=\"complex-search__left\" v-model=\"searchType\" style=\"width: 120px\">\n                <el-option :label=\"$t('views.login.loginForm.username.label')\" value=\"username\" />\n                <el-option\n                  :label=\"$t('views.userManage.userForm.nick_name.label')\"\n                  value=\"nick_name\"\n                />\n                <el-option :label=\"$t('views.userManage.source.label')\" value=\"source\" />\n              </el-select>\n              <el-input\n                v-if=\"searchType === 'username'\"\n                v-model=\"searchForm.username\"\n                @change=\"getList\"\n                :placeholder=\"$t('common.searchBar.placeholder')\"\n                style=\"width: 220px\"\n                clearable\n              />\n              <el-input\n                v-else-if=\"searchType === 'nick_name'\"\n                v-model=\"searchForm.nick_name\"\n                @change=\"getList\"\n                :placeholder=\"$t('common.searchBar.placeholder')\"\n                style=\"width: 220px\"\n                clearable\n              />\n              <el-select\n                v-else-if=\"searchType === 'source'\"\n                v-model=\"searchForm.source\"\n                @change=\"getList\"\n                style=\"width: 220px\"\n                clearable\n                :placeholder=\"$t('common.inputPlaceholder')\"\n              >\n                <el-option :label=\"$t('views.userManage.source.local')\" value=\"LOCAL\" />\n                <el-option label=\"CAS\" value=\"CAS\" />\n                <el-option label=\"LDAP\" value=\"LDAP\" />\n                <el-option label=\"OIDC\" value=\"OIDC\" />\n                <el-option label=\"OAuth2\" value=\"OAuth2\" />\n                <el-option :label=\"$t('views.userManage.source.wecom')\" value=\"wecom\" />\n                <el-option :label=\"$t('views.userManage.source.lark')\" value=\"lark\" />\n                <el-option :label=\"$t('views.userManage.source.dingtalk')\" value=\"dingtalk\" />\n              </el-select>\n            </div>\n          </div>\n\n          <app-table\n            :data=\"tableData\"\n            :pagination-config=\"paginationConfig\"\n            @sizeChange=\"handleSizeChange\"\n            @changePage=\"getList\"\n            @selection-change=\"handleSelectionChange\"\n            :maxTableHeight=\"330\"\n          >\n            <el-table-column type=\"selection\" width=\"55\" />\n            <el-table-column\n              prop=\"nick_name\"\n              :label=\"$t('views.userManage.userForm.nick_name.label')\"\n              show-overflow-tooltip\n            />\n            <el-table-column prop=\"username\" :label=\"$t('views.login.loginForm.username.label')\" />\n            <el-table-column prop=\"source\" :label=\"$t('views.userManage.source.label')\">\n              <template #default=\"{ row }\">\n                {{\n                  row.source === 'LOCAL'\n                    ? $t('views.userManage.source.local')\n                    : row.source === 'wecom'\n                      ? $t('views.userManage.source.wecom')\n                      : row.source === 'lark'\n                        ? $t('views.userManage.source.lark')\n                        : row.source === 'dingtalk'\n                          ? $t('views.userManage.source.dingtalk')\n                          : row.source === 'OAUTH2' || row.source === 'OAuth2'\n                            ? 'OAuth2'\n                            : row.source\n                }}\n              </template>\n            </el-table-column>\n            <el-table-column :label=\"$t('common.operation')\" width=\"100\" fixed=\"right\">\n              <template #default=\"{ row }\">\n                <el-tooltip effect=\"dark\" :content=\"`${$t('common.remove')}`\" placement=\"top\">\n                  <el-button\n                    type=\"primary\"\n                    text\n                    @click.stop=\"handleDeleteUser(row)\"\n                    v-hasPermission=\"\n                      new ComplexPermission(\n                        [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n                        [\n                          PermissionConst.WORKSPACE_USER_GROUP_REMOVE_MEMBER,\n                          PermissionConst.USER_GROUP_REMOVE_MEMBER,\n                        ],\n                        [],\n                        'OR',\n                      )\n                    \"\n                  >\n                    <AppIcon iconName=\"app-delete-users\"></AppIcon>\n                  </el-button>\n                </el-tooltip>\n              </template>\n            </el-table-column>\n          </app-table>\n        </div>\n      </div>\n    </el-card>\n\n    <CreateOrUpdateGroupDialog ref=\"createOrUpdateGroupDialogRef\" @refresh=\"refresh\" />\n    <CreateGroupUserDialog ref=\"createGroupUserDialogRef\" @refresh=\"getList\" />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, watch, reactive } from 'vue'\nimport SystemGroupApi from '@/api/system/user-group'\nimport { t } from '@/locales'\nimport { i18n_name } from '@/utils/common'\nimport type { ChatUserGroupUserItem } from '@/api/type/systemChatUser'\nimport CreateOrUpdateGroupDialog from './component/CreateOrUpdateGroupDialog.vue'\nimport CreateGroupUserDialog from './component/CreateGroupUserDialog.vue'\nimport type { ListItem } from '@/api/type/common'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\nimport { hasPermission } from '@/utils/permission/index'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\n\nconst filterText = ref('')\nconst loading = ref(false)\nconst list = ref<ListItem[]>([])\nconst filterList = ref<ListItem[]>([]) // 搜索过滤后列表\nconst current = ref<ListItem>()\n\nasync function getUserGroupList() {\n  try {\n    const res = await loadPermissionApi('userGroup').getUserGroup(loading)\n    list.value = res.data\n    filterList.value = filter(list.value, filterText.value)\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nconst editPermission = () => {\n  return hasPermission(\n    new ComplexPermission(\n      [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n      [PermissionConst.WORKSPACE_USER_GROUP_EDIT, PermissionConst.USER_GROUP_EDIT],\n      [],\n      'OR',\n    ),\n    'OR',\n  )\n}\n\nconst dlePermission = () => {\n  return hasPermission(\n    new ComplexPermission(\n      [RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE],\n      [PermissionConst.WORKSPACE_USER_GROUP_DELETE, PermissionConst.USER_GROUP_DELETE],\n      [],\n      'OR',\n    ),\n    'OR',\n  )\n}\n\nonMounted(async () => {\n  await getUserGroupList()\n  current.value = list.value[0]\n})\n\nfunction filter(list: ListItem[], filterText: string) {\n  if (!filterText.length) {\n    return list\n  }\n  return list.filter((v: ListItem) => v.name.toLowerCase().includes(filterText.toLowerCase()))\n}\n\nwatch(filterText, (val: string) => {\n  filterList.value = filter(list.value, val)\n})\n\nfunction clickUserGroup(item: ListItem) {\n  current.value = item\n}\n\nconst createOrUpdateGroupDialogRef = ref<InstanceType<typeof CreateOrUpdateGroupDialog>>()\n\nfunction createOrUpdate(item?: ListItem) {\n  createOrUpdateGroupDialogRef.value?.open(item)\n}\n\nfunction deleteGroup(item: ListItem) {\n  MsgConfirm(\n    `${t('views.chatUser.group.delete.confirmTitle')}${item.name} ?`,\n    t('views.chatUser.group.delete.confirmMessage'),\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loadPermissionApi('userGroup')\n        .delUserGroup(item.id as string, loading)\n        .then(async () => {\n          MsgSuccess(t('common.deleteSuccess'))\n          await getUserGroupList()\n          current.value = item.id === current.value?.id ? list.value[0] : current.value\n        })\n    })\n    .catch(() => {})\n}\n\nasync function refresh(group?: ListItem) {\n  await getUserGroupList()\n  // 创建后选中新建的\n  if (group) {\n    current.value = group\n  } else {\n    current.value = list.value.find((item) => item.id === current.value?.id)\n  }\n}\n\nconst rightLoading = ref(false)\n\nconst searchType = ref('username')\nconst searchForm = ref<Record<string, any>>({\n  username: '',\n  nick_name: '',\n  source: '',\n})\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst tableData = ref<ChatUserGroupUserItem[]>([])\n\nasync function getList() {\n  if (!current.value?.id) return\n  try {\n    const params = {\n      [searchType.value]: searchForm.value[searchType.value as keyof typeof searchForm.value],\n    }\n    const res = await loadPermissionApi('userGroup').getUserListByGroup(\n      current.value?.id,\n      paginationConfig,\n      params,\n      rightLoading,\n    )\n    tableData.value = res.data.records\n    paginationConfig.total = res.data.total\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nfunction handleSizeChange() {\n  paginationConfig.current_page = 1\n  getList()\n}\n\nwatch(\n  () => current.value?.id,\n  () => {\n    getList()\n  },\n)\n\nconst createGroupUserDialogRef = ref<InstanceType<typeof CreateGroupUserDialog>>()\n\nfunction createUser() {\n  createGroupUserDialogRef.value?.open(current.value?.id as string)\n}\n\nconst multipleSelection = ref<any[]>([])\n\nfunction handleSelectionChange(val: any[]) {\n  multipleSelection.value = val\n}\n\nfunction handleDeleteUser(item?: ChatUserGroupUserItem) {\n  MsgConfirm(\n    item\n      ? `${t('views.workspace.member.delete.confirmTitle')}${item.nick_name} ?`\n      : t('views.chatUser.group.batchDeleteMember', { count: multipleSelection.value.length }),\n    '',\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loadPermissionApi('userGroup')\n        .postRemoveMember(\n          current.value?.id as string,\n          {\n            group_relation_ids: item\n              ? [item.user_group_relation_id]\n              : multipleSelection.value.map((item) => item.user_group_relation_id),\n          },\n          loading,\n        )\n        .then(async () => {\n          MsgSuccess(t('common.removeSuccess'))\n          await getList()\n        })\n    })\n    .catch(() => {})\n}\n\nconst mouseId = ref('')\n\nfunction mouseenter(row: any) {\n  mouseId.value = row.id\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.user-left {\n  box-sizing: border-box;\n  width: var(--setting-left-width);\n  min-width: var(--setting-left-width);\n\n  .list-height-left {\n    height: calc(100vh - 231px);\n  }\n}\n\n.user-right {\n  flex: 1;\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n  padding: 24px;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-resource-management/ApplicationResourceIndex.vue",
    "content": "<template>\n  <div class=\"p-16-24\">\n    <el-breadcrumb separator-icon=\"ArrowRight\">\n      <el-breadcrumb-item>{{ t('views.system.resource_management.label') }}</el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <h5 class=\"ml-4 color-text-primary\">{{ t('views.application.title') }}</h5>\n      </el-breadcrumb-item>\n    </el-breadcrumb>\n    <el-card class=\"mt-16\" style=\"height: calc(var(--app-main-height) + 20px)\">\n      <div class=\"flex-between mb-16\">\n        <div class=\"complex-search\">\n          <el-select\n            class=\"complex-search__left\"\n            v-model=\"search_type\"\n            style=\"width: 120px\"\n            @change=\"search_type_change\"\n          >\n            <el-option :label=\"$t('common.creator')\" value=\"create_user\" />\n\n            <el-option :label=\"$t('common.name')\" value=\"name\" />\n            <el-option :label=\"$t('common.type')\" value=\"type\" />\n          </el-select>\n          <el-input\n            v-if=\"search_type === 'name'\"\n            v-model=\"search_form.name\"\n            @change=\"getList\"\n            :placeholder=\"$t('common.searchBar.placeholder')\"\n            style=\"width: 220px\"\n            clearable\n          />\n          <el-select\n            v-else-if=\"search_type === 'create_user'\"\n            v-model=\"search_form.create_user\"\n            @change=\"getList\"\n            clearable\n            filterable\n            style=\"width: 220px\"\n          >\n            <el-option v-for=\"u in user_options\" :key=\"u.id\" :value=\"u.id\" :label=\"u.nick_name\" />\n          </el-select>\n          <el-select\n            v-else-if=\"search_type === 'type'\"\n            v-model=\"search_form.type\"\n            @change=\"getList\"\n            clearable\n            filterable\n            style=\"width: 220px\"\n          >\n            <el-option v-for=\"u in type_options\" :key=\"u.id\" :value=\"u.value\" :label=\"u.label\" />\n          </el-select>\n        </div>\n      </div>\n\n      <app-table\n        :data=\"applicationList\"\n        :pagination-config=\"paginationConfig\"\n        @sizeChange=\"getList\"\n        @changePage=\"getList\"\n        :maxTableHeight=\"260\"\n      >\n        <!-- <el-table-column type=\"selection\" width=\"55\" /> -->\n        <el-table-column width=\"220\" :label=\"$t('common.name')\" show-overflow-tooltip>\n          <template #default=\"{ row }\">\n            <el-space :size=\"8\">\n              <el-avatar shape=\"square\" :size=\"24\" style=\"background: none\">\n                <img :src=\"resetUrl(row?.icon)\" alt=\"\" />\n              </el-avatar>\n              <span class=\"ellipsis\" style=\"max-width: 160px\">\n                {{ row.name }}\n              </span>\n            </el-space>\n          </template>\n        </el-table-column>\n\n        <el-table-column prop=\"tool_type\" :label=\"$t('common.type')\" width=\"100\">\n          <template #default=\"scope\">\n            <el-tag size=\"small\" class=\"warning-tag\" v-if=\"isWorkFlow(scope.row.type)\">\n              {{ $t('views.application.senior') }}\n            </el-tag>\n            <el-tag size=\"small\" class=\"blue-tag\" v-else>\n              {{ $t('views.application.simple') }}\n            </el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column\n          width=\"150\"\n          prop=\"is_publish\"\n          :label=\"$t('common.status.label')\"\n          show-overflow-tooltip\n        >\n          <template #header>\n            <div>\n              <span>{{ $t('common.status.label') }}</span>\n              <el-popover :width=\"100\" trigger=\"click\" :visible=\"statusVisible\" :persistent=\"false\">\n                <template #reference>\n                  <el-button\n                    style=\"margin-top: -2px\"\n                    :type=\"statusArr && statusArr.length > 0 ? 'primary' : ''\"\n                    link\n                    @click=\"statusVisible = !statusVisible\"\n                  >\n                    <el-icon>\n                      <Filter />\n                    </el-icon>\n                  </el-button>\n                </template>\n                <div class=\"filter\">\n                  <div class=\"form-item mb-16\">\n                    <div @click.stop>\n                      <el-checkbox-group\n                        v-model=\"statusArr\"\n                        style=\"display: flex; flex-direction: column\"\n                      >\n                        <el-checkbox\n                          v-for=\"item in statusOptions\"\n                          :key=\"item.value\"\n                          :label=\"item.label\"\n                          :value=\"item.value\"\n                        />\n                      </el-checkbox-group>\n                    </div>\n                  </div>\n                </div>\n                <div class=\"text-right\">\n                  <el-button size=\"small\" @click=\"filterStatusChange('clear')\"\n                    >{{ $t('common.clear') }}\n                  </el-button>\n                  <el-button type=\"primary\" @click=\"filterStatusChange\" size=\"small\"\n                    >{{ $t('common.confirm') }}\n                  </el-button>\n                </div>\n              </el-popover>\n            </div>\n          </template>\n          <template #default=\"scope\">\n            <div v-if=\"scope.row.is_publish\" class=\"flex align-center\">\n              <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n                <SuccessFilled />\n              </el-icon>\n              <span class=\"color-text-primary\">\n                {{ $t('common.status.published') }}\n              </span>\n            </div>\n            <div v-else class=\"flex align-center\">\n              <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n              <span class=\"color-text-primary\">\n                {{ $t('common.status.unpublished') }}\n              </span>\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column\n          v-if=\"user.isEE()\"\n          width=\"150\"\n          prop=\"workspace_name\"\n          :label=\"$t('views.workspace.title')\"\n          show-overflow-tooltip\n        >\n          <template #header>\n            <div>\n              <span>{{ $t('views.workspace.title') }}</span>\n              <el-popover\n                :width=\"200\"\n                trigger=\"click\"\n                :visible=\"workspaceVisible\"\n                :persistent=\"false\"\n              >\n                <template #reference>\n                  <el-button\n                    style=\"margin-top: -2px\"\n                    :type=\"workspaceArr && workspaceArr.length > 0 ? 'primary' : ''\"\n                    link\n                    @click=\"workspaceVisible = !workspaceVisible\"\n                  >\n                    <el-icon>\n                      <Filter />\n                    </el-icon>\n                  </el-button>\n                </template>\n                <div class=\"filter\">\n                  <div class=\"form-item mb-16 ml-4\">\n                    <div @click.stop>\n                      <el-input\n                        v-model=\"filterText\"\n                        :placeholder=\"$t('common.search')\"\n                        prefix-icon=\"Search\"\n                        clearable\n                      />\n                      <el-scrollbar height=\"300\" v-if=\"filterData.length\">\n                        <el-checkbox-group\n                          v-model=\"workspaceArr\"\n                          style=\"display: flex; flex-direction: column\"\n                        >\n                          <el-checkbox\n                            v-for=\"item in filterData\"\n                            :key=\"item.value\"\n                            :label=\"item.label\"\n                            :value=\"item.value\"\n                          />\n                        </el-checkbox-group>\n                      </el-scrollbar>\n                      <el-empty v-else :description=\"$t('common.noData')\" />\n                    </div>\n                  </div>\n                </div>\n                <div class=\"text-right\">\n                  <el-button size=\"small\" @click=\"filterWorkspaceChange('clear')\"\n                    >{{ $t('common.clear') }}\n                  </el-button>\n                  <el-button type=\"primary\" @click=\"filterWorkspaceChange\" size=\"small\"\n                    >{{ $t('common.confirm') }}\n                  </el-button>\n                </div>\n              </el-popover>\n            </div>\n          </template>\n        </el-table-column>\n\n        <el-table-column prop=\"nick_name\" :label=\"$t('common.creator')\" show-overflow-tooltip />\n        <el-table-column :label=\"$t('views.application.publishTime')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.update_time) }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.createTime')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.create_time) }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"120\" fixed=\"right\">\n          <template #default=\"{ row }\">\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('views.application.operation.toChat')\"\n              placement=\"top\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  :title=\"$t('views.application.operation.toChat')\"\n                  @click.stop=\"toChat(row)\"\n                >\n                  <AppIcon iconName=\"app-create-chat\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('views.system.resource_management.management')\"\n              placement=\"top\"\n              v-if=\"managePermission()\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  :title=\"$t('views.system.resource_management.management')\"\n                  @click=\"\n                    router.push({\n                      path: `/application/resource-management/${row.id}/${row.type}/overview`,\n                    })\n                  \"\n                >\n                  <AppIcon iconName=\"app-admin-operation\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-dropdown trigger=\"click\" v-if=\"MoreFilledPermission()\">\n              <el-button text @click.stop type=\"primary\">\n                <AppIcon iconName=\"app-more\"></AppIcon>\n              </el-button>\n              <template #dropdown>\n                <el-dropdown-menu>\n                  <el-dropdown-item\n                    @click.stop=\"openAuthorization(row)\"\n                    v-if=\"permissionPrecise.auth()\"\n                  >\n                    <AppIcon\n                      iconName=\"app-resource-authorization\"\n                      class=\"color-secondary\"\n                    ></AppIcon>\n                    {{ $t('views.system.resourceAuthorization.title') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    @click.stop=\"exportApplication(row)\"\n                    v-if=\"permissionPrecise.export()\"\n                  >\n                    <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('common.export') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    @click.stop=\"openTriggerDrawer(row)\"\n                    v-if=\"permissionPrecise.trigger_read()\"\n                  >\n                    <AppIcon iconName=\"app-trigger\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.trigger.title') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    @click.stop=\"deleteApplication(row)\"\n                    v-if=\"permissionPrecise.delete()\"\n                  >\n                    <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('common.delete') }}\n                  </el-dropdown-item>\n                </el-dropdown-menu>\n              </template>\n            </el-dropdown>\n          </template>\n        </el-table-column>\n      </app-table>\n    </el-card>\n    <ResourceAuthorizationDrawer\n      :type=\"SourceTypeEnum.APPLICATION\"\n      ref=\"ResourceAuthorizationDrawerRef\"\n    />\n    <ResourceTriggerDrawer\n      ref=\"resourceTriggerDrawerRef\"\n      :source=\"SourceTypeEnum.APPLICATION\"\n    ></ResourceTriggerDrawer>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, computed, watch } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport ApplicationResourceApi from '@/api/system-resource-management/application'\nimport ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'\nimport { t } from '@/locales'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport ResourceTriggerDrawer from '@/views/trigger/ResourceTriggerDrawer.vue'\nimport useStore from '@/stores'\nimport { datetimeFormat } from '@/utils/time'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\nimport { isWorkFlow } from '@/utils/application.ts'\nimport UserApi from '@/api/user/user.ts'\nimport permissionMap from '@/permission'\nimport { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'\nimport { SourceTypeEnum } from '@/enums/common'\n\nconst router = useRouter()\nconst route = useRoute()\nconst { user, application } = useStore()\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['application']['systemManage']\n})\n\nconst managePermission = () => {\n  return (\n    permissionPrecise.value.overview_read() ||\n    permissionPrecise.value.access_read() ||\n    permissionPrecise.value.edit() ||\n    permissionPrecise.value.chat_log_read() ||\n    permissionPrecise.value.chat_user_read()\n  )\n}\n\nconst MoreFilledPermission = () => {\n  return (\n    permissionPrecise.value.export() ||\n    permissionPrecise.value.delete() ||\n    permissionPrecise.value.auth() ||\n    permissionPrecise.value.trigger_read()\n  )\n}\n\nconst resourceTriggerDrawerRef = ref<InstanceType<typeof ResourceTriggerDrawer>>()\nconst openTriggerDrawer = (data: any) => {\n  resourceTriggerDrawerRef.value?.open(data)\n}\n\nconst ResourceAuthorizationDrawerRef = ref()\n\nfunction openAuthorization(item: any) {\n  ResourceAuthorizationDrawerRef.value.open(item.id)\n}\n\nconst apiInputParams = ref([])\n\nfunction toChat(row: any) {\n  row?.work_flow?.nodes\n    ?.filter((v: any) => v.id === 'base-node')\n    .map((v: any) => {\n      apiInputParams.value = v.properties.api_input_field_list\n        ? v.properties.api_input_field_list.map((v: any) => {\n            return {\n              name: v.variable,\n              value: v.default_value,\n            }\n          })\n        : v.properties.input_field_list\n          ? v.properties.input_field_list\n              .filter((v: any) => v.assignment_method === 'api_input')\n              .map((v: any) => {\n                return {\n                  name: v.variable,\n                  value: v.default_value,\n                }\n              })\n          : []\n    })\n  const apiParams = mapToUrlParams(apiInputParams.value)\n    ? '?' + mapToUrlParams(apiInputParams.value)\n    : ''\n  ApplicationResourceApi.getAccessToken(row.id, loading).then((res: any) => {\n    window.open(application.location + res?.data?.access_token + apiParams)\n  })\n}\n\nfunction mapToUrlParams(map: any[]) {\n  const params = new URLSearchParams()\n\n  map.forEach((item: any) => {\n    params.append(encodeURIComponent(item.name), encodeURIComponent(item.value))\n  })\n\n  return params.toString() // 返回 URL 查询字符串\n}\n\nfunction deleteApplication(row: any) {\n  MsgConfirm(\n    `${t('views.application.delete.confirmTitle')}${row.name} ?`,\n    row.resource_count > 0\n      ? t('views.application.delete.resourceCountMessage', row.resource_count)\n      : '',\n    {\n      confirmButtonText: t('common.confirm'),\n      cancelButtonText: t('common.cancel'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      ApplicationResourceApi.delApplication(row.id, loading).then(() => {\n        const index = applicationList.value.findIndex((v) => v.id === row.id)\n        applicationList.value.splice(index, 1)\n        MsgSuccess(t('common.deleteSuccess'))\n      })\n    })\n    .catch(() => {})\n}\n\nconst exportApplication = (application: any) => {\n  ApplicationResourceApi.exportApplication(application.id, application.name, loading).catch((e) => {\n    if (e.response.status !== 403) {\n      e.response.data.text().then((res: string) => {\n        MsgError(`${t('views.application.tip.ExportError')}:${JSON.parse(res).message}`)\n      })\n    }\n  })\n}\n\nconst search_type = ref('name')\nconst search_form = ref<any>({\n  name: '',\n  create_user: '',\n  type: '',\n})\nconst user_options = ref<any[]>([])\nconst type_options = ref<any[]>([\n  {\n    label: t('views.application.senior'),\n    value: 'WORK_FLOW',\n  },\n  {\n    label: t('views.application.simple'),\n    value: 'SIMPLE',\n  },\n])\nconst loading = ref(false)\nconst applicationList = ref<any[]>([])\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst workspaceOptions = ref<any[]>([])\nconst workspaceVisible = ref(false)\nconst workspaceArr = ref<any[]>([])\nconst statusVisible = ref(false)\nconst statusArr = ref<any[]>([])\nconst statusOptions = ref<any[]>([\n  {\n    label: t('common.status.published'),\n    value: true,\n  },\n  {\n    label: t('common.status.unpublished'),\n    value: false,\n  },\n])\n\nconst filterText = ref('')\nconst filterData = ref<any[]>([])\n\nwatch(\n  [() => workspaceOptions.value, () => filterText.value],\n  () => {\n    if (!filterText.value.length) {\n      filterData.value = workspaceOptions.value\n    }\n    filterData.value = workspaceOptions.value.filter((v: any) =>\n      v.label.toLowerCase().includes(filterText.value.toLowerCase()),\n    )\n  },\n  { immediate: true },\n)\n\nfunction filterWorkspaceChange(val: string) {\n  if (val === 'clear') {\n    workspaceArr.value = []\n  }\n  filterText.value = ''\n  getList()\n  workspaceVisible.value = false\n}\n\nfunction filterStatusChange(val: string) {\n  if (val === 'clear') {\n    statusArr.value = []\n  }\n  getList()\n  statusVisible.value = false\n}\n\nasync function getWorkspaceList() {\n  if (user.isEE()) {\n    const res = await loadPermissionApi('workspace').getSystemWorkspaceList(loading)\n    workspaceOptions.value = res.data.map((item: any) => ({\n      label: item.name,\n      value: item.id,\n    }))\n  }\n}\n\nconst search_type_change = () => {\n  search_form.value = { name: '', create_user: '', type: '' }\n}\n\nfunction getList() {\n  const params: any = {}\n  if (search_form.value[search_type.value]) {\n    params[search_type.value] = search_form.value[search_type.value]\n  }\n  if (workspaceArr.value.length > 0) {\n    params['workspace_ids'] = JSON.stringify(workspaceArr.value)\n  }\n  if (statusArr.value.length > 0) {\n    params['status'] = JSON.stringify(statusArr.value)\n  }\n  ApplicationResourceApi.getApplication(paginationConfig, params, loading).then((res: any) => {\n    paginationConfig.total = res.data?.total\n    applicationList.value = res.data?.records\n  })\n}\n\nonMounted(() => {\n  getWorkspaceList()\n  getList()\n\n  UserApi.getAllMemberList('').then((res: any) => {\n    user_options.value = res.data\n  })\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-resource-management/KnowledgeResourceIndex.vue",
    "content": "<template>\n  <div class=\"p-16-24\">\n    <el-breadcrumb separator-icon=\"ArrowRight\">\n      <el-breadcrumb-item>{{ t('views.system.resource_management.label') }}</el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <h5 class=\"ml-4 color-text-primary\">{{ t('views.knowledge.title') }}</h5>\n      </el-breadcrumb-item>\n    </el-breadcrumb>\n    <el-card class=\"mt-16\" style=\"height: calc(var(--app-main-height) + 20px)\">\n      <div class=\"flex-between mb-16\">\n        <div class=\"complex-search\">\n          <el-select\n            class=\"complex-search__left\"\n            v-model=\"search_type\"\n            style=\"width: 120px\"\n            @change=\"search_type_change\"\n          >\n            <el-option :label=\"$t('common.creator')\" value=\"create_user\" />\n            <el-option :label=\"$t('common.name')\" value=\"name\" />\n            <el-option :label=\"$t('common.type')\" value=\"type\" />\n          </el-select>\n          <el-input\n            v-if=\"search_type === 'name'\"\n            v-model=\"search_form.name\"\n            @change=\"getList\"\n            :placeholder=\"$t('common.searchBar.placeholder')\"\n            style=\"width: 220px\"\n            clearable\n          />\n          <el-select\n            v-else-if=\"search_type === 'create_user'\"\n            v-model=\"search_form.create_user\"\n            @change=\"getList\"\n            filterable\n            clearable\n            style=\"width: 220px\"\n          >\n            <el-option v-for=\"u in user_options\" :key=\"u.id\" :value=\"u.id\" :label=\"u.nick_name\" />\n          </el-select>\n          <el-select\n            v-else-if=\"search_type === 'type'\"\n            v-model=\"search_form.type\"\n            @change=\"getList\"\n            clearable\n            filterable\n            style=\"width: 220px\"\n          >\n            <el-option v-for=\"u in type_options\" :key=\"u.id\" :value=\"u.value\" :label=\"u.label\" />\n          </el-select>\n        </div>\n      </div>\n\n      <app-table\n        :data=\"knowledgeList\"\n        :pagination-config=\"paginationConfig\"\n        @sizeChange=\"getList\"\n        @changePage=\"getList\"\n        :maxTableHeight=\"260\"\n      >\n        <!-- <el-table-column type=\"selection\" width=\"55\" /> -->\n        <el-table-column width=\"220\" :label=\"$t('common.name')\" show-overflow-tooltip>\n          <template #default=\"{ row }\">\n            <el-space :size=\"8\">\n              <KnowledgeIcon :type=\"row.type\" :size=\"24\" />\n              <span class=\"ellipsis\" style=\"max-width: 160px\">\n                {{ row.name }}\n              </span>\n            </el-space>\n          </template>\n        </el-table-column>\n\n        <el-table-column prop=\"tool_type\" :label=\"$t('common.type')\" width=\"110\">\n          <template #default=\"{ row }\">\n            <span v-if=\"row.type === 1\">{{\n              $t('views.knowledge.knowledgeType.webKnowledge')\n            }}</span>\n            <span v-else-if=\"row.type === 2\">{{\n              $t('views.knowledge.knowledgeType.larkKnowledge')\n            }}</span>\n            <span v-else-if=\"row.type === 4\">{{\n              $t('views.knowledge.knowledgeType.workflowKnowledge')\n            }}</span>\n            <span v-else>{{ $t('views.knowledge.knowledgeType.generalKnowledge') }}</span>\n          </template>\n        </el-table-column>\n        <el-table-column\n          v-if=\"user.isEE()\"\n          width=\"150\"\n          prop=\"workspace_name\"\n          :label=\"$t('views.workspace.title')\"\n          show-overflow-tooltip\n        >\n          <template #header>\n            <div>\n              <span>{{ $t('views.workspace.title') }}</span>\n              <el-popover\n                :width=\"200\"\n                trigger=\"click\"\n                :visible=\"workspaceVisible\"\n                :persistent=\"false\"\n              >\n                <template #reference>\n                  <el-button\n                    style=\"margin-top: -2px\"\n                    :type=\"workspaceArr && workspaceArr.length > 0 ? 'primary' : ''\"\n                    link\n                    @click=\"workspaceVisible = !workspaceVisible\"\n                  >\n                    <el-icon>\n                      <Filter />\n                    </el-icon>\n                  </el-button>\n                </template>\n                <div class=\"filter\">\n                  <div class=\"form-item mb-16 ml-4\">\n                    <div @click.stop>\n                      <el-input\n                        v-model=\"filterText\"\n                        :placeholder=\"$t('common.search')\"\n                        prefix-icon=\"Search\"\n                        clearable\n                      />\n                      <el-scrollbar height=\"300\" v-if=\"filterData.length\">\n                        <el-checkbox-group\n                          v-model=\"workspaceArr\"\n                          style=\"display: flex; flex-direction: column\"\n                        >\n                          <el-checkbox\n                            v-for=\"item in filterData\"\n                            :key=\"item.value\"\n                            :label=\"item.label\"\n                            :value=\"item.value\"\n                          />\n                        </el-checkbox-group>\n                      </el-scrollbar>\n                      <el-empty v-else :description=\"$t('common.noData')\" />\n                    </div>\n                  </div>\n                </div>\n                <div class=\"text-right\">\n                  <el-button size=\"small\" @click=\"filterWorkspaceChange('clear')\"\n                    >{{ $t('common.clear') }}\n                  </el-button>\n                  <el-button type=\"primary\" @click=\"filterWorkspaceChange\" size=\"small\"\n                    >{{ $t('common.confirm') }}\n                  </el-button>\n                </div>\n              </el-popover>\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"nick_name\" :label=\"$t('common.creator')\" show-overflow-tooltip />\n        <el-table-column :label=\"$t('views.document.table.updateTime')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.update_time) }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.createTime')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.create_time) }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"120\" fixed=\"right\">\n          <template #default=\"{ row }\">\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('views.system.resource_management.management')\"\n              placement=\"top\"\n            >\n              <span class=\"mr-8\" v-if=\"ManagePermission()\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  :title=\"$t('views.system.resource_management.management')\"\n                  @click=\"\n                    router.push({\n                      path: `/knowledge/${row.id}/resource-management/${row.type}/document`,\n                    })\n                  \"\n                >\n                  <AppIcon iconName=\"app-admin-operation\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('views.knowledge.setting.vectorization')\"\n              placement=\"top\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  :title=\"$t('views.knowledge.setting.vectorization')\"\n                  @click.stop=\"reEmbeddingKnowledge(row)\"\n                  v-if=\"permissionPrecise.vector()\"\n                >\n                  <AppIcon iconName=\"app-vectorization\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-dropdown trigger=\"click\" v-if=\"MoreFilledPermission()\">\n              <el-button text @click.stop type=\"primary\">\n                <AppIcon iconName=\"app-more\"></AppIcon>\n              </el-button>\n              <template #dropdown>\n                <el-dropdown-menu>\n                  <el-dropdown-item\n                    @click.stop=\"syncKnowledge(row)\"\n                    v-if=\"row.type === 1 && permissionPrecise.sync()\"\n                  >\n                    <AppIcon iconName=\"app-sync\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.knowledge.setting.sync') }}\n                  </el-dropdown-item>\n\n                  <el-dropdown-item\n                    @click.stop=\"openGenerateDialog(row)\"\n                    v-if=\"permissionPrecise.generate()\"\n                  >\n                    <AppIcon iconName=\"app-generate-question\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.document.generateQuestion.title') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    @click=\"\n                      router.push({\n                        path: `/knowledge/${row.id}/resource-management/${row.type}/setting`,\n                      })\n                    \"\n                    v-if=\"permissionPrecise.edit()\"\n                  >\n                    <AppIcon iconName=\"app-setting\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('common.setting') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    @click.stop=\"openAuthorization(row)\"\n                    v-if=\"permissionPrecise.auth()\"\n                  >\n                    <AppIcon\n                      iconName=\"app-resource-authorization\"\n                      class=\"color-secondary\"\n                    ></AppIcon>\n                    {{ $t('views.system.resourceAuthorization.title') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    @click.stop=\"exportKnowledge(row)\"\n                    v-if=\"permissionPrecise.export()\"\n                  >\n                    <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.document.setting.export') }} Excel\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    @click.stop=\"exportZipKnowledge(row)\"\n                    v-if=\"permissionPrecise.export()\"\n                  >\n                    <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.document.setting.export') }} ZIP\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    text\n                    @click.stop=\"openResourceMappingDrawer(row)\"\n                    v-if=\"permissionPrecise.relate_map()\"\n                  >\n                    <AppIcon iconName=\"app-resource-mapping\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.system.resourceMapping.title') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    type=\"danger\"\n                    @click.stop=\"deleteKnowledge(row)\"\n                    v-if=\"permissionPrecise.delete()\"\n                  >\n                    <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('common.delete') }}\n                  </el-dropdown-item>\n                </el-dropdown-menu>\n              </template>\n            </el-dropdown>\n          </template>\n        </el-table-column>\n      </app-table>\n    </el-card>\n    <SyncWebDialog ref=\"SyncWebDialogRef\" />\n    <GenerateRelatedDialog ref=\"GenerateRelatedDialogRef\" apiType=\"systemManage\" />\n    <ResourceAuthorizationDrawer\n      :type=\"SourceTypeEnum.KNOWLEDGE\"\n      ref=\"ResourceAuthorizationDrawerRef\"\n    />\n    <ResourceMappingDrawer ref=\"resourceMappingDrawerRef\"></ResourceMappingDrawer>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, computed, watch } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport KnowledgeResourceApi from '@/api/system-resource-management/knowledge'\nimport UserApi from '@/api/user/user'\nimport SyncWebDialog from '@/views/knowledge/component/SyncWebDialog.vue'\nimport GenerateRelatedDialog from '@/components/generate-related-dialog/index.vue'\nimport ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'\nimport { datetimeFormat } from '@/utils/time'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\nimport permissionMap from '@/permission'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { SourceTypeEnum } from '@/enums/common'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nimport { hasPermission } from '@/utils/permission'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport ResourceMappingDrawer from '@/components/resource_mapping/index.vue'\n\nconst router = useRouter()\nconst { user } = useStore()\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge']['systemManage']\n})\n\nconst ManagePermission = () => {\n  return (\n    permissionPrecise.value.doc_read() ||\n    permissionPrecise.value.problem_read() ||\n    permissionPrecise.value.edit() ||\n    permissionPrecise.value.knowledge_chat_user_read() ||\n    permissionPrecise.value.hit_test() ||\n    hasPermission([RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_WORKFLOW_READ], 'OR')\n  )\n}\n\nconst MoreFilledPermission = () => {\n  return (['sync', 'generate', 'edit', 'export', 'delete', 'auth', 'relate_map'] as const).some(\n    (key) => permissionPrecise.value[key](),\n  )\n}\n\nconst search_type = ref('name')\nconst search_form = ref<any>({\n  name: '',\n  create_user: '',\n  type: '',\n})\nconst user_options = ref<any[]>([])\nconst type_options = ref<any[]>([\n  {\n    label: t('views.knowledge.knowledgeType.generalKnowledge'),\n    value: '0',\n  },\n  {\n    label: t('views.knowledge.knowledgeType.webKnowledge'),\n    value: '1',\n  },\n  {\n    label: t('views.knowledge.knowledgeType.larkKnowledge'),\n    value: '2',\n  },\n  {\n    label: t('views.knowledge.knowledgeType.workflowKnowledge'),\n    value: '4',\n  },\n])\nconst loading = ref(false)\nconst knowledgeList = ref<any[]>([])\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst ResourceAuthorizationDrawerRef = ref()\n\nfunction openAuthorization(item: any) {\n  ResourceAuthorizationDrawerRef.value.open(item.id)\n}\n\nconst exportKnowledge = (item: any) => {\n  KnowledgeResourceApi.exportKnowledge(item.name, item.id, loading).then(() => {\n    MsgSuccess(t('common.exportSuccess'))\n  })\n}\nconst exportZipKnowledge = (item: any) => {\n  KnowledgeResourceApi.exportZipKnowledge(item.name, item.id, loading).then(() => {\n    MsgSuccess(t('common.exportSuccess'))\n  })\n}\n\nfunction deleteKnowledge(row: any) {\n  MsgConfirm(\n    `${t('views.knowledge.delete.confirmTitle')}${row.name} ?`,\n    row.resource_count > 0\n      ? t('views.knowledge.delete.resourceCountMessage', row.resource_count)\n      : '',\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      KnowledgeResourceApi.delKnowledge(row.id, loading).then(() => {\n        getList()\n        MsgSuccess(t('common.deleteSuccess'))\n      })\n    })\n    .catch(() => {})\n}\n\nconst GenerateRelatedDialogRef = ref<InstanceType<typeof GenerateRelatedDialog>>()\n\nfunction openGenerateDialog(row: any) {\n  if (GenerateRelatedDialogRef.value) {\n    GenerateRelatedDialogRef.value.open([], 'knowledge', row)\n  }\n}\n\nconst SyncWebDialogRef = ref()\n\nfunction syncKnowledge(row: any) {\n  SyncWebDialogRef.value.open(row.id)\n}\n\nfunction reEmbeddingKnowledge(row: any) {\n  KnowledgeResourceApi.putReEmbeddingKnowledge(row.id).then(() => {\n    MsgSuccess(t('common.submitSuccess'))\n  })\n}\n\nconst workspaceOptions = ref<any[]>([])\nconst workspaceVisible = ref(false)\nconst workspaceArr = ref<any[]>([])\n\nconst filterText = ref('')\nconst filterData = ref<any[]>([])\n\nwatch(\n  [() => workspaceOptions.value, () => filterText.value],\n  () => {\n    if (!filterText.value.length) {\n      filterData.value = workspaceOptions.value\n    }\n    filterData.value = workspaceOptions.value.filter((v: any) =>\n      v.label.toLowerCase().includes(filterText.value.toLowerCase()),\n    )\n  },\n  { immediate: true },\n)\n\nfunction filterWorkspaceChange(val: string) {\n  if (val === 'clear') {\n    workspaceArr.value = []\n  }\n  filterText.value = ''\n  getList()\n  workspaceVisible.value = false\n}\n\nasync function getWorkspaceList() {\n  if (user.isEE()) {\n    const res = await loadPermissionApi('workspace').getSystemWorkspaceList(loading)\n    workspaceOptions.value = res.data.map((item: any) => ({\n      label: item.name,\n      value: item.id,\n    }))\n  }\n}\n\nconst search_type_change = () => {\n  search_form.value = { name: '', create_user: '' }\n}\n\nfunction getList() {\n  const params: any = {}\n  if (search_form.value[search_type.value]) {\n    params[search_type.value] = search_form.value[search_type.value]\n  }\n  if (workspaceArr.value.length > 0) {\n    params.workspace_ids = JSON.stringify(workspaceArr.value)\n  }\n  KnowledgeResourceApi.getKnowledgeListPage(paginationConfig, params, loading).then((res: any) => {\n    paginationConfig.total = res.data?.total\n    knowledgeList.value = res.data?.records\n  })\n}\n\nconst resourceMappingDrawerRef = ref<InstanceType<typeof ResourceMappingDrawer>>()\nconst openResourceMappingDrawer = (knowledge: any) => {\n  resourceMappingDrawerRef.value?.open('KNOWLEDGE', knowledge)\n}\nonMounted(() => {\n  getWorkspaceList()\n  getList()\n\n  UserApi.getAllMemberList('').then((res: any) => {\n    user_options.value = res.data\n  })\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-resource-management/ModelResourceIndex.vue",
    "content": "<template>\n  <div class=\"p-16-24\">\n    <el-breadcrumb separator-icon=\"ArrowRight\">\n      <el-breadcrumb-item>{{ t('views.system.resource_management.label') }}</el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <h5 class=\"ml-4 color-text-primary\">{{ t('views.model.title') }}</h5>\n      </el-breadcrumb-item>\n    </el-breadcrumb>\n    <el-card class=\"mt-16\" style=\"height: calc(var(--app-main-height) + 20px)\">\n      <div class=\"flex-between mb-16\">\n        <div class=\"complex-search\">\n          <el-select\n            class=\"complex-search__left\"\n            v-model=\"search_type\"\n            style=\"width: 120px\"\n            @change=\"search_type_change\"\n          >\n            <el-option :label=\"$t('common.creator')\" value=\"create_user\" />\n            <el-option :label=\"$t('views.model.modelForm.model_type.label')\" value=\"model_type\" />\n            <el-option :label=\"$t('views.model.modelForm.modeName.label')\" value=\"name\" />\n          </el-select>\n          <el-input\n            v-if=\"search_type === 'name'\"\n            v-model=\"model_search_form.name\"\n            @change=\"getList\"\n            :placeholder=\"$t('common.searchBar.placeholder')\"\n            style=\"width: 220px\"\n            clearable\n          />\n          <el-select\n            v-else-if=\"search_type === 'create_user'\"\n            v-model=\"model_search_form.create_user\"\n            @change=\"getList\"\n            filterable\n            clearable\n            style=\"width: 220px\"\n          >\n            <el-option v-for=\"u in user_options\" :key=\"u.id\" :value=\"u.id\" :label=\"u.nick_name\" />\n          </el-select>\n          <el-select\n            v-else-if=\"search_type === 'model_type'\"\n            v-model=\"model_search_form.model_type\"\n            clearable\n            @change=\"getList\"\n            style=\"width: 220px\"\n          >\n            <template v-for=\"item in modelTypeList\" :key=\"item.value\">\n              <el-option :label=\"item.text\" :value=\"item.value\" />\n            </template>\n          </el-select>\n        </div>\n      </div>\n\n      <app-table\n        :data=\"modelList\"\n        :pagination-config=\"paginationConfig\"\n        @sizeChange=\"getList\"\n        @changePage=\"getList\"\n        :maxTableHeight=\"260\"\n      >\n        <!-- <el-table-column type=\"selection\" width=\"55\" /> -->\n        <el-table-column width=\"220\" :label=\"$t('common.name')\" show-overflow-tooltip>\n          <template #default=\"{ row }\">\n            <el-space :size=\"8\">\n              <span\n                style=\"width: 24px; height: 24px; display: inline-block\"\n                :innerHTML=\"getRowProvider(row)?.icon\"\n              >\n              </span>\n              <span class=\"ellipsis\" style=\"max-width: 160px\"> {{ row.name }}</span>\n            </el-space>\n          </template>\n        </el-table-column>\n        <el-table-column\n          prop=\"provider\"\n          :label=\"$t('views.model.provider')\"\n          show-overflow-tooltip\n          width=\"160\"\n        >\n          <template #default=\"{ row }\">\n            <el-space :size=\"8\">\n              <span\n                style=\"width: 24px; height: 24px; display: inline-block\"\n                :innerHTML=\"getRowProvider(row)?.icon\"\n              >\n              </span>\n              <span> {{ getRowProvider(row)?.name }}</span>\n            </el-space>\n          </template>\n        </el-table-column>\n        <el-table-column width=\"120\" :label=\"$t('views.model.modelForm.model_type.label')\">\n          <template #default=\"{ row }\">\n            {{ $t(modelType[row.model_type as keyof typeof modelType]) }}\n          </template>\n        </el-table-column>\n        <el-table-column\n          width=\"220\"\n          :label=\"$t('views.model.modelForm.base_model.label')\"\n          show-overflow-tooltip\n        >\n          <template #default=\"{ row }\">\n            {{ row.model_name }}\n          </template>\n        </el-table-column>\n\n        <el-table-column\n          v-if=\"user.isEE()\"\n          width=\"150\"\n          prop=\"workspace_name\"\n          :label=\"$t('views.workspace.title')\"\n          show-overflow-tooltip\n        >\n          <template #header>\n            <div>\n              <span>{{ $t('views.workspace.title') }}</span>\n              <el-popover\n                :width=\"200\"\n                trigger=\"click\"\n                :visible=\"workspaceVisible\"\n                :persistent=\"false\"\n              >\n                <template #reference>\n                  <el-button\n                    style=\"margin-top: -2px\"\n                    :type=\"workspaceArr && workspaceArr.length > 0 ? 'primary' : ''\"\n                    link\n                    @click=\"workspaceVisible = !workspaceVisible\"\n                  >\n                    <el-icon>\n                      <Filter />\n                    </el-icon>\n                  </el-button>\n                </template>\n                <div class=\"filter\">\n                  <div class=\"form-item mb-16 ml-4\">\n                    <div @click.stop>\n                      <el-input\n                        v-model=\"filterText\"\n                        :placeholder=\"$t('common.search')\"\n                        prefix-icon=\"Search\"\n                        clearable\n                      />\n                      <el-scrollbar height=\"300\" v-if=\"filterData.length\">\n                        <el-checkbox-group\n                          v-model=\"workspaceArr\"\n                          style=\"display: flex; flex-direction: column\"\n                        >\n                          <el-checkbox\n                            v-for=\"item in filterData\"\n                            :key=\"item.value\"\n                            :label=\"item.label\"\n                            :value=\"item.value\"\n                          />\n                        </el-checkbox-group>\n                      </el-scrollbar>\n                      <el-empty v-else :description=\"$t('common.noData')\" />\n                    </div>\n                  </div>\n                </div>\n                <div class=\"text-right\">\n                  <el-button size=\"small\" @click=\"filterWorkspaceChange('clear')\"\n                    >{{ $t('common.clear') }}\n                  </el-button>\n                  <el-button type=\"primary\" @click=\"filterWorkspaceChange\" size=\"small\"\n                    >{{ $t('common.confirm') }}\n                  </el-button>\n                </div>\n              </el-popover>\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"nick_name\" :label=\"$t('common.creator')\" show-overflow-tooltip />\n        <el-table-column :label=\"$t('views.document.table.updateTime')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.update_time) }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.createTime')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.create_time) }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"120\" fixed=\"right\">\n          <template #default=\"{ row }\">\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('common.modify')\"\n              placement=\"top\"\n              v-if=\"permissionPrecise.modify()\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  :title=\"$t('common.modify')\"\n                  @click.stop=\"openEditModel(row)\"\n                >\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('views.system.resourceAuthorization.title')\"\n              placement=\"top\"\n              v-if=\"permissionPrecise.auth()\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  :title=\"$t('views.system.resourceAuthorization.title')\"\n                  @click.stop=\"openAuthorization(row)\"\n                >\n                  <AppIcon iconName=\"app-resource-authorization\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n\n            <el-dropdown trigger=\"click\" v-if=\"MoreFilledPermission()\">\n              <el-button text @click.stop type=\"primary\">\n                <AppIcon iconName=\"app-more\"></AppIcon>\n              </el-button>\n              <template #dropdown>\n                <el-dropdown-menu>\n                  <el-dropdown-item\n                    @click.stop=\"openParamSetting(row)\"\n                    v-if=\"\n                      ['TTS', 'LLM', 'IMAGE', 'TTI', 'STT', 'EMBEDDING'].includes(row.model_type) &&\n                      permissionPrecise.paramSetting()\n                    \"\n                  >\n                    <AppIcon iconName=\"app-setting\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.model.modelForm.title.paramSetting') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    text\n                    @click.stop=\"openResourceMappingDrawer(row)\"\n                    v-if=\"permissionPrecise.relate_map()\"\n                  >\n                    <AppIcon iconName=\"app-resource-mapping\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.system.resourceMapping.title') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    @click.stop=\"deleteModel(row)\"\n                    v-if=\"permissionPrecise.delete()\"\n                  >\n                    <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('common.delete') }}\n                  </el-dropdown-item>\n                </el-dropdown-menu>\n              </template>\n            </el-dropdown>\n          </template>\n        </el-table-column>\n      </app-table>\n    </el-card>\n    <EditModel ref=\"editModelRef\" @submit=\"getList\"></EditModel>\n    <ParamSettingDialog ref=\"paramSettingRef\" />\n    <ResourceAuthorizationDrawer\n      :type=\"SourceTypeEnum.MODEL\"\n      ref=\"ResourceAuthorizationDrawerRef\"\n    />\n    <ResourceMappingDrawer ref=\"resourceMappingDrawerRef\"></ResourceMappingDrawer>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onBeforeMount, onMounted, ref, reactive, watch, computed } from 'vue'\nimport type { Provider, Model } from '@/api/type/model'\nimport EditModel from '@/views/model/component/EditModel.vue'\nimport ParamSettingDialog from '@/views/model/component/ParamSettingDialog.vue'\nimport ModelResourceApi from '@/api/system-resource-management/model'\nimport ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'\nimport { SourceTypeEnum } from '@/enums/common'\nimport { modelTypeList } from '@/views/model/component/data'\nimport { modelType } from '@/enums/model'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nimport { datetimeFormat } from '@/utils/time'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\nimport UserApi from '@/api/user/user.ts'\nimport permissionMap from '@/permission'\nimport { MsgConfirm, MsgSuccess } from '@/utils/message'\nimport ResourceMappingDrawer from '@/components/resource_mapping/index.vue'\n\nconst { user, model } = useStore()\n\nconst search_type = ref('name')\nconst model_search_form = ref<{\n  name: string\n  create_user: string\n  model_type: string\n}>({\n  name: '',\n  create_user: '',\n  model_type: '',\n})\n\nconst loading = ref(false)\nconst modelList = ref<Array<Model>>([])\nconst user_options = ref<any[]>([])\nconst provider_list = ref<Array<Provider>>([])\n\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst MoreFilledPermission = () => {\n  return (\n    permissionPrecise.value.delete() ||\n    permissionPrecise.value.modify() ||\n    permissionPrecise.value.relate_map()\n  )\n}\n\nconst ResourceAuthorizationDrawerRef = ref()\n\nfunction openAuthorization(item: any) {\n  ResourceAuthorizationDrawerRef.value.open(item.id)\n}\n\nconst deleteModel = (row: any) => {\n  MsgConfirm(\n    `${t('views.model.delete.confirmTitle')}${row.name} ?`,\n    row.resource_count > 0\n      ? t('views.model.delete.resourceCountMessage', { count: row.resource_count })\n      : '',\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      ModelResourceApi.deleteModel(row.id).then(() => {\n        getList()\n        MsgSuccess(t('common.deleteSuccess'))\n      })\n    })\n    .catch(() => {})\n}\n\nconst paramSettingRef = ref<InstanceType<typeof ParamSettingDialog>>()\nconst openParamSetting = (row: any) => {\n  paramSettingRef.value?.open(row)\n}\n\nconst editModelRef = ref<InstanceType<typeof EditModel>>()\nconst openEditModel = (row: any) => {\n  const provider = provider_list.value.find((p) => p.provider === row.provider)\n  if (provider) {\n    editModelRef.value?.open(provider, row)\n  }\n}\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['model']['systemManage']\n})\n\nconst workspaceOptions = ref<any[]>([])\nconst workspaceVisible = ref(false)\nconst workspaceArr = ref<any[]>([])\n\nconst getRowProvider = computed(() => {\n  return (row: any) => {\n    return provider_list.value.find((p) => p.provider === row.provider)\n  }\n})\n\nconst filterText = ref('')\nconst filterData = ref<any[]>([])\n\nwatch(\n  [() => workspaceOptions.value, () => filterText.value],\n  () => {\n    if (!filterText.value.length) {\n      filterData.value = workspaceOptions.value\n    }\n    filterData.value = workspaceOptions.value.filter((v: any) =>\n      v.label.toLowerCase().includes(filterText.value.toLowerCase()),\n    )\n  },\n  { immediate: true },\n)\n\nfunction filterWorkspaceChange(val: string) {\n  if (val === 'clear') {\n    workspaceArr.value = []\n  }\n  getList()\n  workspaceVisible.value = false\n}\n\nasync function getWorkspaceList() {\n  if (user.isEE()) {\n    const res = await loadPermissionApi('workspace').getSystemWorkspaceList(loading)\n    workspaceOptions.value = res.data.map((item: any) => ({\n      label: item.name,\n      value: item.id,\n    }))\n  }\n}\n\nconst search_type_change = () => {\n  model_search_form.value = { name: '', create_user: '', model_type: '' }\n}\n\nfunction getRequestParams() {\n  const obj: any = {\n    name: model_search_form.value.name,\n    create_user: model_search_form.value.create_user,\n    model_type: model_search_form.value.model_type,\n  }\n  if (workspaceArr.value.length > 0) {\n    obj['workspace_ids'] = JSON.stringify(workspaceArr.value)\n  }\n  return obj\n}\n\nfunction getList() {\n  ModelResourceApi.getModelListPage(paginationConfig, getRequestParams(), loading).then(\n    (res: any) => {\n      paginationConfig.total = res.data?.total\n      modelList.value = res.data?.records\n    },\n  )\n}\n\nfunction getProvider() {\n  model.asyncGetProvider(loading).then((res: any) => {\n    provider_list.value = res?.data\n    getList()\n  })\n}\n\nconst resourceMappingDrawerRef = ref<InstanceType<typeof ResourceMappingDrawer>>()\nconst openResourceMappingDrawer = (model: any) => {\n  resourceMappingDrawerRef.value?.open('MODEL', model)\n}\n\nonMounted(() => {\n  getWorkspaceList()\n  getProvider()\n\n  UserApi.getAllMemberList('').then((res: any) => {\n    user_options.value = res.data\n  })\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-resource-management/ToolResourceIndex.vue",
    "content": "<template>\n  <div class=\"resource-manage_tool p-16-24\">\n    <el-breadcrumb separator-icon=\"ArrowRight\">\n      <el-breadcrumb-item>{{ t('views.system.resource_management.label') }}</el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <h5 class=\"ml-4 color-text-primary\">{{ t('views.tool.title') }}</h5>\n      </el-breadcrumb-item>\n    </el-breadcrumb>\n    <el-card class=\"mt-16\" style=\"height: calc(var(--app-main-height) + 20px)\">\n      <div class=\"flex-between mb-16\">\n        <div class=\"complex-search\">\n          <el-select\n            class=\"complex-search__left\"\n            v-model=\"search_type\"\n            style=\"width: 120px\"\n            @change=\"search_type_change\"\n          >\n            <el-option :label=\"$t('common.creator')\" value=\"create_user\" />\n            <el-option :label=\"$t('common.name')\" value=\"name\" />\n            <el-option :label=\"$t('common.type')\" value=\"tool_type\" />\n            <el-option :label=\"$t('views.tool.form.source.label')\" value=\"source\" />\n          </el-select>\n          <el-input\n            v-if=\"search_type === 'name'\"\n            v-model=\"search_form.name\"\n            @change=\"getList\"\n            :placeholder=\"$t('common.searchBar.placeholder')\"\n            style=\"width: 220px\"\n            clearable\n          />\n          <el-select\n            v-else-if=\"search_type === 'create_user'\"\n            v-model=\"search_form.create_user\"\n            @change=\"getList\"\n            filterable\n            clearable\n            style=\"width: 220px\"\n          >\n            <el-option v-for=\"u in user_options\" :key=\"u.id\" :value=\"u.id\" :label=\"u.nick_name\" />\n          </el-select>\n          <el-select\n            v-else-if=\"search_type === 'tool_type'\"\n            v-model=\"search_form.tool_type\"\n            @change=\"getList\"\n            clearable\n            filterable\n            style=\"width: 220px\"\n          >\n            <el-option v-for=\"u in type_options\" :key=\"u.id\" :value=\"u.value\" :label=\"u.label\" />\n          </el-select>\n          <el-select\n            v-else-if=\"search_type === 'source'\"\n            v-model=\"search_form.source\"\n            @change=\"getList\"\n            clearable\n            filterable\n            style=\"width: 220px\"\n          >\n            <el-option v-for=\"u in source_options\" :key=\"u.id\" :value=\"u.value\" :label=\"u.label\" />\n          </el-select>\n        </div>\n      </div>\n\n      <app-table\n        :data=\"toolList\"\n        :pagination-config=\"paginationConfig\"\n        @sizeChange=\"getList\"\n        @changePage=\"getList\"\n        :maxTableHeight=\"260\"\n      >\n        <!-- <el-table-column type=\"selection\" width=\"55\" /> -->\n        <el-table-column width=\"220\" :label=\"$t('common.name')\" show-overflow-tooltip>\n          <template #default=\"{ row }\">\n            <el-space :size=\"8\">\n              <el-icon size=\"24\">\n                <el-avatar v-if=\"row?.icon\" shape=\"square\" :size=\"24\" style=\"background: none\">\n                  <img :src=\"resetUrl(row?.icon)\" alt=\"\" />\n                </el-avatar>\n\n                <ToolIcon v-else :size=\"24\" :type=\"row?.tool_type\" />\n              </el-icon>\n              <span class=\"ellipsis\" style=\"max-width: 160px\">\n                {{ row.name }}\n              </span>\n            </el-space>\n          </template>\n        </el-table-column>\n\n        <el-table-column prop=\"tool_type\" :label=\"$t('common.type')\">\n          <template #default=\"scope\">\n            <span v-if=\"scope.row.tool_type === 'MCP'\"> MCP </span>\n            <span v-else-if=\"scope.row.tool_type === 'DATA_SOURCE'\">\n              {{ $t('views.tool.dataSource.title') }}\n            </span>\n            <span v-else-if=\"scope.row.tool_type === 'SKILL'\">\n              Skills\n            </span>\n            <span v-else> {{ $t('views.tool.title') }} </span>\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"source\" :label=\"$t('views.tool.form.source.label')\">\n          <template #default=\"scope\">\n            <span v-if=\"scope.row.template_id\">{{ $t('views.tool.toolStore.title') }}</span>\n            <span v-else> {{ $t(ToolType['CUSTOM']) }} </span>\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.status.label')\" width=\"120\">\n          <template #default=\"{ row }\">\n            <div v-if=\"row.is_active\" class=\"flex align-center\">\n              <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n                <SuccessFilled />\n              </el-icon>\n              <span class=\"color-text-primary\">\n                {{ $t('common.status.enabled') }}\n              </span>\n            </div>\n            <div v-else class=\"flex align-center\">\n              <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n              <span class=\"color-text-primary\">\n                {{ $t('common.status.disabled') }}\n              </span>\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column\n          v-if=\"user.isEE()\"\n          width=\"150\"\n          prop=\"workspace_name\"\n          :label=\"$t('views.workspace.title')\"\n          show-overflow-tooltip\n        >\n          <template #header>\n            <div>\n              <span>{{ $t('views.workspace.title') }}</span>\n              <el-popover\n                :width=\"200\"\n                trigger=\"click\"\n                :visible=\"workspaceVisible\"\n                :persistent=\"false\"\n              >\n                <template #reference>\n                  <el-button\n                    style=\"margin-top: -2px\"\n                    :type=\"workspaceArr && workspaceArr.length > 0 ? 'primary' : ''\"\n                    link\n                    @click=\"workspaceVisible = !workspaceVisible\"\n                  >\n                    <el-icon>\n                      <Filter />\n                    </el-icon>\n                  </el-button>\n                </template>\n                <div class=\"filter\">\n                  <div class=\"form-item mb-16 ml-4\">\n                    <div @click.stop>\n                      <el-input\n                        v-model=\"filterText\"\n                        :placeholder=\"$t('common.search')\"\n                        prefix-icon=\"Search\"\n                        clearable\n                      />\n                      <el-scrollbar height=\"300\" v-if=\"filterData.length\">\n                        <el-checkbox-group\n                          v-model=\"workspaceArr\"\n                          style=\"display: flex; flex-direction: column\"\n                        >\n                          <el-checkbox\n                            v-for=\"item in filterData\"\n                            :key=\"item.value\"\n                            :label=\"item.label\"\n                            :value=\"item.value\"\n                          />\n                        </el-checkbox-group>\n                      </el-scrollbar>\n                      <el-empty v-else :description=\"$t('common.noData')\" />\n                    </div>\n                  </div>\n                </div>\n                <div class=\"text-right\">\n                  <el-button size=\"small\" @click=\"filterWorkspaceChange('clear')\"\n                    >{{ $t('common.clear') }}\n                  </el-button>\n                  <el-button type=\"primary\" @click=\"filterWorkspaceChange\" size=\"small\"\n                    >{{ $t('common.confirm') }}\n                  </el-button>\n                </div>\n              </el-popover>\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"nick_name\" :label=\"$t('common.creator')\" show-overflow-tooltip />\n        <el-table-column :label=\"$t('views.document.table.updateTime')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.update_time) }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.createTime')\" width=\"180\">\n          <template #default=\"{ row }\">\n            {{ datetimeFormat(row.create_time) }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"160\" fixed=\"right\">\n          <template #default=\"{ row }\">\n            <span @click.stop>\n              <el-switch\n                v-model=\"row.is_active\"\n                :before-change=\"() => changeState(row)\"\n                size=\"small\"\n                class=\"mr-4\"\n                v-if=\"permissionPrecise.switch()\"\n              />\n            </span>\n            <el-divider direction=\"vertical\" />\n\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('common.edit')\"\n              placement=\"top\"\n              v-if=\"row.template_id && permissionPrecise.edit()\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click.stop=\"addInternalTool(row, true)\"\n                  :title=\"$t('common.edit')\"\n                >\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('common.edit')\"\n              placement=\"top\"\n              v-if=\"!row.template_id && row.tool_type === 'CUSTOM' && permissionPrecise.edit()\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click.stop=\"openCreateDialog(row)\"\n                  :title=\"$t('common.edit')\"\n                >\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('common.edit')\"\n              placement=\"top\"\n              v-if=\"!row.template_id && row.tool_type === 'MCP' && permissionPrecise.edit()\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click.stop=\"openCreateMcpDialog(row)\"\n                  :title=\"$t('common.edit')\"\n                >\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('common.edit')\"\n              placement=\"top\"\n              v-if=\"!row.template_id && row.tool_type === 'DATA_SOURCE' && permissionPrecise.edit()\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click.stop=\"openCreateDataSourceDialog(row)\"\n                  :title=\"$t('common.edit')\"\n                >\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('common.edit')\"\n              placement=\"top\"\n              v-if=\"!row.template_id && row.tool_type === 'SKILL' && permissionPrecise.edit()\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click.stop=\"openCreateSkillToolDialog(row)\"\n                  :title=\"$t('common.edit')\"\n                >\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"$t('common.copy')\"\n              placement=\"top\"\n              v-if=\"!row.template_id && permissionPrecise.copy()\"\n            >\n              <span class=\"mr-8\">\n                <el-button\n                  type=\"primary\"\n                  text\n                  @click.stop=\"copyTool(row)\"\n                  :title=\"$t('common.copy')\"\n                >\n                  <AppIcon iconName=\"app-copy\"></AppIcon>\n                </el-button>\n              </span>\n            </el-tooltip>\n            <el-dropdown trigger=\"click\" v-if=\"MoreFilledPermission(row)\">\n              <el-button text @click.stop type=\"primary\">\n                <AppIcon iconName=\"app-more\"></AppIcon>\n              </el-button>\n              <template #dropdown>\n                <el-dropdown-menu>\n                  <el-dropdown-item\n                    v-if=\"row.init_field_list?.length > 0 && permissionPrecise.edit()\"\n                    @click.stop=\"configInitParams(row)\"\n                  >\n                    <AppIcon iconName=\"app-operation\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('common.param.initParam') }}\n                  </el-dropdown-item>\n\n                  <el-dropdown-item\n                    @click.stop=\"openAuthorization(row)\"\n                    v-if=\"permissionPrecise.auth()\"\n                  >\n                    <AppIcon\n                      iconName=\"app-resource-authorization\"\n                      class=\"color-secondary\"\n                    ></AppIcon>\n                    {{ $t('views.system.resourceAuthorization.title') }}\n                  </el-dropdown-item>\n\n                  <el-dropdown-item\n                    v-if=\"!row.template_id && permissionPrecise.export()\"\n                    @click.stop=\"exportTool(row)\"\n                  >\n                    <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('common.export') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    v-if=\"row.tool_type === 'MCP' && permissionPrecise.edit()\"\n                    @click.stop=\"showMcpConfig(row)\"\n                  >\n                    <AppIcon iconName=\"app-operate-log\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.tool.mcp.mcpConfig') }}\n                  </el-dropdown-item>\n\n                  <el-dropdown-item\n                    @click.stop=\"openTriggerDrawer(row)\"\n                    v-if=\"row.tool_type === 'CUSTOM' && permissionPrecise.trigger_read()\"\n                  >\n                    <AppIcon iconName=\"app-trigger\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.trigger.title') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    text\n                    @click.stop=\"openResourceMappingDrawer(row)\"\n                    v-if=\"permissionPrecise.relate_map()\"\n                  >\n                    <AppIcon iconName=\"app-resource-mapping\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('views.system.resourceMapping.title') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    text\n                    @click.stop=\"openToolRecordDrawer(row)\"\n                    v-if=\"row.tool_type === 'CUSTOM' && permissionPrecise.record()\"\n                  >\n                    <AppIcon iconName=\"app-schedule-report\" class=\"color-secondary\" />\n                    {{ $t('common.ExecutionRecord.subTitle') }}\n                  </el-dropdown-item>\n                  <el-dropdown-item\n                    v-if=\"permissionPrecise.delete()\"\n                    divided\n                    @click.stop=\"deleteTool(row)\"\n                  >\n                    <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                    {{ $t('common.delete') }}\n                  </el-dropdown-item>\n                </el-dropdown-menu>\n              </template>\n            </el-dropdown>\n          </template>\n        </el-table-column>\n      </app-table>\n    </el-card>\n\n    <InitParamDrawer ref=\"InitParamDrawerRef\" @refresh=\"refresh\" />\n    <ToolFormDrawer ref=\"ToolFormDrawerRef\" @refresh=\"refresh\" :title=\"ToolDrawertitle\" />\n    <SkillToolFormDrawer ref=\"SkillToolFormDrawerRef\" @refresh=\"refresh\" :title=\"SkillToolDrawertitle\" />\n    <McpToolFormDrawer ref=\"McpToolFormDrawerRef\" @refresh=\"refresh\" :title=\"McpToolDrawertitle\" />\n    <DataSourceToolFormDrawer\n      ref=\"DataSourceToolFormDrawerRef\"\n      @refresh=\"refresh\"\n      :title=\"DataSourceToolDrawertitle\"\n    />\n    <AddInternalToolDialog ref=\"AddInternalToolDialogRef\" @refresh=\"confirmAddInternalTool\" />\n    <McpToolConfigDialog ref=\"McpToolConfigDialogRef\" @refresh=\"refresh\" />\n    <ResourceAuthorizationDrawer :type=\"SourceTypeEnum.TOOL\" ref=\"ResourceAuthorizationDrawerRef\" />\n    <ResourceMappingDrawer ref=\"resourceMappingDrawerRef\"></ResourceMappingDrawer>\n    <ToolRecordDrawer ref=\"toolRecordDrawerRef\" />\n    <ResourceTriggerDrawer\n      ref=\"resourceTriggerDrawerRef\"\n      :source=\"SourceTypeEnum.TOOL\"\n    ></ResourceTriggerDrawer>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, computed, watch } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport InitParamDrawer from '@/views/tool/component/InitParamDrawer.vue'\nimport ToolResourceApi from '@/api/system-resource-management/tool'\nimport AddInternalToolDialog from '@/views/tool/tool-store/AddInternalToolDialog.vue'\nimport ToolFormDrawer from '@/views/tool/ToolFormDrawer.vue'\nimport McpToolFormDrawer from '@/views/tool/McpToolFormDrawer.vue'\nimport DataSourceToolFormDrawer from '@/views/tool/DataSourceToolFormDrawer.vue'\nimport ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'\nimport ResourceTriggerDrawer from '@/views/trigger/ResourceTriggerDrawer.vue'\nimport { t } from '@/locales'\nimport { SourceTypeEnum } from '@/enums/common'\nimport { resetUrl } from '@/utils/common'\nimport { ToolType } from '@/enums/tool'\nimport useStore from '@/stores'\nimport { datetimeFormat } from '@/utils/time'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\nimport UserApi from '@/api/user/user.ts'\nimport { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'\nimport permissionMap from '@/permission'\nimport McpToolConfigDialog from '@/views/tool/component/McpToolConfigDialog.vue'\nimport ResourceMappingDrawer from '@/components/resource_mapping/index.vue'\nimport ToolRecordDrawer from '@/views/tool/execution-record/TriggerRecordDrawer.vue'\nimport SkillToolFormDrawer from \"@/views/tool/SkillToolFormDrawer.vue\";\n\nconst { user } = useStore()\n\nconst search_type = ref('name')\nconst search_form = ref<any>({\n  name: '',\n  create_user: '',\n  tool_type: '',\n  source: '',\n})\nconst user_options = ref<any[]>([])\nconst type_options = ref<any[]>([\n  {\n    label: 'MCP',\n    value: 'MCP',\n  },\n  {\n    label: t('views.tool.dataSource.title'),\n    value: 'DATA_SOURCE',\n  },\n  {\n    label: t('views.tool.title'),\n    value: 'CUSTOM',\n  },\n  {\n    label: 'Skills',\n    value: 'SKILL',\n  },\n])\nconst source_options = ref<any[]>([\n  {\n    label: t('views.tool.toolStore.title'),\n    value: 'TOOL_STORE',\n  },\n  {\n    label: t('common.custom'),\n    value: 'CUSTOM',\n  },\n])\nconst loading = ref(false)\nconst changeStateloading = ref(false)\nconst toolList = ref<any[]>([])\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\nconst workspaceOptions = ref<any[]>([])\nconst workspaceVisible = ref(false)\nconst workspaceArr = ref<any[]>([])\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool']['systemManage']\n})\n\nconst MoreFilledPermission = (row: any) => {\n  return (\n    permissionPrecise.value.export() ||\n    permissionPrecise.value.delete() ||\n    permissionPrecise.value.auth() ||\n    permissionPrecise.value.relate_map() ||\n    permissionPrecise.value.trigger_read() ||\n    (row.init_field_list?.length > 0 && permissionPrecise.value.edit())\n  )\n}\n\nconst resourceTriggerDrawerRef = ref<InstanceType<typeof ResourceTriggerDrawer>>()\nconst openTriggerDrawer = (data: any) => {\n  resourceTriggerDrawerRef.value?.open(data)\n}\n\nconst ResourceAuthorizationDrawerRef = ref()\n\nfunction openAuthorization(item: any) {\n  ResourceAuthorizationDrawerRef.value.open(item.id)\n}\n\nfunction exportTool(row: any) {\n  ToolResourceApi.exportTool(row.id, row.name, loading).catch((e: any) => {\n    if (e.response.status !== 403) {\n      e.response.data.text().then((res: string) => {\n        MsgError(`${t('views.application.tip.ExportError')}:${JSON.parse(res).message}`)\n      })\n    }\n  })\n}\n\nconst McpToolConfigDialogRef = ref()\n\nfunction showMcpConfig(item: any) {\n  ToolResourceApi.getToolById(item?.id, loading).then((res: any) => {\n    McpToolConfigDialogRef.value.open(res.data)\n  })\n}\n\nfunction deleteTool(row: any) {\n  MsgConfirm(\n    `${t('views.tool.delete.confirmTitle')}：${row.name} ?`,\n    row.resource_count > 0 ? t('views.tool.delete.resourceCountMessage', row.resource_count) : '',\n    {\n      confirmButtonText: t('common.confirm'),\n      cancelButtonText: t('common.cancel'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      ToolResourceApi.delTool(row.id, loading).then(() => {\n        getList()\n        MsgSuccess(t('common.deleteSuccess'))\n      })\n    })\n    .catch(() => {})\n}\n\nfunction configInitParams(item: any) {\n  ToolResourceApi.getToolById(item?.id, changeStateloading).then((res: any) => {\n    InitParamDrawerRef.value.open(res.data)\n  })\n}\n\nasync function copyTool(row: any) {\n  ToolDrawertitle.value = t('views.tool.copyTool')\n  const res = await ToolResourceApi.getToolById(row.id, changeStateloading)\n  const obj = cloneDeep(res.data)\n  delete obj['id']\n  obj['name'] = obj['name'] + `  ${t('common.copyTitle')}`\n  ToolFormDrawerRef.value.open(obj)\n}\n\nconst ToolFormDrawerRef = ref()\nconst McpToolFormDrawerRef = ref()\nconst DataSourceToolFormDrawerRef = ref()\nconst SkillToolFormDrawerRef = ref()\nconst ToolDrawertitle = ref('')\nconst McpToolDrawertitle = ref('')\nconst DataSourceToolDrawertitle = ref('')\nconst SkillToolDrawertitle = ref('')\n\nfunction openCreateDialog(data?: any) {\n  // 有template_id的不允许编辑，是模板转换来的\n  if (data?.template_id) {\n    return\n  }\n\n  ToolDrawertitle.value = t('views.tool.editTool')\n  if (data) {\n    ToolResourceApi.getToolById(data?.id, loading).then((res: any) => {\n      ToolFormDrawerRef.value.open(res.data)\n    })\n  } else {\n    ToolFormDrawerRef.value.open(data)\n  }\n}\n\nfunction openCreateMcpDialog(data?: any) {\n  // 有template_id的不允许编辑，是模板转换来的\n  if (data?.template_id) {\n    return\n  }\n\n  McpToolDrawertitle.value = data ? t('views.tool.mcp.editMcpTool') : t('views.tool.mcp.createMcpTool')\n  if (data) {\n    ToolResourceApi.getToolById(data?.id, loading).then((res: any) => {\n      McpToolFormDrawerRef.value.open(res.data)\n    })\n  } else {\n    McpToolFormDrawerRef.value.open(data)\n  }\n}\n\nfunction openCreateDataSourceDialog(data?: any) {\n  // 有template_id的不允许编辑，是模板转换来的\n  if (data?.template_id) {\n    return\n  }\n\n  DataSourceToolDrawertitle.value = data\n    ? t('views.tool.dataSource.editDataSource')\n    : t('views.tool.dataSource.createDataSource')\n  if (data) {\n    ToolResourceApi.getToolById(data?.id, loading).then((res: any) => {\n      DataSourceToolFormDrawerRef.value.open(res.data)\n    })\n  } else {\n    DataSourceToolFormDrawerRef.value.open(data)\n  }\n}\n\nfunction openCreateSkillToolDialog(data?: any) {\n  // 有template_id的不允许编辑，是模板转换来的\n  if (data?.template_id) {\n    return\n  }\n\n  SkillToolDrawertitle.value = data\n    ? t('views.tool.skill.editSkillTool')\n    : t('views.tool.skill.createSkillTool')\n  if (data) {\n    ToolResourceApi.getToolById(data?.id, loading).then((res: any) => {\n      SkillToolFormDrawerRef.value.open(res.data)\n    })\n  } else {\n    SkillToolFormDrawerRef.value.open(data)\n  }\n}\n\nconst AddInternalToolDialogRef = ref<InstanceType<typeof AddInternalToolDialog>>()\n\nfunction addInternalTool(data?: any, isEdit?: boolean) {\n  AddInternalToolDialogRef.value?.open(data, isEdit)\n}\n\nfunction confirmAddInternalTool(data?: any, isEdit?: boolean) {\n  if (isEdit) {\n    ToolResourceApi.putTool(data?.id as string, { name: data.name }, loading).then((res: any) => {\n      MsgSuccess(t('common.saveSuccess'))\n      refresh()\n    })\n  }\n}\n\nconst InitParamDrawerRef = ref()\n\nasync function changeState(row: any) {\n  if (row.is_active) {\n    MsgConfirm(\n      `${t('views.tool.disabled.confirmTitle')}${row.name} ?`,\n      t('views.tool.disabled.confirmMessage'),\n      {\n        confirmButtonText: t('common.status.disable'),\n        confirmButtonClass: 'danger',\n      },\n    ).then(() => {\n      const obj = {\n        is_active: !row.is_active,\n      }\n      ToolResourceApi.putTool(row.id, obj, changeStateloading)\n        .then(() => {\n          getList()\n          return true\n        })\n        .catch(() => {\n          return false\n        })\n    })\n  } else {\n    const res = await ToolResourceApi.getToolById(row.id, changeStateloading)\n    if (\n      (!res.data.init_params || Object.keys(res.data.init_params).length === 0) &&\n      res.data.init_field_list &&\n      res.data.init_field_list.length > 0 &&\n      res.data.init_field_list.filter((item: any) => item.default_value && item.show_default_value)\n        .length !== res.data.init_field_list.length\n    ) {\n      InitParamDrawerRef.value.open(res.data, !row.is_active)\n      return false\n    }\n    const obj = {\n      is_active: !row.is_active,\n    }\n    ToolResourceApi.putTool(row.id, obj, changeStateloading)\n      .then(() => {\n        getList()\n        return true\n      })\n      .catch(() => {\n        return false\n      })\n  }\n}\n\nconst filterText = ref('')\nconst filterData = ref<any[]>([])\n\nwatch(\n  [() => workspaceOptions.value, () => filterText.value],\n  () => {\n    if (!filterText.value.length) {\n      filterData.value = workspaceOptions.value\n    }\n    filterData.value = workspaceOptions.value.filter((v: any) =>\n      v.label.toLowerCase().includes(filterText.value.toLowerCase()),\n    )\n  },\n  { immediate: true },\n)\n\nfunction filterWorkspaceChange(val: string) {\n  if (val === 'clear') {\n    workspaceArr.value = []\n  }\n  getList()\n  workspaceVisible.value = false\n}\n\nasync function getWorkspaceList() {\n  if (user.isEE()) {\n    const res = await loadPermissionApi('workspace').getSystemWorkspaceList(loading)\n    workspaceOptions.value = res.data.map((item: any) => ({\n      label: item.name,\n      value: item.id,\n    }))\n  }\n}\n\nconst search_type_change = () => {\n  search_form.value = { name: '', create_user: '' }\n}\n\nfunction getList() {\n  const params: any = {}\n  if (search_form.value[search_type.value]) {\n    params[search_type.value] = search_form.value[search_type.value]\n  }\n  if (workspaceArr.value.length > 0) {\n    params.workspace_ids = JSON.stringify(workspaceArr.value)\n  }\n  ToolResourceApi.getToolListPage(paginationConfig, params, loading).then((res) => {\n    paginationConfig.total = res.data?.total\n    toolList.value = res.data?.records\n  })\n}\n\nfunction refresh(data?: any) {\n  if (data) {\n    getList()\n  } else {\n    paginationConfig.total = 0\n    paginationConfig.current_page = 1\n    getList()\n  }\n}\n\nconst resourceMappingDrawerRef = ref<InstanceType<typeof ResourceMappingDrawer>>()\nconst openResourceMappingDrawer = (tool: any) => {\n  resourceMappingDrawerRef.value?.open('TOOL', tool)\n}\n\nconst toolRecordDrawerRef = ref<InstanceType<typeof ToolRecordDrawer>>()\nconst openToolRecordDrawer = (data: any) => {\n  toolRecordDrawerRef.value?.open(data)\n}\n\nonMounted(() => {\n  getWorkspaceList()\n  getList()\n\n  UserApi.getAllMemberList('').then((res: any) => {\n    user_options.value = res.data\n  })\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-setting/authentication/component/CAS.vue",
    "content": "<template>\n  <div class=\"authentication-setting__main main-calc-height\">\n    <el-scrollbar>\n      <div class=\"form-container p-24\" v-loading=\"loading\">\n        <el-form\n          ref=\"authFormRef\"\n          :rules=\"rules\"\n          :model=\"form\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n        >\n          <el-form-item\n            :label=\"$t('views.system.authentication.cas.ldpUri')\"\n            prop=\"config.ldpUri\"\n          >\n            <el-input\n              v-model=\"form.config.ldpUri\"\n              :placeholder=\"$t('views.system.authentication.cas.ldpUriPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.cas.validateUrl')\"\n            prop=\"config.validateUrl\"\n          >\n            <el-input\n              v-model=\"form.config.validateUrl\"\n              :placeholder=\"$t('views.system.authentication.cas.validateUrlPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.cas.redirectUrl')\"\n            prop=\"config.redirectUrl\"\n          >\n            <el-input\n              v-model=\"form.config.redirectUrl\"\n              :placeholder=\"$t('views.system.authentication.cas.redirectUrlPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item>\n            <el-checkbox v-model=\"form.is_active\"\n            >{{ $t('views.system.authentication.cas.enableAuthentication') }}\n            </el-checkbox>\n          </el-form-item>\n        </el-form>\n\n        <div>\n          <el-button @click=\"submit(authFormRef)\" type=\"primary\" :disabled=\"loading\"\n                     v-hasPermission=\"\n                      new ComplexPermission(\n                        [RoleConst.ADMIN],\n                        [PermissionConst.LOGIN_AUTH_EDIT],\n                        [],'OR',)\"\n          >\n            {{ $t('common.save') }}\n          </el-button>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport {reactive, ref, watch, onMounted} from 'vue'\nimport authApi from '@/api/system-settings/auth-setting'\nimport type {FormInstance, FormRules} from 'element-plus'\nimport {t} from '@/locales'\nimport {MsgSuccess} from '@/utils/message'\nimport {PermissionConst, RoleConst} from '@/utils/permission/data'\nimport {ComplexPermission} from '@/utils/permission/type'\n\nconst form = ref<any>({\n  id: '',\n  auth_type: 'CAS',\n  config: {\n    ldpUri: '',\n    validateUrl: '',\n    redirectUrl: ''\n  },\n  is_active: true\n})\n\nconst authFormRef = ref()\n\nconst loading = ref(false)\n\nconst rules = reactive<FormRules<any>>({\n  'config.ldpUri': [\n    {\n      required: true,\n      message: t('views.system.authentication.cas.ldpUriPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.validateUrl': [\n    {\n      required: true,\n      message: t('views.system.authentication.cas.validateUrlPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.redirectUrl': [\n    {\n      required: true,\n      message: t('views.system.authentication.cas.redirectUrlPlaceholder'),\n      trigger: 'blur'\n    }\n  ]\n})\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {\n        MsgSuccess(t('common.saveSuccess'))\n      })\n    }\n  })\n}\n\nfunction getDetail() {\n  authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {\n    if (res.data && JSON.stringify(res.data) !== '{}') {\n      if (!res.data.config.validateUrl) {\n        res.data.config.validateUrl = res.data.config.ldpUri\n      }\n      form.value = res.data\n    }\n    if (!form.value.config.redirectUrl) {\n        form.value.config.redirectUrl = window.location.origin + window.MaxKB.prefix + '/api/cas'\n      }\n  })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-setting/authentication/component/EditModal.vue",
    "content": "template\n<template>\n  <el-drawer\n    v-model=\"visible\"\n    size=\"60%\"\n    :append-to-body=\"true\"\n    :destroy-on-close=\"true\"\n    @close=\"handleClose\"\n  >\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <h4>\n          {{ currentPlatform.name + $t('views.system.authentication.scanTheQRCode.setting') }}\n        </h4>\n      </div>\n    </template>\n\n    <el-form\n      :model=\"currentPlatform.config\"\n      label-width=\"120px\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      ref=\"formRef\"\n    >\n      <el-form-item\n        v-for=\"(value, key) in currentPlatform.config\"\n        :key=\"key\"\n        :label=\"formatFieldName(key)\"\n        :prop=\"key\"\n        :rules=\"getValidationRules(key)\"\n      >\n        <el-input\n          v-model=\"currentPlatform.config[key]\"\n          :type=\"isPasswordField(key) ? 'password' : 'text'\"\n          :show-password=\"isPasswordField(key)\"\n        >\n        </el-input>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click=\"handleClose\">{{ $t('common.cancel') }}</el-button>\n        <el-button @click=\"validateConnection\">{{\n            $t('views.system.authentication.scanTheQRCode.validate')\n          }}</el-button>\n        <el-button type=\"primary\" @click=\"validateForm\">{{ $t('common.save') }}</el-button>\n      </span>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport {reactive, ref} from 'vue'\nimport {ElForm} from 'element-plus'\nimport platformApi from '@/api/system-settings/platform-source'\nimport {MsgError, MsgSuccess} from '@/utils/message'\nimport {t} from '@/locales'\n\nconst visible = ref(false)\nconst loading = ref(false)\nconst formRef = ref<InstanceType<typeof ElForm>>()\n\ninterface PlatformConfig {\n  [key: string]: string\n}\n\ninterface Platform {\n  key: string\n  logoSrc: string\n  name: string\n  isActive: boolean\n  isValid: boolean\n  config: PlatformConfig\n}\n\nconst currentPlatform = reactive<Platform>({\n  key: '',\n  logoSrc: '',\n  name: '',\n  isActive: false,\n  isValid: false,\n  config: {}\n})\n\nconst formatFieldName = (key?: any): string => {\n  const fieldNames: { [key: string]: string } = {\n    corp_id: 'Corp ID',\n    app_key: currentPlatform?.key != 'lark' ? 'APP Key' : 'App ID',\n    app_secret: 'APP Secret',\n    agent_id: 'Agent ID',\n    callback_url: t('views.application.applicationAccess.callback')\n  }\n  return (\n    fieldNames[key as keyof typeof fieldNames] ||\n    (key ? key.charAt(0).toUpperCase() + key.slice(1) : '')\n  )\n}\n\nconst getValidationRules = (key: any) => {\n  switch (key) {\n    case 'app_key':\n      return [\n        {\n          required: true,\n          message: t('views.system.authentication.scanTheQRCode.appKeyPlaceholder'),\n          trigger: ['blur', 'change']\n        }\n      ]\n    case 'app_secret':\n      return [\n        {\n          required: true,\n          message: t('views.system.authentication.scanTheQRCode.appSecretPlaceholder'),\n          trigger: ['blur', 'change']\n        }\n      ]\n    case 'corp_id':\n      return [\n        {\n          required: true,\n          message: t('views.system.authentication.scanTheQRCode.corpIdPlaceholder'),\n          trigger: ['blur', 'change']\n        }\n      ]\n    case 'agent_id':\n      return [\n        {\n          required: true,\n          message: t('views.system.authentication.scanTheQRCode.agentIdPlaceholder'),\n          trigger: ['blur', 'change']\n        }\n      ]\n    case 'callback_url':\n      return [\n        {\n          required: true,\n          message: t('views.application.applicationAccess.callbackTip'),\n          trigger: ['blur', 'change']\n        },\n        {\n          pattern: /^https?:\\/\\/.+/,\n          message: t('views.system.authentication.scanTheQRCode.callbackWarning'),\n          trigger: ['blur', 'change']\n        }\n      ]\n    default:\n      return []\n  }\n}\n\nconst open = async (platform: Platform) => {\n  visible.value = true\n  loading.value = true\n  Object.assign(currentPlatform, platform)\n\n  // 设置默认的 callback_url\n  const defaultCallbackUrl = window.location.origin + window.MaxKB.prefix\n  switch (platform.key) {\n    case 'wecom':\n      if (currentPlatform.config.app_key) {\n        currentPlatform.config.agent_id = currentPlatform.config.app_key\n        delete currentPlatform.config.app_key\n      }\n      currentPlatform.config.callback_url = `${defaultCallbackUrl}/api/wecom`\n      break\n    case 'dingtalk':\n      if (currentPlatform.config.agent_id) {\n        currentPlatform.config.corp_id = currentPlatform.config.agent_id\n        delete currentPlatform.config.agent_id\n      }\n      currentPlatform.config = {\n        corp_id: currentPlatform.config.corp_id,\n        app_key: currentPlatform.config.app_key,\n        app_secret: currentPlatform.config.app_secret,\n        callback_url: defaultCallbackUrl\n      }\n      currentPlatform.config.callback_url = `${defaultCallbackUrl}/api/dingtalk`\n      break\n    case 'lark':\n      currentPlatform.config.callback_url = `${defaultCallbackUrl}/api/lark`\n      break\n    default:\n      break\n  }\n  formRef.value?.clearValidate()\n}\ndefineExpose({open})\n\nconst validateForm = () => {\n  formRef.value?.validate((valid) => {\n    if (valid) {\n      saveConfig()\n    } else {\n      MsgError(t('views.system.authentication.scanTheQRCode.validateFailedTip'))\n    }\n  })\n}\n\nconst handleClose = () => {\n  visible.value = false\n  formRef.value?.clearValidate()\n  emit('refresh')\n}\n\nfunction validateConnection() {\n  platformApi.validateConnection(currentPlatform, loading).then((res: any) => {\n    if (res.data) {\n      MsgSuccess(t('views.system.authentication.scanTheQRCode.validateSuccess'))\n    } else {\n      MsgError(t('views.system.authentication.scanTheQRCode.validateFailed'))\n    }\n  })\n}\n\nconst passwordFields = new Set(['app_secret', 'client_secret', 'secret'])\n\nconst isPasswordField = (key: any) => passwordFields.has(key)\nconst emit = defineEmits(['refresh'])\n\nfunction saveConfig() {\n  platformApi.updateConfig(currentPlatform, loading).then((res: any) => {\n    MsgSuccess(t('common.saveSuccess'))\n    emit('refresh')\n    visible.value = false\n    formRef.value?.clearValidate()\n  })\n}\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-setting/authentication/component/LDAP.vue",
    "content": "<template>\n  <div class=\"authentication-setting__main main-calc-height\">\n    <el-scrollbar>\n      <div class=\"form-container p-24\" v-loading=\"loading\">\n        <el-form\n          ref=\"authFormRef\"\n          :rules=\"rules\"\n          :model=\"form\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n        >\n          <el-form-item\n            :label=\"$t('views.system.authentication.ldap.address')\"\n            prop=\"config.ldap_server\"\n          >\n            <el-input\n              v-model=\"form.config.ldap_server\"\n              :placeholder=\"$t('views.system.authentication.ldap.serverPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.ldap.bindDN')\"\n            prop=\"config.base_dn\"\n          >\n            <el-input\n              v-model=\"form.config.base_dn\"\n              :placeholder=\"$t('views.system.authentication.ldap.bindDNPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item :label=\"$t('views.system.password')\" prop=\"config.password\">\n            <el-input\n              v-model=\"form.config.password\"\n              :placeholder=\"$t('views.login.loginForm.password.placeholder')\"\n              show-password\n            />\n          </el-form-item>\n          <el-form-item :label=\"$t('views.system.authentication.ldap.ou')\" prop=\"config.ou\">\n            <el-input\n              v-model=\"form.config.ou\"\n              :placeholder=\"$t('views.system.authentication.ldap.ouPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.ldap.ldap_filter')\"\n            prop=\"config.ldap_filter\"\n          >\n            <el-input\n              v-model=\"form.config.ldap_filter\"\n              :placeholder=\"$t('views.system.authentication.ldap.ldap_filterPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.ldap.ldap_mapping')\"\n            prop=\"config.ldap_mapping\"\n          >\n            <el-input\n              v-model=\"form.config.ldap_mapping\"\n              placeholder='{\"name\":\"name\",\"email\":\"mail\",\"username\":\"cn\"}'\n            />\n          </el-form-item>\n          <el-form-item>\n            <el-checkbox v-model=\"form.is_active\">{{\n              $t('views.system.authentication.ldap.enableAuthentication')\n            }}</el-checkbox>\n          </el-form-item>\n        </el-form>\n\n        <div>\n          <span\n            v-hasPermission=\"\n              new ComplexPermission([RoleConst.ADMIN], [PermissionConst.LOGIN_AUTH_EDIT], [], 'OR')\n            \"\n            class=\"mr-12\"\n          >\n            <el-button @click=\"submit(authFormRef)\" type=\"primary\" :disabled=\"loading\">\n              {{ $t('common.save') }}\n            </el-button>\n          </span>\n          <span>\n            <el-button @click=\"submit(authFormRef, 'test')\" :disabled=\"loading\">\n              {{ $t('views.system.test') }}</el-button\n            >\n          </span>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch, onMounted } from 'vue'\nimport authApi from '@/api/system-settings/auth-setting'\nimport type { FormInstance, FormRules } from 'element-plus'\nimport { t } from '@/locales'\nimport { MsgSuccess } from '@/utils/message'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\nconst form = ref<any>({\n  id: '',\n  auth_type: 'LDAP',\n  config: {\n    ldap_server: '',\n    base_dn: '',\n    password: '',\n    ou: '',\n    ldap_filter: '',\n    ldap_mapping: '',\n  },\n  is_active: true,\n})\n\nconst authFormRef = ref()\n\nconst loading = ref(false)\n\nconst rules = reactive<FormRules<any>>({\n  'config.ldap_server': [\n    {\n      required: true,\n      message: t('views.system.authentication.ldap.serverPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.base_dn': [\n    {\n      required: true,\n      message: t('views.system.authentication.ldap.bindDNPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.password': [\n    {\n      required: true,\n      message: t('views.login.loginForm.password.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.ou': [\n    {\n      required: true,\n      message: t('views.system.authentication.ldap.ouPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.ldap_filter': [\n    {\n      required: true,\n      message: t('views.system.authentication.ldap.ldap_filterPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.ldap_mapping': [\n    {\n      required: true,\n      message: t('views.system.authentication.ldap.ldap_mappingPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst submit = async (formEl: FormInstance | undefined, test?: string) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      if (test) {\n        authApi.postAuthSetting(form.value, loading).then((res) => {\n          MsgSuccess(t('views.system.testSuccess'))\n        })\n      } else {\n        authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {\n          MsgSuccess(t('common.saveSuccess'))\n        })\n      }\n    }\n  })\n}\n\nfunction getDetail() {\n  authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {\n    if (res.data && JSON.stringify(res.data) !== '{}') {\n      form.value = res.data\n      if (res.data.config.ldap_mapping) {\n        form.value.config.ldap_mapping = JSON.stringify(JSON.parse(res.data.config.ldap_mapping))\n      }\n    }\n  })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-setting/authentication/component/OAuth2.vue",
    "content": "<template>\n  <div class=\"authentication-setting__main main-calc-height\">\n    <el-scrollbar>\n      <div class=\"form-container p-24\" v-loading=\"loading\">\n        <el-form\n          ref=\"authFormRef\"\n          :rules=\"rules\"\n          :model=\"form\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n        >\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.authEndpoint')\"\n            prop=\"config.authEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.authEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oauth2.authEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.tokenEndpoint')\"\n            prop=\"config.tokenEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.tokenEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oauth2.tokenEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.userInfoEndpoint')\"\n            prop=\"config.userInfoEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.userInfoEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oauth2.userInfoEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.scope')\"\n            prop=\"config.scope\"\n          >\n            <el-input\n              v-model=\"form.config.scope\"\n              :placeholder=\"$t('views.system.authentication.oauth2.scopePlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.clientId')\"\n            prop=\"config.clientId\"\n          >\n            <el-input\n              v-model=\"form.config.clientId\"\n              :placeholder=\"$t('views.system.authentication.oauth2.clientIdPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.clientSecret')\"\n            prop=\"config.clientSecret\"\n          >\n            <el-input\n              v-model=\"form.config.clientSecret\"\n              :placeholder=\"$t('views.system.authentication.oauth2.clientSecretPlaceholder')\"\n              show-password\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.filedMapping')\"\n            prop=\"config.fieldMapping\"\n          >\n            <el-input\n              v-model=\"form.config.fieldMapping\"\n              :placeholder=\"$t('views.system.authentication.oauth2.filedMappingPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.redirectUrl')\"\n            prop=\"config.redirectUrl\"\n          >\n            <el-input\n              v-model=\"form.config.redirectUrl\"\n              :placeholder=\"$t('views.system.authentication.oauth2.redirectUrlPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item>\n            <el-checkbox v-model=\"form.is_active\"\n            >{{ $t('views.system.authentication.oauth2.enableAuthentication') }}\n            </el-checkbox>\n          </el-form-item>\n        </el-form>\n\n        <div>\n          <el-button @click=\"submit(authFormRef)\" type=\"primary\" :disabled=\"loading\"\n                     v-hasPermission=\"\n                      new ComplexPermission(\n                        [RoleConst.ADMIN],\n                        [PermissionConst.LOGIN_AUTH_EDIT],\n                        [],'OR',)\"\n          >\n            {{ $t('common.save') }}\n          </el-button>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport {reactive, ref, onMounted} from 'vue'\nimport authApi from '@/api/system-settings/auth-setting'\nimport type {FormInstance, FormRules} from 'element-plus'\nimport {t} from '@/locales'\nimport {MsgSuccess} from '@/utils/message'\nimport {PermissionConst, RoleConst} from '@/utils/permission/data'\nimport {ComplexPermission} from '@/utils/permission/type'\n\nconst form = ref<any>({\n  id: '',\n  auth_type: 'OAuth2',\n  config: {\n    authEndpoint: '',\n    tokenEndpoint: '',\n    userInfoEndpoint: '',\n    scope: '',\n    clientId: '',\n    clientSecret: '',\n    redirectUrl: '',\n    fieldMapping: ''\n  },\n  is_active: true\n})\n\nconst authFormRef = ref()\n\nconst loading = ref(false)\n\nconst rules = reactive<FormRules<any>>({\n  'config.authEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.authEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.tokenEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.tokenEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.userInfoEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.userInfoEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.scope': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.scopePlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.clientId': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.clientIdPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.clientSecret': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.clientSecretPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.redirectUrl': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.redirectUrlPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.fieldMapping': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.filedMappingPlaceholder'),\n      trigger: 'blur'\n    }\n  ]\n})\n\nconst submit = async (formEl: FormInstance | undefined, test?: string) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {\n        MsgSuccess(t('common.saveSuccess'))\n      })\n    }\n  })\n}\n\nfunction getDetail() {\n  authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {\n    if (res.data && JSON.stringify(res.data) !== '{}') {\n      form.value = res.data\n    }\n    if (!form.value.config.redirectUrl) {\n      form.value.config.redirectUrl = window.location.origin + window.MaxKB.prefix + '/api/oauth2'\n    }\n  })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-setting/authentication/component/OIDC.vue",
    "content": "<template>\n  <div class=\"authentication-setting__main main-calc-height\">\n    <el-scrollbar>\n      <div class=\"form-container p-24\" v-loading=\"loading\">\n        <el-form\n          ref=\"authFormRef\"\n          :rules=\"rules\"\n          :model=\"form\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n        >\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.authEndpoint')\"\n            prop=\"config.authEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.authEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oidc.authEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.tokenEndpoint')\"\n            prop=\"config.tokenEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.tokenEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oidc.tokenEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.userInfoEndpoint')\"\n            prop=\"config.userInfoEndpoint\"\n          >\n            <el-input\n              v-model=\"form.config.userInfoEndpoint\"\n              :placeholder=\"$t('views.system.authentication.oidc.userInfoEndpointPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item label=\"Scope\" prop=\"config.scope\">\n            <el-input v-model=\"form.config.scope\" placeholder=\"openid+profile+email \"/>\n          </el-form-item>\n          <el-form-item label=\"State\" prop=\"config.state\">\n            <el-input v-model=\"form.config.state\" placeholder=\"\"/>\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.clientId')\"\n            prop=\"config.clientId\"\n          >\n            <el-input\n              v-model=\"form.config.clientId\"\n              :placeholder=\"$t('views.system.authentication.oidc.clientIdPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.clientSecret')\"\n            prop=\"config.clientSecret\"\n          >\n            <el-input\n              v-model=\"form.config.clientSecret\"\n              :placeholder=\"$t('views.system.authentication.oidc.clientSecretPlaceholder')\"\n              show-password\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oauth2.filedMapping')\"\n            prop=\"config.fieldMapping\"\n          >\n            <el-input\n              v-model=\"form.config.fieldMapping\"\n              :placeholder=\"$t('views.system.authentication.oauth2.filedMappingPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.authentication.oidc.redirectUrl')\"\n            prop=\"config.redirectUrl\"\n          >\n            <el-input\n              v-model=\"form.config.redirectUrl\"\n              :placeholder=\"$t('views.system.authentication.oidc.redirectUrlPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item>\n            <el-checkbox v-model=\"form.is_active\"\n            >{{ $t('views.system.authentication.oidc.enableAuthentication') }}\n            </el-checkbox>\n          </el-form-item>\n        </el-form>\n\n        <div>\n          <el-button @click=\"submit(authFormRef)\" type=\"primary\" :disabled=\"loading\"\n                     v-hasPermission=\"\n                      new ComplexPermission(\n                        [RoleConst.ADMIN],\n                        [PermissionConst.LOGIN_AUTH_EDIT],\n                        [],'OR',)\"\n          >\n            {{ $t('common.save') }}\n          </el-button>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport {reactive, ref, watch, onMounted} from 'vue'\nimport authApi from '@/api/system-settings/auth-setting'\nimport type {FormInstance, FormRules} from 'element-plus'\nimport {t} from '@/locales'\nimport {MsgSuccess} from '@/utils/message'\nimport {PermissionConst, RoleConst} from '@/utils/permission/data'\nimport {ComplexPermission} from '@/utils/permission/type'\n\nconst form = ref<any>({\n  id: '',\n  auth_type: 'OIDC',\n  config: {\n    authEndpoint: '',\n    tokenEndpoint: '',\n    userInfoEndpoint: '',\n    scope: '',\n    state: '',\n    clientId: '',\n    clientSecret: '',\n    fieldMapping: '{\"username\": \"preferred_username\", \"email\": \"email\"}',\n    redirectUrl: ''\n  },\n  is_active: true\n})\n\nconst authFormRef = ref()\n\nconst loading = ref(false)\n\nconst rules = reactive<FormRules<any>>({\n  'config.authEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.authEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.tokenEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.tokenEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.userInfoEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.userInfoEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.scope': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.scopePlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.clientId': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.clientIdPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.clientSecret': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.clientSecretPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.fieldMapping': [\n    {\n      required: true,\n      message: t('views.system.authentication.oauth2.filedMappingPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.redirectUrl': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.redirectUrlPlaceholder'),\n      trigger: 'blur'\n    }\n  ],\n  'config.logoutEndpoint': [\n    {\n      required: true,\n      message: t('views.system.authentication.oidc.logoutEndpointPlaceholder'),\n      trigger: 'blur'\n    }\n  ]\n})\n\nconst submit = async (formEl: FormInstance | undefined, test?: string) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {\n        MsgSuccess(t('common.saveSuccess'))\n      })\n    }\n  })\n}\n\nfunction getDetail() {\n  authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {\n    if (res.data && JSON.stringify(res.data) !== '{}') {\n      form.value = res.data\n      if (\n        form.value.config.fieldMapping === '' ||\n        form.value.config.fieldMapping === undefined\n      ) {\n        form.value.config.fieldMapping = '{\"username\": \"preferred_username\", \"email\": \"email\"}'\n      }\n    }\n    if (!form.value.config.redirectUrl) {\n        form.value.config.redirectUrl = window.location.origin + window.MaxKB.prefix + '/api/oidc'\n      }\n  })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-setting/authentication/component/SCAN.vue",
    "content": "<template>\n  <div v-loading=\"loading\" class=\"scan-height\">\n    <el-scrollbar>\n      <div v-for=\"item in platforms\" :key=\"item.key\" class=\"mb-16\">\n        <el-card class=\"border-none mb-16\" shadow=\"never\">\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <img :src=\"item.logoSrc\" alt=\"\" width=\"24px\" />\n              <h5 class=\"ml-8\">{{ item.name }}</h5>\n              <el-tag v-if=\"item.isValid\" size=\"small\" type=\"success\" class=\"ml-8\"\n                >{{ $t('views.system.authentication.scanTheQRCode.effective') }}\n              </el-tag>\n            </div>\n            <div>\n              <el-button\n                type=\"primary\"\n                v-if=\"!item.isValid\"\n                @click=\"showDialog(item)\"\n                v-hasPermission=\"\n                  new ComplexPermission(\n                    [RoleConst.ADMIN],\n                    [PermissionConst.LOGIN_AUTH_EDIT],\n                    [],\n                    'OR',\n                  )\n                \"\n                >{{ $t('views.system.authentication.scanTheQRCode.access') }}\n              </el-button>\n              <span v-if=\"item.isValid\">\n                <span class=\"mr-4\">{{\n                  item.isActive\n                    ? $t('views.system.authentication.scanTheQRCode.alreadyTurnedOn')\n                    : $t('views.system.authentication.scanTheQRCode.notEnabled')\n                }}</span>\n                <el-switch\n                  size=\"small\"\n                  v-model=\"item.isActive\"\n                  :disabled=\"!item.isValid\"\n                  @change=\"changeStatus(item)\"\n                />\n              </span>\n            </div>\n          </div>\n          <el-collapse-transition>\n            <div v-if=\"item.isValid\" class=\"border-t mt-16\">\n              <el-row :gutter=\"12\" class=\"mt-16\">\n                <el-col v-for=\"(value, key) in item.config\" :key=\"key\" :span=\"12\">\n                  <el-text class=\"color-secondary lighter\">{{ formatFieldName(key, item) }}</el-text>\n                  <div class=\"mt-4 mb-16 flex align-center\">\n                    <span\n                      v-if=\"key !== 'app_secret'\"\n                      class=\"vertical-middle lighter break-all ellipsis-1\"\n                      >{{ value }}</span\n                    >\n                    <span\n                      v-if=\"key === 'app_secret' && !showPassword[item.key]?.[key]\"\n                      class=\"vertical-middle lighter break-all ellipsis-1\"\n                      >************</span\n                    >\n                    <span\n                      v-if=\"key === 'app_secret' && showPassword[item.key]?.[key]\"\n                      class=\"vertical-middle lighter break-all ellipsis-1\"\n                      >{{ value }}</span\n                    >\n                    <span>\n                      <el-button type=\"primary\" text @click=\"() => copyClick(value)\">\n                        <AppIcon iconName=\"app-copy\" />\n                      </el-button>\n                    </span>\n\n                    <span class=\"ml-4\">\n                      <el-button\n                        v-if=\"key === 'app_secret'\"\n                        type=\"primary\"\n                        text\n                        @click=\"toggleShowPassword(item.key)\"\n                      >\n                        <AppIcon\n                          iconName=\"app-password-hide\"\n                          v-if=\"key === 'app_secret' && !showPassword[item.key]?.[key]\"\n                        />\n\n                        <el-icon v-if=\"key === 'app_secret' && showPassword[item.key]?.[key]\">\n                          <View />\n                        </el-icon>\n                      </el-button>\n                    </span>\n                  </div>\n                </el-col>\n              </el-row>\n              <el-button type=\"primary\" @click=\"showDialog(item)\">\n                {{ $t('common.edit') }}\n              </el-button>\n              <el-button @click=\"validateConnection(item)\">\n                {{ $t('views.system.authentication.scanTheQRCode.validate') }}\n              </el-button>\n            </div>\n          </el-collapse-transition>\n        </el-card>\n      </div>\n      <EditModel ref=\"EditModelRef\" @refresh=\"refresh\" />\n    </el-scrollbar>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { reactive, ref, onMounted } from 'vue'\nimport { copyClick } from '@/utils/clipboard'\nimport EditModel from './EditModal.vue'\nimport platformApi from '@/api/system-settings/platform-source'\nimport { MsgError, MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\ninterface PlatformConfig {\n  [key: string]: string\n}\n\ninterface Platform {\n  key: string\n  logoSrc: string\n  name: string\n  isActive: boolean\n  isValid: boolean\n  config: PlatformConfig\n}\n\nconst EditModelRef = ref()\nconst loading = ref(false)\nconst platforms = reactive<Platform[]>(initializePlatforms())\nconst showPassword = reactive<{ [platformKey: string]: { [key: string]: boolean } }>({})\n\nonMounted(() => {\n  getPlatformInfo()\n})\n\nfunction initializePlatforms(): Platform[] {\n  return [\n    createPlatform('wecom', t('views.system.authentication.scanTheQRCode.wecom')),\n    createPlatform('dingtalk', t('views.system.authentication.scanTheQRCode.dingtalk')),\n    createPlatform('lark', t('views.system.authentication.scanTheQRCode.lark')),\n  ]\n}\n\nfunction createPlatform(key: string, name: string): Platform {\n  let logo = ''\n  switch (key) {\n    case 'wecom':\n      logo = 'wechat-work'\n      break\n    case 'dingtalk':\n      logo = 'dingtalk'\n      break\n    case 'lark':\n      logo = 'lark'\n      break\n    default:\n      logo = '' // 默认值\n      break\n  }\n\n  const config = {\n    ...(key === 'wecom' ? { corp_id: '', agent_id: '' } : { app_key: '' }),\n    app_secret: '',\n    callback_url: '',\n  }\n\n  return {\n    key,\n    logoSrc: new URL(`../../../../assets/logo/logo_${logo}.svg`, import.meta.url).href,\n    name,\n    isActive: false,\n    isValid: false,\n    config,\n  }\n}\n\nfunction formatFieldName(key?: any, item?: Platform): string {\n  const fieldNames: { [key: string]: string } = {\n    corp_id: 'Corp ID',\n    app_key: item?.key != 'lark' ? 'APP Key' : 'App ID',\n    app_secret: 'APP Secret',\n    agent_id: 'Agent ID',\n    callback_url: t('views.application.applicationAccess.callback'),\n  }\n  return (\n    fieldNames[key as keyof typeof fieldNames] ||\n    (key ? key.charAt(0).toUpperCase() + key.slice(1) : '')\n  )\n}\n\nfunction getPlatformInfo() {\n  loading.value = true\n  platformApi.getPlatformInfo(loading).then((res: any) => {\n    if (res) {\n      platforms.forEach((platform) => {\n        const data = res.data.find((item: any) => item.auth_type === platform.key)\n        if (data) {\n          Object.assign(platform, {\n            isValid: data.is_valid,\n            isActive: data.is_active,\n            config: data.config,\n          })\n          if (platform.key === 'dingtalk') {\n            const { corp_id, app_key, app_secret } = platform.config\n            platform.config = {\n              corp_id,\n              app_key,\n              app_secret,\n              callback_url: platform.config.callback_url,\n            }\n          }\n          showPassword[platform.key] = {}\n          showPassword[platform.key]['app_secret'] = false\n        }\n      })\n    }\n  })\n}\n\nfunction validateConnection(currentPlatform: Platform) {\n  platformApi.validateConnection(currentPlatform, loading).then((res: any) => {\n    if (res.data) {\n      MsgSuccess(t('views.system.authentication.scanTheQRCode.validateSuccess'))\n    } else {\n      MsgError(t('views.system.authentication.scanTheQRCode.validateFailed'))\n    }\n  })\n}\n\nfunction refresh() {\n  getPlatformInfo()\n}\n\nfunction changeStatus(currentPlatform: Platform) {\n  platformApi.updateConfig(currentPlatform, loading).then((res: any) => {\n    MsgSuccess(t('common.saveSuccess'))\n  })\n}\n\nfunction toggleShowPassword(platformKey: string) {\n  if (!showPassword[platformKey]) {\n    showPassword[platformKey] = {}\n  }\n  showPassword[platformKey]['app_secret'] = !showPassword[platformKey]['app_secret']\n}\n\nfunction showDialog(platform: Platform) {\n  EditModelRef.value?.open(platform)\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.scan-height {\n  height: calc(100vh - var(--app-header-height) - var(--app-view-padding) * 2 - 70px);\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-setting/authentication/component/Saml2.vue",
    "content": "<template>\n  <div class=\"authentication-setting__main main-calc-height\">\n    <el-scrollbar>\n      <div class=\"form-container p-24\" v-loading=\"loading\">\n        <el-form\n            ref=\"authFormRef\"\n            :rules=\"rules\"\n            :model=\"form\"\n            label-position=\"top\"\n            require-asterisk-position=\"right\"\n        >\n          <el-form-item\n              :label=\"$t('views.system.authentication.saml2.ldp')\"\n              prop=\"config.idpMetaUrl\"\n          >\n            <el-input\n                v-model=\"form.config.idpMetaUrl\"\n                :placeholder=\"$t('views.system.authentication.saml2.ldpPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item>\n            <el-checkbox v-model=\"form.config.wantAssertionsSigned\">{{\n                $t('views.system.authentication.saml2.enableAuthnRequests')\n              }}\n            </el-checkbox>\n          </el-form-item>\n          <el-form-item>\n            <el-checkbox v-model=\"form.config.wantAuthnRequestsSigned\">{{\n                $t('views.system.authentication.saml2.enableAssertions')\n              }}\n            </el-checkbox>\n          </el-form-item>\n          <el-form-item\n              :label=\"$t('views.system.authentication.saml2.privateKey')\"\n              prop=\"config.privateKey\"\n          >\n            <el-input\n                v-model=\"form.config.privateKey\"\n                type=\"password\"\n                show-password\n                :placeholder=\"$t('views.system.authentication.saml2.privateKeyPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n              :label=\"$t('views.system.authentication.saml2.certificate')\"\n              prop=\"config.certificate\"\n          >\n            <el-input\n                v-model=\"form.config.certificate\"\n                type=\"password\"\n                show-password\n                :placeholder=\"$t('views.system.authentication.saml2.certificatePlaceholder')\"\n            />\n          </el-form-item>\n\n          <el-form-item :label=\"$t('views.system.authentication.saml2.filedMapping')\"\n                        prop=\"config.mapping\">\n            <el-input\n                v-model=\"form.config.mapping\"\n                :placeholder=\"$t('views.system.authentication.saml2.filedMappingPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n              :label=\"$t('views.system.authentication.saml2.spEntityId')\"\n              prop=\"config.spEntityId\"\n          >\n            <el-input\n                v-model=\"form.config.spEntityId\"\n                :placeholder=\"$t('views.system.authentication.saml2.spEntityIdPlaceholder')\"\n            />\n          </el-form-item>\n          <el-form-item\n              :label=\"$t('views.system.authentication.saml2.spAcs')\"\n              prop=\"config.spAcs\"\n          >\n            <el-input\n                v-model=\"form.config.spAcs\"\n                :placeholder=\"$t('views.system.authentication.saml2.spAcsPlaceholder')\"\n            />\n          </el-form-item>\n\n          <el-form-item>\n            <el-checkbox v-model=\"form.is_active\">{{\n                $t('views.system.authentication.saml2.enableAuthentication')\n              }}\n            </el-checkbox>\n          </el-form-item>\n        </el-form>\n\n        <div>\n          <span\n              v-hasPermission=\"\n              new ComplexPermission([RoleConst.ADMIN], [PermissionConst.LOGIN_AUTH_EDIT], [], 'OR')\n            \"\n              class=\"mr-12\"\n          >\n            <el-button @click=\"submit(authFormRef)\" type=\"primary\" :disabled=\"loading\">\n              {{ $t('common.save') }}\n            </el-button>\n          </span>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport {reactive, ref, watch, onMounted} from 'vue'\nimport authApi from '@/api/system-settings/auth-setting'\nimport type {FormInstance, FormRules} from 'element-plus'\nimport {t} from '@/locales'\nimport {MsgSuccess} from '@/utils/message'\nimport {PermissionConst, RoleConst} from '@/utils/permission/data'\nimport {ComplexPermission} from '@/utils/permission/type'\n\nconst form = ref<any>({\n  id: '',\n  auth_type: 'SAML2',\n  config: {\n    idpMetaUrl: '',\n    wantAssertionsSigned: true,\n    wantAuthnRequestsSigned: true,\n    privateKey: '',\n    certificate: '',\n    mapping: '',\n    spEntityId: window.location.origin + window.MaxKB.prefix + '/api/saml2/metadata',\n    spAcs: window.location.origin + window.MaxKB.prefix + '/api/saml2/sso',\n  },\n  is_active: true,\n})\n\nconst authFormRef = ref()\n\nconst loading = ref(false)\n\nconst rules = reactive<FormRules<any>>({\n  'config.idpMetaUrl': [\n    {\n      required: true,\n      message: t('views.system.authentication.saml2.ldpPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.privateKey': [\n    {\n      required: true,\n      message: t('views.system.authentication.saml2.privateKeyPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.certificate': [\n    {\n      required: true,\n      message: t('views.system.authentication.saml2.certificatePlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n  'config.mapping': [\n    {\n      required: true,\n      message: t('views.system.authentication.saml2.filedMappingPlaceholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst submit = async (formEl: FormInstance | undefined, test?: string) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      authApi.putAuthSetting(form.value.auth_type, form.value, loading).then((res) => {\n        MsgSuccess(t('common.saveSuccess'))\n      })\n    }\n  })\n}\n\nfunction getDetail() {\n  authApi.getAuthSetting(form.value.auth_type, loading).then((res: any) => {\n    if (res.data && JSON.stringify(res.data) !== '{}') {\n      form.value = res.data\n      if (res.data.config.mapping) {\n        form.value.config.mapping = JSON.stringify(JSON.parse(res.data.config.mapping))\n      }\n      if (!form.value.config.spEntityId) {\n        form.value.config.spEntityId = window.location.origin + window.MaxKB.prefix + '/api/saml2/metadata'\n      }\n      if (!form.value.config.spAcs) {\n        form.value.config.spAcs = window.location.origin + window.MaxKB.prefix + '/api/saml2/sso'\n      }\n    }\n  })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-setting/authentication/component/Setting.vue",
    "content": "<template>\n  <div class=\"authentication-setting__main main-calc-height\">\n    <el-scrollbar>\n      <div class=\"form-container p-24\" v-loading=\"loading\">\n        <el-form\n          ref=\"authFormRef\"\n          :model=\"form\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n          @submit.prevent\n        >\n          <!-- 登录方式选择框 -->\n          <el-form-item\n            :label=\"$t('views.system.login_method')\"\n            :rules=\"[\n              {\n                required: true,\n                message: $t('views.applicationOverview.appInfo.LimitDialog.loginMethodRequired'),\n                trigger: 'change',\n              },\n            ]\"\n            prop=\"login_methods\"\n            style=\"padding-top: 16px\"\n          >\n            <el-checkbox-group v-model=\"form.login_methods\" @change=\"handleLoginMethodsChange\">\n              <template v-for=\"t in systemLoginMethods\" :key=\"t.value\">\n                <el-checkbox :label=\"t.label\" :value=\"t.value\"/>\n              </template>\n            </el-checkbox-group>\n          </el-form-item>\n\n          <el-form-item\n            :label=\"$t('views.system.default_login')\"\n            :rules=\"[\n              {\n                required: true,\n                message: $t('views.applicationOverview.appInfo.LimitDialog.loginMethodRequired'),\n                trigger: 'change',\n              },\n            ]\"\n            prop=\"default_value\"\n          >\n            <el-radio-group\n              v-model=\"form.default_value\"\n              class=\"radio-group\"\n              style=\"margin-left: 10px\"\n            >\n              <el-radio\n                v-for=\"method in loginMethods\"\n                :key=\"method.value\"\n                :label=\"method.value\"\n                class=\"radio-item\"\n              >\n                {{ method.label }}\n              </el-radio>\n            </el-radio-group>\n          </el-form-item>\n\n          <el-form-item\n            :label=\"$t('views.system.display_code')\"\n            :rules=\"[\n              {\n                required: true,\n                message: $t('views.applicationOverview.appInfo.LimitDialog.displayCodeRequired'),\n                trigger: 'change',\n              },\n            ]\"\n            prop=\"max_attempts\"\n          >\n            <el-row :gutter=\"16\" style=\"margin-left: 10px\">\n              <el-col :span=\"24\">\n                <span style=\"font-size: 13px\">\n                  {{ $t('views.system.loginFailed') }}\n                </span>\n                <el-input-number\n                  style=\"margin-left: 8px\"\n                  v-model=\"form.max_attempts\"\n                  :min=\"-1\"\n                  :max=\"10\"\n                  :step=\"1\"\n                  controls-position=\"right\"\n                />\n                <span class=\"ml-8\" style=\"font-size: 13px\">\n                  {{ $t('views.system.loginFailedMessage') }}\n                </span>\n                <span class=\"ml-8 font-small\" style=\"color: #909399\">\n                  ({{ $t('views.system.display_codeTip') }})\n                </span>\n              </el-col>\n\n              <el-col :span=\"24\" style=\"margin-top: 8px\">\n                <span style=\"font-size: 13px\">\n                  {{ $t('views.system.loginFailed') }}\n                </span>\n                <el-input-number\n                  style=\"margin-left: 8px\"\n                  v-model=\"form.failed_attempts\"\n                  :min=\"-1\"\n                  :max=\"10\"\n                  :step=\"1\"\n                  controls-position=\"right\"\n                  @change=\"onFailedAttemptsChange\"\n                />\n                <span style=\"margin-left: 8px; font-size: 13px\">\n                  {{ $t('views.system.failedTip') }}\n                </span>\n                <el-input-number\n                  style=\"margin-left: 8px\"\n                  v-model=\"form.lock_time\"\n                  :min=\"1\"\n                  :step=\"1\"\n                  controls-position=\"right\"\n                />\n                <span style=\"margin-left: 8px; font-size: 13px\">\n                  {{ $t('views.system.minute') }}\n                </span>\n              </el-col>\n            </el-row>\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('views.system.third_party_user_default_role')\"\n            :rules=\"[\n              {\n                required: true,\n                message: $t('views.system.thirdPartyUserDefaultRoleRequired'),\n                trigger: 'change',\n              },\n            ]\"\n          >\n            <el-row :gutter=\"16\" style=\"margin-left: 10px\">\n              <el-col :span=\"24\">\n                <div class=\"flex\">\n                  <span\n                    style=\"font-size: 13px; white-space: nowrap; width: 50px\"\n                    class=\"text-right mr-8\"\n                  >\n                    {{ $t('views.role.member.role') }}\n                  </span>\n                  <el-select\n                    filterable\n                    clearable\n                    v-model=\"form.role_id\"\n                    :placeholder=\"`${$t('common.selectPlaceholder')}${$t('views.role.member.role')}`\"\n                    @change=\"handleRoleChange\"\n                    class=\"w-240\"\n                  >\n                    <el-option\n                      v-for=\"role in roleOptions\"\n                      :key=\"role.id\"\n                      :label=\"role.name\"\n                      :value=\"role.id\"\n                    />\n                  </el-select>\n                </div>\n              </el-col>\n              <el-col :span=\"24\" v-if=\"user.isEE() && showWorkspaceSelector\" class=\"mt-16\">\n                <div class=\"flex\">\n                  <span\n                    style=\"font-size: 13px; white-space: nowrap; width: 50px\"\n                    class=\"text-right mr-8\"\n                  >\n                    {{ $t('views.role.member.workspace') }}\n                  </span>\n                  <el-select\n                    filterable\n                    clearable\n                    v-model=\"form.workspace_id\"\n                    :placeholder=\"`${$t('common.selectPlaceholder')}${$t('views.role.member.workspace')}`\"\n                    class=\"w-240\"\n                  >\n                    <el-option\n                      v-for=\"workspace in workspaceOptions\"\n                      :key=\"workspace.id\"\n                      :label=\"workspace.name\"\n                      :value=\"workspace.id\"\n                    />\n                  </el-select>\n                </div>\n              </el-col>\n              <el-col\n                :span=\"24\"\n                v-if=\"(user.isEE() || user.isPE()) && showPermissionSelector\"\n                class=\"mt-16\"\n              >\n                <div class=\"flex\">\n                  <span\n                    style=\"font-size: 13px; white-space: nowrap; width: 50px\"\n                    class=\"text-right mr-8\"\n                  >\n                    {{ $t('views.system.resourceAuthorization.title') }}\n                  </span>\n                  <el-select\n                    filterable\n                    clearable\n                    v-model=\"form.permission\"\n                    :placeholder=\"`${$t('common.selectPlaceholder')}${$t('views.system.resourceAuthorization.title')}`\"\n                    class=\"w-240\"\n                  >\n                    <el-option\n                      v-for=\"permission in permissionOptions\"\n                      :key=\"permission.value\"\n                      :label=\"permission.label\"\n                      :value=\"permission.value\"\n                    />\n                  </el-select>\n                </div>\n              </el-col>\n            </el-row>\n          </el-form-item>\n        </el-form>\n        <div style=\"margin-top: 16px\">\n          <span\n            v-hasPermission=\"\n              new ComplexPermission([RoleConst.ADMIN], [PermissionConst.LOGIN_AUTH_EDIT], [], 'OR')\n            \"\n            class=\"mr-12\"\n          >\n            <!-- 直接调用 submit，不传参 -->\n            <el-button @click=\"submit\" type=\"primary\" :disabled=\"loading\">\n              {{ $t('common.save') }}\n            </el-button>\n          </span>\n        </div>\n      </div>\n    </el-scrollbar>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport {computed, onMounted, ref, watch} from 'vue'\nimport {ComplexPermission} from '@/utils/permission/type'\nimport {EditionConst, PermissionConst, RoleConst} from '@/utils/permission/data'\nimport type {FormInstance} from 'element-plus'\nimport {t} from '@/locales'\nimport authApi from '@/api/system-settings/auth-setting.ts'\nimport {MsgSuccess} from '@/utils/message.ts'\nimport WorkspaceApi from '@/api/workspace/workspace.ts'\nimport useStore from '@/stores'\nimport {AuthorizationEnum} from '@/enums/system.ts'\nimport {hasPermission} from '@/utils/permission'\n\nconst loginMethods = ref<Array<{ label: string; value: string }>>([])\nconst systemLoginMethods = ref<Array<{ label: string; value: string }>>([])\nconst loading = ref(false)\n// 明确允许 null，避免未挂载时访问出错\nconst authFormRef = ref<FormInstance | null>(null)\n\nconst form = ref<any>({\n  default_value: 'LOCAL',\n  max_attempts: 1,\n  failed_attempts: 5,\n  lock_time: 10,\n  role_id: 'USER',\n  workspace_id: 'default',\n  permission: 'NOT_AUTH',\n  login_methods: ['LOCAL'],\n})\n\nconst normalizeInputValue = (val: number | null): number => {\n  // 若输入为空或无法转换为有效数字，默认设为 1\n  let normalizedVal = typeof val === 'number' ? Math.trunc(val) : NaN\n  if (!Number.isFinite(normalizedVal)) {\n    normalizedVal = 1\n  }\n\n  if (normalizedVal === 0) {\n    normalizedVal = 1\n  } else if (normalizedVal < -1) {\n    normalizedVal = -1\n  }\n\n  return normalizedVal\n}\n\nconst onFailedAttemptsChange = (val: number | null) => {\n  form.value.failed_attempts = normalizeInputValue(val)\n}\n\nconst onMaxAttemptsChange = (val: number | null) => {\n  form.value.max_attempts = normalizeInputValue(val)\n}\n\n// 提交：使用 authFormRef.value.validate() 的 Promise 风格，并保证 loading 在 finally 中恢复\nconst submit = async () => {\n  const formRef = authFormRef.value\n  if (!formRef) return\n  try {\n    await formRef.validate()\n    loading.value = true\n    const params = {\n      default_value: form.value.default_value,\n      max_attempts: form.value.max_attempts,\n      failed_attempts: form.value.failed_attempts,\n      lock_time: form.value.lock_time,\n      role_id: form.value.role_id,\n      workspace_id: form.value.workspace_id,\n      permission: form.value.permission,\n      login_methods: form.value.login_methods,\n    }\n    await authApi.putLoginSetting(params)\n    MsgSuccess(t('common.saveSuccess'))\n  } catch (err) {\n    // 验证或请求失败：按需处理，避免未捕获异常\n    // console.error(err);\n  } finally {\n    loading.value = false\n  }\n}\n\nconst roleOptions = ref<Array<{ id: string; name: string; type?: string }>>([])\nconst workspaceOptions = ref<Array<{ id: string; name: string }>>([])\nconst {user} = useStore()\nconst selectedRoleType = ref<string>('') // 存储选中角色类型，用于控制 workspace 显示\nconst showWorkspaceSelector = computed(() => selectedRoleType.value !== 'ADMIN')\nconst showPermissionSelector = computed(() => selectedRoleType.value === 'USER')\nconst permissionOptions = computed(() => {\n  const baseOptions = [\n    {\n      label: t('views.system.resourceAuthorization.setting.check'),\n      value: AuthorizationEnum.VIEW,\n      desc: t('views.system.resourceAuthorization.setting.checkDesc'),\n    },\n    {\n      label: t('views.system.resourceAuthorization.setting.management'),\n      value: AuthorizationEnum.MANAGE,\n      desc: t('views.system.resourceAuthorization.setting.managementDesc'),\n    },\n    {\n      label: t('views.system.resourceAuthorization.setting.notAuthorized'),\n      value: AuthorizationEnum.NOT_AUTH,\n      desc: '',\n    },\n  ]\n\n  if (hasPermission([EditionConst.IS_EE, EditionConst.IS_PE], 'OR')) {\n    baseOptions.splice(2, 0, {\n      label: t('views.system.resourceAuthorization.setting.role'),\n      value: AuthorizationEnum.ROLE,\n      desc: t('views.system.resourceAuthorization.setting.roleDesc'),\n    })\n  }\n\n  return baseOptions\n})\n// 当角色变更时更新 selectedRoleType\nconst handleRoleChange = (roleId: string) => {\n  const selectedRole = roleOptions.value.find((role) => role.id === roleId)\n  selectedRoleType.value = selectedRole?.type || ''\n  if (form.value.workspace_id === 'None' && showWorkspaceSelector) {\n    form.value.workspace_id = 'default'\n  }\n}\nconst handleLoginMethodsChange = (values: string[]) => {\n  // 根据选中的登录方式过滤 systemLoginMethods\n  loginMethods.value = systemLoginMethods.value.filter(method =>\n    values.includes(method.value)\n  )\n\n  // 如果当前默认登录方式不在选中的范围内，则重置为第一个选中的方式\n  if (values.length > 0 && !values.includes(form.value.default_value)) {\n    form.value.default_value = values[0]\n  }\n\n  // 如果没有任何选中的登录方式，清空默认登录方式\n  if (values.length === 0) {\n    form.value.default_value = ''\n    // 重新触发验证\n    setTimeout(() => {\n      authFormRef.value?.validateField('login_methods')\n    }, 0)\n  }\n}\n\nonMounted(async () => {\n  loading.value = true\n  try {\n    const isEE = typeof user?.isEE === 'function' ? user.isEE() : false\n\n    // 并行请求：角色列表 + 登录设置；若为 EE 同时请求 workspace 列表\n    const roleP = WorkspaceApi.getWorkspaceRoleList()\n      .then((r) => r)\n      .catch(() => ({data: []}))\n    const settingP = authApi\n      .getLoginSetting()\n      .then((r) => r)\n      .catch(() => ({data: {}}))\n    const tasks: Promise<any>[] = [roleP, settingP]\n    if (isEE) {\n      tasks.push(\n        WorkspaceApi.getWorkspaceList()\n          .then((r) => r)\n          .catch(() => ({data: []})),\n      )\n    }\n\n    const results = await Promise.all(tasks)\n    const roleRes = results[0] ?? {data: []}\n    const settingRes = results[1] ?? {data: {}}\n    const workspaceRes = isEE ? (results[2] ?? {data: []}) : null\n\n    // 处理角色列表（尽早回显）\n    const rolesData = Array.isArray(roleRes?.data) ? roleRes.data : []\n    roleOptions.value = rolesData.map((item: any) => ({\n      id: item.id,\n      name: item.name,\n      type: item.type,\n    }))\n\n    // 处理 setting（合并默认值，避免访问未定义）\n    const data = settingRes?.data ?? {}\n    form.value = {\n      ...form.value,\n      ...data,\n      failed_attempts: data.failed_attempts ?? form.value.failed_attempts ?? 5,\n      lock_time: data.lock_time ?? form.value.lock_time ?? 10,\n      role_id: data.role_id ?? form.value.role_id ?? 'USER',\n      workspace_id: data.workspace_id ?? form.value.workspace_id ?? 'default',\n      permission: data.permission ?? form.value.permission ?? 'NOT_AUTH',\n    }\n    loginMethods.value = Array.isArray(data.auth_types) ? data.auth_types : []\n    systemLoginMethods.value = Array.isArray(data.system_options) ? data.system_options : []\n\n    // 处理 workspace 列表（如果需要）\n    if (isEE && workspaceRes) {\n      const wks = Array.isArray(workspaceRes.data) ? workspaceRes.data : []\n      workspaceOptions.value = wks.map((item: any) => ({id: item.id, name: item.name}))\n    }\n\n    // 初始化 selectedRoleType（基于当前回显的 role_id 与已加载的 roleOptions）\n    const initRole = roleOptions.value.find((r) => r.id === form.value.role_id)\n    selectedRoleType.value = initRole?.type || ''\n  } catch (e) {\n    // overall error, 保持默认回显\n    // console.error(e);\n  } finally {\n    loading.value = false\n  }\n})\n</script>\n\n<style scoped>\n.radio-group {\n  display: flex;\n  flex-wrap: wrap;\n  flex-direction: row;\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-setting/authentication/index.vue",
    "content": "<template>\n  <div class=\"authentication-setting p-16-24\">\n    <el-breadcrumb separator-icon=\"ArrowRight\" class=\"mb-16\">\n      <el-breadcrumb-item>{{ t('views.system.subTitle') }}</el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <h5 class=\"ml-4 color-text-primary\">{{ $t('views.system.authentication.title') }}</h5>\n      </el-breadcrumb-item>\n    </el-breadcrumb>\n    <el-tabs v-model=\"activeName\" class=\"mt-4\">\n      <template v-for=\"(item, index) in tabList\" :key=\"index\">\n        <el-tab-pane :label=\"item.label\" :name=\"item.name\">\n          <component :is=\"item.component\" />\n        </el-tab-pane>\n      </template>\n    </el-tabs>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted } from 'vue'\nimport { useRouter } from 'vue-router'\nimport LDAP from './component/LDAP.vue'\nimport CAS from './component/CAS.vue'\nimport OIDC from './component/OIDC.vue'\nimport SCAN from './component/SCAN.vue'\nimport OAuth2 from './component/OAuth2.vue'\nimport Saml2 from \"./component/Saml2.vue\";\nimport Setting from './component/Setting.vue'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\n\nconst { user } = useStore()\nconst router = useRouter()\n\nconst activeName = ref('SETTING')\nconst tabList = [\n  {\n    label: t('views.system.setting'),\n    name: \"SETTING\",\n    component: Setting,\n  },\n  {\n    label: t('views.system.authentication.ldap.title'),\n    name: 'LDAP',\n    component: LDAP,\n  },\n  {\n    label: t('views.system.authentication.cas.title'),\n    name: 'CAS',\n    component: CAS,\n  },\n  {\n    label: t('views.system.authentication.oidc.title'),\n    name: 'OIDC',\n    component: OIDC,\n  },\n  {\n    label: t('views.system.authentication.oauth2.title'),\n    name: 'OAuth2',\n    component: OAuth2,\n  },\n  {\n    label: t('views.system.authentication.saml2.title'),\n    name: 'SAML2',\n    component: Saml2,\n  },\n  {\n    label: t('views.system.authentication.scanTheQRCode.title'),\n    name: 'SCAN',\n    component: SCAN,\n  },\n]\n\nonMounted(() => {\n  if (user.isExpire()) {\n    router.push({ path: `/application` })\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n.authentication-setting__main {\n  background-color: var(--app-view-bg-color);\n  box-sizing: border-box;\n  min-width: 700px;\n  height: calc(100vh - var(--app-header-height) - var(--app-view-padding) * 2 - 70px);\n  box-sizing: border-box;\n  :deep(.form-container) {\n    width: 70%;\n    margin: 0 auto;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-setting/email/index.vue",
    "content": "<template>\n  <div class=\"email-setting p-16-24\">\n    <el-breadcrumb separator-icon=\"ArrowRight\" class=\"mb-16\">\n      <el-breadcrumb-item>{{ t('views.system.subTitle') }}</el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <h5 class=\"ml-4 color-text-primary\">{{ $t('views.system.email.title') }}</h5>\n      </el-breadcrumb-item>\n    </el-breadcrumb>\n    <el-card style=\"--el-card-padding: 16px\" v-loading=\"loading\">\n      <el-scrollbar>\n        <div class=\"email-setting__main p-16\">\n          <el-form\n            ref=\"emailFormRef\"\n            :rules=\"rules\"\n            :model=\"form\"\n            label-position=\"top\"\n            require-asterisk-position=\"right\"\n          >\n            <el-form-item :label=\"$t('views.system.email.smtpHost')\" prop=\"email_host\">\n              <el-input\n                v-model=\"form.email_host\"\n                :placeholder=\"$t('views.system.email.smtpHostPlaceholder')\"\n              />\n            </el-form-item>\n            <el-form-item :label=\"$t('views.system.email.smtpPort')\" prop=\"email_port\">\n              <el-input\n                v-model=\"form.email_port\"\n                :placeholder=\"$t('views.system.email.smtpPortPlaceholder')\"\n              />\n            </el-form-item>\n            <el-form-item :label=\"$t('views.system.email.smtpUser')\" prop=\"email_host_user\">\n              <el-input\n                v-model=\"form.email_host_user\"\n                :placeholder=\"$t('views.system.email.smtpUserPlaceholder')\"\n              />\n            </el-form-item>\n            <el-form-item :label=\"$t('views.system.email.sendEmail')\" prop=\"from_email\">\n              <el-input\n                v-model=\"form.from_email\"\n                :placeholder=\"$t('views.system.email.sendEmailPlaceholder')\"\n              />\n            </el-form-item>\n            <el-form-item :label=\"$t('views.system.password')\" prop=\"email_host_password\">\n              <el-input\n                v-model=\"form.email_host_password\"\n                :placeholder=\"$t('views.system.email.smtpPasswordPlaceholder')\"\n                show-password\n              />\n            </el-form-item>\n            <el-form-item>\n              <el-checkbox v-model=\"form.email_use_ssl\"\n                >{{ $t('views.system.email.enableSSL') }}\n              </el-checkbox>\n            </el-form-item>\n            <el-form-item>\n              <el-checkbox v-model=\"form.email_use_tls\"\n                >{{ $t('views.system.email.enableTLS') }}\n              </el-checkbox>\n            </el-form-item>\n            <span\n              v-hasPermission=\"\n                new ComplexPermission(\n                  [RoleConst.ADMIN],\n                  [PermissionConst.EMAIL_SETTING_EDIT],\n                  [],\n                  'OR',\n                )\n              \"\n              class=\"mr-12\"\n            >\n              <el-button @click=\"submit(emailFormRef)\" type=\"primary\" :disabled=\"loading\">\n                {{ $t('common.save') }}\n              </el-button>\n            </span>\n            <span>\n              <el-button @click=\"submit(emailFormRef, 'test')\" :disabled=\"loading\">\n                {{ $t('views.system.test') }}\n              </el-button>\n            </span>\n          </el-form>\n        </div>\n      </el-scrollbar>\n    </el-card>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch, onMounted } from 'vue'\nimport emailApi from '@/api/system-settings/email-setting'\nimport type { FormInstance, FormRules } from 'element-plus'\n\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\nconst form = ref<any>({\n  email_host: '',\n  email_port: '',\n  email_host_user: '',\n  email_host_password: '',\n  email_use_tls: false,\n  email_use_ssl: false,\n  from_email: '',\n})\n\nconst emailFormRef = ref()\n\nconst loading = ref(false)\n\nconst rules = reactive<FormRules<any>>({\n  email_host: [\n    { required: true, message: t('views.system.email.smtpHostPlaceholder'), trigger: 'blur' },\n  ],\n  email_port: [\n    { required: true, message: t('views.system.email.smtpPortPlaceholder'), trigger: 'blur' },\n  ],\n  email_host_user: [\n    { required: true, message: t('views.system.email.smtpUserPlaceholder'), trigger: 'blur' },\n  ],\n  email_host_password: [\n    { required: true, message: t('views.system.email.smtpPasswordPlaceholder'), trigger: 'blur' },\n  ],\n  from_email: [\n    { required: true, message: t('views.system.email.sendEmailPlaceholder'), trigger: 'blur' },\n  ],\n})\n\nconst submit = async (formEl: FormInstance | undefined, test?: string) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      if (test) {\n        emailApi.postTestEmail(form.value, loading).then((res) => {\n          MsgSuccess(t('views.system.testSuccess'))\n        })\n      } else {\n        emailApi.putEmailSetting(form.value, loading).then((res) => {\n          MsgSuccess(t('common.saveSuccess'))\n        })\n      }\n    }\n  })\n}\n\nfunction getDetail() {\n  emailApi.getEmailSetting(loading).then((res: any) => {\n    if (res.data && JSON.stringify(res.data) !== '{}') {\n      form.value = res.data\n    }\n  })\n}\n\nonMounted(() => {\n  getDetail()\n})\n</script>\n<style lang=\"scss\" scoped>\n.email-setting {\n  &__main {\n    width: 70%;\n    margin: 0 auto;\n    height: calc(100vh - 200px);\n  }\n\n  :deep(.el-checkbox__label) {\n    font-weight: 400;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-setting/theme/LoginPreview.vue",
    "content": "<template>\n  <div class=\"login-preview mr-16 white-bg\">\n    <div class=\"header\">\n      <div class=\"tag flex-between\">\n        <div class=\"flex align-center\">\n          <img v-if=\"props.data.icon\" :src=\"fileURL\" alt=\"\" height=\"20px\" class=\"mr-8\" />\n          <img v-else src=\"@/assets/logo/logo.svg\" height=\"24px\" class=\"mr-8\" />\n          <span class=\"ellipsis\">{{ data.title }}</span>\n        </div>\n        <el-icon><Close /></el-icon>\n      </div>\n    </div>\n    <login-layout style=\"height: 530px\" :lang=\"false\">\n      <LoginContainer :subTitle=\"data.slogan\" class=\"login-container\">\n        <div class=\"mask\"></div>\n        <h2 class=\"mb-24\">{{ $t('views.login.title') }}</h2>\n        <el-form class=\"login-form\">\n          <div class=\"mb-24\">\n            <el-form-item>\n              <el-input\n                size=\"large\"\n                class=\"input-item\"\n                :placeholder=\"$t('views.login.loginForm.username.placeholder')\"\n              >\n              </el-input>\n            </el-form-item>\n          </div>\n          <div class=\"mb-24\">\n            <el-form-item>\n              <el-input\n                type=\"password\"\n                size=\"large\"\n                class=\"input-item\"\n                :placeholder=\"$t('views.login.loginForm.password.placeholder')\"\n                show-password\n              >\n              </el-input>\n            </el-form-item>\n          </div>\n        </el-form>\n        <el-button size=\"large\" type=\"primary\" class=\"w-full\">{{\n          $t('views.login.buttons.login')\n        }}</el-button>\n        <div class=\"operate-container flex-between mt-12\">\n          <el-button class=\"forgot-password\" link type=\"primary\">\n            {{ $t('views.login.forgotPassword') }}?\n          </el-button>\n        </div>\n      </LoginContainer>\n    </login-layout>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed } from 'vue'\nimport LoginLayout from \"@/layout/login-layout/LoginLayout.vue\";\nimport LoginContainer from \"@/layout/login-layout/LoginContainer.vue\";\n\nconst props = defineProps({\n  data: {\n    type: Object,\n    default: null\n  }\n})\n\nconst fileURL = computed(() => {\n  if (props.data.icon) {\n    if (typeof props.data.icon === 'string') {\n      return props.data.icon\n    } else {\n      return URL.createObjectURL(props.data.icon)\n    }\n  } else {\n    return ''\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.login-preview {\n  border-radius: 4px;\n  transform-origin: center;\n  .login-container {\n    transform: translate(0, 0) scale(0.8);\n    position: relative;\n    .mask {\n      position: absolute;\n      left: 0;\n      top: 0;\n      height: 100%;\n      width: 100%;\n      z-index: 3;\n    }\n  }\n\n  .header {\n    background: #eceeef;\n    height: 38px;\n    border-radius: 4px 4px 0 0;\n    position: relative;\n    .tag {\n      width: 180px;\n      height: 30px;\n      background: #ffffff;\n      box-shadow: var(-app-text-color-light-1);\n      position: absolute;\n      bottom: 0;\n      left: 8px;\n      border-radius: 4px 4px 0 0;\n      padding: 0 8px;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-setting/theme/index.vue",
    "content": "<template>\n  <div class=\"theme-setting p-16-24\" v-loading=\"loading\">\n    <el-breadcrumb separator-icon=\"ArrowRight\" class=\"mb-16\">\n      <el-breadcrumb-item>{{ t('views.system.subTitle') }}</el-breadcrumb-item>\n      <el-breadcrumb-item>\n        <h5 class=\"ml-4 color-text-primary\">{{ $t('theme.title') }}</h5>\n      </el-breadcrumb-item>\n    </el-breadcrumb>\n    <el-scrollbar>\n      <el-card style=\"--el-card-padding: 16px\">\n        <h5 class=\"mb-16\">{{ $t('theme.platformDisplayTheme') }}</h5>\n        <el-radio-group\n          v-model=\"themeRadio\"\n          class=\"app-radio-button-group\"\n          @change=\"changeThemeHandle\"\n        >\n          <template v-for=\"(item, index) in themeList\" :key=\"index\">\n            <el-radio-button :label=\"item.label\" :value=\"item.value\" />\n          </template>\n          <el-radio-button :label=\"$t('common.custom')\" value=\"custom\" />\n        </el-radio-group>\n        <div v-if=\"themeRadio === 'custom'\">\n          <h5 class=\"mt-16 mb-8\">{{ $t('theme.customTheme') }}</h5>\n          <el-color-picker v-model=\"customColor\" @change=\"customColorHandle\" />\n        </div>\n      </el-card>\n\n      <el-card style=\"--el-card-padding: 16px\" class=\"mt-16\">\n        <h5 class=\"mb-16\">{{ $t('theme.platformLoginSettings') }}</h5>\n        <el-card shadow=\"never\" class=\"layout-bg\">\n          <div class=\"flex-between\">\n            <h5 class=\"mb-16\">{{ $t('theme.pagePreview') }}</h5>\n            <el-button type=\"primary\" link @click=\"resetForm('login')\">\n              {{ $t('theme.restoreDefaults') }}\n            </el-button>\n          </div>\n          <el-scrollbar>\n            <div class=\"theme-preview\">\n              <el-row :gutter=\"8\">\n                <el-col :span=\"16\">\n                  <LoginPreview :data=\"themeForm\" />\n                </el-col>\n                <el-col :span=\"8\">\n                  <div class=\"theme-form\">\n                    <el-card shadow=\"never\" class=\"mb-8\">\n                      <div class=\"flex-between mb-8\">\n                        <span class=\"lighter\">{{ $t('theme.websiteLogo') }}</span>\n                        <el-upload\n                          ref=\"uploadRef\"\n                          action=\"#\"\n                          :auto-upload=\"false\"\n                          :show-file-list=\"false\"\n                          accept=\"image/jpeg, image/png, image/gif\"\n                          :on-change=\"\n                            (file: any, fileList: any) => onChange(file, fileList, 'icon')\n                          \"\n                        >\n                          <el-button size=\"small\">\n                            {{ $t('theme.replacePicture') }}\n                          </el-button>\n                        </el-upload>\n                      </div>\n                      <el-text type=\"info\" size=\"small\">{{ $t('theme.websiteLogoTip') }} </el-text>\n                    </el-card>\n                    <el-card shadow=\"never\" class=\"mb-8\">\n                      <div class=\"flex-between mb-8\">\n                        <span class=\"lighter\"> {{ $t('theme.loginLogo') }}</span>\n                        <el-upload\n                          ref=\"uploadRef\"\n                          action=\"#\"\n                          :auto-upload=\"false\"\n                          :show-file-list=\"false\"\n                          accept=\"image/jpeg, image/png, image/gif\"\n                          :on-change=\"\n                            (file: any, fileList: any) => onChange(file, fileList, 'loginLogo')\n                          \"\n                        >\n                          <el-button size=\"small\">\n                            {{ $t('theme.replacePicture') }}\n                          </el-button>\n                        </el-upload>\n                      </div>\n                      <el-text type=\"info\" size=\"small\">{{ $t('theme.loginLogoTip') }} </el-text>\n                    </el-card>\n                    <el-card shadow=\"never\" class=\"mb-8\">\n                      <div class=\"flex-between mb-8\">\n                        <span class=\"lighter\">{{ $t('theme.loginBackground') }}</span>\n                        <el-upload\n                          ref=\"uploadRef\"\n                          action=\"#\"\n                          :auto-upload=\"false\"\n                          :show-file-list=\"false\"\n                          accept=\"image/jpeg, image/png, image/gif\"\n                          :on-change=\"\n                            (file: any, fileList: any) => onChange(file, fileList, 'loginImage')\n                          \"\n                        >\n                          <el-button size=\"small\">\n                            {{ $t('theme.replacePicture') }}\n                          </el-button>\n                        </el-upload>\n                      </div>\n                      <el-text type=\"info\" size=\"small\">\n                        {{ $t('theme.loginBackgroundTip') }}\n                      </el-text>\n                    </el-card>\n\n                    <el-form\n                      ref=\"themeFormRef\"\n                      :model=\"themeForm\"\n                      label-position=\"top\"\n                      require-asterisk-position=\"right\"\n                      :rules=\"rules\"\n                      @submit.prevent\n                    >\n                      <el-form-item :label=\"$t('theme.websiteName')\" prop=\"title\">\n                        <el-input\n                          v-model=\"themeForm.title\"\n                          :placeholder=\"$t('theme.websiteNamePlaceholder')\"\n                          show-word-limit\n                          maxlength=\"128\"\n                        >\n                        </el-input>\n                        <el-text type=\"info\">{{ $t('theme.websiteNameTip') }} </el-text>\n                      </el-form-item>\n                      <el-form-item :label=\"$t('theme.websiteSlogan')\" prop=\"slogan\">\n                        <el-input\n                          v-model=\"themeForm.slogan\"\n                          :placeholder=\"$t('theme.websiteSloganPlaceholder')\"\n                          maxlength=\"64\"\n                          show-word-limit\n                        >\n                        </el-input>\n                        <el-text type=\"info\">{{ $t('theme.websiteSloganTip') }} </el-text>\n                      </el-form-item>\n                    </el-form>\n                  </div>\n                </el-col>\n              </el-row>\n            </div>\n          </el-scrollbar>\n          <div class=\"mt-16\">\n            <el-text type=\"info\">{{ $t('theme.logoDefaultTip') }}</el-text>\n          </div>\n        </el-card>\n      </el-card>\n      <el-card style=\"--el-card-padding: 16px\" class=\"mt-16\">\n        <h5 class=\"mb-16\">{{ $t('theme.platformSetting') }}</h5>\n        <el-card shadow=\"never\" class=\"layout-bg\">\n          <div class=\"flex-between\">\n            <h5 class=\"mb-16\">{{ $t('theme.pagePreview') }}</h5>\n            <el-button type=\"primary\" link @click=\"resetForm('platform')\">\n              {{ $t('theme.restoreDefaults') }}\n            </el-button>\n          </div>\n          <el-scrollbar>\n            <div class=\"theme-preview\">\n              <el-row :gutter=\"8\">\n                <el-col :span=\"16\">\n                  <div class=\"theme-platform mr-16\">\n                    <div\n                      class=\"theme-platform-header border-b flex-between\"\n                      :class=\"!isDefaultTheme ? 'custom-header' : ''\"\n                    >\n                      <div class=\"flex-center h-full\">\n                        <div class=\"app-title-container cursor\">\n                          <div class=\"logo flex-center\">\n                            <LogoFull height=\"25px\" />\n                          </div>\n                        </div>\n                      </div>\n                      <div class=\"flex-center\">\n                        <AppIcon\n                          iconName=\"app-github\"\n                          class=\"cursor color-secondary mr-8 ml-8\"\n                          style=\"font-size: 20px\"\n                          v-if=\"themeForm.showProject\"\n                        ></AppIcon>\n                        <AppIcon\n                          iconName=\"app-user-manual\"\n                          class=\"cursor color-secondary mr-8 ml-8\"\n                          style=\"font-size: 20px\"\n                          v-if=\"themeForm.showUserManual\"\n                        ></AppIcon>\n                        <AppIcon\n                          iconName=\"app-help\"\n                          class=\"cursor color-secondary ml-8\"\n                          style=\"font-size: 20px\"\n                          v-if=\"themeForm.showForum\"\n                        ></AppIcon>\n                      </div>\n                    </div>\n                  </div>\n                </el-col>\n                <el-col :span=\"8\">\n                  <div class=\"theme-form\">\n                    <div>\n                      <el-checkbox\n                        v-model=\"themeForm.showUserManual\"\n                        :label=\"$t('theme.showUserManual')\"\n                      />\n                      <div class=\"ml-24\">\n                        <el-input\n                          v-model=\"themeForm.userManualUrl\"\n                          :placeholder=\"$t('theme.urlPlaceholder')\"\n                          show-word-limit\n                          maxlength=\"128\"\n                        />\n                      </div>\n                    </div>\n                    <div class=\"mt-4\">\n                      <el-checkbox v-model=\"themeForm.showForum\" :label=\"$t('theme.showForum')\" />\n                      <div class=\"ml-24\">\n                        <el-input\n                          v-model=\"themeForm.forumUrl\"\n                          :placeholder=\"$t('theme.urlPlaceholder')\"\n                          show-word-limit\n                          maxlength=\"128\"\n                        />\n                      </div>\n                    </div>\n                    <div class=\"mt-4\">\n                      <el-checkbox\n                        v-model=\"themeForm.showProject\"\n                        :label=\"$t('theme.showProject')\"\n                      />\n                      <div class=\"ml-24\">\n                        <el-input\n                          v-model=\"themeForm.projectUrl\"\n                          :placeholder=\"$t('theme.urlPlaceholder')\"\n                          show-word-limit\n                          maxlength=\"128\"\n                        />\n                      </div>\n                    </div>\n                  </div>\n                </el-col>\n              </el-row>\n            </div>\n          </el-scrollbar>\n          <div class=\"mt-16\">\n            <el-text type=\"info\">{{ $t('theme.defaultTip') }}</el-text>\n          </div>\n        </el-card>\n      </el-card>\n    </el-scrollbar>\n    <div class=\"theme-setting__operate w-full p-16-24\">\n      <el-button @click=\"resetTheme\">{{ $t('theme.abandonUpdate') }}</el-button>\n      <el-button\n        type=\"primary\"\n        @click=\"updateTheme(themeFormRef)\"\n        v-hasPermission=\"\n          new ComplexPermission(\n            [RoleConst.ADMIN],\n            [PermissionConst.APPEARANCE_SETTINGS_EDIT],\n            [],\n            'OR',\n          )\n        \"\n      >\n        {{ $t('theme.saveAndApply') }}\n      </el-button>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ref, reactive, onMounted, computed } from 'vue'\nimport { useRouter, onBeforeRouteLeave } from 'vue-router'\nimport type { FormInstance, FormRules, UploadFiles } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport LoginPreview from './LoginPreview.vue'\nimport { themeList, defaultSetting, defaultPlatformSetting } from '@/utils/theme'\nimport ThemeApi from '@/api/system-settings/theme'\nimport { MsgSuccess, MsgError } from '@/utils/message'\nimport useStore from '@/stores'\nimport { t } from '@/locales'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { ComplexPermission } from '@/utils/permission/type'\n\nconst { theme } = useStore()\nconst router = useRouter()\n\nonBeforeRouteLeave((to, from) => {\n  theme.setTheme(cloneTheme.value)\n})\n\nconst themeInfo = computed(() => theme.themeInfo)\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\n\nconst themeFormRef = ref<FormInstance>()\nconst loading = ref(false)\nconst cloneTheme = ref(null)\nconst themeForm = ref<any>({\n  theme: '',\n  icon: '',\n  loginLogo: '',\n  loginImage: '',\n  title: 'MaxKB',\n  slogan: t('theme.defaultSlogan'),\n  ...defaultPlatformSetting,\n})\nconst themeRadio = ref('')\nconst customColor = ref('')\n\nconst rules = reactive<FormRules>({\n  title: [{ required: true, message: t('theme.websiteNamePlaceholder'), trigger: 'blur' }],\n  slogan: [{ required: true, message: t('theme.websiteSloganPlaceholder'), trigger: 'blur' }],\n})\n\nconst onChange = (file: any, fileList: UploadFiles, attr: string) => {\n  const isLimit = file?.size / 1024 / 1024 < 10\n  if (!isLimit) {\n    MsgError(t('theme.fileMessageError'))\n    return false\n  } else {\n    themeForm.value[attr] = file.raw\n  }\n  theme.setTheme(themeForm.value)\n}\n\nfunction changeThemeHandle(val: string) {\n  if (val !== 'custom') {\n    themeForm.value.theme = val\n    theme.setTheme(themeForm.value)\n  }\n}\n\nfunction customColorHandle(val: string) {\n  themeForm.value.theme = val\n  theme.setTheme(themeForm.value)\n}\n\nfunction resetTheme() {\n  theme.setTheme(cloneTheme.value)\n  themeForm.value = cloneDeep(themeInfo.value)\n}\n\nfunction resetForm(val: string) {\n  themeForm.value =\n    val === 'login'\n      ? {\n          ...themeForm.value,\n          theme: themeForm.value.theme,\n          ...defaultSetting,\n        }\n      : {\n          ...themeForm.value,\n          theme: themeForm.value.theme,\n          ...defaultPlatformSetting,\n        }\n\n  theme.setTheme(themeForm.value)\n}\n\nconst updateTheme = async (formEl: FormInstance | undefined, test?: string) => {\n  if (!formEl) return\n  await formEl.validate((valid, fields) => {\n    if (valid) {\n      const fd = new FormData()\n      Object.keys(themeForm.value).map((item) => {\n        fd.append(item, themeForm.value[item])\n      })\n      ThemeApi.postThemeInfo(fd, loading).then((res) => {\n        theme.theme()\n        cloneTheme.value = cloneDeep(themeForm.value)\n        MsgSuccess(t('theme.saveSuccess'))\n      })\n    }\n  })\n}\n\nonMounted(() => {\n  // if (user.isExpire()) {\n  //   router.push({path: `/application`})\n  // }\n  if (themeInfo.value) {\n    themeRadio.value = themeList.some((v) => v.value === themeInfo.value.theme)\n      ? themeInfo.value.theme\n      : 'custom'\n    customColor.value = themeInfo.value.theme\n    themeForm.value = cloneDeep(themeInfo.value)\n    cloneTheme.value = cloneDeep(themeInfo.value)\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.theme-setting {\n  height: 100%;\n  display: flex;\n  flex-direction: column;\n  box-sizing: border-box;\n  position: relative;\n  padding-bottom: 64px;\n\n  &__operate {\n    position: absolute;\n    bottom: 0;\n    right: 0;\n    left: 0;\n    background: #ffffff;\n    text-align: right;\n    box-sizing: border-box;\n    box-shadow: 0px -2px 4px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n  }\n\n  .theme-preview {\n    min-width: 1000px;\n  }\n\n  .theme-platform {\n    background: #ffffff;\n    height: 220px;\n\n    .theme-platform-header {\n      padding: 10px 20px;\n      background: var(--app-header-bg-color);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-shared/AuthorizedWorkspaceDialog.vue",
    "content": "<template>\n  <el-dialog modal-class=\"authorized-workspace\" v-model=\"centerDialogVisible\" width=\"840\">\n    <template #header>\n      <h4 class=\"mb-8\">{{ $t('views.shared.authorized_workspace') }}</h4>\n      <el-text class=\"color-secondary lighter\">{{ $t('views.shared.authorized_tip') }}</el-text>\n    </template>\n\n    <p class=\"mb-8 lighter\">{{ $t('common.type') }}</p>\n    <el-radio-group v-model=\"listType\">\n      <el-radio value=\"WHITE_LIST\">{{ $t('views.shared.WHITE_LIST') }}</el-radio>\n      <el-radio value=\"BLACK_LIST\">{{ $t('views.shared.BLACK_LIST') }}</el-radio>\n    </el-radio-group>\n    <p class=\"mb-8 lighter mt-16\">{{ $t('views.shared.select_workspace') }}</p>\n    <div class=\"flex border\" v-loading=\"loading\" style=\"overflow: hidden\">\n      <div class=\"border-r\">\n        <el-input\n          v-model=\"search\"\n          :validate-event=\"false\"\n          :placeholder=\"$t('common.search')\"\n          style=\"width: 364px; padding: 16px 16px 0 16px\"\n          clearable\n        >\n          <template #prefix>\n            <el-icon>\n              <Search />\n            </el-icon>\n          </template>\n        </el-input>\n        <div class=\"mt-8\">\n          <el-checkbox\n            class=\"mb-8\"\n            style=\"margin-left: 16px\"\n            v-model=\"checkAll\"\n            :indeterminate=\"isIndeterminate\"\n            @change=\"handleCheckAllChange\"\n            v-if=\"!search\"\n          >\n            {{ $t('common.allCheck') }}\n          </el-checkbox>\n          <el-scrollbar max-height=\"205\" wrap-class=\"p-16 pt-0\">\n            <el-checkbox-group\n              class=\"checkbox-group-block\"\n              v-model=\"checkedWorkspace\"\n              @change=\"handleCheckedWorkspaceChange\"\n            >\n              <el-checkbox\n                v-for=\"space in workspaceWithKeywords\"\n                :key=\"space.id\"\n                :label=\"space.name\"\n                :value=\"space\"\n              >\n                <div class=\"flex\">\n                  <AppIcon iconName=\"app-workspace\"></AppIcon>\n                  <span class=\"ml-4 ellipsis\" :title=\"space.name\"> {{ space.name }}</span>\n                </div>\n              </el-checkbox>\n            </el-checkbox-group>\n          </el-scrollbar>\n        </div>\n      </div>\n      <div class=\"w-full\">\n        <div class=\"flex-between p-16\">\n          <span class=\"lighter\">\n            {{ $t('common.selected') }}: {{ checkedWorkspace.length }} 个\n          </span>\n\n          <el-button @click=\"clearWorkspaceAll\" link type=\"primary\">\n            {{ $t('common.clear') }}\n          </el-button>\n        </div>\n        <el-scrollbar max-height=\"250\" wrap-class=\"p-16 pt-0\">\n          <template v-for=\"(ele, index) in checkedWorkspace\" :key=\"index\">\n            <div class=\"flex-between\">\n              <div class=\"flex align-center\">\n                <AppIcon iconName=\"app-workspace\"></AppIcon>\n                <span class=\"ml-4 lighter ellipsis\" :title=\"ele.name\">{{ ele.name }}</span>\n              </div>\n              <el-button link>\n                <el-icon @click=\"clearWorkspace(ele)\" :size=\"18\">\n                  <Close />\n                </el-icon>\n              </el-button>\n            </div>\n          </template>\n        </el-scrollbar>\n      </div>\n    </div>\n\n    <template #footer>\n      <el-button @click=\"centerDialogVisible = false\"> {{ $t('common.cancel') }}</el-button>\n      <el-button type=\"primary\" @click=\"handleConfirm\"> {{ $t('common.save') }}</el-button>\n    </template>\n  </el-dialog>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ref, computed } from 'vue'\nimport type { CheckboxValueType } from 'element-plus'\nimport authorizationApi from '@/api/system-shared/authorization'\nimport { loadPermissionApi } from '@/utils/dynamics-api/permission-api.ts'\n\nconst checkAll = ref(false)\nconst isIndeterminate = ref(true)\nconst checkedWorkspace = ref<any[]>([])\nconst workspace = ref<any[]>([])\nconst listType = ref('WHITE_LIST')\nconst search = ref('')\nlet knowledge_id = ''\nlet currentType = 'Knowledge'\nconst loading = ref(false)\nconst centerDialogVisible = ref(false)\n\nconst workspaceWithKeywords = computed(() => {\n  return workspace.value.filter((ele: any) => (ele.name as string).includes(search.value))\n})\nconst handleCheckAllChange = (val: CheckboxValueType) => {\n  checkedWorkspace.value = val ? workspace.value : []\n  isIndeterminate.value = false\n  if (!val) {\n    clearWorkspaceAll()\n  }\n}\nconst handleCheckedWorkspaceChange = (value: CheckboxValueType[]) => {\n  const checkedCount = value.length\n  checkAll.value = checkedCount === workspace.value.length\n  isIndeterminate.value = checkedCount > 0 && checkedCount < workspace.value.length\n}\n\nconst open = async ({ id }: any, type = 'Knowledge') => {\n  knowledge_id = id\n  loading.value = true\n  currentType = type\n  const [authList, systemWorkspaceList] = await Promise.all([\n    authorizationApi[`getSharedAuthorization${type}`](id),\n    loadPermissionApi('workspace').getSystemWorkspaceList(),\n  ])\n  workspace.value = systemWorkspaceList.data as any\n  listType.value = (authList.data || {}).authentication_type || 'WHITE_LIST'\n  const workspace_id_list = (authList.data || {}).workspace_id_list || []\n  checkedWorkspace.value = workspace.value.filter((ele) => workspace_id_list.includes(ele.id))\n  handleCheckedWorkspaceChange(checkedWorkspace.value)\n  loading.value = false\n  centerDialogVisible.value = true\n}\n\nconst handleConfirm = () => {\n  authorizationApi[`postSharedAuthorization${currentType}`](knowledge_id, {\n    workspace_id_list: checkedWorkspace.value.map((ele: any) => ele.id),\n    authentication_type: listType.value,\n  }).then(() => {\n    centerDialogVisible.value = false\n  })\n}\n\nconst clearWorkspace = (val: any) => {\n  checkedWorkspace.value = checkedWorkspace.value.filter((ele: any) => ele.id !== val.id)\n}\n\nconst clearWorkspaceAll = () => {\n  checkedWorkspace.value = []\n  handleCheckedWorkspaceChange([])\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/system-shared/KnowLedgeSharedIndex.vue",
    "content": "<template>\n  <div class=\"tool-shared\">\n    <KnowledgeListContainer>\n      <template #header>\n        <el-breadcrumb separator-icon=\"ArrowRight\">\n          <el-breadcrumb-item>{{ $t('views.shared.shared_resources') }}</el-breadcrumb-item>\n          <el-breadcrumb-item>\n            <h5 class=\"ml-4 color-text-primary\">{{ $t('views.knowledge.title') }}</h5>\n          </el-breadcrumb-item>\n        </el-breadcrumb>\n      </template>\n    </KnowledgeListContainer>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, computed } from 'vue'\n\nimport KnowledgeListContainer from '@/views/knowledge/component/KnowledgeListContainer.vue'\n\nonMounted(() => {})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/system-shared/ModelSharedIndex.vue",
    "content": "<template>\n  <div class=\"shared-model-manage\">\n    <ContentContainer>\n      <template #header>\n        <el-breadcrumb separator-icon=\"ArrowRight\">\n          <el-breadcrumb-item>{{ $t('views.shared.shared_resources') }}</el-breadcrumb-item>\n          <el-breadcrumb-item>\n            <h5 class=\"ml-4 color-text-primary\">{{ $t('views.model.title') }}</h5>\n          </el-breadcrumb-item>\n        </el-breadcrumb>\n      </template>\n      <el-card style=\"--el-card-padding: 0\">\n        <modelListContainer />\n      </el-card>\n    </ContentContainer>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, computed } from 'vue'\nimport modelListContainer from '@/views/model/index.vue'\n\nonMounted(() => {})\n</script>\n\n<style lang=\"scss\" scoped>\n.shared-model-manage {\n  padding: 8px 12px 8px 4px;\n  :deep(.model-list-height) {\n    height: calc(var(--app-main-height) - 70px);\n    padding-right: 0 !important;\n  }\n  :deep(.provider-list) {\n    height: calc(var(--app-main-height) - 70px);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/system-shared/ToolSharedIndex.vue",
    "content": "<template>\n  <div class=\"tool-shared\">\n    <ToolListContainer>\n      <template #header>\n        <el-space wrap>\n          <el-breadcrumb separator-icon=\"ArrowRight\">\n            <el-breadcrumb-item>{{ $t('views.shared.shared_resources') }}</el-breadcrumb-item>\n            <el-breadcrumb-item>\n              <h5 class=\"ml-4 color-text-primary\">{{ $t('views.tool.title') }}</h5>\n            </el-breadcrumb-item>\n          </el-breadcrumb>\n          <el-divider direction=\"vertical\" />\n          <el-radio-group v-model=\"toolType\" @change=\"radioChange\" class=\"app-radio-button-group\">\n            <el-radio-button value=\"\">{{ $t('common.status.all') }}</el-radio-button>\n            <el-radio-button value=\"CUSTOM\">{{ $t('views.tool.title') }}</el-radio-button>\n            <el-radio-button value=\"SKILL\">Skills</el-radio-button>\n            <el-radio-button value=\"MCP\">MCP</el-radio-button>\n            <el-radio-button value=\"DATA_SOURCE\">{{ $t('views.tool.dataSource.title') }}</el-radio-button>\n          </el-radio-group>\n        </el-space>\n      </template>\n    </ToolListContainer>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, computed } from 'vue'\n\nimport ToolListContainer from '@/views/tool/component/ToolListContainer.vue'\n\nimport useStore from '@/stores'\n\nconst { tool } = useStore()\n\nconst toolType = ref('')\n\nfunction radioChange() {\n  tool.setToolType(toolType.value)\n}\n\nonMounted(() => {})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/DataSourceToolFormDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"60%\" :before-close=\"close\">\n    <template #header>\n      <h4>{{ title }}</h4>\n    </template>\n    <div>\n      <h4 class=\"title-decoration-1 mb-16\">\n        {{ $t('views.model.modelForm.title.baseInfo') }}\n      </h4>\n      <el-form\n        ref=\"FormRef\"\n        :model=\"form\"\n        :rules=\"rules\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        v-loading=\"loading\"\n        @submit.prevent\n      >\n        <el-form-item :label=\"$t('common.name')\" prop=\"name\">\n          <div class=\"flex w-full\">\n            <div\n              v-if=\"form.id\"\n              class=\"edit-avatar mr-12\"\n              @mouseenter=\"showEditIcon = true\"\n              @mouseleave=\"showEditIcon = false\"\n            >\n              <el-Avatar\n                v-if=\"isAppIcon(form.icon)\"\n                :id=\"form.id\"\n                shape=\"square\"\n                :size=\"32\"\n                style=\"background: none\"\n              >\n                <img :src=\"String(form.icon)\" alt=\"\" />\n              </el-Avatar>\n              <el-avatar v-else class=\"avatar-purple\" shape=\"square\" :size=\"32\">\n                <img src=\"@/assets/tool/icon_datasource.svg\" style=\"width: 58%\" alt=\"\" />\n              </el-avatar>\n              <el-Avatar\n                v-if=\"showEditIcon\"\n                :id=\"form.id\"\n                shape=\"square\"\n                class=\"edit-mask\"\n                :size=\"32\"\n                @click=\"openEditAvatar\"\n              >\n                <AppIcon iconName=\"app-edit\"></AppIcon>\n              </el-Avatar>\n            </div>\n            <el-avatar v-else class=\"avatar-purple mr-12\" shape=\"square\" :size=\"32\">\n              <img src=\"@/assets/tool/icon_datasource.svg\" style=\"width: 58%\" alt=\"\" />\n            </el-avatar>\n            <el-input\n              v-model=\"form.name\"\n              :placeholder=\"$t('views.tool.form.toolName.placeholder')\"\n              maxlength=\"64\"\n              show-word-limit\n              @blur=\"form.name = form.name?.trim()\"\n            />\n          </div>\n        </el-form-item>\n\n        <el-form-item :label=\"$t('common.desc')\">\n          <el-input\n            v-model=\"form.desc\"\n            type=\"textarea\"\n            :placeholder=\"$t('common.descPlaceholder')\"\n            maxlength=\"128\"\n            show-word-limit\n            :autosize=\"{ minRows: 3 }\"\n            @blur=\"form.desc = form.desc?.trim()\"\n          />\n        </el-form-item>\n      </el-form>\n      <div class=\"flex-between\">\n        <h4 class=\"title-decoration-1 mb-16\">\n          {{ $t('common.param.initParam') }}\n        </h4>\n        <el-button link type=\"primary\" @click=\"openAddInitDialog()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n      <el-table ref=\"initFieldTableRef\" :data=\"form.init_field_list\" class=\"mb-16\">\n        <el-table-column prop=\"field\" :label=\"$t('dynamicsForm.paramForm.field.label')\">\n          <template #default=\"{ row }\">\n            <span :title=\"row.field\" class=\"ellipsis-1\">{{ row.field }}</span>\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('dynamicsForm.paramForm.input_type.label')\">\n          <template #default=\"{ row }\">\n            <el-tag size=\"small\" type=\"info\" class=\"info-tag\">{{\n              input_type_list.find((item) => item.value === row.input_type)?.label\n            }}</el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.required')\">\n          <template #default=\"{ row }\">\n            <div @click.stop>\n              <el-switch disabled size=\"small\" v-model=\"row.required\" />\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n          <template #default=\"{ row, $index }\">\n            <span class=\"mr-4\">\n              <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n                <el-button type=\"primary\" text @click.stop=\"openAddInitDialog(row, $index)\">\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n            </span>\n            <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n              <el-button type=\"primary\" text @click=\"deleteInitField($index)\">\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"flex-between\">\n        <h4 class=\"title-decoration-1 mb-16\">\n          {{ $t('common.param.inputParam') }}\n          <el-text type=\"info\" class=\"color-secondary lighter\">\n            {{ $t('views.tool.form.param.paramInfo1') }}\n          </el-text>\n        </h4>\n        <el-button link type=\"primary\" @click=\"openAddDialog()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n\n      <el-table ref=\"inputFieldTableRef\" :data=\"form.input_field_list\" class=\"mb-16\">\n        <el-table-column prop=\"name\" :label=\"$t('views.tool.form.paramName.label')\" />\n        <el-table-column :label=\"$t('views.tool.form.dataType.label')\">\n          <template #default=\"{ row }\">\n            <el-tag type=\"info\" class=\"info-tag\">{{ row.type }}</el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.required')\">\n          <template #default=\"{ row }\">\n            <div @click.stop>\n              <el-switch size=\"small\" v-model=\"row.is_required\" />\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"source\" :label=\"$t('views.tool.form.source.label')\">\n          <template #default=\"{ row }\">\n            {{\n              row.source === 'custom' ? $t('common.custom') : $t('views.tool.form.source.reference')\n            }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n          <template #default=\"{ row, $index }\">\n            <span class=\"mr-4\">\n              <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n                <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n            </span>\n            <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n              <el-button type=\"primary\" text @click=\"deleteField($index)\">\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </template>\n        </el-table-column>\n      </el-table>\n      <h4 class=\"title-decoration-1 mb-16\">\n        {{ $t('views.tool.form.param.code') }}\n        <span class=\"color-danger\" style=\"margin-left: -10px\">*</span>\n        <el-text type=\"info\" class=\"color-secondary\">\n          {{ $t('views.tool.form.param.paramInfo2') }}\n        </el-text>\n      </h4>\n\n      <div class=\"mb-8\" v-if=\"showEditor\">\n        <CodemirrorEditor\n          :title=\"$t('views.tool.form.param.code')\"\n          v-model=\"form.code\"\n          @submitDialog=\"submitCodemirrorEditor\"\n        />\n      </div>\n    </div>\n\n    <template #footer>\n      <div>\n        <el-button :loading=\"loading\" @click=\"visible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button\n          type=\"primary\"\n          @click=\"submit(FormRef)\"\n          :loading=\"loading\"\n          v-if=\"isEdit ? permissionPrecise.edit(form?.id as string) : permissionPrecise.create()\"\n        >\n          {{ isEdit ? $t('common.save') : $t('common.create') }}\n        </el-button>\n      </div>\n    </template>\n\n    <FieldFormDialog ref=\"FieldFormDialogRef\" @refresh=\"refreshFieldList\" />\n    <UserFieldFormDialog ref=\"UserFieldFormDialogRef\" @refresh=\"refreshInitFieldList\" />\n    <EditAvatarDialog ref=\"EditAvatarDialogRef\" @refresh=\"refreshTool\" iconType=\"DATA_SOURCE\" />\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive, watch, nextTick, computed } from 'vue'\nimport FieldFormDialog from '@/views/tool/component/FieldFormDialog.vue'\nimport UserFieldFormDialog from '@/views/tool/component/UserFieldFormDialog.vue'\nimport EditAvatarDialog from '@/views/tool/component/EditAvatarDialog.vue'\nimport { input_type_list } from '@/components/dynamics-form/constructor/data'\nimport type { toolData } from '@/api/type/tool'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nimport { isAppIcon } from '@/utils/common'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\n\nconst props = defineProps({\n  title: String,\n})\nconst { folder, user } = useStore()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst emit = defineEmits(['refresh'])\nconst FieldFormDialogRef = ref()\nconst ToolDebugDrawerRef = ref()\nconst UserFieldFormDialogRef = ref()\nconst EditAvatarDialogRef = ref()\nconst initFieldTableRef = ref()\nconst inputFieldTableRef = ref()\n\nconst FormRef = ref()\n\nconst isEdit = ref(false)\nconst loading = ref(false)\nconst visible = ref(false)\nconst showEditor = ref(false)\nconst currentIndex = ref<any>(null)\nconst showEditIcon = ref(false)\nconst codeTemplate = `\ndef get_form_list(node, **kwargs):\n    \"\"\"\n    获取表单配置列表\n\n    Args:\n        node: 节点对象\n        **kwargs: 其他关键字参数\n\n    Returns:\n        list: 包含表单字段配置的列表，用于构建文件树选择器\n    \"\"\"\n    return [{\n        \"field\": 'file_list',\n        \"text_field\": 'name',\n        \"value_field\": 'token',\n        \"input_type\": 'Tree',\n        \"attrs\": {\n            \"lazy\": True,\n            \"fetch_list_function\": \"get_file_list\",\n        },\n        \"label\": '',\n    }]\n\n\ndef get_file_list(app_id=None, app_secret=None, folder_token=None, **kwargs):\n    \"\"\"\n    获取文件列表\n\n    Args:\n        app_id (str, optional): 应用ID\n        app_secret (str, optional): 应用密钥\n        folder_token (str, optional): 文件夹token\n        **kwargs: 其他关键字参数，包括current_node当前节点信息\n\n    Returns:\n        list: 过滤后的文件列表，每个文件包含leaf标识和原始文件信息\n    \"\"\"\n    pass\n\ndef get_down_file_list(app_id=None, app_secret=None, **kwargs):\n    \"\"\"\n    获取需要下载的文件列表（过滤掉文件夹）\n\n    Args:\n        app_id (str, optional): 应用ID\n        app_secret (str, optional): 应用密钥\n        **kwargs: 其他关键字参数，包括file_list文件列表\n\n    Returns:\n        list: 过滤后的文件列表，不包含文件夹类型\n    \"\"\"\n    pass\n\n\ndef download(app_id=None, app_secret=None, **kwargs):\n    \"\"\"\n    下载文件\n\n    支持下载文档(docx)、表格(sheet)和普通文件\n    - 对于文档和表格，先创建导出任务，轮询等待导出完成后下载\n    - 对于普通文件，直接下载\n\n    Args:\n        app_id (str, optional): 应用ID\n        app_secret (str, optional): 应用密钥\n        **kwargs: 其他关键字参数，包括download_item下载项信息\n\n    Returns:\n        dict: 包含文件字节数组(base64编码)和文件名的字典\n              {'file_bytes': [base64_chunk1, base64_chunk2, ...], 'name': 'filename.ext'}\n\n    Raises:\n        Exception: 当创建导出任务失败、查询任务失败或导出任务超时时抛出异常\n    \"\"\"\n    pass\n`\n\nconst form = ref<toolData>({\n  name: '',\n  desc: '',\n  code: codeTemplate,\n  icon: '',\n  input_field_list: [],\n  init_field_list: [],\n  tool_type: 'DATA_SOURCE',\n})\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    isEdit.value = false\n    showEditor.value = false\n    currentIndex.value = null\n    form.value = {\n      name: '',\n      desc: '',\n      code: codeTemplate,\n      icon: '',\n      input_field_list: [],\n      init_field_list: [],\n      tool_type: 'DATA_SOURCE',\n    }\n    FormRef.value?.clearValidate()\n  }\n})\n\nconst rules = reactive({\n  name: [\n    {\n      required: true,\n      message: t('views.tool.form.toolName.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nfunction submitCodemirrorEditor(val: string) {\n  form.value.code = val\n}\n\nfunction close() {\n  if (!areAllValuesNonEmpty(form.value)) {\n    visible.value = false\n  } else {\n    MsgConfirm(t('common.tip'), t('views.tool.tip.saveMessage'), {\n      confirmButtonText: t('common.confirm'),\n    })\n      .then(() => {\n        visible.value = false\n      })\n      .catch(() => {})\n  }\n}\n\nfunction areAllValuesNonEmpty(obj: any) {\n  return Object.values(obj).some((value) => {\n    return Array.isArray(value)\n      ? value.length !== 0\n      : value !== null && value !== undefined && value !== ''\n  })\n}\n\nfunction deleteField(index: any) {\n  form.value.input_field_list?.splice(index, 1)\n}\n\nfunction openAddDialog(data?: any, index?: any) {\n  if (typeof index !== 'undefined') {\n    currentIndex.value = index\n  }\n\n  FieldFormDialogRef.value.open(data)\n}\n\nfunction refreshFieldList(data: any) {\n  if (currentIndex.value !== null) {\n    form.value.input_field_list?.splice(currentIndex.value, 1, data)\n  } else {\n    form.value.input_field_list?.push(data)\n  }\n  currentIndex.value = null\n}\n\nfunction openAddInitDialog(data?: any, index?: any) {\n  if (typeof index !== 'undefined') {\n    currentIndex.value = index\n  }\n\n  UserFieldFormDialogRef.value.open(data)\n}\n\nfunction refreshInitFieldList(data: any) {\n  if (currentIndex.value !== null) {\n    form.value.init_field_list?.splice(currentIndex.value, 1, data)\n  } else {\n    form.value.init_field_list?.push(data)\n  }\n  currentIndex.value = null\n  UserFieldFormDialogRef.value.close()\n}\n\nfunction refreshTool(data: any) {\n  form.value.icon = data\n}\n\nfunction deleteInitField(index: any) {\n  form.value.init_field_list?.splice(index, 1)\n}\n\nfunction openEditAvatar() {\n  EditAvatarDialogRef.value.open(form.value)\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid: any) => {\n    if (valid) {\n      loading.value = true\n      if (isEdit.value) {\n        loadSharedApi({ type: 'tool', systemType: apiType.value })\n          .putTool(form.value?.id as string, form.value)\n          .then((res: any) => {\n            MsgSuccess(t('common.editSuccess'))\n            emit('refresh', res.data)\n            return user.profile()\n          })\n          .then(() => {\n            visible.value = false\n          })\n          .finally(() => {\n            loading.value = false\n          })\n      } else {\n        const obj = {\n          folder_id: folder.currentFolder?.id,\n          ...form.value,\n        }\n        loadSharedApi({ type: 'tool', systemType: apiType.value })\n          .postTool(obj)\n          .then((res: any) => {\n            MsgSuccess(t('common.createSuccess'))\n            emit('refresh')\n            return user.profile()\n          })\n          .then(() => {\n            visible.value = false\n          })\n          .finally(() => {\n            loading.value = false\n          })\n      }\n    }\n  })\n}\n\nconst open = (data: any) => {\n  if (data) {\n    isEdit.value = data?.id ? true : false\n    form.value = cloneDeep(data)\n  }\n  visible.value = true\n  setTimeout(() => {\n    showEditor.value = true\n  }, 100)\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/McpToolFormDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"60%\" :before-close=\"close\">\n    <template #header>\n      <h4>{{ title }}</h4>\n    </template>\n    <div>\n      <h4 class=\"title-decoration-1 mb-16\">\n        {{ $t('views.model.modelForm.title.baseInfo') }}\n      </h4>\n      <el-form\n        ref=\"FormRef\"\n        :model=\"form\"\n        :rules=\"rules\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        hide-required-asterisk\n        v-loading=\"loading\"\n        @submit.prevent\n      >\n        <el-form-item :label=\"$t('common.name')\" prop=\"name\">\n          <div class=\"flex w-full\">\n            <div\n              v-if=\"form.id\"\n              class=\"edit-avatar mr-12\"\n              @mouseenter=\"showEditIcon = true\"\n              @mouseleave=\"showEditIcon = false\"\n            >\n              <el-Avatar\n                v-if=\"isAppIcon(form.icon)\"\n                :id=\"form.id\"\n                shape=\"square\"\n                :size=\"32\"\n                style=\"background: none\"\n              >\n                <img :src=\"String(form.icon)\" alt=\"\" />\n              </el-Avatar>\n\n              <el-avatar v-else shape=\"square\" :size=\"32\">\n                <img src=\"@/assets/tool/icon_mcp.svg\" style=\"width: 75%\" alt=\"\" />\n              </el-avatar>\n              <el-Avatar\n                v-if=\"showEditIcon\"\n                :id=\"form.id\"\n                shape=\"square\"\n                class=\"edit-mask\"\n                :size=\"32\"\n                @click=\"openEditAvatar\"\n              >\n                <AppIcon iconName=\"app-edit\"></AppIcon>\n              </el-Avatar>\n            </div>\n\n            <el-avatar v-else shape=\"square\" :size=\"32\" class=\"mr-12\">\n              <img src=\"@/assets/tool/icon_mcp.svg\" style=\"width: 75%\" alt=\"\" />\n            </el-avatar>\n            <el-input\n              v-model=\"form.name\"\n              :placeholder=\"$t('views.tool.form.mcpName.placeholder')\"\n              maxlength=\"64\"\n              show-word-limit\n              @blur=\"form.name = form.name?.trim()\"\n            />\n          </div>\n        </el-form-item>\n\n        <el-form-item :label=\"$t('common.desc')\">\n          <el-input\n            v-model=\"form.desc\"\n            type=\"textarea\"\n            :placeholder=\"$t('common.descPlaceholder')\"\n            maxlength=\"128\"\n            show-word-limit\n            :autosize=\"{ minRows: 3 }\"\n            @blur=\"form.desc = form.desc?.trim()\"\n          />\n        </el-form-item>\n        <h4 class=\"title-decoration-1 mb-16\">\n          {{ $t('views.tool.mcp.title') }}\n        </h4>\n\n        <el-form-item prop=\"code\">\n          <template #label>\n            {{ $t('views.tool.mcp.label') }}\n            <span class=\"color-danger\">*</span>\n            <el-text type=\"info\" class=\"color-secondary\">\n              （{{ $t('views.tool.mcp.tip') }}）\n            </el-text>\n          </template>\n          <el-input\n            v-model=\"form.code\"\n            :placeholder=\"mcpServerJson\"\n            type=\"textarea\"\n            :autosize=\"{ minRows: 5 }\"\n          />\n        </el-form-item>\n      </el-form>\n    </div>\n\n    <template #footer>\n      <div>\n        <el-button :loading=\"loading\" @click=\"testConnection\">{{\n          $t('views.system.test')\n        }}</el-button>\n        <el-button :loading=\"loading\" @click=\"visible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button\n          type=\"primary\"\n          @click=\"submit(FormRef)\"\n          :loading=\"loading\"\n          v-if=\"isEdit ? permissionPrecise.edit(form?.id as string) : permissionPrecise.create()\"\n        >\n          {{ isEdit ? $t('common.save') : $t('common.create') }}\n        </el-button>\n      </div>\n    </template>\n    <EditAvatarDialog ref=\"EditAvatarDialogRef\" @refresh=\"refreshTool\" iconType=\"MCP\" />\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, reactive, ref, watch } from 'vue'\nimport EditAvatarDialog from '@/views/tool/component/EditAvatarDialog.vue'\nimport type { toolData } from '@/api/type/tool'\nimport type { FormInstance } from 'element-plus'\nimport { MsgConfirm, MsgError, MsgSuccess } from '@/utils/message'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nimport { isAppIcon } from '@/utils/common'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\n\nconst props = defineProps({\n  title: String,\n})\nconst { folder, user } = useStore()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst emit = defineEmits(['refresh'])\nconst EditAvatarDialogRef = ref()\nconst mcpServerJson = `{\n  \"math\": {\n    \"url\": \"your_server\",\n    \"transport\": \"sse\"\n  }\n}`\n\nconst FormRef = ref()\n\nconst isEdit = ref(false)\nconst loading = ref(false)\nconst visible = ref(false)\nconst showEditor = ref(false)\nconst currentIndex = ref<any>(null)\nconst showEditIcon = ref(false)\n\nconst form = ref<toolData>({\n  name: '',\n  desc: '',\n  code: '',\n  icon: '',\n  input_field_list: [],\n  init_field_list: [],\n  tool_type: 'MCP',\n})\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    isEdit.value = false\n    showEditor.value = false\n    currentIndex.value = null\n    form.value = {\n      name: '',\n      desc: '',\n      code: '',\n      icon: '',\n      input_field_list: [],\n      init_field_list: [],\n      tool_type: 'MCP',\n    }\n    FormRef.value?.clearValidate()\n  }\n})\n\nconst rules = reactive({\n  name: [\n    {\n      required: true,\n      message: t('views.tool.form.mcpName.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  code: [\n    {\n      required: true,\n      message: t('views.tool.mcp.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nfunction close() {\n  if (!areAllValuesNonEmpty(form.value)) {\n    visible.value = false\n  } else {\n    MsgConfirm(t('common.tip'), t('views.tool.tip.saveMessage'), {\n      confirmButtonText: t('common.confirm'),\n    })\n      .then(() => {\n        visible.value = false\n      })\n      .catch(() => {})\n  }\n}\n\nfunction areAllValuesNonEmpty(obj: any) {\n  return Object.values(obj).some((value) => {\n    return Array.isArray(value)\n      ? value.length !== 0\n      : value !== null && value !== undefined && value !== ''\n  })\n}\n\nfunction refreshTool(data: any) {\n  form.value.icon = data\n}\n\nfunction openEditAvatar() {\n  EditAvatarDialogRef.value.open(form.value)\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid: any) => {\n    if (valid) {\n      try {\n        const parsed = JSON.parse(form.value.code as string)\n        if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n          throw new Error('Code must be a valid JSON object')\n        }\n      } catch (e) {\n        MsgError(t('workflow.nodes.mcpNode.mcpServerTip'))\n        return\n      }\n      loading.value = true\n      if (isEdit.value) {\n        loadSharedApi({ type: 'tool', systemType: apiType.value })\n          .putTool(form.value?.id as string, form.value)\n          .then((res: any) => {\n            MsgSuccess(t('common.editSuccess'))\n            emit('refresh', res.data)\n            return user.profile()\n          })\n          .then(() => {\n            visible.value = false\n          })\n          .finally(() => {\n            loading.value = false\n          })\n      } else {\n        const obj = {\n          folder_id: folder.currentFolder?.id,\n          ...form.value,\n        }\n        loadSharedApi({ type: 'tool', systemType: apiType.value })\n          .postTool(obj)\n          .then((res: any) => {\n            MsgSuccess(t('common.createSuccess'))\n            emit('refresh')\n            return user.profile()\n          })\n          .then(() => {\n            visible.value = false\n          })\n          .finally(() => {\n            loading.value = false\n          })\n      }\n    }\n  })\n}\n\nfunction testConnection() {\n  if (!form.value.code) {\n    return\n  }\n  loading.value = true\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .postToolTestConnection({ code: form.value.code }, loading)\n    .then(() => {\n      MsgSuccess(t('views.system.testSuccess'))\n    })\n    .finally(() => {\n      loading.value = false\n    })\n}\n\nconst open = (data: any) => {\n  if (data) {\n    isEdit.value = data?.id ? true : false\n    form.value = cloneDeep(data)\n  }\n  visible.value = true\n  setTimeout(() => {\n    showEditor.value = true\n  }, 100)\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/SkillToolFormDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"60%\" :before-close=\"close\">\n    <template #header>\n      <h4>{{ title }}</h4>\n    </template>\n    <div>\n      <h4 class=\"title-decoration-1 mb-16\">\n        {{ $t('views.model.modelForm.title.baseInfo') }}\n      </h4>\n      <el-form\n        ref=\"FormRef\"\n        :model=\"form\"\n        :rules=\"rules\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        v-loading=\"loading\"\n        @submit.prevent\n      >\n        <el-form-item :label=\"$t('common.name')\" prop=\"name\">\n          <div class=\"flex w-full\">\n            <div\n              v-if=\"form.id\"\n              class=\"edit-avatar mr-12\"\n              @mouseenter=\"showEditIcon = true\"\n              @mouseleave=\"showEditIcon = false\"\n            >\n              <el-Avatar\n                v-if=\"isAppIcon(form.icon)\"\n                :id=\"form.id\"\n                shape=\"square\"\n                :size=\"32\"\n                style=\"background: none\"\n              >\n                <img :src=\"String(form.icon)\" alt=\"\" />\n              </el-Avatar>\n\n              <el-avatar v-else shape=\"square\" :size=\"32\">\n                <img src=\"@/assets/tool/icon_skill.svg\" style=\"width: 75%\" alt=\"\" />\n              </el-avatar>\n              <el-Avatar\n                v-if=\"showEditIcon\"\n                :id=\"form.id\"\n                shape=\"square\"\n                class=\"edit-mask\"\n                :size=\"32\"\n                @click=\"openEditAvatar\"\n              >\n                <AppIcon iconName=\"app-edit\"></AppIcon>\n              </el-Avatar>\n            </div>\n\n            <el-avatar v-else shape=\"square\" :size=\"32\" class=\"mr-12\">\n              <img src=\"@/assets/tool/icon_skill.svg\" style=\"width: 65%\" alt=\"\" />\n            </el-avatar>\n            <el-input\n              v-model=\"form.name\"\n              :placeholder=\"$t('views.tool.form.skillName.placeholder')\"\n              maxlength=\"64\"\n              show-word-limit\n              @blur=\"form.name = form.name?.trim()\"\n            />\n          </div>\n        </el-form-item>\n\n        <el-form-item :label=\"$t('common.desc')\">\n          <el-input\n            v-model=\"form.desc\"\n            type=\"textarea\"\n            :placeholder=\"$t('common.descPlaceholder')\"\n            maxlength=\"128\"\n            show-word-limit\n            :autosize=\"{ minRows: 3 }\"\n            @blur=\"form.desc = form.desc?.trim()\"\n          />\n        </el-form-item>\n        <div class=\"flex-between\">\n          <h4 class=\"title-decoration-1 mb-16\">\n            {{ $t('common.param.initParam') }}\n            <el-text type=\"info\" class=\"color-secondary lighter\">\n              {{ $t('views.tool.skill.initParamPlaceholder') }}\n            </el-text>\n          </h4>\n          <el-button link type=\"primary\" @click=\"openAddInitDialog()\">\n            <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n            {{ $t('common.add') }}\n          </el-button>\n        </div>\n        <el-table ref=\"initFieldTableRef\" :data=\"form.init_field_list\" class=\"mb-16\">\n          <el-table-column prop=\"field\" :label=\"$t('dynamicsForm.paramForm.field.label')\">\n            <template #default=\"{ row }\">\n              <span :title=\"row.field\" class=\"ellipsis-1\">{{ row.field }}</span>\n            </template>\n          </el-table-column>\n          <el-table-column :label=\"$t('dynamicsForm.paramForm.input_type.label')\">\n            <template #default=\"{ row }\">\n              <el-tag size=\"small\" type=\"info\" class=\"info-tag\">{{\n                input_type_list.find((item) => item.value === row.input_type)?.label\n              }}</el-tag>\n            </template>\n          </el-table-column>\n          <el-table-column :label=\"$t('common.required')\">\n            <template #default=\"{ row }\">\n              <div @click.stop>\n                <el-switch disabled size=\"small\" v-model=\"row.required\" />\n              </div>\n            </template>\n          </el-table-column>\n          <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n            <template #default=\"{ row, $index }\">\n              <span class=\"mr-4\">\n                <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n                  <el-button type=\"primary\" text @click.stop=\"openAddInitDialog(row, $index)\">\n                    <AppIcon iconName=\"app-edit\"></AppIcon>\n                  </el-button>\n                </el-tooltip>\n              </span>\n              <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n                <el-button type=\"primary\" text @click=\"deleteInitField($index)\">\n                  <AppIcon iconName=\"app-delete\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n            </template>\n          </el-table-column>\n        </el-table>\n        <h4 class=\"title-decoration-1 mb-16\">\n          {{ $t('views.tool.skill.skillFile') }}\n        </h4>\n\n        <el-form-item prop=\"fileList\">\n          <div v-if=\"form.fileList?.length\" class=\"w-full\">\n            <template v-for=\"(item, index) in form.fileList\" :key=\"index\">\n              <el-card shadow=\"never\" style=\"--el-card-padding: 8px 12px; line-height: normal\">\n                <div class=\"flex-between\">\n                  <div class=\"flex\">\n                    <img :src=\"getImgUrl(item && item?.name)\" alt=\"\" width=\"40\" />\n                    <div class=\"ml-8\">\n                      <p class=\"ellipsis-1\" :title=\"item && item?.name\">\n                        {{ item && item?.name }}\n                      </p>\n                      <el-text type=\"info\" size=\"small\">{{\n                        filesize(item && item?.size) || '0K'\n                      }}</el-text>\n                    </div>\n                  </div>\n                </div>\n              </el-card>\n            </template>\n\n            <el-upload\n              v-model:file-list=\"form.fileList\"\n              action=\"#\"\n              :auto-upload=\"false\"\n              :show-file-list=\"false\"\n              accept=\".zip\"\n              :on-change=\"fileHandleChange\"\n            >\n              <el-button link type=\"primary\">{{ $t('views.tool.skill.reUpload') }}</el-button>\n            </el-upload>\n          </div>\n          <el-upload\n            v-else\n            class=\"w-full mb-4\"\n            drag\n            v-model:file-list=\"form.fileList\"\n            action=\"#\"\n            :auto-upload=\"false\"\n            :show-file-list=\"false\"\n            accept=\".zip\"\n            :on-change=\"fileHandleChange\"\n          >\n            <img src=\"@/assets/upload-icon.svg\" alt=\"\" />\n            <div class=\"el-upload__text\">\n              <p>\n                {{ $t('views.document.upload.uploadMessage') }}\n                <em class=\"hover\">\n                  {{ $t('views.document.upload.selectFile') }}\n                </em>\n              </p>\n              <div class=\"upload__decoration\">\n                <p>\n                  {{ $t('views.document.upload.formats') }}ZIP,\n                  {{ $t('views.document.upload.fileLimitSizeTip') }} {{ file_size_limit }} MB\n                </p>\n              </div>\n            </div>\n          </el-upload>\n        </el-form-item>\n      </el-form>\n    </div>\n\n    <template #footer>\n      <div>\n        <el-button :loading=\"loading\" @click=\"visible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button\n          type=\"primary\"\n          @click=\"submit(FormRef)\"\n          :loading=\"loading\"\n          v-if=\"isEdit ? permissionPrecise.edit(form?.id as string) : permissionPrecise.create()\"\n        >\n          {{ isEdit ? $t('common.save') : $t('common.create') }}\n        </el-button>\n      </div>\n    </template>\n    <EditAvatarDialog ref=\"EditAvatarDialogRef\" @refresh=\"refreshTool\" iconType=\"SKILL\" />\n    <UserFieldFormDialog ref=\"UserFieldFormDialogRef\" @refresh=\"refreshInitFieldList\" />\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, reactive, ref, watch } from 'vue'\nimport EditAvatarDialog from '@/views/tool/component/EditAvatarDialog.vue'\nimport UserFieldFormDialog from '@/views/tool/component/UserFieldFormDialog.vue'\nimport { input_type_list } from '@/components/dynamics-form/constructor/data'\nimport type { toolData } from '@/api/type/tool'\nimport type { FormInstance, UploadFiles } from 'element-plus'\nimport { MsgConfirm, MsgError, MsgSuccess } from '@/utils/message'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nimport { filesize, getImgUrl, isAppIcon } from '@/utils/common'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\n\nconst props = defineProps({\n  title: String,\n})\nconst { folder, user } = useStore()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst UserFieldFormDialogRef = ref()\nconst uploadRef = ref()\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst emit = defineEmits(['refresh'])\nconst EditAvatarDialogRef = ref()\nconst FormRef = ref()\n\nconst isEdit = ref(false)\nconst loading = ref(false)\nconst visible = ref(false)\nconst showEditor = ref(false)\nconst currentIndex = ref<any>(null)\nconst showEditIcon = ref(false)\nconst file_size_limit = ref(100)\n\nconst form = ref<toolData>({\n  name: '',\n  desc: '',\n  code: '',\n  icon: '',\n  input_field_list: [],\n  init_field_list: [],\n  tool_type: 'SKILL',\n  fileList: [],\n})\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    isEdit.value = false\n    showEditor.value = false\n    currentIndex.value = null\n    form.value = {\n      name: '',\n      desc: '',\n      code: '',\n      icon: '',\n      input_field_list: [],\n      init_field_list: [],\n      tool_type: 'SKILL',\n      fileList: [],\n    }\n    FormRef.value?.clearValidate()\n  }\n})\n\nconst rules = reactive({\n  name: [\n    {\n      required: true,\n      message: t('views.tool.form.skillName.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n  fileList: [\n    { required: true, message: t('views.document.upload.requiredMessage'), trigger: 'change' },\n  ],\n})\n\nfunction close() {\n  if (!areAllValuesNonEmpty(form.value)) {\n    visible.value = false\n  } else {\n    MsgConfirm(t('common.tip'), t('views.tool.tip.saveMessage'), {\n      confirmButtonText: t('common.confirm'),\n    })\n      .then(() => {\n        visible.value = false\n      })\n      .catch(() => {})\n  }\n}\n\nfunction areAllValuesNonEmpty(obj: any) {\n  return Object.values(obj).some((value) => {\n    return Array.isArray(value)\n      ? value.length !== 0\n      : value !== null && value !== undefined && value !== ''\n  })\n}\n\nfunction refreshTool(data: any) {\n  form.value.icon = data\n}\n\nfunction openEditAvatar() {\n  EditAvatarDialogRef.value.open(form.value)\n}\n\nfunction openAddInitDialog(data?: any, index?: any) {\n  if (typeof index !== 'undefined') {\n    currentIndex.value = index\n  }\n\n  UserFieldFormDialogRef.value.open(data)\n}\n\nfunction refreshInitFieldList(data: any) {\n  if (currentIndex.value !== null) {\n    form.value.init_field_list?.splice(currentIndex.value, 1, data)\n  } else {\n    form.value.init_field_list?.push(data)\n  }\n  currentIndex.value = null\n  UserFieldFormDialogRef.value.close()\n}\n\nfunction deleteInitField(index: any) {\n  form.value.init_field_list?.splice(index, 1)\n}\n\nconst fileHandleChange = (file: any, fileList: UploadFiles) => {\n  //1、判断文件大小是否合法，文件限制不能大于100M\n  const isLimit = file?.size / 1024 / 1024 < file_size_limit.value\n  if (!isLimit) {\n    MsgError(t('views.document.tip.fileLimitSizeTip1') + file_size_limit.value + 'MB')\n    fileList.splice(-1, 1) //移除当前超出大小的文件\n    return false\n  }\n\n  if (file?.size === 0) {\n    MsgError(t('views.document.upload.errorMessage3'))\n    fileList.splice(-1, 1)\n    return false\n  }\n  if (fileList.length > 1) {\n    form.value.fileList = fileList.slice(-1) // 截取最后一个文件\n  }\n  const fd = new FormData()\n  fd.append('file', file.raw)\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .uploadSkillFile(fd, loading)\n    .then((res: any) => {\n      form.value.code = res.data\n      loading.value = false\n    })\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid: any) => {\n    if (valid) {\n      loading.value = true\n      if (isEdit.value) {\n        loadSharedApi({ type: 'tool', systemType: apiType.value })\n          .putTool(form.value?.id as string, form.value)\n          .then((res: any) => {\n            MsgSuccess(t('common.editSuccess'))\n            emit('refresh', res.data)\n            return user.profile()\n          })\n          .then(() => {\n            visible.value = false\n            uploadRef.value?.clearFiles()\n          })\n          .finally(() => {\n            loading.value = false\n          })\n      } else {\n        const obj = {\n          folder_id: folder.currentFolder?.id,\n          ...form.value,\n        }\n        loadSharedApi({ type: 'tool', systemType: apiType.value })\n          .postTool(obj)\n          .then((res: any) => {\n            MsgSuccess(t('common.createSuccess'))\n            emit('refresh')\n            return user.profile()\n          })\n          .then(() => {\n            visible.value = false\n            uploadRef.value?.clearFiles()\n          })\n          .finally(() => {\n            loading.value = false\n          })\n      }\n    }\n  })\n}\n\nconst open = (data: any) => {\n  if (data) {\n    isEdit.value = data?.id ? true : false\n    form.value = cloneDeep(data)\n  }\n  visible.value = true\n  setTimeout(() => {\n    showEditor.value = true\n  }, 100)\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/ToolDebugDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"debugVisible\"\n    size=\"60%\"\n    :append-to-body=\"true\"\n    :modal=\"false\"\n    :show-close=\"false\"\n  >\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <el-button class=\"cursor mr-4\" link @click.prevent=\"debugVisible = false\">\n          <el-icon :size=\"20\">\n            <Back />\n          </el-icon>\n        </el-button>\n        <h4>{{ $t('common.debug') }}</h4>\n      </div>\n    </template>\n    <div>\n      <div v-if=\"form.init_field_list.length > 0\" class=\"mb-16\">\n        <h4 class=\"title-decoration-1 mb-16\">\n          {{ $t('common.param.initParam') }}\n        </h4>\n        <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n          <DynamicsForm\n            v-model=\"form.init_params\"\n            :model=\"form.init_params\"\n            label-position=\"top\"\n            require-asterisk-position=\"right\"\n            :render_data=\"form.init_field_list\"\n            ref=\"dynamicsFormRef\"\n          >\n          </DynamicsForm>\n        </el-card>\n      </div>\n      <div v-if=\"form.debug_field_list.length > 0\" class=\"mb-16\">\n        <h4 class=\"title-decoration-1 mb-16\">\n          {{ $t('common.param.inputParam') }}\n        </h4>\n        <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n          <el-form\n            ref=\"FormRef\"\n            :model=\"form\"\n            label-position=\"top\"\n            require-asterisk-position=\"right\"\n            hide-required-asterisk\n            v-loading=\"loading\"\n            @submit.prevent\n          >\n            <template v-for=\"(item, index) in form.debug_field_list\" :key=\"index\">\n              <el-form-item\n                :label=\"item.name\"\n                :prop=\"'debug_field_list.' + index + '.value'\"\n                :rules=\"{\n                  required: item.is_required,\n                  message: $t('views.tool.form.param.inputPlaceholder'),\n                  trigger: 'blur',\n                }\"\n              >\n                <template #label>\n                  <div class=\"flex\">\n                    <span\n                      >{{ item.name }}\n                      <span class=\"color-danger\" v-if=\"item.is_required\">*</span></span\n                    >\n                    <el-tag size=\"small\" type=\"info\" class=\"info-tag ml-4\">{{ item.type }}</el-tag>\n                  </div>\n                </template>\n                <el-input\n                  v-model=\"item.value\"\n                  :placeholder=\"$t('views.tool.form.param.inputPlaceholder')\"\n                />\n              </el-form-item>\n            </template>\n          </el-form>\n        </el-card>\n      </div>\n\n      <el-button type=\"primary\" @click=\"submit(FormRef)\" :loading=\"loading\">\n        {{ $t('views.tool.form.debug.run') }}\n      </el-button>\n      <div v-if=\"showResult\" class=\"mt-8\">\n        <h4 class=\"title-decoration-1 mb-16 mt-16\">\n          {{ $t('views.tool.form.debug.runResult') }}\n        </h4>\n        <div class=\"mb-16\">\n          <el-alert\n            v-if=\"isSuccess\"\n            :title=\"$t('views.tool.form.debug.runSuccess')\"\n            type=\"success\"\n            show-icon\n            :closable=\"false\"\n          />\n          <el-alert\n            v-else\n            :title=\"$t('views.tool.form.debug.runFailed')\"\n            type=\"error\"\n            show-icon\n            :closable=\"false\"\n          />\n        </div>\n\n        <p class=\"lighter mb-8\">{{ $t('views.tool.form.debug.output') }}</p>\n\n        <el-card :class=\"isSuccess ? '' : 'color-danger'\" class=\"pre-wrap\" shadow=\"never\">\n          {{ String(result) == '0' ? 0 : result || '-' }}\n        </el-card>\n      </div>\n    </div>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive, watch, computed } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { useRoute } from 'vue-router'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst FormRef = ref()\nconst dynamicsFormRef = ref()\nconst loading = ref(false)\nconst debugVisible = ref(false)\nconst showResult = ref(false)\nconst isSuccess = ref(false)\nconst result = ref('')\n\nconst form = ref<any>({\n  debug_field_list: [],\n  code: '',\n  input_field_list: [],\n  init_field_list: [],\n  init_params: {},\n})\n\nwatch(debugVisible, (bool) => {\n  if (!bool) {\n    showResult.value = false\n    isSuccess.value = false\n    result.value = ''\n    form.value = {\n      debug_field_list: [],\n      code: '',\n      input_field_list: [],\n      init_field_list: [],\n      init_params: {},\n    }\n  }\n})\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  const validate = formEl ? formEl.validate() : Promise.resolve()\n  Promise.all([dynamicsFormRef.value?.validate(), validate]).then(() => {\n    loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .postToolDebug(form.value, loading)\n      .then((res: any) => {\n        if (res.code === 500) {\n          showResult.value = true\n          isSuccess.value = false\n          result.value = res.message\n        } else {\n          showResult.value = true\n          isSuccess.value = true\n          result.value = res.data\n        }\n      })\n  })\n}\n\nconst open = (data: any) => {\n  if (data.input_field_list.length > 0) {\n    data.input_field_list.forEach((item: any) => {\n      form.value.debug_field_list.push({\n        value: '',\n        ...item,\n      })\n    })\n  }\n  form.value.code = data.code\n  form.value.input_field_list = data.input_field_list\n  form.value.init_field_list = data.init_field_list\n  debugVisible.value = true\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/tool/ToolFormDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"60%\" :before-close=\"close\">\n    <template #header>\n      <h4>{{ title }}</h4>\n    </template>\n    <div>\n      <h4 class=\"title-decoration-1 mb-16\">\n        {{ $t('views.model.modelForm.title.baseInfo') }}\n      </h4>\n      <el-form\n        ref=\"FormRef\"\n        :model=\"form\"\n        :rules=\"rules\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        v-loading=\"loading\"\n        @submit.prevent\n      >\n        <el-form-item :label=\"$t('common.name')\" prop=\"name\">\n          <div class=\"flex w-full\">\n            <div\n              v-if=\"form.id\"\n              class=\"edit-avatar mr-12\"\n              @mouseenter=\"showEditIcon = true\"\n              @mouseleave=\"showEditIcon = false\"\n            >\n              <el-Avatar\n                v-if=\"isAppIcon(form.icon)\"\n                :id=\"form.id\"\n                shape=\"square\"\n                :size=\"32\"\n                style=\"background: none\"\n              >\n                <img :src=\"String(form.icon)\" alt=\"\" />\n              </el-Avatar>\n              <el-avatar v-else class=\"avatar-green\" shape=\"square\" :size=\"32\">\n                <img src=\"@/assets/tool/icon_tool.svg\" style=\"width: 58%\" alt=\"\" />\n              </el-avatar>\n              <el-Avatar\n                v-if=\"showEditIcon\"\n                :id=\"form.id\"\n                shape=\"square\"\n                class=\"edit-mask\"\n                :size=\"32\"\n                @click=\"openEditAvatar\"\n              >\n                <AppIcon iconName=\"app-edit\"></AppIcon>\n              </el-Avatar>\n            </div>\n            <el-avatar v-else class=\"avatar-green mr-12\" shape=\"square\" :size=\"32\">\n              <img src=\"@/assets/tool/icon_tool.svg\" style=\"width: 58%\" alt=\"\" />\n            </el-avatar>\n            <el-input\n              v-model=\"form.name\"\n              :placeholder=\"$t('views.tool.form.toolName.placeholder')\"\n              maxlength=\"64\"\n              show-word-limit\n              @blur=\"form.name = form.name?.trim()\"\n            />\n          </div>\n        </el-form-item>\n\n        <el-form-item :label=\"$t('common.desc')\">\n          <el-input\n            v-model=\"form.desc\"\n            type=\"textarea\"\n            :placeholder=\"$t('common.descPlaceholder')\"\n            maxlength=\"128\"\n            show-word-limit\n            :autosize=\"{ minRows: 3 }\"\n            @blur=\"form.desc = form.desc?.trim()\"\n          />\n        </el-form-item>\n      </el-form>\n      <div class=\"flex-between\">\n        <h4 class=\"title-decoration-1 mb-16\">\n          {{ $t('common.param.initParam') }}\n        </h4>\n        <el-button link type=\"primary\" @click=\"openAddInitDialog()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n      <el-table ref=\"initFieldTableRef\" :data=\"form.init_field_list\" class=\"mb-16\">\n        <el-table-column prop=\"field\" :label=\"$t('dynamicsForm.paramForm.field.label')\">\n          <template #default=\"{ row }\">\n            <span :title=\"row.field\" class=\"ellipsis-1\">{{ row.field }}</span>\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('dynamicsForm.paramForm.input_type.label')\">\n          <template #default=\"{ row }\">\n            <el-tag size=\"small\" type=\"info\" class=\"info-tag\">{{\n              input_type_list.find((item) => item.value === row.input_type)?.label\n            }}</el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.required')\">\n          <template #default=\"{ row }\">\n            <div @click.stop>\n              <el-switch disabled size=\"small\" v-model=\"row.required\" />\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n          <template #default=\"{ row, $index }\">\n            <span class=\"mr-4\">\n              <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n                <el-button type=\"primary\" text @click.stop=\"openAddInitDialog(row, $index)\">\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n            </span>\n            <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n              <el-button type=\"primary\" text @click=\"deleteInitField($index)\">\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </template>\n        </el-table-column>\n      </el-table>\n      <div class=\"flex-between\">\n        <h4 class=\"title-decoration-1 mb-16\">\n          {{ $t('common.param.inputParam') }}\n          <el-text type=\"info\" class=\"color-secondary lighter\">\n            {{ $t('views.tool.form.param.paramInfo1') }}\n          </el-text>\n        </h4>\n        <el-button link type=\"primary\" @click=\"openAddDialog()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n\n      <el-table ref=\"inputFieldTableRef\" :data=\"form.input_field_list\" class=\"mb-16\">\n        <el-table-column prop=\"name\" :label=\"$t('views.tool.form.paramName.label')\" />\n        <el-table-column :label=\"$t('views.tool.form.dataType.label')\">\n          <template #default=\"{ row }\">\n            <el-tag size=\"small\" type=\"info\" class=\"info-tag\">{{ row.type }}</el-tag>\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.required')\">\n          <template #default=\"{ row }\">\n            <div @click.stop>\n              <el-switch size=\"small\" v-model=\"row.is_required\" />\n            </div>\n          </template>\n        </el-table-column>\n        <el-table-column prop=\"source\" :label=\"$t('views.tool.form.source.label')\">\n          <template #default=\"{ row }\">\n            {{\n              row.source === 'custom' ? $t('common.custom') : $t('views.tool.form.source.reference')\n            }}\n          </template>\n        </el-table-column>\n        <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n          <template #default=\"{ row, $index }\">\n            <span class=\"mr-4\">\n              <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n                <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n                  <AppIcon iconName=\"app-edit\"></AppIcon>\n                </el-button>\n              </el-tooltip>\n            </span>\n            <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n              <el-button type=\"primary\" text @click=\"deleteField($index)\">\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </template>\n        </el-table-column>\n      </el-table>\n\n      <h4 class=\"title-decoration-1 mb-16\">\n        {{ $t('views.tool.form.param.code') }}\n        <span class=\"color-danger\" style=\"margin-left: -10px\">*</span>\n        <el-text type=\"info\" class=\"color-secondary\">\n          {{ $t('views.tool.form.param.paramInfo2') }}\n        </el-text>\n      </h4>\n\n      <div class=\"mb-8\" v-if=\"showEditor\">\n        <CodemirrorEditor\n          :title=\"$t('views.tool.form.param.code')\"\n          v-model=\"form.code\"\n          @submitDialog=\"submitCodemirrorEditor\"\n        />\n      </div>\n      <h4 class=\"title-decoration-1 mb-16 mt-16\">\n        {{ $t('common.param.outputParam') }}\n        <el-text type=\"info\" class=\"color-secondary lighter\">\n          {{ $t('views.tool.form.param.paramInfo1') }}\n        </el-text>\n      </h4>\n      <div class=\"flex-between border-r-6 p-8-12 mb-8 layout-bg lighter\">\n        <span>{{ $t('common.result') }} {result}</span>\n      </div>\n    </div>\n\n    <template #footer>\n      <div>\n        <el-button :loading=\"loading\" @click=\"visible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button :loading=\"loading\" @click=\"openDebug\" v-if=\"permissionPrecise.debug()\">{{\n          $t('common.debug')\n        }}</el-button>\n        <el-button\n          type=\"primary\"\n          @click=\"submit(FormRef)\"\n          :loading=\"loading\"\n          v-if=\"isEdit ? permissionPrecise.edit(form?.id as string) : permissionPrecise.create()\"\n        >\n          {{ isEdit ? $t('common.save') : $t('common.create') }}\n        </el-button>\n      </div>\n    </template>\n\n    <ToolDebugDrawer ref=\"ToolDebugDrawerRef\" />\n    <FieldFormDialog ref=\"FieldFormDialogRef\" @refresh=\"refreshFieldList\" />\n    <UserFieldFormDialog ref=\"UserFieldFormDialogRef\" @refresh=\"refreshInitFieldList\" />\n    <EditAvatarDialog ref=\"EditAvatarDialogRef\" @refresh=\"refreshTool\" />\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, reactive, watch, nextTick, computed } from 'vue'\nimport FieldFormDialog from '@/views/tool/component/FieldFormDialog.vue'\nimport ToolDebugDrawer from './ToolDebugDrawer.vue'\nimport UserFieldFormDialog from '@/views/tool/component/UserFieldFormDialog.vue'\nimport EditAvatarDialog from '@/views/tool/component/EditAvatarDialog.vue'\nimport { input_type_list } from '@/components/dynamics-form/constructor/data'\nimport type { toolData } from '@/api/type/tool'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess, MsgConfirm } from '@/utils/message'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nimport { isAppIcon } from '@/utils/common'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport permissionMap from '@/permission'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst route = useRoute()\n\nconst props = defineProps({\n  title: String,\n})\nconst { folder, user } = useStore()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst emit = defineEmits(['refresh'])\nconst FieldFormDialogRef = ref()\nconst ToolDebugDrawerRef = ref()\nconst UserFieldFormDialogRef = ref()\nconst EditAvatarDialogRef = ref()\nconst initFieldTableRef = ref()\nconst inputFieldTableRef = ref()\n\nconst FormRef = ref()\n\nconst isEdit = ref(false)\nconst loading = ref(false)\nconst visible = ref(false)\nconst showEditor = ref(false)\nconst currentIndex = ref<any>(null)\nconst showEditIcon = ref(false)\n\nconst form = ref<toolData>({\n  name: '',\n  desc: '',\n  code: '',\n  icon: '',\n  input_field_list: [],\n  init_field_list: [],\n})\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    isEdit.value = false\n    showEditor.value = false\n    currentIndex.value = null\n    form.value = {\n      name: '',\n      desc: '',\n      code: '',\n      icon: '',\n      input_field_list: [],\n      init_field_list: [],\n    }\n    FormRef.value?.clearValidate()\n  }\n})\n\nconst rules = reactive({\n  name: [\n    {\n      required: true,\n      message: t('views.tool.form.toolName.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nfunction submitCodemirrorEditor(val: string) {\n  form.value.code = val\n}\n\nfunction close() {\n  if (!areAllValuesNonEmpty(form.value)) {\n    visible.value = false\n  } else {\n    MsgConfirm(t('common.tip'), t('views.tool.tip.saveMessage'), {\n      confirmButtonText: t('common.confirm'),\n    })\n      .then(() => {\n        visible.value = false\n      })\n      .catch(() => {})\n  }\n}\n\nfunction areAllValuesNonEmpty(obj: any) {\n  return Object.values(obj).some((value) => {\n    return Array.isArray(value)\n      ? value.length !== 0\n      : value !== null && value !== undefined && value !== ''\n  })\n}\n\nfunction openDebug() {\n  ToolDebugDrawerRef.value.open(form.value)\n}\n\nfunction deleteField(index: any) {\n  form.value.input_field_list?.splice(index, 1)\n}\n\nfunction openAddDialog(data?: any, index?: any) {\n  if (typeof index !== 'undefined') {\n    currentIndex.value = index\n  }\n\n  FieldFormDialogRef.value.open(data)\n}\n\nfunction refreshFieldList(data: any) {\n  if (currentIndex.value !== null) {\n    form.value.input_field_list?.splice(currentIndex.value, 1, data)\n  } else {\n    form.value.input_field_list?.push(data)\n  }\n  currentIndex.value = null\n}\n\nfunction openAddInitDialog(data?: any, index?: any) {\n  if (typeof index !== 'undefined') {\n    currentIndex.value = index\n  }\n\n  UserFieldFormDialogRef.value.open(data)\n}\n\nfunction refreshInitFieldList(data: any) {\n  if (currentIndex.value !== null) {\n    form.value.init_field_list?.splice(currentIndex.value, 1, data)\n  } else {\n    form.value.init_field_list?.push(data)\n  }\n  currentIndex.value = null\n  UserFieldFormDialogRef.value.close()\n}\n\nfunction refreshTool(data: any) {\n  form.value.icon = data\n}\n\nfunction deleteInitField(index: any) {\n  form.value.init_field_list?.splice(index, 1)\n}\n\nfunction openEditAvatar() {\n  EditAvatarDialogRef.value.open(form.value)\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid: any) => {\n    if (valid) {\n      loading.value = true\n      if (isEdit.value) {\n        loadSharedApi({ type: 'tool', systemType: apiType.value })\n          .putTool(form.value?.id as string, form.value)\n          .then((res: any) => {\n            MsgSuccess(t('common.editSuccess'))\n            emit('refresh', res.data)\n            return user.profile()\n          })\n          .then(() => {\n            visible.value = false\n          })\n          .finally(() => {\n            loading.value = false\n          })\n      } else {\n        const obj = {\n          folder_id: folder.currentFolder?.id,\n          ...form.value,\n        }\n        loadSharedApi({ type: 'tool', systemType: apiType.value })\n          .postTool(obj)\n          .then((res: any) => {\n            MsgSuccess(t('common.createSuccess'))\n            emit('refresh')\n            return user.profile()\n          })\n          .then(() => {\n            visible.value = false\n          })\n          .finally(() => {\n            loading.value = false\n          })\n      }\n    }\n  })\n}\n\nconst open = (data: any) => {\n  if (data) {\n    isEdit.value = data?.id ? true : false\n    form.value = cloneDeep(data)\n  }\n  visible.value = true\n  setTimeout(() => {\n    showEditor.value = true\n  }, 100)\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/WorkflowFormDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"title\"\n    v-model=\"dialogVisible\"\n    width=\"720\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form\n      ref=\"FolderFormRef\"\n      :rules=\"rules\"\n      :model=\"workflowForm\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-form-item :label=\"$t('common.name')\" prop=\"name\">\n        <el-input\n          v-model=\"workflowForm.name\"\n          :placeholder=\"$t('components.folder.folderNamePlaceholder')\"\n          maxlength=\"64\"\n          show-word-limit\n          @blur=\"workflowForm.name = workflowForm.name.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('common.desc')\" prop=\"desc\">\n        <el-input\n          v-model=\"workflowForm.desc\"\n          type=\"textarea\"\n          :placeholder=\"$t('common.descPlaceholder')\"\n          maxlength=\"128\"\n          show-word-limit\n          :autosize=\"{ minRows: 3 }\"\n          @blur=\"workflowForm.desc = workflowForm.desc.trim()\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\" :loading=\"loading\">\n          {{ $t('common.cancel') }}\n        </el-button>\n        <el-button type=\"primary\" @click=\"submitHandle\" :disabled=\"loading\" :loading=\"loading\">\n          {{ isEdit ? $t('common.confirm') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch, reactive, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport folderApi from '@/api/workspace/folder'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport useStore from '@/stores'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst { user, tool, knowledge, folder } = useStore()\nconst emit = defineEmits(['refresh'])\n\nconst props = defineProps({\n  title: {\n    type: String,\n    default: t('components.folder.addFolder'),\n  },\n})\n\nconst FolderFormRef = ref()\nconst route = useRoute()\nconst loading = ref(false)\nconst dialogVisible = ref<boolean>(false)\nconst sourceType = ref<any>('')\nconst isEdit = ref<boolean>(false)\nconst editId = ref<string>('')\nconst default_workflow = {}\nconst workflowForm = ref<any>({\n  name: '',\n  desc: '',\n  tool_type: 'WORKFLOW',\n  work_flow: {},\n})\n\nconst rules = reactive({\n  name: [\n    {\n      required: true,\n      message: t('views.tool.form.toolName.requiredMessage'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    sourceType.value = ''\n    workflowForm.value = {\n      name: '',\n      desc: '',\n      tool_type: 'WORKFLOW',\n      work_flow: {},\n    }\n    isEdit.value = false\n    FolderFormRef.value.resetFields()\n  }\n})\n\nconst open = (source: string, data?: any) => {\n  sourceType.value = source\n  if (data) {\n    //  编辑当前id\n    editId.value = data.id\n    workflowForm.value.name = data.name\n    workflowForm.value.desc = data.desc\n    isEdit.value = true\n  }\n  dialogVisible.value = true\n}\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst submitHandle = async () => {\n  await FolderFormRef.value.validate((valid: any) => {\n    if (valid) {\n      if (isEdit.value) {\n        loadSharedApi({ type: 'tool', systemType: apiType.value })\n          .putTool(workflowForm.value?.id as string, workflowForm.value)\n          .then((res: any) => {\n            MsgSuccess(t('common.editSuccess'))\n            emit('refresh', res.data)\n            return user.profile()\n          })\n          .then(() => {\n            dialogVisible.value = false\n          })\n          .finally(() => {\n            loading.value = false\n          })\n      } else {\n        loadSharedApi({ type: 'tool', systemType: apiType.value })\n          .postTool({ ...workflowForm.value, folder_id: folder.currentFolder?.id, code: 'None' })\n          .then((res: any) => {\n            MsgSuccess(t('common.createSuccess'))\n            emit('refresh', res.data)\n            return user.profile()\n          })\n          .then(() => {\n            dialogVisible.value = false\n          })\n          .finally(() => {\n            loading.value = false\n          })\n      }\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/component/EditAvatarDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"`Logo ${$t('common.setting')}`\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    width=\"550\"\n  >\n    <el-radio-group v-model=\"radioType\" class=\"radio-block mb-16\">\n      <el-radio value=\"default\">\n        <p>{{ $t('common.EditAvatarDialog.default') }}</p>\n        <ToolIcon :size=\"32\" :type=\"iconType\" />\n      </el-radio>\n\n      <el-radio value=\"custom\">\n        <p>{{ $t('common.EditAvatarDialog.customizeUpload') }}</p>\n        <div class=\"flex mt-8\">\n          <el-avatar\n            v-if=\"fileURL\"\n            shape=\"square\"\n            :size=\"32\"\n            style=\"background: none\"\n            class=\"mr-16\"\n          >\n            <img :src=\"fileURL\" alt=\"\" />\n          </el-avatar>\n          <el-upload\n            ref=\"uploadRef\"\n            action=\"#\"\n            :auto-upload=\"false\"\n            :show-file-list=\"false\"\n            accept=\"image/jpeg, image/png, image/gif\"\n            :on-change=\"onChange\"\n          >\n            <el-button icon=\"Upload\" :disabled=\"radioType !== 'custom'\"\n              >{{ $t('common.EditAvatarDialog.upload') }}\n            </el-button>\n          </el-upload>\n        </div>\n        <div class=\"el-upload__tip info mt-8\">\n          {{ $t('common.EditAvatarDialog.sizeTip') }}\n        </div>\n      </el-radio>\n    </el-radio-group>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit\" :loading=\"loading\">\n          {{ $t('common.save') }}</el-button\n        >\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport { MsgError, MsgSuccess } from '@/utils/message'\nimport { isAppIcon } from '@/utils/common'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport { useRoute } from 'vue-router'\n\nconst props = defineProps<{\n  iconType?: string\n}>()\nconst emit = defineEmits(['refresh'])\nconst route = useRoute()\n\nconst iconFile = ref<any>(null)\nconst fileURL = ref<any>(null)\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\nconst detail = ref<any>(null)\nconst radioType = ref('default')\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst isShared = computed(() => {\n  return route.path.includes('shared')\n})\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    iconFile.value = null\n    fileURL.value = null\n  }\n})\n\nconst open = (data: any) => {\n  radioType.value = isAppIcon(data.icon) ? 'custom' : 'default'\n  fileURL.value = isAppIcon(data.icon) ? data.icon : null\n  detail.value = cloneDeep(data)\n  dialogVisible.value = true\n}\n\nconst onChange = (file: any) => {\n  //1、判断文件大小是否合法，文件限制不能大于10MB\n  const isLimit = file?.size / 1024 / 1024 < 10\n  if (!isLimit) {\n    MsgError(t('common.EditAvatarDialog.fileSizeExceeded'))\n    return false\n  } else {\n    iconFile.value = file\n    fileURL.value = URL.createObjectURL(file.raw)\n  }\n}\n\nfunction submit() {\n  if (radioType.value === 'default') {\n    emit('refresh', '')\n    dialogVisible.value = false\n  } else if (radioType.value === 'custom' && iconFile.value) {\n    const fd = new FormData()\n    fd.append('file', iconFile.value.raw)\n    loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .putToolIcon(detail.value.id, fd, loading)\n      .then((res: any) => {\n        emit('refresh', res.data)\n        dialogVisible.value = false\n      })\n  } else {\n    MsgError(t('common.EditAvatarDialog.uploadImagePrompt'))\n  }\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/component/FieldFormDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"isEdit ? $t('common.param.editParam') : $t('common.param.addParam')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item :label=\"$t('views.tool.form.paramName.label')\" prop=\"name\">\n        <el-input\n          v-model=\"form.name\"\n          :placeholder=\"$t('views.tool.form.paramName.placeholder')\"\n          maxlength=\"64\"\n          show-word-limit\n          @blur=\"form.name = form.name.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('views.tool.form.dataType.label')\">\n        <el-select v-model=\"form.type\">\n          <el-option v-for=\"item in typeOptions\" :key=\"item\" :label=\"item\" :value=\"item\" />\n        </el-select>\n      </el-form-item>\n      <el-form-item :label=\"$t('dynamicsForm.paramForm.tooltip.label')\">\n        <el-input\n          v-model=\"form.desc\"\n          :placeholder=\"$t('dynamicsForm.paramForm.tooltip.placeholder')\"\n          :maxlength=\"128\"\n          show-word-limit\n          @blur=\"form.desc = form.desc?.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('views.tool.form.source.label')\">\n        <el-select v-model=\"form.source\">\n          <el-option :label=\"$t('views.tool.form.source.reference')\" value=\"reference\" />\n          <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n        </el-select>\n      </el-form-item>\n      <el-form-item :label=\"$t('dynamicsForm.paramForm.required.label')\" @click.prevent>\n        <el-switch size=\"small\" v-model=\"form.is_required\"></el-switch>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, watch } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst typeOptions = ['string', 'int', 'dict', 'array', 'float', 'boolean']\n\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\n\nconst form = ref<any>({\n  name: '',\n  type: typeOptions[0],\n  desc: '',\n  source: 'reference',\n  is_required: true,\n})\n\nconst rules = reactive({\n  name: [\n    {\n      required: true,\n      message: t('views.tool.form.paramName.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      name: '',\n      type: typeOptions[0],\n      desc: '',\n      source: 'reference',\n      is_required: true,\n    }\n    isEdit.value = false\n  }\n})\n\nconst open = (row: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n    isEdit.value = true\n  }\n\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value)\n      dialogVisible.value = false\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/component/InitParamDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"debugVisible\" size=\"60%\" :append-to-body=\"true\">\n    <template #header>\n      <h4>{{ $t('common.param.initParam') }}</h4>\n    </template>\n    <div>\n      <div v-if=\"form.init_field_list?.length > 0\">\n        <DynamicsForm\n          v-model=\"form.init_params\"\n          :model=\"form.init_params\"\n          label-position=\"top\"\n          require-asterisk-position=\"right\"\n          :render_data=\"form.init_field_list\"\n          ref=\"dynamicsFormRef\"\n        >\n        </DynamicsForm>\n      </div>\n    </div>\n    <template #footer>\n      <div>\n        <el-button type=\"primary\" @click=\"submit()\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </div>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, watch, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport DynamicsForm from '@/components/dynamics-form/index.vue'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport { cloneDeep } from 'lodash'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst emit = defineEmits(['refresh'])\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst dynamicsFormRef = ref()\nconst loading = ref(false)\nconst debugVisible = ref(false)\n\nconst form = ref<any>({\n  init_params: {},\n})\n\nwatch(debugVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      init_params: {},\n      is_active: false,\n    }\n  }\n})\n\nconst submit = async () => {\n  dynamicsFormRef.value.validate().then(() => {\n    loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .putTool(form.value?.id as string, form.value, loading)\n      .then((res: any) => {\n        MsgSuccess(t('common.editSuccess'))\n        emit('refresh')\n        debugVisible.value = false\n      })\n  })\n}\n\nconst open = (data: any, is_active: boolean) => {\n  if (data) {\n    form.value = cloneDeep(data)\n    form.value.is_active = is_active\n  }\n  const init_params = form.value.init_field_list\n    .map((item: any) => {\n      if (item.show_default_value === false) {\n        return { [item.field]: undefined }\n      }\n      return { [item.field]: item.default_value }\n    })\n    .reduce((x: any, y: any) => ({ ...x, ...y }), {})\n  form.value.init_params = { ...init_params, ...form.value.init_params }\n  debugVisible.value = true\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/tool/component/McpToolConfigDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.tool.mcp.mcpConfig')\"\n    width=\"600\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n    class=\"mcp-config-dialog\"\n  >\n    <el-form label-width=\"auto\" label-position=\"top\">\n      <el-form-item @mouseenter.stop=\"showIcon = true\" @mouseleave.stop=\"showIcon = false\">\n        <el-input\n          type=\"textarea\"\n          v-model=\"mcp_servers\"\n          rows=\"8\"\n          disabled\n          class=\"config-textarea\"\n        ></el-input>\n        <el-button circle class=\"copy-icon\" v-show=\"showIcon\" @click.stop=\"copyClick(mcp_servers)\">\n          <AppIcon iconName=\"app-copy\" class=\"color-secondary\" />\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { copyClick } from '@/utils/clipboard'\n\nconst mcp_servers = ref<string>('')\nconst dialogVisible = ref<boolean>(false)\nconst showIcon = ref<boolean>(false)\n\nconst close = () => {\n  dialogVisible.value = false\n}\nconst open = (item: any) => {\n  mcp_servers.value = item.code\n  dialogVisible.value = true\n}\n\ndefineExpose({ open })\n</script>\n\n<style scoped lang=\"scss\">\n.mcp-config-dialog {\n  .copy-icon {\n    position: absolute;\n    top: 12px;\n    right: 12px;\n    box-shadow: 0px 4px 8px 0px rgba(var(--el-text-color-primary-rgb), 0.1);\n    z-index: 2;\n  }\n  .config-textarea {\n    :deep(.el-textarea__inner) {\n      color: var(--el-text-color-primary);\n      cursor: pointer;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/tool/component/ToolListContainer.vue",
    "content": "<template>\n  <ContentContainer>\n    <template #header>\n      <slot name=\"header\"></slot>\n    </template>\n    <template #search>\n      <div class=\"flex\">\n        <div class=\"flex-between complex-search\">\n          <el-select\n            class=\"complex-search__left\"\n            v-model=\"search_type\"\n            style=\"width: 120px\"\n            @change=\"search_type_change\"\n          >\n            <el-option :label=\"$t('common.creator')\" value=\"create_user\" />\n            <el-option :label=\"$t('common.name')\" value=\"name\" />\n          </el-select>\n          <el-input\n            v-if=\"search_type === 'name'\"\n            v-model=\"search_form.name\"\n            @change=\"searchHandle\"\n            :placeholder=\"$t('common.searchBar.placeholder')\"\n            style=\"width: 220px\"\n            clearable\n          />\n          <el-select\n            v-else-if=\"search_type === 'create_user'\"\n            v-model=\"search_form.create_user\"\n            @change=\"searchHandle\"\n            filterable\n            clearable\n            style=\"width: 220px\"\n          >\n            <el-option v-for=\"u in user_options\" :key=\"u.id\" :value=\"u.id\" :label=\"u.nick_name\" />\n          </el-select>\n        </div>\n        <el-button\n          class=\"ml-8\"\n          v-if=\"!isShared && permissionPrecise.create()\"\n          @click=\"openToolStoreDialog()\"\n        >\n          <AppIcon iconName=\"app-tool-store\" class=\"mr-4\" />\n          {{ $t('views.tool.toolStore.title') }}\n        </el-button>\n        <el-dropdown trigger=\"click\">\n          <el-button type=\"primary\" class=\"ml-8\" v-if=\"!isShared && permissionPrecise.create()\">\n            {{ $t('common.create') }}\n            <el-icon class=\"el-icon--right\">\n              <arrow-down />\n            </el-icon>\n          </el-button>\n          <template #dropdown>\n            <el-dropdown-menu class=\"create-dropdown\">\n              <el-dropdown-item @click=\"openCreateDialog()\">\n                <div class=\"flex align-center\">\n                  <el-avatar class=\"avatar-green\" shape=\"square\" :size=\"32\">\n                    <img src=\"@/assets/tool/icon_tool.svg\" style=\"width: 58%\" alt=\"\" />\n                  </el-avatar>\n                  <div class=\"pre-wrap ml-8\">\n                    <div class=\"lighter\">{{ $t('views.tool.createTool') }}</div>\n                  </div>\n                </div>\n              </el-dropdown-item>\n              <el-dropdown-item @click=\"openCreateSkillDialog()\">\n                <div class=\"flex align-center\">\n                  <el-avatar shape=\"square\" :size=\"32\">\n                    <img src=\"@/assets/tool/icon_skill.svg\" style=\"width: 58%\" alt=\"\" />\n                  </el-avatar>\n                  <div class=\"pre-wrap ml-8\">\n                    <div class=\"lighter\">{{ $t('views.tool.skill.createSkillTool') }}</div>\n                  </div>\n                </div>\n              </el-dropdown-item>\n              <el-dropdown-item @click=\"openCreateMcpDialog()\">\n                <div class=\"flex align-center\">\n                  <el-avatar shape=\"square\" :size=\"32\">\n                    <img src=\"@/assets/tool/icon_mcp.svg\" style=\"width: 75%\" alt=\"\" />\n                  </el-avatar>\n                  <div class=\"pre-wrap ml-8\">\n                    <div class=\"lighter\">{{ $t('views.tool.mcp.createMcpTool') }}</div>\n                  </div>\n                </div>\n              </el-dropdown-item>\n\n              <el-dropdown-item @click=\"openCreateWorkflowDialog()\">\n                <div class=\"flex align-center\">\n                  <el-avatar class=\"avatar-purple\" shape=\"square\" :size=\"32\">\n                    <img src=\"@/assets/tool/icon_datasource.svg\" style=\"width: 58%\" alt=\"\" />\n                  </el-avatar>\n                  <div class=\"pre-wrap ml-8\">\n                    <div class=\"lighter\">创建工作流</div>\n                  </div>\n                </div>\n              </el-dropdown-item>\n              <el-dropdown-item @click=\"openCreateDataSourceDialog()\">\n                <div class=\"flex align-center\">\n                  <el-avatar class=\"avatar-purple\" shape=\"square\" :size=\"32\">\n                    <img src=\"@/assets/tool/icon_datasource.svg\" style=\"width: 58%\" alt=\"\" />\n                  </el-avatar>\n                  <div class=\"pre-wrap ml-8\">\n                    <div class=\"lighter\">{{ $t('views.tool.dataSource.createDataSource') }}</div>\n                  </div>\n                </div>\n              </el-dropdown-item>\n              <el-upload\n                ref=\"elUploadRef\"\n                :file-list=\"[]\"\n                action=\"#\"\n                multiple\n                :auto-upload=\"false\"\n                :show-file-list=\"false\"\n                :limit=\"1\"\n                :on-change=\"(file: any, fileList: any) => importTool(file)\"\n                class=\"import-button\"\n              >\n                <el-dropdown-item v-if=\"permissionPrecise.import()\">\n                  <div class=\"flex align-center w-full\">\n                    <el-avatar shape=\"square\" :size=\"32\" style=\"background: none\">\n                      <img src=\"@/assets/icon_import.svg\" alt=\"\" />\n                    </el-avatar>\n                    <div class=\"pre-wrap ml-8\">\n                      <div class=\"lighter\">{{ $t('common.importCreate') }}</div>\n                    </div>\n                  </div>\n                </el-dropdown-item>\n              </el-upload>\n              <el-dropdown-item @click=\"openCreateFolder\" divided v-if=\"apiType === 'workspace'\">\n                <div class=\"flex align-center\">\n                  <AppIcon iconName=\"app-folder\" style=\"font-size: 32px\"></AppIcon>\n\n                  <div class=\"pre-wrap ml-4\">\n                    <div class=\"lighter\">\n                      {{ $t('components.folder.addFolder') }}\n                    </div>\n                  </div>\n                </div>\n              </el-dropdown-item>\n            </el-dropdown-menu>\n          </template>\n        </el-dropdown>\n      </div>\n    </template>\n\n    <div\n      v-loading.fullscreen.lock=\"paginationConfig.current_page === 1 && loading\"\n      style=\"max-height: calc(100vh - 120px)\"\n    >\n      <InfiniteScroll\n        :size=\"tool.toolList.length\"\n        :total=\"paginationConfig.total\"\n        :page_size=\"paginationConfig.page_size\"\n        v-model:current_page=\"paginationConfig.current_page\"\n        @load=\"getList\"\n        :loading=\"loading\"\n      >\n        <el-row v-if=\"tool.toolList.length > 0\" :gutter=\"15\" class=\"w-full\">\n          <template v-for=\"(item, index) in tool.toolList\" :key=\"index\">\n            <el-col :xs=\"24\" :sm=\"12\" :md=\"12\" :lg=\"8\" :xl=\"6\" class=\"mb-16\">\n              <CardBox\n                :title=\"item.name\"\n                :description=\"item.desc\"\n                class=\"cursor\"\n                @click.stop=\"openCreateDialog(item)\"\n                :disabled=\"permissionPrecise.edit(item.id)\"\n              >\n                <template #icon>\n                  <el-avatar v-if=\"item?.icon\" shape=\"square\" :size=\"32\" style=\"background: none\">\n                    <img :src=\"resetUrl(item?.icon)\" alt=\"\" />\n                  </el-avatar>\n                  <ToolIcon v-else :size=\"32\" :type=\"item?.tool_type\" />\n                </template>\n                <template #title>\n                  <div class=\"flex align-center\">\n                    <span class=\"ellipsis-1\" :title=\"item.name\">\n                      {{ item.name }}\n                    </span>\n                    <el-tag\n                      v-if=\"item.version\"\n                      class=\"ml-4\"\n                      size=\"small\"\n                      type=\"info\"\n                      effect=\"plain\"\n                    >\n                      {{ item.version }}\n                    </el-tag>\n                  </div>\n                </template>\n                <template #subTitle>\n                  <el-text class=\"color-secondary lighter flex align-center\" size=\"small\">\n                    <span\n                      :title=\"i18n_name(item.nick_name)\"\n                      class=\"ellipsis\"\n                      style=\"max-width: 90px\"\n                    >\n                      {{ i18n_name(item.nick_name) }}\n                    </span>\n                    <span class=\"ml-4 mr-4\"> {{ $t('common.createdIn') }}</span>\n                    <span> {{ dateFormat(item.create_time) }}</span>\n                  </el-text>\n                </template>\n                <template #tag=\"{ hoverShow }\">\n                  <el-tag v-if=\"isShared\" size=\"small\" type=\"info\" class=\"info-tag\">\n                    {{ t('views.shared.title') }}\n                  </el-tag>\n                  <el-tooltip effect=\"dark\" :content=\"$t('views.tool.updatedVersion')\">\n                    <el-button\n                      text\n                      @click.stop\n                      v-if=\"\n                        showUpdateStoreTool(item) && !isShared && permissionPrecise.edit(item.id)\n                      \"\n                      @click=\"updateStoreTool(item)\"\n                    >\n                      <el-icon v-if=\"hoverShow\">\n                        <Refresh />\n                      </el-icon>\n                      <div v-else class=\"dot-success\"></div>\n                    </el-button>\n                  </el-tooltip>\n                </template>\n\n                <template #footer>\n                  <div v-if=\"item.is_active\" class=\"flex align-center\">\n                    <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n                      <SuccessFilled />\n                    </el-icon>\n                    <span class=\"color-secondary\">\n                      {{ $t('common.status.enabled') }}\n                    </span>\n                  </div>\n                  <div v-else class=\"flex align-center\">\n                    <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n                    <span class=\"color-secondary\">\n                      {{ $t('common.status.disabled') }}\n                    </span>\n                  </div>\n                </template>\n                <template #mouseEnter>\n                  <div @click.stop v-if=\"!isShared && MoreFieldPermission(item.id)\">\n                    <el-switch\n                      v-model=\"item.is_active\"\n                      :before-change=\"() => changeState(item)\"\n                      size=\"small\"\n                      class=\"mr-4\"\n                      v-if=\"permissionPrecise.switch(item.id)\"\n                    />\n                    <el-divider direction=\"vertical\" />\n                    <el-dropdown trigger=\"click\">\n                      <el-button text @click.stop>\n                        <AppIcon iconName=\"app-more\"></AppIcon>\n                      </el-button>\n                      <template #dropdown>\n                        <el-dropdown-menu>\n                          <el-dropdown-item\n                            v-if=\"item.tool_type === 'MCP'\"\n                            @click.stop=\"showMcpConfig(item)\"\n                          >\n                            <AppIcon iconName=\"app-operate-log\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.tool.mcp.mcpConfig') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            v-if=\"item.template_id && permissionPrecise.edit(item.id)\"\n                            @click.stop=\"addInternalTool(item, true)\"\n                          >\n                            <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.edit') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            v-if=\"!item.template_id && permissionPrecise.edit(item.id)\"\n                            @click.stop=\"openCreateDialog(item)\"\n                          >\n                            <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.edit') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            v-if=\"!item.template_id && permissionPrecise.copy(item.id)\"\n                            @click.stop=\"copyTool(item)\"\n                          >\n                            <AppIcon iconName=\"app-copy\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.copy') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            v-if=\"\n                              item.init_field_list?.length > 0 && permissionPrecise.edit(item.id)\n                            \"\n                            @click.stop=\"configInitParams(item)\"\n                          >\n                            <AppIcon iconName=\"app-operation\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.param.initParam') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click.stop=\"openAuthorization(item)\"\n                            v-if=\"apiType === 'workspace' && permissionPrecise.auth(item.id)\"\n                          >\n                            <AppIcon\n                              iconName=\"app-resource-authorization\"\n                              class=\"color-secondary\"\n                            ></AppIcon>\n                            {{ $t('views.system.resourceAuthorization.title') }}\n                          </el-dropdown-item>\n\n                          <el-dropdown-item\n                            @click.stop=\"openTriggerDrawer(item)\"\n                            v-if=\"\n                              ['workspace', 'systemManage'].includes(apiType) &&\n                              item.tool_type === 'CUSTOM' &&\n                              permissionPrecise.trigger_read(item.id)\n                            \"\n                          >\n                            <AppIcon iconName=\"app-trigger\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.trigger.title') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            text\n                            @click.stop=\"openResourceMappingDrawer(item)\"\n                            v-if=\"permissionPrecise.relate_map(item.id)\"\n                          >\n                            <AppIcon\n                              iconName=\"app-resource-mapping\"\n                              class=\"color-secondary\"\n                            ></AppIcon>\n                            {{ $t('views.system.resourceMapping.title') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            text\n                            @click.stop=\"openToolRecordDrawer(item)\"\n                            v-if=\"item.tool_type === 'CUSTOM' && permissionPrecise.record(item.id)\"\n                          >\n                            <AppIcon\n                              iconName=\"app-schedule-report\"\n                              class=\"color-secondary\"\n                            ></AppIcon>\n                            {{ $t('common.ExecutionRecord.subTitle') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            @click.stop=\"openMoveToDialog(item)\"\n                            v-if=\"permissionPrecise.copy(item.id) && apiType === 'workspace'\"\n                          >\n                            <AppIcon iconName=\"app-migrate\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.moveTo') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            v-if=\"isSystemShare\"\n                            @click.stop=\"openAuthorizedWorkspaceDialog(item)\"\n                          >\n                            <AppIcon iconName=\"app-lock\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('views.shared.authorized_workspace') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            v-if=\"!item.template_id && permissionPrecise.export(item.id)\"\n                            @click.stop=\"exportTool(item)\"\n                          >\n                            <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.export') }}\n                          </el-dropdown-item>\n                          <el-dropdown-item\n                            v-if=\"permissionPrecise.delete(item.id)\"\n                            divided\n                            @click.stop=\"deleteTool(item)\"\n                          >\n                            <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                            {{ $t('common.delete') }}\n                          </el-dropdown-item>\n                        </el-dropdown-menu>\n                      </template>\n                    </el-dropdown>\n                  </div>\n                </template>\n              </CardBox>\n            </el-col>\n          </template>\n        </el-row>\n        <el-empty :description=\"$t('common.noData')\" v-else />\n      </InfiniteScroll>\n    </div>\n  </ContentContainer>\n  <InitParamDrawer ref=\"InitParamDrawerRef\" @refresh=\"refresh\" />\n  <ToolFormDrawer ref=\"ToolFormDrawerRef\" @refresh=\"refresh\" :title=\"ToolDrawertitle\" />\n  <McpToolFormDrawer ref=\"McpToolFormDrawerRef\" @refresh=\"refresh\" :title=\"McpToolDrawertitle\" />\n  <SkillToolFormDrawer\n    ref=\"SkillToolFormDrawerRef\"\n    @refresh=\"refresh\"\n    :title=\"SkillToolDrawertitle\"\n  />\n  <DataSourceToolFormDrawer\n    ref=\"DataSourceToolFormDrawerRef\"\n    @refresh=\"refresh\"\n    :title=\"DataSourceToolDrawertitle\"\n  />\n  <CreateFolderDialog ref=\"CreateFolderDialogRef\" v-if=\"!isShared\" @refresh=\"refreshFolder\" />\n  <ToolStoreDialog ref=\"toolStoreDialogRef\" :api-type=\"apiType\" @refresh=\"refresh\" />\n  <AddInternalToolDialog ref=\"AddInternalToolDialogRef\" @refresh=\"confirmAddInternalTool\" />\n  <McpToolConfigDialog ref=\"McpToolConfigDialogRef\" @refresh=\"refresh\" />\n  <AuthorizedWorkspace\n    ref=\"AuthorizedWorkspaceDialogRef\"\n    v-if=\"isSystemShare\"\n  ></AuthorizedWorkspace>\n  <MoveToDialog\n    ref=\"MoveToDialogRef\"\n    :source=\"SourceTypeEnum.TOOL\"\n    @refresh=\"refreshToolList\"\n    v-if=\"apiType === 'workspace'\"\n  />\n  <ResourceAuthorizationDrawer\n    :type=\"SourceTypeEnum.TOOL\"\n    ref=\"ResourceAuthorizationDrawerRef\"\n    v-if=\"apiType === 'workspace'\"\n  />\n  <ToolStoreDescDrawer ref=\"toolStoreDescDrawerRef\" />\n  <ResourceMappingDrawer ref=\"resourceMappingDrawerRef\"></ResourceMappingDrawer>\n  <ResourceTriggerDrawer\n    ref=\"resourceTriggerDrawerRef\"\n    :source=\"SourceTypeEnum.TOOL\"\n  ></ResourceTriggerDrawer>\n  <ToolRecordDrawer ref=\"toolRecordDrawerRef\" />\n  <WorkflowFormDialog ref=\"workflowFormDialogRef\"></WorkflowFormDialog>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, computed, watch } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport { useRoute, onBeforeRouteLeave, useRouter } from 'vue-router'\nimport InitParamDrawer from '@/views/tool/component/InitParamDrawer.vue'\nimport ToolFormDrawer from '@/views/tool/ToolFormDrawer.vue'\nimport McpToolFormDrawer from '@/views/tool/McpToolFormDrawer.vue'\nimport SkillToolFormDrawer from '@/views/tool/SkillToolFormDrawer.vue'\nimport DataSourceToolFormDrawer from '@/views/tool/DataSourceToolFormDrawer.vue'\nimport CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'\nimport AuthorizedWorkspace from '@/views/system-shared/AuthorizedWorkspaceDialog.vue'\nimport ToolStoreDialog from '@/views/tool/tool-store/ToolStoreDialog.vue'\nimport AddInternalToolDialog from '@/views/tool/tool-store/AddInternalToolDialog.vue'\nimport MoveToDialog from '@/components/folder-tree/MoveToDialog.vue'\nimport ResourceAuthorizationDrawer from '@/components/resource-authorization-drawer/index.vue'\nimport McpToolConfigDialog from '@/views/tool/component/McpToolConfigDialog.vue'\nimport ResourceTriggerDrawer from '@/views/trigger/ResourceTriggerDrawer.vue'\nimport ToolStoreDescDrawer from '@/views/tool/component/ToolStoreDescDrawer.vue'\nimport ResourceMappingDrawer from '@/components/resource_mapping/index.vue'\nimport WorkflowFormDialog from '../WorkflowFormDialog.vue'\nimport ToolRecordDrawer from '@/views/tool/execution-record/TriggerRecordDrawer.vue'\nimport ToolStoreApi from '@/api/tool/store.ts'\nimport { resetUrl, i18n_name } from '@/utils/common'\nimport { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'\nimport { SourceTypeEnum } from '@/enums/common'\nimport { dateFormat } from '@/utils/time'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\nimport useStore from '@/stores'\nimport { t } from '@/locales'\n\nimport bus from '@/bus'\nconst router = useRouter()\nconst route = useRoute()\n\nconst { folder, user, tool } = useStore()\nonBeforeRouteLeave((to, from) => {\n  tool.setToolList([])\n})\nconst emit = defineEmits(['refreshFolder'])\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst workflowFormDialogRef = ref<InstanceType<typeof WorkflowFormDialog>>()\nconst isShared = computed(() => {\n  return folder.currentFolder.id === 'share'\n})\nconst isSystemShare = computed(() => {\n  return apiType.value === 'systemShare'\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst MoreFieldPermission = (id: any) => {\n  return (\n    permissionPrecise.value.edit(id) ||\n    permissionPrecise.value.export(id) ||\n    permissionPrecise.value.delete(id) ||\n    permissionPrecise.value.auth(id) ||\n    permissionPrecise.value.relate_map(id) ||\n    permissionPrecise.value.trigger_read(id) ||\n    permissionPrecise.value.record(id) ||\n    isSystemShare.value\n  )\n}\n\nconst resourceTriggerDrawerRef = ref<InstanceType<typeof ResourceTriggerDrawer>>()\nconst openTriggerDrawer = (data: any) => {\n  resourceTriggerDrawerRef.value?.open(data)\n}\n\nconst resourceMappingDrawerRef = ref<InstanceType<typeof ResourceMappingDrawer>>()\nconst openResourceMappingDrawer = (tool: any) => {\n  resourceMappingDrawerRef.value?.open('TOOL', tool)\n}\n\nconst ResourceAuthorizationDrawerRef = ref()\n\nfunction openAuthorization(item: any) {\n  ResourceAuthorizationDrawerRef.value.open(item.id)\n}\n\nconst toolRecordDrawerRef = ref<InstanceType<typeof ToolRecordDrawer>>()\nconst openToolRecordDrawer = (data: any) => {\n  toolRecordDrawerRef.value?.open(data)\n}\n\nconst InitParamDrawerRef = ref()\nconst search_type = ref('name')\nconst search_form = ref<any>({\n  name: '',\n  create_user: '',\n})\nconst user_options = ref<any[]>([])\n\nconst loading = ref(false)\nconst changeStateloading = ref(false)\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 30,\n  total: 0,\n})\n\nconst search_type_change = () => {\n  search_form.value = { name: '', create_user: '' }\n}\nconst ToolFormDrawerRef = ref()\nconst McpToolFormDrawerRef = ref()\nconst SkillToolFormDrawerRef = ref()\nconst DataSourceToolFormDrawerRef = ref()\nconst ToolDrawertitle = ref('')\nconst McpToolDrawertitle = ref('')\nconst SkillToolDrawertitle = ref('')\nconst DataSourceToolDrawertitle = ref('')\n\nconst MoveToDialogRef = ref()\n\nfunction openMoveToDialog(data: any) {\n  const obj = {\n    id: data.id,\n    folder_id: data.folder,\n  }\n  MoveToDialogRef.value?.open(obj)\n}\n\nfunction refreshToolList(row: any) {\n  // 不是根目录才会移除\n  if (folder.currentFolder?.parent_id) {\n    const list = cloneDeep(tool.toolList)\n    const index = list.findIndex((v) => v.id === row.id)\n    list.splice(index, 1)\n    tool.setToolList(list)\n  }\n}\n\nconst AuthorizedWorkspaceDialogRef = ref()\n\nfunction openAuthorizedWorkspaceDialog(row: any) {\n  if (AuthorizedWorkspaceDialogRef.value) {\n    AuthorizedWorkspaceDialogRef.value.open(row, 'Tool')\n  }\n}\n\nconst toolStoreDescDrawerRef = ref<InstanceType<typeof ToolStoreDescDrawer>>()\n\nfunction openCreateDialog(data?: any) {\n  if (data && data.tool_type === 'WORKFLOW') {\n    router.push({ name: 'ToolWorkflow', params: { id: data.id, folderId: data.folder_id } })\n    return\n  }\n  // mcp工具\n  if (data?.tool_type === 'MCP') {\n    bus.emit('select_node', data.folder_id)\n    openCreateMcpDialog(data)\n    return\n  }\n  // 数据源工具\n  if (data?.tool_type === 'DATA_SOURCE') {\n    bus.emit('select_node', data.folder_id)\n    openCreateDataSourceDialog(data)\n    return\n  }\n  // 技能\n  if (data?.tool_type === 'SKILL') {\n    bus.emit('select_node', data.folder_id)\n    openCreateSkillDialog(data)\n    return\n  }\n\n  // 有版本号的展示readme，是商店更新过来的\n  if (data?.version) {\n    let readMe = ''\n    storeTools.value\n      .filter((item) => item.id === data.template_id)\n      .forEach((item) => {\n        readMe = item.readMe\n      })\n    bus.emit('select_node', data.folder_id)\n    toolStoreDescDrawerRef.value?.open(readMe, data)\n    return\n  }\n  // 有template_id的不允许编辑，是模板转换来的\n  if (data?.template_id) {\n    return\n  }\n  // 共享过来的工具不让编辑\n  if (isShared.value) {\n    return\n  }\n  ToolDrawertitle.value = data ? t('views.tool.editTool') : t('views.tool.createTool')\n  if (data) {\n    loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .getToolById(data?.id, loading)\n      .then((res: any) => {\n        bus.emit('select_node', data.folder_id)\n        ToolFormDrawerRef.value.open(res.data)\n      })\n  } else {\n    ToolFormDrawerRef.value.open(data)\n  }\n  if (data) {\n    bus.emit('select_node', data.folder_id)\n  }\n}\n\nfunction openCreateMcpDialog(data?: any) {\n  // 有template_id的不允许编辑，是模板转换来的\n  if (data?.template_id) {\n    return\n  }\n  // 共享过来的工具不让编辑\n  if (isShared.value) {\n    return\n  }\n  McpToolDrawertitle.value = data\n    ? t('views.tool.mcp.editMcpTool')\n    : t('views.tool.mcp.createMcpTool')\n  if (data) {\n    loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .getToolById(data?.id, loading)\n      .then((res: any) => {\n        McpToolFormDrawerRef.value.open(res.data)\n      })\n  } else {\n    McpToolFormDrawerRef.value.open(data)\n  }\n}\n\nfunction openCreateSkillDialog(data?: any) {\n  // 有版本号的展示readme，是商店更新过来的\n  if (data?.version) {\n    let readMe = ''\n    storeTools.value\n      .filter((item) => item.id === data.template_id)\n      .forEach((item) => {\n        readMe = item.readMe\n      })\n    bus.emit('select_node', data.folder_id)\n    toolStoreDescDrawerRef.value?.open(readMe, data)\n    return\n  }\n  // 有template_id的不允许编辑，是模板转换来的\n  if (data?.template_id) {\n    return\n  }\n  // 共享过来的工具不让编辑\n  if (isShared.value) {\n    return\n  }\n  SkillToolDrawertitle.value = data\n    ? t('views.tool.skill.editSkillTool')\n    : t('views.tool.skill.createSkillTool')\n  if (data) {\n    loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .getToolById(data?.id, loading)\n      .then((res: any) => {\n        SkillToolFormDrawerRef.value.open(res.data)\n      })\n  } else {\n    SkillToolFormDrawerRef.value.open(data)\n  }\n}\nconst openCreateWorkflowDialog = (data?: any) => {\n  // 有template_id的不允许编辑，是模板转换来的\n  if (data?.template_id) {\n    return\n  }\n  // 共享过来的工具不让编辑\n  if (isShared.value) {\n    return\n  }\n  DataSourceToolDrawertitle.value = data\n    ? t('views.tool.dataSource.editDataSource')\n    : t('views.tool.dataSource.createDataSource')\n  if (data) {\n    loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .getToolById(data?.id, loading)\n      .then((res: any) => {\n        workflowFormDialogRef.value?.open(res.data)\n      })\n  } else {\n    workflowFormDialogRef.value?.open(data)\n  }\n}\nfunction openCreateDataSourceDialog(data?: any) {\n  // 有template_id的不允许编辑，是模板转换来的\n  if (data?.template_id) {\n    return\n  }\n  // 共享过来的工具不让编辑\n  if (isShared.value) {\n    return\n  }\n  DataSourceToolDrawertitle.value = data\n    ? t('views.tool.dataSource.editDataSource')\n    : t('views.tool.dataSource.createDataSource')\n  if (data) {\n    loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .getToolById(data?.id, loading)\n      .then((res: any) => {\n        DataSourceToolFormDrawerRef.value.open(res.data)\n      })\n  } else {\n    DataSourceToolFormDrawerRef.value.open(data)\n  }\n}\n\nasync function changeState(row: any) {\n  if (row.is_active) {\n    MsgConfirm(\n      `${t('views.tool.disabled.confirmTitle')}${row.name} ?`,\n      t('views.tool.disabled.confirmMessage'),\n      {\n        confirmButtonText: t('common.status.disable'),\n        confirmButtonClass: 'danger',\n      },\n    ).then(() => {\n      const obj = {\n        is_active: !row.is_active,\n      }\n      loadSharedApi({ type: 'tool', systemType: apiType.value })\n        .putTool(row.id, obj, changeStateloading)\n        .then(() => {\n          const list = cloneDeep(tool.toolList)\n          const index = list.findIndex((v) => v.id === row.id)\n          list[index].is_active = !row.is_active\n          tool.setToolList(list)\n          return true\n        })\n        .catch(() => {\n          return false\n        })\n    })\n  } else {\n    const res = await loadSharedApi({ type: 'tool', systemType: apiType.value }).getToolById(\n      row.id,\n      changeStateloading,\n    )\n    if (\n      (!res.data.init_params || Object.keys(res.data.init_params).length === 0) &&\n      res.data.init_field_list &&\n      res.data.init_field_list.length > 0 &&\n      res.data.init_field_list.filter((item: any) => item.default_value && item.show_default_value)\n        .length !== res.data.init_field_list.length\n    ) {\n      InitParamDrawerRef.value.open(res.data, !row.is_active)\n      return false\n    }\n    const obj = {\n      is_active: !row.is_active,\n    }\n    loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .putTool(row.id, obj, changeStateloading)\n      .then(() => {\n        const list = cloneDeep(tool.toolList)\n        const index = list.findIndex((v) => v.id === row.id)\n        list[index].is_active = !row.is_active\n        tool.setToolList(list)\n        return true\n      })\n      .catch(() => {\n        return false\n      })\n  }\n}\n\nasync function copyTool(row: any) {\n  // mcp工具\n  if (row?.tool_type === 'MCP') {\n    bus.emit('select_node', row.folder_id)\n    await copyMcpTool(row)\n    return\n  }\n  // 数据源工具\n  if (row?.tool_type === 'DATA_SOURCE') {\n    bus.emit('select_node', row.folder_id)\n    await copyDataSource(row)\n    return\n  }\n  // 技能\n  if (row?.tool_type === 'SKILL') {\n    bus.emit('select_node', row.folder_id)\n    await copySkillTool(row)\n    return\n  }\n  ToolDrawertitle.value = t('views.tool.copyTool')\n  const res = await loadSharedApi({ type: 'tool', systemType: apiType.value }).getToolById(\n    row.id,\n    changeStateloading,\n  )\n  const obj = cloneDeep(res.data)\n  delete obj['id']\n  obj['name'] = obj['name'] + `  ${t('common.copyTitle')}`\n  ToolFormDrawerRef.value.open(obj)\n}\n\nasync function copyMcpTool(row: any) {\n  McpToolDrawertitle.value = t('views.tool.mcp.copyMcpTool')\n  const res = await loadSharedApi({ type: 'tool', systemType: apiType.value }).getToolById(\n    row.id,\n    changeStateloading,\n  )\n  const obj = cloneDeep(res.data)\n  delete obj['id']\n  obj['name'] = obj['name'] + `  ${t('common.copyTitle')}`\n  McpToolFormDrawerRef.value.open(obj)\n}\n\nasync function copyDataSource(row: any) {\n  DataSourceToolDrawertitle.value = t('views.tool.dataSource.copyDataSource')\n  const res = await loadSharedApi({ type: 'tool', systemType: apiType.value }).getToolById(\n    row.id,\n    changeStateloading,\n  )\n  const obj = cloneDeep(res.data)\n  delete obj['id']\n  obj['name'] = obj['name'] + `  ${t('common.copyTitle')}`\n  DataSourceToolFormDrawerRef.value.open(obj)\n}\n\nasync function copySkillTool(row: any) {\n  SkillToolDrawertitle.value = t('views.tool.skill.copySkillTool')\n  const res = await loadSharedApi({ type: 'tool', systemType: apiType.value }).getToolById(\n    row.id,\n    changeStateloading,\n  )\n  const obj = cloneDeep(res.data)\n  delete obj['id']\n  obj['name'] = obj['name'] + `  ${t('common.copyTitle')}`\n  SkillToolFormDrawerRef.value.open(obj)\n}\n\nfunction exportTool(row: any) {\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .exportTool(row.id, row.name, loading)\n    .catch((e: any) => {\n      if (e.response.status !== 403) {\n        e.response.data.text().then((res: string) => {\n          MsgError(`${t('views.application.tip.ExportError')}:${JSON.parse(res).message}`)\n        })\n      }\n    })\n}\n\nfunction deleteTool(row: any) {\n  MsgConfirm(\n    `${t('views.tool.delete.confirmTitle')}：${row.name} ?`,\n    row.resource_count > 0 ? t('views.tool.delete.resourceCountMessage', row.resource_count) : '',\n    {\n      confirmButtonText: t('common.confirm'),\n      cancelButtonText: t('common.cancel'),\n      confirmButtonClass: 'danger',\n    },\n  )\n    .then(() => {\n      loadSharedApi({ type: 'tool', systemType: apiType.value })\n        .delTool(row.id, loading)\n        .then(() => {\n          const list = cloneDeep(tool.toolList)\n          const index = list.findIndex((v) => v.id === row.id)\n          list.splice(index, 1)\n          tool.setToolList(list)\n          MsgSuccess(t('common.deleteSuccess'))\n        })\n    })\n    .catch(() => {})\n}\n\nfunction configInitParams(item: any) {\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getToolById(item?.id, changeStateloading)\n    .then((res: any) => {\n      InitParamDrawerRef.value.open(res.data)\n    })\n}\n\nconst toolStoreDialogRef = ref<InstanceType<typeof ToolStoreDialog>>()\n\nfunction openToolStoreDialog() {\n  toolStoreDialogRef.value?.open(folder.currentFolder.id)\n}\n\nconst AddInternalToolDialogRef = ref<InstanceType<typeof AddInternalToolDialog>>()\n\nfunction addInternalTool(data?: any, isEdit?: boolean) {\n  AddInternalToolDialogRef.value?.open(data, isEdit)\n}\n\nfunction confirmAddInternalTool(data?: any, isEdit?: boolean) {\n  if (isEdit) {\n    loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .putTool(data?.id as string, { name: data.name }, loading)\n      .then((res: any) => {\n        MsgSuccess(t('common.saveSuccess'))\n        refresh()\n      })\n  }\n}\n\nconst storeTools = ref<any[]>([])\n\nfunction getStoreToolList() {\n  ToolStoreApi.getStoreToolList({ name: '' }, loading).then((res: any) => {\n    storeTools.value = res.data.apps\n  })\n}\n\nfunction showUpdateStoreTool(item: any) {\n  for (const tool of storeTools.value) {\n    if (tool.id === item.template_id && tool.version !== item.version) {\n      item.downloadUrl = tool.downloadUrl\n      item.downloadCallbackUrl = tool.downloadCallbackUrl\n      item.icon = tool.icon\n      item.versions = tool.versions\n      item.label = tool.label\n      return true\n    }\n  }\n}\n\nfunction updateStoreTool(item: any) {\n  MsgConfirm(\n    t('views.tool.toolStore.confirmTip') + item.name,\n    t('views.tool.toolStore.updateStoreToolMessage'),\n    {\n      cancelButtonText: t('common.cancel'),\n      confirmButtonText: t('common.confirm'),\n    },\n  )\n    .then(() => {\n      const obj = {\n        download_url: item.downloadUrl,\n        download_callback_url: item.downloadCallbackUrl,\n        icon: item.icon,\n        versions: item.versions,\n        label: item.label,\n      }\n      loadSharedApi({ type: 'tool', systemType: apiType.value })\n        .updateStoreTool(item.id, obj, loading)\n        .then(async (res: any) => {\n          if (res?.data) {\n            tool.setToolList([])\n            return user.profile()\n          }\n        })\n        .then(() => {\n          getList()\n        })\n    })\n    .catch(() => {})\n}\n\nconst elUploadRef = ref()\n\nfunction importTool(file: any) {\n  const formData = new FormData()\n  formData.append('file', file.raw, file.name)\n  formData.append('folder_id', folder.currentFolder.id || user.getWorkspaceId())\n  elUploadRef.value.clearFiles()\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .postImportTool(formData, loading)\n    .then(async (res: any) => {\n      if (res?.data) {\n        tool.setToolList([])\n        return user.profile()\n      }\n    })\n    .then(() => {\n      getList()\n    })\n    .catch((e: any) => {\n      if (e.code === 400) {\n        MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), {\n          cancelButtonText: t('common.confirm'),\n          confirmButtonText: t('common.professional'),\n        }).then(() => {\n          window.open('https://maxkb.cn/pricing.html', '_blank')\n        })\n      }\n    })\n}\n\nconst McpToolConfigDialogRef = ref()\n\nfunction showMcpConfig(item: any) {\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getToolById(item?.id, loading)\n    .then((res: any) => {\n      McpToolConfigDialogRef.value.open(res.data)\n    })\n}\n\nfunction refresh(data?: any) {\n  if (data) {\n    const list = cloneDeep(tool.toolList)\n    const index = list.findIndex((v) => v.id === data.id)\n    list.splice(index, 1, data)\n    tool.setToolList(list)\n  } else {\n    paginationConfig.total = 0\n    paginationConfig.current_page = 1\n    tool.setToolList([])\n    getList()\n  }\n}\n\n// 文件夹相关\nconst CreateFolderDialogRef = ref()\n\nfunction openCreateFolder() {\n  CreateFolderDialogRef.value.open(SourceTypeEnum.TOOL, folder.currentFolder.id)\n}\n\nwatch(\n  () => folder.currentFolder,\n  (newValue) => {\n    if (newValue && newValue.id) {\n      paginationConfig.current_page = 1\n      tool.setToolList([])\n      getList()\n    }\n  },\n  { deep: true, immediate: true },\n)\n\nwatch(\n  () => tool.tool_type,\n  () => {\n    paginationConfig.current_page = 1\n    tool.setToolList([])\n    getList()\n  },\n)\n\nfunction getList() {\n  const params: any = {\n    folder_id: folder.currentFolder?.id || user.getWorkspaceId(),\n    scope: apiType.value === 'systemShare' ? 'SHARED' : 'WORKSPACE',\n    tool_type: tool.tool_type || '',\n  }\n  if (search_form.value[search_type.value]) {\n    params[search_type.value] = search_form.value[search_type.value]\n  }\n  loadSharedApi({ type: 'tool', isShared: isShared.value, systemType: apiType.value })\n    .getToolListPage(paginationConfig, params, loading)\n    .then((res: any) => {\n      paginationConfig.total = res.data?.total\n      tool.setToolList([...tool.toolList, ...res.data?.records])\n    })\n}\n\nfunction clickFolder(item: any) {\n  folder.setCurrentFolder(item)\n}\n\nfunction refreshFolder() {\n  emit('refreshFolder')\n}\n\nfunction searchHandle() {\n  paginationConfig.current_page = 1\n  tool.setToolList([])\n  getList()\n}\n\nonMounted(() => {\n  if (apiType.value !== 'workspace') {\n    getList()\n  }\n  loadSharedApi({ type: 'workspace', isShared: isShared.value, systemType: apiType.value })\n    .getAllMemberList(user.getWorkspaceId(), loading)\n    .then((res: any) => {\n      user_options.value = res.data\n    })\n  getStoreToolList()\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/component/ToolStoreDescDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visibleInternalDesc\" size=\"60%\" :append-to-body=\"true\">\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <el-button class=\"cursor mr-4\" link @click.prevent=\"visibleInternalDesc = false\">\n          <el-icon :size=\"20\">\n            <Back />\n          </el-icon>\n        </el-button>\n        <h4>详情</h4>\n      </div>\n    </template>\n\n    <div>\n      <div class=\"card-header\">\n        <div class=\"flex-between\">\n          <div class=\"title flex align-center\">\n            <el-avatar\n              v-if=\"isAppIcon(toolDetail?.icon)\"\n              shape=\"square\"\n              :size=\"64\"\n              style=\"background: none\"\n              class=\"mr-8\"\n            >\n              <img :src=\"toolDetail?.icon\" alt=\"\" />\n            </el-avatar>\n            <el-avatar\n              v-else-if=\"toolDetail?.name\"\n              :name=\"toolDetail?.name\"\n              pinyinColor\n              shape=\"square\"\n              :size=\"64\"\n              class=\"mr-8\"\n            />\n            <div class=\"ml-16\">\n              <h3 class=\"mb-8\">{{ toolDetail.name }}</h3>\n              <el-text type=\"info\" v-if=\"toolDetail?.desc\">\n                {{ toolDetail.desc }}\n              </el-text>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"mt-16\" v-if=\"toolDetail?.downloads != undefined\">\n          <el-text type=\"info\">\n            <div>\n              {{ $t('views.document.upload.download') }}:\n              {{ numberFormat(toolDetail?.downloads || 0) }}\n            </div>\n          </el-text>\n        </div>\n      </div>\n      <MdPreview\n        ref=\"editorRef\"\n        editorId=\"preview-only\"\n        :modelValue=\"markdownContent\"\n        style=\"background: none\"\n        noImgZoomIn\n      />\n    </div>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport { isAppIcon, numberFormat } from '@/utils/common'\nconst emit = defineEmits(['refresh', 'addTool'])\n\nconst visibleInternalDesc = ref(false)\nconst markdownContent = ref('')\nconst toolDetail = ref<any>({})\n\nwatch(visibleInternalDesc, (bool) => {\n  if (!bool) {\n    markdownContent.value = ''\n  }\n})\n\nconst open = (data: any, detail: any) => {\n  toolDetail.value = detail\n  if (data) {\n    markdownContent.value = cloneDeep(data)\n  }\n  visibleInternalDesc.value = true\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/tool/component/UserFieldFormDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"isEdit ? $t('common.param.editParam') : $t('common.param.addParam')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <DynamicsFormConstructor\n      v-model=\"currentRow\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      :input_type_list=\"inputTypeList\"\n      ref=\"DynamicsFormConstructorRef\"\n    ></DynamicsFormConstructor>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"close\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit()\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport _ from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst DynamicsFormConstructorRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\nconst currentItem = ref<FormField | any>()\nconst check_field = (field_list: Array<string>, obj: any) => {\n  return field_list.every((field) => _.get(obj, field, undefined) !== undefined)\n}\nconst currentRow = computed(() => {\n  if (currentItem.value) {\n    const row = currentItem.value\n    switch (row.type) {\n      case 'input':\n        if (check_field(['field', 'input_type', 'label', 'required', 'attrs'], currentItem.value)) {\n          return currentItem.value\n        }\n        return {\n          attrs: row.attrs || { maxlength: 200, minlength: 0 },\n          field: row.field || row.variable,\n          input_type: 'TextInput',\n          label: row.label || row.name,\n          default_value: row.default_value,\n          required: row.required != undefined ? row.required : row.is_required,\n        }\n      case 'select':\n        if (\n          check_field(\n            ['field', 'input_type', 'label', 'required', 'option_list'],\n            currentItem.value,\n          )\n        ) {\n          return currentItem.value\n        }\n        return {\n          attrs: row.attrs || {},\n          field: row.field || row.variable,\n          input_type: 'SingleSelect',\n          label: row.label || row.name,\n          default_value: row.default_value,\n          required: row.required != undefined ? row.required : row.is_required,\n          option_list: row.option_list\n            ? row.option_list\n            : row.optionList.map((o: any) => {\n                return { key: o, value: o }\n              }),\n        }\n\n      case 'date':\n        if (\n          check_field(\n            [\n              'field',\n              'input_type',\n              'label',\n              'required',\n              'attrs.format',\n              'attrs.value-format',\n              'attrs.type',\n            ],\n            currentItem.value,\n          )\n        ) {\n          return currentItem.value\n        }\n        return {\n          field: row.field || row.variable,\n          input_type: 'DatePicker',\n          label: row.label || row.name,\n          default_value: row.default_value,\n          required: row.required != undefined ? row.required : row.is_required,\n          attrs: {\n            format: 'YYYY-MM-DD HH:mm:ss',\n            'value-format': 'YYYY-MM-DD HH:mm:ss',\n            type: 'datetime',\n          },\n        }\n      default:\n        return currentItem.value\n    }\n  } else {\n    return {\n      input_type: 'TextInput',\n      required: false,\n      attrs: { maxlength: 200, minlength: 0 },\n      show_default_value: true,\n    }\n  }\n})\nconst currentIndex = ref(null)\nconst inputTypeList = ref([\n  { label: t('dynamicsForm.input_type_list.TextInput'), value: 'TextInputConstructor' },\n  { label: t('dynamicsForm.input_type_list.Slider'), value: 'SliderConstructor' },\n  { label: t('dynamicsForm.input_type_list.PasswordInput'), value: 'PasswordInputConstructor' },\n  { label: t('dynamicsForm.input_type_list.SingleSelect'), value: 'SingleSelectConstructor' },\n  { label: t('dynamicsForm.input_type_list.MultiSelect'), value: 'MultiSelectConstructor' },\n  { label: t('dynamicsForm.input_type_list.RadioCard'), value: 'RadioCardConstructor' },\n  { label: t('dynamicsForm.input_type_list.DatePicker'), value: 'DatePickerConstructor' },\n  { label: t('dynamicsForm.input_type_list.SwitchInput'), value: 'SwitchInputConstructor' },\n  { label: t('dynamicsForm.input_type_list.JsonInput'), value: 'JsonInputConstructor' },\n])\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any, index: any) => {\n  dialogVisible.value = true\n\n  if (row) {\n    isEdit.value = true\n    currentItem.value = cloneDeep(row)\n    currentIndex.value = index\n  } else {\n    currentItem.value = null\n  }\n}\n\nconst close = () => {\n  dialogVisible.value = false\n  isEdit.value = false\n  currentIndex.value = null\n  currentItem.value = null as any\n}\n\nconst submit = async () => {\n  const formEl = DynamicsFormConstructorRef.value\n  if (!formEl) return\n  await formEl.validate().then(() => {\n    emit('refresh', formEl?.getData(), currentIndex.value)\n    isEdit.value = false\n    currentItem.value = null as any\n    currentIndex.value = null\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/execution-record/ExecutionDetailDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"visible\"\n    size=\"800px\"\n    :modal=\"false\"\n    destroy-on-close\n    :before-close=\"closeHandle\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :show-close=\"false\"\n  >\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <el-button class=\"cursor mr-4\" link @click.prevent=\"visible = false\">\n          <el-icon :size=\"20\">\n            <Back />\n          </el-icon>\n        </el-button>\n        <h4>{{ $t('chat.executionDetails.title') }}</h4>\n      </div>\n    </template>\n    <div>\n      <el-scrollbar>\n        <h4 class=\"title-decoration-1 mb-16 mt-4\">\n          {{ $t('common.ExecutionRecord.title') }}\n        </h4>\n        <el-card class=\"mb-24\" shadow=\"never\" style=\"--el-card-padding: 12px 16px\">\n          <el-row :gutter=\"16\" class=\"lighter\">\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('views.trigger.triggerSource') }}</p>\n              <p class=\"flex align-center\">\n                <KnowledgeIcon\n                  :size=\"22\"\n                  v-if=\"props.currentContent?.source_type === 'KNOWLEDGE'\"\n                  :type=\"4\"\n                />\n                <TriggerIcon\n                  v-else-if=\"props.currentContent?.source_type === 'TRIGGER'\"\n                  :type=\"props.currentContent?.trigger_type\"\n                  :size=\"22\"\n                />\n                <el-avatar shape=\"square\" :size=\"22\" style=\"background: none\" v-else>\n                  <img\n                    :src=\"resetUrl(props.currentContent?.source_icon, resetUrl('./favicon.ico'))\"\n                    alt=\"\"\n                  />\n                </el-avatar>\n\n                <span class=\"ellipsis-1 ml-8\" :title=\"props.currentContent?.source_name\">{{\n                  props.currentContent?.source_name || '-'\n                }}</span>\n              </p>\n            </el-col>\n            <el-col :span=\"6\" v-if=\"apiType === 'systemShare'\">\n              <p class=\"color-secondary mb-4\">{{ $t('views.workspace.title') }}</p>\n              <p class=\"flex align-center\">\n                {{props.currentContent?.workspace_name}}\n              </p>\n            </el-col>\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('common.status.label') }}</p>\n              <p>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-if=\"props.currentContent?.state === 'SUCCESS'\"\n                >\n                  <el-icon class=\"color-success\"><SuccessFilled /></el-icon>\n                  {{ $t('common.status.success') }}\n                </el-text>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-else-if=\"props.currentContent?.state === 'FAILURE'\"\n                >\n                  <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n                  {{ $t('common.status.fail') }}\n                </el-text>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-else-if=\"props.currentContent?.state === 'REVOKED'\"\n                >\n                  <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n                  {{ $t('common.status.REVOKED') }}\n                </el-text>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-else-if=\"props.currentContent?.state === 'REVOKE'\"\n                >\n                  <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n                  {{ $t('common.status.REVOKE') }}\n                </el-text>\n                <el-text class=\"color-text-primary\" v-else>\n                  <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n                  {{ $t('common.status.STARTED') }}\n                </el-text>\n              </p>\n            </el-col>\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('chat.KnowledgeSource.consumeTime') }}</p>\n              <p>\n                {{\n                  props.currentContent?.run_time != undefined\n                    ? props.currentContent?.run_time?.toFixed(2) + 's'\n                    : '-'\n                }}\n              </p>\n            </el-col>\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('chat.executionDetails.createTime') }}</p>\n              <p>{{ datetimeFormat(props.currentContent?.create_time) }}</p>\n            </el-col>\n          </el-row>\n        </el-card>\n        <h4 class=\"title-decoration-1 mb-16 mt-4\">\n          {{ $t('chat.executionDetails.title') }}\n        </h4>\n        <el-card class=\"mb-8\" shadow=\"never\" style=\"--el-card-padding: 12px 16px\">\n          <div class=\"flex-between cursor\" @click=\"showDetail = !showDetail\">\n            <div class=\"flex align-center\">\n              <el-icon class=\"mr-8 arrow-icon\" :class=\"showDetail ? 'rotate-90' : ''\">\n                <CaretRight />\n              </el-icon>\n              <el-avatar\n                v-if=\"detail?.tool_icon\"\n                shape=\"square\"\n                :size=\"24\"\n                style=\"background: none\"\n              >\n                <img :src=\"resetUrl(detail?.tool_icon)\" alt=\"\" />\n              </el-avatar>\n              <ToolIcon v-else :size=\"24\" :type=\"detail?.tool_type\" />\n              <h4 class=\"ml-8\">{{ detail?.tool_name }}</h4>\n            </div>\n            <div class=\"flex align-center\">\n              <span class=\"mr-16 color-secondary\" v-if=\"detail?.state !== 'STARTED'\"\n                >{{ detail?.run_time?.toFixed(2) || 0.0 }} s</span\n              >\n              <el-icon class=\"color-success\" :size=\"16\" v-if=\"detail?.state === 'SUCCESS'\">\n                <CircleCheck />\n              </el-icon>\n              <el-icon class=\"is-loading\" :size=\"16\" v-else-if=\"detail?.state === 'STARTED'\">\n                <Loading />\n              </el-icon>\n              <el-icon class=\"color-danger\" :size=\"16\" v-else>\n                <CircleClose />\n              </el-icon>\n            </div>\n          </div>\n          <el-collapse-transition>\n            <div class=\"mt-12\" v-if=\"showDetail\">\n              <!-- 工具库 -->\n              <div class=\"card-never border-r-6 mt-8\">\n                <h5 class=\"p-8-12\">{{ $t('chat.executionDetails.input') }}</h5>\n                <div class=\"p-8-12 border-t-dashed lighter break-all\">\n                  {{ detail?.meta.input || '-' }}\n                </div>\n              </div>\n              <div class=\"card-never border-r-6 mt-8\">\n                <h5 class=\"p-8-12\">{{ $t('chat.executionDetails.output') }}</h5>\n                <div class=\"p-8-12 border-t-dashed lighter break-all\">\n                  {{ detail?.meta.output || '-' }}\n                </div>\n              </div>\n            </div>\n          </el-collapse-transition>\n        </el-card>\n      </el-scrollbar>\n    </div>\n    <template #footer>\n      <div>\n        <el-button @click=\"pre\" :disabled=\"pre_disable || loading\">{{\n          $t('common.pages.prev')\n        }}</el-button>\n        <el-button @click=\"next\" :disabled=\"next_disable || loading\">{{\n          $t('common.pages.next')\n        }}</el-button>\n      </div>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport { datetimeFormat } from '@/utils/time'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nconst props = withDefaults(\n  defineProps<{\n    /**\n     * 当前的action_id\n     */\n    currentId: string\n    currentContent: any\n    /**\n     * 下一条\n     */\n    next: () => void\n    /**\n     * 上一条\n     */\n    pre: () => void\n\n    pre_disable: boolean\n\n    next_disable: boolean\n  }>(),\n  {},\n)\n\nconst emit = defineEmits(['update:currentId', 'update:currentContent'])\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst detail = ref<any>(null)\nconst showDetail = ref<boolean>(true)\n\nconst loading = ref(false)\nconst visible = ref(false)\n\nfunction closeHandle() {}\n\nwatch(\n  () => props.currentId,\n  () => {\n    if (props.currentId) {\n      getDetail()\n    }\n  },\n)\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    emit('update:currentId', '')\n    emit('update:currentContent', null)\n  }\n})\n\nfunction getDetail() {\n  if (!props.currentContent) {\n    return\n  }\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getToolRecordDetail(props.currentContent?.tool_id, props.currentContent?.id, loading)\n    .then((ok: any) => {\n      detail.value = ok.data\n    })\n}\n\nconst open = (row: any) => {\n  visible.value = true\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/tool/execution-record/TriggerRecordDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"drawer\"\n    :title=\"$t('common.ExecutionRecord.title')\"\n    direction=\"rtl\"\n    size=\"800px\"\n    :before-close=\"close\"\n  >\n    <div class=\"flex-between mb-16\">\n      <div class=\"flex-between complex-search\">\n        <el-select\n          class=\"complex-search__left\"\n          v-model=\"searchType\"\n          @change=\"changeFilterHandle\"\n          style=\"width: 100px\"\n        >\n          <el-option :label=\"$t('views.trigger.triggerSource')\" value=\"source_name\" />\n          <el-option :label=\"$t('common.type')\" value=\"source_type\" />\n          <el-option :label=\"$t('common.status.label')\" value=\"state\" />\n        </el-select>\n        <el-input\n          v-if=\"searchType === 'source_name'\"\n          v-model=\"query.source_name\"\n          :placeholder=\"$t('common.search')\"\n          style=\"width: 220px\"\n          clearable\n          @change=\"getList(true)\"\n        />\n        <el-select\n          v-else-if=\"searchType === 'state'\"\n          v-model=\"query.state\"\n          @change=\"getList(true)\"\n          filterable\n          clearable\n          :reserve-keyword=\"false\"\n          collapse-tags\n          collapse-tags-tooltip\n          style=\"width: 220px\"\n          :placeholder=\"$t('common.search')\"\n        >\n          <el-option :label=\"$t('common.status.success')\" value=\"SUCCESS\" />\n          <el-option :label=\"$t('common.status.STARTED')\" value=\"STARTED\" />\n          <el-option :label=\"$t('common.status.fail')\" value=\"FAILURE\" />\n        </el-select>\n        <el-select\n          v-else\n          v-model=\"query.source_type\"\n          @change=\"getList(true)\"\n          filterable\n          clearable\n          :reserve-keyword=\"false\"\n          collapse-tags\n          collapse-tags-tooltip\n          style=\"width: 220px\"\n          :placeholder=\"$t('common.search')\"\n        >\n          <el-option :label=\"$t('views.application.title')\" value=\"APPLICATION\" />\n          <el-option :label=\"$t('views.knowledge.title')\" value=\"KNOWLEDGE\" />\n          <el-option :label=\"$t('views.trigger.title')\" value=\"TRIGGER\" />\n        </el-select>\n      </div>\n    </div>\n\n    <app-table\n      ref=\"multipleTableRef\"\n      class=\"mt-16\"\n      :data=\"tableData\"\n      :pagination-config=\"paginationConfig\"\n      @sizeChange=\"changeSize\"\n      @changePage=\"getList(true)\"\n      :default-sort=\"{ prop: 'create_time', order: 'descending' }\"\n      @sort-change=\"handleSortChange\"\n      :maxTableHeight=\"200\"\n      :row-key=\"(row: any) => row.id\"\n      v-loading=\"loading\"\n    >\n      <el-table-column\n        prop=\"name\"\n        :label=\"$t('views.trigger.triggerSource')\"\n        min-width=\"130\"\n        show-overflow-tooltip\n      >\n        <template #default=\"{ row }\">\n          <el-space :size=\"8\">\n            <KnowledgeIcon :size=\"22\" v-if=\"row.source_type === 'KNOWLEDGE'\" :type=\"4\" />\n            <TriggerIcon\n              v-else-if=\"row.source_type === 'TRIGGER'\"\n              :type=\"row.trigger_type\"\n              :size=\"22\"\n            />\n            <el-avatar shape=\"square\" :size=\"22\" style=\"background: none\" v-else>\n              <img :src=\"resetUrl(row?.source_icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n            </el-avatar>\n            <span class=\"ellipsis\">{{ row.source_name }}</span>\n          </el-space>\n        </template>\n      </el-table-column>\n      <el-table-column\n        prop=\"source_type\"\n        width=\"100\"\n        show-overflow-tooltip\n        :label=\"$t('common.type')\"\n      >\n        <template #default=\"{ row }\">\n          <span v-if=\"row.source_type === 'APPLICATION'\">{{ $t('views.application.title') }}</span>\n          <span v-else-if=\"row.source_type === 'KNOWLEDGE'\">{{ $t('views.knowledge.title') }}</span>\n          <span v-else>{{ $t('views.trigger.title') }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column prop=\"state\" :label=\"$t('common.status.label')\" width=\"100\">\n        <template #default=\"{ row }\">\n          <el-text class=\"color-text-primary\" v-if=\"row.state === 'SUCCESS'\">\n            <el-icon class=\"color-success\"><SuccessFilled /></el-icon>\n            {{ $t('common.status.success') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else-if=\"row.state === 'FAILURE'\">\n            <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n            {{ $t('common.status.fail') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else-if=\"row.state === 'REVOKED'\">\n            <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n            {{ $t('common.status.REVOKED') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else-if=\"row.state === 'REVOKE'\">\n            <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n            {{ $t('common.status.REVOKE') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else>\n            <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n            {{ $t('common.status.STARTED') }}\n          </el-text>\n        </template>\n      </el-table-column>\n      <el-table-column prop=\"run_time\" :label=\"$t('chat.KnowledgeSource.consumeTime')\">\n        <template #default=\"{ row }\">\n          {{ row.run_time != undefined ? row.run_time?.toFixed(2) + 's' : '-' }}\n        </template>\n      </el-table-column>\n      <el-table-column\n        v-if=\"apiType === 'systemShare'\"\n        prop=\"workspace_name\"\n        :label=\"$t('views.workspace.title')\"\n      >\n        <template #default=\"{ row }\">\n          {{ row.workspace_name }}\n        </template>\n      </el-table-column>\n      <el-table-column\n        sortable\n        prop=\"create_time\"\n        :label=\"$t('chat.executionDetails.createTime')\"\n        width=\"180\"\n      >\n        <template #default=\"{ row }\">\n          {{ datetimeFormat(row.create_time) }}\n        </template>\n      </el-table-column>\n\n      <el-table-column :label=\"$t('common.operation')\" width=\"90\">\n        <template #default=\"{ row }\">\n          <div class=\"flex\">\n            <el-tooltip effect=\"dark\" :content=\"$t('chat.executionDetails.title')\" placement=\"top\">\n              <el-button type=\"primary\" text @click.stop=\"toDetails(row)\">\n                <AppIcon iconName=\"app-operate-log\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </div>\n        </template>\n      </el-table-column>\n    </app-table>\n\n    <ExecutionDetailDrawer\n      ref=\"ExecutionDetailDrawerRef\"\n      v-model:currentId=\"currentId\"\n      v-model:currentContent=\"currentContent\"\n      :next=\"nextRecord\"\n      :pre=\"preRecord\"\n      :pre_disable=\"pre_disable\"\n      :next_disable=\"next_disable\"\n    />\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, computed } from 'vue'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport { datetimeFormat } from '@/utils/time'\nimport type { Dict } from '@/api/type/common'\nimport ExecutionDetailDrawer from './ExecutionDetailDrawer.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport { useRoute } from 'vue-router'\nconst route = useRoute()\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst searchType = ref<string>('source_name')\nconst drawer = ref<boolean>(false)\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\nconst tableData = ref<Array<any>>([])\nconst query = ref<any>({\n  state: '',\n  name: '',\n  order: '',\n})\nconst loading = ref<boolean>(false)\nconst current_trigger_id = ref<string>()\n\nconst ExecutionDetailDrawerRef = ref<any>()\n\nconst currentId = ref<string>('')\nconst currentContent = ref<string>('')\n\nconst toDetails = (row: any) => {\n  currentContent.value = row\n  currentId.value = row.id\n  ExecutionDetailDrawerRef.value?.open(row)\n}\n\nconst changeFilterHandle = () => {\n  query.value = { name: '', statu: '' }\n}\nconst changeSize = () => {\n  paginationConfig.current_page = 1\n  getList()\n}\nfunction handleSortChange({ prop, order }: { prop: string; order: string }) {\n  query.value.order = order === 'ascending' ? `ett.${prop}` : `-ett.${prop}`\n  getList()\n}\n\nconst getList = (isLoading?: boolean) => {\n  if (current_trigger_id.value) {\n    return loadSharedApi({ type: 'tool', systemType: apiType.value })\n      .pageToolRecord(\n        current_trigger_id.value,\n        paginationConfig,\n        { ...query.value },\n        isLoading ? loading : undefined,\n      )\n      .then((ok: any) => {\n        tableData.value = ok.data.records\n        paginationConfig.total = ok.data.total\n      })\n  } else return Promise.resolve()\n}\n\nconst pre_disable = computed(() => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value)\n  return index === 0 && paginationConfig.current_page === 1\n})\n\nconst next_disable = computed(() => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value) + 1\n  return (\n    index >= tableData.value.length &&\n    index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=\n      paginationConfig.total - 1\n  )\n})\n\n/**\n * 下一页\n */\nconst nextRecord = () => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value) + 1\n  if (index >= tableData.value.length) {\n    if (paginationConfig.current_page * paginationConfig.page_size >= paginationConfig.total) {\n      return\n    }\n    paginationConfig.current_page = paginationConfig.current_page + 1\n    getList(true).then(() => {\n      currentId.value = tableData.value[index].id\n      currentContent.value = tableData.value[index]\n    })\n    return\n  } else {\n    currentId.value = tableData.value[index].id\n    currentContent.value = tableData.value[index]\n  }\n}\n/**\n * 上一页\n */\nconst preRecord = () => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value) - 1\n  if (index < 0 && 1) {\n    if (paginationConfig.current_page === 1) {\n      return\n    }\n    paginationConfig.current_page = paginationConfig.current_page - 1\n    getList(true).then(() => {\n      currentId.value = tableData.value[tableData.value.length - 1].id\n      currentContent.value = tableData.value[tableData.value.length - 1]\n    })\n  } else {\n    currentId.value = tableData.value[index].id\n    currentContent.value = tableData.value[index]\n  }\n}\nconst open = (tool: any) => {\n  current_trigger_id.value = tool.id\n  getList(true)\n  drawer.value = true\n}\nconst close = () => {\n  paginationConfig.current_page = 1\n  paginationConfig.total = 0\n  tableData.value = []\n  drawer.value = false\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/index.vue",
    "content": "<template>\n  <LayoutContainer showCollapse resizable class=\"tool-manage\">\n    <template #left>\n      <h4 class=\"p-12-16 pb-0 mt-12\">{{ $t('views.tool.title') }}</h4>\n\n      <folder-tree\n        :source=\"SourceTypeEnum.TOOL\"\n        :data=\"folderList\"\n        :currentNodeKey=\"folder.currentFolder?.id\"\n        @handleNodeClick=\"folderClickHandle\"\n        @refreshTree=\"refreshFolder\"\n        :shareTitle=\"$t('views.shared.shared_tool')\"\n        :showShared=\"permissionPrecise['is_share']()\"\n        :draggable=\"true\"\n      />\n    </template>\n    <ToolListContainer @refreshFolder=\"refreshFolder\">\n      <template #header>\n        <el-space wrap>\n          <h2 v-if=\"folder.currentFolder?.id === 'share'\">\n            {{ $t('views.shared.shared_tool') }}\n          </h2>\n          <FolderBreadcrumb :folderList=\"folderList\" @click=\"folderClickHandle\" v-else />\n          <el-divider direction=\"vertical\" />\n          <el-radio-group v-model=\"toolType\" @change=\"radioChange\" class=\"app-radio-button-group\">\n            <el-radio-button value=\"\">{{ $t('common.status.all') }}</el-radio-button>\n            <el-radio-button value=\"CUSTOM\">{{ $t('views.tool.title') }}</el-radio-button>\n            <el-radio-button value=\"SKILL\">Skills</el-radio-button>\n            <el-radio-button value=\"MCP\">MCP</el-radio-button>\n            <el-radio-button value=\"DATA_SOURCE\">{{\n              $t('views.tool.dataSource.title')\n            }}</el-radio-button>\n            <el-radio-button value=\"WORKFLOW\">工作流</el-radio-button>\n          </el-radio-group>\n        </el-space>\n      </template>\n    </ToolListContainer>\n  </LayoutContainer>\n</template>\n\n<script lang=\"ts\" setup>\nimport { onMounted, ref, reactive, computed } from 'vue'\nimport ToolListContainer from '@/views/tool/component/ToolListContainer.vue'\nimport { SourceTypeEnum } from '@/enums/common'\nimport permissionMap from '@/permission'\nimport { useRoute } from 'vue-router'\nimport useStore from '@/stores'\nimport bus from '@/bus'\nconst route = useRoute()\nconst { folder, tool } = useStore()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst permissionPrecise = computed(() => {\n  return permissionMap['tool'][apiType.value]\n})\n\nconst loading = ref(false)\nconst toolType = ref('')\n\nconst folderList = ref<any[]>([])\n\nfunction getFolder(bool?: boolean) {\n  const params = {}\n  folder.asyncGetFolder(SourceTypeEnum.TOOL, params, apiType.value, loading).then((res: any) => {\n    folderList.value = res.data\n    if (bool) {\n      // 初始化刷新\n      folder.setCurrentFolder(res.data?.[0] || {})\n    }\n  })\n}\n\nfunction folderClickHandle(row: any) {\n  if (row.id === folder.currentFolder?.id) {\n    return\n  }\n  folder.setCurrentFolder(row)\n  tool.setToolList([])\n}\n\nfunction radioChange() {\n  tool.setToolType(toolType.value)\n}\n\nfunction refreshFolder() {\n  getFolder()\n}\n\nonMounted(() => {\n  getFolder(folder.currentFolder?.id ? false : true)\n  radioChange()\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/tool-store/AddInternalToolDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('views.tool.form.toolName.name')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    append-to-body\n    width=\"450\"\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item prop=\"name\" :label=\"$t('common.name')\">\n        <el-input v-model=\"form.name\" maxlength=\"64\" show-word-limit></el-input>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\n\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref<boolean>(false)\n\nconst form = ref<any>({\n  name: ''\n})\n\nconst rules = reactive({\n  name: [\n    {\n      required: true,\n      message: t('views.tool.form.toolName.placeholder'),\n      trigger: 'blur'\n    }\n  ]\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      name: ''\n    }\n  }\n})\n\nconst open = (row: any, edit?: boolean) => {\n  if (row) {\n    form.value = cloneDeep(row)\n  }\n  isEdit.value = edit || false\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value, isEdit.value)\n      dialogVisible.value = false\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool/tool-store/InternalDescDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visibleInternalDesc\" size=\"60%\" :append-to-body=\"true\">\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <el-button class=\"cursor mr-4\" link @click.prevent=\"visibleInternalDesc = false\">\n          <el-icon :size=\"20\">\n            <Back />\n          </el-icon>\n        </el-button>\n        <h4>{{ $t('common.detail') }}</h4>\n      </div>\n    </template>\n\n    <div>\n      <div class=\"card-header\">\n        <div class=\"flex-between\">\n          <div class=\"title flex align-center\">\n            <el-avatar\n              v-if=\"isAppIcon(toolDetail?.icon)\"\n              shape=\"square\"\n              :size=\"64\"\n              style=\"background: none\"\n              class=\"mr-8\"\n            >\n              <img :src=\"toolDetail?.icon\" alt=\"\" />\n            </el-avatar>\n            <el-avatar\n              v-else-if=\"toolDetail?.name\"\n              :name=\"toolDetail?.name\"\n              pinyinColor\n              shape=\"square\"\n              :size=\"64\"\n              class=\"mr-8\"\n            />\n            <div class=\"ml-16\">\n              <h3 class=\"mb-8\">{{ toolDetail.name }}</h3>\n              <el-text type=\"info\" v-if=\"toolDetail?.desc\">\n                {{ toolDetail.desc }}\n              </el-text>\n            </div>\n          </div>\n          <div @click.stop>\n            <el-button type=\"primary\" @click=\"addInternalTool(toolDetail)\">\n              {{ $t('common.add') }}\n            </el-button>\n          </div>\n        </div>\n      </div>\n      <MdPreview\n        ref=\"editorRef\"\n        editorId=\"preview-only\"\n        :modelValue=\"markdownContent\"\n        style=\"background: none\"\n        noImgZoomIn\n      />\n    </div>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport { isAppIcon, numberFormat } from '@/utils/common'\nconst emit = defineEmits(['refresh', 'addTool'])\n\nconst visibleInternalDesc = ref(false)\nconst markdownContent = ref('')\nconst toolDetail = ref<any>({})\n\nwatch(visibleInternalDesc, (bool) => {\n  if (!bool) {\n    markdownContent.value = ''\n  }\n})\n\nconst open = (data: any, detail: any) => {\n  toolDetail.value = detail\n  if (data) {\n    markdownContent.value = cloneDeep(data)\n  }\n  visibleInternalDesc.value = true\n}\n\nconst addInternalTool = (data: any) => {\n  emit('addTool', data)\n  visibleInternalDesc.value = false\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/tool/tool-store/ToolCard.vue",
    "content": "<template>\n  <CardBox :title=\"props.tool.name\" :description=\"props.tool.desc\" class=\"cursor tool-card\">\n    <template #icon>\n      <el-avatar\n        v-if=\"isAppIcon(props.tool?.icon)\"\n        shape=\"square\"\n        :size=\"32\"\n        style=\"background: none\"\n      >\n        <img :src=\"resetUrl(props.tool?.icon)\" alt=\"\" />\n      </el-avatar>\n      <el-avatar\n        v-else-if=\"props.tool?.name\"\n        :name=\"props.tool?.name\"\n        pinyinColor\n        shape=\"square\"\n        :size=\"32\"\n      />\n    </template>\n    <template #title>\n      <div class=\"flex align-center\">\n        <span :title=\"props.tool?.name\" class=\"ellipsis\"> {{ props.tool?.name }}</span>\n        <el-tag v-if=\"props.tool?.version\" size=\"small\" class=\"ml-4\" type=\"info\" effect=\"plain\">\n          {{ props.tool?.version }}\n        </el-tag>\n      </div>\n    </template>\n    <template #tag>\n      <el-tag size=\"small\" type=\"info\" v-if=\"props.tool?.label === 'data_source'\" class=\"info-tag\">\n        {{ $t('views.tool.dataSource.title') }}\n      </el-tag>\n      <el-tag size=\"small\" type=\"info\" v-else-if=\"props.tool?.label === 'skill'\" class=\"info-tag\">\n        Skills\n      </el-tag>\n      <el-tag size=\"small\" type=\"info\" class=\"info-tag\" v-else>\n        {{ $t('views.tool.title') }}\n      </el-tag>\n    </template>\n    <template #subTitle>\n      <el-text class=\"color-secondary lighter\" size=\"small\">\n        {{ getSubTitle(props.tool) }}\n      </el-text>\n    </template>\n    <template #footer>\n      <span class=\"card-footer-left color-secondary\" v-if=\"props.tool?.downloads != undefined\">\n        {{ `${$t('views.document.upload.download')}: ${numberFormat(props.tool.downloads || 0)} ` }}\n      </span>\n      <div class=\"card-footer-operation mb-8\" @click.stop>\n        <el-button @click=\"emit('handleDetail')\">\n          {{ $t('common.detail') }}\n        </el-button>\n        <el-button type=\"primary\" :loading=\"props.addLoading\" @click=\"emit('handleAdd')\">\n          {{ $t('common.add') }}\n        </el-button>\n      </div>\n    </template>\n  </CardBox>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { isAppIcon, resetUrl, numberFormat } from '@/utils/common'\nconst props = defineProps<{\n  tool: any\n  getSubTitle: (v: any) => string\n  addLoading: boolean\n}>()\n\nconst emit = defineEmits<{\n  (e: 'handleAdd'): void\n  (e: 'handleDetail'): void\n}>()\n</script>\n\n<style lang=\"scss\" scoped>\n.tool-card {\n  :deep(.card-footer) {\n    & > div:first-of-type {\n      flex: 1;\n    }\n\n    .card-footer-operation {\n      display: none;\n    }\n  }\n\n  &:hover {\n    .card-footer-left {\n      display: none;\n    }\n\n    .card-footer-operation {\n      display: flex !important;\n\n      .el-button {\n        flex: 1;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/tool/tool-store/ToolStoreDialog.vue",
    "content": "<template>\n  <el-dialog\n    v-model=\"dialogVisible\"\n    width=\"1200\"\n    append-to-body\n    class=\"tool-store-dialog\"\n    align-center\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <template #header=\"{ titleId }\">\n      <div class=\"dialog-header flex-between mb-8\">\n        <h4 :id=\"titleId\" class=\"medium w-240 mr-8\">\n          {{ $t('views.tool.toolStore.title') }}\n        </h4>\n\n        <div class=\"flex align-center\" style=\"margin-right: 28px\">\n          <el-input\n            v-model=\"searchValue\"\n            :placeholder=\"$t('common.search')\"\n            prefix-icon=\"Search\"\n            class=\"w-240 mr-8\"\n            clearable\n            @change=\"getList\"\n          />\n          <el-divider direction=\"vertical\" />\n        </div>\n      </div>\n    </template>\n\n    <LayoutContainer v-loading=\"loading\" :minLeftWidth=\"204\">\n      <template #left>\n        <el-anchor\n          direction=\"vertical\"\n          :offset=\"130\"\n          type=\"default\"\n          container=\".category-scrollbar\"\n          @click=\"handleClick\"\n        >\n          <el-anchor-link\n            v-for=\"category in categories\"\n            :key=\"category.id\"\n            :href=\"`#category-${category.id}`\"\n            :title=\"category.title\"\n          />\n        </el-anchor>\n      </template>\n\n      <el-scrollbar class=\"layout-bg\" wrap-class=\"p-16-24 category-scrollbar\">\n        <template v-if=\"filterList === null\">\n          <div v-for=\"category in categories\" :key=\"category.id\">\n            <h4\n              class=\"title-decoration-1 mb-16 mt-8 color-text-primary\"\n              :id=\"`category-${category.id}`\"\n            >\n              {{ category.title }}\n            </h4>\n            <el-row :gutter=\"16\">\n              <el-col v-for=\"tool in category.tools\" :key=\"tool.id\" :span=\"8\" class=\"mb-16\">\n                <ToolCard\n                  :tool=\"tool\"\n                  :addLoading=\"addLoading\"\n                  :get-sub-title=\"getSubTitle\"\n                  @handleAdd=\"handleOpenAdd(tool)\"\n                  @handleDetail=\"handleDetail(tool)\"\n                >\n                </ToolCard>\n              </el-col>\n            </el-row>\n          </div>\n        </template>\n        <div v-else>\n          <h4 class=\"color-text-primary medium mb-16\">\n            <span class=\"color-primary\">{{ searchValue }}</span>\n            {{ t('views.tool.toolStore.searchResult', { count: filterList.length }) }}\n          </h4>\n          <el-row :gutter=\"16\" v-if=\"filterList.length\">\n            <el-col v-for=\"tool in filterList\" :key=\"tool.id\" :span=\"8\" class=\"mb-16\">\n              <ToolCard\n                :tool=\"tool\"\n                :addLoading=\"addLoading\"\n                :get-sub-title=\"getSubTitle\"\n                @handleAdd=\"handleOpenAdd(tool)\"\n                @handleDetail=\"handleDetail(tool)\"\n              />\n            </el-col>\n          </el-row>\n          <el-empty v-else :description=\"$t('common.noData')\" />\n        </div>\n      </el-scrollbar>\n    </LayoutContainer>\n  </el-dialog>\n  <InternalDescDrawer ref=\"internalDescDrawerRef\" @addTool=\"handleOpenAdd\" />\n  <AddInternalToolDialog ref=\"addInternalToolDialogRef\" @refresh=\"handleAdd\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { onBeforeMount, ref, watch } from 'vue'\nimport ToolStoreApi from '@/api/tool/store'\nimport { t } from '@/locales'\nimport ToolCard from './ToolCard.vue'\nimport { MsgSuccess } from '@/utils/message'\nimport InternalDescDrawer from './InternalDescDrawer.vue'\nimport AddInternalToolDialog from './AddInternalToolDialog.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport useStore from '@/stores'\nconst { user } = useStore()\ninterface ToolCategory {\n  id: string\n  title: string\n  tools: any[]\n}\nconst props = defineProps({\n  apiType: {\n    type: String as () => 'workspace' | 'systemShare' | 'systemManage',\n    default: 'workspace',\n  },\n})\nconst emit = defineEmits(['refresh'])\n\nconst dialogVisible = ref(false)\nconst loading = ref(false)\nconst searchValue = ref('')\nconst folderId = ref('')\nconst defaultCategories = ref<ToolCategory[]>([\n  {\n    id: 'web_search',\n    title: t('views.tool.toolStore.webSearch'),\n    tools: [],\n  },\n  {\n    id: 'database_search',\n    title: t('views.tool.toolStore.databaseQuery'),\n    tools: [],\n  },\n])\nconst categories = ref<ToolCategory[]>([...defaultCategories.value])\n\nconst filterList = ref<any>(null)\n\nfunction getSubTitle(tool: any) {\n  return categories.value.find((i) => i.id === tool.label)?.title ?? ''\n}\n\nfunction open(id: string) {\n  folderId.value = id\n  filterList.value = null\n  dialogVisible.value = true\n\n  getList()\n}\n\nasync function getList() {\n  filterList.value = null\n  const [v1, v2] = await Promise.all([getInternalToolList(), getStoreToolList()])\n\n  const merged = [...v1, ...v2].reduce((acc, category) => {\n    const existing = acc.find((item: any) => item.id === category.id)\n    if (existing) {\n      existing.tools = [...existing.tools, ...category.tools]\n    } else {\n      acc.push({ ...category })\n    }\n    return acc\n  }, [] as ToolCategory[])\n\n  categories.value = merged.filter((item: any) => item.tools.length > 0)\n}\n\nasync function getInternalToolList() {\n  try {\n    const categories = defaultCategories.value\n    const res = await ToolStoreApi.getInternalToolList({ name: searchValue.value }, loading)\n    if (searchValue.value.length) {\n      filterList.value = [...res.data, ...(filterList.value || [])]\n    } else {\n      filterList.value = null\n      categories.forEach((category) => {\n        // if (category.id === 'recommend') {\n        //   category.tools = res.data\n        // } else {\n        category.tools = res.data.filter((tool: any) => tool.label === category.id)\n        // }\n      })\n    }\n    return categories\n  } catch (error) {\n    console.error(error)\n    return []\n  }\n}\n\nasync function getStoreToolList() {\n  try {\n    const res = await ToolStoreApi.getStoreToolList({ name: searchValue.value }, loading)\n    const tags = res.data.additionalProperties.tags\n    const storeTools = res.data.apps\n    let categories = []\n    //\n    storeTools.forEach((tool: any) => {\n      tool.desc = tool.description\n    })\n    if (searchValue.value.length) {\n      filterList.value = [...res.data.apps, ...(filterList.value || [])]\n    } else {\n      filterList.value = null\n      categories = tags.map((tag: any) => ({\n        id: tag.key,\n        title: tag.name, // 国际化\n        tools: storeTools.filter((tool: any) => tool.label === tag.key),\n      }))\n    }\n    return categories\n  } catch (error) {\n    console.error(error)\n    return []\n  }\n}\n\nconst handleClick = (e: MouseEvent) => {\n  e.preventDefault()\n}\n\nconst internalDescDrawerRef = ref<InstanceType<typeof InternalDescDrawer>>()\nasync function handleDetail(tool: any) {\n  console.log(tool)\n  if (tool.tool_type === 'INTERNAL') {\n    const index = tool.icon.replace('icon.png', 'detail.md')\n    const response = await fetch(index)\n    const content = await response.text()\n    internalDescDrawerRef.value?.open(content, tool)\n  } else {\n    internalDescDrawerRef.value?.open(tool.readMe, tool)\n  }\n}\n\nconst addInternalToolDialogRef = ref<InstanceType<typeof AddInternalToolDialog>>()\nfunction handleOpenAdd(data?: any, isEdit?: boolean) {\n  addInternalToolDialogRef.value?.open(data, isEdit)\n}\n\nconst addLoading = ref(false)\nasync function handleAdd(tool: any) {\n  if (tool.tool_type === 'INTERNAL') {\n    await handleInternalAdd(tool)\n  } else {\n    await handleStoreAdd(tool)\n  }\n}\n\nasync function handleInternalAdd(tool: any) {\n  try {\n    await loadSharedApi({ type: 'tool', systemType: props.apiType })\n      .addInternalTool(tool.id, { name: tool.name, folder_id: folderId.value }, addLoading)\n      .then(() => {\n        return user.profile()\n      })\n    emit('refresh')\n    MsgSuccess(t('common.addSuccess'))\n    dialogVisible.value = false\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nasync function handleStoreAdd(tool: any) {\n  try {\n    const obj = {\n      name: tool.name,\n      folder_id: folderId.value,\n      download_url: tool.downloadUrl,\n      download_callback_url: tool.downloadCallbackUrl,\n      icon: tool.icon,\n      versions: tool.versions,\n      label: tool.label,\n    }\n    await loadSharedApi({ type: 'tool', systemType: props.apiType })\n      .addStoreTool(tool.id, obj, addLoading)\n      .then(() => {\n        return user.profile()\n      })\n    emit('refresh')\n    MsgSuccess(t('common.addSuccess'))\n    dialogVisible.value = false\n  } catch (error) {\n    console.error(error)\n  }\n}\n\nfunction radioChange() {\n  searchValue.value = ''\n  getList()\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\">\n.tool-store-dialog {\n  padding: 0;\n  .el-dialog__headerbtn {\n    top: 7px;\n  }\n\n  .el-dialog__header {\n    padding: 12px 20px 4px 24px;\n    border-bottom: 1px solid var(--el-border-color-light);\n\n    .dialog-header {\n      position: relative;\n\n      .store-type {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n        transform: translate(-50%, -50%);\n      }\n    }\n  }\n\n  .layout-container__left {\n    background-color: var(--app-layout-bg-color);\n    border-radius: 0 0 0 8px;\n  }\n  .layout-container__right {\n    background-color: var(--app-layout-bg-color);\n    border-radius: 0 0 8px 0;\n  }\n\n  .el-anchor {\n    background-color: var(--app-layout-bg-color);\n\n    .el-anchor__marker {\n      display: none;\n    }\n\n    .el-anchor__list {\n      padding: 8px;\n    }\n\n    .el-anchor__item {\n      .el-anchor__link {\n        padding: 8px 16px;\n        font-weight: 500;\n        font-size: 14px;\n        color: var(--el-text-color-primary);\n        border-radius: 6px;\n\n        &.is-active {\n          color: var(--el-color-primary);\n          background-color: #3370ff1a;\n        }\n      }\n    }\n  }\n\n  .category-scrollbar {\n    height: calc(100vh - 200px);\n    // min-height: 500px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/tool-workflow/component/PublishHistory.vue",
    "content": "<template>\n  <div class=\"workflow-publish-history border-l white-bg\">\n    <h4 class=\"border-b p-16-24\">{{ $t('workflow.setting.releaseHistory') }}</h4>\n    <div class=\"list-height pt-0\">\n      <el-scrollbar>\n        <div class=\"p-8 pt-0\">\n          <common-list\n            :data=\"LogData\"\n            class=\"mt-8\"\n            v-loading=\"loading\"\n            @click=\"clickListHandle\"\n            @mouseenter=\"mouseenter\"\n            @mouseleave=\"mouseId = ''\"\n          >\n            <template #default=\"{ row, index }\">\n              <div class=\"flex-between\">\n                <div style=\"max-width: 80%\">\n                  <h5 :class=\"index === 0 ? 'primary' : ''\" class=\"flex align-center\">\n                    <ReadWrite\n                      @change=\"editName($event, row)\"\n                      :data=\"row.name || datetimeFormat(row.update_time)\"\n                      trigger=\"manual\"\n                      :write=\"row.writeStatus\"\n                      @close=\"closeWrite(row)\"\n                    />\n                    <el-tag v-if=\"index === 0\" class=\"default-tag ml-4\"\n                      >{{ $t('workflow.setting.latestRelease') }}\n                    </el-tag>\n                  </h5>\n                  <el-text type=\"info\" class=\"color-secondary flex align-center mt-8\">\n                    <el-avatar :size=\"20\" class=\"avatar-grey mr-4\">\n                      <el-icon>\n                        <UserFilled />\n                      </el-icon>\n                    </el-avatar>\n                    {{ row.publish_user_name }}\n                  </el-text>\n                </div>\n\n                <div @click.stop v-show=\"mouseId === row.id\">\n                  <el-dropdown trigger=\"click\" :teleported=\"false\">\n                    <el-button text>\n                      <AppIcon iconName=\"app-more\"></AppIcon>\n                    </el-button>\n                    <template #dropdown>\n                      <el-dropdown-menu>\n                        <el-dropdown-item\n                          v-if=\"permissionPrecise.workflow_edit(id)\"\n                          @click.stop=\"openEditVersion(row)\"\n                        >\n                          <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n                          {{ $t('common.edit') }}\n                        </el-dropdown-item>\n                        <el-dropdown-item @click=\"refreshVersion(row)\">\n                          <el-icon class=\"color-secondary\">\n                            <RefreshLeft />\n                          </el-icon>\n                          {{ $t('workflow.setting.restoreCurrentVersion') }}\n                        </el-dropdown-item>\n                      </el-dropdown-menu>\n                    </template>\n                  </el-dropdown>\n                </div>\n              </div>\n            </template>\n\n            <template #empty>\n              <div class=\"text-center\">\n                <el-text type=\"info\"> {{ $t('chat.noHistory') }}</el-text>\n              </div>\n            </template>\n          </common-list>\n        </div>\n      </el-scrollbar>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { datetimeFormat } from '@/utils/time'\nimport { MsgSuccess, MsgError } from '@/utils/message'\nimport { t } from '@/locales'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport permissionMap from '@/permission'\n\nconst route = useRoute()\nconst {\n  params: { id, folderId },\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst emit = defineEmits(['click', 'refreshVersion'])\nconst loading = ref(false)\nconst LogData = ref<any[]>([])\n\nconst mouseId = ref('')\n\nfunction mouseenter(row: any) {\n  mouseId.value = row.id\n}\n\nfunction clickListHandle(item: any) {\n  emit('click', item)\n}\n\nfunction refreshVersion(item: any) {\n  emit('refreshVersion', item)\n}\n\nfunction openEditVersion(item: any) {\n  item['writeStatus'] = true\n}\n\nfunction closeWrite(item: any) {\n  item['writeStatus'] = false\n}\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nfunction editName(val: string, item: any) {\n  if (val) {\n    const obj = {\n      name: val,\n    }\n    loadSharedApi({ type: 'tool', isShared: isShared.value, systemType: apiType.value })\n      .updateToolWorkflowVersion(id as string, item.id, obj, loading)\n      .then(() => {\n        MsgSuccess(t('common.modifySuccess'))\n        item['writeStatus'] = false\n        getList()\n      })\n  } else {\n    MsgError(t('workflow.tip.nameMessage'))\n  }\n}\n\nfunction getList() {\n  loadSharedApi({ type: 'tool', isShared: isShared.value, systemType: apiType.value })\n    .listToolWorkflowVersion(id, loading)\n    .then((res: any) => {\n      LogData.value = res.data\n    })\n}\n\nonMounted(() => {\n  getList()\n})\n</script>\n<style lang=\"scss\" scoped>\n.workflow-publish-history {\n  width: 320px;\n  position: absolute;\n  right: 0;\n  top: 57px;\n  height: calc(100vh - 57px);\n  z-index: 9;\n\n  .list-height {\n    height: calc(100vh - 120px);\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/tool-workflow/component/debug/index.vue",
    "content": "<template>\n  <div></div>\n</template>\n<script setup lang=\"ts\"></script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool-workflow/component/debug/parameters/index.vue",
    "content": "<template>\n  <div v-if=\"userInputFieldList.length > 0\" class=\"mb-16\">\n    <h4 class=\"title-decoration-1 mb-16\">\n      {{ $t('common.param.inputParam') }}\n    </h4>\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        ref=\"formRef\"\n        :model=\"userInputForm\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        hide-required-asterisk\n        @submit.prevent\n      >\n        <template v-for=\"(item, index) in userInputFieldList\" :key=\"index\">\n          <el-form-item\n            :label=\"item.label\"\n            :prop=\"item.field\"\n            :rules=\"{\n              required: item.is_required,\n              message: $t('views.tool.form.param.inputPlaceholder'),\n              trigger: 'blur',\n            }\"\n          >\n            <template #label>\n              <div class=\"flex\">\n                <span\n                  >{{ item.label }}\n                  <span class=\"color-danger\" v-if=\"item.is_required\">*</span></span\n                >\n                <el-tag type=\"info\" class=\"info-tag ml-4\">{{ item.type }}</el-tag>\n              </div>\n            </template>\n            <el-input\n              v-if=\"['string'].includes(item.type)\"\n              v-model=\"userInputForm[item.field]\"\n              :placeholder=\"$t('views.tool.form.param.inputPlaceholder')\"\n            />\n            <JsonInput\n              v-if=\"['array', 'dict'].includes(item.type)\"\n              v-model=\"userInputForm[item.field]\"\n            />\n            <el-input-number\n              v-if=\"['int', 'float'].includes(item.type)\"\n              v-model=\"userInputForm[item.field]\"\n            />\n            <el-switch\n              v-if=\"['boolean'].includes(item.type)\"\n              v-model=\"userInputForm[item.field]\"\n              :active-value=\"true\"\n              :inactive-value=\"false\"\n            />\n          </el-form-item>\n        </template>\n      </el-form>\n    </el-card>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport JsonInput from '@/components/dynamics-form/items/JsonInput.vue'\nimport { FormInstance } from 'element-plus'\nconst props = defineProps<{\n  workflow: any\n}>()\nconst userInputFieldList = computed(() => {\n  return (\n    props.workflow?.nodes?.find((node: any) => node.id === 'tool-base-node')?.properties\n      ?.user_input_field_list || []\n  )\n})\nconst formRef = ref<FormInstance>()\nconst userInputForm = ref<any>({})\nconst validate = () => {\n  return formRef.value?.validate()\n}\nconst getData = () => {\n  return userInputForm.value\n}\ndefineExpose({ validate, getData })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool-workflow/component/debug/result/index.vue",
    "content": "<template>\n  <el-tabs v-model=\"activeName\" class=\"demo-tabs\">\n    <el-tab-pane label=\"输出\" name=\"result\">\n      <div class=\"mt-8\">\n        <h4 class=\"title-decoration-1 mb-16 mt-16\">回复内容</h4>\n\n        <el-card\n          style=\"height: 400px; overflow: auto\"\n          :class=\"isSuccess ? '' : 'color-danger'\"\n          class=\"pre-wrap\"\n          shadow=\"never\"\n        >\n          <AnswerContent\n            :application=\"details\"\n            :loading=\"loading\"\n            v-model:chat-record=\"currentChat\"\n            type=\"ai-chat\"\n            :send-message=\"sendMessage\"\n            :chat-management=\"ChatManagement\"\n            :executionIsRightPanel=\"false\"\n            @open-execution-detail=\"() => {}\"\n            @openParagraph=\"() => {}\"\n            @openParagraphDocument=\"() => {}\"\n            :selection=\"true\"\n          ></AnswerContent>\n        </el-card>\n      </div>\n      <h4 class=\"title-decoration-1 mb-16 mt-16\">输出参数</h4>\n      <div class=\"mb-16\" v-if=\"isSuccess !== undefined\">\n        <el-alert\n          v-if=\"isSuccess\"\n          :title=\"$t('views.tool.form.debug.runSuccess')\"\n          type=\"success\"\n          show-icon\n          :closable=\"false\"\n        />\n        <el-alert\n          v-else\n          :title=\"$t('views.tool.form.debug.runFailed')\"\n          type=\"error\"\n          show-icon\n          :closable=\"false\"\n        />\n      </div>\n      <el-card\n        style=\"overflow: auto\"\n        :class=\"isSuccess ? '' : 'color-danger'\"\n        class=\"pre-wrap\"\n        shadow=\"never\"\n      >\n        {{ output }}\n      </el-card>\n    </el-tab-pane>\n    <el-tab-pane label=\"执行详情\" name=\"executionDetails\">\n      <template v-for=\"(item, index) in arraySort(executionDetails ?? [], 'index')\" :key=\"index\">\n        <ExecutionDetailCard :data=\"item\"> </ExecutionDetailCard>\n      </template>\n    </el-tab-pane>\n  </el-tabs>\n</template>\n<script setup lang=\"ts\">\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { ref, reactive, computed } from 'vue'\nimport { randomId } from '@/utils/common'\nimport { ChatManagement, type chatType } from '@/api/type/application'\nimport { t } from '@/locales'\nimport AnswerContent from '@/components/ai-chat/component/answer-content/index.vue'\nimport ExecutionDetailCard from '@/components/execution-detail-card/index.vue'\nimport { arraySort } from '@/utils/array'\n\nconst props = defineProps<{\n  isShared: boolean\n  apiType: 'systemShare' | 'workspace' | 'systemManage' | 'workspaceShare'\n  toolDetails: any\n}>()\nconst activeName = ref<string>('result')\nconst details = {\n  show_avatar: false,\n  show_user_avatar: false,\n}\nconst currentToolId = ref<string>()\nconst currentData = ref<any>({})\n\nconst output = computed(() => {\n  if (toolRecord.value) {\n    return toolRecord.value.meta.output\n  }\n  return {}\n})\n\nconst executionDetails = computed(() => {\n  if (toolRecord.value) {\n    return Object.values(toolRecord.value.meta.details)\n  }\n  return []\n})\n\nconst showResult = ref<boolean>(false)\n\nconst isSuccess = computed(() => {\n  if (toolRecord.value) {\n    return toolRecord.value.state == 'FAILURE' ? false : true\n  }\n  return undefined\n})\n\nconst toolRecord = ref<any>()\n\nconst execute = (toolId: string, data: any) => {\n  currentToolId.value = toolId\n  currentData.value = data\n  ChatManagement.addChatRecord(currentChat, 50, loading)\n  ChatManagement.write(currentChat.id)\n  return loadSharedApi({ type: 'tool', isShared: props.isShared, systemType: props.apiType })\n    .debugToolWorkflow(toolId, data)\n    .then((response: any) => {\n      if (response.status === 460) {\n        return Promise.reject(t('chat.tip.errorIdentifyMessage'))\n      } else if (response.status === 461) {\n        return Promise.reject(t('chat.tip.errorLimitMessage'))\n      } else {\n        const reader = response.body.getReader()\n        // 处理流数据\n        const write = getWrite(\n          currentChat,\n          reader,\n          response.headers.get('Content-Type') !== 'application/json',\n        )\n        return write()\n      }\n    })\n    .finally(() => {\n      getToolRecord()\n      ChatManagement.close(currentChat.id)\n    })\n    .catch((e: any) => {\n      console.log(e)\n    })\n}\nconst loading = ref<boolean>(false)\nconst currentChat = reactive<any>({\n  id: randomId(),\n  answer_text_list: [[]],\n  buffer: [],\n  reasoning_content: '',\n  reasoning_content_buffer: [],\n  write_ed: false,\n  is_stop: false,\n  record_id: '',\n  chat_id: '',\n  vote_status: '-1',\n  status: undefined,\n})\n/**\n * 获取一个递归函数,处理流式数据\n * @param chat    每一条对话记录\n * @param reader  流数据\n * @param stream  是否是流式数据\n */\nconst getWrite = (chat: any, reader: any, stream: boolean) => {\n  let tempResult = ''\n\n  const write_stream = async () => {\n    try {\n      while (true) {\n        const { done, value } = await reader.read()\n\n        if (done) {\n          ChatManagement.close(chat.id)\n          return\n        }\n\n        const decoder = new TextDecoder('utf-8')\n        let str = decoder.decode(value, { stream: true })\n\n        tempResult += str\n        const split = tempResult.match(/data:.*?}\\n\\n/g)\n        if (split) {\n          str = split.join('')\n          tempResult = tempResult.replace(str, '')\n\n          // 批量处理所有 chunk\n          for (const item of split) {\n            const chunk = JSON.parse(item.replace('data:', ''))\n            chat.chat_id = chunk.chat_id\n            chat.record_id = chunk.chat_record_id\n\n            if (!chunk.is_end) {\n              ChatManagement.appendChunk(chat.id, chunk)\n            }\n\n            if (chunk.is_end) {\n              return Promise.resolve()\n            }\n          }\n        }\n        // 如果没有匹配到完整chunk，继续读取下一块\n      }\n    } catch (e) {\n      return Promise.reject(e)\n    }\n  }\n\n  const write_json = async () => {\n    try {\n      while (true) {\n        const { done, value } = await reader.read()\n\n        if (done) {\n          const result_block = JSON.parse(tempResult)\n          if (result_block.code === 500) {\n            return Promise.reject(result_block.message)\n          } else {\n            if (result_block.content) {\n              ChatManagement.append(chat.id, result_block.content)\n            }\n          }\n          ChatManagement.close(chat.id)\n          return\n        }\n\n        if (value) {\n          const decoder = new TextDecoder('utf-8')\n          tempResult += decoder.decode(value)\n        }\n      }\n    } catch (e) {\n      return Promise.reject(e)\n    }\n  }\n\n  return stream ? write_stream : write_json\n}\n\nconst sendMessage = (val: string, other_params_data?: any, chat?: chatType) => {\n  loadSharedApi({ type: 'tool', isShared: props.isShared, systemType: props.apiType })\n    .debugToolWorkflow(currentToolId.value, { ...other_params_data, ...currentData.value })\n    .then((response: any) => {\n      if (response.status === 460) {\n        return Promise.reject(t('chat.tip.errorIdentifyMessage'))\n      } else if (response.status === 461) {\n        return Promise.reject(t('chat.tip.errorLimitMessage'))\n      } else {\n        const reader = response.body.getReader()\n        // 处理流数据\n        const write = getWrite(\n          currentChat,\n          reader,\n          response.headers.get('Content-Type') !== 'application/json',\n        )\n        return write()\n      }\n    })\n    .finally(() => {\n      ChatManagement.close(currentChat.id)\n      getToolRecord()\n    })\n    .catch((e: any) => {\n      console.log(e)\n    })\n  return Promise.resolve(true)\n}\nconst getToolRecord = () => {\n  loadSharedApi({\n    type: 'tool',\n    isShared: props.isShared,\n    systemType: props.apiType,\n  })\n    .getToolRecordDetail(currentToolId.value, currentChat.record_id)\n    .then((ok: any) => {\n      toolRecord.value = ok.data\n    })\n}\ndefineExpose({\n  execute,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool-workflow/component/debug-drawer/index.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"drawer\"\n    title=\"调试\"\n    direction=\"rtl\"\n    :before-close=\"close\"\n    :destroy-on-close=\"true\"\n  >\n    <Parameters\n      v-if=\"active == 'parameters'\"\n      ref=\"paramtersRef\"\n      :workflow=\"toolDetail?.work_flow\"\n    ></Parameters>\n    <Result v-else ref=\"resultRef\" :isShared=\"isShared\" :apiType=\"apiType\"></Result>\n    <template #footer v-if=\"active == 'parameters'\">\n      <el-button @click=\"close\">取消</el-button>\n      <el-button type=\"primary\" @click=\"run\">运行</el-button>\n    </template>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, nextTick } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport Parameters from '../debug/parameters/index.vue'\nimport Result from '../debug/result/index.vue'\n\nconst route = useRoute()\nconst {\n  params: { folderId },\n  /*\n  folderId 可以区分 resource-management shared还是 workspace\n  */\n} = route as any\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('share/')) {\n    return 'workspaceShare'\n  } else {\n    return 'workspace'\n  }\n})\nconst active = ref<string>('parameters')\nconst toolDetail = ref<any>()\nfunction getDetail(toolId: string) {\n  loadSharedApi({ type: 'tool', isShared: isShared.value, systemType: apiType.value })\n    .getToolById(toolId)\n    .then((res: any) => {\n      toolDetail.value = res.data\n    })\n}\nconst drawer = ref<boolean>(false)\nconst open = (toolId: any) => {\n  drawer.value = true\n  getDetail(toolId)\n}\nconst close = () => {\n  drawer.value = false\n  active.value = 'parameters'\n}\nconst paramtersRef = ref<InstanceType<typeof Parameters>>()\nconst resultRef = ref<InstanceType<typeof Result>>()\nconst run = () => {\n  paramtersRef.value?.validate()?.then(() => {\n    const parameters = paramtersRef.value?.getData()\n    active.value = 'result'\n    nextTick(() => {\n      resultRef.value?.execute(toolDetail.value.id, parameters)\n    })\n  })\n}\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/tool-workflow/index.vue",
    "content": "<template>\n  <div class=\"knowledge-workflow\" v-loading=\"loading\">\n    <div class=\"header border-b flex-between p-12-24 white-bg\">\n      <div class=\"flex align-center\">\n        <back-button @click=\"back\"></back-button>\n        <h4 class=\"ellipsis\" style=\"max-width: 300px\" :title=\"detail?.name\">{{ detail?.name }}</h4>\n        <div v-if=\"showHistory && disablePublic\">\n          <el-text type=\"info\" class=\"ml-16 color-secondary\"\n            >{{ $t('workflow.info.previewVersion') }}\n            {{ currentVersion.name || datetimeFormat(currentVersion.update_time) }}\n          </el-text>\n        </div>\n        <el-text type=\"info\" class=\"ml-16 color-secondary\" v-else-if=\"saveTime\"\n          >{{ $t('workflow.info.saveTime') }}{{ datetimeFormat(saveTime) }}\n        </el-text>\n      </div>\n      <div v-if=\"showHistory && disablePublic && !route.path.includes('share/')\">\n        <el-button type=\"primary\" class=\"mr-8\" @click=\"refreshVersion()\">\n          {{ $t('workflow.setting.restoreVersion') }}\n        </el-button>\n        <el-divider direction=\"vertical\" />\n        <el-button text @click=\"closeHistory\">\n          <el-icon>\n            <Close />\n          </el-icon>\n        </el-button>\n      </div>\n      <div v-else-if=\"!route.path.includes('share/')\">\n        <el-button\n          class=\"ml-8\"\n          v-if=\"permissionPrecise.create()\"\n          @click=\"openTemplateStoreDialog()\"\n        >\n          <AppIcon iconName=\"app-template-center\" class=\"mr-4\" />\n          {{ $t('workflow.setting.templateCenter') }}\n        </el-button>\n        <el-button @click=\"showPopover = !showPopover\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\" />\n          {{ $t('workflow.setting.addComponent') }}\n        </el-button>\n        <el-button @click=\"clickShowDebug\" :disabled=\"showDebug\" v-if=\"permissionPrecise.debug(id)\">\n          <AppIcon iconName=\"app-debug-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.debug') }}\n        </el-button>\n        <el-button v-if=\"permissionPrecise.workflow_edit(id)\" @click=\"saveTool(true)\">\n          <AppIcon iconName=\"app-save-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.save') }}\n        </el-button>\n        <el-button type=\"primary\" v-if=\"permissionPrecise.workflow_edit(id)\" @click=\"publish\">\n          {{ $t('common.publish') }}\n        </el-button>\n\n        <el-dropdown trigger=\"click\">\n          <el-button text @click.stop class=\"ml-8 mt-4\">\n            <AppIcon iconName=\"app-more\" class=\"rotate-90\"></AppIcon>\n          </el-button>\n          <template #dropdown>\n            <el-dropdown-menu>\n              <el-dropdown-item\n                @click.stop=\"exportToolWorkflow(detail.name, detail.id)\"\n                v-if=\"permissionPrecise.workflow_export(id)\"\n              >\n                <AppIcon iconName=\"app-export\" class=\"color-secondary\"></AppIcon>\n                {{ $t('workflow.operation.exportWorkflow') }}\n              </el-dropdown-item>\n\n              <el-dropdown-item @click=\"openHistory\">\n                <AppIcon iconName=\"app-history-outlined\" class=\"color-secondary\"></AppIcon>\n                {{ $t('workflow.setting.releaseHistory') }}\n              </el-dropdown-item>\n              <el-dropdown-item v-if=\"permissionPrecise.workflow_edit(id)\">\n                <AppIcon iconName=\"app-save-outlined\" class=\"color-secondary\"></AppIcon>\n                {{ $t('workflow.setting.autoSave') }}\n                <div class=\"ml-4\">\n                  <el-switch size=\"small\" v-model=\"isSave\" @change=\"changeSave\" />\n                </div>\n              </el-dropdown-item>\n            </el-dropdown-menu>\n          </template>\n        </el-dropdown>\n      </div>\n    </div>\n    <!-- 下拉框 -->\n    <el-collapse-transition>\n      <DropdownMenu\n        :show=\"showPopover\"\n        :id=\"id\"\n        v-click-outside=\"clickoutside\"\n        @clickNodes=\"clickNodes\"\n        @onmousedown=\"onmousedown\"\n        :workflowRef=\"workflowRef\"\n      />\n    </el-collapse-transition>\n    <!-- 主画布 -->\n    <div class=\"workflow-main\" ref=\"workflowMainRef\">\n      <workflow ref=\"workflowRef\" v-if=\"detail\" :data=\"detail?.work_flow\" />\n    </div>\n    <!-- 调试 -->\n    <el-collapse-transition>\n      <div class=\"workflow-debug-container\" :class=\"enlarge ? 'enlarge' : ''\" v-if=\"showDebug\">\n        <div class=\"workflow-debug-header\" :class=\"!isDefaultTheme ? 'custom-header' : ''\">\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <div class=\"mr-12 ml-24 flex\">\n                <el-avatar\n                  v-if=\"isAppIcon(detail?.icon)\"\n                  shape=\"square\"\n                  :size=\"32\"\n                  style=\"background: none\"\n                >\n                  <img :src=\"resetUrl(detail?.icon)\" alt=\"\" />\n                </el-avatar>\n                <LogoIcon v-else height=\"32px\" />\n              </div>\n\n              <h4 class=\"ellipsis\" style=\"max-width: 270px\" :title=\"detail?.name\">\n                {{ detail?.name || $t('views.knowledge.form.appName.label') }}\n              </h4>\n            </div>\n            <div class=\"mr-16\">\n              <el-button link @click=\"enlarge = !enlarge\">\n                <AppIcon\n                  :iconName=\"enlarge ? 'app-minify' : 'app-magnify'\"\n                  class=\"color-secondary\"\n                  style=\"font-size: 20px\"\n                >\n                </AppIcon>\n              </el-button>\n              <el-button link @click=\"showDebug = false\">\n                <el-icon :size=\"20\" class=\"color-secondary\">\n                  <Close />\n                </el-icon>\n              </el-button>\n            </div>\n          </div>\n        </div>\n      </div>\n    </el-collapse-transition>\n\n    <!-- 发布历史 -->\n    <PublishHistory\n      v-if=\"showHistory\"\n      @click=\"checkVersion\"\n      v-click-outside=\"clickoutsideHistory\"\n      @refreshVersion=\"refreshVersion\"\n    />\n    <TemplateStoreDialog\n      ref=\"templateStoreDialogRef\"\n      :api-type=\"apiType\"\n      source=\"work_flow\"\n      @refresh=\"getDetail\"\n    />\n    <DebugDrawer ref=\"debugDrawerRef\"></DebugDrawer>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onBeforeMount, onBeforeUnmount, computed, nextTick, provide } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport type { Action } from 'element-plus'\nimport Workflow from '@/workflow/index.vue'\nimport DropdownMenu from '@/components/workflow-dropdown-menu/index.vue'\n\nimport PublishHistory from '@/views/tool-workflow/component/PublishHistory.vue'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport { MsgSuccess, MsgError, MsgConfirm } from '@/utils/message'\nimport { datetimeFormat } from '@/utils/time'\nimport useStore from '@/stores'\nimport { ToolWorkFlowInstance } from '@/workflow/common/validate'\nimport { hasPermission } from '@/utils/permission'\nimport { t } from '@/locales'\nimport { ComplexPermission, Permission } from '@/utils/permission/type'\nimport { EditionConst, PermissionConst, RoleConst } from '@/utils/permission/data'\nimport permissionMap from '@/permission'\nimport { WorkflowMode } from '@/enums/application'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { toolBaseNode, toolStartNode } from '@/workflow/common/data'\nimport TemplateStoreDialog from '@/views/knowledge/template-store/TemplateStoreDialog.vue'\nimport DebugDrawer from './component/debug-drawer/index.vue'\nprovide('getResourceDetail', () => detail)\nprovide('workflowMode', WorkflowMode.Tool)\nprovide('loopWorkflowMode', WorkflowMode.ToolLoop)\nconst { theme } = useStore()\nconst router = useRouter()\nconst route = useRoute()\nconst {\n  params: { id, folderId },\n  /*\n  folderId 可以区分 resource-management shared还是 workspace\n  */\n} = route as any\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('share/')) {\n    return 'workspaceShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap['knowledge'][apiType.value]\n})\n\nconst isDefaultTheme = computed(() => {\n  return theme.isDefaultTheme()\n})\n\nconst workflowRef = ref()\nconst debugDrawerRef = ref<InstanceType<typeof DebugDrawer>>()\nconst loading = ref(false)\nconst detail = ref<any>(null)\n\nconst showPopover = ref(false)\nconst showDebug = ref(false)\nconst enlarge = ref(false)\nconst saveTime = ref<any>('')\nconst isSave = ref(false)\nconst showHistory = ref(false)\nconst disablePublic = ref(false)\nconst currentVersion = ref<any>({})\nconst cloneWorkFlow = ref(null)\n\nconst apiInputParams = ref([])\n\nconst isPublish = computed(() => detail.value?.is_publish)\n\nfunction back() {\n  if (JSON.stringify(cloneWorkFlow.value) !== JSON.stringify(getGraphData())) {\n    MsgConfirm(t('common.tip'), t('workflow.tip.saveMessage'), {\n      confirmButtonText: t('workflow.setting.exitSave'),\n      cancelButtonText: t('workflow.setting.exit'),\n      distinguishCancelAndClose: true,\n    })\n      .then(() => {\n        saveTool(true, true)\n      })\n      .catch((action: Action) => {\n        if (action === 'cancel') {\n          go()\n        }\n      })\n  } else {\n    go()\n  }\n}\n\nfunction clickoutsideHistory() {\n  if (!disablePublic.value) {\n    showHistory.value = false\n    disablePublic.value = false\n  }\n}\n\nfunction refreshVersion(item?: any) {\n  if (item) {\n    renderGraphData(item)\n  }\n  showHistory.value = false\n  disablePublic.value = false\n}\n\nfunction checkVersion(item: any) {\n  disablePublic.value = true\n  currentVersion.value = item\n  renderGraphData(item)\n  closeInterval()\n}\n\nfunction renderGraphData(item: any) {\n  item.work_flow['nodes'].map((v: any) => {\n    v['properties']['noRender'] = true\n  })\n  detail.value.work_flow = item.work_flow\n  saveTime.value = item?.update_time\n  workflowRef.value?.clearGraphData()\n  nextTick(() => {\n    workflowRef.value?.render(item.work_flow)\n  })\n}\n\nfunction closeHistory() {\n  getDetail()\n  if (isSave.value) {\n    initInterval()\n  }\n  showHistory.value = false\n  disablePublic.value = false\n}\n\nfunction openHistory() {\n  showHistory.value = true\n}\n\nfunction changeSave(bool: boolean) {\n  if (bool) {\n    initInterval()\n  } else {\n    closeInterval()\n  }\n  localStorage.setItem('workflowAutoSave', bool.toString())\n}\n\nfunction clickNodes(item: any) {\n  showPopover.value = false\n}\n\nfunction onmousedown(item: any) {\n  showPopover.value = false\n}\n\nfunction clickoutside() {\n  showPopover.value = false\n}\n\nconst publish = () => {\n  workflowRef.value\n    ?.validate()\n    .then(() => {\n      const workflow = getGraphData()\n      const workflowInstance = new ToolWorkFlowInstance(workflow, WorkflowMode.Tool)\n      try {\n        workflowInstance.is_valid()\n      } catch (e: any) {\n        console.log('ss', workflow)\n        MsgError(e.toString())\n        return\n      }\n      loadSharedApi({ type: 'tool', isShared: isShared.value, systemType: apiType.value })\n        .putToolWorkflow(id, { work_flow: workflow })\n        .then(() => {\n          return loadSharedApi({\n            type: 'tool',\n            isShared: isShared.value,\n            systemType: apiType.value,\n          }).publish(id, {}, loading)\n        })\n        .then((ok: any) => {\n          detail.value.is_publish = true\n          MsgSuccess(t('views.application.tip.publishSuccess'))\n        })\n        .catch((res: any) => {\n          console.log(res)\n          const node = res.node\n          const err_message = res.errMessage\n          if (typeof err_message == 'string') {\n            MsgError(\n              res.node.properties?.stepName +\n                ` ${t('workflow.node').toLowerCase()} ` +\n                err_message.toLowerCase(),\n            )\n          } else {\n            const keys = Object.keys(err_message)\n            MsgError(\n              node.properties?.stepName +\n                ` ${t('workflow.node').toLowerCase()} ` +\n                err_message[keys[0]]?.[0]?.message.toLowerCase(),\n            )\n          }\n        })\n    })\n    .catch((res: any) => {\n      const node = res.node\n      const err_message = res.errMessage\n      if (typeof err_message == 'string') {\n        MsgError(res.node.properties?.stepName + ` ${t('workflow.node')}，` + err_message)\n      } else {\n        const keys = Object.keys(err_message)\n        MsgError(\n          node.properties?.stepName +\n            ` ${t('workflow.node')}，` +\n            err_message[keys[0]]?.[0]?.message,\n        )\n      }\n    })\n}\n\nconst elUploadRef = ref()\nconst importKnowledgeWorkflow = (file: any) => {\n  const formData = new FormData()\n  formData.append('file', file.raw)\n  const name = file.name.replace('.kbwf', '')\n  elUploadRef.value.clearFiles()\n  MsgConfirm(\n    t('common.tip'),\n    `${t('views.application.tip.confirmUse')} ${name} ${t('views.application.tip.overwrite')}?`,\n    {\n      confirmButtonText: t('common.confirm'),\n      cancelButtonText: t('common.cancel'),\n    },\n  )\n    .then(() => {\n      loadSharedApi({ type: 'knowledge', isShared: isShared.value, systemType: apiType.value })\n        .importKnowledgeWorkflow(id, formData, loading)\n        .then(() => {\n          getDetail()\n        })\n        .catch((error: any) => {\n          if (error.code === 400) {\n            MsgConfirm(t('common.tip'), t('views.application.tip.professionalMessage'), {\n              cancelButtonText: t('common.confirm'),\n              confirmButtonText: t('common.professional'),\n            }).then(() => {\n              window.open('https://maxkb.cn/pricing.html', '_blank')\n            })\n          }\n        })\n    })\n    .catch(() => {})\n}\n\nfunction exportToolWorkflow(name: string, id: string) {\n  loadSharedApi({ type: 'tool', isShared: isShared.value, systemType: apiType.value })\n    .exportToolWorkflow(id, name, loading)\n    .catch((error: any) => {\n      if (error.response.status !== 403) {\n        error.response.data.text().then((res: string) => {\n          MsgError(`${t('views.application.tip.ExportError')}:${JSON.parse(res).message}`)\n        })\n      }\n    })\n}\n\nconst clickShowDebug = () => {\n  workflowRef.value\n    ?.validate()\n    .then(() => {\n      const graphData = getGraphData()\n      const workflow = new ToolWorkFlowInstance(graphData, WorkflowMode.Tool)\n      try {\n        workflow.is_valid()\n        detail.value = {\n          ...detail.value,\n          type: 'WORK_FLOW',\n          ...workflow.get_base_node()?.properties.node_data,\n          work_flow: getGraphData(),\n        }\n        debugDrawerRef.value?.open(id)\n      } catch (e: any) {\n        MsgError(e.toString())\n      }\n    })\n    .catch((res: any) => {\n      const node = res.node\n      const err_message = res.errMessage\n      if (typeof err_message == 'string') {\n        MsgError(res.node.properties?.stepName + ` ${t('workflow.node')}，` + err_message)\n      } else {\n        const keys = Object.keys(err_message)\n        MsgError(\n          node.properties?.stepName +\n            ` ${t('workflow.node')}，` +\n            err_message[keys[0]]?.[0]?.message,\n        )\n      }\n    })\n}\n\nfunction getGraphData() {\n  return workflowRef.value?.getGraphData()\n}\n\nconst isShared = computed(() => {\n  return folderId === 'share'\n})\n\nfunction getDetail() {\n  loadSharedApi({ type: 'tool', isShared: isShared.value, systemType: apiType.value })\n    .getToolById(id)\n    .then((res: any) => {\n      detail.value = res.data\n      saveTime.value = res.data?.update_time\n      console.log(res.data)\n      if (!detail.value.work_flow || !('nodes' in detail.value.work_flow)) {\n        detail.value.work_flow = { nodes: [toolBaseNode, toolStartNode] }\n      }\n\n      workflowRef.value?.clearGraphData()\n      nextTick(() => {\n        workflowRef.value?.render(detail.value.work_flow)\n        cloneWorkFlow.value = getGraphData()\n      })\n    })\n}\n\nfunction saveTool(bool?: boolean, back?: boolean) {\n  const obj = {\n    work_flow: getGraphData(),\n  }\n  loading.value = back || false\n  loadSharedApi({ type: 'tool', isShared: isShared.value, systemType: apiType.value })\n    .putToolWorkflow(id, obj)\n    .then(() => {\n      saveTime.value = new Date()\n      if (bool) {\n        cloneWorkFlow.value = getGraphData()\n        MsgSuccess(t('common.saveSuccess'))\n        if (back) {\n          go()\n        }\n      }\n    })\n    .catch(() => {\n      loading.value = false\n    })\n}\n\nconst go = () => {\n  if (route.path.includes('resource-management')) {\n    return router.push({ path: '/system/resource-management/tool' })\n  } else if (route.path.includes('shared')) {\n    return router.push({ path: '/system/shared/tool' })\n  } else {\n    return router.push({ path: '/tool' })\n  }\n}\n\nconst templateStoreDialogRef = ref()\nfunction openTemplateStoreDialog() {\n  templateStoreDialogRef.value?.open(folderId)\n}\nlet interval: any\n/**\n * 定时保存\n */\nconst initInterval = () => {\n  interval = setInterval(() => {\n    saveTool()\n  }, 60000)\n}\n\n/**\n * 关闭定时\n */\nconst closeInterval = () => {\n  if (interval) {\n    clearInterval(interval)\n  }\n}\n\nonBeforeMount(() => {\n  getDetail()\n  const workflowAutoSave = localStorage.getItem('workflowAutoSave')\n  isSave.value = workflowAutoSave === 'true' ? true : false\n  // 初始化定时任务\n  if (isSave.value) {\n    initInterval()\n  }\n})\n\nonBeforeUnmount(() => {\n  // 清除定时任务\n  closeInterval()\n  workflowRef.value?.clearGraphData()\n})\n</script>\n<style lang=\"scss\">\n.knowledge-workflow {\n  background: var(--app-layout-bg-color);\n  height: 100%;\n\n  .workflow-main {\n    height: calc(100vh - 62px);\n    box-sizing: border-box;\n  }\n\n  .workflow-dropdown-tabs {\n    .el-tabs__nav-wrap {\n      padding: 0 16px;\n    }\n  }\n}\n\n.workflow-debug-container {\n  z-index: 2000;\n  position: relative;\n  border-radius: 8px;\n  border: 1px solid #ffffff;\n  background: var(--dialog-bg-gradient-color);\n  box-shadow: 0px 4px 8px 0px var(--app-text-color-light-1);\n  position: fixed;\n  bottom: 16px;\n  right: 16px;\n  overflow: hidden;\n  width: 460px;\n  height: 680px;\n\n  .workflow-debug-header {\n    background: var(--app-header-bg-color);\n    height: var(--app-header-height);\n    line-height: var(--app-header-height);\n    box-sizing: border-box;\n    border-bottom: 1px solid var(--el-border-color);\n  }\n\n  .scrollbar-height {\n    height: calc(100% - var(--app-header-height) - 24px);\n    padding-top: 24px;\n  }\n\n  &.enlarge {\n    width: 50% !important;\n    height: 100% !important;\n    bottom: 0 !important;\n    right: 0 !important;\n  }\n\n  .chat-width {\n    max-width: 100% !important;\n    margin: 0 auto;\n  }\n}\n\n@media only screen and (max-height: 680px) {\n  .workflow-debug-container {\n    height: 600px;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/views/trigger/ResourceTriggerDrawer.vue",
    "content": "<template>\n  <el-drawer v-model=\"visible\" size=\"600\" :destroy-on-close=\"true\">\n    <template #header>\n      <h4>{{ $t('views.trigger.title') }}</h4>\n    </template>\n    <div class=\"flex-between\">\n      <h4 class=\"title-decoration-1 mb-12\">\n        {{ $t('views.trigger.title') }}\n      </h4>\n      <el-button\n        v-if=\"permissionPrecise.trigger_create(toolId)\"\n        link\n        type=\"primary\"\n        @click=\"openCreateTriggerDrawer()\"\n      >\n        <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n        {{ $t('common.add') }}\n      </el-button>\n    </div>\n\n    <div v-if=\"triggerList.length > 0\" class=\"w-full\" v-loading=\"loading\">\n      <template v-for=\"(item, index) in triggerList\" :key=\"index\">\n        <div class=\"flex-between border border-r-6 white-bg mb-8\" style=\"padding: 2px 8px\">\n          <div class=\"flex align-center\" style=\"width: 60%\">\n            <TriggerIcon :type=\"item.trigger_type\" class=\"mr-8\" :size=\"20\" />\n            <auto-tooltip :content=\"item.name\">\n              {{ item.name }}\n            </auto-tooltip>\n          </div>\n\n          <div>\n            <span v-if=\"item.trigger_type === 'SCHEDULED'\" class=\"mr-8 color-secondary lighter\">\n              {{ getTriggerCycleLabel(item.trigger_setting) }}</span\n            >\n            <span class=\"mr-4\">\n              <el-button text @click=\"openEditTriggerDrawer(item)\">\n                <AppIcon iconName=\"app-edit\" class=\"color-secondary\"></AppIcon>\n              </el-button>\n            </span>\n\n            <el-button\n              v-if=\"permissionPrecise.trigger_delete(toolId)\"\n              text\n              @click=\"removeTrigger(item)\"\n            >\n              <el-icon><Close /></el-icon>\n            </el-button>\n          </div>\n        </div>\n      </template>\n    </div>\n    <el-empty v-else :description=\"$t('common.noData')\" />\n\n    <TriggerDrawer\n      @refresh=\"refreshTrigger\"\n      ref=\"triggerDrawerRef\"\n      :create-trigger=\"createTrigger\"\n      :edit-trigger=\"editTrigger\"\n      :resourceType=\"props.source\"\n    ></TriggerDrawer>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport TriggerDrawer from '@/views/trigger/TriggerDrawer.vue'\nimport permissionMap from '@/permission'\nimport triggerAPI from '@/api/trigger/trigger'\nimport { getTriggerCycleLabel } from '@/utils/trigger'\n\nconst props = defineProps<{\n  source: string\n}>()\n\nconst route = useRoute()\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap[props.source.toLowerCase() as 'application' | 'tool'][\n    apiType.value as 'workspace' | 'systemManage'\n  ]\n})\n\nconst toolId = ref<string>('')\nconst visible = ref<boolean>(false)\nconst loading = ref<boolean>(false)\n\nconst emit = defineEmits(['refresh'])\n\nconst createTrigger = (trigger: any) => {\n  if (toolId.value) {\n    return loadSharedApi({ type: 'trigger', systemType: apiType.value }).postResourceTrigger(\n      props.source,\n      toolId.value,\n      trigger,\n    )\n  }\n  return Promise.resolve<any>({})\n}\nconst editTrigger = (trigger_id: string, trigger: any) => {\n  if (toolId.value) {\n    return loadSharedApi({\n      type: 'trigger',\n      systemType: apiType.value,\n    }).putResourceTrigger(props.source, toolId.value, trigger_id, trigger)\n  }\n  return Promise.resolve<any>({})\n}\nconst triggerList = ref<Array<any>>([])\n\nconst triggerDrawerRef = ref<InstanceType<typeof TriggerDrawer>>()\n\nconst openCreateTriggerDrawer = () => {\n  triggerDrawerRef.value?.open(undefined, props.source, toolId.value)\n}\nconst openEditTriggerDrawer = (trigger: any) => {\n  triggerDrawerRef.value?.open(trigger.id, props.source, toolId.value)\n}\n\nfunction getTriggerList() {\n  loadSharedApi({ type: 'trigger', systemType: apiType.value })\n    .getResourceTriggerList(props.source, toolId.value, loading)\n    .then((res: any) => {\n      triggerList.value = res.data\n    })\n}\n\nfunction refreshTrigger() {\n  getTriggerList()\n}\n\nfunction removeTrigger(trigger: any) {\n  loadSharedApi({ type: 'trigger', systemType: apiType.value })\n    .deleteResourceTrigger(props.source, toolId.value, trigger.id, loading)\n    .then((res: any) => {\n      getTriggerList()\n    })\n}\n\nconst open = (data: any) => {\n  toolId.value = data.id\n  getTriggerList()\n  visible.value = true\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/trigger/TriggerDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"drawer\"\n    :title=\"is_edit ? $t('views.trigger.editTrigger') : $t('views.trigger.createTrigger')\"\n    size=\"600\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n  >\n    <el-form\n      :model=\"form\"\n      label-width=\"auto\"\n      ref=\"triggerFormRef\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      class=\"mb-24\"\n    >\n      <el-form-item\n        :label=\"$t('views.trigger.from.triggerName.label')\"\n        prop=\"name\"\n        :rules=\"{\n          message: $t('views.trigger.from.triggerName.requiredMessage'),\n          trigger: 'blur',\n          required: true,\n        }\"\n      >\n        <el-input\n          v-model=\"form.name\"\n          maxlength=\"64\"\n          :placeholder=\"$t('views.trigger.from.triggerName.placeholder')\"\n          show-word-limit\n          @blur=\"form.name = form.name?.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('common.desc')\" prop=\"desc\">\n        <el-input\n          v-model=\"form.desc\"\n          type=\"textarea\"\n          :placeholder=\"$t('common.inputPlaceholder')\"\n          :rows=\"3\"\n          maxlength=\"256\"\n          show-word-limit\n        />\n      </el-form-item>\n      <el-form-item\n        :label=\"$t('common.type')\"\n        prop=\"trigger_type\"\n        :rules=\"{\n          message: $t('common.selectPlaceholder'),\n          trigger: 'blur',\n          required: true,\n        }\"\n      >\n        <el-card\n          shadow=\"never\"\n          class=\"mb-16 w-full cursor\"\n          :class=\"form.trigger_type === 'SCHEDULED' ? 'border-active' : ''\"\n          @click=\"changeTriggerType('SCHEDULED')\"\n        >\n          <div class=\"flex align-center line-height-22\">\n            <el-avatar shape=\"square\" :size=\"32\">\n              <img src=\"@/assets/trigger/icon_scheduled.svg\" style=\"width: 58%\" alt=\"\" />\n            </el-avatar>\n            <div class=\"ml-12\">\n              <h5>{{ $t('views.trigger.type.scheduled') }}</h5>\n              <el-text type=\"info\" class=\"color-secondary font-small\">{{\n                $t('views.trigger.type.scheduledDesc')\n              }}</el-text>\n            </div>\n          </div>\n\n          <el-card\n            v-if=\"form.trigger_type === 'SCHEDULED'\"\n            shadow=\"never\"\n            class=\"card-never mt-16 w-full\"\n          >\n            <div class=\"flex-between\">\n              <p style=\"margin-top: -8px\">\n                {{\n                  form.trigger_setting.schedule_type === 'cron'\n                    ? $t('views.trigger.triggerCycle.cronExpression')\n                    : $t('views.trigger.triggerCycle.title')\n                }}\n              </p>\n              <el-tooltip\n                :content=\"\n                  form.trigger_setting.schedule_type === 'cron'\n                    ? $t('views.trigger.triggerCycle.switchCycle')\n                    : $t('views.trigger.triggerCycle.switchCron')\n                \"\n                placement=\"top\"\n                effect=\"light\"\n              >\n                <el-button text @click.stop=\"switchScheduleType\">\n                  <el-icon><Switch /></el-icon>\n                </el-button>\n              </el-tooltip>\n            </div>\n\n            <el-cascader\n              v-if=\"form.trigger_setting.schedule_type !== 'cron'\"\n              v-model=\"scheduled\"\n              :options=\"triggerCycleOptions\"\n              @change=\"handleChangeScheduled\"\n              style=\"width: 100%\"\n            />\n            <el-input\n              v-else\n              v-model=\"form.trigger_setting.cron_expression\"\n              :placeholder=\"t('views.trigger.triggerCycle.placeholder')\"\n              clearable\n              @blur=\"validateCron\"\n              @input=\"validateCron\"\n            />\n            <div v-if=\"cronError\" class=\"el-form-item__error\">{{ cronError }}</div>\n          </el-card>\n        </el-card>\n        <el-card\n          shadow=\"never\"\n          class=\"w-full cursor\"\n          :class=\"form.trigger_type === 'EVENT' ? 'border-active' : ''\"\n          @click=\"changeTriggerType('EVENT')\"\n        >\n          <div class=\"flex align-center line-height-22\">\n            <el-avatar shape=\"square\" class=\"avatar-orange\" :size=\"32\">\n              <img src=\"@/assets/trigger/icon_event.svg\" style=\"width: 58%\" alt=\"\" />\n            </el-avatar>\n            <div class=\"ml-12\">\n              <h5>{{ $t('views.trigger.type.event') }}</h5>\n              <el-text type=\"info\" class=\"color-secondary font-small\">{{\n                $t('views.trigger.type.eventDesc')\n              }}</el-text>\n            </div>\n          </div>\n          <el-card v-if=\"form.trigger_type === 'EVENT'\" shadow=\"never\" class=\"card-never mt-16\">\n            <el-form-item :label=\"$t('views.trigger.from.event_url.label')\">\n              <div\n                class=\"complex-input flex-between align-center w-full\"\n                style=\"background-color: #ffffff\"\n              >\n                <el-input\n                  class=\"complex-input__left\"\n                  v-bind:modelValue=\"event_url\"\n                  readonly\n                ></el-input>\n\n                <el-tooltip :content=\"$t('common.copy')\" placement=\"top\">\n                  <el-button text @click=\"copyClick(event_url)\" class=\"mr-4\">\n                    <AppIcon iconName=\"app-copy\" class=\"color-secondary\"></AppIcon>\n                  </el-button>\n                </el-tooltip>\n              </div>\n            </el-form-item>\n            <el-form-item label=\"Bearer Token\">\n              <div class=\"complex-input flex-between w-full\" style=\"background-color: #ffffff\">\n                <el-input\n                  class=\"complex-input__left\"\n                  :placeholder=\"$t('common.inputPlaceholder')\"\n                  v-model=\"form.trigger_setting.token\"\n                  readonly\n                  style=\"width: 80%\"\n                >\n                </el-input>\n                <div>\n                  <el-tooltip :content=\"$t('common.copy')\" placement=\"top\">\n                    <el-button text @click=\"copyClick(form.trigger_setting.token)\">\n                      <AppIcon iconName=\"app-copy\" class=\"color-secondary\"></AppIcon>\n                    </el-button>\n                  </el-tooltip>\n                  <el-tooltip :content=\"$t('common.refresh')\" placement=\"top\">\n                    <el-button @click=\"refreshToken\" text style=\"margin: 0 4px 0 0 !important\">\n                      <AppIcon iconName=\"app-refresh\" class=\"color-secondary\"></AppIcon>\n                    </el-button>\n                  </el-tooltip>\n                </div>\n              </div>\n            </el-form-item>\n            <el-form-item>\n              <template #label>\n                <div class=\"flex-between\">\n                  {{ $t('views.trigger.requestParameter') }}\n                  <el-button link type=\"primary\" @click.stop=\"addParameter()\">\n                    <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                  </el-button>\n                </div>\n              </template>\n              <el-card\n                class=\"w-full border-none\"\n                shadow=\"never\"\n                style=\"--el-card-padding: 8px 16px 16px\"\n              >\n                <el-row style=\"width: 100%\" :gutter=\"10\">\n                  <el-col :span=\"7\">\n                    {{ $t('views.tool.form.paramName.label') }}\n                  </el-col>\n                  <el-col :span=\"7\">\n                    {{ $t('common.type') }}\n                  </el-col>\n                  <el-col :span=\"7\">\n                    {{ $t('common.desc') }}\n                  </el-col>\n                  <el-col :span=\"3\">\n                    {{ $t('common.required') }}\n                  </el-col>\n                </el-row>\n                <el-row\n                  style=\"width: 99%\"\n                  v-for=\"(option, $index) in form.trigger_setting.body\"\n                  :key=\"$index\"\n                  :gutter=\"8\"\n                >\n                  <el-col :span=\"7\" class=\"mb-8\">\n                    <el-input\n                      v-model=\"form.trigger_setting.body[$index].field\"\n                      :placeholder=\"$t('common.inputPlaceholder')\"\n                    />\n                  </el-col>\n                  <el-col :span=\"7\">\n                    <el-select\n                      v-model=\"form.trigger_setting.body[$index].type\"\n                      :placeholder=\"$t('common.selectPlaceholder')\"\n                    >\n                      <el-option label=\"string\" value=\"string\" />\n                      <el-option label=\"int\" value=\"int\" />\n                      <el-option label=\"dict\" value=\"dict\" />\n                      <el-option label=\"array\" value=\"array\" />\n                      <el-option label=\"float\" value=\"float\" />\n                      <el-option label=\"boolean\" value=\"boolean\" />\n                    </el-select>\n                  </el-col>\n                  <el-col :span=\"7\">\n                    <el-input\n                      v-model=\"form.trigger_setting.body[$index].desc\"\n                      :placeholder=\"$t('common.inputPlaceholder')\"\n                    />\n                  </el-col>\n                  <el-col :span=\"2\">\n                    <el-switch v-model=\"form.trigger_setting.body[$index].required\" size=\"small\" />\n                  </el-col>\n                  <el-col :span=\"1\">\n                    <el-button text class=\"ml-8\" @click.stop=\"delParameter($index)\">\n                      <AppIcon iconName=\"app-delete\" class=\"color-secondary\"></AppIcon>\n                    </el-button>\n                  </el-col>\n                </el-row>\n              </el-card>\n            </el-form-item>\n          </el-card>\n        </el-card>\n      </el-form-item>\n      <el-form-item\n        :label=\"$t('views.trigger.taskExecution')\"\n        prop=\"trigger_task\"\n        :rules=\"{\n          type: 'array',\n          message: $t('common.selectPlaceholder'),\n          trigger: 'change',\n          required: true,\n        }\"\n      >\n        <template v-if=\"['APPLICATION', 'TOOL'].includes(resourceType)\">\n          <!-- 资源端智能体 -->\n          <div class=\"w-full\" v-if=\"resourceType === 'APPLICATION'\">\n            <template v-for=\"(item, index) in applicationTask\" :key=\"index\">\n              <div class=\"border border-r-6 white-bg mb-8\" style=\"padding: 2px 8px\">\n                <div class=\"flex-between\">\n                  <div class=\"flex align-center\" style=\"line-height: 20px\">\n                    <el-avatar\n                      v-if=\"applicationDetailsDict[item.source_id]?.icon\"\n                      shape=\"square\"\n                      :size=\"20\"\n                      style=\"background: none\"\n                      class=\"mr-8\"\n                    >\n                      <img :src=\"resetUrl(applicationDetailsDict[item.source_id]?.icon)\" alt=\"\" />\n                    </el-avatar>\n                    <AppIcon v-else class=\"mr-8\" :size=\"20\" />\n\n                    <div class=\"ellipsis-1\" :title=\"applicationDetailsDict[item.source_id]?.name\">\n                      {{ applicationDetailsDict[item.source_id]?.name }}\n                    </div>\n                  </div>\n                  <div style=\"margin-top: -2px\">\n                    <span class=\"mr-4\">\n                      <el-button\n                        text\n                        @click=\"showTast = showTast === 'agent' + index ? '' : 'agent' + index\"\n                      >\n                        <el-icon\n                          class=\"arrow-icon\"\n                          :class=\"showTast === 'agent' + index ? 'rotate-180' : ''\"\n                        >\n                          <ArrowDown />\n                        </el-icon>\n                      </el-button>\n                    </span>\n                  </div>\n                </div>\n                <ApplicationParameter\n                  class=\"mt-8 mb-8\"\n                  ref=\"applicationParameterRef\"\n                  v-if=\"showTast === 'agent' + index && applicationDetailsDict[item.source_id]\"\n                  :application=\"applicationDetailsDict[item.source_id]\"\n                  :trigger=\"form\"\n                  v-model=\"item.parameter\"\n                ></ApplicationParameter>\n              </div>\n            </template>\n          </div>\n          <!-- 资源端工具 -->\n          <div class=\"w-full\" v-if=\"resourceType === 'TOOL'\">\n            <template v-for=\"(item, index) in toolTask\" :key=\"index\">\n              <div class=\"border border-r-6 white-bg mb-4\" style=\"padding: 2px 8px 5px\">\n                <div class=\"flex-between\">\n                  <div class=\"flex align-center\" style=\"line-height: 20px\">\n                    <el-avatar\n                      v-if=\"toolDetailsDict[item.source_id]?.icon\"\n                      shape=\"square\"\n                      :size=\"20\"\n                      style=\"background: none\"\n                      class=\"mr-8\"\n                    >\n                      <img :src=\"resetUrl(toolDetailsDict[item.source_id]?.icon)\" alt=\"\" />\n                    </el-avatar>\n                    <ToolIcon v-else class=\"mr-8\" :size=\"20\" />\n\n                    <div class=\"ellipsis-1\" :title=\"toolDetailsDict[item.source_id]?.name\">\n                      {{ toolDetailsDict[item.source_id]?.name }}\n                    </div>\n                  </div>\n                  <div style=\"margin-top: -2px\">\n                    <span class=\"mr-4\">\n                      <el-button\n                        text\n                        @click=\"showTast = showTast === 'tool' + index ? '' : 'tool' + index\"\n                      >\n                        <el-icon\n                          class=\"arrow-icon\"\n                          :class=\"showTast === 'tool' + index ? 'rotate-180' : ''\"\n                        >\n                          <ArrowDown />\n                        </el-icon>\n                      </el-button>\n                    </span>\n                  </div>\n                </div>\n                <ToolParameter\n                  class=\"mt-8 mb-8\"\n                  ref=\"toolParameterRef\"\n                  v-if=\"showTast === 'tool' + index && toolDetailsDict[item.source_id]\"\n                  :tool=\"toolDetailsDict[item.source_id]\"\n                  :trigger=\"form\"\n                  v-model=\"item.parameter\"\n                ></ToolParameter>\n              </div>\n            </template>\n          </div>\n        </template>\n        <!-- 触发器 -->\n        <el-card\n          shadow=\"never\"\n          class=\"card-never w-full\"\n          style=\"--el-card-padding: 8px 12px\"\n          v-else\n        >\n          <!-- 智能体    -->\n          <div class=\"flex-between\" @click=\"collapseData.agent = !collapseData.agent\">\n            <div class=\"flex align-center lighter cursor\">\n              <el-icon class=\"mr-8 arrow-icon\" :class=\"collapseData.agent ? 'rotate-90' : ''\">\n                <CaretRight />\n              </el-icon>\n              {{ $t('views.application.title') }}\n              <span class=\"ml-4\" v-if=\"applicationTask?.length\">\n                ({{ applicationTask?.length }})</span\n              >\n            </div>\n            <div class=\"flex\">\n              <el-button type=\"primary\" link @click.stop=\"openApplicationDialog()\">\n                <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n              </el-button>\n            </div>\n          </div>\n          <div class=\"w-full\" v-if=\"collapseData.agent\">\n            <template v-for=\"(item, index) in applicationTask\" :key=\"index\">\n              <div class=\"border border-r-6 white-bg mb-4\" style=\"padding: 2px 8px\">\n                <div class=\"flex-between\">\n                  <div class=\"flex align-center\" style=\"line-height: 20px\">\n                    <el-avatar\n                      v-if=\"applicationDetailsDict[item.source_id]?.icon\"\n                      shape=\"square\"\n                      :size=\"20\"\n                      style=\"background: none\"\n                      class=\"mr-8\"\n                    >\n                      <img :src=\"resetUrl(applicationDetailsDict[item.source_id]?.icon)\" alt=\"\" />\n                    </el-avatar>\n                    <AppIcon v-else class=\"mr-8\" :size=\"20\" />\n\n                    <div class=\"ellipsis-1\" :title=\"applicationDetailsDict[item.source_id]?.name\">\n                      {{ applicationDetailsDict[item.source_id]?.name }}\n                    </div>\n                  </div>\n                  <div style=\"margin-top: -2px\">\n                    <span class=\"mr-4\">\n                      <el-button\n                        text\n                        @click=\"showTast = showTast === 'agent' + index ? '' : 'agent' + index\"\n                      >\n                        <el-icon\n                          class=\"arrow-icon\"\n                          :class=\"showTast === 'agent' + index ? 'rotate-180' : ''\"\n                        >\n                          <ArrowDown />\n                        </el-icon>\n                      </el-button>\n                    </span>\n                    <span class=\"mr-4\">\n                      <el-button text @click=\"deleteTask(item)\">\n                        <el-icon><Close /></el-icon>\n                      </el-button>\n                    </span>\n                  </div>\n                </div>\n                <ApplicationParameter\n                  class=\"mt-8 mb-8\"\n                  ref=\"applicationParameterRef\"\n                  v-if=\"showTast === 'agent' + index && applicationDetailsDict[item.source_id]\"\n                  :application=\"applicationDetailsDict[item.source_id]\"\n                  :trigger=\"form\"\n                  v-model=\"item.parameter\"\n                ></ApplicationParameter>\n              </div>\n            </template>\n          </div>\n          <!-- 工具    -->\n          <div class=\"flex-between\" @click=\"collapseData.tool = !collapseData.tool\">\n            <div class=\"flex align-center lighter cursor\">\n              <el-icon class=\"mr-8 arrow-icon\" :class=\"collapseData.tool ? 'rotate-90' : ''\">\n                <CaretRight />\n              </el-icon>\n              {{ $t('views.tool.title') }}\n              <span class=\"ml-4\" v-if=\"toolTask?.length\"> ({{ toolTask?.length }})</span>\n            </div>\n            <div class=\"flex\">\n              <el-button type=\"primary\" link @click.stop=\"openToolDialog()\">\n                <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n              </el-button>\n            </div>\n          </div>\n          <div class=\"w-full\" v-if=\"collapseData.tool\">\n            <template v-for=\"(item, index) in toolTask\" :key=\"index\">\n              <div class=\"border border-r-6 white-bg mb-4\" style=\"padding: 2px 8px 5px\">\n                <div class=\"flex-between\">\n                  <div class=\"flex align-center\" style=\"line-height: 20px\">\n                    <el-avatar\n                      v-if=\"toolDetailsDict[item.source_id]?.icon\"\n                      shape=\"square\"\n                      :size=\"20\"\n                      style=\"background: none\"\n                      class=\"mr-8\"\n                    >\n                      <img :src=\"resetUrl(toolDetailsDict[item.source_id]?.icon)\" alt=\"\" />\n                    </el-avatar>\n                    <ToolIcon v-else class=\"mr-8\" :size=\"20\" />\n\n                    <div class=\"ellipsis-1\" :title=\"toolDetailsDict[item.source_id]?.name\">\n                      {{ toolDetailsDict[item.source_id]?.name }}\n                    </div>\n                  </div>\n                  <div style=\"margin-top: -2px\">\n                    <span class=\"mr-4\">\n                      <el-button\n                        text\n                        @click=\"showTast = showTast === 'tool' + index ? '' : 'tool' + index\"\n                      >\n                        <el-icon\n                          class=\"arrow-icon\"\n                          :class=\"showTast === 'tool' + index ? 'rotate-180' : ''\"\n                        >\n                          <ArrowDown />\n                        </el-icon>\n                      </el-button>\n                    </span>\n                    <span class=\"mr-4\">\n                      <el-button text @click=\"deleteTask(item)\">\n                        <el-icon><Close /></el-icon>\n                      </el-button>\n                    </span>\n                  </div>\n                </div>\n                <ToolParameter\n                  class=\"mt-8 mb-8\"\n                  ref=\"toolParameterRef\"\n                  v-if=\"showTast === 'tool' + index && toolDetailsDict[item.source_id]\"\n                  :tool=\"toolDetailsDict[item.source_id]\"\n                  :trigger=\"form\"\n                  v-model=\"item.parameter\"\n                ></ToolParameter>\n              </div>\n            </template>\n          </div>\n        </el-card>\n      </el-form-item>\n    </el-form>\n    <ApplicationDialog @refresh=\"applicationRefresh\" ref=\"applicationDialogRef\"></ApplicationDialog>\n    <ToolDialog @refresh=\"toolRefresh\" ref=\"toolDialogRef\"></ToolDialog>\n    <template #footer>\n      <el-button @click=\"close\">{{ $t('common.cancel') }}</el-button>\n      <el-button v-if=\"!is_edit || editPermission\" type=\"primary\" @click=\"submit\">{{\n        is_edit ? $t('common.save') : $t('common.create')\n      }}</el-button>\n    </template>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport { v4 as uuidv4 } from 'uuid'\nimport { ref, computed, onMounted, reactive } from 'vue'\nimport { copyClick } from '@/utils/clipboard'\nimport ApplicationDialog from '@/views/application/component/ApplicationDialog.vue'\nimport ToolDialog from '@/views/application/component/ToolDialog.vue'\nimport applicationAPI from '@/api/application/application'\nimport triggerAPI from '@/api/trigger/trigger'\nimport systemManageTriggerAPI from '@/api/system-resource-management/trigger'\nimport toolAPI from '@/api/tool/tool'\nimport ToolParameter from '@/views/trigger/component/ToolParameter.vue'\nimport ApplicationParameter from '@/views/trigger/component/ApplicationParameter.vue'\nimport { resetUrl } from '@/utils/common.ts'\nimport { triggerCycleOptions } from '@/utils/trigger.ts'\nimport { t } from '@/locales'\nimport { type FormInstance } from 'element-plus'\nimport { useRoute } from 'vue-router'\nimport { cloneDeep } from 'lodash'\nimport { isValidCron } from 'cron-validator'\nimport Result from '@/request/Result'\nimport { hasPermission } from '@/utils/permission'\nimport permissionMap from '@/permission'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst emit = defineEmits(['refresh'])\nconst props = withDefaults(\n  defineProps<{\n    createTrigger?: (trigger: any) => Promise<Result<any>>\n    editTrigger?: (trigger_id: string, trigger: any) => Promise<Result<any>>\n    resourceType?: string\n  }>(),\n  {\n    createTrigger: triggerAPI.postTrigger,\n    editTrigger: triggerAPI.putTrigger,\n    resourceType: '',\n  },\n)\n\nconst collapseData = reactive({\n  tool: true,\n  agent: true,\n})\nconst showTast = ref<string>('')\n\nconst route = useRoute()\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst permissionPrecise = computed(() => {\n  return permissionMap[current_source_type.value?.toLocaleLowerCase() as 'application' | 'tool'][\n    apiType.value as 'workspace' | 'systemManage'\n  ]\n})\n\nconst editPermission = computed(() => {\n  if (current_source_id.value && current_source_type.value) {\n    return permissionPrecise.value.trigger_edit(current_source_id.value)\n  } else {\n    return hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TRIGGER_EDIT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    )\n  }\n})\n\nconst triggerFormRef = ref<FormInstance>()\n\nconst getDefaultValue = () => {\n  return {\n    id: uuidv4(),\n    name: '',\n    desc: '',\n    trigger_task: [],\n    trigger_type: 'SCHEDULED',\n    trigger_setting: {\n      token: uuidv4().replace(/-/g, ''),\n      body: [],\n    },\n  }\n}\n\nconst form = ref<any>(getDefaultValue())\nconst is_edit = ref<boolean>(false)\nconst event_url = computed(() => {\n  return `${window.origin}${window.MaxKB.prefix}/api/trigger/v1/webhook/${form.value.id}`\n})\n\nconst lastPresetSetting = ref<any>(null)\nconst cronError = ref('')\n\nconst validateCron = () => {\n  const cron = form.value.trigger_setting.cron_expression?.trim()\n  if (!cron) {\n    cronError.value = ''\n    return\n  }\n  const fields = cron.split(/\\s+/)\n  if (fields.length !== 5 || !isValidCron(cron)) {\n    cronError.value = 'Cron表达式不合法'\n  } else {\n    cronError.value = ''\n  }\n}\n\nfunction switchScheduleType() {\n  const currentType = form.value.trigger_setting.schedule_type || 'daily'\n  const isCron = currentType === 'cron'\n\n  if (!isCron) {\n    lastPresetSetting.value = cloneDeep({\n      schedule_type: form.value.trigger_setting.schedule_type,\n      interval_unit: form.value.trigger_setting.interval_unit,\n      interval_value: form.value.trigger_setting.interval_value,\n      days: form.value.trigger_setting.days,\n      time: form.value.trigger_setting.time,\n    })\n\n    form.value.trigger_setting.schedule_type = 'cron'\n    form.value.trigger_setting.interval_unit = undefined\n    form.value.trigger_setting.interval_value = undefined\n    form.value.trigger_setting.days = undefined\n    form.value.trigger_setting.time = undefined\n    return\n  }\n  cronError.value = ''\n  const backup = lastPresetSetting.value\n  form.value.trigger_setting.schedule_type = backup?.schedule_type || 'daily'\n  form.value.trigger_setting.interval_unit = backup?.interval_unit\n  form.value.trigger_setting.interval_value = backup?.interval_value\n  form.value.trigger_setting.days = backup?.days\n  form.value.trigger_setting.time = backup?.time\n}\n\nconst addParameter = () => {\n  form.value.trigger_setting.body.push({ field: '', type: '' })\n}\nconst delParameter = (index: number | string) => {\n  form.value.trigger_setting.body.splice(index, 1)\n}\nconst handleChangeScheduled = (v: Array<any>) => {\n  scheduled.value = v\n}\n\nconst changeTriggerType = (type: string) => {\n  form.value.trigger_type = type\n}\nconst applicationDetailsDict = ref<any>({})\nconst toolDetailsDict = ref<any>({})\nconst applicationRefresh = (application_selected: any) => {\n  const application_list: Array<any> = application_selected.application_ids\n  const existApplicationIds = Object.keys(applicationDetailsDict)\n  application_list\n    .filter((id) => !existApplicationIds.includes(id))\n    .map((id) => {\n      return loadSharedApi({ type: 'application', systemType: apiType.value })\n        .getApplicationDetail(id)\n        .then((ok: any) => {\n          applicationDetailsDict.value[ok.data.id] = ok.data\n        })\n    })\n  const task_source_id_list = form.value.trigger_task\n    .filter((task: any) => task.source_type === 'APPLICATION')\n    .map((task: any) => task.source_id)\n\n  application_list\n    .filter((id) => !task_source_id_list.includes(id))\n    .forEach((id) => {\n      form.value.trigger_task.push({\n        source_type: 'APPLICATION',\n        source_id: id,\n        is_active: false,\n        parameter: {},\n      })\n    })\n  showTast.value = 'agent0'\n}\nconst applicationTask = computed(() => {\n  return form.value.trigger_task.filter((task: any) => task.source_type === 'APPLICATION')\n})\nconst toolTask = computed(() => {\n  return form.value.trigger_task.filter((task: any) => task.source_type === 'TOOL')\n})\nconst deleteTask = (task: any) => {\n  form.value.trigger_task = form.value.trigger_task.filter(\n    (t: any) => !(t.source_type === task.source_type && t.source_id === task.source_id),\n  )\n}\nconst applicationParameterRef = ref<Array<InstanceType<typeof ApplicationParameter>>>()\nconst toolParameterRef = ref<Array<InstanceType<typeof ToolParameter>>>()\nconst toolRefresh = (tool_selected: any) => {\n  const tool_ids: Array<any> = tool_selected.tool_ids\n\n  const existToolIds = Object.keys(toolDetailsDict)\n  tool_ids\n    .filter((id) => !existToolIds.includes(id))\n    .map((id) => {\n      loadSharedApi({ type: 'tool', systemType: apiType.value })\n        .getToolById(id)\n        .then((ok: any) => {\n          toolDetailsDict.value[ok.data.id] = ok.data\n        })\n    })\n  const task_source_id_list = form.value.trigger_task\n    .filter((task: any) => task.source_type === 'TOOL')\n    .map((task: any) => task.source_id)\n  tool_ids\n    .filter((id) => !task_source_id_list.includes(id))\n    .forEach((id) => {\n      form.value.trigger_task.push({\n        source_type: 'TOOL',\n        source_id: id,\n        is_active: false,\n        parameter: {},\n      })\n    })\n  showTast.value = 'tool0'\n}\n\nconst applicationDialogRef = ref<InstanceType<typeof ApplicationDialog>>()\nconst toolDialogRef = ref<InstanceType<typeof ToolDialog>>()\nconst openApplicationDialog = () => {\n  const application_id_list = form.value.trigger_task\n    .filter((task: any) => task.source_type === 'APPLICATION')\n    .map((task: any) => task.source_id)\n  applicationDialogRef.value?.open(application_id_list)\n}\nconst openToolDialog = () => {\n  const tool_id_list = form.value.trigger_task\n    .filter((task: any) => task.source_type === 'TOOL')\n    .map((task: any) => task.source_id)\n  toolDialogRef.value?.open(tool_id_list)\n}\nconst drawer = ref<boolean>(false)\n\nconst scheduled = computed({\n  get: () => {\n    const schedule_type = form.value.trigger_setting.schedule_type\n    if (schedule_type) {\n      if (schedule_type === 'interval') {\n        const interval_value = form.value.trigger_setting.interval_value\n        const interval_unit = form.value.trigger_setting.interval_unit\n        return [schedule_type, interval_unit, interval_value].filter((item) => item !== undefined)\n      } else {\n        const days = form.value.trigger_setting.days\n          ? form.value.trigger_setting.days[0]\n          : undefined\n        const time = form.value.trigger_setting.time\n          ? form.value.trigger_setting.time[0]\n          : undefined\n        if (schedule_type == 'daily') {\n          return [schedule_type, time].filter((item) => item !== undefined)\n        }\n        return [schedule_type, days, time].filter((item) => item !== undefined)\n      }\n    }\n    return []\n  },\n  set: (value) => {\n    const schedule_type = value[0]\n    form.value.trigger_setting.schedule_type = schedule_type\n    if (schedule_type == 'interval') {\n      form.value.trigger_setting.interval_unit = value[1]\n      form.value.trigger_setting.interval_value = value[2]\n    } else {\n      if (schedule_type == 'daily') {\n        form.value.trigger_setting.time = [value[1]]\n      } else {\n        form.value.trigger_setting.days = [value[1]]\n        form.value.trigger_setting.time = [value[2]]\n      }\n    }\n  },\n})\n\nconst init = (trigger_id: string) => {\n  if (current_source_id.value && current_source_type.value) {\n    let api\n    if (apiType.value === 'workspace') {\n      api = triggerAPI.getResourceTriggerDetail(\n        current_source_type.value,\n        current_source_id.value,\n        trigger_id,\n      )\n    } else {\n      api = systemManageTriggerAPI.getResourceTriggerDetail(\n        current_source_type.value,\n        current_source_id.value,\n        trigger_id,\n      )\n    }\n    api.then((ok) => {\n      form.value = { ...ok.data, trigger_task: [ok.data.trigger_task] }\n      applicationDetailsDict.value = { [ok.data.application_task.id]: ok.data.application_task }\n      toolDetailsDict.value = { [ok.data.tool_task.id]: ok.data.tool_task }\n    })\n  } else {\n    triggerAPI.getTriggerDetail(trigger_id).then((ok) => {\n      form.value = ok.data\n      applicationDetailsDict.value = (ok.data.application_task_list || [])\n        .map((item: any) => ({ [item.id]: item }))\n        .reduce((x: any, y: any) => ({ ...x, ...y }), {})\n      toolDetailsDict.value = (ok.data.tool_task_list || [])\n        .map((item: any) => ({ [item.id]: item }))\n        .reduce((x: any, y: any) => ({ ...x, ...y }), {})\n    })\n  }\n}\n\nfunction refreshToken() {\n  form.value.trigger_setting.token = uuidv4().replace(/-/g, '')\n}\nconst current_trigger_id = ref<string>()\nconst current_source_id = ref<string>()\nconst current_source_type = ref<string>()\n\nconst open = (trigger_id?: string, source_type?: string, source_id?: string) => {\n  is_edit.value = trigger_id ? true : false\n  current_trigger_id.value = trigger_id\n  drawer.value = true\n  if (source_type && source_id) {\n    current_source_type.value = source_type\n    current_source_id.value = source_id\n    if (source_type == 'APPLICATION') {\n      applicationRefresh({ application_ids: [source_id] })\n    }\n    if (source_type == 'TOOL') {\n      toolRefresh({ tool_ids: [source_id] })\n    }\n  }\n  if (trigger_id) {\n    init(trigger_id)\n  }\n}\n\nconst close = () => {\n  cronError.value = ''\n  current_source_id.value = undefined\n  current_source_type.value = undefined\n  drawer.value = false\n  form.value = getDefaultValue()\n}\nconst submit = () => {\n  if (\n    form.value.trigger_type === 'SCHEDULED' &&\n    form.value.trigger_setting.schedule_type === 'cron'\n  ) {\n    validateCron()\n    if (cronError.value) return\n  }\n\n  Promise.all([\n    ...(toolParameterRef.value ? toolParameterRef.value.map((item) => item.validate()) : []),\n    ...(applicationParameterRef.value\n      ? applicationParameterRef.value.map((item) => item.validate())\n      : []),\n    triggerFormRef.value?.validate(),\n  ]).then((ok) => {\n    if (is_edit.value) {\n      if (current_trigger_id.value) {\n        props.editTrigger(current_trigger_id.value, form.value).then((ok) => {\n          close()\n          emit('refresh')\n        })\n      }\n    } else {\n      props.createTrigger(form.value).then((ok) => {\n        close()\n        emit('refresh')\n      })\n    }\n  })\n}\nonMounted(() => {})\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/trigger/component/ApplicationParameter.vue",
    "content": "<template>\n  <el-form\n    @submit.prevent\n    :model=\"modelValue\"\n    label-position=\"top\"\n    require-asterisk-position=\"right\"\n    label-width=\"auto\"\n    hide-required-asterisk\n    ref=\"applicationParameterFormRef\"\n  >\n    <template v-for=\"(f, index) in base_field_list\" :key=\"f.field\">\n      <el-form-item\n        v-if=\"modelValue[f.field]\"\n        :label=\"$t('workflow.nodes.startNode.question')\"\n        :prop=\"`${f.field}.value`\"\n        :rules=\"{\n          message: $t('common.inputPlaceholder'),\n          trigger: 'blur',\n          required: f.required,\n        }\"\n      >\n        <template #label>\n          <div class=\"flex-between\">\n            <div>\n              {{ f.label.value }}\n              <span class=\"color-danger\" v-if=\"f.required\">*</span>\n            </div>\n            <el-select\n              :teleported=\"false\"\n              v-if=\"\n                modelValue[f.field] &&\n                trigger.trigger_type === 'EVENT' &&\n                trigger.trigger_setting.body.length\n              \"\n              v-model=\"modelValue[f.field].source\"\n              size=\"small\"\n              style=\"width: 85px\"\n            >\n              <el-option :label=\"$t('chat.quote')\" value=\"reference\" />\n              <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n            </el-select>\n          </div>\n        </template>\n\n        <el-cascader\n          v-if=\"modelValue[f.field].source === 'reference'\"\n          v-model=\"modelValue[f.field].value\"\n          :options=\"options\"\n          :placeholder=\"$t('common.selectPlaceholder')\"\n          :props=\"props\"\n          style=\"width: 100%\"\n        />\n        <el-input\n          v-else\n          v-model=\"modelValue[f.field].value\"\n          :placeholder=\"$t('common.inputPlaceholder')\"\n        />\n      </el-form-item>\n    </template>\n    <template v-for=\"(f, index) in user_input_field_list\" :key=\"f.field\">\n      <el-form-item\n        v-if=\"modelValue['user_input_field_list'] && modelValue['user_input_field_list'][f.field]\"\n        :label=\"$t('workflow.nodes.startNode.question')\"\n        :prop=\"`user_input_field_list.${f.field}.value`\"\n        :rules=\"{\n          message: $t('common.inputPlaceholder'),\n          trigger: 'blur',\n          required: f.required,\n        }\"\n      >\n        <template #label>\n          <div class=\"flex-between\">\n            <div>\n              {{ f.label.value }}\n              <span class=\"color-danger\" v-if=\"f.required\">*</span>\n            </div>\n            <el-select\n              :teleported=\"false\"\n              v-if=\"\n                modelValue['user_input_field_list'][f.field] &&\n                trigger.trigger_type === 'EVENT' &&\n                trigger.trigger_setting.body.length\n              \"\n              v-model=\"modelValue['user_input_field_list'][f.field].source\"\n              size=\"small\"\n              style=\"width: 85px\"\n            >\n              <el-option :label=\"$t('chat.quote')\" value=\"reference\" />\n              <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n            </el-select>\n          </div>\n        </template>\n\n        <el-cascader\n          v-if=\"modelValue['user_input_field_list'][f.field].source === 'reference'\"\n          v-model=\"modelValue['user_input_field_list'][f.field].value\"\n          :options=\"options\"\n          :placeholder=\"$t('common.selectPlaceholder')\"\n          :props=\"props\"\n          style=\"width: 100%\"\n        />\n        <el-input\n          v-else\n          v-model=\"modelValue['user_input_field_list'][f.field].value\"\n          :placeholder=\"$t('common.inputPlaceholder')\"\n        />\n      </el-form-item>\n    </template>\n    <template v-for=\"(f, index) in api_input_field_list\" :key=\"f.field\">\n      <el-form-item\n        v-if=\"modelValue['api_input_field_list'] && modelValue['api_input_field_list'][f.field]\"\n        :label=\"$t('workflow.nodes.startNode.question')\"\n        :prop=\"`api_input_field_list.${f.field}.value`\"\n        :rules=\"{\n          message: $t('common.inputPlaceholder'),\n          trigger: 'blur',\n          required: f.required,\n        }\"\n      >\n        <template #label>\n          <div class=\"flex-between\">\n            <div>\n              {{ f.label.value }}\n              <span class=\"color-danger\" v-if=\"f.required\">*</span>\n            </div>\n            <el-select\n              :teleported=\"false\"\n              v-if=\"modelValue['api_input_field_list'][f.field] && showSource\"\n              v-model=\"modelValue['api_input_field_list'][f.field].source\"\n              size=\"small\"\n              style=\"width: 85px\"\n            >\n              <el-option :label=\"$t('chat.quote')\" value=\"reference\" />\n              <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n            </el-select>\n          </div>\n        </template>\n\n        <el-cascader\n          v-if=\"modelValue['api_input_field_list'][f.field].source === 'reference'\"\n          v-model=\"modelValue['api_input_field_list'][f.field].value\"\n          :options=\"options\"\n          :placeholder=\"$t('common.selectPlaceholder')\"\n          :props=\"props\"\n          style=\"width: 100%\"\n        />\n        <el-input\n          v-else\n          v-model=\"modelValue['api_input_field_list'][f.field].value\"\n          :placeholder=\"$t('common.inputPlaceholder')\"\n        />\n      </el-form-item>\n    </template>\n  </el-form>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { type FormInstance } from 'element-plus'\nimport { t } from '@/locales'\nconst applicationParameterFormRef = ref<FormInstance>()\nconst props = defineProps<{ application?: any; modelValue: any; trigger: any }>()\nconst emit = defineEmits(['update:modelValue'])\nconst showSource = computed(() => {\n  return props.trigger.trigger_type === 'EVENT' && props.trigger.trigger_setting.body.length > 0\n})\nwatch(\n  () => showSource.value,\n  () => {\n    if (!showSource.value) {\n      const parameter: any = { ...props.modelValue }\n      base_field_list.value.forEach((f) => {\n        if (!parameter[f.field]) {\n          parameter[f.field] = { source: 'custom', value: f.default_value }\n        } else {\n          parameter[f.field] = { ...parameter[f.field], source: 'custom' }\n        }\n      })\n      api_input_field_list.value.forEach((f) => {\n        if (!parameter.api_input_field_list) {\n          parameter['api_input_field_list'] = {}\n        }\n        if (!parameter['api_input_field_list'][f.field]) {\n          parameter['api_input_field_list'][f.field] = {\n            source: 'custom',\n            value: f.default_value ? f.default_value : '',\n          }\n        } else {\n          parameter['api_input_field_list'][f.field] = {\n            ...parameter['api_input_field_list'][f.field],\n            source: 'custom',\n          }\n        }\n      })\n      user_input_field_list.value.forEach((f) => {\n        if (!parameter['user_input_field_list']) {\n          parameter['user_input_field_list'] = {}\n        }\n        if (!parameter['user_input_field_list'][f.field]) {\n          parameter['user_input_field_list'][f.field] = {\n            source: 'custom',\n            value: f.default_value ? f.default_value : '',\n          }\n        } else {\n          parameter['user_input_field_list'][f.field] = {\n            ...parameter['user_input_field_list'][f.field],\n            source: 'custom',\n          }\n        }\n      })\n      emit('update:modelValue', { ...parameter })\n    }\n  },\n)\nconst options = computed(() => {\n  if (props.trigger.trigger_type === 'EVENT') {\n    const body = props.trigger.trigger_setting.body\n    if (body) {\n      return [\n        {\n          label: 'body',\n          value: 'body',\n          children: body.map((item: any) => ({ label: item.field, value: item.field })),\n        },\n      ]\n    }\n    return []\n  } else {\n  }\n  return []\n})\n\nconst base_node = computed(() => {\n  return (props.application?.work_flow?.nodes || []).find((n: any) => n.type === 'base-node')\n})\nconst api_input_field_list = computed(() => {\n  const result: Array<any> = []\n  if (base_node.value && base_node.value.properties.api_input_field_list) {\n    base_node.value.properties.api_input_field_list.forEach((item: any) => {\n      result.push({\n        field: item.variable,\n        required: item.is_required,\n        label: { value: item.variable },\n      })\n    })\n  }\n  return result\n})\nconst user_input_field_list = computed(() => {\n  const result: Array<any> = []\n  if (base_node.value && base_node.value.properties.user_input_field_list) {\n    base_node.value.properties.user_input_field_list.forEach((item: any) => {\n      result.push({\n        field: item.field,\n        required: item.required,\n        label:\n          typeof item.label == 'string'\n            ? { value: item.label }\n            : { ...item.label, value: item.label.label },\n      })\n    })\n  }\n  return result\n})\nconst base_field_list = computed<Array<any>>(() => {\n  const result: Array<any> = [\n    { field: 'question', required: true, default_value: '', label: { value: 'Question' } },\n  ]\n  if (base_node.value) {\n    if (base_node.value.properties.node_data.file_upload_enable) {\n      if (base_node.value.properties.node_data.file_upload_setting.document) {\n        result.push({\n          field: 'document_list',\n          required: true,\n          default_value: '[]',\n          label: { value: t('common.fileUpload.document') },\n        })\n      }\n      if (base_node.value.properties.node_data.file_upload_setting.image) {\n        result.push({\n          field: 'image_list',\n          required: true,\n          default_value: '[]',\n          label: { value: t('common.fileUpload.image') },\n        })\n      }\n      if (base_node.value.properties.node_data.file_upload_setting.audio) {\n        result.push({\n          field: 'audio_list',\n          required: true,\n          default_value: '[]',\n          label: { value: t('common.fileUpload.audio') },\n        })\n      }\n      if (base_node.value.properties.node_data.file_upload_setting.video) {\n        result.push({\n          field: 'video_list',\n          required: true,\n          default_value: '[]',\n          label: { value: t('common.fileUpload.video') },\n        })\n      }\n\n      if (base_node.value.properties.node_data.file_upload_setting.other) {\n        result.push({\n          field: 'other_list',\n          required: true,\n          default_value: '[]',\n          label: { value: t('common.fileUpload.other') },\n        })\n      }\n    }\n  }\n  return result\n})\nconst init_parameters = () => {\n  const parameter: any = { ...props.modelValue }\n  base_field_list.value.forEach((f) => {\n    if (!parameter[f.field]) {\n      parameter[f.field] = { source: 'custom', value: f.default_value }\n    }\n  })\n  api_input_field_list.value.forEach((f) => {\n    if (!parameter.api_input_field_list) {\n      parameter['api_input_field_list'] = {}\n    }\n    if (!parameter['api_input_field_list'][f.field]) {\n      parameter['api_input_field_list'][f.field] = {\n        source: 'custom',\n        value: f.default_value ? f.default_value : '',\n      }\n    }\n  })\n  user_input_field_list.value.forEach((f) => {\n    if (!parameter['user_input_field_list']) {\n      parameter['user_input_field_list'] = {}\n    }\n    if (!parameter['user_input_field_list'][f.field]) {\n      parameter['user_input_field_list'][f.field] = {\n        source: 'custom',\n        value: f.default_value ? f.default_value : '',\n      }\n    }\n  })\n\n  emit('update:modelValue', { ...parameter })\n}\n\ninit_parameters()\nconst validate = () => {\n  return applicationParameterFormRef.value?.validate()\n}\ndefineExpose({ validate })\n</script>\n<style lang=\"\"></style>\n"
  },
  {
    "path": "ui/src/views/trigger/component/ToolParameter.vue",
    "content": "<template>\n  <el-form\n    @submit.prevent\n    :model=\"modelValue\"\n    label-position=\"top\"\n    require-asterisk-position=\"right\"\n    label-width=\"auto\"\n    hide-required-asterisk\n    ref=\"toolParameterFormRef\"\n  >\n    <template v-for=\"(f, index) in input_field_list\" :key=\"f.field\">\n      <el-form-item\n        v-if=\"modelValue[f.field]\"\n        :label=\"$t('workflow.nodes.startNode.question')\"\n        :prop=\"`${f.field}.value`\"\n        :rules=\"{\n          message: $t('common.inputPlaceholder'),\n          trigger: 'blur',\n          required: f.required,\n        }\"\n      >\n        <template #label>\n          <div class=\"flex-between\">\n            <div>\n              {{ f.label.value }}\n              <span class=\"color-danger\" v-if=\"f.required\">*</span>\n            </div>\n            <el-select\n              :teleported=\"false\"\n              v-if=\"\n                modelValue[f.field] &&\n                trigger.trigger_type === 'EVENT' &&\n                trigger.trigger_setting.body.length\n              \"\n              v-model=\"modelValue[f.field].source\"\n              size=\"small\"\n              style=\"width: 85px\"\n            >\n              <el-option :label=\"$t('chat.quote')\" value=\"reference\" />\n              <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n            </el-select>\n          </div>\n        </template>\n\n        <el-cascader\n          v-if=\"modelValue[f.field].source === 'reference'\"\n          v-model=\"modelValue[f.field].value\"\n          :options=\"options\"\n          :placeholder=\"$t('common.selectPlaceholder')\"\n          :props=\"props\"\n          style=\"width: 100%\"\n        />\n        <el-input\n          v-else\n          v-model=\"modelValue[f.field].value\"\n          :placeholder=\"$t('common.inputPlaceholder')\"\n        />\n      </el-form-item>\n    </template>\n  </el-form>\n</template>\n<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { type FormInstance } from 'element-plus'\nconst toolParameterFormRef = ref<FormInstance>()\nconst props = defineProps<{ tool?: any; modelValue: any; trigger: any }>()\nconst emit = defineEmits(['update:modelValue'])\nconst showSource = computed(() => {\n  return props.trigger.trigger_type === 'EVENT' && props.trigger.trigger_setting.body.length > 0\n})\n\nwatch(\n  () => showSource.value,\n  () => {\n    if (!showSource.value) {\n      const parameter: any = {}\n      input_field_list.value.forEach((f) => {\n        if (props.modelValue[f.field]) {\n          parameter[f.field] = {\n            ...props.modelValue[f.field],\n            source: 'custom',\n          }\n        } else {\n          parameter[f.field] = { source: 'custom', value: f.default_value }\n        }\n      })\n      emit('update:modelValue', { ...parameter })\n    }\n  },\n)\nconst options = computed(() => {\n  if (props.trigger.trigger_type === 'EVENT') {\n    const body = props.trigger.trigger_setting.body\n    if (body) {\n      return [\n        {\n          label: 'body',\n          value: 'body',\n          children: body.map((item: any) => ({ label: item.field, value: item.field })),\n        },\n      ]\n    }\n    return []\n  } else {\n  }\n  return []\n})\n\nconst input_field_list = computed(() => {\n  const result: Array<any> = []\n  if (props.tool && props.tool.input_field_list) {\n    props.tool.input_field_list.forEach((item: any) => {\n      result.push({\n        field: item.name,\n        required: item.is_required,\n        label: { value: item.name },\n      })\n    })\n  }\n  return result\n})\n\nconst init_parameters = () => {\n  const parameter: any = {}\n  input_field_list.value.forEach((f) => {\n    parameter[f.field] = { source: 'custom', value: f.default_value }\n  })\n  emit('update:modelValue', { ...parameter, ...props.modelValue })\n}\n\ninit_parameters()\nconst validate = () => {\n  return toolParameterFormRef.value?.validate()\n}\ndefineExpose({ validate })\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/trigger/execution-record/ExecutionDetailDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"visible\"\n    size=\"800px\"\n    :modal=\"false\"\n    destroy-on-close\n    :before-close=\"closeHandle\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :show-close=\"false\"\n  >\n    <template #header>\n      <div class=\"flex align-center\" style=\"margin-left: -8px\">\n        <el-button class=\"cursor mr-4\" link @click.prevent=\"visible = false\">\n          <el-icon :size=\"20\">\n            <Back />\n          </el-icon>\n        </el-button>\n        <h4>{{ $t('chat.executionDetails.title') }}</h4>\n      </div>\n    </template>\n    <div>\n      <el-scrollbar>\n        <h4 class=\"title-decoration-1 mb-16 mt-4\">\n          {{ $t('common.ExecutionRecord.title') }}\n        </h4>\n        <el-card class=\"mb-24\" shadow=\"never\" style=\"--el-card-padding: 12px 16px\">\n          <el-row :gutter=\"16\" class=\"lighter\">\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('views.trigger.triggerTask') }}</p>\n              <p class=\"flex align-center\">\n                <ToolIcon\n                  v-if=\"\n                    props.currentContent?.source_type === 'TOOL' &&\n                    !props.currentContent?.source_icon\n                  \"\n                  :size=\"22\"\n                />\n                <el-avatar v-else shape=\"square\" :size=\"22\" style=\"background: none\">\n                  <img\n                    :src=\"resetUrl(props.currentContent?.source_icon, resetUrl('./favicon.ico'))\"\n                    alt=\"\"\n                  />\n                </el-avatar>\n                <span class=\"ellipsis-1 ml-8\" :title=\"props.currentContent?.source_name\">{{\n                  props.currentContent?.source_name || '-'\n                }}</span>\n              </p>\n            </el-col>\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('common.status.label') }}</p>\n              <p>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-if=\"props.currentContent?.state === 'SUCCESS'\"\n                >\n                  <el-icon class=\"color-success\"><SuccessFilled /></el-icon>\n                  {{ $t('common.status.success') }}\n                </el-text>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-else-if=\"props.currentContent?.state === 'FAILURE'\"\n                >\n                  <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n                  {{ $t('common.status.fail') }}\n                </el-text>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-else-if=\"props.currentContent?.state === 'REVOKED'\"\n                >\n                  <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n                  {{ $t('common.status.REVOKED') }}\n                </el-text>\n                <el-text\n                  class=\"color-text-primary\"\n                  v-else-if=\"props.currentContent?.state === 'REVOKE'\"\n                >\n                  <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n                  {{ $t('common.status.REVOKE') }}\n                </el-text>\n                <el-text class=\"color-text-primary\" v-else>\n                  <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n                  {{ $t('common.status.STARTED') }}\n                </el-text>\n              </p>\n            </el-col>\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('chat.KnowledgeSource.consumeTime') }}</p>\n              <p>\n                {{\n                  props.currentContent?.run_time != undefined\n                    ? props.currentContent?.run_time?.toFixed(2) + 's'\n                    : '-'\n                }}\n              </p>\n            </el-col>\n            <el-col :span=\"6\">\n              <p class=\"color-secondary mb-4\">{{ $t('chat.executionDetails.createTime') }}</p>\n              <p>{{ datetimeFormat(props.currentContent?.create_time) }}</p>\n            </el-col>\n          </el-row>\n        </el-card>\n        <h4 class=\"title-decoration-1 mb-16 mt-4\">\n          {{ $t('chat.executionDetails.title') }}\n        </h4>\n        <template v-if=\"taskRecordDetails && taskRecordDetails.state === 'TRIGGER_ERROR'\">\n          <div class=\"card-never border-r-6 mb-12\">\n            <h5 class=\"p-8-12\">{{ $t('views.trigger.triggerParam') }}</h5>\n            <div class=\"p-8-12 border-t-dashed lighter\">\n              {{ taskRecordDetails.meta.input }}\n            </div>\n          </div>\n          <div class=\"card-never border-r-6 mb-12\">\n            <h5 class=\"p-8-12\">{{ $t('views.trigger.errorMsg') }}</h5>\n            <div class=\"p-8-12 border-t-dashed lighter\">\n              {{ taskRecordDetails.meta.err_message }}\n            </div>\n          </div>\n        </template>\n        <ExecutionDetailContent\n          v-else-if=\"props.currentContent?.source_type === 'APPLICATION'\"\n          :detail=\"detail\"\n          :appType=\"props.currentContent.type\"\n        ></ExecutionDetailContent>\n        <template v-else v-for=\"(item, index) in arraySort(detail ?? [], 'index')\" :key=\"index\">\n          <ExecutionDetailCard :data=\"item\"> </ExecutionDetailCard>\n        </template>\n      </el-scrollbar>\n    </div>\n    <template #footer>\n      <div>\n        <el-button @click=\"pre\" :disabled=\"pre_disable || loading\">{{\n          $t('common.pages.prev')\n        }}</el-button>\n        <el-button @click=\"next\" :disabled=\"next_disable || loading\">{{\n          $t('common.pages.next')\n        }}</el-button>\n      </div>\n    </template>\n  </el-drawer>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { arraySort } from '@/utils/array'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport ExecutionDetailCard from '@/components/execution-detail-card/index.vue'\nimport ExecutionDetailContent from '@/components/ai-chat/component/knowledge-source-component/ExecutionDetailContent.vue'\nimport { datetimeFormat } from '@/utils/time'\nimport triggerAPI from '@/api/trigger/trigger'\nconst props = withDefaults(\n  defineProps<{\n    /**\n     * 当前的action_id\n     */\n    currentId: string\n    currentContent: any\n    /**\n     * 下一条\n     */\n    next: () => void\n    /**\n     * 上一条\n     */\n    pre: () => void\n\n    pre_disable: boolean\n\n    next_disable: boolean\n  }>(),\n  {},\n)\n\nconst emit = defineEmits(['update:currentId', 'update:currentContent'])\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst taskRecordDetails = ref<any>()\nconst detail = ref<any>(null)\n\nconst loading = ref(false)\nconst visible = ref(false)\n\nfunction closeHandle() {}\n\nwatch(\n  () => props.currentId,\n  () => {\n    if (props.currentId) {\n      getDetail()\n    }\n  },\n)\n\nwatch(visible, (bool) => {\n  if (!bool) {\n    emit('update:currentId', '')\n    emit('update:currentContent', null)\n  }\n})\n\nfunction getDetail() {\n  triggerAPI\n    .getTriggerTaskRecordDetails(\n      props.currentContent?.trigger_id,\n      props.currentContent?.trigger_task_id,\n      props.currentContent?.id,\n    )\n    .then((ok) => {\n      if (ok.data.details) {\n        detail.value = Object.values(ok.data.details)\n      }\n      taskRecordDetails.value = ok.data\n    })\n}\n\nconst open = (row: any) => {\n  visible.value = true\n}\n\ndefineExpose({\n  open,\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/views/trigger/execution-record/TriggerTaskRecordDrawer.vue",
    "content": "<template>\n  <el-drawer\n    v-model=\"drawer\"\n    :title=\"$t('common.ExecutionRecord.title')\"\n    direction=\"rtl\"\n    size=\"800px\"\n    :before-close=\"close\"\n  >\n    <div class=\"flex-between mb-16\">\n      <div class=\"flex-between complex-search\">\n        <el-select\n          class=\"complex-search__left\"\n          v-model=\"searchType\"\n          @change=\"changeFilterHandle\"\n          style=\"width: 100px\"\n        >\n          <el-option :label=\"$t('common.name')\" value=\"name\" />\n          <el-option :label=\"$t('common.status.label')\" value=\"state\" />\n          <el-option :label=\"$t('common.sourceType')\" value=\"source_type\" />\n        </el-select>\n        <el-input\n          v-if=\"searchType === 'name'\"\n          v-model=\"query.name\"\n          :placeholder=\"$t('common.search')\"\n          style=\"width: 220px\"\n          clearable\n          @change=\"getList(true)\"\n        />\n        <el-select\n          v-else-if=\"searchType === 'source_type'\"\n          v-model=\"query.source_type\"\n          @change=\"getList(true)\"\n          filterable\n          clearable\n          :reserve-keyword=\"false\"\n          collapse-tags\n          collapse-tags-tooltip\n          style=\"width: 220px\"\n          :placeholder=\"$t('common.search')\"\n        >\n          <el-option :label=\"$t('views.application.title')\" value=\"APPLICATION\" />\n          <el-option :label=\"$t('views.tool.title')\" value=\"TOOL\" />\n        </el-select>\n        <el-select\n          v-else-if=\"searchType === 'state'\"\n          v-model=\"query.state\"\n          @change=\"getList(true)\"\n          filterable\n          clearable\n          :reserve-keyword=\"false\"\n          collapse-tags\n          collapse-tags-tooltip\n          style=\"width: 220px\"\n          :placeholder=\"$t('common.search')\"\n        >\n          <el-option :label=\"$t('common.status.success')\" value=\"SUCCESS\" />\n          <el-option :label=\"$t('common.status.STARTED')\" value=\"STARTED\" />\n          <el-option :label=\"$t('common.status.fail')\" value=\"FAILURE\" />\n        </el-select>\n      </div>\n    </div>\n\n    <app-table\n      ref=\"multipleTableRef\"\n      class=\"mt-16\"\n      :data=\"tableData\"\n      :pagination-config=\"paginationConfig\"\n      @sizeChange=\"changeSize\"\n      @changePage=\"getList(true)\"\n      :default-sort=\"{ prop: 'create_time', order: 'descending' }\"\n      @sort-change=\"handleSortChange\"\n      :maxTableHeight=\"200\"\n      :row-key=\"(row: any) => row.id\"\n      v-loading=\"loading\"\n    >\n      <el-table-column\n        prop=\"name\"\n        :label=\"$t('views.trigger.triggerTask')\"\n        min-width=\"130\"\n        show-overflow-tooltip\n      >\n        <template #default=\"{ row }\">\n          <el-space :size=\"8\">\n            <ToolIcon v-if=\"row.source_type === 'TOOL' && !row.source_icon\" :size=\"22\" />\n            <el-avatar v-else shape=\"square\" :size=\"22\" style=\"background: none\">\n              <img :src=\"resetUrl(row?.source_icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n            </el-avatar>\n            <span class=\"ellipsis\">{{ row.source_name }}</span>\n          </el-space>\n        </template>\n      </el-table-column>\n\n      <el-table-column\n        prop=\"source_type\"\n        width=\"100\"\n        show-overflow-tooltip\n        :label=\"$t('common.type')\"\n      >\n        <template #default=\"{ row }\">\n          {{\n            row.source_type === 'APPLICATION'\n              ? $t('views.application.title')\n              : $t('views.tool.title')\n          }}\n        </template>\n      </el-table-column>\n\n      <el-table-column prop=\"state\" :label=\"$t('common.status.label')\" width=\"100\">\n        <template #default=\"{ row }\">\n          <el-text class=\"color-text-primary\" v-if=\"row.state === 'SUCCESS'\">\n            <el-icon class=\"color-success\"><SuccessFilled /></el-icon>\n            {{ $t('common.status.success') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else-if=\"row.state === 'FAILURE'\">\n            <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n            {{ $t('common.status.fail') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else-if=\"row.state === 'REVOKED'\">\n            <el-icon class=\"color-danger\"><CircleCloseFilled /></el-icon>\n            {{ $t('common.status.REVOKED') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else-if=\"row.state === 'REVOKE'\">\n            <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n            {{ $t('common.status.REVOKE') }}\n          </el-text>\n          <el-text class=\"color-text-primary\" v-else>\n            <el-icon class=\"is-loading color-primary\"><Loading /></el-icon>\n            {{ $t('common.status.STARTED') }}\n          </el-text>\n        </template>\n      </el-table-column>\n      <el-table-column prop=\"run_time\" :label=\"$t('chat.KnowledgeSource.consumeTime')\">\n        <template #default=\"{ row }\">\n          {{ row.run_time != undefined ? row.run_time?.toFixed(2) + 's' : '-' }}\n        </template>\n      </el-table-column>\n      <el-table-column\n        sortable\n        prop=\"create_time\"\n        :label=\"$t('chat.executionDetails.createTime')\"\n        width=\"180\"\n      >\n        <template #default=\"{ row }\">\n          {{ datetimeFormat(row.create_time) }}\n        </template>\n      </el-table-column>\n\n      <el-table-column :label=\"$t('common.operation')\" width=\"90\">\n        <template #default=\"{ row }\">\n          <div class=\"flex\">\n            <el-tooltip effect=\"dark\" :content=\"$t('chat.executionDetails.title')\" placement=\"top\">\n              <el-button type=\"primary\" text @click.stop=\"toDetails(row)\">\n                <AppIcon iconName=\"app-operate-log\"></AppIcon>\n              </el-button>\n            </el-tooltip>\n          </div>\n        </template>\n      </el-table-column>\n    </app-table>\n\n    <ExecutionDetailDrawer\n      ref=\"ExecutionDetailDrawerRef\"\n      v-model:currentId=\"currentId\"\n      v-model:currentContent=\"currentContent\"\n      :next=\"nextRecord\"\n      :pre=\"preRecord\"\n      :pre_disable=\"pre_disable\"\n      :next_disable=\"next_disable\"\n    />\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, computed } from 'vue'\nimport { isAppIcon, resetUrl } from '@/utils/common'\nimport triggerAPI from '@/api/trigger/trigger'\nimport { datetimeFormat } from '@/utils/time'\nimport type { Dict } from '@/api/type/common'\nimport ExecutionDetailDrawer from './ExecutionDetailDrawer.vue'\n\nconst searchType = ref<string>('name')\nconst drawer = ref<boolean>(false)\nconst paginationConfig = reactive({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\nconst tableData = ref<Array<any>>([])\nconst query = ref<any>({\n  state: '',\n  name: '',\n  source_type: '',\n  order: '',\n})\nconst loading = ref<boolean>(false)\nconst current_trigger_id = ref<string>()\n\nconst tableIndexMap = computed<Dict<number>>(() => {\n  return tableData.value\n    .map((row, index) => ({\n      [row.id]: index,\n    }))\n    .reduce((pre, next) => ({ ...pre, ...next }), {})\n})\nconst ExecutionDetailDrawerRef = ref<any>()\n\nconst currentId = ref<string>('')\nconst currentContent = ref<string>('')\n\nconst toDetails = (row: any) => {\n  currentContent.value = row\n  currentId.value = row.id\n  ExecutionDetailDrawerRef.value?.open(row)\n}\n\nconst changeFilterHandle = () => {\n  query.value = { name: '', statu: '' }\n}\nconst changeSize = () => {\n  paginationConfig.current_page = 1\n  getList()\n}\nfunction handleSortChange({ prop, order }: { prop: string; order: string }) {\n  query.value.order = order === 'ascending' ? `ett.${prop}` : `-ett.${prop}`\n  getList()\n}\nconst getList = (isLoading?: boolean) => {\n  if (current_trigger_id.value) {\n    return triggerAPI\n      .pageTriggerTaskRecord(\n        current_trigger_id.value,\n        paginationConfig,\n        { ...query.value },\n        isLoading ? loading : undefined,\n      )\n      .then((ok) => {\n        tableData.value = ok.data.records\n        paginationConfig.total = ok.data.total\n      })\n  } else return Promise.resolve()\n}\n\nconst pre_disable = computed(() => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value)\n  return index === 0 && paginationConfig.current_page === 1\n})\n\nconst next_disable = computed(() => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value) + 1\n  return (\n    index >= tableData.value.length &&\n    index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=\n      paginationConfig.total - 1\n  )\n})\n\n/**\n * 下一页\n */\nconst nextRecord = () => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value) + 1\n  if (index >= tableData.value.length) {\n    if (paginationConfig.current_page * paginationConfig.page_size >= paginationConfig.total) {\n      return\n    }\n    paginationConfig.current_page = paginationConfig.current_page + 1\n    getList(true).then(() => {\n      currentId.value = tableData.value[index].id\n      currentContent.value = tableData.value[index]\n    })\n    return\n  } else {\n    currentId.value = tableData.value[index].id\n    currentContent.value = tableData.value[index]\n  }\n}\n/**\n * 上一页\n */\nconst preRecord = () => {\n  const index = tableData.value.findIndex((item) => item.id === currentId.value) - 1\n  if (index < 0 && 1) {\n    if (paginationConfig.current_page === 1) {\n      return\n    }\n    paginationConfig.current_page = paginationConfig.current_page - 1\n    getList(true).then(() => {\n      currentId.value = tableData.value[tableData.value.length - 1].id\n      currentContent.value = tableData.value[tableData.value.length - 1]\n    })\n  } else {\n    currentId.value = tableData.value[index].id\n    currentContent.value = tableData.value[index]\n  }\n}\nconst open = (trigger_id: string) => {\n  current_trigger_id.value = trigger_id\n  getList(true)\n  drawer.value = true\n}\nconst close = () => {\n  paginationConfig.current_page = 1\n  paginationConfig.total = 0\n  tableData.value = []\n  drawer.value = false\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/views/trigger/index.vue",
    "content": "<template>\n  <div class=\"trigger-manage p-16-24\">\n    <h2 class=\"ml-24 mb-16\">{{ $t('views.trigger.title') }}</h2>\n    <el-card style=\"--el-card-padding: 0\">\n      <div class=\"main-calc-height\">\n        <div class=\"p-24\">\n          <div class=\"flex-between\">\n            <div>\n              <el-button\n                v-if=\"triggerPermissionMap.create()\"\n                type=\"primary\"\n                @click=\"openCreateTriggerDrawer\"\n                >{{ $t('common.create') }}\n              </el-button>\n              <el-button\n                v-if=\"triggerPermissionMap.edit()\"\n                @click=\"batchChangeState(true)\"\n                :disabled=\"multipleSelection.length === 0\"\n                >{{ $t('common.status.enable') }}\n              </el-button>\n              <el-button\n                v-if=\"triggerPermissionMap.edit()\"\n                @click=\"batchChangeState(false)\"\n                :disabled=\"multipleSelection.length === 0\"\n                >{{ $t('common.status.disable') }}\n              </el-button>\n              <el-button\n                v-if=\"triggerPermissionMap.delete()\"\n                @click=\"batchDelete\"\n                :disabled=\"multipleSelection.length === 0\"\n                >{{ $t('common.delete') }}\n              </el-button>\n            </div>\n            <div class=\"flex-between complex-search\">\n              <el-select\n                class=\"complex-search__left\"\n                v-model=\"search_type\"\n                style=\"width: 90px\"\n                @change=\"search_type_change\"\n              >\n                <el-option :label=\"$t('common.name')\" value=\"name\" />\n                <el-option :label=\"$t('common.type')\" value=\"type\" />\n                <el-option :label=\"$t('views.trigger.task')\" value=\"task\" />\n                <el-option :label=\"$t('common.status.label')\" value=\"is_active\" />\n                <el-option :label=\"$t('common.creator')\" value=\"create_user\" />\n              </el-select>\n              <el-input\n                v-if=\"search_type === 'name'\"\n                v-model=\"search_form.name\"\n                @change=\"searchHandle\"\n                :placeholder=\"$t('common.searchBar.placeholder')\"\n                style=\"width: 220px\"\n                clearable\n              />\n              <el-select\n                v-else-if=\"search_type === 'type'\"\n                v-model=\"search_form.type\"\n                @change=\"searchHandle\"\n                filterable\n                clearable\n                style=\"width: 220px\"\n              >\n                <el-option :label=\"$t('views.trigger.type.scheduled')\" value=\"SCHEDULED\" />\n                <el-option :label=\"$t('views.trigger.type.event')\" value=\"EVENT\" />\n              </el-select>\n              <el-select\n                v-else-if=\"search_type === 'is_active'\"\n                v-model=\"search_form.is_active\"\n                @change=\"searchHandle\"\n                filterable\n                clearable\n                style=\"width: 220px\"\n              >\n                <el-option :label=\"$t('common.status.enabled')\" value=\"true\" />\n                <el-option :label=\"$t('common.status.disabled')\" value=\"false\" />\n              </el-select>\n              <el-select\n                v-else-if=\"search_type === 'create_user'\"\n                v-model=\"search_form.create_user\"\n                @change=\"searchHandle\"\n                filterable\n                clearable\n                style=\"width: 220px\"\n              >\n                <el-option\n                  v-for=\"u in user_options\"\n                  :key=\"u.id\"\n                  :value=\"u.id\"\n                  :label=\"u.nick_name\"\n                />\n              </el-select>\n              <el-input\n                v-if=\"search_type === 'task'\"\n                v-model=\"search_form.task\"\n                @change=\"searchHandle\"\n                :placeholder=\"$t('common.search')\"\n                style=\"width: 220px\"\n                clearable\n              />\n            </div>\n          </div>\n          <app-table\n            ref=\"multipleTableRef\"\n            class=\"mt-16\"\n            :data=\"triggerData\"\n            :pagination-config=\"paginationConfig\"\n            @sizeChange=\"handleSizeChange\"\n            @changePage=\"getList\"\n            @selection-change=\"handleSelectionChange\"\n            v-loading=\"loading\"\n            :row-key=\"(row: any) => row.id\"\n            :maxTableHeight=\"300\"\n          >\n            <el-table-column type=\"selection\" width=\"55\" :reserve-selection=\"true\" />\n            <el-table-column\n              prop=\"name\"\n              :label=\"multipleSelection.length === 0 ? $t('common.name') : `${$t('common.selected')} ${multipleSelection.length} ${$t('views.document.items') }`\"\n              show-overflow-tooltip\n              width=\"220\"\n            >\n              <template #default=\"{ row }\">\n                <div class=\"flex align-center\">\n                  <TriggerIcon :type=\"row.trigger_type\" class=\"mr-8\" :size=\"24\" />\n                  <span class=\"ellipsis\" style=\"max-width: 160px\">\n                    {{ row.name }}\n                  </span>\n                </div>\n              </template>\n            </el-table-column>\n            <el-table-column prop=\"trigger_type\" :label=\"$t('common.type')\" width=\"120\">\n              <template #default=\"{ row }\">\n                {{ $t(TriggerType[row.trigger_type as keyof typeof TriggerType]) }}\n              </template>\n            </el-table-column>\n            <el-table-column prop=\"is_active\" :label=\"$t('common.status.label')\" width=\"120\">\n              <template #default=\"{ row }\">\n                <div v-if=\"row.is_active\" class=\"flex align-center\">\n                  <el-icon class=\"color-success mr-8\" style=\"font-size: 16px\">\n                    <SuccessFilled />\n                  </el-icon>\n                  <span class=\"color-text-primary\">\n                    {{ $t('common.status.enabled') }}\n                  </span>\n                </div>\n                <div v-else class=\"flex align-center\">\n                  <AppIcon iconName=\"app-disabled\" class=\"color-secondary mr-8\"></AppIcon>\n                  <span class=\"color-text-primary\">\n                    {{ $t('common.status.disabled') }}\n                  </span>\n                </div>\n              </template>\n            </el-table-column>\n\n            <el-table-column\n              prop=\"desc\"\n              :label=\"$t('common.desc')\"\n              show-overflow-tooltip\n              min-width=\"170\"\n            >\n            </el-table-column>\n            <el-table-column prop=\"next_run_time\" :label=\"$t('views.trigger.nextTime')\" width=\"175\">\n              <template #default=\"{ row }\">\n                {{ datetimeFormat(row.next_run_time) }}\n              </template>\n            </el-table-column>\n            <el-table-column prop=\"trigger_task\" :label=\"$t('views.trigger.task')\" width=\"150\">\n              <template #default=\"{ row }\">\n                <el-popover\n                  placement=\"top-start\"\n                  :popper-style=\"{ width: 'auto', maxWidth: '300px' }\"\n                  :persistent=\"false\"\n                >\n                  <template #reference>\n                    <div class=\"flex\">\n                      <el-tag size=\"small\"\n                        class=\"info-tag mr-8 cursor\"\n                        v-if=\"\n                          row.trigger_task.filter((item: any) => item.type === 'APPLICATION').length\n                        \"\n                      >\n                        {{ $t('views.application.title') }}\n                        {{\n                          row.trigger_task.filter((item: any) => item.type === 'APPLICATION').length\n                        }}\n                      </el-tag>\n                      <el-tag size=\"small\"\n                        class=\"info-tag cursor\"\n                        v-if=\"row.trigger_task.filter((item: any) => item.type === 'TOOL').length\"\n                      >\n                        {{ $t('views.tool.title') }}\n                        {{ row.trigger_task.filter((item: any) => item.type === 'TOOL').length }}\n                      </el-tag>\n                    </div>\n                  </template>\n\n                  <div>\n                    <!-- 智能体部分 -->\n                    <div\n                      v-if=\"\n                        row.trigger_task.filter((item: any) => item.type === 'APPLICATION').length\n                      \"\n                    >\n                      <h5 class=\"color-input-placeholder\">\n                        {{ $t('views.application.title') }}\n                        {{\n                          row.trigger_task.filter((item: any) => item.type === 'APPLICATION').length\n                        }}\n                      </h5>\n                      <div\n                        v-for=\"item in row.trigger_task.filter(\n                          (item: any) => item.type === 'APPLICATION',\n                        )\"\n                        :key=\"item.id\"\n                        class=\"flex align-center mt-8\"\n                      >\n                        <el-avatar shape=\"square\" :size=\"20\" style=\"background: none\" class=\"mr-8\">\n                          <img :src=\"resetUrl(item?.icon, resetUrl('./favicon.ico'))\" alt=\"\" />\n                        </el-avatar>\n                        <span class=\"ellipsis-1\" :title=\"item.name\">{{ item.name }}</span>\n                      </div>\n                    </div>\n                    <el-divider\n                      class=\"mt-8 mb-8\"\n                      v-if=\"\n                        row.trigger_task.filter((item: any) => item.type === 'APPLICATION')\n                          .length &&\n                        row.trigger_task.filter((item: any) => item.type === 'TOOL').length\n                      \"\n                    />\n\n                    <!-- 工具部分 -->\n                    <div v-if=\"row.trigger_task.filter((item: any) => item.type === 'TOOL').length\">\n                      <h5 class=\"color-input-placeholder\">\n                        {{ $t('views.tool.title') }}\n                        {{ row.trigger_task.filter((item: any) => item.type === 'TOOL').length }}\n                      </h5>\n                      <div\n                        v-for=\"item in row.trigger_task.filter((item: any) => item.type === 'TOOL')\"\n                        :key=\"item.id\"\n                        class=\"flex align-center mt-8\"\n                      >\n                        <el-avatar\n                          v-if=\"item?.icon\"\n                          shape=\"square\"\n                          :size=\"20\"\n                          style=\"background: none\"\n                          class=\"mr-8\"\n                        >\n                          <img :src=\"resetUrl(item?.icon)\" alt=\"\" />\n                        </el-avatar>\n                        <ToolIcon v-else :size=\"20\" :type=\"item?.tool_type\" class=\"mr-8\" />\n                        <span class=\"ellipsis-1\" :title=\"item.name\">{{ item.name }}</span>\n                      </div>\n                    </div>\n                  </div>\n                </el-popover>\n              </template>\n            </el-table-column>\n            <el-table-column prop=\"create_user\" :label=\"$t('common.creator')\" width=\"130\">\n            </el-table-column>\n            <el-table-column\n              prop=\"create_time\"\n              :label=\"$t('common.createTime')\"\n              width=\"175\"\n              sortable\n            >\n              <template #default=\"{ row }\">\n                {{ datetimeFormat(row.create_time) }}\n              </template>\n            </el-table-column>\n            <el-table-column align=\"left\" width=\"160\" fixed=\"right\" :label=\"$t('common.operation')\">\n              <template #default=\"{ row }\">\n                <span v-if=\"triggerPermissionMap.edit()\" @click.stop>\n                  <el-switch\n                    :before-change=\"() => changeState(row)\"\n                    :loading=\"loading\"\n                    size=\"small\"\n                    v-model=\"row.is_active\"\n                  />\n                </span>\n                <el-divider direction=\"vertical\" />\n                <el-tooltip effect=\"dark\" :content=\"$t('common.edit')\" placement=\"top\">\n                  <span class=\"mr-4\">\n                    <el-button type=\"primary\" text @click=\"openEditTriggerDrawer(row)\">\n                      <AppIcon iconName=\"app-edit\"></AppIcon>\n                    </el-button>\n                  </span>\n                </el-tooltip>\n                <el-tooltip\n                  v-if=\"triggerPermissionMap.record()\"\n                  effect=\"dark\"\n                  :content=\"$t('common.ExecutionRecord.title')\"\n                  placement=\"top\"\n                >\n                  <span class=\"mr-4\">\n                    <el-button type=\"primary\" text @click=\"openExecutionRecordDrawer(row)\">\n                      <AppIcon iconName=\"app-schedule-report\"></AppIcon>\n                    </el-button>\n                  </span>\n                </el-tooltip>\n\n                <el-tooltip\n                  v-if=\"triggerPermissionMap.delete()\"\n                  effect=\"dark\"\n                  :content=\"$t('common.delete')\"\n                  placement=\"top\"\n                >\n                  <span class=\"mr-4\">\n                    <el-button type=\"primary\" text @click=\"deleteTrigger(row)\">\n                      <AppIcon iconName=\"app-delete\"></AppIcon>\n                    </el-button>\n                  </span>\n                </el-tooltip>\n              </template>\n            </el-table-column>\n          </app-table>\n        </div>\n      </div>\n    </el-card>\n    <TriggerDrawer @refresh=\"getList()\" ref=\"triggerDrawerRef\"></TriggerDrawer>\n    <TriggerTaskRecordDrawer ref=\"triggerTaskRecordDrawerRef\"></TriggerTaskRecordDrawer>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed } from 'vue'\nimport { useRouter, useRoute } from 'vue-router'\nimport type { ElTable } from 'element-plus'\nimport { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'\nimport useStore from '@/stores'\nimport triggerAPI from '@/api/trigger/trigger'\nimport { TriggerType } from '@/enums/trigger'\nimport { t } from '@/locales'\nimport TriggerTaskRecordDrawer from './execution-record/TriggerTaskRecordDrawer.vue'\nimport permissionMap from '@/permission'\nimport { datetimeFormat } from '@/utils/time'\nimport WorkspaceApi from '@/api/workspace/workspace'\nimport { resetUrl } from '@/utils/common'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport type { TriggerData } from '@/api/type/trigger'\nimport TriggerDrawer from '@/views/trigger/TriggerDrawer.vue'\nimport { hasPermission } from '@/utils/permission'\nimport { PermissionConst, RoleConst } from '@/utils/permission/data'\n\nconst { user } = useStore()\n\nconst triggerTaskRecordDrawerRef = ref<InstanceType<typeof TriggerTaskRecordDrawer>>()\n\nconst triggerDrawerRef = ref<InstanceType<typeof TriggerDrawer>>()\nconst openCreateTriggerDrawer = () => {\n  triggerDrawerRef.value?.open()\n}\nconst openEditTriggerDrawer = (trigger: any) => {\n  triggerDrawerRef.value?.open(trigger.id)\n}\n\nconst openExecutionRecordDrawer = (trigger: any) => {\n  triggerTaskRecordDrawerRef.value?.open(trigger.id)\n}\n\nconst loading = ref(false)\nconst paginationConfig = ref({\n  current_page: 1,\n  page_size: 20,\n  total: 0,\n})\n\nconst user_options = ref<any[]>([])\nconst search_type = ref('name')\nconst search_form = ref<any>({\n  name: '',\n  type: '',\n  task: '',\n  is_active: '',\n  create_user: '',\n})\n\nconst search_type_change = () => {\n  search_form.value = {\n    name: '',\n    type: '',\n    task: '',\n    is_active: '',\n    create_user: '',\n  }\n}\n\nfunction searchHandle() {\n  paginationConfig.value.current_page = 1\n  triggerData.value = []\n  getList()\n}\n\nfunction deleteTrigger(row: any) {\n  MsgConfirm(`${t('views.trigger.delete.confirmTitle')} ${row.name} ?`, ``, {\n    confirmButtonText: t('common.confirm'),\n    confirmButtonClass: 'danger',\n  }).then(() => {\n    triggerAPI.deleteTrigger(row.id, loading).then(() => {\n      MsgSuccess(t('common.deleteSuccess'))\n      getList()\n    })\n  })\n}\n\nconst handleSelectionChange = (val: any[]) => {\n  multipleSelection.value = val\n}\n\nfunction batchChangeState(is_active: boolean) {\n  const idList: string[] = []\n  multipleSelection.value.map((v) => {\n    if (v) {\n      idList.push(v.id)\n    }\n  })\n  triggerAPI.activateMulTrigger({ id_list: idList, is_active: is_active }, loading).then(() => {\n    const msg: string = is_active\n      ? t('common.status.enableSuccess')\n      : t('common.status.disableSuccess')\n    MsgSuccess(msg)\n    multipleTableRef.value?.clearSelection()\n    getList()\n  })\n}\n\nfunction batchDelete() {\n  MsgConfirm(\n    `${t('views.document.delete.confirmTitle1')} ${multipleSelection.value.length} ${t('views.trigger.delete.confirmTitle2')}`,\n    '',\n    {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    },\n  ).then(() => {\n    const arr: string[] = []\n    multipleSelection.value.map((v) => {\n      if (v) {\n        arr.push(v.id)\n      }\n    })\n    triggerAPI.delMulTrigger(arr, loading).then(() => {\n      MsgSuccess(t('views.document.delete.successMessage'))\n      multipleTableRef.value?.clearSelection()\n      getList()\n    })\n  })\n}\n\nconst triggerPermissionMap = {\n  edit: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TRIGGER_EDIT.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  create: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TRIGGER_CREATE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  delete: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TRIGGER_DELETE.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n  record: () =>\n    hasPermission(\n      [\n        RoleConst.WORKSPACE_MANAGE.getWorkspaceRole,\n        PermissionConst.TRIGGER_RECORD.getWorkspacePermissionWorkspaceManageRole,\n      ],\n      'OR',\n    ),\n}\n\nasync function changeState(row: any) {\n  const obj = {\n    is_active: !row.is_active,\n  }\n  const str = !row.is_active ? t('common.status.enableSuccess') : t('common.status.disableSuccess')\n  await updateData(row.id, obj, str)\n}\n\n/**\n * 更新状态/数据\n */\nfunction updateData(triggerId: string, data: TriggerData, msg: string) {\n  triggerAPI\n    .putTrigger(triggerId, data, loading)\n    .then((res: any) => {\n      const trigger: TriggerData = triggerData.value.find((v) => v.id === triggerId)\n      if (trigger) {\n        trigger.is_active = res.data.is_active\n      }\n      MsgSuccess(msg)\n      return true\n    })\n    .catch(() => {\n      return false\n    })\n}\n\nconst multipleSelection = ref<any[]>([])\nconst multipleTableRef = ref<InstanceType<typeof ElTable>>()\n\nconst triggerData = ref<any[]>([])\n\nfunction handleSizeChange() {\n  paginationConfig.value.current_page = 1\n  getList()\n}\n\nfunction getList(bool?: boolean) {\n  const param: any = {}\n  if (search_form.value[search_type.value]) {\n    param[search_type.value] = search_form.value[search_type.value]\n  }\n  triggerAPI\n    .pageTrigger(paginationConfig.value, param, bool ? undefined : loading)\n    .then((res: any) => {\n      triggerData.value = res.data.records\n      paginationConfig.value.total = res.data.total\n    })\n}\n\nonMounted(() => {\n  getList()\n  WorkspaceApi.getAllMemberList(user.getWorkspaceId(), loading).then((res) => {\n    user_options.value = res.data\n  })\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/common/AddFormCollect.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"title\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <DynamicsFormConstructor\n      v-model=\"dynamicsFormData\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      ref=\"dynamicsFormConstructorRef\"\n    ></DynamicsFormConstructor>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{$t('common.cancel')}} </el-button>\n        <el-button type=\"primary\" @click=\"submit()\" :loading=\"loading\"> {{$t('common.add')}} </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'\nimport { t } from '@/locales'\nconst props = withDefaults(\n  defineProps<{ title?: string; addFormField: (form_data: any) => void }>(),\n  { title: t('common.param.addParam') }\n)\nconst dialogVisible = ref<boolean>(false)\nconst dynamicsFormConstructorRef = ref<InstanceType<typeof DynamicsFormConstructor>>()\nconst emit = defineEmits(['submit'])\nconst dynamicsFormData = ref<any>({})\nconst loading = ref<boolean>(false)\nconst open = () => {\n  dialogVisible.value = true\n}\nconst close = () => {\n  dialogVisible.value = false\n  dynamicsFormData.value = {}\n}\nconst submit = () => {\n  dynamicsFormConstructorRef.value?.validate().then(() => {\n    props.addFormField(dynamicsFormConstructorRef.value?.getData())\n    close()\n  })\n}\ndefineExpose({ close, open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/common/CustomLine.vue",
    "content": "<template>\n  <div class=\"custom-edge cursor\" @mouseup.stop @click.stop v-show=\"props.model.isHovered\">\n    <svg\n      @click=\"deleteEdge\"\n      width=\"22\"\n      height=\"22\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    >\n      <path\n        d=\"M12 23.0001C5.925 23.0001 1 18.0751 1 12.0001C1 5.92512 5.925 1.00012 12 1.00012C18.075 1.00012 23 5.92512 23 12.0001C23 18.0751 18.075 23.0001 12 23.0001Z\"\n        fill=\"#3370FF\"\n      />\n      <path\n        d=\"M9.02524 7.61124L12.0002 10.5862L14.9752 7.61124C15.069 7.5175 15.1962 7.46484 15.3287 7.46484C15.4613 7.46484 15.5885 7.5175 15.6822 7.61124L16.3892 8.31824C16.483 8.412 16.5356 8.53915 16.5356 8.67174C16.5356 8.80432 16.483 8.93147 16.3892 9.02524L13.4142 12.0002L16.3892 14.9752C16.483 15.069 16.5356 15.1962 16.5356 15.3287C16.5356 15.4613 16.483 15.5885 16.3892 15.6822L15.6822 16.3892C15.5885 16.483 15.4613 16.5356 15.3287 16.5356C15.1962 16.5356 15.069 16.483 14.9752 16.3892L12.0002 13.4142L9.02524 16.3892C8.93147 16.483 8.80432 16.5356 8.67174 16.5356C8.53916 16.5356 8.412 16.483 8.31824 16.3892L7.61124 15.6822C7.5175 15.5885 7.46484 15.4613 7.46484 15.3287C7.46484 15.1962 7.5175 15.069 7.61124 14.9752L10.5862 12.0002L7.61124 9.02524C7.5175 8.93147 7.46484 8.80432 7.46484 8.67174C7.46484 8.53915 7.5175 8.412 7.61124 8.31824L8.31824 7.61124C8.412 7.5175 8.53916 7.46484 8.67174 7.46484C8.80432 7.46484 8.93147 7.5175 9.02524 7.61124Z\"\n        fill=\"white\"\n      />\n    </svg>\n  </div>\n</template>\n<script setup lang=\"ts\">\nconst props = defineProps<{ model: any }>()\nconst deleteEdge = () => {\n  props.model.graphModel.deleteEdgeById(props.model.id)\n}\n</script>\n\n<style lang=\"scss\">\n.custom-edge {\n  color: var(--el-color-primary);\n  stroke: none;\n  z-index: 100000;\n  position: absolute;\n  pointer-events: all;\n}\n</style>\n"
  },
  {
    "path": "ui/src/workflow/common/EditFormCollect.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"title\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <DynamicsFormConstructor\n      v-model=\"dynamicsFormData\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      ref=\"dynamicsFormConstructorRef\"\n    ></DynamicsFormConstructor>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit()\" :loading=\"loading\">\n          {{ $t('common.modify') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'\nimport { t } from '@/locales'\nconst props = withDefaults(\n  defineProps<{ title?: string; editFormField: (form_data: any, index: number) => void }>(),\n  { title: t('common.param.editParam') }\n)\nconst dialogVisible = ref<boolean>(false)\nconst dynamicsFormConstructorRef = ref<InstanceType<typeof DynamicsFormConstructor>>()\nconst emit = defineEmits(['submit'])\nconst dynamicsFormData = ref<any>({})\nconst currentIndex = ref<number>(0)\nconst loading = ref<boolean>(false)\nconst open = (form_data: any, index: number) => {\n  dialogVisible.value = true\n  dynamicsFormData.value = form_data\n  currentIndex.value = index\n}\nconst close = () => {\n  dialogVisible.value = false\n  dynamicsFormData.value = {}\n}\nconst submit = () => {\n  dynamicsFormConstructorRef.value?.validate().then(() => {\n    props.editFormField(dynamicsFormConstructorRef.value?.getData(), currentIndex.value)\n    close()\n  })\n}\ndefineExpose({ close, open })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/common/NodeCascader.vue",
    "content": "<template>\n  <el-cascader\n    @wheel=\"wheel\"\n    :teleported=\"false\"\n    :options=\"options\"\n    @visible-change=\"visibleChange\"\n    v-bind=\"$attrs\"\n    v-model=\"data\"\n    separator=\" > \"\n    clearable\n  >\n    <template #default=\"{ node, data }\">\n      <span class=\"flex align-center\" @wheel=\"wheel\">\n        <component\n          :is=\"iconComponent(`${data.type}-icon`)\"\n          class=\"mr-8\"\n          :size=\"18\"\n          :item=\"data\"\n        />{{ data.label }}</span\n      >\n    </template>\n  </el-cascader>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed, inject } from 'vue'\nimport { iconComponent } from '../icons/utils'\nimport { t } from '@/locales'\nimport { WorkflowMode } from '@/enums/application'\nconst props = defineProps<{\n  nodeModel: any\n  modelValue: Array<any>\n  global?: boolean\n}>()\nconst emit = defineEmits(['update:modelValue'])\nconst workflowMode = inject('workflowMode') as WorkflowMode\nconst data = computed({\n  set: (value) => {\n    emit('update:modelValue', value)\n  },\n  get: () => {\n    return props.modelValue\n  },\n})\nconst options = ref<Array<any>>([])\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nfunction visibleChange(bool: boolean) {\n  if (bool) {\n    initOptions()\n  }\n}\n\nconst validate = () => {\n  const incomingNodeValue = getOptionsValue()\n  if (!data.value || data.value.length === 0) {\n    return Promise.reject(t('workflow.variable.ReferencingRequired'))\n  }\n  if (data.value.length < 2) {\n    return Promise.reject(t('workflow.variable.ReferencingError'))\n  }\n  const node_id = data.value[0]\n  const node_field = data.value[1]\n  const nodeParent = incomingNodeValue.find((item: any) => item.value === node_id)\n  if (!nodeParent) {\n    data.value = []\n    return Promise.reject(t('workflow.variable.NoReferencing'))\n  }\n  if (!nodeParent.children.some((item: any) => item.value === node_field)) {\n    data.value = []\n    return Promise.reject(t('workflow.variable.NoReferencing'))\n  }\n  return Promise.resolve('')\n}\n\nconst get_up_node_field_list = (contain_self: boolean, use_cache: boolean) => {\n  const result = props.nodeModel.get_up_node_field_list(contain_self, use_cache)\n  if (props.nodeModel.graphModel.get_up_node_field_list) {\n    const _u = props.nodeModel.graphModel.get_up_node_field_list(contain_self, use_cache)\n\n    _u.forEach((item: any) => {\n      result.push(item)\n    })\n  }\n  return result.filter((v: any) => v.children && v.children.length > 0)\n}\nconst getOptionsValue = () => {\n  if ([WorkflowMode.ApplicationLoop, WorkflowMode.KnowledgeLoop].includes(workflowMode)) {\n    return props.global\n      ? get_up_node_field_list(false, true).filter(\n          (v: any) =>\n            ['global', 'chat', 'output', 'loop'].includes(v.value) &&\n            v.children &&\n            v.children.length > 0,\n        )\n      : get_up_node_field_list(false, true).filter((v: any) => v.children && v.children.length > 0)\n  } else {\n    const result = props.global\n      ? props.nodeModel\n          .get_up_node_field_list(false, true)\n          .filter(\n            (v: any) =>\n              ['global', 'chat', 'output'].includes(v.value) && v.children && v.children.length > 0,\n          )\n      : props.nodeModel\n          .get_up_node_field_list(false, true)\n          .filter((v: any) => v.children && v.children.length > 0)\n    return result\n  }\n}\nconst initOptions = () => {\n  options.value = getOptionsValue()\n}\ndefineExpose({ validate })\nonMounted(() => {\n  initOptions()\n})\n</script>\n<style scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/common/NodeContainer.vue",
    "content": "<template>\n  <div @mousedown=\"mousedown\" class=\"workflow-node-container p-16\" style=\"overflow: visible\">\n    <div\n      class=\"step-container white-bg border-r-8 p-16\"\n      :class=\"{ isSelected: props.nodeModel.isSelected, error: node_status !== 200 }\"\n      style=\"overflow: visible\"\n    >\n      <div v-resize=\"resizeStepContainer\">\n        <div class=\"flex-between\">\n          <div\n            class=\"flex align-center\"\n            @dragstart.prevent\n            @drag.prevent\n            @dragover.prevent\n            @dragend.prevent\n            style=\"width: 69%\"\n          >\n            <component\n              :is=\"iconComponent(`${nodeModel.type}-icon`)\"\n              class=\"mr-8\"\n              :size=\"24\"\n              :item=\"nodeModel?.properties.node_data\"\n            />\n            <h4\n              class=\"ellipsis-1 break-all\"\n              v-html=\"highlightedStepName(nodeModel.properties.stepName)\"\n            ></h4>\n          </div>\n\n          <div @mousemove.stop @mousedown.stop @keydown.stop @click.stop>\n            <el-button text @click=\"showNode = !showNode\">\n              <el-icon class=\"arrow-icon color-secondary\" :class=\"showNode ? 'rotate-180' : ''\"\n                ><ArrowDownBold />\n              </el-icon>\n            </el-button>\n            <el-dropdown\n              v-if=\"showConditionOperate(nodeModel.type)\"\n              :teleported=\"false\"\n              trigger=\"click\"\n              placement=\"bottom-start\"\n            >\n              <el-button text>\n                <img src=\"@/assets/workflow/icon_or.svg\" alt=\"\" v-if=\"condition === 'OR'\" />\n                <img src=\"@/assets/workflow/icon_and.svg\" alt=\"\" v-if=\"condition === 'AND'\" />\n              </el-button>\n              <template #dropdown>\n                <div style=\"width: 280px\" class=\"p-12-16\">\n                  <h5>{{ $t('workflow.condition.title') }}</h5>\n                  <p class=\"mt-8 lighter\">\n                    <span>{{ $t('workflow.condition.front') }}</span>\n                    <el-select v-model=\"condition\" size=\"small\" style=\"width: 60px; margin: 0 8px\">\n                      <el-option :label=\"$t('workflow.condition.AND')\" value=\"AND\" />\n                      <el-option :label=\"$t('workflow.condition.OR')\" value=\"OR\" />\n                    </el-select>\n                    <span>{{ $t('workflow.condition.text') }}</span>\n                  </p>\n                </div>\n              </template>\n            </el-dropdown>\n            <el-dropdown v-if=\"showOperate(nodeModel.type)\" :teleported=\"false\" trigger=\"click\">\n              <el-button text>\n                <AppIcon iconName=\"app-more\" class=\"color-secondary\"></AppIcon>\n              </el-button>\n              <template #dropdown>\n                <el-dropdown-menu style=\"min-width: 80px\">\n                  <el-dropdown-item @click=\"renameNode\" class=\"p-8\">{{\n                    $t('common.rename')\n                  }}</el-dropdown-item>\n                  <el-dropdown-item @click=\"copyNode\" class=\"p-8\">{{\n                    $t('common.copy')\n                  }}</el-dropdown-item>\n                  <el-dropdown-item @click=\"deleteNode\" class=\"border-t p-8\">{{\n                    $t('common.delete')\n                  }}</el-dropdown-item>\n                </el-dropdown-menu>\n              </template>\n            </el-dropdown>\n          </div>\n        </div>\n        <el-collapse-transition>\n          <div @mousedown.stop @keydown.stop @click.stop v-show=\"showNode\" class=\"mt-16\">\n            <el-alert\n              v-if=\"node_status != 200\"\n              class=\"mb-16\"\n              :title=\"\n                props.nodeModel.type === 'application-node'\n                  ? $t('workflow.tip.applicationNodeError')\n                  : $t('workflow.tip.toolNodeError')\n              \"\n              type=\"error\"\n              show-icon\n              :closable=\"false\"\n            />\n            <slot></slot>\n            <template v-if=\"nodeFields.length > 0\">\n              <div class=\"flex-between\">\n                <h5 class=\"title-decoration-1 mb-8 mt-8\">\n                  {{ output_title }}\n                </h5>\n                <div v-if=\"exceptionNodeList.includes(nodeModel.type)\" class=\"text-right\">\n                  <span class=\"mt-8 mr-8 lighter\">{{ $t('common.param.exception') }}</span>\n                  <el-switch v-model=\"enable_exception\" size=\"small\" />\n                </div>\n              </div>\n              <div class=\"border-r-6 p-4-12 layout-bg lighter\">\n                <template v-for=\"(item, index) in nodeFields\" :key=\"index\">\n                  <div\n                    class=\"flex-between mb-8 mt-8\"\n                    @mouseenter=\"showicon = index\"\n                    @mouseleave=\"showicon = null\"\n                  >\n                    <span class=\"break-all\">{{ item.label }} {{ '{' + item.value + '}' }}</span>\n                    <el-tooltip\n                      effect=\"dark\"\n                      :content=\"$t('workflow.setting.copyParam')\"\n                      placement=\"top\"\n                      v-if=\"showicon === index\"\n                    >\n                      <el-button link @click=\"copyClick(item.globeLabel)\" style=\"padding: 0\">\n                        <AppIcon iconName=\"app-copy\"></AppIcon>\n                      </el-button>\n                    </el-tooltip>\n                  </div>\n                </template>\n              </div>\n\n              <div class=\"border-r-6 p-4-12 layout-bg lighter mt-8\" v-if=\"enable_exception\">\n                <template v-for=\"(item, index) in abnormalNodeFields\" :key=\"index\">\n                  <div\n                    class=\"flex-between mb-8 mt-8\"\n                    @mouseenter=\"showicon = 'abnormal' + index\"\n                    @mouseleave=\"showicon = null\"\n                  >\n                    <span class=\"break-all\">{{ item.label }} {{ '{' + item.value + '}' }}</span>\n                    <el-tooltip\n                      effect=\"dark\"\n                      :content=\"$t('workflow.setting.copyParam')\"\n                      placement=\"top\"\n                      v-if=\"showicon === 'abnormal' + index\"\n                    >\n                      <el-button link @click=\"copyClick(item.globeLabel)\" style=\"padding: 0\">\n                        <AppIcon iconName=\"app-copy\"></AppIcon>\n                      </el-button>\n                    </el-tooltip>\n                  </div>\n                </template>\n              </div>\n            </template>\n          </div>\n        </el-collapse-transition>\n      </div>\n    </div>\n\n    <el-collapse-transition>\n      <DropdownMenu\n        v-if=\"showAnchor\"\n        @mousemove.stop\n        @mousedown.stop\n        @click.stop\n        @wheel=\"handleWheel\"\n        :show=\"showAnchor\"\n        :inner=\"true\"\n        :id=\"id\"\n        style=\"left: 100%; transform: translate(0, -50%)\"\n        :style=\"dropdownMenuStyle\"\n        @clickNodes=\"clickNodes\"\n      />\n    </el-collapse-transition>\n\n    <el-dialog\n      :title=\"$t('workflow.nodeName')\"\n      v-model=\"nodeNameDialogVisible\"\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n      :destroy-on-close=\"true\"\n      append-to-body\n      @submit.prevent\n    >\n      <el-form label-position=\"top\" ref=\"titleFormRef\" :model=\"form\">\n        <el-form-item\n          prop=\"title\"\n          :rules=\"[\n            {\n              required: true,\n              message: $t('common.inputPlaceholder'),\n              trigger: 'blur',\n            },\n          ]\"\n        >\n          <el-input v-model=\"form.title\" @blur=\"form.title = form.title.trim()\" />\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <span class=\"dialog-footer\">\n          <el-button @click.prevent=\"nodeNameDialogVisible = false\">\n            {{ $t('common.cancel') }}\n          </el-button>\n          <el-button type=\"primary\" @click=\"editName(titleFormRef)\">\n            {{ $t('common.save') }}\n          </el-button>\n        </span>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, onMounted, watch, nextTick } from 'vue'\nimport { set } from 'lodash'\nimport { iconComponent } from '../icons/utils'\nimport { copyClick } from '@/utils/clipboard'\nimport { WorkflowType, WorkflowKind } from '@/enums/application'\nimport { MsgError, MsgConfirm } from '@/utils/message'\nimport type { FormInstance } from 'element-plus'\nimport { t } from '@/locales'\nimport { useRoute } from 'vue-router'\nimport DropdownMenu from '@/components/workflow-dropdown-menu/index.vue'\n\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\n\nconst height = ref<{\n  stepContainerHeight: number\n  inputContainerHeight: number\n  outputContainerHeight: number\n}>({\n  stepContainerHeight: 0,\n  inputContainerHeight: 0,\n  outputContainerHeight: 0,\n})\nconst showAnchor = ref<boolean>(false)\nconst anchorData = ref<any>()\nconst dropdownMenuStyle = computed(() => {\n  return {\n    top: anchorData.value\n      ? anchorData.value.y - props.nodeModel.y + props.nodeModel.height / 2 + 'px'\n      : '0px',\n  }\n})\nconst titleFormRef = ref()\nconst nodeNameDialogVisible = ref<boolean>(false)\nconst form = ref<any>({\n  title: '',\n})\n\nconst condition = computed({\n  set: (v) => {\n    set(props.nodeModel.properties, 'condition', v)\n  },\n  get: () => {\n    if (props.nodeModel.properties.condition) {\n      return props.nodeModel.properties.condition\n    }\n    set(props.nodeModel.properties, 'condition', 'AND')\n    return true\n  },\n})\nconst showNode = computed({\n  set: (v) => {\n    set(props.nodeModel.properties, 'showNode', v)\n  },\n  get: () => {\n    if (props.nodeModel.properties.showNode !== undefined) {\n      return props.nodeModel.properties.showNode\n    }\n    set(props.nodeModel.properties, 'showNode', true)\n    return true\n  },\n})\n\nconst handleWheel = (event: any) => {\n  const isCombinationKeyPressed = event.ctrlKey || event.metaKey\n  if (!isCombinationKeyPressed) {\n    event.stopPropagation()\n  }\n}\nconst node_status = computed(() => {\n  if (props.nodeModel.properties.status) {\n    return props.nodeModel.properties.status\n  }\n  return 200\n})\n\nfunction renameNode() {\n  form.value.title = props.nodeModel.properties.stepName\n  nodeNameDialogVisible.value = true\n}\nconst editName = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      if (\n        !props.nodeModel.graphModel.nodes\n          .filter((node: any) => node.id !== props.nodeModel.id)\n          ?.some((node: any) => node.properties.stepName === form.value.title)\n      ) {\n        set(props.nodeModel.properties, 'stepName', form.value.title)\n        props.nodeModel.clear_next_node_field(true)\n        nodeNameDialogVisible.value = false\n        formEl.resetFields()\n      } else {\n        MsgError(t('workflow.tip.repeatedNodeError'))\n      }\n    }\n  })\n}\n\nconst mousedown = (event?: any) => {\n  if (!event?.shiftKey) {\n    props.nodeModel.graphModel.clearSelectElements()\n  }\n  set(props.nodeModel, 'isSelected', !props.nodeModel.isSelected)\n  set(props.nodeModel, 'isHovered', !props.nodeModel.isSelected)\n  props.nodeModel.graphModel.toFront(props.nodeModel.id)\n}\nconst showicon = ref<number | string | null>(null)\nconst copyNode = () => {\n  props.nodeModel.graphModel.clearSelectElements()\n  const cloneNode = props.nodeModel.graphModel.cloneNode(props.nodeModel.id)\n  set(cloneNode, 'isSelected', true)\n  set(cloneNode, 'isHovered', true)\n  props.nodeModel.graphModel.toFront(cloneNode.id)\n}\nconst deleteNode = () => {\n  MsgConfirm(t('common.tip'), t('workflow.delete.confirmTitle'), {\n    confirmButtonText: t('common.confirm'),\n    confirmButtonClass: 'danger',\n  }).then(() => {\n    if (props.nodeModel.type === WorkflowType.LoopNode) {\n      const next = props.nodeModel.graphModel.getNodeOutgoingNode(props.nodeModel.id)\n      next.forEach((n: any) => {\n        if (n.type === 'loop-body-node') {\n          props.nodeModel.graphModel.deleteNode(n.id)\n        }\n      })\n    }\n    props.nodeModel.graphModel.deleteNode(props.nodeModel.id)\n  })\n  props.nodeModel.graphModel.eventCenter.emit('delete_node')\n}\nconst resizeStepContainer = (wh: any) => {\n  if (wh.height) {\n    if (!props.nodeModel.virtual) {\n      height.value.stepContainerHeight = wh.height\n      props.nodeModel.setHeight(height.value.stepContainerHeight)\n    }\n  }\n}\n\nfunction clickNodes(item: any) {\n  const width = item.properties.width ? item.properties.width : 214\n  const nodeModel = props.nodeModel.graphModel.addNode({\n    type: item.type,\n    properties: item.properties,\n    x: anchorData.value?.x + width / 2 + 200,\n    y: anchorData.value?.y - item.height,\n  })\n  props.nodeModel.graphModel.addEdge({\n    type: 'app-edge',\n    sourceNodeId: props.nodeModel.id,\n    sourceAnchorId: anchorData.value?.id,\n    targetNodeId: nodeModel.id,\n    targetAnchorId: nodeModel.id + '_left',\n  })\n\n  closeNodeMenu()\n}\nconst enable_exception = computed({\n  set: (v) => {\n    set(props.nodeModel.properties, 'enableException', v)\n  },\n  get: () => {\n    if (props.nodeModel.properties.enableException !== undefined) {\n      return props.nodeModel.properties.enableException\n    }\n    set(props.nodeModel.properties, 'enableException', false)\n    return false\n  },\n})\nconst props = withDefaults(\n  defineProps<{\n    nodeModel: any\n    exceptionNodeList?: string[]\n  }>(),\n  {\n    exceptionNodeList: () => [\n      'ai-chat-node',\n      'video-understand-node',\n      'image-generate-node',\n      'image-understand-node',\n    ],\n  },\n)\n\nconst nodeFields = computed(() => {\n  if (props.nodeModel.properties.config.fields) {\n    const fields = props.nodeModel.properties.config.fields?.map((field: any) => {\n      return {\n        label: field.label,\n        value: field.value,\n        globeLabel: `{{${props.nodeModel.properties.stepName}.${field.value}}}`,\n        globeValue: `{{context['${props.nodeModel.id}'].${field.value}}}`,\n      }\n    })\n    return fields\n  }\n  return []\n})\n\nconst output_title = computed(() => {\n  return props.nodeModel.properties.config.output_title ?? t('common.param.outputParam')\n})\n\nconst abnormalNodeFields = computed(() => {\n  return [\n    {\n      label: t('workflow.abnormalInformation'),\n      value: 'exception_message',\n      globeLabel: `{{${props.nodeModel.properties.stepName}.exception_message}}`,\n      globeValue: `{{context['${props.nodeModel.id}'].exception_message}}`,\n    },\n  ]\n})\nwatch(enable_exception, () => {\n  props.nodeModel.graphModel.eventCenter.emit(\n    'delete_edge',\n    props.nodeModel.outgoing.edges\n      .filter((item: any) =>\n        [`${props.nodeModel.id}_exception_right`].includes(item.sourceAnchorId),\n      )\n      .map((item: any) => item.id),\n  )\n})\n\nfunction showOperate(type: string) {\n  return ![\n    WorkflowType.Start,\n    WorkflowType.Base,\n    WorkflowType.KnowledgeBase,\n    WorkflowType.LoopStartNode.toString(),\n  ].includes(type)\n}\n\nfunction showConditionOperate(type: string) {\n  return (\n    ![\n      WorkflowType.Start,\n      WorkflowType.Base,\n      WorkflowType.ToolBaseNode,\n      WorkflowType.ToolStartNode,\n      WorkflowType.KnowledgeBase,\n      WorkflowType.LoopStartNode.toString(),\n      WorkflowType.DataSourceLocalNode,\n      WorkflowType.DataSourceWebNode,\n    ].includes(type) && props.nodeModel.properties.kind != WorkflowKind.DataSource\n  )\n}\nconst openNodeMenu = (anchorValue: any) => {\n  showAnchor.value = true\n  anchorData.value = anchorValue\n}\nconst closeNodeMenu = () => {\n  showAnchor.value = false\n  anchorData.value = undefined\n}\n/**\n * 检索选中时候触发\n * @param kw\n */\n\nconst keyWord = ref('')\nconst currentKeyWord = ref(false)\nconst selectOn = (kw: string) => {\n  keyWord.value = kw\n  props.nodeModel.isSelected = false\n  currentKeyWord.value = false\n}\n/**\n * 定位时触发\n * @param kw\n */\nconst focusOn = (kw: string) => {\n  props.nodeModel.setSelected(true)\n  currentKeyWord.value = true\n}\n/**\n * 清除时触发\n */\nconst clearSelectOn = () => {\n  keyWord.value = ''\n  currentKeyWord.value = false\n}\n\n// 高亮选中关键字\n\nconst highlightedStepName = (contentText: string) => {\n  let res = contentText\n  if (keyWord.value === '') {\n    return res\n  } else {\n    const wordsArray = contentText.split('')\n    for (let i = 0; i < wordsArray.length; i++) {\n      if (keyWord.value.includes(wordsArray[i])) {\n        wordsArray[i] = currentKeyWord.value\n          ? `<span style='background: #FF8800;'>${wordsArray[i]}</span>`\n          : `<span style='background: #FFC60A;'>${wordsArray[i]}</span>`\n      }\n    }\n    res = wordsArray.join('')\n    return res\n  }\n}\nonMounted(() => {\n  set(props.nodeModel, 'openNodeMenu', (anchorData: any) => {\n    showAnchor.value ? closeNodeMenu() : openNodeMenu(anchorData)\n  })\n  set(props.nodeModel, 'selectOn', selectOn)\n  set(props.nodeModel, 'focusOn', focusOn)\n  set(props.nodeModel, 'clearSelectOn', clearSelectOn)\n})\n</script>\n<style lang=\"scss\" scoped>\n.workflow-node-container {\n  .step-container {\n    border: 2px solid #ffffff !important;\n    box-sizing: border-box;\n    box-shadow: 0px 2px 4px 0px rgba(var(--el-text-color-primary-rgb), 0.12);\n    &:hover {\n      box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n    }\n    &.isSelected {\n      border: 2px solid var(--el-color-primary) !important;\n    }\n    &.error {\n      border: 1px solid #f54a45 !important;\n    }\n  }\n}\n:deep(.el-card) {\n  overflow: visible;\n}\n.app-card {\n  background: #fff;\n  border-radius: 8px;\n  box-shadow: 0px 2px 4px 0px rgba(31, 35, 41, 0.12);\n}\n</style>\n"
  },
  {
    "path": "ui/src/workflow/common/NodeControl.vue",
    "content": "<template>\n  <el-card shadow=\"always\" style=\"--el-card-padding: 8px 12px; --el-card-border-radius: 8px\">\n    <el-button\n      @click=\"changeCursor(true)\"\n      style=\"border: none; padding: 4px; height: 24px\"\n      :class=\"{ 'is-drag-active': isDrag }\"\n    >\n      <el-icon :size=\"16\"><Position /></el-icon>\n    </el-button>\n    <el-button\n      @click=\"changeCursor(false)\"\n      style=\"border: none; padding: 4px; height: 24px; margin-left: 8px\"\n      :class=\"{ 'is-drag-active': !isDrag }\"\n    >\n      <AppIcon iconName=\"app-raisehand\" :size=\"16\"></AppIcon>\n    </el-button>\n    <el-divider direction=\"vertical\" />\n    <el-button link @click=\"zoomOut\" style=\"border: none\">\n      <el-tooltip\n        effect=\"dark\"\n        :content=\"$t('workflow.control.zoomOut')\"\n        placement=\"top\"\n      >\n        <el-icon :size=\"16\" :title=\"$t('workflow.control.zoomOut')\"\n          ><ZoomOut\n        /></el-icon>\n      </el-tooltip>\n    </el-button>\n    <el-button link @click=\"zoomIn\" style=\"border: none\">\n      <el-tooltip\n        effect=\"dark\"\n        :content=\"$t('workflow.control.zoomIn')\"\n        placement=\"top\"\n      >\n        <el-icon :size=\"16\" :title=\"$t('workflow.control.zoomIn')\"\n          ><ZoomIn\n        /></el-icon>\n      </el-tooltip>\n    </el-button>\n    <el-button link @click=\"fitView\" style=\"border: none\">\n      <el-tooltip\n        effect=\"dark\"\n        :content=\"$t('workflow.control.fitView')\"\n        placement=\"top\"\n      >\n        <AppIcon\n          iconName=\"app-fitview\"\n          :title=\"$t('workflow.control.fitView')\"\n        ></AppIcon>\n      </el-tooltip>\n    </el-button>\n    <el-divider direction=\"vertical\" />\n    <el-button link @click=\"retract\" style=\"border: none\">\n      <el-tooltip\n        effect=\"dark\"\n        :content=\"$t('workflow.control.retract')\"\n        placement=\"top\"\n      >\n        <AppIcon\n          style=\"font-size: 16px\"\n          iconName=\"app-retract\"\n          :title=\"$t('workflow.control.retract')\"\n        ></AppIcon>\n      </el-tooltip>\n    </el-button>\n    <el-button link @click=\"extend\" style=\"border: none\">\n      <el-tooltip\n        effect=\"dark\"\n        :content=\"$t('workflow.control.extend')\"\n        placement=\"top\"\n      >\n        <AppIcon\n          style=\"font-size: 16px\"\n          iconName=\"app-extend\"\n          :title=\"$t('workflow.control.extend')\"\n        ></AppIcon>\n      </el-tooltip>\n    </el-button>\n    <el-button link @click=\"layout\" style=\"border: none\">\n      <el-tooltip\n        effect=\"dark\"\n        :content=\"$t('workflow.control.beautify')\"\n        placement=\"top\"\n      >\n        <AppIcon\n          style=\"font-size: 16px\"\n          iconName=\"app-beautify\"\n          :title=\"$t('workflow.control.beautify')\"\n        ></AppIcon>\n      </el-tooltip>\n    </el-button>\n  </el-card>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nconst props = defineProps({\n  lf: Object || String || null,\n})\n\nconst isDrag = ref(false)\n\nfunction zoomIn() {\n  props.lf?.zoom(true, [0, 0])\n}\nfunction zoomOut() {\n  props.lf?.zoom(false, [0, 0])\n}\nfunction fitView() {\n  props.lf?.resetZoom()\n  props.lf?.resetTranslate()\n  props.lf?.fitView()\n}\nconst layout = () => {\n  props.lf?.extension.dagre.layout()\n  props.lf?.graphModel.nodes.forEach((node: any) => {\n    if (node.type === 'loop-body-node') {\n      node?.loopLayout?.()\n    }\n  })\n}\nconst retract = () => {\n  props.lf?.graphModel.nodes.forEach((element: any) => {\n    element.properties.showNode = false\n  })\n}\nconst extend = () => {\n  props.lf?.graphModel.nodes.forEach((element: any) => {\n    element.properties.showNode = true\n  })\n}\nconst changeCursor = (bool: boolean) => {\n  const element: HTMLElement = document.querySelector('.lf-drag-able') as HTMLElement\n  isDrag.value = bool\n  if (bool) {\n    element.style.cursor = 'default'\n    props.lf?.openSelectionSelect()\n    props.lf?.extension.selectionSelect.setSelectionSense(true, false)\n  } else {\n    element.style.cursor = 'pointer'\n    props.lf?.closeSelectionSelect()\n  }\n}\n</script>\n<style scoped lang=\"scss\">\n.is-drag-active {\n  background-color: var(--el-color-primary-light-9);\n  color: var(--el-color-primary);\n}\n</style>\n"
  },
  {
    "path": "ui/src/workflow/common/NodeSearch.vue",
    "content": "<template>\n  <div>\n    <!-- 搜索 -->\n    <el-card\n      class=\"workflow-search\"\n      v-if=\"showSearch\"\n      shadow=\"always\"\n      style=\"--el-card-padding: 8px 12px; --el-card-border-radius: 8px\"\n    >\n      <div class=\"workflow-search-container flex-between\">\n        <el-input\n          ref=\"searchInputRef\"\n          v-bind:modelValue=\"searchText\"\n          @update:modelValue=\"handleSearch\"\n          :placeholder=\"$t('workflow.tip.searchPlaceholder')\"\n          clearable\n          @keyup.enter=\"next\"\n          @keyup.esc=\"closeSearch\"\n        >\n        </el-input>\n        <span>\n          <el-space :size=\"4\">\n            <span class=\"lighter\" v-if=\"selectedCount && selectedCount > 0\">\n              {{ currentIndex + 1 }}/{{ selectedCount }}\n            </span>\n            <span\n              class=\"lighter color-secondary\"\n              style=\"width: 42px\"\n              v-else-if=\"searchText.length > 0\"\n            >\n              无结果\n            </span>\n            <el-divider direction=\"vertical\" />\n\n            <el-button text>\n              <el-icon @click=\"up\"><ArrowUp /></el-icon>\n            </el-button>\n            <el-button text>\n              <el-icon @click=\"next\"><ArrowDown /></el-icon>\n            </el-button>\n            <el-button text @click=\"closeSearch()\">\n              <el-icon><Close /></el-icon>\n            </el-button>\n          </el-space>\n        </span>\n      </div>\n    </el-card>\n    <!-- 开启搜索按钮 -->\n    <el-button v-else @click=\"openSearch()\" circle class=\"workflow-search-button\" size=\"large\">\n      <el-icon :size=\"20\"><Search /></el-icon>\n    </el-button>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, onUnmounted, nextTick, computed } from 'vue'\n// Props定义\ninterface Props {\n  lf?: any\n}\nconst props = withDefaults(defineProps<Props>(), {})\n\n// 状态\nconst showSearch = ref(false)\nconst searchText = ref('')\nconst searchInputRef = ref<any>(null)\n\n// 快捷键处理\nconst handleKeyDown = (e: KeyboardEvent) => {\n  // Ctrl+F 或 Cmd+F (Mac)\n  if ((e.ctrlKey || e.metaKey) && e.key === 'f') {\n    e.preventDefault() // 阻止浏览器默认搜索\n    openSearch()\n  }\n\n  // 按ESC关闭\n  if (e.key === 'Escape' && showSearch.value) {\n    closeSearch()\n  }\n}\n\nconst focusOn = (node: any) => {\n  props.lf?.graphModel.transformModel.focusOn(\n    node.x,\n    node.y,\n    props.lf?.container.clientWidth,\n    props.lf?.container.clientHeight,\n  )\n}\nconst selectedNodes = ref<Array<any>>()\nconst currentIndex = ref<number>(0)\nconst selectedCount = computed(() => {\n  return selectedNodes.value?.length\n})\n\nconst getSelectNodes = (kw: string) => {\n  const result: Array<any> = []\n  const graph_data = props.lf?.getGraphData()\n  graph_data.nodes.filter((node: any) => {\n    if (node.properties.stepName.includes(kw)) {\n      if (node.type !== 'loop-body-node') {\n        result.push({\n          ...node,\n          order: 1,\n          focusOn: () => {\n            focusOn(node)\n            props.lf?.graphModel.getNodeModelById(node.id)?.focusOn(searchText.value)\n          },\n          selectOn: () => {\n            props.lf?.graphModel.getNodeModelById(node.id)?.selectOn(searchText.value)\n          },\n          clearSelectOn: () => {\n            props.lf?.graphModel.getNodeModelById(node.id)?.clearSelectOn(searchText.value)\n          },\n        })\n      }\n    }\n    if (node.type == 'loop-body-node') {\n      const nodeModel = props.lf?.graphModel\n      const childNodeModel = nodeModel.getNodeModelById(node.id)\n      childNodeModel.getSelectNodes(searchText.value).map((childNode: any) => {\n        result.push({\n          ...childNode,\n          order: 2,\n          focusOn: () => {\n            focusOn(node)\n            childNodeModel.focusOn({ node: childNode, kw: searchText.value })\n          },\n          selectOn: () => {\n            childNodeModel.selectOn({ node: childNode, kw: searchText.value })\n          },\n          clearSelectOn: () => {\n            childNodeModel.clearSelectOn({ node: childNode, kw: searchText.value })\n          },\n        })\n      })\n    }\n  })\n  result.sort((a, b) => a.order - b.order || a.y - b.y || a.x - b.x)\n  return result\n}\nconst selectNodes = (nodes: Array<any>) => {\n  nodes.forEach((node) => node.selectOn())\n}\nconst next = () => {\n  if (selectedNodes.value && selectedNodes.value.length > 0) {\n    selectedNodes.value[currentIndex.value]?.selectOn()\n    if (selectedNodes.value.length - 1 >= currentIndex.value + 1) {\n      currentIndex.value++\n    } else {\n      currentIndex.value = 0\n    }\n    selectedNodes.value[currentIndex.value]?.focusOn()\n  }\n}\nconst up = () => {\n  if (selectedNodes.value && selectedNodes.value.length > 0) {\n    selectedNodes.value[currentIndex.value]?.selectOn()\n    if (currentIndex.value - 1 < 0) {\n      currentIndex.value = selectedNodes.value.length - 1\n    } else {\n      currentIndex.value--\n    }\n    selectedNodes.value[currentIndex.value]?.focusOn()\n  }\n}\n\nconst onSearch = (kw: string) => {\n  if (selectedNodes.value === undefined) {\n    const selected = getSelectNodes(kw)\n    if (selected && selected.length > 0) {\n      selectedNodes.value = selected\n      selectNodes(selected)\n      selected[currentIndex.value].focusOn()\n    }\n  }\n}\n// 打开搜索\nconst openSearch = () => {\n  showSearch.value = true\n  searchText.value = ''\n\n  nextTick(() => {\n    searchInputRef.value?.focus()\n  })\n}\n\n// 关闭搜索\nconst closeSearch = () => {\n  clearSelect()\n  showSearch.value = false\n  searchText.value = ''\n}\nconst clearSelect = () => {\n  if (selectedNodes.value) {\n    selectedNodes.value.forEach((node) => {\n      node.clearSelectOn()\n    })\n  }\n  selectedNodes.value = undefined\n  currentIndex.value = 0\n  props.lf?.graphModel.clearSelectElements()\n  const graph_data = props.lf?.getGraphData()\n  graph_data.nodes.forEach((node: any) => {\n    if (node.type == 'loop-body-node') {\n      props.lf?.graphModel.getNodeModelById(node.id).clearSelectElements()\n    }\n  })\n}\n// 执行搜索\nconst handleSearch = (kw: string) => {\n  searchText.value = kw\n  clearSelect()\n\n  if (searchText.value.trim()) {\n    onSearch?.(searchText.value)\n  }\n}\nconst reSearch = () => {\n  console.log('ss')\n  handleSearch(searchText.value)\n}\n// 生命周期\nonMounted(() => {\n  window.addEventListener('keydown', handleKeyDown)\n})\n\nonUnmounted(() => {\n  window.removeEventListener('keydown', handleKeyDown)\n})\ndefineExpose({ reSearch })\n</script>\n\n<style scoped>\n.workflow-search-button {\n  position: absolute;\n  top: 72px;\n  left: 24px;\n  z-index: 2;\n}\n.workflow-search {\n  position: absolute;\n  top: 72px;\n  left: 50%;\n  transform: translateX(-50%);\n  z-index: 2;\n}\n.workflow-search-container {\n  width: 360px;\n  :deep(.el-input__wrapper) {\n    box-shadow: none;\n    padding: 0 8px 0 1px !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/workflow/common/app-node.ts",
    "content": "import { WorkflowKind } from './../../enums/application'\nimport Components from '@/components'\nimport ElementPlus from 'element-plus'\nimport * as ElementPlusIcons from '@element-plus/icons-vue'\nimport zhCn from 'element-plus/es/locale/lang/zh-cn'\nimport { HtmlResize } from '@logicflow/extension'\nimport { h as lh } from '@logicflow/core'\nimport { createApp, h } from 'vue'\nimport directives from '@/directives'\nimport i18n from '@/locales'\nimport { WorkflowMode, WorkflowType } from '@/enums/application'\nimport { nodeDict } from '@/workflow/common/data'\nimport { isActive, connect, disconnect } from './teleport'\nimport { t } from '@/locales'\nimport { type Dict } from '@/api/type/common'\n\nclass AppNode extends HtmlResize.view {\n  isMounted\n  r?: any\n  component: any\n  app: any\n  root?: any\n  VueNode: any\n  up_node_field_dict?: Dict<Array<any>>\n  constructor(props: any, VueNode: any) {\n    super(props)\n    this.component = VueNode\n    this.isMounted = false\n    props.model.clear_next_node_field = this.clear_next_node_field.bind(this)\n    props.model.get_up_node_field_dict = this.get_up_node_field_dict.bind(this)\n    props.model.get_node_field_list = this.get_node_field_list.bind(this)\n    props.model.get_up_node_field_list = this.get_up_node_field_list.bind(this)\n\n    if (props.model.properties.noRender) {\n      delete props.model.properties.noRender\n    } else {\n      props.model.properties.stepName = this.getNodeName(\n        props.graphModel.nodes.filter((node: any) => node.id !== props.model.id),\n        props.model.properties.stepName,\n      )\n    }\n\n    props.model.properties.config = this.getConfig(props)\n    if (props.model.properties.height) {\n      props.model.height = props.model.properties.height\n    }\n  }\n  getConfig(props: any) {\n    return nodeDict[props.model.type].properties.config\n  }\n  getNodeName(nodes: Array<any>, baseName: string) {\n    let index = 0\n    let name = baseName\n    while (true) {\n      if (index > 0) {\n        name = baseName + index\n      }\n      if (!nodes.some((node: any) => node.properties.stepName === name.trim())) {\n        return name\n      }\n      index++\n    }\n  }\n  get_node_field_list() {\n    const result = []\n    if (this.props.model.type === 'start-node') {\n      result.push({\n        value: 'global',\n        label: t('workflow.variable.global'),\n        type: 'global',\n        children: this.props.model.properties?.config?.globalFields || [],\n      })\n      result.push({\n        value: 'chat',\n        label: t('workflow.variable.chat'),\n        type: 'chat',\n        children: this.props.model.properties?.config?.chatFields || [],\n      })\n    }\n    if (this.props.model.type === 'knowledge-base-node') {\n      let globalFields = []\n      if (this.props.model.properties.config?.globalFields) {\n        globalFields = this.props.model.properties.config.globalFields.map((item: any) => ({\n          label: typeof item.label == 'string' ? item.label : item.label.label,\n          value: item.value,\n        }))\n      }\n\n      result.push({\n        value: 'global',\n        label: t('workflow.variable.global'),\n        type: 'global',\n        children: globalFields,\n      })\n    }\n    const children = [...(this.props.model.properties?.config?.fields || [])]\n    if (this.props.model.properties.enableException) {\n      children.push({\n        label: t('workflow.abnormalInformation'),\n        value: 'exception_message',\n        globeLabel: `{{${this.props.model.properties.stepName}.exception_message}}`,\n        globeValue: `{{context['${this.props.model.id}'].exception_message}}`,\n      })\n    }\n    const value: any = {\n      value: this.props.model.id,\n      icon: this.props.model.properties.node_data?.icon,\n      label: this.props.model.properties.stepName,\n      type: this.props.model.type,\n      children: children,\n    }\n    if (this.props.model.properties.kind) {\n      value['kind'] = this.props.model.properties.kind\n    }\n    result.push(value)\n    return result\n  }\n  get_up_node_field_dict(contain_self: boolean, use_cache: boolean) {\n    if (!this.up_node_field_dict || !use_cache) {\n      const up_node_list = this.props.graphModel.getNodeIncomingNode(this.props.model.id)\n      this.up_node_field_dict = up_node_list\n        .filter((node) => node.id != 'start-node' && node.id != 'loop-start-node')\n        .map((node) => node.get_up_node_field_dict(true, use_cache))\n        .reduce((pre, next) => ({ ...pre, ...next }), {})\n    }\n    if (contain_self) {\n      return {\n        ...this.up_node_field_dict,\n        [this.props.model.id]: this.get_node_field_list(),\n      }\n    }\n    return this.up_node_field_dict ? this.up_node_field_dict : {}\n  }\n\n  get_up_node_field_list(contain_self: boolean, use_cache: boolean) {\n    const result = Object.values(this.get_up_node_field_dict(contain_self, use_cache)).reduce(\n      (pre, next) => [...pre, ...next],\n      [],\n    )\n    const start_node_field_list =\n      (\n        this.props.graphModel.getNodeModelById('start-node') ||\n        this.props.graphModel.getNodeModelById('loop-start-node')\n      )?.get_node_field_list() || []\n    const kbn = this.props.graphModel.getNodeModelById('knowledge-base-node')\n    if (kbn) {\n      const knowledgeBaseFieldList = kbn.get_node_field_list()\n      return [...knowledgeBaseFieldList, ...start_node_field_list, ...result]\n    }\n\n    return [...start_node_field_list, ...result]\n  }\n\n  clear_next_node_field(contain_self: boolean) {\n    const next_node_list = this.props.graphModel.getNodeOutgoingNode(this.props.model.id)\n    next_node_list.forEach((node) => {\n      node.clear_next_node_field(true)\n    })\n    if (contain_self) {\n      this.up_node_field_dict = undefined\n    }\n  }\n  getAnchorShape(anchorData: any) {\n    const { x, y, type } = anchorData\n    let isConnect = false\n\n    if (type == 'left') {\n      isConnect = this.props.graphModel.edges.some((edge) => edge.targetAnchorId == anchorData.id)\n    } else {\n      isConnect = this.props.graphModel.edges.some((edge) => edge.sourceAnchorId == anchorData.id)\n    }\n\n    return lh(\n      'foreignObject',\n      {\n        ...anchorData,\n        x: x - 10,\n        y: y - 12,\n        width: 30,\n        height: 30,\n      },\n      [\n        lh('div', {\n          style: { zindex: 0 },\n          onClick: () => {\n            if (type == 'right') {\n              this.props.model.openNodeMenu(anchorData)\n            }\n          },\n          dangerouslySetInnerHTML: {\n            __html: isConnect\n              ? `<svg width=\"100%\" height=\"100%\" viewBox=\"0 0 42 42\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n              <g filter=\"url(#filter0_d_5119_232585)\">\n              <path d=\"M20.9998 29.8333C28.0875 29.8333 33.8332 24.0876 33.8332 17C33.8332 9.91231 28.0875 4.16663 20.9998 4.16663C13.9122 4.16663 8.1665 9.91231 8.1665 17C8.1665 24.0876 13.9122 29.8333 20.9998 29.8333Z\" fill=\"white\"/>\n              <path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M20.9998 27.5C26.7988 27.5 31.4998 22.799 31.4998 17C31.4998 11.201 26.7988 6.49996 20.9998 6.49996C15.2008 6.49996 10.4998 11.201 10.4998 17C10.4998 22.799 15.2008 27.5 20.9998 27.5ZM33.8332 17C33.8332 24.0876 28.0875 29.8333 20.9998 29.8333C13.9122 29.8333 8.1665 24.0876 8.1665 17C8.1665 9.91231 13.9122 4.16663 20.9998 4.16663C28.0875 4.16663 33.8332 9.91231 33.8332 17Z\" fill=\"${anchorData.id.endsWith('_exception_right') ? '#FF8800' : '#3370FF'}\"/>\n              </g>\n              <defs>\n              <filter id=\"filter0_d_5119_232585\" x=\"-1\" y=\"-1\" width=\"44\" height=\"44\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n              <feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n              <feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n              <feOffset dy=\"4\"/>\n              <feGaussianBlur stdDeviation=\"4\"/>\n              <feComposite in2=\"hardAlpha\" operator=\"out\"/>\n              <feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.2 0 0 0 0 0.439216 0 0 0 0 1 0 0 0 0.1 0\"/>\n              <feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_5119_232585\"/>\n              <feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect1_dropShadow_5119_232585\" result=\"shape\"/>\n              </filter>\n              </defs>\n              </svg>\n              `\n              : `<svg width=\"100%\" height=\"100%\" viewBox=\"0 0 42 42\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n        <g filter=\"url(#filter0_d_5199_166905)\">\n        <path d=\"M20.9998 29.8333C28.0875 29.8333 33.8332 24.0876 33.8332 17C33.8332 9.91231 28.0875 4.16663 20.9998 4.16663C13.9122 4.16663 8.1665 9.91231 8.1665 17C8.1665 24.0876 13.9122 29.8333 20.9998 29.8333Z\" fill=\"${anchorData.id.endsWith('_exception_right') ? '#FF8800' : '#3370FF'}\"/>\n        <path d=\"M19.8332 11.75C19.8332 11.4278 20.0943 11.1666 20.4165 11.1666H21.5832C21.9053 11.1666 22.1665 11.4278 22.1665 11.75V15.8333H26.2498C26.572 15.8333 26.8332 16.0945 26.8332 16.4166V17.5833C26.8332 17.9055 26.572 18.1666 26.2498 18.1666H22.1665V22.25C22.1665 22.5721 21.9053 22.8333 21.5832 22.8333H20.4165C20.0943 22.8333 19.8332 22.5721 19.8332 22.25V18.1666H15.7498C15.4277 18.1666 15.1665 17.9055 15.1665 17.5833V16.4166C15.1665 16.0945 15.4277 15.8333 15.7498 15.8333H19.8332V11.75Z\" fill=\"white\"/>\n        </g>\n        <defs>\n        <filter id=\"filter0_d_5199_166905\" x=\"-1\" y=\"-1\" width=\"44\" height=\"44\" filterUnits=\"userSpaceOnUse\" color-interpolation-filters=\"sRGB\">\n        <feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/>\n        <feColorMatrix in=\"SourceAlpha\" type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0\" result=\"hardAlpha\"/>\n        <feOffset dy=\"4\"/>\n        <feGaussianBlur stdDeviation=\"4\"/>\n        <feComposite in2=\"hardAlpha\" operator=\"out\"/>\n        <feColorMatrix type=\"matrix\" values=\"0 0 0 0 0.2 0 0 0 0 0.439216 0 0 0 0 1 0 0 0 0.1 0\"/>\n        <feBlend mode=\"normal\" in2=\"BackgroundImageFix\" result=\"effect1_dropShadow_5199_166905\"/>\n        <feBlend mode=\"normal\" in=\"SourceGraphic\" in2=\"effect1_dropShadow_5199_166905\" result=\"shape\"/>\n        </filter>\n        </defs>\n        </svg>`,\n          },\n        }),\n      ],\n    )\n  }\n\n  setHtml(rootEl: HTMLElement) {\n    if (!this.isMounted) {\n      this.isMounted = true\n      const node = document.createElement('div')\n      rootEl.appendChild(node)\n      this.renderVueComponent(node)\n    } else {\n      if (this.r && this.r.component) {\n        this.r.component.props.properties = this.props.model.getProperties()\n      }\n    }\n  }\n  componentWillUnmount() {\n    super.componentWillUnmount()\n    this.unmount()\n  }\n  getComponentContainer() {\n    return this.root\n  }\n  protected targetId() {\n    return `${this.props.graphModel.flowId}:${this.props.model.id}`\n  }\n  protected renderVueComponent(root: any) {\n    this.unmountVueComponent()\n    this.root = root\n    const { model, graphModel } = this.props\n\n    if (root) {\n      if (isActive()) {\n        connect(\n          this.targetId(),\n          this.component,\n          root,\n          model,\n          graphModel,\n          undefined,\n          this.props.graphModel.get_provide,\n        )\n      } else {\n        this.r = h(this.component, {\n          properties: this.props.model.properties,\n          nodeModel: this.props.model,\n        })\n        this.app = createApp({\n          render() {\n            return this.r\n          },\n          provide() {\n            return {\n              getNode: () => model,\n              getGraph: () => graphModel,\n              workflowMode: WorkflowMode.Application,\n            }\n          },\n        })\n\n        this.app.use(ElementPlus, {\n          locale: zhCn,\n        })\n        this.app.use(Components)\n        this.app.use(directives)\n        this.app.use(i18n)\n        for (const [key, component] of Object.entries(ElementPlusIcons)) {\n          this.app.component(key, component)\n        }\n        this.app?.mount(root)\n      }\n    }\n  }\n\n  protected unmountVueComponent() {\n    if (this.app) {\n      this.app.unmount()\n      this.app = null\n    }\n    if (this.root) {\n      this.root.innerHTML = ''\n    }\n    return this.root\n  }\n\n  unmount() {\n    if (isActive()) {\n      disconnect(this.targetId())\n    }\n    this.unmountVueComponent()\n  }\n}\n\nclass AppNodeModel extends HtmlResize.model {\n  refreshDeges() {\n    // 更新节点连接边的path\n    this.incoming.edges.forEach((edge: any) => {\n      // 调用自定义的更新方案\n      edge.updatePathByAnchor()\n    })\n    this.outgoing.edges.forEach((edge: any) => {\n      edge.updatePathByAnchor()\n    })\n  }\n  set_position(position: { x?: number; y?: number }) {\n    const { x, y } = position\n    if (x) {\n      this.x = x\n    }\n    if (y) {\n      this.y = y\n    }\n    this.refreshDeges()\n  }\n  getResizeOutlineStyle() {\n    const style = super.getResizeOutlineStyle()\n    style.stroke = 'none'\n    return style\n  }\n  getControlPointStyle() {\n    const style = super.getControlPointStyle()\n    style.stroke = 'none'\n    style.fill = 'none'\n    return style\n  }\n  getNodeStyle() {\n    return {\n      overflow: 'visible',\n    }\n  }\n  getOutlineStyle() {\n    const style = super.getOutlineStyle()\n    style.stroke = 'none'\n    if (style.hover) {\n      style.hover.stroke = 'none'\n    }\n    return style\n  }\n  // 如果不用修改锚地形状，可以重写颜色相关样式\n  getAnchorStyle(anchorInfo: any) {\n    const style = super.getAnchorStyle(anchorInfo)\n    if (anchorInfo.type === 'left') {\n      style.fill = 'red'\n      style.hover.fill = 'transparent'\n      style.hover.stroke = 'transpanrent'\n      style.className = 'lf-hide-default'\n    } else {\n      style.fill = 'green'\n    }\n    return style\n  }\n\n  setHeight(height: number) {\n    const sourceHeight = this.height\n    const targetHeight = height + 100\n    this.height = targetHeight\n    this.properties['height'] = targetHeight\n    this.move(0, (targetHeight - sourceHeight) / 2)\n    this.outgoing.edges.forEach((edge: any) => {\n      // 调用自定义的更新方案\n      edge.updatePathByAnchor()\n    })\n    this.incoming.edges.forEach((edge: any) => {\n      // 调用自定义的更新方案\n      edge.updatePathByAnchor()\n    })\n  }\n  get_width() {\n    return this.properties?.width || 340\n  }\n\n  setAttributes() {\n    const { t } = i18n.global\n    this.width = this.get_width()\n    const isLoop = (node_id: string, target_node_id: string) => {\n      const up_node_list = this.graphModel.getNodeIncomingNode(node_id)\n      for (const index in up_node_list) {\n        const item = up_node_list[index]\n        if (item.id === target_node_id) {\n          return true\n        } else {\n          const result = isLoop(item.id, target_node_id)\n          if (result) {\n            return true\n          }\n        }\n      }\n      return false\n    }\n    const circleOnlyAsTarget = {\n      message: t('workflow.tip.onlyRight'),\n      validate: (sourceNode: any, targetNode: any, sourceAnchor: any) => {\n        return sourceAnchor.type === 'right'\n      },\n    }\n    this.sourceRules.push({\n      message: t('workflow.tip.notRecyclable'),\n      validate: (sourceNode: any, targetNode: any, sourceAnchor: any, targetAnchor: any) => {\n        if (targetNode.id == sourceNode.id) {\n          return false\n        }\n        const up_edge_list = this.graphModel.getNodeIncomingEdge(targetNode.id)\n        const is_c = up_edge_list.find(\n          (up_edge) =>\n            up_edge.targetAnchorId == targetAnchor.id && up_edge.sourceAnchorId == sourceAnchor.id,\n        )\n        return !is_c && !isLoop(sourceNode.id, targetNode.id)\n      },\n    })\n\n    this.sourceRules.push(circleOnlyAsTarget)\n    this.targetRules.push({\n      message: t('workflow.tip.onlyLeft'),\n      validate: (sourceNode: any, targetNode: any, sourceAnchor: any, targetAnchor: any) => {\n        return targetAnchor.type === 'left'\n      },\n    })\n  }\n  getDefaultAnchor() {\n    const { id, x, y, width } = this\n    const showNode = this.properties.showNode === undefined ? true : this.properties.showNode\n    const anchors: any = []\n    if (\n      ![\n        WorkflowType.Base as string,\n        WorkflowType.KnowledgeBase as string,\n        WorkflowType.ToolBaseNode as string,\n      ].includes(this.type)\n    ) {\n      if (\n        ![\n          WorkflowType.Start,\n          WorkflowType.LoopStartNode.toString(),\n          WorkflowType.ToolStartNode,\n        ].includes(this.type) &&\n        this.properties.kind != WorkflowKind.DataSource\n      ) {\n        anchors.push({\n          x: x - width / 2 + 10,\n          y: showNode ? y : y - 15,\n          id: `${id}_left`,\n          edgeAddable: false,\n          type: 'left',\n        })\n      }\n\n      if (this.properties.enableException) {\n        anchors.push({\n          x: x + width / 2 - 10,\n          y: y + this.height / 2 - 80,\n          id: `${id}_exception_right`,\n          type: 'right',\n        })\n      }\n      anchors.push({\n        x: x + width / 2 - 10,\n        y: showNode ? y : y - 15,\n        id: `${id}_right`,\n        type: 'right',\n      })\n    }\n\n    return anchors\n  }\n}\nexport { AppNodeModel, AppNode }\n"
  },
  {
    "path": "ui/src/workflow/common/data.ts",
    "content": "import { WorkflowKind } from './../../enums/application'\nimport { WorkflowType, WorkflowMode } from '@/enums/application'\nimport { t } from '@/locales'\n\nexport const startNode = {\n  id: WorkflowType.Start,\n  type: WorkflowType.Start,\n  x: 480,\n  y: 3340,\n  properties: {\n    height: 364,\n    stepName: t('workflow.nodes.startNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.startNode.question'),\n          value: 'question',\n        },\n      ],\n      globalFields: [\n        { label: t('workflow.nodes.startNode.currentTime'), value: 'time' },\n        {\n          label: t('views.application.form.historyRecord.label'),\n          value: 'history_context',\n        },\n        {\n          label: t('chat.chatId'),\n          value: 'chat_id',\n        },\n      ],\n    },\n    fields: [{ label: t('workflow.nodes.startNode.question'), value: 'question' }],\n    globalFields: [{ label: t('workflow.nodes.startNode.currentTime'), value: 'time' }],\n    showNode: true,\n  },\n}\nexport const baseNode = {\n  id: WorkflowType.Base,\n  type: WorkflowType.Base,\n  x: 360,\n  y: 2761.3875,\n  text: '',\n  properties: {\n    height: 728.375,\n    stepName: t('common.info'),\n    input_field_list: [],\n    node_data: {\n      name: '',\n      desc: '',\n      prologue: t('views.application.form.defaultPrologue'),\n      tts_type: 'BROWSER',\n    },\n    config: {},\n    showNode: true,\n    user_input_config: { title: t('chat.userInput') },\n    user_input_field_list: [],\n  },\n}\nexport const knowledgeBaseNode = {\n  id: WorkflowType.KnowledgeBase,\n  type: WorkflowType.KnowledgeBase,\n  x: 360,\n  y: 2761.3875,\n  text: '',\n  properties: {\n    height: 728.375,\n    stepName: t('common.info'),\n    input_field_list: [],\n    node_data: {\n      name: '',\n      desc: '',\n      prologue: t('views.application.form.defaultPrologue'),\n      tts_type: 'BROWSER',\n    },\n    config: {},\n    showNode: true,\n    user_input_config: { title: t('chat.userInput') },\n    user_input_field_list: [],\n  },\n}\nexport const toolBaseNode = {\n  id: WorkflowType.ToolBaseNode,\n  type: WorkflowType.ToolBaseNode,\n  x: 360,\n  y: 2761.3875,\n  text: '',\n  properties: {\n    width: 500,\n    height: 728.375,\n    stepName: t('common.info'),\n    input_field_list: [],\n    node_data: {},\n    config: {},\n    showNode: true,\n    user_input_config: { title: t('chat.userInput') },\n    user_input_field_list: [],\n  },\n}\nexport const toolStartNode = {\n  id: WorkflowType.ToolStartNode,\n  type: WorkflowType.ToolStartNode,\n  x: 360,\n  y: 2761.3875,\n  text: '',\n  properties: {\n    height: 728.375,\n    stepName: t('workflow.nodes.startNode.label'),\n    input_field_list: [],\n    node_data: {},\n    config: {},\n    showNode: true,\n    user_input_config: { title: t('chat.userInput') },\n    user_input_field_list: [],\n  },\n}\nexport const dataSourceLocalNode = {\n  type: WorkflowType.DataSourceLocalNode,\n  x: 360,\n  y: 2761.3875,\n  text: t('workflow.nodes.dataSourceLocalNode.text'),\n  label: t('workflow.nodes.dataSourceLocalNode.label'),\n  properties: {\n    kind: WorkflowKind.DataSource,\n    height: 728.375,\n    stepName: t('workflow.nodes.dataSourceLocalNode.label'),\n    input_field_list: [],\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.dataSourceLocalNode.fileList'),\n          value: 'file_list',\n        },\n      ],\n    },\n    showNode: true,\n    user_input_config: {},\n    user_input_field_list: [],\n  },\n}\n\nexport const dataSourceWebNode = {\n  id: WorkflowType.DataSourceWebNode,\n  type: WorkflowType.DataSourceWebNode,\n  x: 360,\n  y: 2761.3875,\n  text: t('workflow.nodes.dataSourceWebNode.text'),\n  label: t('workflow.nodes.dataSourceWebNode.label'),\n  properties: {\n    kind: WorkflowKind.DataSource,\n    height: 180,\n    stepName: t('workflow.nodes.dataSourceWebNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.dataSourceWebNode.field_label'),\n          value: 'document_list',\n        },\n      ],\n    },\n  },\n}\n\nexport const knowledgeWriteNode = {\n  type: WorkflowType.KnowledgeWriteNode,\n  text: t('workflow.nodes.knowledgeWriteNode.text'),\n  label: t('workflow.nodes.knowledgeWriteNode.label'),\n  height: 100,\n  properties: {\n    stepName: t('workflow.nodes.knowledgeWriteNode.label'),\n    config: {\n      fields: [],\n    },\n  },\n}\n\n/**\n * 说明\n * type 与 nodes 文件对应\n */\nexport const baseNodes = [baseNode, startNode]\n/**\n * ai对话节点配置数据\n */\nexport const aiChatNode = {\n  type: WorkflowType.AiChat,\n  text: t('workflow.nodes.aiChatNode.text'),\n  label: t('workflow.nodes.aiChatNode.label'),\n  height: 340,\n  properties: {\n    stepName: t('workflow.nodes.aiChatNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.aiChatNode.answer'),\n          value: 'answer',\n        },\n        {\n          label: t('workflow.nodes.aiChatNode.think'),\n          value: 'reasoning_content',\n        },\n        {\n          label: t('workflow.nodes.aiChatNode.historyMessage'),\n          value: 'history_message',\n        },\n      ],\n    },\n  },\n}\n/**\n * 知识库检索配置数据\n */\nexport const searchKnowledgeNode = {\n  type: WorkflowType.SearchKnowledge,\n  text: t('workflow.nodes.searchKnowledgeNode.text'),\n  label: t('workflow.nodes.searchKnowledgeNode.label'),\n  height: 355,\n  properties: {\n    stepName: t('workflow.nodes.searchKnowledgeNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.searchKnowledgeNode.paragraph_list'),\n          value: 'paragraph_list',\n        },\n        {\n          label: t('workflow.nodes.searchKnowledgeNode.is_hit_handling_method_list'),\n          value: 'is_hit_handling_method_list',\n        },\n        {\n          label: t('workflow.nodes.searchKnowledgeNode.result'),\n          value: 'data',\n        },\n        {\n          label: t('workflow.nodes.searchKnowledgeNode.directly_return'),\n          value: 'directly_return',\n        },\n      ],\n    },\n  },\n}\n\n/**\n * 知识库检索配置数据\n */\nexport const searchDocumentNode = {\n  type: WorkflowType.SearchDocument,\n  text: t('workflow.nodes.searchDocumentNode.text'),\n  label: t('workflow.nodes.searchDocumentNode.label'),\n  height: 355,\n  properties: {\n    width: 600,\n    stepName: t('workflow.nodes.searchDocumentNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.searchDocumentNode.knowledgeList'),\n          value: 'knowledge_list',\n        },\n        {\n          label: t('workflow.nodes.searchDocumentNode.documentList'),\n          value: 'document_list',\n        },\n      ],\n    },\n  },\n}\n\nexport const questionNode = {\n  type: WorkflowType.Question,\n  text: t('workflow.nodes.questionNode.text'),\n  label: t('workflow.nodes.questionNode.label'),\n  height: 345,\n  properties: {\n    stepName: t('workflow.nodes.questionNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.questionNode.result'),\n          value: 'answer',\n        },\n      ],\n    },\n  },\n}\nexport const variableSplittingNode = {\n  type: WorkflowType.VariableSplittingNode,\n  text: t('workflow.nodes.variableSplittingNode.text'),\n  label: t('workflow.nodes.variableSplittingNode.label'),\n  height: 345,\n  properties: {\n    stepName: t('workflow.nodes.variableSplittingNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.result'),\n          value: 'result',\n        },\n      ],\n    },\n  },\n}\n\nexport const parameterExtractionNode = {\n  type: WorkflowType.ParameterExtractionNode,\n  text: t('workflow.nodes.parameterExtractionNode.text'),\n  label: t('workflow.nodes.parameterExtractionNode.label'),\n  height: 345,\n  properties: {\n    width: 430,\n    stepName: t('workflow.nodes.parameterExtractionNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.result'),\n          value: 'result',\n        },\n      ],\n    },\n  },\n}\n\nexport const conditionNode = {\n  type: WorkflowType.Condition,\n  text: t('workflow.nodes.conditionNode.text'),\n  label: t('workflow.nodes.conditionNode.label'),\n  height: 175,\n  properties: {\n    width: 600,\n    stepName: t('workflow.nodes.conditionNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.conditionNode.branch_name'),\n          value: 'branch_name',\n        },\n      ],\n    },\n  },\n}\nexport const replyNode = {\n  type: WorkflowType.Reply,\n  text: t('workflow.nodes.replyNode.text'),\n  label: t('workflow.nodes.replyNode.label'),\n  height: 210,\n  properties: {\n    stepName: t('workflow.nodes.replyNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.content'),\n          value: 'answer',\n        },\n      ],\n    },\n  },\n}\nexport const rerankerNode = {\n  type: WorkflowType.RerankerNode,\n  text: t('workflow.nodes.rerankerNode.text'),\n  label: t('workflow.nodes.rerankerNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.rerankerNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.rerankerNode.result_list'),\n          value: 'result_list',\n        },\n        {\n          label: t('workflow.nodes.rerankerNode.result'),\n          value: 'result',\n        },\n      ],\n    },\n  },\n}\nexport const formNode = {\n  type: WorkflowType.FormNode,\n  text: t('workflow.nodes.formNode.text'),\n  label: t('workflow.nodes.formNode.label'),\n  height: 252,\n  properties: {\n    width: 600,\n    stepName: t('workflow.nodes.formNode.label'),\n    node_data: {\n      is_result: true,\n      form_field_list: [],\n      form_content_format: `${t('workflow.nodes.formNode.form_content_format1')}\n{{form}}\n${t('workflow.nodes.formNode.form_content_format2')}`,\n    },\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.formNode.form_data'),\n          value: 'form_data',\n        },\n      ],\n    },\n  },\n}\nexport const documentExtractNode = {\n  type: WorkflowType.DocumentExtractNode,\n  text: t('workflow.nodes.documentExtractNode.text'),\n  label: t('workflow.nodes.documentExtractNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.documentExtractNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.documentExtractNode.content'),\n          value: 'content',\n        },\n        {\n          label: t('workflow.nodes.dataSourceWebNode.field_label'),\n          value: 'document_list',\n        },\n      ],\n    },\n  },\n}\nexport const documentSplitNode = {\n  type: WorkflowType.DocumentSplitNode,\n  text: t('workflow.nodes.documentSplitNode.text'),\n  label: t('workflow.nodes.documentSplitNode.label'),\n  height: 252,\n  properties: {\n    width: 500,\n    stepName: t('workflow.nodes.documentSplitNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.documentSplitNode.paragraphList'),\n          value: 'paragraph_list',\n        },\n      ],\n    },\n  },\n}\nexport const imageUnderstandNode = {\n  type: WorkflowType.ImageUnderstandNode,\n  text: t('workflow.nodes.imageUnderstandNode.text'),\n  label: t('workflow.nodes.imageUnderstandNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.imageUnderstandNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.imageUnderstandNode.answer'),\n          value: 'answer',\n        },\n      ],\n    },\n  },\n}\n\nexport const videoUnderstandNode = {\n  type: WorkflowType.VideoUnderstandNode,\n  text: t('workflow.nodes.videoUnderstandNode.text'),\n  label: t('workflow.nodes.videoUnderstandNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.videoUnderstandNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.videoUnderstandNode.answer'),\n          value: 'answer',\n        },\n      ],\n    },\n  },\n}\nexport const variableAggregationNode = {\n  type: WorkflowType.VariableAggregationNode,\n  text: t('workflow.nodes.variableAggregationNode.text'),\n  label: t('workflow.nodes.variableAggregationNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.variableAggregationNode.label'),\n    config: {\n      fields: [],\n    },\n  },\n}\n\nexport const variableAssignNode = {\n  type: WorkflowType.VariableAssignNode,\n  text: t('workflow.nodes.variableAssignNode.text'),\n  label: t('workflow.nodes.variableAssignNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.variableAssignNode.label'),\n    config: {},\n  },\n}\n\nexport const mcpNode = {\n  type: WorkflowType.McpNode,\n  text: t('workflow.nodes.mcpNode.text'),\n  label: t('workflow.nodes.mcpNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.mcpNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.result'),\n          value: 'result',\n        },\n      ],\n    },\n  },\n}\n\nexport const imageGenerateNode = {\n  type: WorkflowType.ImageGenerateNode,\n  text: t('workflow.nodes.imageGenerateNode.text'),\n  label: t('workflow.nodes.imageGenerateNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.imageGenerateNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.imageGenerateNode.answer'),\n          value: 'answer',\n        },\n        {\n          label: t('common.fileUpload.image'),\n          value: 'image',\n        },\n      ],\n    },\n  },\n}\n\nexport const speechToTextNode = {\n  type: WorkflowType.SpeechToTextNode,\n  text: t('workflow.nodes.speechToTextNode.text'),\n  label: t('workflow.nodes.speechToTextNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.speechToTextNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.result'),\n          value: 'result',\n        },\n      ],\n    },\n  },\n}\nexport const textToSpeechNode = {\n  type: WorkflowType.TextToSpeechNode,\n  text: t('workflow.nodes.textToSpeechNode.text'),\n  label: t('workflow.nodes.textToSpeechNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.textToSpeechNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.result'),\n          value: 'result',\n        },\n      ],\n    },\n  },\n}\n\n/**\n * 自定义工具配置数据\n */\nexport const toolNode = {\n  type: WorkflowType.ToolLibCustom,\n  text: t('workflow.nodes.toolNode.text'),\n  label: t('workflow.nodes.toolNode.label'),\n  height: 260,\n  properties: {\n    stepName: t('workflow.nodes.toolNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.result'),\n          value: 'result',\n        },\n      ],\n    },\n  },\n}\n\nexport const intentNode = {\n  type: WorkflowType.IntentNode,\n  text: t('workflow.nodes.intentNode.text'),\n  label: t('workflow.nodes.intentNode.label'),\n  height: 260,\n  properties: {\n    stepName: t('workflow.nodes.intentNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.classify'),\n          value: 'category',\n        },\n        {\n          label: t('common.reason'),\n          value: 'reason',\n        },\n      ],\n    },\n  },\n}\n\nexport const loopStartNode = {\n  id: WorkflowType.LoopStartNode,\n  type: WorkflowType.LoopStartNode,\n  x: 480,\n  y: 3340,\n  properties: {\n    height: 364,\n    stepName: t('workflow.nodes.loopStartNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('workflow.nodes.loopStartNode.loopIndex'),\n          value: 'index',\n        },\n        {\n          label: t('workflow.nodes.loopStartNode.loopItem'),\n          value: 'item',\n        },\n      ],\n      globalFields: [],\n    },\n    showNode: true,\n  },\n}\n\nexport const loopNode = {\n  type: WorkflowType.LoopNode,\n  visible: false,\n  text: t('workflow.nodes.loopNode.text'),\n  label: t('workflow.nodes.loopNode.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.loopNode.label'),\n    workflow: {\n      edges: [],\n      nodes: [\n        {\n          x: 480,\n          y: 3340,\n          id: 'loop-start-node',\n          type: 'loop-start-node',\n          properties: {\n            config: {\n              fields: [],\n              globalFields: [],\n            },\n            fields: [],\n            height: 361.333,\n            showNode: true,\n            stepName: '开始',\n            globalFields: [],\n          },\n        },\n      ],\n    },\n    config: {\n      fields: [],\n    },\n  },\n}\n\nexport const imageToVideoNode = {\n  type: WorkflowType.ImageToVideoGenerateNode,\n  text: t('workflow.nodes.imageToVideoGenerate.text'),\n  label: t('workflow.nodes.imageToVideoGenerate.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.imageToVideoGenerate.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.fileUpload.video'),\n          value: 'video',\n        },\n      ],\n    },\n  },\n}\n\nexport const loopBodyNode = {\n  type: WorkflowType.LoopBodyNode,\n  text: t('workflow.nodes.loopBodyNode.text'),\n  label: t('workflow.nodes.loopBodyNode.label'),\n  height: 1080,\n  properties: {\n    width: 1920,\n    stepName: t('workflow.nodes.loopBodyNode.label'),\n    config: {\n      fields: [],\n    },\n  },\n}\nexport const loopContinueNode = {\n  type: WorkflowType.LoopContinueNode,\n  text: t('workflow.nodes.loopContinueNode.text'),\n  label: t('workflow.nodes.loopContinueNode.label'),\n  height: 100,\n  properties: {\n    width: 600,\n    stepName: t('workflow.nodes.loopContinueNode.label'),\n    config: {\n      fields: [],\n    },\n  },\n}\n\nexport const textToVideoNode = {\n  type: WorkflowType.TextToVideoGenerateNode,\n  text: t('workflow.nodes.textToVideoGenerate.text'),\n  label: t('workflow.nodes.textToVideoGenerate.label'),\n  height: 252,\n  properties: {\n    stepName: t('workflow.nodes.textToVideoGenerate.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.fileUpload.video'),\n          value: 'video',\n        },\n      ],\n    },\n  },\n}\n\nexport const loopBreakNode = {\n  type: WorkflowType.LoopBreakNode,\n  text: t('workflow.nodes.loopBreakNode.text'),\n  label: t('workflow.nodes.loopBreakNode.label'),\n  height: 100,\n  properties: {\n    width: 600,\n    stepName: t('workflow.nodes.loopBreakNode.label'),\n    config: {\n      fields: [],\n    },\n  },\n}\n\nexport const knowledgeMenuNodes = [\n  {\n    label: t('views.tool.dataSource.title'),\n    list: [dataSourceLocalNode, dataSourceWebNode],\n  },\n  {\n    label: t('views.knowledge.title'),\n    list: [documentSplitNode, knowledgeWriteNode, documentExtractNode],\n  },\n  {\n    label: t('workflow.nodes.classify.aiCapability'),\n    list: [\n      aiChatNode,\n      intentNode,\n      textToSpeechNode,\n      speechToTextNode,\n      imageGenerateNode,\n      imageUnderstandNode,\n      textToVideoNode,\n      imageToVideoNode,\n      videoUnderstandNode,\n      questionNode,\n    ],\n  },\n\n  {\n    label: t('workflow.nodes.classify.businessLogic'),\n    list: [conditionNode, replyNode, loopNode],\n  },\n  {\n    label: t('workflow.nodes.classify.dataProcessing'),\n    list: [\n      variableAssignNode,\n      variableAggregationNode,\n      variableSplittingNode,\n      parameterExtractionNode,\n    ],\n  },\n  {\n    label: t('workflow.nodes.classify.other'),\n    list: [mcpNode, toolNode],\n  },\n]\n\nexport const menuNodes = [\n  {\n    label: t('workflow.nodes.classify.aiCapability'),\n    list: [\n      aiChatNode,\n      intentNode,\n      textToSpeechNode,\n      speechToTextNode,\n      imageGenerateNode,\n      imageUnderstandNode,\n      textToVideoNode,\n      imageToVideoNode,\n      videoUnderstandNode,\n      questionNode,\n    ],\n  },\n  {\n    label: t('views.knowledge.title'),\n    list: [searchKnowledgeNode, searchDocumentNode, rerankerNode, documentExtractNode],\n  },\n  {\n    label: t('workflow.nodes.classify.businessLogic'),\n    list: [conditionNode, formNode, replyNode, loopNode],\n  },\n  {\n    label: t('workflow.nodes.classify.dataProcessing'),\n    list: [\n      variableAssignNode,\n      variableAggregationNode,\n      variableSplittingNode,\n      parameterExtractionNode,\n    ],\n  },\n  {\n    label: t('workflow.nodes.classify.other'),\n    list: [mcpNode, toolNode],\n  },\n]\nexport const applicationLoopMenuNodes = [\n  {\n    label: t('workflow.nodes.classify.aiCapability'),\n    list: [\n      aiChatNode,\n      intentNode,\n      textToSpeechNode,\n      speechToTextNode,\n      imageGenerateNode,\n      imageUnderstandNode,\n      textToVideoNode,\n      imageToVideoNode,\n      videoUnderstandNode,\n      questionNode,\n    ],\n  },\n  {\n    label: t('views.knowledge.title'),\n    list: [searchKnowledgeNode, searchDocumentNode, rerankerNode, documentExtractNode],\n  },\n  {\n    label: t('workflow.nodes.classify.businessLogic'),\n    list: [conditionNode, formNode, replyNode, loopContinueNode, loopBreakNode],\n  },\n  {\n    label: t('workflow.nodes.classify.dataProcessing'),\n    list: [\n      variableAssignNode,\n      variableAggregationNode,\n      variableSplittingNode,\n      parameterExtractionNode,\n    ],\n  },\n  {\n    label: t('workflow.nodes.classify.other'),\n    list: [mcpNode, toolNode],\n  },\n]\nexport const knowledgeLoopMenuNodes = [\n  {\n    label: t('views.tool.dataSource.title'),\n    list: [dataSourceLocalNode, dataSourceWebNode],\n  },\n  {\n    label: t('views.knowledge.title'),\n    list: [documentSplitNode, knowledgeWriteNode, documentExtractNode],\n  },\n  {\n    label: t('workflow.nodes.classify.aiCapability'),\n    list: [\n      aiChatNode,\n      intentNode,\n      textToSpeechNode,\n      speechToTextNode,\n      imageGenerateNode,\n      imageUnderstandNode,\n      textToVideoNode,\n      imageToVideoNode,\n      videoUnderstandNode,\n      questionNode,\n    ],\n  },\n  {\n    label: t('workflow.nodes.classify.businessLogic'),\n    list: [conditionNode, replyNode, loopContinueNode, loopBreakNode],\n  },\n  {\n    label: t('workflow.nodes.classify.dataProcessing'),\n    list: [\n      variableAssignNode,\n      variableAggregationNode,\n      variableSplittingNode,\n      parameterExtractionNode,\n    ],\n  },\n  {\n    label: t('workflow.nodes.classify.other'),\n    list: [mcpNode, toolNode],\n  },\n]\nexport const toolLoopMenuNodes = [\n  {\n    label: t('views.tool.dataSource.title'),\n    list: [dataSourceLocalNode, dataSourceWebNode],\n  },\n  {\n    label: t('views.knowledge.title'),\n    list: [documentSplitNode, knowledgeWriteNode, documentExtractNode],\n  },\n  {\n    label: t('workflow.nodes.classify.aiCapability'),\n    list: [\n      aiChatNode,\n      intentNode,\n      textToSpeechNode,\n      speechToTextNode,\n      imageGenerateNode,\n      imageUnderstandNode,\n      textToVideoNode,\n      imageToVideoNode,\n      videoUnderstandNode,\n      questionNode,\n    ],\n  },\n  {\n    label: t('workflow.nodes.classify.businessLogic'),\n    list: [conditionNode, replyNode, loopContinueNode, loopBreakNode],\n  },\n  {\n    label: t('workflow.nodes.classify.dataProcessing'),\n    list: [\n      variableAssignNode,\n      variableAggregationNode,\n      variableSplittingNode,\n      parameterExtractionNode,\n    ],\n  },\n  {\n    label: t('workflow.nodes.classify.other'),\n    list: [mcpNode, toolNode],\n  },\n]\nconst toolMenuNodes = [\n  {\n    label: t('workflow.nodes.classify.aiCapability'),\n    list: [\n      aiChatNode,\n      intentNode,\n      textToSpeechNode,\n      speechToTextNode,\n      imageGenerateNode,\n      imageUnderstandNode,\n      textToVideoNode,\n      imageToVideoNode,\n      videoUnderstandNode,\n      questionNode,\n    ],\n  },\n  {\n    label: t('views.knowledge.title'),\n    list: [searchKnowledgeNode, searchDocumentNode, rerankerNode, documentExtractNode],\n  },\n  {\n    label: t('workflow.nodes.classify.businessLogic'),\n    list: [conditionNode, formNode, replyNode, loopNode],\n  },\n  {\n    label: t('workflow.nodes.classify.dataProcessing'),\n    list: [\n      variableAssignNode,\n      variableAggregationNode,\n      variableSplittingNode,\n      parameterExtractionNode,\n    ],\n  },\n  {\n    label: t('workflow.nodes.classify.other'),\n    list: [mcpNode, toolNode],\n  },\n]\nexport const getMenuNodes = (workflowMode: WorkflowMode) => {\n  if (workflowMode == WorkflowMode.Application) {\n    return menuNodes\n  }\n  if (workflowMode == WorkflowMode.ApplicationLoop) {\n    return applicationLoopMenuNodes\n  }\n  if (workflowMode == WorkflowMode.Knowledge) {\n    return knowledgeMenuNodes\n  }\n  if (workflowMode == WorkflowMode.KnowledgeLoop) {\n    return knowledgeLoopMenuNodes\n  }\n  if (workflowMode == WorkflowMode.Tool) {\n    return toolMenuNodes\n  }\n  if (workflowMode == WorkflowMode.ToolLoop) {\n    return toolLoopMenuNodes\n  }\n}\n\n/**\n * 工具配置数据\n */\nexport const toolLibNode = {\n  type: WorkflowType.ToolLib,\n  text: t('workflow.nodes.toolNode.text'),\n  label: t('workflow.nodes.toolNode.label'),\n  height: 170,\n  properties: {\n    stepName: t('workflow.nodes.toolNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.result'),\n          value: 'result',\n        },\n      ],\n    },\n  },\n}\n\n/**\n * 工作流工具配置数据\n */\nexport const toolWorkflowLibNode = {\n  type: WorkflowType.ToolWorkflowLib,\n  text: t('workflow.nodes.toolWorlflowNode.text','工作流工具'),\n  label: t('workflow.nodes.toolWorlflowNode.label','工作流工具'),\n  height: 170,\n  properties: {\n    stepName: t('workflow.nodes.toolWorlflowNode.label','工作流工具'),\n    config: {\n      fields: [\n      ],\n    },\n  },\n}\n\nexport const applicationNode = {\n  type: WorkflowType.Application,\n  text: t('workflow.nodes.applicationNode.label'),\n  label: t('workflow.nodes.applicationNode.label'),\n  height: 260,\n  properties: {\n    stepName: t('workflow.nodes.applicationNode.label'),\n    config: {\n      fields: [\n        {\n          label: t('common.result'),\n          value: 'result',\n        },\n      ],\n    },\n  },\n}\n\nexport const compareList = [\n  { value: 'is_null', label: t('workflow.compare.is_null') },\n  { value: 'is_not_null', label: t('workflow.compare.is_not_null') },\n  { value: 'contain', label: t('workflow.compare.contain') },\n  { value: 'not_contain', label: t('workflow.compare.not_contain') },\n  { value: 'eq', label: t('workflow.compare.eq') },\n  { value: 'not_eq', label: t('workflow.compare.not_eq') },\n  { value: 'ge', label: t('workflow.compare.ge') },\n  { value: 'gt', label: t('workflow.compare.gt') },\n  { value: 'le', label: t('workflow.compare.le') },\n  { value: 'lt', label: t('workflow.compare.lt') },\n  { value: 'len_eq', label: t('workflow.compare.len_eq') },\n  { value: 'len_ge', label: t('workflow.compare.len_ge') },\n  { value: 'len_gt', label: t('workflow.compare.len_gt') },\n  { value: 'len_le', label: t('workflow.compare.len_le') },\n  { value: 'len_lt', label: t('workflow.compare.len_lt') },\n  { value: 'is_true', label: t('workflow.compare.is_true') },\n  { value: 'is_not_true', label: t('workflow.compare.is_not_true') },\n  { value: 'start_with', label: 'startWith' },\n  { value: 'end_with', label: 'endWith' },\n]\nexport const nodeDict: any = {\n  [WorkflowType.AiChat]: aiChatNode,\n  [WorkflowType.SearchKnowledge]: searchKnowledgeNode,\n  [WorkflowType.SearchDocument]: searchDocumentNode,\n  [WorkflowType.Question]: questionNode,\n  [WorkflowType.Condition]: conditionNode,\n  [WorkflowType.Base]: baseNode,\n  [WorkflowType.Start]: startNode,\n  [WorkflowType.Reply]: replyNode,\n  [WorkflowType.ToolLib]: toolNode,\n  [WorkflowType.ToolWorkflowLib]: toolWorkflowLibNode,\n  [WorkflowType.ToolLibCustom]: toolNode,\n  [WorkflowType.RerankerNode]: rerankerNode,\n  [WorkflowType.FormNode]: formNode,\n  [WorkflowType.Application]: applicationNode,\n  [WorkflowType.DocumentExtractNode]: documentExtractNode,\n  [WorkflowType.DocumentSplitNode]: documentSplitNode,\n  [WorkflowType.ImageUnderstandNode]: imageUnderstandNode,\n  [WorkflowType.TextToSpeechNode]: textToSpeechNode,\n  [WorkflowType.SpeechToTextNode]: speechToTextNode,\n  [WorkflowType.ImageGenerateNode]: imageGenerateNode,\n  [WorkflowType.VariableAssignNode]: variableAssignNode,\n  [WorkflowType.McpNode]: mcpNode,\n  [WorkflowType.TextToVideoGenerateNode]: textToVideoNode,\n  [WorkflowType.ImageToVideoGenerateNode]: imageToVideoNode,\n  [WorkflowType.IntentNode]: intentNode,\n  [WorkflowType.LoopNode]: loopNode,\n  [WorkflowType.LoopBodyNode]: loopBodyNode,\n  [WorkflowType.LoopStartNode]: loopStartNode,\n  [WorkflowType.LoopBreakNode]: loopBodyNode,\n  [WorkflowType.LoopContinueNode]: loopContinueNode,\n  [WorkflowType.VariableSplittingNode]: variableSplittingNode,\n  [WorkflowType.VideoUnderstandNode]: videoUnderstandNode,\n  [WorkflowType.ParameterExtractionNode]: parameterExtractionNode,\n  [WorkflowType.VariableAggregationNode]: variableAggregationNode,\n  [WorkflowType.KnowledgeBase]: knowledgeBaseNode,\n  [WorkflowType.DataSourceLocalNode]: dataSourceLocalNode,\n  [WorkflowType.DataSourceWebNode]: dataSourceWebNode,\n  [WorkflowType.KnowledgeWriteNode]: knowledgeWriteNode,\n  [WorkflowType.ToolBaseNode]: toolBaseNode,\n  [WorkflowType.ToolStartNode]: toolStartNode,\n}\n\nexport function isWorkFlow(type: string | undefined) {\n  return type === 'WORK_FLOW'\n}\n\nexport function isLastNode(nodeModel: any) {\n  const incoming = nodeModel.graphModel.getNodeIncomingNode(nodeModel.id)\n  const outcomming = nodeModel.graphModel.getNodeOutgoingNode(nodeModel.id)\n  if (incoming.length > 0 && outcomming.length === 0) {\n    return true\n  } else {\n    return false\n  }\n}\n"
  },
  {
    "path": "ui/src/workflow/common/edge.ts",
    "content": "import { BezierEdge, BezierEdgeModel, h } from '@logicflow/core'\nimport { createApp, h as vh } from 'vue'\nimport { isActive, connect, disconnect } from './teleport'\nimport CustomLine from './CustomLine.vue'\nfunction isMouseInElement(element: any, e: any) {\n  const rect = element.getBoundingClientRect()\n  return (\n    e.clientX >= rect.left &&\n    e.clientX <= rect.right &&\n    e.clientY >= rect.top &&\n    e.clientY <= rect.bottom\n  )\n}\nconst DEFAULT_WIDTH = 32\nconst DEFAULT_HEIGHT = 32\nclass CustomEdge2 extends BezierEdge {\n  isMounted\n  customLineApp?: any\n  root?: any\n  constructor() {\n    super()\n    this.isMounted = false\n    this.handleMouseUp = (e: any) => {\n      this.props.graphModel.clearSelectElements()\n      this.props.model.isSelected = true\n      const element = e.target.parentNode.parentNode.querySelector('.lf-custom-edge-wrapper')\n      if (isMouseInElement(element, e)) {\n        this.props.model.graphModel.deleteEdgeById(this.props.model.id)\n      }\n    }\n  }\n  /**\n   * 渲染vue组件\n   * @param root\n   */\n  protected renderVueComponent(root: any) {\n    this.unmountVueComponent()\n    this.root = root\n    const { graphModel } = this.props\n    if (root) {\n      if (isActive()) {\n        connect(\n          this.targetId(),\n          CustomLine,\n          root,\n          this.props.model,\n          graphModel,\n          (node: any, graph: any) => {\n            return { model: node, graph }\n          },\n        )\n      } else {\n        this.customLineApp = createApp({\n          render: () => vh(CustomLine, { model: this.props.model }),\n        })\n        this.customLineApp?.mount(root)\n      }\n    }\n  }\n  protected targetId() {\n    return `${this.props.graphModel.flowId}:${this.props.model.id}`\n  }\n  /**\n   * 组件即将卸载勾子\n   */\n  componentWillUnmount() {\n    if (super.componentWillUnmount) {\n      super.componentWillUnmount()\n    }\n    if (isActive()) {\n      disconnect(this.targetId())\n    }\n    this.unmountVueComponent()\n  }\n  /**\n   * 卸载vue\n   * @returns\n   */\n  protected unmountVueComponent() {\n    if (this.customLineApp) {\n      this.customLineApp.unmount()\n      this.customLineApp = null\n    }\n    if (this.root) {\n      this.root.innerHTML = ''\n    }\n    return this.root\n  }\n\n  getEdge() {\n    const { model } = this.props\n    const id = model.id\n    const { customWidth = DEFAULT_WIDTH, customHeight = DEFAULT_HEIGHT } = model.getProperties()\n    const { startPoint, endPoint, path, isAnimation, arrowConfig } = model\n    const animationStyle = model.getEdgeAnimationStyle()\n    const {\n      strokeDasharray,\n      stroke,\n      strokeDashoffset,\n      animationName,\n      animationDuration,\n      animationIterationCount,\n      animationTimingFunction,\n      animationDirection,\n    } = animationStyle\n    const positionData = {\n      x: (startPoint.x + endPoint.x - customWidth) / 2,\n      y: (startPoint.y + endPoint.y - customHeight) / 2,\n      width: customWidth,\n      height: customHeight,\n    }\n    const style = model.getEdgeStyle()\n    const wrapperStyle = {\n      width: customWidth,\n      height: customHeight,\n    }\n\n    setTimeout(() => {\n      const s = document.getElementById(id)\n      if (s && !this.isMounted) {\n        this.isMounted = true\n        this.renderVueComponent(s)\n      }\n    }, 0)\n\n    delete style.stroke\n\n    return h('g', {}, [\n      h(\n        'style' as any,\n        { type: 'text/css' },\n        '.lf-edge{stroke:#afafaf}.lf-edge:hover{stroke: #3370FF;}',\n      ),\n      h('path', {\n        d: path,\n        ...style,\n        ...arrowConfig,\n        ...(isAnimation\n          ? {\n              strokeDasharray,\n              stroke,\n              style: {\n                strokeDashoffset,\n                animationName,\n                animationDuration,\n                animationIterationCount,\n                animationTimingFunction,\n                animationDirection,\n              },\n            }\n          : {}),\n      }),\n      h(\n        'foreignObject',\n        {\n          ...positionData,\n          y: positionData.y + 5,\n          x: positionData.x + 5,\n          style: {},\n        },\n        [\n          h('div', {\n            id,\n            style: { ...wrapperStyle },\n            className: 'lf-custom-edge-wrapper',\n          }),\n        ],\n      ),\n    ])\n  }\n}\n\nclass CustomEdgeModel2 extends BezierEdgeModel {\n  getArrowStyle() {\n    const arrowStyle = super.getArrowStyle()\n    arrowStyle.offset = 1\n    arrowStyle.verticalLength = 0\n    return arrowStyle\n  }\n\n  getEdgeStyle() {\n    const style = super.getEdgeStyle()\n    // svg属性\n    style.strokeWidth = 2\n    style.stroke = '#BBBFC4'\n    style.offset = 0\n    return style\n  }\n  /**\n   * 重写此方法，使保存数据是能带上锚点数据。\n   */\n  getData() {\n    const data: any = super.getData()\n    if (data) {\n      data.sourceAnchorId = this.sourceAnchorId\n      data.targetAnchorId = this.targetAnchorId\n    }\n    return data\n  }\n  /**\n   * 给边自定义方案，使其支持基于锚点的位置更新边的路径\n   */\n  updatePathByAnchor() {\n    const sourceNodeModel = this.graphModel.getNodeModelById(this.sourceNodeId)\n    const sourceAnchor = sourceNodeModel\n      .getDefaultAnchor()\n      .find((anchor: any) => anchor.id === this.sourceAnchorId)\n\n    const targetNodeModel = this.graphModel.getNodeModelById(this.targetNodeId)\n    const targetAnchor = targetNodeModel\n      .getDefaultAnchor()\n      .find((anchor: any) => anchor.id === this.targetAnchorId)\n    if (sourceAnchor && targetAnchor) {\n      const startPoint = {\n        x: sourceAnchor.x,\n        y: sourceAnchor.y,\n      }\n      this.updateStartPoint(startPoint)\n      const endPoint = {\n        x: targetAnchor.x,\n        y: targetAnchor.y,\n      }\n\n      this.updateEndPoint(endPoint)\n    }\n\n    // 这里需要将原有的pointsList设置为空，才能触发bezier的自动计算control点。\n    this.pointsList = []\n    this.initPoints()\n  }\n  setAttributes(): void {\n    super.setAttributes()\n    this.isHitable = true\n    this.zIndex = 0\n  }\n}\n\nexport default {\n  type: 'app-edge',\n  view: CustomEdge2,\n  model: CustomEdgeModel2,\n}\n"
  },
  {
    "path": "ui/src/workflow/common/loopEdge.ts",
    "content": "import { BezierEdge, BezierEdgeModel, h } from '@logicflow/core'\n\nclass CustomEdgeModel2 extends BezierEdgeModel {\n  getArrowStyle() {\n    const arrowStyle = super.getArrowStyle()\n    arrowStyle.offset = 0\n    arrowStyle.verticalLength = 0\n    return arrowStyle\n  }\n\n  getEdgeStyle() {\n    const style = super.getEdgeStyle()\n    // svg属性\n    style.strokeWidth = 2\n    style.stroke = '#BBBFC4'\n    style.offset = 0\n    return style\n  }\n  /**\n   * 重写此方法，使保存数据是能带上锚点数据。\n   */\n  getData() {\n    const data: any = super.getData()\n    if (data) {\n      data.sourceAnchorId = this.sourceAnchorId\n      data.targetAnchorId = this.targetAnchorId\n    }\n    return data\n  }\n  /**\n   * 给边自定义方案，使其支持基于锚点的位置更新边的路径\n   */\n  updatePathByAnchor() {\n    // TODO\n    const sourceNodeModel = this.graphModel.getNodeModelById(this.sourceNodeId)\n    const sourceAnchor = sourceNodeModel\n      .getDefaultAnchor()\n      .find((anchor: any) => anchor.id === this.sourceAnchorId)\n\n    const targetNodeModel = this.graphModel.getNodeModelById(this.targetNodeId)\n    const targetAnchor = targetNodeModel\n      .getDefaultAnchor()\n      .find((anchor: any) => anchor.id === this.targetAnchorId)\n    if (sourceAnchor && targetAnchor) {\n      const startPoint = {\n        x: sourceAnchor.x,\n        y: sourceAnchor.y - 10\n      }\n      this.updateStartPoint(startPoint)\n      const endPoint = {\n        x: targetAnchor.x,\n        y: targetAnchor.y + 3\n      }\n\n      this.updateEndPoint(endPoint)\n    }\n\n    // 这里需要将原有的pointsList设置为空，才能触发bezier的自动计算control点。\n    this.pointsList = []\n    this.initPoints()\n  }\n  setAttributes(): void {\n    super.setAttributes()\n    this.isHitable = true\n    this.zIndex = 0\n  }\n}\n\nexport default {\n  type: 'loop-edge',\n  view: BezierEdge,\n  model: CustomEdgeModel2\n}\n"
  },
  {
    "path": "ui/src/workflow/common/shortcut.ts",
    "content": "import { cloneDeep } from 'lodash'\nimport type LogicFlow from '@logicflow/core'\nimport { type GraphModel } from '@logicflow/core'\nimport { MsgSuccess, MsgError, MsgConfirm } from '@/utils/message'\nimport { WorkflowType } from '@/enums/application'\nimport { t } from '@/locales'\nimport { getMenuNodes } from './data'\nlet selected: any | null = null\n\nfunction translationNodeData(nodeData: any, distance: any) {\n  nodeData.x += distance\n  nodeData.y += distance\n  if (nodeData.text) {\n    nodeData.text.x += distance\n    nodeData.text.y += distance\n  }\n  return nodeData\n}\n\nfunction translationEdgeData(edgeData: any, distance: any) {\n  if (edgeData.startPoint) {\n    edgeData.startPoint.x += distance\n    edgeData.startPoint.y += distance\n  }\n  if (edgeData.endPoint) {\n    edgeData.endPoint.x += distance\n    edgeData.endPoint.y += distance\n  }\n  if (edgeData.pointsList && edgeData.pointsList.length > 0) {\n    edgeData.pointsList.forEach((point: any) => {\n      point.x += distance\n      point.y += distance\n    })\n  }\n  if (edgeData.text) {\n    edgeData.text.x += distance\n    edgeData.text.y += distance\n  }\n  return edgeData\n}\n\nconst TRANSLATION_DISTANCE = 40\nlet CHILDREN_TRANSLATION_DISTANCE = 40\n\nexport function initDefaultShortcut(lf: LogicFlow, graph: GraphModel) {\n  const { keyboard } = lf\n  const {\n    options: { keyboard: keyboardOptions },\n  } = keyboard\n  const copy_node = () => {\n    CHILDREN_TRANSLATION_DISTANCE = TRANSLATION_DISTANCE\n    if (!keyboardOptions?.enabled) return true\n    if (graph.textEditElement) return true\n    const { guards } = lf.options\n    const elements = graph.getSelectElements(false)\n    const enabledClone = guards && guards.beforeClone ? guards.beforeClone(elements) : true\n    if (!enabledClone || (elements.nodes.length === 0 && elements.edges.length === 0)) {\n      selected = null\n      return true\n    }\n    const base_nodes = elements.nodes.filter(\n      (node: any) => node.type === WorkflowType.Start || node.type === WorkflowType.Base,\n    )\n    if (base_nodes.length > 0) {\n      MsgError(base_nodes[0]?.properties?.stepName + t('workflow.tip.cannotCopy'))\n      return\n    }\n    selected = cloneDeep(elements)\n    selected.nodes.forEach((node: any) => translationNodeData(node, TRANSLATION_DISTANCE))\n    selected.edges.forEach((edge: any) => translationEdgeData(edge, TRANSLATION_DISTANCE))\n    MsgSuccess(t('workflow.tip.copyError'))\n    return false\n  }\n  const paste_node = () => {\n    if (!keyboardOptions?.enabled) return true\n    if (graph.textEditElement) return true\n    const menus = getMenuNodes(lf.graphModel.get_provide(null, null).workflowMode)\n    const nodes = menus?.flatMap((m: any) => m.list).map((n) => n.type)\n    selected.nodes = selected.nodes.filter((n: any) => nodes?.includes(n.type))\n    if (selected && (selected.nodes || selected.edges)) {\n      lf.clearSelectElements()\n      const addElements = lf.addElements(selected, CHILDREN_TRANSLATION_DISTANCE)\n      if (!addElements) return true\n      addElements.nodes.forEach((node) => lf.selectElementById(node.id, true))\n      addElements.edges.forEach((edge) => lf.selectElementById(edge.id, true))\n      selected.nodes.forEach((node: any) => translationNodeData(node, TRANSLATION_DISTANCE))\n      selected.edges.forEach((edge: any) => translationEdgeData(edge, TRANSLATION_DISTANCE))\n      CHILDREN_TRANSLATION_DISTANCE = CHILDREN_TRANSLATION_DISTANCE + TRANSLATION_DISTANCE\n    }\n    return false\n  }\n  const delete_node = () => {\n    const elements = graph.getSelectElements(true)\n    lf.clearSelectElements()\n    if (elements.nodes.length == 0 && elements.edges.length == 0) {\n      return\n    }\n    if (elements.edges.length > 0 && elements.nodes.length == 0) {\n      elements.edges\n        .filter((edge) => !['loop-edge'].includes(edge.type || ''))\n        .forEach((edge: any) => lf.deleteEdge(edge.id))\n      return\n    }\n    const nodes = elements.nodes.filter((node) =>\n      [\n        'start-node',\n        'base-node',\n        'loop-body-node',\n        'loop-start-node',\n        'knowledge-base-node',\n      ].includes(node.type),\n    )\n    if (nodes.length > 0) {\n      MsgError(`${nodes[0].properties?.stepName}${t('workflow.delete.deleteMessage')}`)\n      return\n    }\n    MsgConfirm(t('common.tip'), t('workflow.delete.confirmTitle'), {\n      confirmButtonText: t('common.confirm'),\n      confirmButtonClass: 'danger',\n    }).then(() => {\n      if (!keyboardOptions?.enabled) return true\n      if (graph.textEditElement) return true\n\n      elements.edges.forEach((edge: any) => lf.deleteEdge(edge.id))\n      elements.nodes.forEach((node: any) => {\n        if (node.type === 'loop-node') {\n          const next = lf.getNodeOutgoingNode(node.id)\n          next.forEach((n: any) => {\n            if (n.type === 'loop-body-node') {\n              lf.deleteNode(n.id)\n            }\n          })\n        }\n        lf.deleteNode(node.id)\n      })\n    })\n\n    return false\n  }\n  graph.eventCenter.on('copy_node', copy_node)\n  // 复制\n  keyboard.on(['cmd + c', 'ctrl + c'], copy_node)\n  // 粘贴\n  keyboard.on(['cmd + v', 'ctrl + v'], paste_node)\n  // undo\n  keyboard.on(['cmd + z', 'ctrl + z'], () => {\n    // if (!keyboardOptions?.enabled) return true\n    // if (graph.textEditElement) return true\n    // lf.undo()\n    // return false\n  })\n  // redo\n  keyboard.on(['cmd + y', 'ctrl + y'], () => {\n    if (!keyboardOptions?.enabled) return true\n    if (graph.textEditElement) return true\n    lf.redo()\n    return false\n  })\n  // delete\n  keyboard.on(['backspace'], delete_node)\n}\n"
  },
  {
    "path": "ui/src/workflow/common/teleport.ts",
    "content": "import { BaseEdgeModel, BaseNodeModel, GraphModel } from '@logicflow/core'\nimport { defineComponent, h, reactive, isVue3, Teleport, markRaw, Fragment } from 'vue-demi'\n\nlet active = false\nconst items = reactive<{ [key: string]: any }>({})\n\nexport function connect(\n  id: string,\n  component: any,\n  container: HTMLDivElement,\n  node: BaseNodeModel | BaseEdgeModel,\n  graph: GraphModel,\n  get_props?: any,\n  get_provide?: any,\n) {\n  if (!get_props) {\n    get_props = (node: BaseNodeModel | BaseEdgeModel, graph: GraphModel) => {\n      return { nodeModel: node, graph }\n    }\n  }\n  if (!get_provide) {\n    get_provide = (node: BaseNodeModel | BaseEdgeModel, graph: GraphModel) => ({\n      getNode: () => node,\n      getGraph: () => graph,\n    })\n  }\n  if (active) {\n    items[id] = markRaw(\n      defineComponent({\n        render: () => h(Teleport, { to: container } as any, [h(component, get_props(node, graph))]),\n        provide: () => get_provide(node, graph),\n      }),\n    )\n  }\n}\n\nexport function disconnect(id: string) {\n  if (active) {\n    delete items[id]\n  }\n}\nexport function disconnectByFlow(flowId: string) {\n  Object.keys(items).forEach((key) => {\n    if (key.startsWith(flowId)) {\n      delete items[key]\n    }\n  })\n}\nexport function disconnectAll() {\n  Object.keys(items).forEach((key) => {\n    delete items[key]\n  })\n}\n\nexport function isActive() {\n  return active\n}\n\nexport function getTeleport(): any {\n  if (!isVue3) {\n    throw new Error('teleport is only available in Vue3')\n  }\n  active = true\n\n  return defineComponent({\n    props: {\n      flowId: {\n        type: String,\n        required: true,\n      },\n    },\n    setup(props) {\n      return () => {\n        const children: Record<string, any>[] = []\n        Object.keys(items).forEach((id) => {\n          // https://github.com/didi/LogicFlow/issues/1768\n          // 多个不同的VueNodeView都会connect注册到items中，因此items存储了可能有多个flowId流程图的数据\n          // 当使用多个LogicFlow时，会创建多个flowId + 同时使用KeepAlive\n          // 每一次items改变，会触发不同flowId持有的setup()执行，由于每次setup()执行就是遍历items，因此存在多次重复渲染元素的问题\n          // 即items[0]会在Page1的setup()执行，items[0]也会在Page2的setup()执行，从而生成两个items[0]\n\n          // 比对当前界面显示的flowId，只更新items[当前页面flowId:nodeId]的数据\n          // 比如items[0]属于Page1的数据，那么Page2无论active=true/false，都无法执行items[0]\n\n          children.push(items[id])\n        })\n        return h(\n          Fragment,\n          {},\n          children.map((item) => h(item)),\n        )\n      }\n    },\n  })\n}\n"
  },
  {
    "path": "ui/src/workflow/common/template.ts",
    "content": "import { baseNodes } from '@/workflow/common/data'\nexport const applicationTemplate: any = {\n  blank: {\n    edges: [],\n    nodes: baseNodes,\n  },\n  assistant: {\n    nodes: [\n      {\n        id: 'base-node',\n        type: 'base-node',\n        x: 120,\n        y: 260.30849999999987,\n        properties: {\n          config: {},\n          height: 734.766,\n          showNode: true,\n          stepName: '基本信息',\n          node_data: {\n            desc: '模板',\n            name: '知识库问答助手',\n            prologue:\n              '您好，我是 XXX 小助手，您可以向我提出 XXX 使用问题。\\n- XXX 主要功能有什么？\\n- XXX 如何收费？\\n- 需要转人工服务',\n            tts_type: 'BROWSER',\n          },\n          input_field_list: [],\n          user_input_config: {\n            title: '用户输入',\n          },\n          api_input_field_list: [],\n          user_input_field_list: [],\n        },\n      },\n      {\n        id: 'start-node',\n        type: 'start-node',\n        x: 120,\n        y: 929.6914999999999,\n        properties: {\n          config: {\n            fields: [\n              {\n                label: '用户问题',\n                value: 'question',\n              },\n            ],\n            globalFields: [\n              {\n                label: '当前时间',\n                value: 'time',\n              },\n              {\n                label: '历史聊天记录',\n                value: 'history_context',\n              },\n              {\n                label: '对话 ID',\n                value: 'chat_id',\n              },\n            ],\n          },\n          fields: [\n            {\n              label: '用户问题',\n              value: 'question',\n            },\n          ],\n          height: 364,\n          showNode: true,\n          stepName: '开始',\n          globalFields: [\n            {\n              label: '当前时间',\n              value: 'time',\n            },\n          ],\n        },\n      },\n      {\n        id: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605',\n        type: 'search-knowledge-node',\n        x: 710,\n        y: 929.6914999999999,\n        properties: {\n          config: {\n            fields: [\n              {\n                label: '检索结果的分段列表',\n                value: 'paragraph_list',\n              },\n              {\n                label: '满足直接回答的分段列表',\n                value: 'is_hit_handling_method_list',\n              },\n              {\n                label: '检索结果',\n                value: 'data',\n              },\n              {\n                label: '满足直接回答的分段内容',\n                value: 'directly_return',\n              },\n            ],\n          },\n          height: 794,\n          showNode: true,\n          stepName: '知识库检索',\n          condition: 'AND',\n          node_data: {\n            knowledge_id_list: [],\n            knowledge_setting: {\n              top_n: 3,\n              similarity: 0.6,\n              search_mode: 'embedding',\n              max_paragraph_char_number: 5000,\n            },\n            question_reference_address: ['start-node', 'question'],\n            all_knowledge_id_list: [],\n            knowledge_list: [],\n          },\n        },\n      },\n      {\n        id: '420a6e4f-44ff-4847-bb81-0923630846b5',\n        type: 'condition-node',\n        x: 1300,\n        y: 929.6914999999999,\n        properties: {\n          width: 600,\n          config: {\n            fields: [\n              {\n                label: '分支名称',\n                value: 'branch_name',\n              },\n            ],\n          },\n          height: 544.148,\n          showNode: true,\n          stepName: '判断器',\n          condition: 'AND',\n          node_data: {\n            branch: [\n              {\n                id: '7887',\n                type: 'IF',\n                condition: 'and',\n                conditions: [\n                  {\n                    field: ['fd0324fc-f5e4-4fa6-a2d9-cb251b467605', 'is_hit_handling_method_list'],\n                    value: 1,\n                    compare: 'is_not_null',\n                  },\n                ],\n              },\n              {\n                id: '6847',\n                type: 'ELSE IF 1',\n                condition: 'and',\n                conditions: [\n                  {\n                    field: ['fd0324fc-f5e4-4fa6-a2d9-cb251b467605', 'paragraph_list'],\n                    value: 1,\n                    compare: 'is_not_null',\n                  },\n                ],\n              },\n              {\n                id: '2794',\n                type: 'ELSE',\n                condition: 'and',\n                conditions: [],\n              },\n            ],\n          },\n          branch_condition_list: [\n            {\n              index: 0,\n              height: 121.383,\n              id: '7887',\n            },\n            {\n              index: 1,\n              height: 121.383,\n              id: '6847',\n            },\n            {\n              index: 2,\n              height: 44,\n              id: '2794',\n            },\n          ],\n        },\n      },\n      {\n        id: '36a440a9-5b00-4d82-b13a-8e7819112918',\n        type: 'reply-node',\n        x: 1890,\n        y: 120,\n        properties: {\n          config: {\n            fields: [\n              {\n                label: '内容',\n                value: 'answer',\n              },\n            ],\n          },\n          height: 386,\n          showNode: true,\n          stepName: '指定回复',\n          condition: 'AND',\n          node_data: {\n            fields: ['fd0324fc-f5e4-4fa6-a2d9-cb251b467605', 'directly_return'],\n            content: '',\n            is_result: true,\n            reply_type: 'referencing',\n          },\n        },\n      },\n      {\n        id: 'f7c3b4a2-cb80-4e47-b050-7fef0315daaf',\n        type: 'ai-chat-node',\n        x: 1890,\n        y: 929.6914999999999,\n        properties: {\n          config: {\n            fields: [\n              {\n                label: 'AI 回答内容',\n                value: 'answer',\n              },\n              {\n                label: '思考过程',\n                value: 'reasoning_content',\n              },\n            ],\n          },\n          height: 993.383,\n          showNode: true,\n          stepName: 'AI 对话',\n          condition: 'AND',\n          node_data: {\n            prompt: '已知信息：\\n{{知识库检索.data}}\\n问题：\\n{{开始.question}}',\n            system: '',\n            model_id: '',\n            is_result: true,\n            max_tokens: null,\n            temperature: null,\n            dialogue_type: 'WORKFLOW',\n            model_setting: {\n              reasoning_content_end: '</think>',\n              reasoning_content_start: '<think>',\n              reasoning_content_enable: false,\n            },\n            dialogue_number: 1,\n          },\n        },\n      },\n      {\n        id: '04dd6c1e-95f9-4757-bb3e-134d503fce54',\n        type: 'reply-node',\n        x: 1890,\n        y: 1798.383,\n        properties: {\n          config: {\n            fields: [\n              {\n                label: '内容',\n                value: 'answer',\n              },\n            ],\n          },\n          height: 504,\n          showNode: true,\n          stepName: '指定回复1',\n          condition: 'AND',\n          node_data: {\n            fields: [],\n            content: '抱歉，没有在知识库查询到相关内容，请提供更详细的信息。',\n            is_result: true,\n            reply_type: 'content',\n          },\n        },\n      },\n    ],\n    edges: [\n      {\n        id: '73f8992c-65ef-409a-a151-378d0927f2aa',\n        type: 'app-edge',\n        sourceNodeId: 'start-node',\n        targetNodeId: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605',\n        startPoint: {\n          x: 280,\n          y: 929.6914999999999,\n        },\n        endPoint: {\n          x: 550,\n          y: 929.6914999999999,\n        },\n        properties: {},\n        pointsList: [\n          {\n            x: 280,\n            y: 929.6914999999999,\n          },\n          {\n            x: 390,\n            y: 929.6914999999999,\n          },\n          {\n            x: 440,\n            y: 929.6914999999999,\n          },\n          {\n            x: 550,\n            y: 929.6914999999999,\n          },\n        ],\n        sourceAnchorId: 'start-node_right',\n        targetAnchorId: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605_left',\n      },\n      {\n        id: '6a8d23d9-5179-424e-80c2-f08d37cdb8d4',\n        type: 'app-edge',\n        sourceNodeId: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605',\n        targetNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5',\n        startPoint: {\n          x: 870,\n          y: 929.6914999999999,\n        },\n        endPoint: {\n          x: 1010,\n          y: 929.6914999999999,\n        },\n        properties: {},\n        pointsList: [\n          {\n            x: 870,\n            y: 929.6914999999999,\n          },\n          {\n            x: 980,\n            y: 929.6914999999999,\n          },\n          {\n            x: 900,\n            y: 929.6914999999999,\n          },\n          {\n            x: 1010,\n            y: 929.6914999999999,\n          },\n        ],\n        sourceAnchorId: 'fd0324fc-f5e4-4fa6-a2d9-cb251b467605_right',\n        targetAnchorId: '420a6e4f-44ff-4847-bb81-0923630846b5_left',\n      },\n      {\n        id: '56006748-d9fe-491b-a14b-04fd568cac08',\n        type: 'app-edge',\n        sourceNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5',\n        targetNodeId: '36a440a9-5b00-4d82-b13a-8e7819112918',\n        startPoint: {\n          x: 1590,\n          y: 793.3089999999999,\n        },\n        endPoint: {\n          x: 1730,\n          y: 120,\n        },\n        properties: {},\n        pointsList: [\n          {\n            x: 1590,\n            y: 793.3089999999999,\n          },\n          {\n            x: 1700,\n            y: 793.3089999999999,\n          },\n          {\n            x: 1620,\n            y: 120,\n          },\n          {\n            x: 1730,\n            y: 120,\n          },\n        ],\n        sourceAnchorId: '420a6e4f-44ff-4847-bb81-0923630846b5_7887_right',\n        targetAnchorId: '36a440a9-5b00-4d82-b13a-8e7819112918_left',\n      },\n      {\n        id: '9bc8721b-07aa-4730-9347-910ed64e26b9',\n        type: 'app-edge',\n        sourceNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5',\n        targetNodeId: 'f7c3b4a2-cb80-4e47-b050-7fef0315daaf',\n        startPoint: {\n          x: 1590,\n          y: 922.6919999999999,\n        },\n        endPoint: {\n          x: 1730,\n          y: 929.6914999999999,\n        },\n        properties: {},\n        pointsList: [\n          {\n            x: 1590,\n            y: 922.6919999999999,\n          },\n          {\n            x: 1700,\n            y: 922.6919999999999,\n          },\n          {\n            x: 1620,\n            y: 929.6914999999999,\n          },\n          {\n            x: 1730,\n            y: 929.6914999999999,\n          },\n        ],\n        sourceAnchorId: '420a6e4f-44ff-4847-bb81-0923630846b5_6847_right',\n        targetAnchorId: 'f7c3b4a2-cb80-4e47-b050-7fef0315daaf_left',\n      },\n      {\n        id: 'c276a5b6-ec29-4ab9-b911-a0a929ff193f',\n        type: 'app-edge',\n        sourceNodeId: '420a6e4f-44ff-4847-bb81-0923630846b5',\n        targetNodeId: '04dd6c1e-95f9-4757-bb3e-134d503fce54',\n        startPoint: {\n          x: 1590,\n          y: 1013.3834999999998,\n        },\n        endPoint: {\n          x: 1730,\n          y: 1798.383,\n        },\n        properties: {},\n        pointsList: [\n          {\n            x: 1590,\n            y: 1013.3834999999998,\n          },\n          {\n            x: 1700,\n            y: 1013.3834999999998,\n          },\n          {\n            x: 1620,\n            y: 1798.383,\n          },\n          {\n            x: 1730,\n            y: 1798.383,\n          },\n        ],\n        sourceAnchorId: '420a6e4f-44ff-4847-bb81-0923630846b5_2794_right',\n        targetAnchorId: '04dd6c1e-95f9-4757-bb3e-134d503fce54_left',\n      },\n    ],\n  },\n}\n\nexport const knowledgeTemplate: any = {\n  default: {\n    edges: [\n      {\n        id: '846dd161-450e-4d2f-8119-78557d88421c',\n        type: 'app-edge',\n        endPoint: {\n          x: 550,\n          y: 720,\n        },\n        pointsList: [\n          {\n            x: 280,\n            y: 720,\n          },\n          {\n            x: 390,\n            y: 720,\n          },\n          {\n            x: 440,\n            y: 720,\n          },\n          {\n            x: 550,\n            y: 720,\n          },\n        ],\n        properties: {},\n        startPoint: {\n          x: 280,\n          y: 720,\n        },\n        sourceNodeId: '768aed24-8139-4689-870f-2065ef05473c',\n        targetNodeId: '1bed736e-711f-4afd-8454-2c8502444af7',\n        sourceAnchorId: '768aed24-8139-4689-870f-2065ef05473c_right',\n        targetAnchorId: '1bed736e-711f-4afd-8454-2c8502444af7_left',\n      },\n      {\n        id: '79cf563e-0b4d-4d41-ad6f-4ee0c6042af6',\n        type: 'app-edge',\n        endPoint: {\n          x: 1010,\n          y: 720,\n        },\n        pointsList: [\n          {\n            x: 870,\n            y: 720,\n          },\n          {\n            x: 980,\n            y: 720,\n          },\n          {\n            x: 900,\n            y: 720,\n          },\n          {\n            x: 1010,\n            y: 720,\n          },\n        ],\n        properties: {},\n        startPoint: {\n          x: 870,\n          y: 720,\n        },\n        sourceNodeId: '1bed736e-711f-4afd-8454-2c8502444af7',\n        targetNodeId: '9018d6b6-be6e-420b-9a0d-7226fd789398',\n        sourceAnchorId: '1bed736e-711f-4afd-8454-2c8502444af7_right',\n        targetAnchorId: '9018d6b6-be6e-420b-9a0d-7226fd789398_left',\n      },\n      {\n        id: '38111bbe-f2ff-428e-acf8-c2f49e45fb08',\n        type: 'app-edge',\n        endPoint: {\n          x: 550,\n          y: 1460,\n        },\n        pointsList: [\n          {\n            x: 280,\n            y: 1460,\n          },\n          {\n            x: 390,\n            y: 1460,\n          },\n          {\n            x: 440,\n            y: 1460,\n          },\n          {\n            x: 550,\n            y: 1460,\n          },\n        ],\n        properties: {},\n        startPoint: {\n          x: 280,\n          y: 1460,\n        },\n        sourceNodeId: 'affa7bad-1898-4bdb-967b-9e12a72492c6',\n        targetNodeId: 'd81adcf1-bfd4-4a1c-b62c-e9ae0eb9488d',\n        sourceAnchorId: 'affa7bad-1898-4bdb-967b-9e12a72492c6_right',\n        targetAnchorId: 'd81adcf1-bfd4-4a1c-b62c-e9ae0eb9488d_left',\n      },\n      {\n        id: '632ed493-fd99-461b-9510-f4e3d02120d0',\n        type: 'app-edge',\n        endPoint: {\n          x: 1010,\n          y: 1460,\n        },\n        pointsList: [\n          {\n            x: 870,\n            y: 1460,\n          },\n          {\n            x: 980,\n            y: 1460,\n          },\n          {\n            x: 900,\n            y: 1460,\n          },\n          {\n            x: 1010,\n            y: 1460,\n          },\n        ],\n        properties: {},\n        startPoint: {\n          x: 870,\n          y: 1460,\n        },\n        sourceNodeId: 'd81adcf1-bfd4-4a1c-b62c-e9ae0eb9488d',\n        targetNodeId: 'a1d0fa5d-4779-4364-8b54-7eff69bd1ec4',\n        sourceAnchorId: 'd81adcf1-bfd4-4a1c-b62c-e9ae0eb9488d_right',\n        targetAnchorId: 'a1d0fa5d-4779-4364-8b54-7eff69bd1ec4_left',\n      },\n      {\n        id: '5888e8cc-75fc-4df7-a627-1dc6feedea17',\n        type: 'app-edge',\n        endPoint: {\n          x: 1790,\n          y: 1440,\n        },\n        pointsList: [\n          {\n            x: 1490,\n            y: 720,\n          },\n          {\n            x: 1600,\n            y: 720,\n          },\n          {\n            x: 1680,\n            y: 1440,\n          },\n          {\n            x: 1790,\n            y: 1440,\n          },\n        ],\n        properties: {},\n        startPoint: {\n          x: 1490,\n          y: 720,\n        },\n        sourceNodeId: '9018d6b6-be6e-420b-9a0d-7226fd789398',\n        targetNodeId: 'ade860cd-db62-4538-9943-c8f42c1b927e',\n        sourceAnchorId: '9018d6b6-be6e-420b-9a0d-7226fd789398_right',\n        targetAnchorId: 'ade860cd-db62-4538-9943-c8f42c1b927e_left',\n      },\n      {\n        id: '3f294d6b-6f4f-4a5c-9d49-f118033eec70',\n        type: 'app-edge',\n        endPoint: {\n          x: 1790,\n          y: 1440,\n        },\n        pointsList: [\n          {\n            x: 1490,\n            y: 1460,\n          },\n          {\n            x: 1600,\n            y: 1460,\n          },\n          {\n            x: 1680,\n            y: 1440,\n          },\n          {\n            x: 1790,\n            y: 1440,\n          },\n        ],\n        properties: {},\n        startPoint: {\n          x: 1490,\n          y: 1460,\n        },\n        sourceNodeId: 'a1d0fa5d-4779-4364-8b54-7eff69bd1ec4',\n        targetNodeId: 'ade860cd-db62-4538-9943-c8f42c1b927e',\n        sourceAnchorId: 'a1d0fa5d-4779-4364-8b54-7eff69bd1ec4_right',\n        targetAnchorId: 'ade860cd-db62-4538-9943-c8f42c1b927e_left',\n      },\n      {\n        id: 'd04c1570-6572-4505-bf84-f29d81c30e57',\n        type: 'app-edge',\n        endPoint: {\n          x: 1790,\n          y: 1440,\n        },\n        pointsList: [\n          {\n            x: 1490,\n            y: 2190,\n          },\n          {\n            x: 1600,\n            y: 2190,\n          },\n          {\n            x: 1680,\n            y: 1440,\n          },\n          {\n            x: 1790,\n            y: 1440,\n          },\n        ],\n        properties: {},\n        startPoint: {\n          x: 1490,\n          y: 2190,\n        },\n        sourceNodeId: 'dddebd93-ea1a-4880-8a52-ea8112f7e769',\n        targetNodeId: 'ade860cd-db62-4538-9943-c8f42c1b927e',\n        sourceAnchorId: 'dddebd93-ea1a-4880-8a52-ea8112f7e769_right',\n        targetAnchorId: 'ade860cd-db62-4538-9943-c8f42c1b927e_left',\n      },\n      {\n        id: '536d7d47-9ad8-4144-a6df-35e3e2398327',\n        type: 'app-edge',\n        endPoint: {\n          x: 2430,\n          y: 1430,\n        },\n        pointsList: [\n          {\n            x: 2110,\n            y: 1440,\n          },\n          {\n            x: 2220,\n            y: 1440,\n          },\n          {\n            x: 2320,\n            y: 1430,\n          },\n          {\n            x: 2430,\n            y: 1430,\n          },\n        ],\n        properties: {},\n        startPoint: {\n          x: 2110,\n          y: 1440,\n        },\n        sourceNodeId: 'ade860cd-db62-4538-9943-c8f42c1b927e',\n        targetNodeId: '1c5abed5-e181-41f7-96f5-b5175cc37f3c',\n        sourceAnchorId: 'ade860cd-db62-4538-9943-c8f42c1b927e_right',\n        targetAnchorId: '1c5abed5-e181-41f7-96f5-b5175cc37f3c_left',\n      },\n      {\n        id: '8b5bb2b5-5baa-4e54-b848-fa87231d9585',\n        type: 'app-edge',\n        endPoint: {\n          x: 1010,\n          y: 2190,\n        },\n        pointsList: [\n          {\n            x: 870,\n            y: 2190,\n          },\n          {\n            x: 980,\n            y: 2190,\n          },\n          {\n            x: 900,\n            y: 2190,\n          },\n          {\n            x: 1010,\n            y: 2190,\n          },\n        ],\n        properties: {},\n        startPoint: {\n          x: 870,\n          y: 2190,\n        },\n        sourceNodeId: '08503db8-f2e3-4eb3-96f7-957f30a6da6e',\n        targetNodeId: 'dddebd93-ea1a-4880-8a52-ea8112f7e769',\n        sourceAnchorId: '08503db8-f2e3-4eb3-96f7-957f30a6da6e_right',\n        targetAnchorId: 'dddebd93-ea1a-4880-8a52-ea8112f7e769_left',\n      },\n    ],\n    nodes: [\n      {\n        x: 120,\n        y: 115.05849999999998,\n        id: 'knowledge-base-node',\n        type: 'knowledge-base-node',\n        properties: {\n          config: {\n            fields: [],\n            globalFields: [],\n          },\n          height: 394.383,\n          showNode: true,\n          stepName: '\\u57fa\\u672c\\u4fe1\\u606f',\n          node_data: {\n            desc: '',\n            name: '',\n            prologue:\n              '\\u60a8\\u597d\\uff0c\\u6211\\u662f XXX \\u5c0f\\u52a9\\u624b\\uff0c\\u60a8\\u53ef\\u4ee5\\u5411\\u6211\\u63d0\\u51fa XXX \\u4f7f\\u7528\\u95ee\\u9898\\u3002\\n- XXX \\u4e3b\\u8981\\u529f\\u80fd\\u6709\\u4ec0\\u4e48\\uff1f\\n- XXX \\u5982\\u4f55\\u6536\\u8d39\\uff1f\\n- \\u9700\\u8981\\u8f6c\\u4eba\\u5de5\\u670d\\u52a1',\n            tts_type: 'BROWSER',\n          },\n          input_field_list: [],\n          user_input_config: {\n            title: '\\u6587\\u6863\\u5904\\u7406\\u8bbe\\u7f6e',\n          },\n          user_input_field_list: [],\n        },\n      },\n      {\n        x: 120,\n        y: 720,\n        id: '768aed24-8139-4689-870f-2065ef05473c',\n        type: 'data-source-local-node',\n        properties: {\n          kind: 'data-source',\n          config: {\n            fields: [\n              {\n                label: '\\u6587\\u4ef6\\u5217\\u8868',\n                value: 'file_list',\n              },\n            ],\n          },\n          height: 566,\n          showNode: true,\n          stepName: '\\u6587\\u672c\\u6587\\u4ef6',\n          node_data: {\n            file_type_list: ['TXT', 'DOCX', 'PDF', 'HTML', 'XLS', 'XLSX', 'CSV'],\n            file_size_limit: 100,\n            file_count_limit: 50,\n          },\n          input_field_list: [],\n          user_input_config: {},\n          user_input_field_list: [],\n        },\n      },\n      {\n        x: 710,\n        y: 720,\n        id: '1bed736e-711f-4afd-8454-2c8502444af7',\n        type: 'document-extract-node',\n        properties: {\n          config: {\n            fields: [\n              {\n                label: '\\u6587\\u6863\\u5185\\u5bb9',\n                value: 'content',\n              },\n              {\n                label: '\\u6587\\u6863\\u5217\\u8868',\n                value: 'document_list',\n              },\n            ],\n          },\n          height: 394,\n          showNode: true,\n          stepName: '\\u6587\\u6863\\u5185\\u5bb9\\u63d0\\u53d6',\n          condition: 'OR',\n          node_data: {\n            document_list: ['768aed24-8139-4689-870f-2065ef05473c', 'file_list'],\n          },\n        },\n      },\n      {\n        x: 1250,\n        y: 2190,\n        id: 'dddebd93-ea1a-4880-8a52-ea8112f7e769',\n        type: 'document-split-node',\n        properties: {\n          width: 500,\n          config: {\n            fields: [\n              {\n                label: '\\u5206\\u6bb5\\u5217\\u8868',\n                value: 'paragraph_list',\n              },\n            ],\n          },\n          height: 652,\n          showNode: true,\n          stepName: 'Web\\u667a\\u80fd\\u5206\\u6bb5',\n          condition: 'AND',\n          node_data: {\n            limit: 4096,\n            patterns: [],\n            chunk_size: 256,\n            limit_type: 'custom',\n            with_filter: false,\n            document_list: ['08503db8-f2e3-4eb3-96f7-957f30a6da6e', 'document_list'],\n            patterns_type: 'custom',\n            split_strategy: 'auto',\n            chunk_size_type: 'custom',\n            limit_reference: [],\n            with_filter_type: 'custom',\n            patterns_reference: [],\n            chunk_size_reference: [],\n            with_filter_reference: [],\n            document_name_relate_problem: false,\n            paragraph_title_relate_problem: true,\n            document_name_relate_problem_type: 'custom',\n            paragraph_title_relate_problem_type: 'custom',\n            document_name_relate_problem_reference: [],\n            paragraph_title_relate_problem_reference: [],\n          },\n        },\n      },\n      {\n        x: 2590,\n        y: 1430,\n        id: '1c5abed5-e181-41f7-96f5-b5175cc37f3c',\n        type: 'knowledge-write-node',\n        properties: {\n          config: {\n            fields: [],\n          },\n          height: 278,\n          showNode: true,\n          stepName: '\\u77e5\\u8bc6\\u5e93\\u5199\\u5165',\n          condition: 'AND',\n          node_data: {\n            document_list: ['ade860cd-db62-4538-9943-c8f42c1b927e', 'Segmented_List'],\n          },\n        },\n      },\n      {\n        x: 1250,\n        y: 720,\n        id: '9018d6b6-be6e-420b-9a0d-7226fd789398',\n        type: 'document-split-node',\n        properties: {\n          width: 500,\n          config: {\n            fields: [\n              {\n                label: '\\u5206\\u6bb5\\u5217\\u8868',\n                value: 'paragraph_list',\n              },\n            ],\n          },\n          height: 652,\n          showNode: true,\n          stepName: '\\u667a\\u80fd\\u5206\\u6bb5',\n          condition: 'AND',\n          node_data: {\n            limit: 4096,\n            patterns: [],\n            chunk_size: 256,\n            limit_type: 'custom',\n            with_filter: false,\n            document_list: ['1bed736e-711f-4afd-8454-2c8502444af7', 'document_list'],\n            patterns_type: 'custom',\n            split_strategy: 'auto',\n            chunk_size_type: 'custom',\n            limit_reference: [],\n            with_filter_type: 'custom',\n            patterns_reference: [],\n            chunk_size_reference: [],\n            with_filter_reference: [],\n            document_name_relate_problem: true,\n            paragraph_title_relate_problem: true,\n            document_name_relate_problem_type: 'custom',\n            paragraph_title_relate_problem_type: 'custom',\n            document_name_relate_problem_reference: [],\n            paragraph_title_relate_problem_reference: [],\n          },\n        },\n      },\n      {\n        x: 120,\n        y: 1460,\n        id: 'affa7bad-1898-4bdb-967b-9e12a72492c6',\n        type: 'data-source-local-node',\n        properties: {\n          kind: 'data-source',\n          config: {\n            fields: [\n              {\n                label: '\\u6587\\u4ef6\\u5217\\u8868',\n                value: 'file_list',\n              },\n            ],\n          },\n          height: 536,\n          showNode: true,\n          stepName: 'QA\\u95ee\\u7b54\\u5bf9',\n          node_data: {\n            file_type_list: ['XLS', 'XLSX', 'CSV', 'ZIP'],\n            file_size_limit: 100,\n            file_count_limit: 50,\n          },\n          input_field_list: [],\n          user_input_config: {},\n          user_input_field_list: [],\n        },\n      },\n      {\n        x: 710,\n        y: 1460,\n        id: 'd81adcf1-bfd4-4a1c-b62c-e9ae0eb9488d',\n        type: 'document-extract-node',\n        properties: {\n          config: {\n            fields: [\n              {\n                label: '\\u6587\\u6863\\u5185\\u5bb9',\n                value: 'content',\n              },\n              {\n                label: '\\u6587\\u6863\\u5217\\u8868',\n                value: 'document_list',\n              },\n            ],\n          },\n          height: 394,\n          showNode: true,\n          stepName: '\\u6587\\u6863\\u5185\\u5bb9\\u63d0\\u53d61',\n          condition: 'AND',\n          node_data: {\n            document_list: ['affa7bad-1898-4bdb-967b-9e12a72492c6', 'file_list'],\n          },\n        },\n      },\n      {\n        x: 1250,\n        y: 1460,\n        id: 'a1d0fa5d-4779-4364-8b54-7eff69bd1ec4',\n        type: 'document-split-node',\n        properties: {\n          width: 500,\n          config: {\n            fields: [\n              {\n                label: '\\u5206\\u6bb5\\u5217\\u8868',\n                value: 'paragraph_list',\n              },\n            ],\n          },\n          height: 580,\n          showNode: true,\n          stepName: 'QA\\u95ee\\u7b54\\u5bf9\\u5206\\u6bb5',\n          condition: 'AND',\n          node_data: {\n            limit: 4096,\n            patterns: [],\n            chunk_size: 256,\n            limit_type: 'custom',\n            with_filter: false,\n            document_list: ['d81adcf1-bfd4-4a1c-b62c-e9ae0eb9488d', 'document_list'],\n            patterns_type: 'custom',\n            split_strategy: 'qa',\n            chunk_size_type: 'custom',\n            limit_reference: [],\n            with_filter_type: 'custom',\n            patterns_reference: [],\n            chunk_size_reference: [],\n            with_filter_reference: [],\n            document_name_relate_problem: true,\n            paragraph_title_relate_problem: false,\n            document_name_relate_problem_type: 'custom',\n            paragraph_title_relate_problem_type: 'custom',\n            document_name_relate_problem_reference: [],\n            paragraph_title_relate_problem_reference: [],\n          },\n        },\n      },\n      {\n        x: 1950,\n        y: 1440,\n        id: 'ade860cd-db62-4538-9943-c8f42c1b927e',\n        type: 'variable-aggregation-node',\n        properties: {\n          config: {\n            fields: [\n              {\n                label: '\\u6587\\u6863\\u5206\\u6bb5\\u5217\\u8868',\n                value: 'Segmented_List',\n              },\n            ],\n          },\n          height: 570.7660000000001,\n          showNode: true,\n          stepName: '\\u805a\\u5408\\u6587\\u6863\\u5206\\u6bb5\\u5217\\u8868',\n          condition: 'OR',\n          node_data: {\n            strategy: 'first_non_null',\n            is_result: true,\n            group_list: [\n              {\n                id: 'kU2zR8yRzNIt3RXKHmJtL',\n                field: 'Segmented_List',\n                label: '\\u6587\\u6863\\u5206\\u6bb5\\u5217\\u8868',\n                variable_list: [\n                  {\n                    v_id: 'N59Lo_VyRKuYASHaKN6g0',\n                    variable: ['9018d6b6-be6e-420b-9a0d-7226fd789398', 'paragraph_list'],\n                  },\n                  {\n                    v_id: 'IRQkWPB7THGXlkSBCWmkY',\n                    variable: ['a1d0fa5d-4779-4364-8b54-7eff69bd1ec4', 'paragraph_list'],\n                  },\n                  {\n                    v_id: 'jGrIINd-6O6UEzNmZvFap',\n                    variable: ['dddebd93-ea1a-4880-8a52-ea8112f7e769', 'paragraph_list'],\n                  },\n                ],\n              },\n            ],\n          },\n        },\n      },\n      {\n        x: 710,\n        y: 2190,\n        id: '08503db8-f2e3-4eb3-96f7-957f30a6da6e',\n        type: 'data-source-web-node',\n        properties: {\n          kind: 'data-source',\n          config: {\n            fields: [\n              {\n                label: '\\u6587\\u6863\\u5217\\u8868',\n                value: 'document_list',\n              },\n            ],\n          },\n          height: 292,\n          showNode: true,\n          stepName: 'Web\\u7ad9\\u70b9',\n        },\n      },\n    ],\n  },\n}\n"
  },
  {
    "path": "ui/src/workflow/common/validate.ts",
    "content": "import { WorkflowKind } from './../../enums/application'\nimport { WorkflowType, WorkflowMode } from '@/enums/application'\n\nimport { t } from '@/locales'\n\nconst end_nodes: Array<string> = [\n  WorkflowType.AiChat,\n  WorkflowType.Reply,\n  WorkflowType.ToolLib,\n  WorkflowType.ToolLibCustom,\n  WorkflowType.ImageUnderstandNode,\n  WorkflowType.Application,\n  WorkflowType.SpeechToTextNode,\n  WorkflowType.TextToSpeechNode,\n  WorkflowType.ImageGenerateNode,\n  WorkflowType.ImageToVideoGenerateNode,\n  WorkflowType.TextToVideoGenerateNode,\n  WorkflowType.LoopBodyNode,\n  WorkflowType.LoopNode,\n  WorkflowType.LoopBreakNode,\n  WorkflowType.VideoUnderstandNode,\n  WorkflowType.VariableAssignNode,\n  WorkflowType.KnowledgeWriteNode,\n]\n\nconst loop_end_nodes: Array<string> = [\n  WorkflowType.AiChat,\n  WorkflowType.Reply,\n  WorkflowType.ToolLib,\n  WorkflowType.ToolLibCustom,\n  WorkflowType.ImageUnderstandNode,\n  WorkflowType.VideoUnderstandNode,\n  WorkflowType.Application,\n  WorkflowType.SpeechToTextNode,\n  WorkflowType.TextToSpeechNode,\n  WorkflowType.ImageGenerateNode,\n  WorkflowType.ImageToVideoGenerateNode,\n  WorkflowType.TextToVideoGenerateNode,\n  WorkflowType.LoopBodyNode,\n  WorkflowType.LoopNode,\n  WorkflowType.LoopBreakNode,\n  WorkflowType.VariableAssignNode,\n]\nconst end_nodes_dict = {\n  [WorkflowMode.Application]: end_nodes,\n  [WorkflowMode.Knowledge]: [WorkflowType.KnowledgeWriteNode],\n  [WorkflowMode.ApplicationLoop]: loop_end_nodes,\n  [WorkflowMode.KnowledgeLoop]: [...loop_end_nodes, WorkflowType.KnowledgeWriteNode],\n  [WorkflowMode.Tool]: end_nodes,\n  [WorkflowMode.ToolLoop]: loop_end_nodes,\n}\n\nexport class WorkFlowInstance {\n  nodes\n  edges\n  workFlowNodes: Array<any>\n  workflowModel: WorkflowMode\n\n  constructor(workflow: { nodes: Array<any>; edges: Array<any> }, workflowModel?: WorkflowMode) {\n    this.nodes = workflow.nodes\n    this.edges = workflow.edges\n    this.workFlowNodes = []\n    this.workflowModel = workflowModel ? workflowModel : WorkflowMode.Application\n  }\n\n  /**\n   * 校验开始节点\n   */\n  is_valid_start_node() {\n    const start_node_list = this.nodes.filter((item) =>\n      [WorkflowType.Start, WorkflowType.LoopStartNode].includes(item.id),\n    )\n    if (start_node_list.length == 0) {\n      throw t('workflow.validate.startNodeRequired')\n    } else if (start_node_list.length > 1) {\n      throw t('workflow.validate.startNodeOnly')\n    }\n  }\n\n  /**\n   * 校验基本信息节点\n   */\n  is_valid_base_node() {\n    const start_node_list = this.nodes.filter((item) => item.id === WorkflowType.Base)\n    if (start_node_list.length == 0) {\n      throw t('workflow.validate.baseNodeRequired')\n    } else if (start_node_list.length > 1) {\n      throw t('workflow.validate.baseNodeOnly')\n    }\n  }\n\n  /**\n   * 校验节点\n   */\n  is_valid() {\n    this.is_valid_start_node()\n    this.is_valid_base_node()\n    this.is_valid_work_flow()\n    this.is_valid_nodes()\n  }\n\n  is_loop_valid() {\n    this.is_valid_start_node()\n    this.is_valid_work_flow()\n    this.is_valid_nodes()\n  }\n\n  /**\n   * 获取开始节点\n   * @returns\n   */\n  get_start_node() {\n    const start_node_list = this.nodes.filter((item) =>\n      [WorkflowType.Start, WorkflowType.LoopStartNode, WorkflowType.ToolStartNode].includes(\n        item.id,\n      ),\n    )\n    return start_node_list[0]\n  }\n\n  /**\n   * 获取基本节点\n   * @returns 基本节点\n   */\n  get_base_node() {\n    const base_node_list = this.nodes.filter((item) => item.id === WorkflowType.Base)\n    return base_node_list[0]\n  }\n\n  exist_break_node() {\n    return this.nodes.some((item) => item.type === WorkflowType.LoopBreakNode)\n  }\n\n  /**\n   * 校验工作流\n   * @param up_node 上一个节点\n   */\n  _is_valid_work_flow(up_node?: any) {\n    if (!up_node) {\n      up_node = this.get_start_node()\n    }\n    this.workFlowNodes.push(up_node)\n    this.is_valid_node(up_node)\n    const next_nodes = this.get_next_nodes(up_node)\n    for (const next_node of next_nodes) {\n      this._is_valid_work_flow(next_node)\n    }\n  }\n\n  is_valid_work_flow() {\n    this.workFlowNodes = []\n    this._is_valid_work_flow()\n    const notInWorkFlowNodes = this.nodes\n      .filter(\n        (node: any) =>\n          node.id !== WorkflowType.Start &&\n          node.id !== WorkflowType.Base &&\n          node.type !== WorkflowType.ToolBaseNode &&\n          node.type !== WorkflowType.ToolStartNode,\n      )\n      .filter((node) => !this.workFlowNodes.includes(node))\n    if (notInWorkFlowNodes.length > 0) {\n      throw `${t('workflow.validate.notInWorkFlowNode')}:${notInWorkFlowNodes.map((node) => node.properties.stepName).join('，')}`\n    }\n    this.workFlowNodes = []\n  }\n\n  /**\n   * 获取流程下一个节点列表\n   * @param node 节点\n   * @returns 节点列表\n   */\n  get_next_nodes(node: any) {\n    const edge_list = this.edges.filter((edge) => edge.sourceNodeId == node.id)\n    const node_list = edge_list\n      .map((edge) => this.nodes.filter((node) => node.id == edge.targetNodeId))\n      .reduce((x, y) => [...x, ...y], [])\n    const end = end_nodes_dict[this.workflowModel]\n    if (node_list.length == 0 && !end.includes(node.type)) {\n      throw t('workflow.validate.noNextNode')\n    }\n    return node_list\n  }\n\n  is_valid_nodes() {\n    for (const node of this.nodes) {\n      if (\n        node.type !== WorkflowType.Base &&\n        node.type !== WorkflowType.Start &&\n        node.type !== WorkflowType.LoopStartNode &&\n        node.type !== WorkflowType.ToolBaseNode &&\n        node.type !== WorkflowType.ToolStartNode\n      ) {\n        if (!this.edges.some((edge) => edge.targetNodeId === node.id)) {\n          throw `${t('workflow.validate.notInWorkFlowNode')}:${node.properties.stepName}`\n        }\n      }\n    }\n  }\n\n  /**\n   * 校验节点\n   * @param node 节点\n   */\n  is_valid_node(node: any) {\n    if (node.properties.status && node.properties.status === 500) {\n      throw `${node.properties.stepName} ${t('workflow.validate.nodeUnavailable')}`\n    }\n    if (node.type === WorkflowType.Condition) {\n      const branch_list = node.properties.node_data.branch\n      for (const branch of branch_list) {\n        const source_anchor_id = `${node.id}_${branch.id}_right`\n        const edge_list = this.edges.filter((edge) => edge.sourceAnchorId == source_anchor_id)\n        if (edge_list.length == 0) {\n          throw `${node.properties.stepName} ${t('workflow.validate.needConnect1')}${branch.type}${t('workflow.validate.needConnect2')}`\n        }\n      }\n    } else {\n      const edge_list = this.edges.filter((edge) => edge.sourceNodeId == node.id)\n      const end = end_nodes_dict[this.workflowModel]\n      if (edge_list.length == 0 && !end.includes(node.type)) {\n        throw `${node.properties.stepName} ${t('workflow.validate.cannotEndNode')}`\n      }\n    }\n    if (node.properties.status && node.properties.status !== 200) {\n      throw `${node.properties.stepName} ${t('workflow.validate.nodeUnavailable')}`\n    }\n  }\n}\nexport class ToolWorkFlowInstance extends WorkFlowInstance {\n  is_valid_start_node() {\n    const start_node_list = this.nodes.filter((item) => item.type === WorkflowType.ToolStartNode)\n\n    if (start_node_list.length == 0) {\n      throw t('workflow.validate.startNodeRequired')\n    }\n  }\n  /**\n   * 校验基本信息节点\n   */\n  is_valid_base_node() {\n    const base_node_list = this.nodes.filter((item) => item.id === WorkflowType.ToolBaseNode)\n    if (base_node_list.length == 0) {\n      throw t('workflow.validate.baseNodeRequired')\n    } else if (base_node_list.length > 1) {\n      throw t('workflow.validate.baseNodeOnly')\n    }\n  }\n  get_start_nodes() {\n    return this.nodes.filter((item) => item.type === WorkflowType.ToolStartNode)\n  }\n  get_base_node() {\n    const base_node_list = this.nodes.filter((item) => item.id === WorkflowType.ToolBaseNode)\n    return base_node_list[0]\n  }\n}\nexport class KnowledgeWorkFlowInstance extends WorkFlowInstance {\n  is_valid_start_node() {\n    const start_node_list =\n      this.workflowModel == WorkflowMode.Knowledge\n        ? this.nodes.filter((item) => item.properties.kind === WorkflowKind.DataSource)\n        : this.nodes.filter((item) => item.type === WorkflowType.LoopStartNode)\n\n    if (start_node_list.length == 0) {\n      throw t('workflow.validate.startNodeRequired')\n    }\n  }\n  /**\n   * 校验基本信息节点\n   */\n  is_valid_base_node() {\n    const base_node_list = this.nodes.filter((item) => item.id === WorkflowType.KnowledgeBase)\n    if (base_node_list.length == 0) {\n      throw t('workflow.validate.baseNodeRequired')\n    } else if (base_node_list.length > 1) {\n      throw t('workflow.validate.baseNodeOnly')\n    }\n  }\n\n  is_valid_work_flow() {\n    this.workFlowNodes = []\n    const start_node_list = this.get_start_nodes()\n    start_node_list.forEach((n) => {\n      this._is_valid_work_flow(n)\n    })\n\n    const notInWorkFlowNodes = this.nodes\n      .filter(\n        (node: any) =>\n          node.id !== WorkflowType.KnowledgeBase &&\n          node.type !== WorkflowType.LoopStartNode &&\n          node.properties.kind !== WorkflowKind.DataSource,\n      )\n      .filter((node) => !this.workFlowNodes.includes(node))\n    if (notInWorkFlowNodes.length > 0) {\n      throw `${t('workflow.validate.notInWorkFlowNode')}:${notInWorkFlowNodes.map((node) => node.properties.stepName).join('，')}`\n    }\n    this.workFlowNodes = []\n  }\n\n  is_valid_nodes() {\n    for (const node of this.nodes) {\n      if (\n        node.type !== WorkflowType.KnowledgeBase &&\n        node.type !== WorkflowType.LoopStartNode &&\n        node.properties.kind !== WorkflowKind.DataSource\n      ) {\n        if (!this.edges.some((edge) => edge.targetNodeId === node.id)) {\n          throw `${t('workflow.validate.notInWorkFlowNode')}:${node.properties.stepName}`\n        }\n      }\n    }\n  }\n  get_start_nodes() {\n    if (this.workflowModel == WorkflowMode.Knowledge) {\n      return this.nodes.filter((item) => item.properties.kind === WorkflowKind.DataSource)\n    } else {\n      return this.nodes.filter((item) => item.type === WorkflowType.LoopStartNode)\n    }\n  }\n  get_end_nodes() {\n    const start_node_list = this.get_start_nodes()\n    return start_node_list.flatMap((n) => {\n      return this._get_end_nodes(n, [])\n    })\n  }\n  _get_end_nodes(startNode: any, value: Array<any>) {\n    const next = this.get_next_nodes(startNode)\n    if (next.length == 0) {\n      value.push(startNode)\n    } else {\n      next.forEach((n) => {\n        this._get_end_nodes(n, value)\n      })\n    }\n    return value\n  }\n\n  /**\n   * 获取流程下一个节点列表\n   * @param node 节点\n   * @returns 节点列表\n   */\n  get_next_nodes(node: any) {\n    const edge_list = this.edges.filter((edge) => edge.sourceNodeId == node.id)\n    const node_list = edge_list\n      .map((edge) => this.nodes.filter((node) => node.id == edge.targetNodeId))\n      .reduce((x, y) => [...x, ...y], [])\n\n    return node_list\n  }\n\n  /**\n   * 校验节点\n   * @param node 节点\n   */\n  is_valid_node(node: any) {\n    if (node.properties.status && node.properties.status === 500) {\n      throw `${node.properties.stepName} ${t('workflow.validate.nodeUnavailable')}`\n    }\n    if (node.type === WorkflowType.Condition) {\n      const branch_list = node.properties.node_data.branch\n      for (const branch of branch_list) {\n        const source_anchor_id = `${node.id}_${branch.id}_right`\n        const edge_list = this.edges.filter((edge) => edge.sourceAnchorId == source_anchor_id)\n        if (edge_list.length == 0) {\n          throw `${node.properties.stepName} ${t('workflow.validate.needConnect1')}${branch.type}${t('workflow.validate.needConnect2')}`\n        }\n      }\n    } else {\n      const edge_list = this.edges.filter((edge) => edge.sourceNodeId == node.id)\n      const end = end_nodes_dict[this.workflowModel]\n      if (this.workflowModel == WorkflowMode.KnowledgeLoop) {\n        if (edge_list.length == 0 && !end.includes(node.type)) {\n          throw `${node.properties.stepName} ${t('workflow.validate.cannotEndNode')}`\n        }\n        return\n      }\n      if (edge_list.length == 0 && !end.includes(node.type)) {\n        if (node.type == WorkflowType.LoopNode) {\n          if (node.properties.node_data.loop_body) {\n            const end_nodes = new KnowledgeWorkFlowInstance(\n              node.properties.node_data.loop_body,\n              WorkflowMode.KnowledgeLoop,\n            ).get_end_nodes()\n            if (!end_nodes.every((n) => end.includes(n.type))) {\n              throw `${node.properties.stepName} ${t('workflow.validate.cannotEndNode')}`\n            }\n          }\n        } else {\n          throw `${node.properties.stepName} ${t('workflow.validate.cannotEndNode')}`\n        }\n      }\n    }\n    if (node.properties.status && node.properties.status !== 200) {\n      throw `${node.properties.stepName} ${t('workflow.validate.nodeUnavailable')}`\n    }\n  }\n}\n"
  },
  {
    "path": "ui/src/workflow/icons/ai-chat-node-icon.vue",
    "content": "<template>\n  <el-avatar class=\"avatar-gradient\" shape=\"square\">\n    <img src=\"@/assets/workflow/icon_robot.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/application-node-icon.vue",
    "content": "<template>\n  <el-avatar\n    v-if=\"isAppIcon(item?.icon)\"\n    shape=\"square\"\n    :size=\"size || 32\"\n    style=\"background: none\"\n    class=\"mr-8\"\n  >\n    <img :src=\"item?.icon\" alt=\"\" />\n  </el-avatar>\n  <LogoIcon v-else :height=\"`${size}px`\" />\n</template>\n<script setup lang=\"ts\">\nimport { isAppIcon } from '@/utils/common'\nconst props = defineProps<{\n  item: {\n    name: string\n    icon: string\n  }\n  size?: string | number\n}>()\n</script>\n"
  },
  {
    "path": "ui/src/workflow/icons/base-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-orange\">\n    <img src=\"@/assets/workflow/icon_hi.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/chat-icon.vue",
    "content": "<template>\n  <img src=\"@/assets/workflow/icon_chat_color.svg\" style=\"width: 18px\" alt=\"\" />\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/condition-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #14C0FF;\">\n    <img src=\"@/assets/workflow/icon_condition.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/data-source-local-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: none\">\n    <img src=\"@/assets/workflow/icon_data-source-local.svg\" style=\"width: 100%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/data-source-web-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #7F3BF5\">\n    <img src=\"@/assets/knowledge/icon_web.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/document-extract-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-blue\">\n    <img src=\"@/assets/workflow/icon_docs.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/document-split-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-blue\">\n    <img src=\"@/assets/workflow/icon_document-split.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/form-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #34c724\">\n    <img src=\"@/assets/workflow/icon_form.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/global-icon.vue",
    "content": "<template>\n  <img src=\"@/assets/workflow/icon_globe_color.svg\" style=\"width: 18px\" alt=\"\" />\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/image-generate-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #14c0ff\">\n    <img src=\"@/assets/workflow/icon_text-image.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/image-to-video-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\">\n    <img src=\"@/assets/workflow/icon_image_to_video.svg\" style=\"width: 100%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/image-understand-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\"  style=\"background: #14c0ff\">\n    <img src=\"@/assets/workflow/icon_image.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/intent-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #7F3BF5\">\n    <img src=\"@/assets/workflow/icon_intent.svg\" style=\"width: 100%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/knowledge-base-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-orange\">\n    <img src=\"@/assets/workflow/icon_knowledge-base.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/knowledge-write-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\">\n    <img src=\"@/assets/workflow/icon_knowledge-write.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/loop-break-node-icon.vue",
    "content": "<template>\n  <el-avatar class=\"avatar-green\" shape=\"square\">\n    <img src=\"@/assets/workflow/icon_loop_break.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/loop-continue-node-icon.vue",
    "content": "<template>\n  <el-avatar class=\"avatar-green\" shape=\"square\">\n    <img src=\"@/assets/workflow/icon_loop.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/loop-icon.vue",
    "content": "<template>\n  <img src=\"@/assets/workflow/icon_chat_color.svg\" style=\"width: 18px\" alt=\"\" />\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/loop-node-icon.vue",
    "content": "<template>\n  <el-avatar class=\"avatar-green\" shape=\"square\">\n    <img src=\"@/assets/workflow/icon_loop.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/loop-start-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #D136D1;\">\n    <img src=\"@/assets/workflow/icon_start.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/mcp-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\">\n    <img src=\"@/assets/tool/icon_mcp.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/output-icon.vue",
    "content": "<template>\n  <img src=\"@/assets/workflow/icon_globe_color.svg\" style=\"width: 18px\" alt=\"\" />\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/parameter-extraction-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-blue\">\n    <img src=\"@/assets/workflow/icon_parameter_extraction.svg\" style=\"width: 100%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/question-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #34C724\">\n    <img src=\"@/assets/workflow/icon_setting.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/reply-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\"  class=\"avatar-orange\">\n    <img src=\"@/assets/workflow/icon_reply.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/reranker-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #7F3BF5\">\n    <img src=\"@/assets/workflow/icon_reranker.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/search-document-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-blue\">\n    <img src=\"@/assets/workflow/icon_doc-search.svg\" style=\"width: 58%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/search-knowledge-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-blue\">\n    <img src=\"@/assets/knowledge/icon_document.svg\" style=\"width: 58%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/speech-to-text-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\"  class=\"avatar-orange\">\n    <img src=\"@/assets/workflow/icon_speech_to_text.svg\" style=\"width: 70%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/start-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #D136D1;\">\n    <img src=\"@/assets/workflow/icon_start.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/text-to-speech-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\"  class=\"avatar-orange\">\n    <img src=\"@/assets/workflow/icon_text_to_speech.svg\" style=\"width: 70%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/text-to-video-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\">\n    <img src=\"@/assets/workflow/icon_text_to_video.svg\" style=\"width: 100%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/tool-base-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-orange\">\n    <img src=\"@/assets/workflow/icon_hi.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/tool-lib-node-icon.vue",
    "content": "<template>\n  <el-avatar\n    v-if=\"isAppIcon(item?.icon)\"\n    shape=\"square\"\n    :size=\"32\"\n    style=\"background: none\"\n    class=\"mr-8\"\n  >\n    <img :src=\"item?.icon\" alt=\"\" />\n  </el-avatar>\n  <el-avatar v-else-if=\"item?.kind === 'data-source'\" class=\"avatar-purple\" shape=\"square\">\n    <img src=\"@/assets/tool/icon_datasource.svg\" style=\"width: 58%\" alt=\"\" />\n  </el-avatar>\n  <el-avatar v-else-if=\"item?.tool_type === 'DATA_SOURCE'\" class=\"avatar-purple\" shape=\"square\">\n    <img src=\"@/assets/tool/icon_datasource.svg\" style=\"width: 58%\" alt=\"\" />\n  </el-avatar>\n  <el-avatar v-else shape=\"square\" style=\"background: #34c724\">\n    <img src=\"@/assets/tool/icon_tool.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\">\nimport { isAppIcon } from '@/utils/common'\nconst props = defineProps<{\n  item?: {\n    name: string\n    icon: string\n    tool_type: string\n    kind?: string\n  }\n}>()\n</script>\n\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/tool-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #34c724\">\n    <img src=\"@/assets/tool/icon_tool_custom.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/tool-start-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #D136D1;\">\n    <img src=\"@/assets/workflow/icon_start.svg\" style=\"width: 75%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/tool-workflow-lib-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" style=\"background: #34c724\">\n    <img src=\"@/assets/tool/icon_tool_workflow.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/utils.ts",
    "content": "const icons: any = import.meta.glob('./**.vue', { eager: true })\nexport function iconComponent(name: string) {\n  const url = `./${name}.vue`\n  return icons[url]?.default || null\n}\n"
  },
  {
    "path": "ui/src/workflow/icons/variable-aggregation-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-blue\">\n    <img src=\"@/assets/workflow/icon_aggregation.svg\" style=\"width: 100%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/variable-assign-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-blue\">\n    <img src=\"@/assets/workflow/icon_assigner.svg\" style=\"width: 65%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/variable-splitting-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\" class=\"avatar-blue\">\n    <img src=\"@/assets/workflow/icon_variable_splitting.svg\" style=\"width: 100%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/icons/video-understand-node-icon.vue",
    "content": "<template>\n  <el-avatar shape=\"square\">\n    <img src=\"@/assets/workflow/icon_video.svg\" style=\"width: 100%\" alt=\"\" />\n  </el-avatar>\n</template>\n<script setup lang=\"ts\"></script>\n"
  },
  {
    "path": "ui/src/workflow/index.vue",
    "content": "<template>\n  <div className=\"workflow-app\" id=\"container\"></div>\n  <!-- 辅助工具栏 -->\n  <Control class=\"workflow-control\" v-if=\"lf\" :lf=\"lf\"></Control>\n  <TeleportContainer :flow-id=\"flowId\" />\n  <NodeSearch :lf=\"lf\" ref=\"nodeSearchRef\"></NodeSearch>\n</template>\n<script setup lang=\"ts\">\nimport LogicFlow from '@logicflow/core'\nimport { ref, onMounted, onUnmounted, inject } from 'vue'\nimport AppEdge from './common/edge'\nimport loopEdge from './common/loopEdge'\nimport Control from './common/NodeControl.vue'\nimport { SelectionSelect } from '@logicflow/extension'\nimport '@logicflow/extension/lib/style/index.css'\nimport '@logicflow/core/dist/style/index.css'\nimport { initDefaultShortcut } from '@/workflow/common/shortcut'\nimport Dagre from '@/workflow/plugins/dagre'\nimport { disconnectAll, getTeleport } from '@/workflow/common/teleport'\nimport { WorkflowMode } from '@/enums/application'\n\nimport NodeSearch from '@/workflow/common/NodeSearch.vue'\nconst nodes: any = import.meta.glob('./nodes/**/index.ts', { eager: true })\nconst workflow_mode = inject('workflowMode') || WorkflowMode.Application\nconst loop_workflow_mode = inject('loopWorkflowMode') || WorkflowMode.ApplicationLoop\nconst nodeSearchRef = ref<InstanceType<typeof NodeSearch>>()\ndefineOptions({ name: 'WorkFlow' })\nconst TeleportContainer = getTeleport()\nconst flowId = ref('')\ntype ShapeItem = {\n  type?: string\n  text?: string\n  icon?: string\n  label?: string\n  className?: string\n  disabled?: boolean\n  properties?: Record<string, any>\n  callback?: (lf: LogicFlow, container?: HTMLElement) => void\n}\n\nconst props = defineProps({\n  data: Object || null,\n})\n\nconst lf = ref()\nonMounted(() => {\n  renderGraphData()\n})\nonUnmounted(() => {\n  disconnectAll()\n})\nconst render = (data: any) => {\n  lf.value.render(data)\n}\n\nconst renderGraphData = (data?: any) => {\n  const container: any = document.querySelector('#container')\n  if (container) {\n    lf.value = new LogicFlow({\n      plugins: [Dagre, SelectionSelect],\n      textEdit: false,\n      adjustEdge: false,\n      adjustEdgeStartAndEnd: false,\n      background: {\n        backgroundColor: '#f5f6f7',\n      },\n      grid: {\n        size: 10,\n        type: 'dot',\n        config: {\n          color: '#DEE0E3',\n          thickness: 1,\n        },\n      },\n      keyboard: {\n        enabled: true,\n      },\n      isSilentMode: false,\n      container: container,\n    })\n    lf.value.setTheme({\n      bezier: {\n        stroke: '#afafaf',\n        strokeWidth: 1,\n      },\n    })\n    lf.value.on('graph:rendered', () => {\n      flowId.value = lf.value.graphModel.flowId\n    })\n    lf.value.on('node:delete', () => {\n      nodeSearchRef.value?.reSearch()\n    })\n    initDefaultShortcut(lf.value, lf.value.graphModel)\n    lf.value.batchRegister([\n      ...Object.keys(nodes).map((key) => nodes[key].default),\n      AppEdge,\n      loopEdge,\n    ])\n\n    lf.value.setDefaultEdgeType('app-edge')\n\n    lf.value.render(data ? data : {})\n    lf.value.graphModel.get_provide = (node: any, graph: any) => {\n      return {\n        getNode: () => node,\n        getGraph: () => graph,\n        workflowMode: workflow_mode,\n        loopWorkflowMode: loop_workflow_mode,\n      }\n    }\n    lf.value.graphModel.eventCenter.on('delete_edge', (id_list: Array<string>) => {\n      id_list.forEach((id: string) => {\n        lf.value.deleteEdge(id)\n      })\n    })\n    lf.value.graphModel.eventCenter.on('anchor:drop', (data: any) => {\n      // 清除当前节点下面的子节点的所有缓存\n      data.nodeModel.clear_next_node_field(false)\n    })\n\n    setTimeout(() => {\n      if (lf.value.graphModel?.nodes.length > 1) {\n        lf.value?.fitView()\n      } else {\n        lf.value?.translateCenter()\n      }\n    }, 500)\n  }\n}\n\nconst validate = () => {\n  return Promise.all(lf.value.graphModel.nodes.map((element: any) => element?.validate?.()))\n}\nconst getGraphData = () => {\n  const graph_data = lf.value.getGraphData()\n  graph_data.nodes.forEach((node: any) => {\n    if (node.type === 'loop-body-node') {\n      const node_model = lf.value.getNodeModelById(node.id)\n      node_model.set_loop_body()\n    }\n  })\n  const _graph_data = lf.value.getGraphData()\n  _graph_data.nodes = _graph_data.nodes.filter((node: any) => node.type !== 'loop-body-node')\n  _graph_data.edges = graph_data.edges.filter((node: any) => node.type !== 'loop-edge')\n  return _graph_data\n}\n\nconst onmousedown = (shapeItem: ShapeItem) => {\n  if (shapeItem.type) {\n    lf.value.dnd.startDrag({\n      type: shapeItem.type,\n      properties: { ...shapeItem.properties },\n    })\n  }\n\n  if (shapeItem.callback) {\n    shapeItem.callback(lf.value)\n  }\n}\nconst addNode = (shapeItem: ShapeItem) => {\n  lf.value.clearSelectElements()\n  const { virtualRectCenterPositionX, virtualRectCenterPositionY } =\n    lf.value.graphModel.getVirtualRectSize()\n  const newNode = lf.value.graphModel.addNode({\n    type: shapeItem.type,\n    properties: shapeItem.properties,\n    x: virtualRectCenterPositionX,\n    y: virtualRectCenterPositionY - lf.value.graphModel.height / 2,\n  })\n  newNode.isSelected = true\n  newNode.isHovered = true\n  lf.value.toFront(newNode.id)\n}\n\nconst clearGraphData = () => {\n  return lf.value.clearData()\n}\n\ndefineExpose({\n  onmousedown,\n  validate,\n  getGraphData,\n  addNode,\n  clearGraphData,\n  renderGraphData,\n  render,\n})\n</script>\n<style lang=\"scss\">\n.workflow-app {\n  width: 100%;\n  height: 100%;\n  position: relative;\n}\n.workflow-control {\n  position: absolute;\n  bottom: 24px;\n  left: 24px;\n  z-index: 2;\n}\n.lf-drag-able {\n  cursor: pointer;\n}\n</style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/ai-chat-node/index.ts",
    "content": "import ChatNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass ChatNode extends AppNode {\n  constructor(props: any) {\n    super(props, ChatNodeVue)\n  }\n}\nexport default {\n  type: 'ai-chat-node',\n  model: AppNodeModel,\n  view: ChatNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/ai-chat-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        @submit.prevent\n        :model=\"chat_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"aiChatNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('views.application.form.aiModel.label')\"\n          :prop=\"chat_data.model_id_type === 'reference' ? 'model_id_reference' : 'model_id'\"\n          :rules=\"{\n            required: true,\n            message:\n              chat_data.model_id_type === 'reference'\n                ? $t('workflow.variable.placeholder')\n                : $t('views.application.form.aiModel.placeholder'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ $t('views.application.form.aiModel.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-select\n                v-model=\"chat_data.model_id_type\"\n                :teleported=\"false\"\n                size=\"small\"\n                style=\"width: 85px\"\n                @change=\"chat_data.model_id_reference = []\"\n              >\n                <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"reference\" />\n                <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n              </el-select>\n            </div>\n          </template>\n          <div class=\"flex-between w-full\" v-if=\"chat_data.model_id_type !== 'reference'\">\n            <div>\n              <ModelSelect\n                @change=\"model_change\"\n                @wheel=\"wheel\"\n                :teleported=\"false\"\n                v-model=\"chat_data.model_id\"\n                :placeholder=\"$t('views.application.form.aiModel.placeholder')\"\n                :options=\"modelOptions\"\n                @submitModel=\"getSelectModel\"\n                showFooter\n                :model-type=\"'LLM'\"\n              ></ModelSelect>\n            </div>\n            <div class=\"ml-8\">\n              <el-button\n                :disabled=\"!chat_data.model_id\"\n                type=\"primary\"\n                link\n                @click=\"openAIParamSettingDialog(chat_data.model_id)\"\n                @refreshForm=\"refreshParam\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </div>\n          <NodeCascader\n            v-else\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.variable.placeholder')\"\n            v-model=\"chat_data.model_id_reference\"\n          />\n        </el-form-item>\n\n        <el-form-item>\n          <template #label>\n            <div class=\"flex-between\">\n              <div class=\"flex align-center\">\n                <span>{{ $t('views.application.form.roleSettings.label') }}</span>\n                <el-tooltip\n                  effect=\"dark\"\n                  :content=\"$t('views.application.form.roleSettings.tooltip')\"\n                  placement=\"right\"\n                >\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon ml-4\"></AppIcon>\n                </el-tooltip>\n              </div>\n\n              <el-button\n                type=\"primary\"\n                link\n                @click=\"openGeneratePromptDialog(chat_data.model_id)\"\n                :disabled=\"chat_data.model_id_type === 'reference' || !chat_data.model_id\"\n              >\n                <AppIcon iconName=\"app-generate-star\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <MdEditorMagnify\n            :title=\"$t('views.application.form.roleSettings.label')\"\n            v-model=\"chat_data.system\"\n            style=\"height: 100px\"\n            @submitDialog=\"submitSystemDialog\"\n            :placeholder=\"`${t('workflow.SystemPromptPlaceholder')}{{${t('workflow.nodes.startNode.label')}.question}}`\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('views.application.form.prompt.label')\"\n          prop=\"prompt\"\n          :rules=\"{\n            required: true,\n            message: $t('views.application.form.prompt.requiredMessage'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span\n                  >{{ $t('views.application.form.prompt.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>{{ $t('views.application.form.prompt.tooltip') }} </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            @wheel=\"wheel\"\n            :title=\"$t('views.application.form.prompt.label')\"\n            v-model=\"chat_data.prompt\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitDialog\"\n            :placeholder=\"`${t('workflow.UserPromptPlaceholder')}{{${t('workflow.nodes.startNode.label')}.question}}`\"\n          />\n        </el-form-item>\n        <el-form-item\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <div>{{ $t('views.application.form.historyRecord.label') }}</div>\n              <el-select v-model=\"chat_data.dialogue_type\" type=\"small\" style=\"width: 100px\">\n                <el-option :label=\"$t('workflow.node')\" value=\"NODE\" />\n                <el-option :label=\"$t('workflow.workflow')\" value=\"WORKFLOW\" />\n              </el-select>\n            </div>\n          </template>\n          <el-input-number\n            v-model=\"chat_data.dialogue_number\"\n            :min=\"0\"\n            :value-on-clear=\"0\"\n            controls-position=\"right\"\n            class=\"w-full\"\n            :step=\"1\"\n            :step-strictly=\"true\"\n          />\n        </el-form-item>\n\n        <div class=\"mb-8 mt-12 flex-between\">\n          <span class=\"mr-4 lighter\">\n            {{ $t('views.tool.skill.title') }}\n          </span>\n          <div class=\"flex\">\n            <el-checkbox\n              v-model=\"chat_data.mcp_output_enable\"\n              :label=\"$t('views.application.form.mcp_output_enable')\"\n            />\n          </div>\n        </div>\n        <el-card shadow=\"never\" style=\"--el-card-padding: 12px\" class=\"mb-12\">\n          <!-- MCP-->\n          <div>\n            <div class=\"flex-between mb-8\" @click=\"collapseData.MCP = !collapseData.MCP\">\n              <div class=\"flex align-center lighter cursor\">\n                <el-icon class=\"mr-8 arrow-icon\" :class=\"collapseData.MCP ? 'rotate-90' : ''\">\n                  <CaretRight /> </el-icon\n                >MCP\n                <span class=\"ml-4\" v-if=\"chat_data.mcp_tool_ids?.length\">\n                  ({{ chat_data.mcp_tool_ids?.length }})</span\n                >\n              </div>\n              <div class=\"flex\">\n                <el-button\n                  type=\"primary\"\n                  link\n                  @click=\"openMcpServersDialog\"\n                  @refreshForm=\"refreshParam\"\n                >\n                  <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                </el-button>\n              </div>\n            </div>\n            <div class=\"w-full mb-16\" v-if=\"chat_data.mcp_tool_ids?.length > 0 && collapseData.MCP\">\n              <template v-for=\"(item, index) in chat_data.mcp_tool_ids\" :key=\"index\">\n                <div\n                  class=\"flex-between border border-r-6 white-bg mb-4\"\n                  style=\"padding: 5px 8px\"\n                  v-if=\"relatedObject(mcpToolSelectOptions, item, 'id')\"\n                >\n                  <div class=\"flex align-center\" style=\"line-height: 20px\">\n                    <el-avatar\n                      v-if=\"relatedObject(mcpToolSelectOptions, item, 'id')?.icon\"\n                      shape=\"square\"\n                      :size=\"20\"\n                      style=\"background: none\"\n                      class=\"mr-8\"\n                    >\n                      <img\n                        :src=\"resetUrl(relatedObject(mcpToolSelectOptions, item, 'id')?.icon)\"\n                        alt=\"\"\n                      />\n                    </el-avatar>\n                    <ToolIcon v-else type=\"MCP\" class=\"mr-8\" :size=\"20\" />\n\n                    <div\n                      class=\"ellipsis\"\n                      :title=\"relatedObject(mcpToolSelectOptions, item, 'id')?.name\"\n                    >\n                      {{\n                        relatedObject(mcpToolSelectOptions, item, 'id')?.name ||\n                        $t('common.custom') + ' MCP'\n                      }}\n                    </div>\n                  </div>\n                  <el-button text @click=\"removeMcpTool(item)\">\n                    <el-icon><Close /></el-icon>\n                  </el-button>\n                </div>\n              </template>\n            </div>\n            <div\n              v-if=\"chat_data.mcp_servers && chat_data.mcp_servers.length > 0 && collapseData.MCP\"\n              class=\"flex-between border border-r-6 white-bg mb-16\"\n              style=\"padding: 5px 8px\"\n            >\n              <div class=\"flex align-center\" style=\"line-height: 20px\">\n                <ToolIcon type=\"MCP\" class=\"mr-8\" :size=\"20\" />\n                <div class=\"ellipsis\">\n                  {{ $t('common.custom') + ' MCP' }}\n                </div>\n              </div>\n              <el-button text @click=\"chat_data.mcp_servers = ''\">\n                <el-icon><Close /></el-icon>\n              </el-button>\n            </div>\n          </div>\n\n          <!-- 工具       -->\n          <div>\n            <div class=\"flex-between mb-8\" @click=\"collapseData.tool = !collapseData.tool\">\n              <div class=\"flex align-center lighter cursor\">\n                <el-icon class=\"mr-8 arrow-icon\" :class=\"collapseData.tool ? 'rotate-90' : ''\">\n                  <CaretRight />\n                </el-icon>\n                {{ $t('views.tool.title') }}\n                <span class=\"ml-4\" v-if=\"chat_data.tool_ids?.length\">\n                  ({{ chat_data.tool_ids?.length }})</span\n                >\n              </div>\n              <div class=\"flex\">\n                <el-button type=\"primary\" link @click=\"openToolDialog\" @refreshForm=\"refreshParam\">\n                  <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                </el-button>\n              </div>\n            </div>\n            <div class=\"w-full mb-16\" v-if=\"chat_data.tool_ids?.length > 0 && collapseData.tool\">\n              <template v-for=\"(item, index) in chat_data.tool_ids\" :key=\"index\">\n                <div class=\"flex-between border border-r-6 white-bg mb-4\" style=\"padding: 5px 8px\">\n                  <div class=\"flex align-center\" style=\"line-height: 20px\">\n                    <el-avatar\n                      v-if=\"relatedObject(toolSelectOptions, item, 'id')?.icon\"\n                      shape=\"square\"\n                      :size=\"20\"\n                      style=\"background: none\"\n                      class=\"mr-8\"\n                    >\n                      <img\n                        :src=\"resetUrl(relatedObject(toolSelectOptions, item, 'id')?.icon)\"\n                        alt=\"\"\n                      />\n                    </el-avatar>\n                    <ToolIcon v-else class=\"mr-8\" :size=\"20\" />\n\n                    <div\n                      class=\"ellipsis\"\n                      :title=\"relatedObject(toolSelectOptions, item, 'id')?.name\"\n                    >\n                      {{ relatedObject(toolSelectOptions, item, 'id')?.name }}\n                    </div>\n                  </div>\n                  <el-button text @click=\"removeTool(item)\">\n                    <el-icon><Close /></el-icon>\n                  </el-button>\n                </div>\n              </template>\n            </div>\n          </div>\n\n          <!-- 技能       -->\n          <div>\n            <div class=\"flex-between mb-8\" @click=\"collapseData.skill = !collapseData.skill\">\n              <div class=\"flex align-center lighter cursor\">\n                <el-icon class=\"mr-8 arrow-icon\" :class=\"collapseData.skill ? 'rotate-90' : ''\">\n                  <CaretRight />\n                </el-icon>\n                Skills\n                <span class=\"ml-4\" v-if=\"chat_data.skill_tool_ids?.length\">\n                  ({{ chat_data.skill_tool_ids?.length }})</span\n                >\n              </div>\n              <div class=\"flex\">\n                <el-button\n                  type=\"primary\"\n                  link\n                  @click=\"openSkillToolDialog\"\n                  @refreshForm=\"refreshParam\"\n                >\n                  <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                </el-button>\n              </div>\n            </div>\n            <div\n              class=\"w-full mb-16\"\n              v-if=\"chat_data.skill_tool_ids?.length > 0 && collapseData.skill\"\n            >\n              <template v-for=\"(item, index) in chat_data.skill_tool_ids\" :key=\"index\">\n                <div class=\"flex-between border border-r-6 white-bg mb-4\" style=\"padding: 5px 8px\">\n                  <div class=\"flex align-center\" style=\"line-height: 20px\">\n                    <el-avatar\n                      v-if=\"relatedObject(skillToolSelectOptions, item, 'id')?.icon\"\n                      shape=\"square\"\n                      :size=\"20\"\n                      style=\"background: none\"\n                      class=\"mr-8\"\n                    >\n                      <img\n                        :src=\"resetUrl(relatedObject(skillToolSelectOptions, item, 'id')?.icon)\"\n                        alt=\"\"\n                      />\n                    </el-avatar>\n                    <ToolIcon v-else class=\"mr-8\" :size=\"20\" type=\"SKILL\" />\n\n                    <div\n                      class=\"ellipsis\"\n                      :title=\"relatedObject(skillToolSelectOptions, item, 'id')?.name\"\n                    >\n                      {{ relatedObject(skillToolSelectOptions, item, 'id')?.name }}\n                    </div>\n                  </div>\n                  <el-button text @click=\"removeSkillTool(item)\">\n                    <el-icon><Close /></el-icon>\n                  </el-button>\n                </div>\n              </template>\n            </div>\n          </div>\n\n          <!-- 应用 没有共享应用，在共享知识库工作流不显示这个      -->\n          <div v-if=\"apiType !== 'systemShare'\">\n            <div class=\"flex-between\" @click=\"collapseData.agent = !collapseData.agent\">\n              <div class=\"flex align-center lighter cursor\">\n                <el-icon class=\"mr-8 arrow-icon\" :class=\"collapseData.agent ? 'rotate-90' : ''\">\n                  <CaretRight />\n                </el-icon>\n                {{ $t('views.application.title') }}\n                <span class=\"ml-4\" v-if=\"chat_data.application_ids?.length\">\n                  ({{ chat_data.application_ids?.length }})</span\n                >\n              </div>\n              <div class=\"flex\">\n                <el-button\n                  type=\"primary\"\n                  link\n                  @click=\"openApplicationDialog\"\n                  @refreshForm=\"refreshParam\"\n                >\n                  <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                </el-button>\n              </div>\n            </div>\n            <div class=\"w-full mt-8\" v-if=\"chat_data.application_ids?.length && collapseData.agent\">\n              <template v-for=\"(item, index) in chat_data.application_ids\" :key=\"index\">\n                <div class=\"flex-between border border-r-6 white-bg mb-4\" style=\"padding: 5px 8px\">\n                  <div class=\"flex align-center\" style=\"line-height: 20px\">\n                    <el-avatar\n                      v-if=\"relatedObject(applicationSelectOptions, item, 'id')?.icon\"\n                      shape=\"square\"\n                      :size=\"20\"\n                      style=\"background: none\"\n                      class=\"mr-8\"\n                    >\n                      <img\n                        :src=\"resetUrl(relatedObject(applicationSelectOptions, item, 'id')?.icon)\"\n                        alt=\"\"\n                      />\n                    </el-avatar>\n                    <AppIcon v-else class=\"mr-8\" :size=\"20\" />\n\n                    <div\n                      class=\"ellipsis\"\n                      :title=\"relatedObject(applicationSelectOptions, item, 'id')?.name\"\n                    >\n                      {{ relatedObject(applicationSelectOptions, item, 'id')?.name }}\n                    </div>\n                  </div>\n                  <el-button text @click=\"removeApplication(item)\">\n                    <el-icon><Close /></el-icon>\n                  </el-button>\n                </div>\n              </template>\n            </div>\n          </div>\n        </el-card>\n\n        <el-form-item @click.prevent>\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span>{{ $t('views.application.form.reasoningContent.label') }}</span>\n              </div>\n              <div>\n                <el-button\n                  type=\"primary\"\n                  link\n                  @click=\"openReasoningParamSettingDialog\"\n                  @refreshForm=\"refreshParam\"\n                  class=\"mr-4\"\n                  v-if=\"chat_data.model_setting.reasoning_content_enable\"\n                >\n                  <AppIcon iconName=\"app-setting\"></AppIcon>\n                </el-button>\n                <el-switch\n                  size=\"small\"\n                  v-model=\"chat_data.model_setting.reasoning_content_enable\"\n                />\n              </div>\n            </div>\n          </template>\n        </el-form-item>\n        <el-form-item\n          @click.prevent\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"chat_data.is_result\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n\n    <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshParam\" />\n    <GeneratePromptDialog @replace=\"replace\" ref=\"GeneratePromptDialogRef\" />\n    <ReasoningParamSettingDialog\n      ref=\"ReasoningParamSettingDialogRef\"\n      @refresh=\"submitReasoningDialog\"\n    />\n    <McpServersDialog ref=\"mcpServersDialogRef\" @refresh=\"submitMcpServersDialog\" />\n    <ToolDialog ref=\"toolDialogRef\" @refresh=\"submitToolDialog\" tool_type=\"CUSTOM\" />\n    <ToolDialog ref=\"skillToolDialogRef\" @refresh=\"submitSkillToolDialog\" tool_type=\"SKILL\" />\n    <ApplicationDialog ref=\"applicationDialogRef\" @refresh=\"submitApplicationDialog\" />\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { cloneDeep, set, groupBy } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormInstance } from 'element-plus'\nimport { ref, computed, onMounted, inject, reactive } from 'vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\nimport GeneratePromptDialog from '@/views/application/component/GeneratePromptDialog.vue'\nimport { t } from '@/locales'\nimport ReasoningParamSettingDialog from '@/views/application/component/ReasoningParamSettingDialog.vue'\nimport ToolDialog from '@/views/application/component/ToolDialog.vue'\nimport McpServersDialog from '@/views/application/component/McpServersDialog.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { useRoute } from 'vue-router'\n\nimport { resetUrl } from '@/utils/common'\nimport { relatedObject } from '@/utils/array.ts'\nimport { WorkflowMode } from '@/enums/application'\nimport ApplicationDialog from '@/views/application/component/ApplicationDialog.vue'\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nfunction submitSystemDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'system', val)\n}\n\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'prompt', val)\n}\n\nconst model_change = (model_id?: string) => {\n  if (model_id) {\n    AIModeParamSettingDialogRef.value?.reset_default(model_id, id)\n  } else {\n    refreshParam({})\n  }\n}\n\nconst defaultPrompt = `${t('workflow.nodes.aiChatNode.defaultPrompt')}：\n{{${t('workflow.nodes.searchKnowledgeNode.label')}.data}}\n${t('views.problem.title')}：\n{{${t('workflow.nodes.startNode.label')}.question}}`\n\nconst collapseData = reactive({\n  MCP: true,\n  tool: true,\n  skill: true,\n  agent: true,\n})\n\nconst form = {\n  model_id: '',\n  model_id_type: 'custom',\n  model_id_reference: [],\n  system: '',\n  prompt: defaultPrompt,\n  dialogue_number: 1,\n  is_result: true,\n  temperature: null,\n  max_tokens: null,\n  dialogue_type: 'WORKFLOW',\n  model_setting: {\n    reasoning_content_start: '<think>',\n    reasoning_content_end: '</think>',\n    reasoning_content_enable: false,\n  },\n}\n\nconst chat_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      if (!props.nodeModel.properties.node_data.model_setting) {\n        set(props.nodeModel.properties.node_data, 'model_setting', {\n          reasoning_content_start: '<think>',\n          reasoning_content_end: '</think>',\n          reasoning_content_enable: false,\n        })\n      }\n      if (!props.nodeModel.properties.node_data.model_id_type) {\n        set(props.nodeModel.properties.node_data, 'model_id_type', 'custom')\n      }\n      if (!props.nodeModel.properties.node_data.model_id_reference) {\n        set(props.nodeModel.properties.node_data, 'model_id_reference', [])\n      }\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\nconst props = defineProps<{ nodeModel: any }>()\n\nconst aiChatNodeFormRef = ref<FormInstance>()\n\nconst modelOptions = ref<any>(null)\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\nconst nodeCascaderRef = ref()\nconst ReasoningParamSettingDialogRef = ref<InstanceType<typeof ReasoningParamSettingDialog>>()\nconst validate = () => {\n  return aiChatNodeFormRef.value?.validate().catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst resource = getResourceDetail()\n\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'LLM',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'LLM',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nconst openAIParamSettingDialog = (modelId: string) => {\n  if (modelId) {\n    AIModeParamSettingDialogRef.value?.open(modelId, id, chat_data.value.model_params_setting)\n  }\n}\n\nconst GeneratePromptDialogRef = ref<InstanceType<typeof GeneratePromptDialog>>()\nconst openGeneratePromptDialog = (modelId: string) => {\n  if (modelId) {\n    GeneratePromptDialogRef.value?.open(modelId, id)\n  }\n}\nconst replace = (v: any) => {\n  set(props.nodeModel.properties.node_data, 'system', v)\n}\nconst openReasoningParamSettingDialog = () => {\n  ReasoningParamSettingDialogRef.value?.open(chat_data.value.model_setting)\n}\n\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'model_params_setting', data)\n}\n\nfunction submitReasoningDialog(val: any) {\n  let model_setting = cloneDeep(props.nodeModel.properties.node_data.model_setting)\n  model_setting = {\n    ...model_setting,\n    ...val,\n  }\n\n  set(props.nodeModel.properties.node_data, 'model_setting', model_setting)\n}\n\nconst mcpServersDialogRef = ref()\nfunction openMcpServersDialog() {\n  const config = {\n    mcp_servers: chat_data.value.mcp_servers,\n    mcp_tool_ids: chat_data.value.mcp_tool_ids,\n    mcp_source: chat_data.value.mcp_source,\n  }\n  mcpServersDialogRef.value.open(config, mcpToolSelectOptions.value)\n}\n\nfunction submitMcpServersDialog(config: any) {\n  set(props.nodeModel.properties.node_data, 'mcp_servers', config.mcp_servers)\n  set(props.nodeModel.properties.node_data, 'mcp_tool_ids', config.mcp_tool_ids)\n  set(props.nodeModel.properties.node_data, 'mcp_source', config.mcp_source)\n  collapseData.MCP = true\n}\n\nconst toolDialogRef = ref()\nfunction openToolDialog() {\n  toolDialogRef.value.open(chat_data.value.tool_ids)\n}\nfunction submitToolDialog(config: any) {\n  set(props.nodeModel.properties.node_data, 'tool_ids', config.tool_ids)\n  collapseData.tool = true\n}\nfunction removeTool(id: any) {\n  const list = props.nodeModel.properties.node_data.tool_ids.filter((v: any) => v !== id)\n  set(props.nodeModel.properties.node_data, 'tool_ids', list)\n}\nfunction removeMcpTool(id: any) {\n  const list = props.nodeModel.properties.node_data.mcp_tool_ids.filter((v: any) => v !== id)\n  set(props.nodeModel.properties.node_data, 'mcp_tool_ids', list)\n}\nfunction removeSkillTool(id: any) {\n  const list = props.nodeModel.properties.node_data.skill_tool_ids.filter((v: any) => v !== id)\n  set(props.nodeModel.properties.node_data, 'skill_tool_ids', list)\n}\n\nconst toolSelectOptions = ref<any[]>([])\nfunction getToolSelectOptions() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          scope: 'WORKSPACE',\n          tool_type: 'CUSTOM',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          scope: 'WORKSPACE',\n          tool_type: 'CUSTOM',\n        }\n\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getAllToolList(obj)\n    .then((res: any) => {\n      toolSelectOptions.value = [...res.data.shared_tools, ...res.data.tools].filter(\n        (item: any) => item.is_active,\n      )\n    })\n}\n\nconst mcpToolSelectOptions = ref<any[]>([])\nfunction getMcpToolSelectOptions() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          scope: 'WORKSPACE',\n          tool_type: 'MCP',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          scope: 'WORKSPACE',\n          tool_type: 'MCP',\n        }\n\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getAllToolList(obj)\n    .then((res: any) => {\n      mcpToolSelectOptions.value = [...res.data.shared_tools, ...res.data.tools].filter(\n        (item: any) => item.is_active,\n      )\n    })\n}\n\nconst applicationSelectOptions = ref<any[]>([])\nfunction getApplicationSelectOptions() {\n  ;(apiType.value === 'systemShare'\n    ? Promise.resolve({ data: [] })\n    : loadSharedApi({ type: 'application', systemType: apiType.value }).getAllApplication({\n        folder_id: resource.value?.workspace_id,\n      })\n  ).then((res: any) => {\n    applicationSelectOptions.value = res.data.filter((item: any) => item.is_publish)\n  })\n}\n\nconst applicationDialogRef = ref()\nfunction openApplicationDialog() {\n  applicationDialogRef.value.open(props.nodeModel.properties.node_data.application_ids)\n}\n\nfunction submitApplicationDialog(config: any) {\n  set(props.nodeModel.properties.node_data, 'application_ids', config.application_ids)\n  collapseData.agent = true\n}\nfunction removeApplication(id: any) {\n  if (chat_data.value.application_ids) {\n    chat_data.value.application_ids = chat_data.value.application_ids.filter((v: any) => v !== id)\n  }\n}\n\nconst skillToolDialogRef = ref()\nfunction openSkillToolDialog() {\n  skillToolDialogRef.value.open(chat_data.value.skill_tool_ids)\n}\n\nfunction submitSkillToolDialog(config: any) {\n  chat_data.value.skill_tool_ids = config.tool_ids\n  collapseData.skill = true\n}\n\nconst skillToolSelectOptions = ref<any[]>([])\nfunction getSkillToolSelectOptions() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          scope: 'WORKSPACE',\n          tool_type: 'SKILL',\n          workspace_id: chat_data.value?.workspace_id,\n        }\n      : {\n          scope: 'WORKSPACE',\n          tool_type: 'SKILL',\n        }\n\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getAllToolList(obj)\n    .then((res: any) => {\n      skillToolSelectOptions.value = [...res.data.shared_tools, ...res.data.tools].filter(\n        (item: any) => item.is_active,\n      )\n    })\n}\n\nonMounted(() => {\n  getSelectModel()\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n  set(props.nodeModel, 'validate', validate)\n  if (!chat_data.value.dialogue_type) {\n    chat_data.value.dialogue_type = 'WORKFLOW'\n  }\n\n  if (props.nodeModel.properties.node_data?.mcp_tool_id) {\n    set(props.nodeModel.properties.node_data, 'mcp_tool_ids', [\n      props.nodeModel.properties.node_data?.mcp_tool_id,\n    ])\n    set(props.nodeModel.properties.node_data, 'mcp_tool_id', undefined)\n  }\n  if (props.nodeModel.properties.node_data?.mcp_output_enable === undefined) {\n    set(props.nodeModel.properties.node_data, 'mcp_output_enable', true)\n  }\n\n  getToolSelectOptions()\n  getMcpToolSelectOptions()\n  getApplicationSelectOptions()\n  getSkillToolSelectOptions()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/application-node/index.ts",
    "content": "import ChatNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass ChatNode extends AppNode {\n  constructor(props: any) {\n    super(props, ChatNodeVue)\n  }\n}\nexport default {\n  type: 'application-node',\n  model: AppNodeModel,\n  view: ChatNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/application-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"applicationNodeFormRef\"\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.startNode.question')\"\n          prop=\"question_reference_address\"\n          :rules=\"{\n            message: $t(\n              'workflow.nodes.searchKnowledgeNode.searchQuestion.requiredMessage',\n            ),\n            trigger: 'blur',\n            required: true,\n          }\"\n        >\n          <NodeCascader\n            ref=\"applicationNodeFormRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"\n              $t('workflow.nodes.searchKnowledgeNode.searchQuestion.placeholder')\n            \"\n            v-model=\"form_data.question_reference_address\"\n          />\n        </el-form-item>\n\n        <el-form-item\n          v-if=\"form_data.hasOwnProperty('document_list') || 'document_list' in form_data\"\n          :label=\"$t('views.problem.relateParagraph.selectDocument')\"\n          prop=\"document_list\"\n          :rules=\"{\n            message: $t('views.chatLog.documentPlaceholder'),\n            trigger: 'blur',\n            required: false,\n          }\"\n        >\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('views.chatLog.documentPlaceholder')\"\n            v-model=\"form_data.document_list\"\n          />\n        </el-form-item>\n\n        <el-form-item\n          v-if=\"form_data.hasOwnProperty('image_list') || 'image_list' in form_data\"\n          :label=\"$t('workflow.nodes.imageUnderstandNode.image.label')\"\n          prop=\"image_list\"\n          :rules=\"{\n            message: $t(\n              'workflow.nodes.imageUnderstandNode.image.requiredMessage',\n            ),\n            trigger: 'blur',\n            required: false,\n          }\"\n        >\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"\n              $t('workflow.nodes.imageUnderstandNode.image.requiredMessage')\n            \"\n            v-model=\"form_data.image_list\"\n          />\n        </el-form-item>\n\n        <el-form-item\n          v-if=\"form_data.hasOwnProperty('audio_list') || 'audio_list' in form_data\"\n          :label=\"$t('workflow.nodes.speechToTextNode.audio.label')\"\n          prop=\"audio_list\"\n          :rules=\"{\n            message: $t('workflow.nodes.speechToTextNode.audio.placeholder'),\n            trigger: 'blur',\n            required: false,\n          }\"\n        >\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.speechToTextNode.audio.placeholder')\"\n            v-model=\"form_data.audio_list\"\n          />\n        </el-form-item>\n        <el-form-item\n          v-if=\"form_data.hasOwnProperty('video_list') || 'video_list' in form_data\"\n          :label=\"$t('workflow.nodes.videoUnderstandNode.video.label')\"\n          prop=\"video_list\"\n          :rules=\"{\n            message: $t(\n              'workflow.nodes.videoUnderstandNode.video.requiredMessage',\n            ),\n            trigger: 'blur',\n            required: false,\n          }\"\n        >\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"\n              $t('workflow.nodes.videoUnderstandNode.video.requiredMessage')\n            \"\n            v-model=\"form_data.video_list\"\n          />\n        </el-form-item>\n        <div v-for=\"(field, index) in form_data.api_input_field_list\" :key=\"'api-input-' + index\">\n          <el-form-item\n            :label=\"typeof field.variable === 'object' && field.variable !== null ? field.variable.label : field.variable\"\n            :prop=\"'api_input_field_list.' + index + '.value'\"\n            :rules=\"[\n    {\n      required: field.is_required,\n      message: `${$t('common.inputPlaceholder')}${typeof field.variable === 'object' && field.variable !== null ? field.variable.label : field.variable}`,\n      trigger: 'blur',\n    },\n  ]\"\n          >\n\n            <NodeCascader\n              ref=\"nodeCascaderRef\"\n              :nodeModel=\"nodeModel\"\n              class=\"w-full\"\n              :placeholder=\"\n                $t('workflow.nodes.searchKnowledgeNode.searchQuestion.placeholder')\n              \"\n              v-model=\"form_data.api_input_field_list[index].value\"\n            />\n          </el-form-item>\n        </div>\n\n        <div v-for=\"(field, index) in form_data.user_input_field_list\" :key=\"'user-input-' + index\">\n          <el-form-item\n            :label=\"typeof field.label === 'object' && field.label !== null ? field.label.label : field.label\"\n            :prop=\"'user_input_field_list.' + index + '.value'\"\n            :rules=\"[\n    {\n      required: field.required,\n      message: `${$t('common.inputPlaceholder')}${typeof field.label === 'object' && field.label !== null ? field.label.label : field.label}`,\n      trigger: 'blur',\n    },\n  ]\"\n          >\n            <NodeCascader\n              ref=\"nodeCascaderRef\"\n              :nodeModel=\"nodeModel\"\n              class=\"w-full\"\n              :placeholder=\"\n      $t('workflow.nodes.searchKnowledgeNode.searchQuestion.placeholder')\n    \"\n              v-model=\"form_data.user_input_field_list[index].value\"\n            />\n          </el-form-item>\n\n        </div>\n        <el-form-item\n          :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n          @click.prevent\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{\n                    $t('workflow.nodes.aiChatNode.returnContent.label')\n                  }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.is_result\"/>\n        </el-form-item>\n      </el-form>\n    </el-card>\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport {set, groupBy, create, cloneDeep} from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport {ref, computed, onMounted, onActivated} from 'vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type {FormInstance} from 'element-plus'\nimport {isWorkFlow} from '@/utils/application'\nimport {useRoute} from 'vue-router'\nimport {loadSharedApi} from '@/utils/dynamics-api/shared-api'\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\nconst form = {\n  question_reference_address: ['start-node', 'question'],\n  api_input_field_list: [],\n  user_input_field_list: [],\n  document_list: ['start-node', 'document'],\n  image_list: ['start-node', 'image'],\n  audio_list: ['start-node', 'audio'],\n  video_list: ['start-node', 'video'],\n}\n\nconst applicationNodeFormRef = ref<FormInstance>()\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nfunction handleFileUpload(type: string, isEnabled: boolean) {\n  const listKey = `${type}_list`\n  if (isEnabled) {\n    if (!props.nodeModel.properties.node_data[listKey]) {\n      set(props.nodeModel.properties.node_data, listKey, [])\n    }\n  } else {\n    // eslint-disable-next-line vue/no-mutating-props\n    delete props.nodeModel.properties.node_data[listKey]\n  }\n}\n\nconst update_field = () => {\n  if (!props.nodeModel.properties.node_data.application_id) {\n    set(props.nodeModel.properties, 'status', 500)\n    return\n  }\n  loadSharedApi({type: 'application', systemType: apiType.value})\n    .getApplicationDetail(props.nodeModel.properties.node_data.application_id)\n    .then((ok: any) => {\n      const old_api_input_field_list = cloneDeep(\n        props.nodeModel.properties.node_data.api_input_field_list,\n      )\n      const old_user_input_field_list = cloneDeep(\n        props.nodeModel.properties.node_data.user_input_field_list,\n      )\n      if (isWorkFlow(ok.data.type)) {\n        const nodeData = ok.data.work_flow.nodes[0].properties.node_data\n        const new_api_input_field_list = cloneDeep(\n          ok.data.work_flow.nodes[0].properties.api_input_field_list,\n        )\n        const new_user_input_field_list = cloneDeep(\n          ok.data.work_flow.nodes[0].properties.user_input_field_list,\n        )\n\n        const merge_api_input_field_list = (new_api_input_field_list || []).map((item: any) => {\n          const find_field = old_api_input_field_list?.find(\n            (old_item: any) => old_item.variable == item.variable,\n          )\n          if (find_field) {\n            return {\n              ...item,\n              value: find_field.value,\n              label:\n                typeof item.label === 'object' && item.label != null\n                  ? item.label.label\n                  : item.label,\n            }\n          } else {\n            return item\n          }\n        })\n        set(\n          props.nodeModel.properties.node_data,\n          'api_input_field_list',\n          merge_api_input_field_list,\n        )\n        const merge_user_input_field_list = (new_user_input_field_list || []).map((item: any) => {\n          const find_field = old_user_input_field_list?.find(\n            (old_item: any) => old_item.field == item.field,\n          )\n          if (find_field) {\n            return {\n              ...item,\n              value: find_field.value,\n              label:\n                typeof item.label === 'object' && item.label != null\n                  ? item.label.label\n                  : item.label,\n            }\n          } else {\n            return item\n          }\n        })\n        set(\n          props.nodeModel.properties.node_data,\n          'user_input_field_list',\n          merge_user_input_field_list,\n        )\n        const fileEnable = nodeData.file_upload_enable\n        const fileUploadSetting = nodeData.file_upload_setting\n        if (fileEnable) {\n          handleFileUpload('document', fileUploadSetting.document)\n          handleFileUpload('image', fileUploadSetting.image)\n          handleFileUpload('audio', fileUploadSetting.audio)\n          handleFileUpload('video', fileUploadSetting.video)\n        } else {\n          ;['document_list', 'image_list', 'audio_list', 'video_list'].forEach((list) => {\n            // eslint-disable-next-line vue/no-mutating-props\n            delete props.nodeModel.properties.node_data[list]\n          })\n        }\n        set(props.nodeModel.properties, 'status', ok.data.id ? 200 : 500)\n      }\n    })\n    .catch((err: any) => {\n      console.log(err)\n      set(props.nodeModel.properties, 'status', 500)\n    })\n}\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst validate = () => {\n  return applicationNodeFormRef.value?.validate().catch((err) => {\n    return Promise.reject({node: props.nodeModel, errMessage: err})\n  })\n}\n\nonMounted(() => {\n  update_field()\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/base-node/component/ApiFieldFormDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"isEdit ? $t('common.param.editParam') : $t('common.param.addParam')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item :label=\"$t('dynamicsForm.paramForm.field.label')\" prop=\"variable\">\n        <el-input\n          v-model=\"form.variable\"\n          :placeholder=\"$t('dynamicsForm.paramForm.field.placeholder')\"\n          maxlength=\"64\"\n          show-word-limit\n          @blur=\"form.variable = form.variable.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('common.desc')\">\n        <el-input\n          v-model=\"form.desc\"\n          :placeholder=\"$t('common.descPlaceholder')\"\n          @blur=\"form.name = form.name.trim()\"\n          maxlength=\"64\"\n          show-word-limit\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('dynamicsForm.paramForm.required.label')\" @click.prevent>\n        <el-switch size=\"small\" v-model=\"form.is_required\"></el-switch>\n      </el-form-item>\n      <el-form-item\n        :label=\"$t('dynamicsForm.default.label')\"\n        prop=\"default_value\"\n        :rules=\"{\n          required: form.is_required,\n          message: $t('dynamicsForm.default.placeholder'),\n          trigger: 'blur',\n        }\"\n      >\n        <el-input\n          v-model=\"form.default_value\"\n          :placeholder=\"$t('dynamicsForm.default.placeholder')\"\n          @blur=\"form.name = form.name.trim()\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\n\nconst form = ref<any>({\n  name: '',\n  variable: '',\n  type: 'input',\n  is_required: true,\n  assignment_method: 'api_input',\n  optionList: [''],\n  default_value: '',\n  desc: '',\n})\n\nconst rules = reactive({\n  name: [\n    { required: true, message: t('dynamicsForm.paramForm.name.requiredMessage'), trigger: 'blur' },\n  ],\n  variable: [\n    { required: true, message: t('dynamicsForm.paramForm.field.requiredMessage'), trigger: 'blur' },\n    {\n      pattern: /^[a-zA-Z0-9_]+$/,\n      message: t('dynamicsForm.paramForm.field.requiredMessage2'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      name: '',\n      variable: '',\n      type: 'input',\n      is_required: true,\n      assignment_method: 'api_input',\n      optionList: [''],\n      default_value: '',\n      desc: '',\n    }\n    isEdit.value = false\n  }\n})\n\nconst open = (row: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n    isEdit.value = true\n  }\n\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  dialogVisible.value = false\n  isEdit.value = false\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value)\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/base-node/component/ApiInputFieldTable.vue",
    "content": "<template>\n  <div class=\"flex-between mb-16\">\n    <h5 class=\"lighter\">{{ $t('views.model.modelForm.title.apiParamPassing') }}</h5>\n    <el-button link type=\"primary\" @click=\"openAddDialog()\">\n      <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n      {{ $t('common.add') }}\n    </el-button>\n  </div>\n  <el-table\n    v-if=\"props.nodeModel.properties.api_input_field_list?.length > 0\"\n    :data=\"props.nodeModel.properties.api_input_field_list\"\n    class=\"mb-16 api-input-field-table\"\n    ref=\"tableRef\"\n    row-key=\"variable\"\n  >\n    <el-table-column prop=\"variable\" :label=\"$t('dynamicsForm.paramForm.field.label')\">\n      <template #default=\"{ row }\">\n        <span class=\"ellipsis-1\" :title=\"row.variable\">\n          {{ row.variable }}\n        </span>\n      </template>\n    </el-table-column>\n    <el-table-column prop=\"desc\" :label=\"$t('common.desc')\">\n      <template #default=\"{ row }\">\n        <span class=\"ellipsis-1\" :title=\"row.desc\">\n          {{ row.desc }}\n        </span>\n      </template>\n    </el-table-column>\n    <el-table-column prop=\"default_value\" :label=\"$t('dynamicsForm.default.label')\">\n      <template #default=\"{ row }\">\n        <span class=\"ellipsis-1\" :title=\"row.default_value\">\n          {{ row.default_value }}\n        </span>\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.required')\">\n      <template #default=\"{ row }\">\n        <div @click.stop>\n          <el-switch disabled size=\"small\" v-model=\"row.is_required\" />\n        </div>\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n      <template #default=\"{ row, $index }\">\n        <span class=\"mr-4\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n            <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n              <AppIcon iconName=\"app-edit\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n          <el-button type=\"primary\" text @click=\"deleteField($index)\">\n            <AppIcon iconName=\"app-delete\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </template>\n    </el-table-column>\n  </el-table>\n\n  <ApiFieldFormDialog ref=\"ApiFieldFormDialogRef\" @refresh=\"refreshFieldList\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport Sortable from 'sortablejs'\nimport ApiFieldFormDialog from './ApiFieldFormDialog.vue'\nimport { MsgError } from '@/utils/message'\nimport { t } from '@/locales'\n\nconst props = defineProps<{ nodeModel: any }>()\nconst tableRef = ref()\nconst currentIndex = ref(null)\nconst ApiFieldFormDialogRef = ref()\nconst inputFieldList = ref<any[]>([])\n\nfunction openAddDialog(data?: any, index?: any) {\n  if (typeof index !== 'undefined') {\n    currentIndex.value = index\n  }\n  ApiFieldFormDialogRef.value.open(data)\n}\n\nfunction deleteField(index: any) {\n  inputFieldList.value.splice(index, 1)\n  props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n  onDragHandle()\n}\n\nfunction refreshFieldList(data: any) {\n  for (let i = 0; i < inputFieldList.value.length; i++) {\n    if (inputFieldList.value[i].variable === data.variable && currentIndex.value !== i) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.variable)\n      return\n    }\n  }\n  // 查看另一个list又没有重复的\n  const arr = props.nodeModel.properties.user_input_field_list\n  for (let i = 0; i < arr.length; i++) {\n    if (arr[i].field === data.variable) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.variable)\n      return\n    }\n  }\n  if (currentIndex.value !== null) {\n    inputFieldList.value.splice(currentIndex.value, 1, data)\n  } else {\n    inputFieldList.value.push(data)\n  }\n  currentIndex.value = null\n  ApiFieldFormDialogRef.value.close()\n  props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n  onDragHandle()\n}\n\n// 表格排序拖拽\nfunction onDragHandle() {\n  if (!tableRef.value) return\n\n  // 获取表格的 tbody DOM 元素\n  const wrapper = tableRef.value.$el as HTMLElement\n  const tbody = wrapper.querySelector('.api-input-field-table .el-table__body-wrapper tbody')\n  if (!tbody) return\n  // 初始化 Sortable\n  Sortable.create(tbody as HTMLElement, {\n    animation: 150,\n    ghostClass: 'ghost-row',\n    onEnd: (evt) => {\n      if (evt.oldIndex === undefined || evt.newIndex === undefined) return\n      // 更新数据顺序\n      const items = cloneDeep([...inputFieldList.value])\n      const [movedItem] = items.splice(evt.oldIndex, 1)\n      items.splice(evt.newIndex, 0, movedItem)\n      inputFieldList.value = items\n      props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n    },\n  })\n}\n\nonMounted(() => {\n  if (!props.nodeModel.properties.api_input_field_list) {\n    if (props.nodeModel.properties.input_field_list) {\n      props.nodeModel.properties.input_field_list\n        .filter((item: any) => {\n          return item.assignment_method === 'api_input'\n        })\n        .forEach((item: any) => {\n          inputFieldList.value.push(item)\n        })\n    }\n  } else {\n    inputFieldList.value.push(...props.nodeModel.properties.api_input_field_list)\n  }\n  set(props.nodeModel.properties, 'api_input_field_list', inputFieldList)\n  onDragHandle()\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/base-node/component/ChatFieldDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"isEdit ? $t('common.param.editParam') : $t('common.param.addParam')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item\n        :label=\"$t('dynamicsForm.paramForm.field.label')\"\n        :required=\"true\"\n        prop=\"field\"\n        :rules=\"rules.field\"\n      >\n        <el-input\n          v-model=\"form.field\"\n          :maxlength=\"64\"\n          :placeholder=\"$t('dynamicsForm.paramForm.field.placeholder')\"\n          show-word-limit\n        />\n      </el-form-item>\n      <el-form-item\n        :label=\"$t('dynamicsForm.paramForm.name.label')\"\n        :required=\"true\"\n        prop=\"label\"\n        :rules=\"rules.label\"\n      >\n        <el-input\n          v-model=\"form.label\"\n          :maxlength=\"64\"\n          show-word-limit\n          :placeholder=\"$t('dynamicsForm.paramForm.name.placeholder')\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"close\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\nconst currentIndex = ref(null)\nconst form = ref<any>({\n  field: '',\n  label: '',\n})\n\nconst rules = reactive({\n  label: [\n    { required: true, message: t('dynamicsForm.paramForm.name.requiredMessage'), trigger: 'blur' },\n  ],\n  field: [\n    { required: true, message: t('dynamicsForm.paramForm.field.requiredMessage'), trigger: 'blur' },\n    {\n      pattern: /^[a-zA-Z0-9_]+$/,\n      message: t('dynamicsForm.paramForm.field.requiredMessage2'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any, index?: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n    isEdit.value = true\n    currentIndex.value = index\n  }\n\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  dialogVisible.value = false\n  isEdit.value = false\n  currentIndex.value = null\n  form.value = {\n    field: '',\n    label: '',\n  }\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value, currentIndex.value)\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/base-node/component/ChatFieldTable.vue",
    "content": "<template>\n  <div class=\"flex-between mb-16\">\n    <h5 class=\"break-all ellipsis lighter\" style=\"max-width: 80%\">\n      {{ $t('workflow.variable.chat') }}\n    </h5>\n    <div>\n      <span class=\"ml-4\">\n        <el-button link type=\"primary\" @click=\"openAddDialog()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </span>\n    </div>\n  </div>\n  <el-table\n    v-if=\"props.nodeModel.properties.chat_input_field_list?.length > 0\"\n    :data=\"props.nodeModel.properties.chat_input_field_list\"\n    class=\"mb-16\"\n    ref=\"tableRef\"\n    row-key=\"field\"\n  >\n    <el-table-column prop=\"field\" :label=\"$t('dynamicsForm.paramForm.field.label')\" width=\"95\">\n      <template #default=\"{ row }\">\n        <span :title=\"row.field\" class=\"ellipsis-1\">{{ row.field }}</span>\n      </template>\n    </el-table-column>\n\n    <el-table-column prop=\"label\" :label=\"$t('dynamicsForm.paramForm.name.label')\">\n      <template #default=\"{ row }\">\n        <span>\n          <span :title=\"row.label\" class=\"ellipsis-1\">\n            {{ row.label }}\n          </span></span\n        >\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n      <template #default=\"{ row, $index }\">\n        <span class=\"mr-4\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n            <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n              <AppIcon iconName=\"app-edit\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n          <el-button type=\"primary\" text @click=\"deleteField($index)\">\n            <AppIcon iconName=\"app-delete\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </template>\n    </el-table-column>\n  </el-table>\n  <ChatFieldDialog ref=\"ChatFieldDialogRef\" @refresh=\"refreshFieldList\"></ChatFieldDialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport ChatFieldDialog from './ChatFieldDialog.vue'\nimport { MsgError } from '@/utils/message'\nimport { t } from '@/locales'\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst tableRef = ref()\nconst ChatFieldDialogRef = ref()\n\nconst inputFieldList = ref<any[]>([])\n\nfunction openAddDialog(data?: any, index?: any) {\n  ChatFieldDialogRef.value.open(data, index)\n}\n\nfunction deleteField(index: any) {\n  inputFieldList.value.splice(index, 1)\n  props.nodeModel.graphModel.eventCenter.emit('chatFieldList')\n}\n\nfunction refreshFieldList(data: any, index: any) {\n  for (let i = 0; i < inputFieldList.value.length; i++) {\n    if (inputFieldList.value[i].field === data.field && index !== i) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.field)\n      return\n    }\n  }\n  if ([undefined, null].includes(index)) {\n    inputFieldList.value.push(data)\n  } else {\n    inputFieldList.value.splice(index, 1, data)\n  }\n  ChatFieldDialogRef.value.close()\n  props.nodeModel.graphModel.eventCenter.emit('chatFieldList')\n}\n\nonMounted(() => {\n  if (props.nodeModel.properties.chat_input_field_list) {\n    inputFieldList.value = cloneDeep(props.nodeModel.properties.chat_input_field_list)\n  }\n  set(props.nodeModel.properties, 'chat_input_field_list', inputFieldList)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/base-node/component/FileUploadSettingDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('workflow.nodes.baseNode.FileUploadSetting.title')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n    width=\"800\"\n    align-center\n    class=\"scrollbar-dialog\"\n  >\n    <el-scrollbar>\n      <div class=\"p-8\" style=\"max-height: calc(100vh - 273px)\">\n        <el-form\n          label-position=\"top\"\n          ref=\"fieldFormRef\"\n          :model=\"form_data\"\n          require-asterisk-position=\"right\"\n        >\n          <el-form-item :label=\"$t('workflow.nodes.baseNode.FileUploadSetting.maxFiles')\">\n            <el-slider\n              v-model=\"form_data.maxFiles\"\n              show-input\n              :show-input-controls=\"false\"\n              :min=\"1\"\n              :max=\"100\"\n            />\n          </el-form-item>\n          <el-form-item :label=\"$t('workflow.nodes.baseNode.FileUploadSetting.fileLimit')\">\n            <el-slider\n              v-model=\"form_data.fileLimit\"\n              show-input\n              :show-input-controls=\"false\"\n              :min=\"1\"\n              :max=\"1000\"\n            />\n          </el-form-item>\n          <el-form-item\n            :label=\"$t('workflow.nodes.baseNode.FileUploadSetting.fileUploadType.label')\"\n          >\n            <el-card\n              shadow=\"hover\"\n              class=\"card-checkbox cursor w-full mb-8\"\n              :class=\"form_data.document ? 'border-active' : ''\"\n              style=\"--el-card-padding: 8px 16px\"\n              @click.stop=\"form_data.document = !form_data.document\"\n            >\n              <div class=\"flex-between\">\n                <div class=\"flex align-center\">\n                  <img class=\"mr-12\" src=\"@/assets/workflow/icon_file-doc.svg\" alt=\"\" />\n                  <div>\n                    <p class=\"line-height-22 mt-4\">\n                      {{ $t('common.fileUpload.document') }}\n                      <el-text class=\"color-secondary\"\n                        >{{\n                          $t(\n                            'workflow.nodes.baseNode.FileUploadSetting.fileUploadType.documentText',\n                          )\n                        }}\n                      </el-text>\n                    </p>\n                    <p>{{ documentExtensions.join('、') }}</p>\n                  </div>\n                </div>\n                <el-checkbox\n                  v-model=\"form_data.document\"\n                  @change=\"form_data.document = !form_data.document\"\n                />\n              </div>\n            </el-card>\n            <el-card\n              shadow=\"hover\"\n              class=\"card-checkbox cursor w-full mb-8\"\n              :class=\"form_data.image ? 'border-active' : ''\"\n              style=\"--el-card-padding: 8px 16px\"\n              @click.stop=\"form_data.image = !form_data.image\"\n            >\n              <div class=\"flex-between\">\n                <div class=\"flex align-center\">\n                  <img class=\"mr-12\" src=\"@/assets/workflow/icon_file-image.svg\" alt=\"\" />\n                  <div>\n                    <p class=\"line-height-22 mt-4\">\n                      {{ $t('common.fileUpload.image') }}\n                      <el-text class=\"color-secondary\"\n                        >{{\n                          $t('workflow.nodes.baseNode.FileUploadSetting.fileUploadType.imageText')\n                        }}\n                      </el-text>\n                    </p>\n                    <p>{{ imageExtensions.join('、') }}</p>\n                  </div>\n                </div>\n                <el-checkbox\n                  v-model=\"form_data.image\"\n                  @change=\"form_data.image = !form_data.image\"\n                />\n              </div>\n            </el-card>\n\n            <el-card\n              shadow=\"hover\"\n              class=\"card-checkbox cursor w-full mb-8\"\n              :class=\"form_data.audio ? 'border-active' : ''\"\n              style=\"--el-card-padding: 8px 16px\"\n              @click.stop=\"form_data.audio = !form_data.audio\"\n            >\n              <div class=\"flex-between\">\n                <div class=\"flex align-center\">\n                  <img class=\"mr-12\" src=\"@/assets/workflow/icon_file-audio.svg\" alt=\"\" />\n                  <div>\n                    <p class=\"line-height-22 mt-4\">\n                      {{ $t('common.fileUpload.audio') }}\n                      <el-text class=\"color-secondary\"\n                        >{{\n                          $t('workflow.nodes.baseNode.FileUploadSetting.fileUploadType.audioText')\n                        }}\n                      </el-text>\n                    </p>\n                    <p>{{ audioExtensions.join('、') }}</p>\n                  </div>\n                </div>\n                <el-checkbox\n                  v-model=\"form_data.audio\"\n                  @change=\"form_data.audio = !form_data.audio\"\n                />\n              </div>\n            </el-card>\n            <el-card\n              shadow=\"hover\"\n              class=\"card-checkbox cursor w-full mb-8\"\n              :class=\"form_data.video ? 'border-active' : ''\"\n              style=\"--el-card-padding: 8px 16px\"\n              @click.stop=\"form_data.video = !form_data.video\"\n            >\n              <div class=\"flex-between\">\n                <div class=\"flex align-center\">\n                  <img\n                    class=\"mr-12\"\n                    width=\"32\"\n                    src=\"@/assets/workflow/icon_file-video.svg\"\n                    alt=\"\"\n                  />\n                  <div>\n                    <p class=\"line-height-22 mt-4\">\n                      {{ $t('common.fileUpload.video') }}\n                      <el-text class=\"color-secondary\"\n                        >{{\n                          $t('workflow.nodes.baseNode.FileUploadSetting.fileUploadType.videoText')\n                        }}\n                      </el-text>\n                    </p>\n                    <p>{{ videoExtensions.join('、') }}</p>\n                  </div>\n                </div>\n                <el-checkbox\n                  v-model=\"form_data.video\"\n                  @change=\"form_data.video = !form_data.video\"\n                />\n              </div>\n            </el-card>\n            <el-card\n              shadow=\"hover\"\n              class=\"card-checkbox cursor w-full mb-8\"\n              :class=\"form_data.other ? 'border-active' : ''\"\n              style=\"--el-card-padding: 8px 16px\"\n              @click.stop=\"form_data.other = !form_data.other\"\n            >\n              <div class=\"flex-between\">\n                <div class=\"flex align-center\">\n                  <img class=\"mr-12\" :width=\"32\" src=\"@/assets/fileType/unknown-icon.svg\" alt=\"\" />\n                  <div>\n                    <p class=\"line-height-22 mt-4\">\n                      {{ $t('common.fileUpload.other') }}\n                      <el-text class=\"color-secondary\"\n                        >{{\n                          $t('workflow.nodes.baseNode.FileUploadSetting.fileUploadType.otherText')\n                        }}\n                      </el-text>\n                    </p>\n                    <el-space wrap :size=\"6\" class=\"mt-4\">\n                      <el-tag\n                        v-for=\"tag in form_data.otherExtensions\"\n                        :key=\"tag\"\n                        closable\n                        :disable-transitions=\"false\"\n                        @close=\"handleClose(tag)\"\n                        type=\"info\"\n                        effect=\"plain\"\n                      >\n                        {{ tag }}\n                      </el-tag>\n                      <el-input\n                        v-if=\"inputVisible\"\n                        ref=\"InputRef\"\n                        v-model=\"inputValue\"\n                        size=\"small\"\n                        @keyup.enter=\"handleInputConfirm\"\n                        @blur=\"handleInputConfirm\"\n                        :style=\"{\n                          '--el-input-border-radius': '4px',\n                        }\"\n                      />\n                      <el-button v-else class=\"button-new-tag\" size=\"small\" @click.stop=\"showInput\">\n                        <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                        {{ $t('common.fileUpload.addExtensions') }}\n                      </el-button>\n                    </el-space>\n                  </div>\n                </div>\n                <el-checkbox\n                  v-model=\"form_data.other\"\n                  @change=\"form_data.other = !form_data.other\"\n                />\n              </div>\n            </el-card>\n            <el-form-item\n              :label=\"$t('workflow.nodes.baseNode.FileUploadSetting.fileUploadType.uploadMethod')\"\n            >\n              <template #label>\n                <span>\n                  {{ $t('workflow.nodes.baseNode.FileUploadSetting.fileUploadType.uploadMethod')\n                  }}<span class=\"color-danger\">*</span>\n                </span>\n              </template>\n              <div class=\"flex align-center\">\n                <el-checkbox v-model=\"form_data.local_upload\" class=\"mr-16\">\n                  {{ $t('common.fileUpload.localUpload') }}\n                </el-checkbox>\n                <el-checkbox v-model=\"form_data.url_upload\">\n                  {{ $t('common.fileUpload.urlUpload') }}\n                </el-checkbox>\n              </div>\n            </el-form-item>\n          </el-form-item>\n        </el-form>\n      </div>\n    </el-scrollbar>\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"close\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit()\" :loading=\"loading\">\n          {{ $t('common.confirm') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { nextTick, ref } from 'vue'\nimport type { InputInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { MsgWarning } from '@/utils/message'\nimport { t } from '@/locales'\n\nconst emit = defineEmits(['refresh'])\nconst props = defineProps<{ nodeModel: any }>()\n\nconst dialogVisible = ref(false)\nconst inputVisible = ref(false)\nconst inputValue = ref('')\nconst loading = ref(false)\nconst fieldFormRef = ref()\nconst InputRef = ref<InputInstance>()\n\nconst documentExtensions = ['TXT', 'MD', 'DOCX', 'HTML', 'CSV', 'XLSX', 'XLS', 'PDF']\nconst imageExtensions = ['JPG', 'JPEG', 'PNG', 'GIF']\nconst audioExtensions = ['MP3', 'WAV', 'OGG', 'ACC', 'M4A']\nconst videoExtensions: any = ['MP4', 'AVI', 'MKV', 'MOV', 'FLV', 'WMV']\n\nconst form_data = ref({\n  maxFiles: 3,\n  fileLimit: 50,\n  document: true,\n  image: false,\n  audio: false,\n  video: false,\n  other: false,\n  otherExtensions: ['PPT', 'DOC'],\n  local_upload: true,\n  url_upload: false,\n})\n\nfunction open(data: any) {\n  dialogVisible.value = true\n  nextTick(() => {\n    form_data.value = { ...form_data.value, ...data }\n  })\n}\n\nfunction close() {\n  dialogVisible.value = false\n}\n\nconst handleClose = (tag: string) => {\n  form_data.value.otherExtensions = form_data.value.otherExtensions.filter((item) => item !== tag)\n}\n\nconst showInput = () => {\n  inputVisible.value = true\n  nextTick(() => {\n    InputRef.value!.input!.focus()\n  })\n}\nconst handleInputConfirm = () => {\n  if (inputValue.value) {\n    inputValue.value = inputValue.value.toUpperCase()\n    if (\n      form_data.value.otherExtensions.includes(inputValue.value) ||\n      documentExtensions.includes(inputValue.value) ||\n      imageExtensions.includes(inputValue.value) ||\n      audioExtensions.includes(inputValue.value)\n    ) {\n      inputVisible.value = false\n      inputValue.value = ''\n      MsgWarning(t('common.fileUpload.existingExtensionsTip'))\n      return\n    }\n    form_data.value.otherExtensions.push(inputValue.value)\n  }\n  inputVisible.value = false\n  inputValue.value = ''\n}\n\nasync function submit() {\n  const formEl = fieldFormRef.value\n  if (!form_data.value.local_upload && !form_data.value.url_upload) {\n    MsgWarning(t('common.fileUpload.uploadMethodTip'))\n    return\n  }\n  if (!formEl) return\n  await formEl.validate().then(() => {\n    const formattedData = cloneDeep(form_data.value)\n    emit('refresh', formattedData)\n    // emit('refresh', form_data.value)\n    props.nodeModel.graphModel.eventCenter.emit('refreshFileUploadConfig')\n    dialogVisible.value = false\n  })\n}\n\ndefineExpose({\n  open,\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/base-node/component/UserFieldFormDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"isEdit ? $t('common.param.editParam') : $t('common.param.addParam')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <DynamicsFormConstructor\n      v-model=\"currentRow\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      :input_type_list=\"inputTypeList\"\n      ref=\"DynamicsFormConstructorRef\"\n    ></DynamicsFormConstructor>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"close\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit()\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport _ from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst DynamicsFormConstructorRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\nconst currentItem = ref<FormField | any>()\nconst check_field = (field_list: Array<string>, obj: any) => {\n  return field_list.every((field) => _.get(obj, field, undefined) !== undefined)\n}\nconst currentRow = computed(() => {\n  if (currentItem.value) {\n    const row = currentItem.value\n    switch (row.type) {\n      case 'input':\n        if (check_field(['field', 'input_type', 'label', 'required', 'attrs'], currentItem.value)) {\n          return currentItem.value\n        }\n        return {\n          attrs: row.attrs || { maxlength: 200, minlength: 0 },\n          field: row.field || row.variable,\n          input_type: 'TextInput',\n          label: row.label || row.name,\n          default_value: row.default_value,\n          required: row.required != undefined ? row.required : row.is_required,\n        }\n      case 'select':\n        if (\n          check_field(\n            ['field', 'input_type', 'label', 'required', 'option_list'],\n            currentItem.value,\n          )\n        ) {\n          return currentItem.value\n        }\n        return {\n          attrs: row.attrs || {},\n          field: row.field || row.variable,\n          input_type: 'SingleSelect',\n          label: row.label || row.name,\n          default_value: row.default_value,\n          required: row.required != undefined ? row.required : row.is_required,\n          option_list: row.option_list\n            ? row.option_list\n            : row.optionList.map((o: any) => {\n                return { key: o, value: o }\n              }),\n        }\n\n      case 'date':\n        if (\n          check_field(\n            [\n              'field',\n              'input_type',\n              'label',\n              'required',\n              'attrs.format',\n              'attrs.value-format',\n              'attrs.type',\n            ],\n            currentItem.value,\n          )\n        ) {\n          return currentItem.value\n        }\n        return {\n          field: row.field || row.variable,\n          input_type: 'DatePicker',\n          label: row.label || row.name,\n          default_value: row.default_value || new Date(),\n          required: row.required != undefined ? row.required : row.is_required,\n          attrs: {\n            format: 'YYYY-MM-DD HH:mm:ss',\n            'value-format': 'YYYY-MM-DD HH:mm:ss',\n            type: 'datetime',\n          },\n        }\n      default:\n        return currentItem.value\n    }\n  } else {\n    return {\n      input_type: 'TextInput',\n      required: false,\n      attrs: { maxlength: 200, minlength: 0 },\n      show_default_value: true,\n    }\n  }\n})\nconst currentIndex = ref(null)\nconst inputTypeList = ref([\n  { label: t('dynamicsForm.input_type_list.TextInput'), value: 'TextInputConstructor' },\n  { label: t('dynamicsForm.input_type_list.PasswordInput'), value: 'PasswordInputConstructor' },\n  { label: t('dynamicsForm.input_type_list.SingleSelect'), value: 'SingleSelectConstructor' },\n  { label: t('dynamicsForm.input_type_list.MultiSelect'), value: 'MultiSelectConstructor' },\n  { label: t('dynamicsForm.input_type_list.RadioCard'), value: 'RadioCardConstructor' },\n  { label: t('dynamicsForm.input_type_list.DatePicker'), value: 'DatePickerConstructor' },\n  { label: t('dynamicsForm.input_type_list.SwitchInput'), value: 'SwitchInputConstructor' },\n  { label: t('dynamicsForm.input_type_list.RadioRow'), value: 'RadioRowConstructor' },\n  { label: t('dynamicsForm.input_type_list.TextareaInput'), value: 'TextareaInputConstructor' },\n  { label: t('dynamicsForm.input_type_list.MultiRow'), value: 'MultiRowConstructor' },\n  { label: t('dynamicsForm.input_type_list.Model'), value: 'ModelConstructor' },\n])\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any, index: any) => {\n  dialogVisible.value = true\n\n  if (row) {\n    isEdit.value = true\n    currentItem.value = cloneDeep(row)\n    currentIndex.value = index\n  } else {\n    currentItem.value = null\n  }\n}\n\nconst close = () => {\n  dialogVisible.value = false\n  isEdit.value = false\n  currentIndex.value = null\n  currentItem.value = null as any\n}\n\nconst submit = async () => {\n  const formEl = DynamicsFormConstructorRef.value\n  if (!formEl) return\n  await formEl.validate().then(() => {\n    emit('refresh', formEl?.getData(), currentIndex.value)\n    isEdit.value = false\n    currentItem.value = null as any\n    currentIndex.value = null\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/base-node/component/UserInputFieldTable.vue",
    "content": "<template>\n  <div class=\"flex-between mb-16\">\n    <h5 class=\"break-all ellipsis lighter\" style=\"max-width: 80%\" :title=\"inputFieldConfig.title\">\n      {{ inputFieldConfig.title }}\n    </h5>\n    <div>\n      <el-button type=\"primary\" link @click=\"openChangeTitleDialog\">\n        <AppIcon iconName=\"app-setting\"></AppIcon>\n      </el-button>\n      <span class=\"ml-4\">\n        <el-button link type=\"primary\" @click=\"openAddDialog()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </span>\n    </div>\n  </div>\n  <el-table\n    v-if=\"props.nodeModel.properties.user_input_field_list?.length > 0\"\n    :data=\"props.nodeModel.properties.user_input_field_list\"\n    class=\"mb-16\"\n    ref=\"tableRef\"\n    row-key=\"field\"\n  >\n    <el-table-column prop=\"field\" :label=\"$t('dynamicsForm.paramForm.field.label')\" width=\"95\">\n      <template #default=\"{ row }\">\n        <span :title=\"row.field\" class=\"ellipsis-1\">{{ row.field }}</span>\n      </template>\n    </el-table-column>\n\n    <el-table-column prop=\"label\" :label=\"$t('dynamicsForm.paramForm.name.label')\">\n      <template #default=\"{ row }\">\n        <span v-if=\"row.label && row.label.input_type === 'TooltipLabel'\">\n          <span :title=\"row.label.label\" class=\"ellipsis-1\">\n            {{ row.label.label }}\n          </span>\n        </span>\n        <span v-else>\n          <span :title=\"row.label\" class=\"ellipsis-1\">\n            {{ row.label }}\n          </span></span\n        >\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('dynamicsForm.paramForm.input_type.label')\" width=\"95\">\n      <template #default=\"{ row }\">\n        <el-tag size=\"small\" type=\"info\" class=\"info-tag\">{{\n          input_type_list.find((item) => item.value === row.input_type)?.label\n        }}</el-tag>\n      </template>\n    </el-table-column>\n\n    <el-table-column prop=\"default_value\" :label=\"$t('dynamicsForm.default.label')\">\n      <template #default=\"{ row }\">\n        <span :title=\"row.default_value\" class=\"ellipsis-1\">{{ getDefaultValue(row) }}</span>\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.required')\">\n      <template #default=\"{ row }\">\n        <div @click.stop>\n          <el-switch disabled size=\"small\" v-model=\"row.required\" />\n        </div>\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n      <template #default=\"{ row, $index }\">\n        <span class=\"mr-4\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n            <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n              <AppIcon iconName=\"app-edit\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n          <el-button type=\"primary\" text @click=\"deleteField($index)\">\n            <AppIcon iconName=\"app-delete\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </template>\n    </el-table-column>\n  </el-table>\n\n  <UserFieldFormDialog ref=\"UserFieldFormDialogRef\" @refresh=\"refreshFieldList\" />\n  <UserInputTitleDialog ref=\"UserInputTitleDialogRef\" @refresh=\"refreshFieldTitle\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport Sortable from 'sortablejs'\nimport UserFieldFormDialog from './UserFieldFormDialog.vue'\nimport { MsgError } from '@/utils/message'\nimport { t } from '@/locales'\nimport UserInputTitleDialog from '@/workflow/nodes/base-node/component/UserInputTitleDialog.vue'\nimport { input_type_list } from '@/components/dynamics-form/constructor/data'\nconst props = defineProps<{ nodeModel: any }>()\n\nconst tableRef = ref()\nconst UserFieldFormDialogRef = ref()\nconst UserInputTitleDialogRef = ref()\nconst inputFieldList = ref<any[]>([])\nconst inputFieldConfig = ref({ title: t('chat.userInput') })\n\nfunction openAddDialog(data?: any, index?: any) {\n  UserFieldFormDialogRef.value.open(data, index)\n}\n\nfunction openChangeTitleDialog() {\n  UserInputTitleDialogRef.value.open(inputFieldConfig.value)\n}\n\nfunction deleteField(index: any) {\n  inputFieldList.value.splice(index, 1)\n  props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n  onDragHandle()\n}\n\nfunction refreshFieldList(data: any, index: any) {\n  for (let i = 0; i < inputFieldList.value.length; i++) {\n    if (inputFieldList.value[i].field === data.field && index !== i) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.field)\n      return\n    }\n  }\n  // 查看另一个list又没有重复的\n  const arr = props.nodeModel.properties.api_input_field_list\n  for (let i = 0; i < arr.length; i++) {\n    if (arr[i].variable === data.field) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.field)\n      return\n    }\n  }\n  if (index !== null) {\n    inputFieldList.value.splice(index, 1, data)\n  } else {\n    inputFieldList.value.push(data)\n  }\n  UserFieldFormDialogRef.value.close()\n  props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n  onDragHandle()\n}\n\nfunction refreshFieldTitle(data: any) {\n  inputFieldConfig.value = data\n  UserInputTitleDialogRef.value.close()\n}\n\nconst getDefaultValue = (row: any) => {\n  if (row.input_type === 'PasswordInput') {\n    return '******'\n  }\n  if (row.default_value) {\n    const default_value = row.option_list\n      ?.filter((v: any) => row.default_value.indexOf(v.value) > -1)\n      .map((v: any) => v.label)\n      .join(',')\n    if (default_value) {\n      return default_value\n    }\n    return row.default_value\n  }\n  if (row.default_value !== undefined) {\n    return row.default_value\n  }\n}\n\nfunction onDragHandle() {\n  if (!tableRef.value) return\n\n  // 获取表格的 tbody DOM 元素\n  const wrapper = tableRef.value.$el as HTMLElement\n  const tbody = wrapper.querySelector('.el-table__body-wrapper tbody')\n  if (!tbody) return\n  // 初始化 Sortable\n  Sortable.create(tbody as HTMLElement, {\n    animation: 150,\n    ghostClass: 'ghost-row',\n    onEnd: (evt) => {\n      if (evt.oldIndex === undefined || evt.newIndex === undefined) return\n      // 更新数据顺序\n      const items = cloneDeep([...inputFieldList.value])\n      const [movedItem] = items.splice(evt.oldIndex, 1)\n      items.splice(evt.newIndex, 0, movedItem)\n      inputFieldList.value = items\n      props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n    },\n  })\n}\n\nonMounted(() => {\n  if (!props.nodeModel.properties.user_input_field_list) {\n    if (props.nodeModel.properties.input_field_list) {\n      props.nodeModel.properties.input_field_list\n        .filter((item: any) => {\n          return item.assignment_method === 'user_input'\n        })\n        .forEach((item: any) => {\n          inputFieldList.value.push(item)\n        })\n    }\n  } else {\n    inputFieldList.value.push(...props.nodeModel.properties.user_input_field_list)\n  }\n  // 兼容旧数据\n  inputFieldList.value.forEach((item, index) => {\n    item.label = item.label || item.name\n    item.field = item.field || item.variable\n    item.required = item.required == undefined ? item.is_required : item.required\n    switch (item.type) {\n      case 'input':\n        item.input_type = 'TextInput'\n        break\n      case 'select':\n        item.input_type = 'SingleSelect'\n        break\n      case 'date':\n        item.input_type = 'DatePicker'\n        break\n    }\n  })\n  set(props.nodeModel.properties, 'user_input_field_list', inputFieldList)\n  if (props.nodeModel.properties.user_input_config) {\n    inputFieldConfig.value = props.nodeModel.properties.user_input_config\n  }\n  set(props.nodeModel.properties, 'user_input_config', inputFieldConfig)\n  onDragHandle()\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/base-node/component/UserInputTitleDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('common.setting')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-form-item :label=\"$t('common.title')\" prop=\"title\">\n        <el-input\n          v-model=\"form.title\"\n          maxlength=\"64\"\n          show-word-limit\n          @blur=\"form.title = form.title.trim()\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\n\nconst form = ref<any>({\n  title: t('chat.userInput'),\n})\n\nconst rules = reactive({\n  title: [\n    { required: true, message: t('dynamicsForm.paramForm.name.requiredMessage'), trigger: 'blur' },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n  }\n\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  dialogVisible.value = false\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value)\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/base-node/index.ts",
    "content": "import BaseNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass BaseNode extends AppNode {\n  constructor(props: any) {\n    super(props, BaseNodeVue)\n  }\n}\n\nclass BaseModel extends AppNodeModel {\n  constructor(data: any, graphModel: any) {\n    super(data, graphModel)\n  }\n  get_width() {\n    return 600\n  }\n}\n\nexport default {\n  type: 'base-node',\n  model: BaseModel,\n  view: BaseNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/base-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <el-form\n      @submit.prevent\n      :model=\"form_data\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      class=\"mb-24\"\n      label-width=\"auto\"\n      ref=\"baseNodeFormRef\"\n    >\n      <el-form-item\n        :label=\"$t('common.name')\"\n        prop=\"name\"\n        :rules=\"{\n          message: t('views.application.form.appName.requiredMessage'),\n          trigger: 'blur',\n          required: true,\n        }\"\n      >\n        <el-input\n          v-model=\"form_data.name\"\n          maxlength=\"64\"\n          :placeholder=\"t('views.application.form.appName.placeholder')\"\n          show-word-limit\n          @blur=\"form_data.name = form_data.name?.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('common.desc')\">\n        <el-input\n          v-model=\"form_data.desc\"\n          :placeholder=\"$t('views.application.form.appDescription.placeholder')\"\n          :rows=\"3\"\n          type=\"textarea\"\n          maxlength=\"256\"\n          show-word-limit\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('views.application.form.prologue')\">\n        <MdEditorMagnify\n          @wheel=\"wheel\"\n          :title=\"$t('views.application.form.prologue')\"\n          v-model=\"form_data.prologue\"\n          style=\"height: 150px\"\n          @submitDialog=\"submitDialog\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <template #label>\n          <div class=\"flex-between\">\n            <div class=\"flex align-center\">\n              <span class=\"mr-4\">{{ $t('workflow.nodes.baseNode.fileUpload.label') }}</span>\n              <el-tooltip\n                effect=\"dark\"\n                :content=\"$t('workflow.nodes.baseNode.fileUpload.tooltip')\"\n                placement=\"right\"\n              >\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n            <div>\n              <el-button\n                v-if=\"form_data.file_upload_enable\"\n                type=\"primary\"\n                link\n                @click=\"openFileUploadSettingDialog\"\n                class=\"mr-4\"\n              >\n                <AppIcon iconName=\"app-setting\" class=\"mr-4\"></AppIcon>\n              </el-button>\n              <el-switch\n                size=\"small\"\n                v-model=\"form_data.file_upload_enable\"\n                @change=\"switchFileUpload\"\n              />\n            </div>\n          </div>\n        </template>\n      </el-form-item>\n      <UserInputFieldTable ref=\"UserInputFieldTableFef\" :node-model=\"nodeModel\" />\n      <ApiInputFieldTable ref=\"ApiInputFieldTableFef\" :node-model=\"nodeModel\" />\n      <ChatFieldTable ref=\"ChatFieldTeble\" :node-model=\"nodeModel\"></ChatFieldTable>\n      <el-form-item>\n        <template #label>\n          <div class=\"flex-between\">\n            <span class=\"mr-4\">{{ $t('views.application.form.voiceInput.label') }}</span>\n            <div class=\"flex\">\n              <el-checkbox v-if=\"form_data.stt_model_enable\" v-model=\"form_data.stt_autosend\">{{\n                $t('views.application.form.voiceInput.autoSend')\n              }}</el-checkbox>\n              <el-switch\n                class=\"ml-8\"\n                size=\"small\"\n                v-model=\"form_data.stt_model_enable\"\n                @change=\"sttModelEnableChange\"\n              />\n            </div>\n          </div>\n        </template>\n        <ModelSelect\n          @wheel=\"wheel\"\n          v-show=\"form_data.stt_model_enable\"\n          v-model=\"form_data.stt_model_id\"\n          :placeholder=\"$t('views.application.form.voiceInput.placeholder')\"\n          :options=\"sttModelOptions\"\n          showFooter\n          :model-type=\"'STT'\"\n        ></ModelSelect>\n      </el-form-item>\n      <el-form-item>\n        <template #label>\n          <div class=\"flex-between\">\n            <span class=\"mr-4\">{{ $t('views.application.form.voicePlay.label') }}</span>\n            <div class=\"flex\">\n              <el-checkbox v-if=\"form_data.tts_model_enable\" v-model=\"form_data.tts_autoplay\">{{\n                $t('views.application.form.voicePlay.autoPlay')\n              }}</el-checkbox>\n              <el-switch\n                class=\"ml-8\"\n                size=\"small\"\n                v-model=\"form_data.tts_model_enable\"\n                @change=\"ttsModelEnableChange\"\n              />\n            </div>\n          </div>\n        </template>\n        <div class=\"w-full\">\n          <el-radio-group v-model=\"form_data.tts_type\" v-show=\"form_data.tts_model_enable\">\n            <el-radio :label=\"$t('views.application.form.voicePlay.browser')\" value=\"BROWSER\" />\n            <el-radio :label=\"$t('views.application.form.voicePlay.tts')\" value=\"TTS\" />\n          </el-radio-group>\n        </div>\n        <div class=\"flex-between w-full\">\n          <ModelSelect\n            @wheel=\"wheel\"\n            v-if=\"form_data.tts_type === 'TTS' && form_data.tts_model_enable\"\n            v-model=\"form_data.tts_model_id\"\n            :placeholder=\"$t('views.application.form.voicePlay.placeholder')\"\n            :options=\"ttsModelOptions\"\n            @change=\"ttsModelChange()\"\n            showFooter\n            :model-type=\"'TTS'\"\n          ></ModelSelect>\n\n          <el-button\n            v-if=\"form_data.tts_type === 'TTS' && form_data.tts_model_enable\"\n            @click=\"openTTSParamSettingDialog\"\n            :disabled=\"!form_data.tts_model_id\"\n            class=\"ml-8\"\n          >\n            <el-icon>\n              <el-icon><Operation /></el-icon>\n            </el-icon>\n          </el-button>\n        </div>\n      </el-form-item>\n    </el-form>\n    <TTSModeParamSettingDialog ref=\"TTSModeParamSettingDialogRef\" @refresh=\"refreshTTSForm\" />\n    <FileUploadSettingDialog\n      ref=\"FileUploadSettingDialogRef\"\n      :node-model=\"nodeModel\"\n      @refresh=\"refreshFileUploadForm\"\n    />\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { groupBy, set } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport type { FormInstance } from 'element-plus'\nimport { ref, computed, onMounted, nextTick, inject, provide } from 'vue'\nimport { MsgError, MsgSuccess, MsgWarning } from '@/utils/message'\nimport { t } from '@/locales'\nimport TTSModeParamSettingDialog from '@/views/application/component/TTSModeParamSettingDialog.vue'\nimport ApiInputFieldTable from './component/ApiInputFieldTable.vue'\nimport UserInputFieldTable from './component/UserInputFieldTable.vue'\nimport FileUploadSettingDialog from '@/workflow/nodes/base-node/component/FileUploadSettingDialog.vue'\nimport ChatFieldTable from './component/ChatFieldTable.vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst sttModelOptions = ref<any>(null)\nconst ttsModelOptions = ref<any>(null)\nconst TTSModeParamSettingDialogRef = ref<InstanceType<typeof TTSModeParamSettingDialog>>()\nconst UserInputFieldTableFef = ref()\nconst ApiInputFieldTableFef = ref()\nconst FileUploadSettingDialogRef = ref<InstanceType<typeof FileUploadSettingDialog>>()\n\nconst form = {\n  name: '',\n  desc: '',\n  prologue: t('views.application.form.defaultPrologue'),\n}\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'prologue', val)\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst baseNodeFormRef = ref<FormInstance>()\n\nconst validate = () => {\n  if (\n    form_data.value.tts_model_enable &&\n    !form_data.value.tts_model_id &&\n    form_data.value.tts_type === 'TTS'\n  ) {\n    return Promise.reject({\n      node: props.nodeModel,\n      errMessage: t('views.application.form.voicePlay.requiredMessage'),\n    })\n  }\n  if (form_data.value.stt_model_enable && !form_data.value.stt_model_id) {\n    return Promise.reject({\n      node: props.nodeModel,\n      errMessage: t('views.application.form.voiceInput.requiredMessage'),\n    })\n  }\n  return baseNodeFormRef.value?.validate().catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst resource = getResourceDetail()\n\nprovide('getSelectModelList', (params: any) => {\n  const obj =\n    apiType.value === 'systemManage'\n      ? { ...params, workspace_id: resource.value?.workspace_id }\n      : { ...params }\n  return loadSharedApi({ type: 'model', systemType: apiType.value }).getSelectModelList(obj)\n})\n\nprovide('getModelParamsForm', (model_id: string) => {\n  return loadSharedApi({ type: 'model', systemType: apiType.value }).getModelParamsForm(model_id)\n})\n\nfunction getSTTModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'STT',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'STT',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      sttModelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nfunction getTTSModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'TTS',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'TTS',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      ttsModelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nfunction ttsModelChange() {\n  nextTick(() => {\n    if (form_data.value.tts_model_id) {\n      TTSModeParamSettingDialogRef.value?.reset_default(form_data.value.tts_model_id, id)\n    } else {\n      refreshTTSForm({})\n    }\n  })\n}\n\nfunction ttsModelEnableChange() {\n  if (!form_data.value.tts_model_enable) {\n    form_data.value.tts_model_id = ''\n    form_data.value.tts_type = 'BROWSER'\n  }\n}\n\nfunction sttModelEnableChange() {\n  if (!form_data.value.stt_model_enable) {\n    form_data.value.stt_model_id = ''\n  }\n}\n\nconst openTTSParamSettingDialog = () => {\n  const model_id = form_data.value.tts_model_id\n  if (!model_id) {\n    MsgSuccess(t('views.application.form.voicePlay.requiredMessage'))\n    return\n  }\n  TTSModeParamSettingDialogRef.value?.open(model_id, id, form_data.value.tts_model_params_setting)\n}\n\nconst refreshTTSForm = (data: any) => {\n  form_data.value.tts_model_params_setting = data\n}\n\nconst switchFileUpload = () => {\n  const default_upload_setting = {\n    maxFiles: 3,\n    fileLimit: 50,\n    document: true,\n    image: false,\n    audio: false,\n    video: false,\n    other: false,\n    otherExtensions: ['ppt', 'doc'],\n  }\n\n  if (form_data.value.file_upload_enable) {\n    form_data.value.file_upload_setting =\n      form_data.value.file_upload_setting || default_upload_setting\n  }\n  props.nodeModel.graphModel.eventCenter.emit('refreshFileUploadConfig')\n}\nconst openFileUploadSettingDialog = () => {\n  FileUploadSettingDialogRef.value?.open(form_data.value.file_upload_setting)\n}\n\nconst refreshFileUploadForm = (data: any) => {\n  form_data.value.file_upload_setting = data\n}\n\nonMounted(() => {\n  set(props.nodeModel, 'validate', validate)\n  if (!props.nodeModel.properties.node_data.tts_type) {\n    set(props.nodeModel.properties.node_data, 'tts_type', 'BROWSER')\n  }\n  getTTSModel()\n  getSTTModel()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/condition-node/index.ts",
    "content": "import ConditioNodeVue from './index.vue'\nimport { cloneDeep, set } from 'lodash'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass ConditioNode extends AppNode {\n  constructor(props: any) {\n    super(props, ConditioNodeVue)\n  }\n}\nconst get_up_index_height = (condition_list: Array<any>, index: number) => {\n  return condition_list\n    .filter((item, i) => i < index)\n    .map((item) => item.height + 8)\n    .reduce((x, y) => x + y, 0)\n}\nclass ConditionModel extends AppNodeModel {\n  refreshBranch() {\n    // 更新节点连接边的path\n    this.incoming.edges.forEach((edge: any) => {\n      // 调用自定义的更新方案\n      edge.updatePathByAnchor()\n    })\n    this.outgoing.edges.forEach((edge: any) => {\n      edge.updatePathByAnchor()\n    })\n  }\n  getDefaultAnchor() {\n    const {\n      id,\n      x,\n      y,\n      width,\n      height,\n      properties: { branch_condition_list }\n    } = this\n    if (this.height === undefined) {\n      this.height = 200\n    }\n    const showNode = this.properties.showNode === undefined ? true : this.properties.showNode\n    const anchors: any = []\n    anchors.push({\n      x: x - width / 2 + 10,\n      y: showNode ? y : y - 15,\n      id: `${id}_left`,\n      edgeAddable: false,\n      type: 'left'\n    })\n\n    if (branch_condition_list) {\n      for (let index = 0; index < branch_condition_list.length; index++) {\n        const element = branch_condition_list[index]\n        const h = get_up_index_height(branch_condition_list, index)\n        anchors.push({\n          x: x + width / 2 - 10,\n          y: showNode ? y - height / 2 + 75 + h + element.height / 2 : y - 15,\n          id: `${id}_${element.id}_right`,\n          type: 'right'\n        })\n      }\n    }\n\n    return anchors\n  }\n}\nexport default {\n  type: 'condition-node',\n  model: ConditionModel,\n  view: ConditioNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/condition-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <el-form\n      :model=\"form_data\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      label-width=\"auto\"\n      ref=\"ConditionNodeFormRef\"\n      @submit.prevent\n    >\n      <VueDraggable\n        ref=\"el\"\n        v-bind:modelValue=\"form_data.branch\"\n        :disabled=\"form_data.branch.length === 2\"\n        handle=\".handle\"\n        :animation=\"150\"\n        ghostClass=\"ghost\"\n        @end=\"onEnd\"\n      >\n        <template v-for=\"(item, index) in form_data.branch\" :key=\"item.id\">\n          <el-card\n            v-resize=\"(wh: any) => resizeCondition(wh, item, index)\"\n            shadow=\"never\"\n            class=\"drag-card card-never mb-8\"\n            :class=\"{\n              'no-drag': index === form_data.branch.length - 1 || form_data.branch.length === 2,\n            }\"\n            style=\"--el-card-padding: 12px\"\n          >\n            <div class=\"handle flex-between lighter\">\n              <span class=\"flex align-center\">\n                <img src=\"@/assets/sort.svg\" alt=\"\" height=\"15\" class=\"handle-img mr-4\" />\n                {{ item.type }}\n              </span>\n              <div class=\"info\" v-if=\"item.conditions.length > 1\">\n                <span>{{\n                  $t('workflow.nodes.conditionNode.conditions.info')\n                }}</span>\n                <el-select\n                  :teleported=\"false\"\n                  v-model=\"item.condition\"\n                  size=\"small\"\n                  style=\"width: 60px; margin: 0 8px\"\n                >\n                  <el-option :label=\"$t('workflow.condition.AND')\" value=\"and\" />\n                  <el-option :label=\"$t('workflow.condition.OR')\" value=\"or\" />\n                </el-select>\n                <span>{{\n                  $t('workflow.nodes.conditionNode.conditions.label')\n                }}</span>\n              </div>\n            </div>\n            <div v-if=\"index !== form_data.branch.length - 1\" class=\"mt-8\">\n              <template v-for=\"(condition, cIndex) in item.conditions\" :key=\"cIndex\">\n                <el-row :gutter=\"8\">\n                  <el-col :span=\"11\">\n                    <el-form-item\n                      :prop=\"'branch.' + index + '.conditions.' + cIndex + '.field'\"\n                      :rules=\"{\n                        type: 'array',\n                        required: true,\n                        message: $t('workflow.variable.placeholder'),\n                        trigger: 'change',\n                      }\"\n                    >\n                      <NodeCascader\n                        ref=\"nodeCascaderRef\"\n                        :nodeModel=\"nodeModel\"\n                        class=\"w-full\"\n                        :placeholder=\"$t('workflow.variable.placeholder')\"\n                        v-model=\"condition.field\"\n                      />\n                    </el-form-item>\n                  </el-col>\n                  <el-col :span=\"6\">\n                    <el-form-item\n                      :prop=\"'branch.' + index + '.conditions.' + cIndex + '.compare'\"\n                      :rules=\"{\n                        required: true,\n                        message: $t(\n                          'workflow.nodes.conditionNode.conditions.requiredMessage',\n                        ),\n                        trigger: 'change',\n                      }\"\n                    >\n                      <el-select\n                        @wheel=\"wheel\"\n                        :teleported=\"false\"\n                        v-model=\"condition.compare\"\n                        :placeholder=\"\n                          $t(\n                            'workflow.nodes.conditionNode.conditions.requiredMessage',\n                          )\n                        \"\n                        clearable\n                        @change=\"changeCondition($event, index, cIndex)\"\n                      >\n                        <template v-for=\"(item, index) in compareList\" :key=\"index\">\n                          <el-option :label=\"item.label\" :value=\"item.value\" />\n                        </template>\n                      </el-select>\n                    </el-form-item>\n                  </el-col>\n                  <el-col :span=\"6\">\n                    <el-form-item\n                      v-if=\"\n                        !['is_null', 'is_not_null', 'is_true', 'is_not_true'].includes(\n                          condition.compare,\n                        )\n                      \"\n                      :prop=\"'branch.' + index + '.conditions.' + cIndex + '.value'\"\n                      :rules=\"{\n                        required: true,\n                        message: $t('workflow.nodes.conditionNode.valueMessage'),\n                        trigger: 'blur',\n                      }\"\n                    >\n                      <el-input\n                        v-model=\"condition.value\"\n                        :placeholder=\"\n                          $t('workflow.nodes.conditionNode.valueMessage')\n                        \"\n                      />\n                    </el-form-item>\n                  </el-col>\n                  <el-col :span=\"1\">\n                    <el-button\n                      :disabled=\"form_data.branch.length === 2 && item.conditions.length === 1\"\n                      link\n                      type=\"info\"\n                      class=\"mt-4\"\n                      @click=\"deleteCondition(index, cIndex)\"\n                    >\n                      <AppIcon iconName=\"app-delete\"></AppIcon>\n                    </el-button>\n                  </el-col>\n                </el-row>\n              </template>\n            </div>\n\n            <el-button\n              link\n              type=\"primary\"\n              @click=\"addCondition(index)\"\n              v-if=\"index !== form_data.branch.length - 1\"\n            >\n              <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n              {{ $t('workflow.nodes.conditionNode.addCondition') }}\n            </el-button>\n          </el-card>\n        </template>\n      </VueDraggable>\n      <el-button link type=\"primary\" @click=\"addBranch\">\n        <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n        {{ $t('workflow.nodes.conditionNode.addBranch') }}\n      </el-button>\n    </el-form>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { cloneDeep, set } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormInstance } from 'element-plus'\nimport { ref, computed, onMounted, nextTick } from 'vue'\nimport { randomId } from '@/utils/common'\nimport { compareList } from '@/workflow/common/data'\nimport { VueDraggable } from 'vue-draggable-plus'\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst form = {\n  branch: [\n    {\n      conditions: [\n        {\n          field: [],\n          compare: '',\n          value: '',\n        },\n      ],\n      id: randomId(),\n      type: 'IF',\n      condition: 'and',\n    },\n    {\n      conditions: [],\n      id: randomId(),\n      type: 'ELSE',\n      condition: 'and',\n    },\n  ],\n}\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\nconst resizeCondition = (wh: any, row: any, index: number) => {\n  const branch_condition_list = cloneDeep(\n    props.nodeModel.properties.branch_condition_list\n      ? props.nodeModel.properties.branch_condition_list\n      : [],\n  )\n  const new_branch_condition_list = branch_condition_list.map((item: any) => {\n    if (item.id === row.id) {\n      return { ...item, height: wh.height, index: index }\n    }\n    return item\n  })\n  set(props.nodeModel.properties, 'branch_condition_list', new_branch_condition_list)\n  refreshBranchAnchor(props.nodeModel.properties.node_data.branch, true)\n}\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n      refreshBranchAnchor(form.branch, true)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst ConditionNodeFormRef = ref<FormInstance>()\nconst nodeCascaderRef = ref()\nconst validate = () => {\n  const v_list = [\n    ConditionNodeFormRef.value?.validate(),\n    ...nodeCascaderRef.value.map((item: any) => item.validate()),\n  ]\n  return Promise.all(v_list).catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nfunction onEnd(event?: any) {\n  const { oldIndex, newIndex } = event\n  if (oldIndex === undefined || newIndex === undefined) return\n  const list = cloneDeep(props.nodeModel.properties.node_data.branch)\n  if (oldIndex === list.length - 1 || newIndex === list.length - 1) {\n    return\n  }\n  const newInstance = { ...list[oldIndex], type: list[newIndex].type, id: list[newIndex].id }\n  const oldInstance = { ...list[newIndex], type: list[oldIndex].type, id: list[oldIndex].id }\n  list[newIndex] = newInstance\n  list[oldIndex] = oldInstance\n  set(props.nodeModel.properties.node_data, 'branch', list)\n}\n\nfunction addBranch() {\n  const list = cloneDeep(props.nodeModel.properties.node_data.branch)\n  const obj = {\n    conditions: [\n      {\n        field: [],\n        compare: '',\n        value: '',\n      },\n    ],\n    type: 'ELSE IF ' + (list.length - 1),\n    id: randomId(),\n    condition: 'and',\n  }\n  list.splice(list.length - 1, 0, obj)\n  refreshBranchAnchor(list, true)\n  set(props.nodeModel.properties.node_data, 'branch', list)\n}\nfunction refreshBranchAnchor(list: Array<any>, is_add: boolean) {\n  const branch_condition_list = cloneDeep(\n    props.nodeModel.properties.branch_condition_list\n      ? props.nodeModel.properties.branch_condition_list\n      : [],\n  )\n  const new_branch_condition_list = list\n    .map((item, index) => {\n      const find = branch_condition_list.find((b: any) => b.id === item.id)\n      if (find) {\n        return { index: index, height: find.height, id: item.id }\n      } else {\n        if (is_add) {\n          return { index: index, height: 12, id: item.id }\n        }\n      }\n    })\n    .filter((item) => item)\n\n  set(props.nodeModel.properties, 'branch_condition_list', new_branch_condition_list)\n  props.nodeModel.refreshBranch()\n}\n\nfunction addCondition(index: number) {\n  const list = cloneDeep(props.nodeModel.properties.node_data.branch)\n  list[index]['conditions'].push({\n    field: [],\n    compare: '',\n    value: '',\n  })\n  set(props.nodeModel.properties.node_data, 'branch', list)\n}\n\nfunction deleteCondition(index: number, cIndex: number) {\n  const list = cloneDeep(props.nodeModel.properties.node_data.branch)\n  list[index]['conditions'].splice(cIndex, 1)\n  if (list[index]['conditions'].length === 0) {\n    const delete_edge = list.splice(index, 1)\n    const delete_target_anchor_id_list = delete_edge.map(\n      (item: any) => props.nodeModel.id + '_' + item.id + '_right',\n    )\n\n    props.nodeModel.graphModel.eventCenter.emit(\n      'delete_edge',\n      props.nodeModel.outgoing.edges\n        .filter((item: any) => delete_target_anchor_id_list.includes(item.sourceAnchorId))\n        .map((item: any) => item.id),\n    )\n    refreshBranchAnchor(list, false)\n\n    list.forEach((item: any, index: number) => {\n      if (item.type === 'ELSE IF ' + (index + 1)) {\n        item.type = 'ELSE IF ' + index\n      }\n    })\n  }\n  set(props.nodeModel.properties.node_data, 'branch', list)\n}\n\nfunction changeCondition(val: string, index: number, cIndex: number) {\n  if (['is_null', 'is_not_null', 'is_true', 'is_not_true'].includes(val)) {\n    const list = cloneDeep(props.nodeModel.properties.node_data.branch)\n    list[index]['conditions'][cIndex].value = 1\n    set(props.nodeModel.properties.node_data, 'branch', list)\n  }\n}\n\nonMounted(() => {\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped>\n\n</style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/data-source-local-node/index.ts",
    "content": "import DataSourceWebNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass DataSourceWebNode extends AppNode {\n  constructor(props: any) {\n    super(props, DataSourceWebNodeVue)\n  }\n}\nexport default {\n  type: 'data-source-local-node',\n  model: AppNodeModel,\n  view: DataSourceWebNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/data-source-local-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"NodeFormRef\"\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.dataSourceLocalNode.fileFormat.label')\"\n          :rules=\"{\n            type: 'array',\n            required: true,\n            message: $t('workflow.nodes.dataSourceLocalNode.fileFormat.requiredMessage'),\n            trigger: 'change',\n          }\"\n          prop=\"file_type_list\"\n        >\n          <el-select\n            v-model=\"form_data.file_type_list\"\n            :placeholder=\"$t('workflow.nodes.dataSourceLocalNode.fileFormat.requiredMessage')\"\n            class=\"w-240\"\n            clearable\n            multiple\n            allow-create\n            filterable\n            default-first-option\n          >\n            <template #label=\"{ label, value }\">\n              <span>{{ label }} </span>\n            </template>\n            <el-option\n              v-for=\"item in file_type_list_options\"\n              :key=\"item\"\n              :label=\"item\"\n              :value=\"item\"\n            />\n          </el-select>\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.dataSourceLocalNode.maxFileNumber.label')\"\n          :rules=\"{\n            required: true,\n            message: $t('common.inputPlaceholder'),\n            trigger: 'change',\n          }\"\n          prop=\"file_count_limit\"\n        >\n          <el-input-number\n            v-model=\"form_data.file_count_limit\"\n            :min=\"1\"\n            :max=\"1000\"\n            :value-on-clear=\"0\"\n            controls-position=\"right\"\n            class=\"w-full\"\n            :step=\"1\"\n            :step-strictly=\"true\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.dataSourceLocalNode.maxFileCountNumber.label')\"\n          :rules=\"{\n            required: true,\n            message: $t('common.inputPlaceholder'),\n            trigger: 'change',\n          }\"\n          prop=\"file_size_limit\"\n        >\n          <el-input-number\n            v-model=\"form_data.file_size_limit\"\n            :min=\"1\"\n            :max=\"1000\"\n            :value-on-clear=\"0\"\n            controls-position=\"right\"\n            class=\"w-full\"\n            :step=\"1\"\n            :step-strictly=\"true\"\n          />\n        </el-form-item>\n      </el-form>\n    </el-card>\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, onMounted, ref } from 'vue'\nimport { set } from 'lodash'\nconst NodeFormRef = ref()\nconst props = defineProps<{ nodeModel: any }>()\n\nconst file_type_list_options = ['TXT', 'DOCX', 'PDF', 'HTML', 'XLS', 'XLSX', 'ZIP', 'CSV', 'MD']\nconst form = {\n  file_type_list: ['TXT', 'DOCX', 'PDF', 'HTML', 'XLS', 'XLSX', 'ZIP', 'CSV', 'MD'],\n  file_size_limit: 100,\n  file_count_limit: 50,\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\nconst validate = () => {\n  return NodeFormRef.value.validate()\n}\n\nonMounted(() => {\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/data-source-web-node/index.ts",
    "content": "import DataSourceWebNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass DataSourceWebNode extends AppNode {\n  constructor(props: any) {\n    super(props, DataSourceWebNodeVue)\n  }\n}\nexport default {\n  type: 'data-source-web-node',\n  model: AppNodeModel,\n  view: DataSourceWebNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/data-source-web-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <span class=\"lighter\">{{ $t('common.noData') }}</span>\n    </el-card>\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed } from 'vue'\nimport { set } from 'lodash'\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst form = {\n  document_list: ['start-node', 'document'],\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/document-extract-node/index.ts",
    "content": "import DocumentExtractNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass RerankerNode extends AppNode {\n  constructor(props: any) {\n    super(props, DocumentExtractNodeVue)\n  }\n}\nexport default {\n  type: 'document-extract-node',\n  model: AppNodeModel,\n  view: RerankerNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/document-extract-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n      >\n        <el-form-item :label=\"$t('views.problem.relateParagraph.selectDocument')\" :rules=\"{\n            type: 'array',\n            required: true,\n            message: $t('views.chatLog.documentPlaceholder'),\n            trigger: 'change'\n          }\"\n        >\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('views.chatLog.documentPlaceholder')\"\n            v-model=\"form_data.document_list\"\n          />\n        </el-form-item>\n      </el-form>\n    </el-card>\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, ref, onMounted } from 'vue'\nimport { set } from 'lodash'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst form = {\n  document_list: [\"start-node\", \"document\"]\n}\n\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  }\n})\n\nconst nodeCascaderRef = ref()\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n  ]).catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nonMounted(() => {\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style lang=\"scss\" scoped>\n\n</style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/document-split-node/index.ts",
    "content": "import DocumentSplitNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass DocumentSplitNode extends AppNode {\n  constructor(props: any) {\n    super(props, DocumentSplitNodeVue)\n  }\n}\n\nexport default {\n  type: 'document-split-node',\n  model: AppNodeModel,\n  view: DocumentSplitNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/document-split-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        ref=\"aiChatNodeFormRef\"\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n      >\n        <el-form-item\n          :label=\"$t('views.problem.relateParagraph.selectDocument')\"\n          :rules=\"{\n            type: 'array',\n            required: true,\n            message: $t('views.chatLog.documentPlaceholder'),\n            trigger: 'change',\n          }\"\n        >\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('views.chatLog.documentPlaceholder')\"\n            v-model=\"form_data.document_list\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.documentSplitNode.splitStrategy.label')\"\n          :rules=\"{\n            required: true,\n            message: $t('workflow.nodes.documentSplitNode.splitStrategy.requiredMessage'),\n            trigger: 'change',\n          }\"\n        >\n          <el-select\n            v-model=\"form_data.split_strategy\"\n            :placeholder=\"$t('workflow.nodes.documentSplitNode.splitStrategy.placeholder')\"\n            :teleported=\"false\"\n          >\n            <el-option :label=\"$t('views.document.setRules.intelligent.label')\" value=\"auto\" />\n            <el-option :label=\"$t('views.document.setRules.advanced.label')\" value=\"custom\" />\n            <el-option :label=\"$t('views.document.fileType.QA.label')\" value=\"qa\" />\n          </el-select>\n        </el-form-item>\n        <el-form-item>\n          <template #label>\n            <div class=\"flex-between\">\n              <span class=\"flex align-center\">\n                <span>{{ $t('workflow.nodes.documentSplitNode.chunk_length.label') }}</span>\n                <el-tooltip effect=\"dark\" placement=\"right\">\n                  <template #content>\n                    {{ $t('workflow.nodes.documentSplitNode.chunk_length.tooltip1') }}<br />\n                    {{ $t('workflow.nodes.documentSplitNode.chunk_length.tooltip2') }}<br />\n                    {{ $t('workflow.nodes.documentSplitNode.chunk_length.tooltip3') }}\n                  </template>\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                </el-tooltip>\n              </span>\n              <el-select\n                v-model=\"form_data.chunk_size_type\"\n                size=\"small\"\n                style=\"width: 85px\"\n                :teleported=\"false\"\n              >\n                <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"referencing\" />\n                <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n              </el-select>\n            </div>\n          </template>\n          <el-input-number\n            v-if=\"form_data.chunk_size_type === 'custom'\"\n            v-model=\"form_data.chunk_size\"\n            :min=\"50\"\n            :max=\"100000\"\n            :value-on-clear=\"0\"\n            controls-position=\"right\"\n            class=\"w-full\"\n            :step=\"1\"\n            :step-strictly=\"true\"\n          />\n          <NodeCascader\n            v-else\n            ref=\"nodeCascaderRef4\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('views.chatLog.documentPlaceholder')\"\n            v-model=\"form_data.chunk_size_reference\"\n          />\n        </el-form-item>\n\n        <el-form-item v-if=\"form_data.split_strategy === 'custom'\">\n          <template #label>\n            <div class=\"flex-between\">\n              <div class=\"flex align-center mb-8\">\n                <span class=\"mr-4\">\n                  {{ $t('views.document.setRules.patterns.label') }}\n                </span>\n                <el-tooltip\n                  effect=\"dark\"\n                  :content=\"$t('views.document.setRules.patterns.tooltip')\"\n                  placement=\"right\"\n                >\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                </el-tooltip>\n              </div>\n              <el-select\n                :teleported=\"false\"\n                v-model=\"form_data.patterns_type\"\n                size=\"small\"\n                style=\"width: 85px\"\n              >\n                <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"referencing\" />\n                <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n              </el-select>\n            </div>\n          </template>\n          <el-select\n            :teleported=\"false\"\n            v-if=\"form_data.patterns_type === 'custom'\"\n            v-model=\"form_data.patterns\"\n            multiple\n            :reserve-keyword=\"false\"\n            allow-create\n            default-first-option\n            filterable\n            :placeholder=\"$t('views.document.setRules.patterns.placeholder')\"\n          >\n            <el-option\n              v-for=\"(item, index) in splitPatternList\"\n              :key=\"index\"\n              :label=\"item.key\"\n              :value=\"item.value\"\n            >\n            </el-option>\n          </el-select>\n          <NodeCascader\n            v-else\n            ref=\"nodeCascaderRef5\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('views.chatLog.documentPlaceholder')\"\n            v-model=\"form_data.patterns_reference\"\n          />\n        </el-form-item>\n        <el-form-item v-if=\"form_data.split_strategy === 'custom'\">\n          <template #label>\n            <div class=\"flex-between\">\n              <span>\n                {{ $t('views.document.setRules.limit.label') }}\n              </span>\n              <el-select\n                v-model=\"form_data.limit_type\"\n                size=\"small\"\n                style=\"width: 85px\"\n                :teleported=\"false\"\n              >\n                <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"referencing\" />\n                <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n              </el-select>\n            </div>\n          </template>\n          <el-input-number\n            v-if=\"form_data.limit_type === 'custom'\"\n            v-model=\"form_data.limit\"\n            :min=\"50\"\n            :max=\"100000\"\n            :value-on-clear=\"0\"\n            controls-position=\"right\"\n            class=\"w-full\"\n            :step=\"1\"\n            :step-strictly=\"true\"\n          />\n          <NodeCascader\n            v-else\n            ref=\"nodeCascaderRef6\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('views.chatLog.documentPlaceholder')\"\n            v-model=\"form_data.limit_reference\"\n          />\n        </el-form-item>\n        <el-form-item v-if=\"form_data.split_strategy === 'custom'\">\n          <template #label>\n            <div class=\"flex-between\">\n              <div class=\"flex align-center mb-8\">\n                <span class=\"mr-4\">\n                  {{ $t('views.document.setRules.with_filter.label') }}\n                </span>\n                <el-tooltip\n                  effect=\"dark\"\n                  :content=\"$t('views.document.setRules.with_filter.text')\"\n                  placement=\"right\"\n                >\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                </el-tooltip>\n              </div>\n              <el-select\n                v-model=\"form_data.with_filter_type\"\n                size=\"small\"\n                style=\"width: 85px\"\n                :teleported=\"false\"\n              >\n                <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"referencing\" />\n                <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n              </el-select>\n            </div>\n          </template>\n          <el-switch\n            v-if=\"form_data.with_filter_type === 'custom'\"\n            size=\"small\"\n            v-model=\"form_data.with_filter\"\n          />\n          <NodeCascader\n            v-else\n            ref=\"nodeCascaderRef7\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('views.chatLog.documentPlaceholder')\"\n            v-model=\"form_data.with_filter_reference\"\n          />\n        </el-form-item>\n        <el-form-item v-if=\"form_data.split_strategy !== 'qa'\">\n          <template #label>\n            <div class=\"flex-between\">\n              <span> {{ $t('workflow.nodes.documentSplitNode.title1') }}</span>\n              <el-select\n                v-model=\"form_data.paragraph_title_relate_problem_type\"\n                size=\"small\"\n                style=\"width: 85px\"\n                :teleported=\"false\"\n              >\n                <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"referencing\" />\n                <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n              </el-select>\n            </div>\n          </template>\n          <el-switch\n            v-if=\"form_data.paragraph_title_relate_problem_type === 'custom'\"\n            size=\"small\"\n            v-model=\"form_data.paragraph_title_relate_problem\"\n          />\n          <NodeCascader\n            v-else\n            ref=\"nodeCascaderRef2\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('views.chatLog.documentPlaceholder')\"\n            v-model=\"form_data.paragraph_title_relate_problem_reference\"\n          />\n        </el-form-item>\n        <el-form-item>\n          <template #label>\n            <div class=\"flex-between\">\n              <span>{{ $t('workflow.nodes.documentSplitNode.title2') }}</span>\n              <el-select\n                v-model=\"form_data.document_name_relate_problem_type\"\n                size=\"small\"\n                style=\"width: 85px\"\n                :teleported=\"false\"\n              >\n                <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"referencing\" />\n                <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n              </el-select>\n            </div>\n          </template>\n          <el-switch\n            v-if=\"form_data.document_name_relate_problem_type === 'custom'\"\n            size=\"small\"\n            v-model=\"form_data.document_name_relate_problem\"\n          />\n          <NodeCascader\n            v-else\n            ref=\"nodeCascaderRef3\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('views.chatLog.documentPlaceholder')\"\n            v-model=\"form_data.document_name_relate_problem_reference\"\n          />\n        </el-form-item>\n      </el-form>\n    </el-card>\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, onMounted, ref } from 'vue'\nimport { set } from 'lodash'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormInstance } from 'element-plus'\nimport type { KeyValue } from '@/api/type/common.ts'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api.ts'\nimport { useRoute } from 'vue-router'\n\nconst route = useRoute()\nconst {\n  query: { id }, // id为knowledgeID\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst props = defineProps<{ nodeModel: any }>()\nconst splitPatternList = ref<Array<KeyValue<string, string>>>([])\n\nconst form = {\n  document_list: [],\n  split_strategy: 'auto',\n  paragraph_title_relate_problem_type: 'custom',\n  paragraph_title_relate_problem: false,\n  paragraph_title_relate_problem_reference: [],\n  document_name_relate_problem_type: 'custom',\n  document_name_relate_problem: false,\n  document_name_relate_problem_reference: [],\n  limit: 4096,\n  limit_type: 'custom',\n  limit_reference: [],\n  chunk_size: 256,\n  chunk_size_type: 'custom',\n  chunk_size_reference: [],\n  patterns: [],\n  patterns_type: 'custom',\n  patterns_reference: [],\n  with_filter: false,\n  with_filter_type: 'custom',\n  with_filter_reference: [],\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst aiChatNodeFormRef = ref<FormInstance>()\nconst nodeCascaderRef = ref()\nconst nodeCascaderRef2 = ref()\nconst nodeCascaderRef3 = ref()\nconst nodeCascaderRef4 = ref()\nconst nodeCascaderRef5 = ref()\nconst nodeCascaderRef6 = ref()\nconst nodeCascaderRef7 = ref()\n\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n    nodeCascaderRef2.value ? nodeCascaderRef2.value.validate() : Promise.resolve(''),\n    nodeCascaderRef3.value ? nodeCascaderRef3.value.validate() : Promise.resolve(''),\n    nodeCascaderRef4.value ? nodeCascaderRef4.value.validate() : Promise.resolve(''),\n    nodeCascaderRef5.value ? nodeCascaderRef5.value.validate() : Promise.resolve(''),\n    nodeCascaderRef6.value ? nodeCascaderRef6.value.validate() : Promise.resolve(''),\n    nodeCascaderRef7.value ? nodeCascaderRef7.value.validate() : Promise.resolve(''),\n    aiChatNodeFormRef.value?.validate(),\n  ]).catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst patternLoading = ref<boolean>(false)\nconst initSplitPatternList = () => {\n  loadSharedApi({ type: 'document', systemType: apiType.value })\n    .listSplitPattern(id, patternLoading)\n    .then((ok: any) => {\n      splitPatternList.value = ok.data\n    })\n}\n\nonMounted(() => {\n  initSplitPatternList()\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/form-node/index.ts",
    "content": "import FormNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass FormNode extends AppNode {\n  constructor(props: any) {\n    super(props, FormNodeVue)\n  }\n  getConfig(props: any) {\n    return props.model.properties.config\n  }\n}\nexport default {\n  type: 'form-node',\n  model: AppNodeModel,\n  view: FormNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/form-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"formNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.formNode.formContent.label')\"\n          prop=\"form_content_format\"\n          :rules=\"{\n            required: true,\n            message: $t('workflow.nodes.formNode.formContent.requiredMessage'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span\n                  >{{ $t('workflow.nodes.formNode.formContent.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{\n                    $t('workflow.nodes.formNode.formContent.tooltip', {\n                      form: '{ form }',\n                    })\n                  }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            :title=\"$t('workflow.nodes.formNode.formContent.label')\"\n            v-model=\"form_data.form_content_format\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitDialog\"\n          />\n        </el-form-item>\n        <el-form-item :label=\"$t('workflow.nodes.formNode.formSetting')\" @click.prevent>\n          <template #label>\n            <div class=\"flex-between\">\n              <h5 class=\"lighter\">\n                {{ $t('workflow.nodes.formNode.formSetting') }}\n              </h5>\n              <el-button link type=\"primary\" @click=\"openAddFormCollect()\">\n                <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                {{ $t('common.add') }}\n              </el-button>\n            </div></template\n          >\n\n          <el-table\n            class=\"border\"\n            v-if=\"form_data.form_field_list.length > 0\"\n            :data=\"form_data.form_field_list\"\n            ref=\"tableRef\"\n            row-key=\"field\"\n          >\n            <el-table-column\n              prop=\"field\"\n              :label=\"$t('dynamicsForm.paramForm.field.label')\"\n              width=\"95\"\n            >\n              <template #default=\"{ row }\">\n                <span :title=\"row.field\" class=\"ellipsis-1\">{{ row.field }}</span>\n              </template>\n            </el-table-column>\n            <el-table-column prop=\"label\" :label=\"$t('dynamicsForm.paramForm.name.label')\">\n              <template #default=\"{ row }\">\n                <span v-if=\"row.label && row.label.input_type === 'TooltipLabel'\">\n                  <span :title=\"row.label.label\" class=\"ellipsis-1\">\n                    {{ row.label.label }}\n                  </span>\n                </span>\n                <span v-else>\n                  <span :title=\"row.label\" class=\"ellipsis-1\">\n                    {{ row.label }}\n                  </span></span\n                >\n              </template>\n            </el-table-column>\n\n            <el-table-column :label=\"$t('dynamicsForm.paramForm.input_type.label')\" width=\"110px\">\n              <template #default=\"{ row }\">\n                <el-tag size=\"small\" type=\"info\" class=\"info-tag\">{{\n                  input_type_list.find((item) => item.value === row.input_type)?.label\n                }}</el-tag>\n              </template>\n            </el-table-column>\n\n            <el-table-column prop=\"default_value\" :label=\"$t('dynamicsForm.default.label')\">\n              <template #default=\"{ row }\">\n                <span :title=\"row.default_value\" class=\"ellipsis-1\">{{\n                  getDefaultValue(row)\n                }}</span>\n              </template>\n            </el-table-column>\n            <el-table-column :label=\"$t('common.required')\" width=\"55\">\n              <template #default=\"{ row }\">\n                <div @click.stop>\n                  <el-switch disabled size=\"small\" v-model=\"row.required\" />\n                </div>\n              </template>\n            </el-table-column>\n            <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"80\">\n              <template #default=\"{ row, $index }\">\n                <span class=\"mr-4\">\n                  <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n                    <el-button type=\"primary\" text @click.stop=\"openEditFormCollect(row, $index)\">\n                      <AppIcon iconName=\"app-edit\"></AppIcon>\n                    </el-button>\n                  </el-tooltip>\n                </span>\n                <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n                  <el-button type=\"primary\" text @click=\"deleteField(row)\">\n                    <AppIcon iconName=\"app-delete\"></AppIcon>\n                  </el-button>\n                </el-tooltip>\n              </template>\n            </el-table-column>\n          </el-table>\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <AddFormCollect ref=\"addFormCollectRef\" :addFormField=\"addFormField\"></AddFormCollect>\n    <EditFormCollect ref=\"editFormCollectRef\" :editFormField=\"editFormField\"></EditFormCollect>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport AddFormCollect from '@/workflow/common/AddFormCollect.vue'\nimport EditFormCollect from '@/workflow/common/EditFormCollect.vue'\nimport { type FormInstance } from 'element-plus'\nimport { ref, onMounted, computed, provide } from 'vue'\nimport { input_type_list } from '@/components/dynamics-form/constructor/data'\nimport { MsgError } from '@/utils/message'\nimport { set, cloneDeep } from 'lodash'\nimport Sortable from 'sortablejs'\nimport { t } from '@/locales'\nconst props = defineProps<{ nodeModel: any }>()\nprovide('getModel', () => props.nodeModel)\nconst formNodeFormRef = ref<FormInstance>()\nconst tableRef = ref()\nconst editFormField = (form_field_data: any, field_index: number) => {\n  const _value = form_data.value.form_field_list.map((item: any, index: number) => {\n    if (field_index === index) {\n      return cloneDeep(form_field_data)\n    }\n    return cloneDeep(item)\n  })\n  form_data.value.form_field_list = _value\n  sync_form_field_list()\n}\nconst addFormField = (form_field_data: any) => {\n  if (form_data.value.form_field_list.some((field: any) => field.field === form_field_data.field)) {\n    MsgError(t('workflow.tip.paramErrorMessage') + form_field_data.field)\n    return\n  }\n  form_data.value.form_field_list = cloneDeep([...form_data.value.form_field_list, form_field_data])\n  sync_form_field_list()\n}\nconst sync_form_field_list = () => {\n  const fields = [\n    {\n      label: t('workflow.nodes.formNode.formAllContent'),\n      value: 'form_data',\n    },\n    ...form_data.value.form_field_list.map((item: any) => ({\n      value: item.field,\n      label: typeof item.label == 'string' ? item.label : item.label.label,\n    })),\n  ]\n  set(props.nodeModel.properties.config, 'fields', fields)\n  props.nodeModel.clear_next_node_field(false)\n  onDragHandle()\n}\nconst addFormCollectRef = ref<InstanceType<typeof AddFormCollect>>()\nconst editFormCollectRef = ref<InstanceType<typeof EditFormCollect>>()\nconst openAddFormCollect = () => {\n  addFormCollectRef.value?.open()\n}\nconst openEditFormCollect = (form_field_data: any, index: number) => {\n  editFormCollectRef.value?.open(cloneDeep(form_field_data), index)\n}\nconst deleteField = (form_field_data: any) => {\n  form_data.value.form_field_list = form_data.value.form_field_list.filter(\n    (field: any) => field.field !== form_field_data.field,\n  )\n  sync_form_field_list()\n}\nconst form = ref<any>({\n  is_result: true,\n  form_content_format: `${t('workflow.nodes.formNode.form_content_format1')}\n{{form}}\n${t('workflow.nodes.formNode.form_content_format2')}`,\n  form_field_list: [],\n})\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form.value)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst getDefaultValue = (row: any) => {\n  if (row.default_value) {\n    const default_value = row.option_list\n      ?.filter((v: any) => row.default_value.indexOf(v.value) > -1)\n      .map((v: any) => v.label)\n      .join(',')\n    if (default_value) {\n      return default_value\n    }\n    return row.default_value\n  }\n  if (row.default_value !== undefined) {\n    return row.default_value\n  }\n}\n\nconst validate = () => {\n  return formNodeFormRef.value?.validate()\n}\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'form_content_format', val)\n}\n\n// 表格排序拖拽\nfunction onDragHandle() {\n  if (!tableRef.value) return\n\n  // 获取表格的 tbody DOM 元素\n  const wrapper = tableRef.value.$el as HTMLElement\n  const tbody = wrapper.querySelector('.el-table__body-wrapper tbody')\n  if (!tbody) return\n  // 初始化 Sortable\n  Sortable.create(tbody as HTMLElement, {\n    animation: 150,\n    ghostClass: 'ghost-row',\n    onEnd: (evt) => {\n      if (evt.oldIndex === undefined || evt.newIndex === undefined) return\n      // 更新数据顺序\n      const items = cloneDeep([...form_data.value.form_field_list])\n      const [movedItem] = items.splice(evt.oldIndex, 1)\n      items.splice(evt.newIndex, 0, movedItem)\n      form_data.value.form_field_list = items\n      sync_form_field_list()\n    },\n  })\n}\nonMounted(() => {\n  set(props.nodeModel, 'validate', validate)\n  sync_form_field_list()\n  props.nodeModel.graphModel.eventCenter.emit('refresh_incoming_node_field')\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/image-generate/index.ts",
    "content": "import ImageGenerateNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass RerankerNode extends AppNode {\n  constructor(props: any) {\n    super(props, ImageGenerateNodeVue)\n  }\n}\n\nexport default {\n  type: 'image-generate-node',\n  model: AppNodeModel,\n  view: RerankerNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/image-generate/index.vue",
    "content": "<template>\n  <NodeContainer :node-model=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"aiChatNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.imageGenerateNode.model.label')\"\n          prop=\"model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('workflow.nodes.imageGenerateNode.model.requiredMessage'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ $t('workflow.nodes.imageGenerateNode.model.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button\n                :disabled=\"!form_data.model_id\"\n                type=\"primary\"\n                link\n                @click=\"openAIParamSettingDialog(form_data.model_id)\"\n                @refreshForm=\"refreshParam\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n\n          <ModelSelect\n            @change=\"model_change\"\n            @wheel=\"wheel\"\n            :teleported=\"false\"\n            v-model=\"form_data.model_id\"\n            :placeholder=\"$t('workflow.nodes.imageGenerateNode.model.requiredMessage')\"\n            :options=\"modelOptions\"\n            showFooter\n            @focus=\"getSelectModel\"\n            :model-type=\"'TTI'\"\n          ></ModelSelect>\n        </el-form-item>\n\n        <el-form-item\n          :label=\"$t('workflow.nodes.imageGenerateNode.prompt.label')\"\n          prop=\"prompt\"\n          :rules=\"{\n            required: true,\n            message: $t('common.prompt.placeholder'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span\n                  >{{ $t('workflow.nodes.imageGenerateNode.prompt.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content\n                  >{{ $t('workflow.nodes.imageGenerateNode.prompt.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            @wheel=\"wheel\"\n            :title=\"$t('workflow.nodes.imageGenerateNode.prompt.label')\"\n            v-model=\"form_data.prompt\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitDialog\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.imageGenerateNode.negative_prompt.label')\"\n          prop=\"prompt\"\n          :rules=\"{\n            required: false,\n            message: $t('common.prompt.placeholder'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.imageGenerateNode.negative_prompt.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content\n                  >{{ $t('workflow.nodes.imageGenerateNode.negative_prompt.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            @wheel=\"wheel\"\n            :title=\"$t('workflow.nodes.imageGenerateNode.negative_prompt.label')\"\n            v-model=\"form_data.negative_prompt\"\n            :placeholder=\"$t('workflow.nodes.imageGenerateNode.negative_prompt.placeholder')\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitNegativeDialog\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n          @click.prevent\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.is_result\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshParam\" />\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, nextTick, onMounted, ref, inject } from 'vue'\nimport { groupBy, set } from 'lodash'\nimport type { FormInstance } from 'element-plus'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\nimport { t } from '@/locales'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { WorkflowMode } from '@/enums/application'\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst props = defineProps<{ nodeModel: any }>()\nconst modelOptions = ref<any>(null)\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\n\nconst aiChatNodeFormRef = ref<FormInstance>()\nconst validate = () => {\n  return aiChatNodeFormRef.value?.validate().catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nconst defaultPrompt = `{{${t('workflow.nodes.startNode.label')}.question}}`\n\nconst form = {\n  model_id: '',\n  system: '',\n  prompt: defaultPrompt,\n  negative_prompt: '',\n  dialogue_number: 0,\n  dialogue_type: 'NODE',\n  is_result: true,\n  temperature: null,\n  max_tokens: null,\n  image_list: ['start-node', 'image'],\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst resource = getResourceDetail()\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'TTI',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'TTI',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nconst model_change = () => {\n  nextTick(() => {\n    if (form_data.value.model_id) {\n      AIModeParamSettingDialogRef.value?.reset_default(form_data.value.model_id, id)\n    } else {\n      refreshParam({})\n    }\n  })\n}\n\nconst openAIParamSettingDialog = (modelId: string) => {\n  if (modelId) {\n    AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting)\n  }\n}\n\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'model_params_setting', data)\n}\n\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'prompt', val)\n}\n\nfunction submitNegativeDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'negative_prompt', val)\n}\n\nonMounted(() => {\n  getSelectModel()\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/image-to-video/index.ts",
    "content": "import VideoGenerateNodeVue from './index.vue'\nimport {AppNode, AppNodeModel} from '@/workflow/common/app-node'\n\nclass VideoNode extends AppNode {\n  constructor(props: any) {\n    super(props, VideoGenerateNodeVue)\n  }\n}\n\nexport default {\n  type: 'image-to-video-node',\n  model: AppNodeModel,\n  view: VideoNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/image-to-video/index.vue",
    "content": "<template>\n  <NodeContainer :node-model=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"aiChatNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.imageToVideoGenerate.model.label')\"\n          prop=\"model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('workflow.nodes.imageToVideoGenerate.model.requiredMessage'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ $t('workflow.nodes.imageToVideoGenerate.model.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button\n                :disabled=\"!form_data.model_id\"\n                type=\"primary\"\n                link\n                @click=\"openAIParamSettingDialog(form_data.model_id)\"\n                @refreshForm=\"refreshParam\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n\n          <ModelSelect\n            @change=\"model_change\"\n            @wheel=\"wheel\"\n            :teleported=\"false\"\n            v-model=\"form_data.model_id\"\n            @focus=\"getSelectModel\"\n            :placeholder=\"$t('workflow.nodes.imageToVideoGenerate.model.requiredMessage')\"\n            :options=\"modelOptions\"\n            showFooter\n            :model-type=\"'ITV'\"\n          ></ModelSelect>\n        </el-form-item>\n\n        <el-form-item\n          :label=\"$t('workflow.nodes.imageToVideoGenerate.prompt.label')\"\n          prop=\"prompt\"\n          :rules=\"{\n            required: true,\n            message: $t('common.prompt.placeholder'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span\n                  >{{ $t('workflow.nodes.imageToVideoGenerate.prompt.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content\n                  >{{ $t('workflow.nodes.imageToVideoGenerate.prompt.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            @wheel=\"wheel\"\n            :title=\"$t('workflow.nodes.imageToVideoGenerate.prompt.label')\"\n            v-model=\"form_data.prompt\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitDialog\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.imageToVideoGenerate.negative_prompt.label')\"\n          prop=\"prompt\"\n          :rules=\"{\n            required: false,\n            message: $t('common.prompt.placeholder'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.imageToVideoGenerate.negative_prompt.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content\n                  >{{ $t('workflow.nodes.imageToVideoGenerate.negative_prompt.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            @wheel=\"wheel\"\n            :title=\"$t('workflow.nodes.imageToVideoGenerate.negative_prompt.label')\"\n            v-model=\"form_data.negative_prompt\"\n            :placeholder=\"$t('workflow.nodes.imageToVideoGenerate.negative_prompt.placeholder')\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitNegativeDialog\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.imageToVideoGenerate.first_frame.label')\"\n          :rules=\"{\n            type: 'array',\n            required: true,\n            message: $t('workflow.nodes.imageToVideoGenerate.first_frame.requiredMessage'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label\n            >{{ $t('workflow.nodes.imageToVideoGenerate.first_frame.label')\n            }}<span class=\"color-danger\">*</span></template\n          >\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.imageToVideoGenerate.first_frame.requiredMessage')\"\n            v-model=\"form_data.first_frame_url\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.imageToVideoGenerate.last_frame.label')\"\n          :rules=\"{\n            type: 'array',\n            required: false,\n            message: $t('workflow.nodes.imageToVideoGenerate.last_frame.requiredMessage'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label\n            >{{ $t('workflow.nodes.imageToVideoGenerate.last_frame.label') }}\n          </template>\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.imageToVideoGenerate.last_frame.requiredMessage')\"\n            clearable\n            v-model=\"form_data.last_frame_url\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n          @click.prevent\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.is_result\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshParam\" />\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, nextTick, onMounted, ref, inject } from 'vue'\nimport { groupBy, set } from 'lodash'\nimport type { FormInstance } from 'element-plus'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\nimport { t } from '@/locales'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport { WorkflowMode } from '@/enums/application'\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst props = defineProps<{ nodeModel: any }>()\nconst modelOptions = ref<any>(null)\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\n\nconst aiChatNodeFormRef = ref<FormInstance>()\nconst validate = () => {\n  return aiChatNodeFormRef.value?.validate().catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nconst defaultPrompt = `{{${t('workflow.nodes.startNode.label')}.question}}`\n\nconst form = {\n  model_id: '',\n  system: '',\n  prompt: defaultPrompt,\n  negative_prompt: '',\n  dialogue_number: 0,\n  dialogue_type: 'NODE',\n  is_result: true,\n  temperature: null,\n  max_tokens: null,\n  first_frame_url: ['start-node', 'image'],\n  last_frame_url: [],\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst resource = getResourceDetail()\n\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'ITV',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'ITV',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nconst model_change = () => {\n  nextTick(() => {\n    if (form_data.value.model_id) {\n      AIModeParamSettingDialogRef.value?.reset_default(form_data.value.model_id, id)\n    } else {\n      refreshParam({})\n    }\n  })\n}\n\nconst openAIParamSettingDialog = (modelId: string) => {\n  if (modelId) {\n    AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting)\n  }\n}\n\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'model_params_setting', data)\n}\n\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'prompt', val)\n}\n\nfunction submitNegativeDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'negative_prompt', val)\n}\n\nonMounted(() => {\n  getSelectModel()\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/image-understand/index.ts",
    "content": "import ImageUnderstandNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass RerankerNode extends AppNode {\n  constructor(props: any) {\n    super(props, ImageUnderstandNodeVue)\n  }\n}\n\nexport default {\n  type: 'image-understand-node',\n  model: AppNodeModel,\n  view: RerankerNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/image-understand/index.vue",
    "content": "<template>\n  <NodeContainer :node-model=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"aiChatNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.imageUnderstandNode.model.label')\"\n          prop=\"model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('workflow.nodes.imageUnderstandNode.model.requiredMessage'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ t('workflow.nodes.imageUnderstandNode.model.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button\n                :disabled=\"!form_data.model_id\"\n                type=\"primary\"\n                link\n                @click=\"openAIParamSettingDialog(form_data.model_id)\"\n                @refreshForm=\"refreshParam\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n\n          <ModelSelect\n            @wheel=\"wheel\"\n            :teleported=\"false\"\n            v-model=\"form_data.model_id\"\n            :placeholder=\"$t('workflow.nodes.imageUnderstandNode.model.requiredMessage')\"\n            :options=\"modelOptions\"\n            showFooter\n            :model-type=\"'IMAGE'\"\n          ></ModelSelect>\n        </el-form-item>\n\n        <el-form-item>\n          <template #label>\n            <div class=\"flex-between\">\n              <div class=\"flex align-center\">\n                <span>{{ $t('views.application.form.roleSettings.label') }}</span>\n                <el-tooltip\n                  effect=\"dark\"\n                  :content=\"$t('views.application.form.roleSettings.tooltip')\"\n                  placement=\"right\"\n                >\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon ml-4\"></AppIcon>\n                </el-tooltip>\n              </div>\n              <el-button\n                type=\"primary\"\n                link\n                @click=\"openGeneratePromptDialog(form_data.model_id)\"\n                :disabled=\"!form_data.model_id\"\n              >\n                <AppIcon iconName=\"app-generate-star\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <MdEditorMagnify\n            :title=\"$t('views.application.form.roleSettings.label')\"\n            v-model=\"form_data.system\"\n            style=\"height: 100px\"\n            @submitDialog=\"submitSystemDialog\"\n            :placeholder=\"`${t('workflow.SystemPromptPlaceholder')}{{${t('workflow.nodes.startNode.label')}.question}}`\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('views.application.form.prompt.label')\"\n          prop=\"prompt\"\n          :rules=\"{\n            required: true,\n            message: $t('views.application.form.prompt.requiredMessage'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span\n                  >{{ $t('views.application.form.prompt.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>{{ $t('views.application.form.prompt.tooltip') }}</template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            @wheel=\"wheel\"\n            :title=\"$t('views.application.form.prompt.label')\"\n            v-model=\"form_data.prompt\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitDialog\"\n            :placeholder=\"`${t('workflow.UserPromptPlaceholder')}{{${t('workflow.nodes.startNode.label')}.question}}`\"\n          />\n        </el-form-item>\n        <el-form-item\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <div>{{ $t('views.application.form.historyRecord.label') }}</div>\n              <el-select\n                v-model=\"form_data.dialogue_type\"\n                type=\"small\"\n                style=\"width: 100px\"\n                :teleported=\"false\"\n              >\n                <el-option :label=\"$t('workflow.node')\" value=\"NODE\" />\n                <el-option :label=\"$t('workflow.workflow')\" value=\"WORKFLOW\" />\n              </el-select>\n            </div>\n          </template>\n          <el-input-number\n            v-model=\"form_data.dialogue_number\"\n            :min=\"0\"\n            :value-on-clear=\"0\"\n            controls-position=\"right\"\n            class=\"w-full\"\n            :step=\"1\"\n            :step-strictly=\"true\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.imageUnderstandNode.image.label')\"\n          :rules=\"{\n            type: 'array',\n            required: true,\n            message: $t('workflow.nodes.imageUnderstandNode.image.requiredMessage'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label\n            >{{ $t('workflow.nodes.imageUnderstandNode.image.label')\n            }}<span class=\"color-danger\">*</span></template\n          >\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.imageUnderstandNode.image.requiredMessage')\"\n            v-model=\"form_data.image_list\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n          @click.prevent\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.is_result\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshParam\" />\n    <GeneratePromptDialog @replace=\"replace\" ref=\"GeneratePromptDialogRef\" />\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, onMounted, ref, inject } from 'vue'\nimport { groupBy, set } from 'lodash'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormInstance } from 'element-plus'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\nimport { t } from '@/locales'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport GeneratePromptDialog from '@/views/application/component/GeneratePromptDialog.vue'\nimport { WorkflowMode } from '@/enums/application'\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst props = defineProps<{ nodeModel: any }>()\nconst modelOptions = ref<any>(null)\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\n\nconst aiChatNodeFormRef = ref<FormInstance>()\nconst nodeCascaderRef = ref()\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n    aiChatNodeFormRef.value?.validate(),\n  ]).catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nconst defaultPrompt = `{{${t('workflow.nodes.startNode.label')}.question}}`\n\nconst form = {\n  model_id: '',\n  system: '',\n  prompt: defaultPrompt,\n  dialogue_number: 0,\n  dialogue_type: 'NODE',\n  is_result: true,\n  temperature: null,\n  max_tokens: null,\n  image_list: ['start-node', 'image'],\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst resource = getResourceDetail()\n\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'IMAGE',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'IMAGE',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nfunction submitSystemDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'system', val)\n}\n\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'prompt', val)\n}\n\nconst openAIParamSettingDialog = (modelId: string) => {\n  if (modelId) {\n    AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting)\n  }\n}\n\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'model_params_setting', data)\n}\n\nconst GeneratePromptDialogRef = ref<InstanceType<typeof GeneratePromptDialog>>()\nconst openGeneratePromptDialog = (modelId: string) => {\n  if (modelId) {\n    GeneratePromptDialogRef.value?.open(modelId, id)\n  }\n}\nconst replace = (v: any) => {\n  set(props.nodeModel.properties.node_data, 'system', v)\n}\n\nonMounted(() => {\n  getSelectModel()\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/intent-classify-node/index.ts",
    "content": "import IntentNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass IntentNode extends AppNode {\n  constructor(props: any) {\n    super(props, IntentNodeVue)\n  }\n}\n\nconst get_up_index_height = (branch_list: Array<any>, index: number) => {\n    return branch_list\n        .filter((item, i) => i < index)\n        .map((item) => item.height + 8)\n        .reduce((x,y) => x+y, 0)\n}\n\nclass IntentModel extends AppNodeModel {\n    refreshBranch() {\n        // 更新节点连接边的path\n        this.incoming.edges.forEach((edge: any) => {\n            // 调用自定义的更新方案\n            edge.updatePathByAnchor()\n        })\n        this.outgoing.edges.forEach((edge: any) => {\n            edge.updatePathByAnchor()\n        })\n    }\n    getDefaultAnchor() {\n        const {\n            id,\n            x,\n            y,\n            width,\n            height,\n            properties: { branch_condition_list }\n        } = this\n        if (this.height === undefined) {\n            this.height = 200\n        }\n        const showNode = this.properties.showNode === undefined ? true : this.properties.showNode\n        const anchors: any = []\n        anchors.push({\n            x: x - width / 2 + 10,\n            y: showNode ? y : y - 15,\n            id: `${id}_left`,\n            edgeAddable: false,\n            type: 'left'\n        })\n\n        if (branch_condition_list) {\n\n            const FORM_ITEMS_HEIGHT = 397  // 上方表单占用高度\n            \n            for (let index = 0; index < branch_condition_list.length; index++) {\n                const element = branch_condition_list[index]\n               \n                anchors.push({\n                x: x + width / 2 - 10,\n                y: showNode\n                        ? y - height / 2 + FORM_ITEMS_HEIGHT  + index *41.36\n                        : y - 15,\n                id: `${id}_${element.id}_right`,\n                type: 'right'\n                })\n                console.log(y - height / 2 + FORM_ITEMS_HEIGHT   + 100/ 2)\n            }\n        }\n        return anchors\n    }\n}\n\n\nexport default {\n  type: 'intent-node',\n  model: IntentModel,\n  view: IntentNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/intent-classify-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"IntentClassifyNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('views.application.form.aiModel.label')\"\n          prop=\"model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('views.application.form.aiModel.placeholder'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <div>\n                <span\n                  >{{ $t('views.application.form.aiModel.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button\n                type=\"primary\"\n                link\n                :disabled=\"!form_data.model_id\"\n                @click=\"openAIParamSettingDialog(form_data.model_id)\"\n                @refreshForm=\"refreshParam\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <ModelSelect\n            @change=\"model_change\"\n            @wheel=\"wheel\"\n            :teleported=\"false\"\n            v-model=\"form_data.model_id\"\n            :placeholder=\"$t('views.application.form.aiModel.placeholder')\"\n            :options=\"modelOptions\"\n            @submitModel=\"getSelectModel\"\n            showFooter\n            :model-type=\"'LLM'\"\n          ></ModelSelect>\n        </el-form-item>\n        <el-form-item\n          prop=\"content_list\"\n          :label=\"$t('workflow.nodes.intentNode.input.label')\"\n          :rules=\"{\n            message: $t('workflow.nodes.textToSpeechNode.content.label'),\n            trigger: 'change',\n            required: true,\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <div>\n                <span\n                  >{{ $t('workflow.nodes.intentNode.input.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n            </div>\n          </template>\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.textToSpeechNode.content.label')\"\n            v-model=\"form_data.content_list\"\n          />\n        </el-form-item>\n        <el-form-item :label=\"$t('views.application.form.historyRecord.label')\">\n          <el-input-number\n            v-model=\"form_data.dialogue_number\"\n            :min=\"0\"\n            :value-on-clear=\"0\"\n            controls-position=\"right\"\n            class=\"w-full\"\n            :step=\"1\"\n            :step-strictly=\"true\"\n          />\n        </el-form-item>\n        <el-form-item>\n          <template #label>\n            <div class=\"flex-between\">\n              <div>\n                <span\n                  >{{ $t('workflow.nodes.intentNode.classify.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button @click=\"addClassfiyBranch\" type=\"primary\" size=\"large\" link>\n                <AppIcon iconName=\"app-add-outlined\" />\n              </el-button>\n            </div>\n          </template>\n          <div>\n            <div v-for=\"(item, index) in form_data.branch\" :key=\"item.id\" class=\"mb-8\">\n              <el-form-item\n                :prop=\"`branch.${index}.content`\"\n                :rules=\"{\n                  message: $t('common.inputPlaceholder'),\n                  trigger: 'change',\n                  required: true,\n                }\"\n              >\n                <el-row :gutter=\"12\" align=\"middle\">\n                  <el-col :span=\"21\">\n                    <el-input\n                      v-model=\"item.content\"\n                      style=\"width: 210px\"\n                      :disabled=\"item.isOther\"\n                      :placeholder=\"$t('common.inputPlaceholder')\"\n                    />\n                  </el-col>\n                  <el-col :span=\"3\">\n                    <el-button\n                      link\n                      size=\"large\"\n                      v-if=\"!item.isOther\"\n                      :disabled=\"form_data.branch.filter((b: any) => !b.isOther).length <= 1\"\n                      @click=\"deleteClassifyBranch(item.id)\"\n                    >\n                      <AppIcon iconName=\"app-delete\"></AppIcon>\n                    </el-button>\n                  </el-col>\n                </el-row>\n              </el-form-item>\n            </div>\n          </div>\n        </el-form-item>\n      </el-form>\n    </el-card>\n\n    <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshParam\" />\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { set, groupBy, cloneDeep } from 'lodash'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\nimport type { FormInstance } from 'element-plus'\nimport { ref, computed, onMounted, inject } from 'vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport { t } from '@/locales'\nimport { useRoute } from 'vue-router'\nimport { randomId } from '@/utils/common'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst nodeCascaderRef = ref()\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\n\nfunction addClassfiyBranch() {\n  const list = cloneDeep(props.nodeModel.properties.node_data.branch)\n  const obj = {\n    id: randomId(),\n    content: '',\n    isOther: false,\n  }\n  list.splice(list.length - 1, 0, obj)\n  refreshBranchAnchor(list, true)\n  set(props.nodeModel.properties.node_data, 'branch', list)\n  props.nodeModel.refreshBranch()\n}\n\nfunction deleteClassifyBranch(id: string) {\n  const list = cloneDeep(props.nodeModel.properties.node_data.branch)\n\n  const itemToDelete = list.find((item: any) => item.id === id)\n  if (!itemToDelete || itemToDelete.isOther) {\n    return\n  }\n\n  const commonItems = list.filter((item: any) => !item.isOther)\n  if (commonItems.length <= 1) {\n    return\n  }\n  // 删除连接线\n  const delete_anchor_id = `${props.nodeModel.id}_${id}_right`\n  const edgetToDelete = (props.nodeModel.outgoing?.edges || [])\n    .filter((edge: any) => edge.sourceAnchorId === delete_anchor_id)\n    .map((edge: any) => edge.id)\n\n  if (edgetToDelete.length > 0) {\n    props.nodeModel.graphModel.eventCenter.emit('delete_edge', edgetToDelete)\n  }\n\n  const newList = list.filter((item: any) => item.id !== id) // 删除分支\n\n  set(props.nodeModel.properties.node_data, 'branch', newList) // 更新数据\n  refreshBranchAnchor(newList, false) // 刷新锚点\n}\n\nfunction refreshBranchAnchor(list: Array<any>, is_add: boolean) {\n  const branch_condition_list = cloneDeep(\n    props.nodeModel.properties.branch_condition_list\n      ? props.nodeModel.properties.branch_condition_list\n      : [],\n  )\n\n  const new_branch_condition_list = list\n    .map((item, index) => {\n      const exist = branch_condition_list.find((b: any) => b.id === item.id)\n      if (exist) {\n        return { index: index, height: exist.height, id: item.id }\n      } else {\n        if (is_add) {\n          return { index: index, height: 12, id: item.id }\n        }\n      }\n    })\n    .filter((item) => item)\n\n  set(props.nodeModel.properties, 'branch_condition_list', new_branch_condition_list)\n  props.nodeModel.refreshBranch()\n}\n\nconst resizeBranch = (wh: any, row: any, index: number) => {\n  const branch_condition_list = cloneDeep(\n    props.nodeModel.properties.branch_condition_list\n      ? props.nodeModel.properties.branch_condition_list\n      : [],\n  )\n  const new_branch_condition_list = branch_condition_list.map((item: any) => {\n    if (item.id === row.id) {\n      return {\n        ...item,\n        height: wh.height, //该分支高度\n        index: index,\n      }\n    }\n    return item\n  })\n  set(props.nodeModel.properties, 'branch_condition_list', new_branch_condition_list)\n  refreshBranchAnchor(props.nodeModel.properties.node_data.branch, true)\n}\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nconst model_change = (model_id?: string) => {\n  if (model_id) {\n    AIModeParamSettingDialogRef.value?.reset_default(model_id, id)\n  } else {\n    refreshParam({})\n  }\n}\n\nconst form = {\n  model_id: '',\n  branch: [\n    {\n      id: randomId(),\n      content: '',\n      isOther: false,\n    },\n    {\n      id: randomId(),\n      content: t('workflow.nodes.intentNode.other'),\n      isOther: true,\n    },\n  ],\n  dialogue_number: 1,\n  content_list: [],\n}\n\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'model_params_setting', data)\n}\n\nconst openAIParamSettingDialog = (modelId: string) => {\n  if (modelId) {\n    AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting)\n  }\n}\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n      refreshBranchAnchor(form.branch, true)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\nconst props = defineProps<{ nodeModel: any }>()\n\nconst IntentClassifyNodeFormRef = ref<FormInstance>()\nconst modelOptions = ref<any>(null)\n\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n    IntentClassifyNodeFormRef.value?.validate(),\n  ])\n    .then(() => {\n      if (\n        form_data.value.branch.length !=\n        new Set(form_data.value.branch.map((item: any) => item.content)).size\n      ) {\n        throw t('workflow.nodes.intentNode.error2')\n      }\n    })\n    .catch((err: any) => {\n      return Promise.reject({ node: props.nodeModel, errMessage: err })\n    })\n}\n\nconst resource = getResourceDetail()\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'LLM',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'LLM',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nonMounted(() => {\n  getSelectModel()\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/knowledge-base-node/component/UserFieldFormDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"\n      isEdit\n        ? $t('common.param.editParam')\n        : $t('common.param.addParam')\n    \"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <DynamicsFormConstructor\n      v-model=\"currentRow\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      :input_type_list=\"inputTypeList\"\n      ref=\"DynamicsFormConstructorRef\"\n    ></DynamicsFormConstructor>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"close\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit()\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport DynamicsFormConstructor from '@/components/dynamics-form/constructor/index.vue'\nimport type { FormField } from '@/components/dynamics-form/type'\nimport _ from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst DynamicsFormConstructorRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\nconst currentItem = ref<FormField | any>()\nconst check_field = (field_list: Array<string>, obj: any) => {\n  return field_list.every((field) => _.get(obj, field, undefined) !== undefined)\n}\nconst currentRow = computed(() => {\n  if (currentItem.value) {\n    const row = currentItem.value\n    switch (row.type) {\n      case 'input':\n        if (check_field(['field', 'input_type', 'label', 'required', 'attrs'], currentItem.value)) {\n          return currentItem.value\n        }\n        return {\n          attrs: row.attrs || { maxlength: 200, minlength: 0 },\n          field: row.field || row.variable,\n          input_type: 'TextInput',\n          label: row.label || row.name,\n          default_value: row.default_value,\n          required: row.required != undefined ? row.required : row.is_required\n        }\n      case 'select':\n        if (\n          check_field(\n            ['field', 'input_type', 'label', 'required', 'option_list'],\n            currentItem.value\n          )\n        ) {\n          return currentItem.value\n        }\n        return {\n          attrs: row.attrs || {},\n          field: row.field || row.variable,\n          input_type: 'SingleSelect',\n          label: row.label || row.name,\n          default_value: row.default_value,\n          required: row.required != undefined ? row.required : row.is_required,\n          option_list: row.option_list\n            ? row.option_list\n            : row.optionList.map((o: any) => {\n                return { key: o, value: o }\n              })\n        }\n\n      case 'date':\n        if (\n          check_field(\n            [\n              'field',\n              'input_type',\n              'label',\n              'required',\n              'attrs.format',\n              'attrs.value-format',\n              'attrs.type'\n            ],\n            currentItem.value\n          )\n        ) {\n          return currentItem.value\n        }\n        return {\n          field: row.field || row.variable,\n          input_type: 'DatePicker',\n          label: row.label || row.name,\n          default_value: row.default_value,\n          required: row.required != undefined ? row.required : row.is_required,\n          attrs: {\n            format: 'YYYY-MM-DD HH:mm:ss',\n            'value-format': 'YYYY-MM-DD HH:mm:ss',\n            type: 'datetime'\n          }\n        }\n      default:\n        return currentItem.value\n    }\n  } else {\n    return { input_type: 'TextInput', required: false, attrs: { maxlength: 200, minlength: 0 }, show_default_value: true }\n  }\n})\nconst currentIndex = ref(null)\nconst inputTypeList = ref([\n  { label: t('dynamicsForm.input_type_list.TextInput'), value: 'TextInputConstructor' },\n  { label: t('dynamicsForm.input_type_list.PasswordInput'), value: 'PasswordInputConstructor' },\n  { label: t('dynamicsForm.input_type_list.SingleSelect'), value: 'SingleSelectConstructor' },\n  { label: t('dynamicsForm.input_type_list.MultiSelect'), value: 'MultiSelectConstructor' },\n  { label: t('dynamicsForm.input_type_list.RadioCard'), value: 'RadioCardConstructor' },\n  { label: t('dynamicsForm.input_type_list.DatePicker'), value: 'DatePickerConstructor' },\n  { label: t('dynamicsForm.input_type_list.SwitchInput'), value: 'SwitchInputConstructor' },\n])\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any, index: any) => {\n  dialogVisible.value = true\n\n  if (row) {\n    isEdit.value = true\n    currentItem.value = cloneDeep(row)\n    currentIndex.value = index\n  } else {\n    currentItem.value = null\n  }\n}\n\nconst close = () => {\n  dialogVisible.value = false\n  isEdit.value = false\n  currentIndex.value = null\n  currentItem.value = null as any\n}\n\nconst submit = async () => {\n  const formEl = DynamicsFormConstructorRef.value\n  if (!formEl) return\n  await formEl.validate().then(() => {\n    emit('refresh', formEl?.getData(), currentIndex.value)\n    isEdit.value = false\n    currentItem.value = null as any\n    currentIndex.value = null\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/knowledge-base-node/component/UserInputFieldTable.vue",
    "content": "<template>\n  <div class=\"flex-between mb-16\">\n    <h5 class=\"break-all ellipsis lighter\" style=\"max-width: 80%\" :title=\"inputFieldConfig.title\">\n      {{ inputFieldConfig.title }}\n    </h5>\n    <div>\n      <el-button type=\"primary\" link @click=\"openChangeTitleDialog\">\n        <AppIcon iconName=\"app-setting\"></AppIcon>\n      </el-button>\n      <span class=\"ml-4\">\n        <el-button link type=\"primary\" @click=\"openAddDialog()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </span>\n    </div>\n  </div>\n  <el-table\n    :data=\"props.nodeModel.properties.user_input_field_list\"\n    class=\"mb-16\"\n    ref=\"tableRef\"\n    row-key=\"field\"\n  >\n    <el-table-column prop=\"field\" :label=\"$t('dynamicsForm.paramForm.field.label')\" width=\"95\">\n      <template #default=\"{ row }\">\n        <span :title=\"row.field\" class=\"ellipsis-1\">{{ row.field }}</span>\n      </template>\n    </el-table-column>\n\n    <el-table-column prop=\"label\" :label=\"$t('dynamicsForm.paramForm.name.label')\">\n      <template #default=\"{ row }\">\n        <span v-if=\"row.label && row.label.input_type === 'TooltipLabel'\">\n          <span :title=\"row.label.label\" class=\"ellipsis-1\">\n            {{ row.label.label }}\n          </span>\n        </span>\n        <span v-else>\n          <span :title=\"row.label\" class=\"ellipsis-1\">\n            {{ row.label }}\n          </span></span\n        >\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('dynamicsForm.paramForm.input_type.label')\" width=\"95\">\n      <template #default=\"{ row }\">\n        <el-tag size=\"small\" type=\"info\" class=\"info-tag\">{{\n          input_type_list.find((item) => item.value === row.input_type)?.label\n        }}</el-tag>\n      </template>\n    </el-table-column>\n\n    <el-table-column prop=\"default_value\" :label=\"$t('dynamicsForm.default.label')\">\n      <template #default=\"{ row }\">\n        <span :title=\"row.default_value\" class=\"ellipsis-1\">{{ getDefaultValue(row) }}</span>\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.required')\">\n      <template #default=\"{ row }\">\n        <div @click.stop>\n          <el-switch disabled size=\"small\" v-model=\"row.required\" />\n        </div>\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n      <template #default=\"{ row, $index }\">\n        <span class=\"mr-4\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n            <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n              <AppIcon iconName=\"app-edit\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n          <el-button type=\"primary\" text @click=\"deleteField($index)\">\n            <AppIcon iconName=\"app-delete\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </template>\n    </el-table-column>\n  </el-table>\n\n  <UserFieldFormDialog ref=\"UserFieldFormDialogRef\" @refresh=\"refreshFieldList\" />\n  <UserInputTitleDialog ref=\"UserInputTitleDialogRef\" @refresh=\"refreshFieldTitle\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport Sortable from 'sortablejs'\nimport UserFieldFormDialog from './UserFieldFormDialog.vue'\nimport { MsgError } from '@/utils/message'\nimport { t } from '@/locales'\nimport UserInputTitleDialog from '@/workflow/nodes/base-node/component/UserInputTitleDialog.vue'\nimport { input_type_list } from '@/components/dynamics-form/constructor/data'\nconst props = defineProps<{ nodeModel: any }>()\n\nconst tableRef = ref()\nconst UserFieldFormDialogRef = ref()\nconst UserInputTitleDialogRef = ref()\nconst inputFieldList = ref<any[]>([])\nconst inputFieldConfig = ref({ title: t('workflow.nodes.KnowledgeBaseNode.DocumentSetting') })\n\nfunction openAddDialog(data?: any, index?: any) {\n  UserFieldFormDialogRef.value.open(data, index)\n}\n\nfunction openChangeTitleDialog() {\n  UserInputTitleDialogRef.value.open(inputFieldConfig.value)\n}\n\nfunction deleteField(index: any) {\n  inputFieldList.value.splice(index, 1)\n  props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n  const fields = inputFieldList.value.map((item) => ({\n    label: item.label.label,\n    value: item.field,\n  }))\n\n  set(props.nodeModel.properties, 'user_input_field_list', cloneDeep(inputFieldList.value))\n  set(props.nodeModel.properties.config, 'fields', fields)\n  onDragHandle()\n}\n\nfunction refreshFieldList(data: any, index: any) {\n  for (let i = 0; i < inputFieldList.value.length; i++) {\n    if (inputFieldList.value[i].field === data.field && index !== i) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.field)\n      return\n    }\n  }\n  if (index !== null) {\n    inputFieldList.value.splice(index, 1, data)\n  } else {\n    inputFieldList.value.push(data)\n  }\n  UserFieldFormDialogRef.value.close()\n  set(props.nodeModel.properties, 'user_input_field_list', cloneDeep(inputFieldList.value))\n  onDragHandle()\n}\n\nfunction refreshFieldTitle(data: any) {\n  inputFieldConfig.value = data\n  UserInputTitleDialogRef.value.close()\n}\n\nconst getDefaultValue = (row: any) => {\n  if (row.input_type === 'PasswordInput') {\n    return '******'\n  }\n  if (row.default_value) {\n    const default_value = row.option_list\n      ?.filter((v: any) => row.default_value.indexOf(v.value) > -1)\n      .map((v: any) => v.label)\n      .join(',')\n    if (default_value) {\n      return default_value\n    }\n    return row.default_value\n  }\n  if (row.default_value !== undefined) {\n    return row.default_value\n  }\n}\n\nfunction onDragHandle() {\n  if (!tableRef.value) return\n\n  // 获取表格的 tbody DOM 元素\n  const wrapper = tableRef.value.$el as HTMLElement\n  const tbody = wrapper.querySelector('.el-table__body-wrapper tbody')\n  if (!tbody) return\n  // 初始化 Sortable\n  Sortable.create(tbody as HTMLElement, {\n    animation: 150,\n    ghostClass: 'ghost-row',\n    onEnd: (evt) => {\n      if (evt.oldIndex === undefined || evt.newIndex === undefined) return\n      // 更新数据顺序\n      const items = cloneDeep([...inputFieldList.value])\n      const [movedItem] = items.splice(evt.oldIndex, 1)\n      items.splice(evt.newIndex, 0, movedItem)\n      inputFieldList.value = items\n    },\n  })\n}\n\nonMounted(() => {\n  inputFieldList.value = []\n  if (props.nodeModel.properties.user_input_field_list) {\n    inputFieldList.value = cloneDeep(props.nodeModel.properties.user_input_field_list)\n  }\n  if (props.nodeModel.properties.user_input_config) {\n    inputFieldConfig.value = props.nodeModel.properties.user_input_config\n  }\n  set(props.nodeModel.properties, 'user_input_config', inputFieldConfig)\n  onDragHandle()\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/knowledge-base-node/component/UserInputTitleDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('common.setting')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-form-item :label=\"$t('common.title')\" prop=\"title\">\n        <el-input\n          v-model=\"form.title\"\n          maxlength=\"64\"\n          show-word-limit\n          @blur=\"form.title = form.title.trim()\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\n\nconst form = ref<any>({\n  title: t('chat.userInput'),\n})\n\nconst rules = reactive({\n  title: [\n    { required: true, message: t('dynamicsForm.paramForm.name.requiredMessage'), trigger: 'blur' },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n  }\n\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  dialogVisible.value = false\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value)\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/knowledge-base-node/index.ts",
    "content": "import BaseNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass BaseNode extends AppNode {\n  constructor(props: any) {\n    super(props, BaseNodeVue)\n  }\n}\n\nclass BaseModel extends AppNodeModel {\n  constructor(data: any, graphModel: any) {\n    super(data, graphModel)\n  }\n  get_width() {\n    return 600\n  }\n}\nexport default {\n  type: 'knowledge-base-node',\n  model: BaseModel,\n  view: BaseNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/knowledge-base-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <UserInputFieldTable ref=\"UserInputFieldTableFef\" :node-model=\"nodeModel\" />\n\n    <h5 class=\"title-decoration-1 mb-8 mt-8\">\n      {{ $t('common.param.outputParam') }}\n    </h5>\n    <template v-if=\"nodeFields.length > 0\">\n      <template v-for=\"(item, index) in nodeFields\" :key=\"index\">\n        <div\n          class=\"flex-between border-r-6 p-8-12 mb-8 layout-bg lighter\"\n          @mouseenter=\"showicon = index\"\n          @mouseleave=\"showicon = null\"\n        >\n          <span class=\"break-all\">{{ item.label }} {{ '{' + item.value + '}' }}</span>\n          <el-tooltip\n            effect=\"dark\"\n            :content=\"$t('workflow.setting.copyParam')\"\n            placement=\"top\"\n            v-if=\"showicon === index\"\n          >\n            <el-button link @click=\"copyClick(item.globeLabel)\" style=\"padding: 0\">\n              <AppIcon iconName=\"app-copy\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </div>\n      </template>\n    </template>\n    <div v-else class=\"border-r-6 p-8-12 mb-8 layout-bg lighter\">\n      {{ $t('common.noData') }}\n    </div>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { ref, computed, onMounted, inject } from 'vue'\nimport { copyClick } from '@/utils/clipboard'\nimport { set } from 'lodash'\n\nimport UserInputFieldTable from './component/UserInputFieldTable.vue'\nconst showicon = ref<number | null>(null)\nconst getResourceDetail = inject('getResourceDetail') as any\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst UserInputFieldTableFef = ref()\nconst default_fields = [\n  {\n    label: '知识库',\n    value: 'knowledge',\n    globeLabel: `{{global.knowledge}}`,\n    globeValue: `{{context['global'].knowledge}}`,\n  },\n]\nconst nodeFields = computed(() => {\n  if (props.nodeModel.properties.user_input_field_list) {\n    const fields = props.nodeModel.properties.user_input_field_list.map((item: any) => ({\n      label: typeof item.label == 'string' ? item.label : item.label.label,\n      value: item.field,\n      globeLabel: `{{global.${item.field}}}`,\n      globeValue: `{{context['global'].${item.field}}}`,\n    }))\n    set(props.nodeModel.properties.config, 'globalFields', [...fields, ...default_fields])\n    return [...fields, ...default_fields]\n  }\n  set(props.nodeModel.properties.config, 'globalFields', [default_fields])\n  return []\n})\nconst resource = getResourceDetail()\n\nonMounted(() => {})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/knowledge-write-node/index.ts",
    "content": "import KnowledgeWriteVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\n\nclass KnowledgeWriteNode extends AppNode {\n    constructor(props: any) {\n        super(props, KnowledgeWriteVue)\n    }\n}\n\nexport default {\n      type: 'knowledge-write-node',\n      model: AppNodeModel,\n      view: KnowledgeWriteNode,\n}"
  },
  {
    "path": "ui/src/workflow/nodes/knowledge-write-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"KnowledgeWriteRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          prop=\"document_list\"\n          :label=\"$t('common.inputContent')\"\n          :rules=\"{\n            message: $t('workflow.nodes.textToSpeechNode.content.label'),\n            trigger: 'change',\n            required: true,\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <div>\n                <span>{{ $t('common.inputContent') }}<span class=\"color-danger\">*</span></span>\n              </div>\n            </div>\n          </template>\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.textToSpeechNode.content.label')\"\n            v-model=\"form_data.document_list\"\n          />\n        </el-form-item>\n      </el-form>\n    </el-card>\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, onMounted, ref } from 'vue'\nimport { set } from 'lodash'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport { isLastNode } from '@/workflow/common/data'\n\nconst props = defineProps<{ nodeModel: any }>()\nconst KnowledgeWriteRef = ref()\nconst nodeCascaderRef = ref()\nconst form = {\n  document_list: [],\n}\n\nconst validate = async () => {\n  let ps = [\n    KnowledgeWriteRef.value?.validate(),\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n  ]\n  return Promise.all(ps).catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nonMounted(() => {\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-body-node/LoopBodyContainer.vue",
    "content": "<template>\n  <div @mousedown=\"mousedown\" class=\"workflow-node-container p-16\" style=\"overflow: visible\">\n    <div\n      class=\"step-container white-bg border-r-8 p-16\"\n      :class=\"{ isSelected: props.nodeModel.isSelected, error: node_status !== 200 }\"\n      style=\"overflow: visible;\"\n    >\n      <div>\n        <div class=\"flex-between\">\n          <div class=\"flex align-center\">\n            <component\n              :is=\"iconComponent(`${nodeModel.type}-icon`)\"\n              class=\"mr-8\"\n              :size=\"24\"\n              :item=\"nodeModel?.properties.node_data\"\n            />\n            <h4 class=\"ellipsis-1 break-all\">{{ nodeModel.properties.stepName }}</h4>\n          </div>\n\n          <!-- 放大缩小按钮 -->\n          <el-button link @click=\"enlargeHandle\">\n            <AppIcon\n              :iconName=\"enlarge ? 'app-minify' : 'app-magnify'\"\n              class=\"color-secondary\"\n              style=\"font-size: 20px\"\n            >\n            </AppIcon>\n          </el-button>\n        </div>\n        <el-collapse-transition>\n          <div @mousedown.stop @keydown.stop @click.stop v-show=\"showNode\" class=\"mt-16\">\n            <el-alert\n              v-if=\"node_status != 200\"\n              class=\"mb-16\"\n              :title=\"\n                props.nodeModel.type === 'application-node'\n                  ? $t('workflow.tip.applicationNodeError')\n                  : $t('workflow.tip.functionNodeError')\n              \"\n              type=\"error\"\n              show-icon\n              :closable=\"false\"\n            />\n            <div :style=\"`height:${height}px`\">\n              <slot></slot>\n            </div>\n\n            <template v-if=\"nodeFields.length > 0\">\n              <h5 class=\"title-decoration-1 mb-8 mt-8\">\n                {{ $t('common.param.outputParam') }}\n              </h5>\n              <template v-for=\"(item, index) in nodeFields\" :key=\"index\">\n                <div\n                  class=\"flex-between border-r-4 p-8-12 mb-8 layout-bg lighter\"\n                  @mouseenter=\"showicon = index\"\n                  @mouseleave=\"showicon = null\"\n                >\n                  <span class=\"break-all\">{{ item.label }} {{ '{' + item.value + '}' }}</span>\n                  <el-tooltip\n                    effect=\"dark\"\n                    :content=\"$t('workflow.setting.copyParam')\"\n                    placement=\"top\"\n                    v-if=\"showicon === index\"\n                  >\n                    <el-button link @click=\"copyClick(item.globeLabel)\" style=\"padding: 0\">\n                      <AppIcon iconName=\"app-copy\"></AppIcon>\n                    </el-button>\n                  </el-tooltip>\n                </div>\n              </template>\n            </template>\n          </div>\n        </el-collapse-transition>\n      </div>\n    </div>\n\n    <el-dialog\n      :title=\"$t('workflow.nodeName')\"\n      v-model=\"nodeNameDialogVisible\"\n      :close-on-click-modal=\"false\"\n      :close-on-press-escape=\"false\"\n      :destroy-on-close=\"true\"\n      append-to-body\n      @submit.prevent\n    >\n      <el-form label-position=\"top\" ref=\"titleFormRef\" :model=\"form\">\n        <el-form-item\n          prop=\"title\"\n          :rules=\"[\n            {\n              required: true,\n              message: $t('common.inputPlaceholder'),\n              trigger: 'blur',\n            },\n          ]\"\n        >\n          <el-input v-model=\"form.title\" @blur=\"form.title = form.title.trim()\" />\n        </el-form-item>\n      </el-form>\n      <template #footer>\n        <span class=\"dialog-footer\">\n          <el-button @click.prevent=\"nodeNameDialogVisible = false\">\n            {{ $t('common.cancel') }}\n          </el-button>\n          <el-button type=\"primary\" @click=\"editName(titleFormRef)\">\n            {{ $t('common.save') }}\n          </el-button>\n        </span>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport { ref, computed, provide, inject } from 'vue'\nimport { set } from 'lodash'\nimport { iconComponent } from '../../icons/utils'\nimport { copyClick } from '@/utils/clipboard'\nimport { ElMessage } from 'element-plus'\nimport type { FormInstance } from 'element-plus'\nimport { t } from '@/locales'\nprovide('workflowMode', inject('loopWorkflowMode'))\n\nconst props = defineProps<{\n  nodeModel: any\n}>()\n\nconst titleFormRef = ref()\nconst nodeNameDialogVisible = ref<boolean>(false)\nconst form = ref<any>({\n  title: '',\n})\n\nconst showNode = computed({\n  set: (v) => {\n    set(props.nodeModel.properties, 'showNode', v)\n  },\n  get: () => {\n    if (props.nodeModel.properties.showNode !== undefined) {\n      return props.nodeModel.properties.showNode\n    }\n    set(props.nodeModel.properties, 'showNode', true)\n    return true\n  },\n})\n\nconst node_status = computed(() => {\n  if (props.nodeModel.properties.status) {\n    return props.nodeModel.properties.status\n  }\n  return 200\n})\n\nconst editName = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      if (\n        !props.nodeModel.graphModel.nodes?.some(\n          (node: any) => node.properties.stepName === form.value.title,\n        )\n      ) {\n        set(props.nodeModel.properties, 'stepName', form.value.title)\n        nodeNameDialogVisible.value = false\n        formEl.resetFields()\n      } else {\n        ElMessage.error(t('workflow.tip.repeatedNodeError'))\n      }\n    }\n  })\n}\n\nconst mousedown = () => {\n  props.nodeModel.graphModel.clearSelectElements()\n  set(props.nodeModel, 'isSelected', true)\n  set(props.nodeModel, 'isHovered', true)\n  props.nodeModel.graphModel.toFront(props.nodeModel.id)\n}\nconst showicon = ref<number | null>(null)\n\nconst height = ref<number>(600)\n\nconst nodeFields = computed(() => {\n  if (props.nodeModel.properties.config.fields) {\n    const fields = props.nodeModel.properties.config.fields?.map((field: any) => {\n      return {\n        label: field.label,\n        value: field.value,\n        globeLabel: `{{${props.nodeModel.properties.stepName}.${field.value}}}`,\n        globeValue: `{{context['${props.nodeModel.id}'].${field.value}}}`,\n      }\n    })\n    return fields\n  }\n  return []\n})\n\nconst enlarge = ref(false)\n\nfunction enlargeHandle() {\n  enlarge.value = !enlarge.value\n  if (enlarge.value) {\n    props.nodeModel.graphModel.transformModel.focusOn(\n      props.nodeModel.x,\n      props.nodeModel.y,\n      props.nodeModel.width + window.innerWidth - props.nodeModel.width,\n      props.nodeModel.height - 30,\n    )\n    height.value =\n      (props.nodeModel.graphModel.height - 100) / props.nodeModel.graphModel.transformModel.SCALE_Y\n    const width = window.innerWidth / props.nodeModel.graphModel.transformModel.SCALE_X\n    props.nodeModel.width = width\n    props.nodeModel.setHeight(height.value)\n  } else {\n    height.value = 600\n    const width = 1920\n    props.nodeModel.width = width\n    props.nodeModel.setHeight(height.value)\n  }\n}\nconst zoom = () => {\n  if (enlarge.value) {\n    enlargeHandle()\n  }\n}\ndefineExpose({ close, zoom })\n</script>\n<style lang=\"scss\" scoped>\n.workflow-node-container {\n  .step-container {\n    border: 2px solid #ffffff !important;\n    box-sizing: border-box;\n    box-shadow: 0px 2px 4px 0px rgba(var(--el-text-color-primary-rgb), 0.12);\n    &:hover {\n      box-shadow: 0px 6px 24px 0px rgba(var(--el-text-color-primary-rgb), 0.08);\n    }\n    &.isSelected {\n      border: 2px solid var(--el-color-primary) !important;\n    }\n    &.error {\n      border: 1px solid #f54a45 !important;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-body-node/index.ts",
    "content": "import LoopNode from './index.vue'\nimport { t } from '@/locales'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass LoopBodyNodeView extends AppNode {\n  constructor(props: any) {\n    super(props, LoopNode)\n  }\n  getNodeName() {\n    return t('workflow.nodes.loopBodyNode.label')\n  }\n  get_up_node_field_list(contain_self: boolean, use_cache: boolean) {\n    const loop_node_id = this.props.model.properties.loop_node_id\n    const loop_node = this.props.graphModel.getNodeModelById(loop_node_id)\n    return loop_node.get_up_node_field_list(contain_self, use_cache)\n  }\n}\nclass LoopBodyModel extends AppNodeModel {\n  refreshBranch() {\n    // 更新节点连接边的path\n    this.incoming.edges.forEach((edge: any) => {\n      // 调用自定义的更新方案\n      edge.updatePathByAnchor()\n    })\n    this.outgoing.edges.forEach((edge: any) => {\n      edge.updatePathByAnchor()\n    })\n  }\n  getDefaultAnchor() {\n    const { id, x, y, width, height } = this\n    const showNode = this.properties.showNode === undefined ? true : this.properties.showNode\n    const anchors: any = []\n    anchors.push({\n      edgeAddable: false,\n      x: x,\n      y: y - height / 2 + 10,\n      id: `${id}_children`,\n      type: 'children',\n    })\n\n    return anchors\n  }\n  setHeight(height: number) {\n    this.properties['height'] = height\n    this.outgoing.edges.forEach((edge: any) => {\n      // 调用自定义的更新方案\n      edge.updatePathByAnchor()\n    })\n    this.incoming.edges.forEach((edge: any) => {\n      // 调用自定义的更新方案\n      edge.updatePathByAnchor()\n    })\n  }\n}\nexport default {\n  type: 'loop-body-node',\n  model: LoopBodyModel,\n  view: LoopBodyNodeView,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-body-node/index.vue",
    "content": "<template>\n  <LoopBodyContainer :nodeModel=\"nodeModel\" ref=\"LoopBodyContainerRef\">\n    <div ref=\"containerRef\" @wheel.stop style=\"height: 100%; width: 100%\"></div>\n  </LoopBodyContainer>\n</template>\n<script setup lang=\"ts\">\nimport { set, cloneDeep } from 'lodash'\nimport AppEdge from '@/workflow/common/edge'\nimport { ref, onMounted, onUnmounted, inject } from 'vue'\nimport LogicFlow from '@logicflow/core'\nimport Dagre from '@/workflow/plugins/dagre'\nimport { initDefaultShortcut } from '@/workflow/common/shortcut'\nimport LoopBodyContainer from '@/workflow/nodes/loop-body-node/LoopBodyContainer.vue'\nimport { WorkflowMode } from '@/enums/application'\nimport { WorkFlowInstance, KnowledgeWorkFlowInstance } from '@/workflow/common/validate'\nimport { t } from '@/locales'\nimport { disconnectByFlow } from '@/workflow/common/teleport'\nconst loop_workflow_mode = inject('loopWorkflowMode') || WorkflowMode.ApplicationLoop\nconst nodes: any = import.meta.glob('@/workflow/nodes/**/index.ts', { eager: true })\nconst props = defineProps<{ nodeModel: any }>()\nconst containerRef = ref()\nconst LoopBodyContainerRef = ref<InstanceType<typeof LoopBodyContainer>>()\nconst validate = () => {\n  const workflow =\n    loop_workflow_mode == WorkflowMode.ApplicationLoop\n      ? new WorkFlowInstance(lf.value.getGraphData(), WorkflowMode.ApplicationLoop)\n      : new KnowledgeWorkFlowInstance(lf.value.getGraphData(), WorkflowMode.KnowledgeLoop)\n  return Promise.all(lf.value.graphModel.nodes.map((element: any) => element?.validate?.()))\n    .then(() => {\n      const loop_node_id = props.nodeModel.properties.loop_node_id\n      const loop_node = props.nodeModel.graphModel.getNodeModelById(loop_node_id)\n      try {\n        workflow.is_loop_valid()\n        if (loop_node.properties.node_data.loop_type == 'LOOP' && !workflow.exist_break_node()) {\n          return Promise.reject({\n            node: loop_node,\n            errMessage: t('workflow.validate.loopNodeBreakNodeRequired'),\n          })\n        }\n\n        return Promise.resolve({})\n      } catch (e) {\n        return Promise.reject({ node: loop_node, errMessage: e })\n      }\n    })\n    .catch((e) => {\n      props.nodeModel.graphModel.selectNodeById(props.nodeModel.id)\n      props.nodeModel.graphModel.transformModel.focusOn(\n        props.nodeModel.x,\n        props.nodeModel.y,\n        props.nodeModel.width,\n        props.nodeModel.height,\n      )\n      throw e\n    })\n}\nconst set_loop_body = () => {\n  const loop_node_id = props.nodeModel.properties.loop_node_id\n  const loop_node = props.nodeModel.graphModel.getNodeModelById(loop_node_id)\n  loop_node.properties.node_data.loop = {\n    x: props.nodeModel.x,\n    y: props.nodeModel.y,\n  }\n  loop_node.properties.node_data.loop_body = lf.value.getGraphData()\n}\n\nconst refresh_loop_fields = (fields: Array<any>) => {\n  const loop_node_id = props.nodeModel.properties.loop_node_id\n  const loop_node = props.nodeModel.graphModel.getNodeModelById(loop_node_id)\n  if (loop_node) {\n    loop_node.properties.config.fields = fields\n    loop_node.clear_next_node_field(true)\n  }\n}\n\nconst lf = ref()\n\nconst renderGraphData = (data?: any) => {\n  const container: any = containerRef.value\n  if (container) {\n    lf.value = new LogicFlow({\n      plugins: [Dagre],\n      textEdit: false,\n      adjustEdge: false,\n      adjustEdgeStartAndEnd: false,\n      background: {\n        backgroundColor: '#f5f6f7',\n      },\n      grid: {\n        size: 10,\n        type: 'dot',\n        config: {\n          color: '#DEE0E3',\n          thickness: 1,\n        },\n      },\n      keyboard: {\n        enabled: true,\n      },\n      isSilentMode: false,\n      container: container,\n    })\n    lf.value.setTheme({\n      bezier: {\n        stroke: '#afafaf',\n        strokeWidth: 1,\n      },\n    })\n\n    function HtmlPointToCanvasPoint(point: any) {\n      let scaleX = lf.value.graphModel.transformModel.SCALE_X as number\n      let scaleY = lf.value.graphModel.transformModel.SCALE_Y as number\n      let translateX = lf.value.graphModel.transformModel.TRANSLATE_X\n      let translateY = lf.value.graphModel.transformModel.TRANSLATE_Y\n      const [x, y] = point\n      props.nodeModel.graphModel.transformModel\n      scaleX *= props.nodeModel.graphModel.transformModel.SCALE_X\n      scaleY *= props.nodeModel.graphModel.transformModel.SCALE_Y\n      translateX *= props.nodeModel.graphModel.transformModel.SCALE_X\n      translateY *= props.nodeModel.graphModel.transformModel.SCALE_Y\n      return [(x - translateX) / scaleX, (y - translateY) / scaleY]\n    }\n\n    lf.value.graphModel.transformModel.HtmlPointToCanvasPoint = HtmlPointToCanvasPoint.bind(\n      lf.value.graphModel.transformModel,\n    )\n\n    initDefaultShortcut(lf.value, lf.value.graphModel)\n    lf.value.graphModel.get_provide = (node: any, graph: any) => {\n      return {\n        getNode: () => node,\n        getGraph: () => graph,\n        workflowMode: loop_workflow_mode,\n      }\n    }\n    lf.value.graphModel.refresh_loop_fields = refresh_loop_fields\n    lf.value.graphModel.get_parent_nodes = () => {\n      return props.nodeModel.graphModel.nodes\n    }\n    lf.value.graphModel.get_up_node_field_list = props.nodeModel.get_up_node_field_list\n    lf.value.batchRegister([...Object.keys(nodes).map((key) => nodes[key].default), AppEdge])\n    lf.value.setDefaultEdgeType('app-edge')\n    lf.value.render(data ? data : {})\n\n    lf.value.graphModel.eventCenter.on('delete_edge', (id_list: Array<string>) => {\n      id_list.forEach((id: string) => {\n        lf.value.deleteEdge(id)\n      })\n    })\n    lf.value.graphModel.eventCenter.on('anchor:drop', (data: any) => {\n      // 清除当前节点下面的子节点的所有缓存\n      data.nodeModel.clear_next_node_field(false)\n    })\n    lf.value.graphModel.eventCenter.on('anchor:drop', (data: any) => {\n      // 清除当前节点下面的子节点的所有缓存\n      data.nodeModel.clear_next_node_field(false)\n    })\n\n    setTimeout(() => {\n      lf.value?.fitView()\n    }, 500)\n  }\n}\n\nconst loopLayout = () => {\n  LoopBodyContainerRef.value?.zoom()\n  lf.value?.extension?.dagre.layout()\n}\nconst selectOn = (node: any, kw: string) => {\n  lf.value?.graphModel.getNodeModelById(node.id).selectOn(kw)\n}\nconst focusOn = (node: any, kw: string) => {\n  lf.value?.graphModel.transformModel.focusOn(\n    node.x,\n    node.y,\n    lf.value?.container.clientWidth,\n    lf.value?.container.clientHeight,\n  )\n  lf.value?.graphModel.getNodeModelById(node.id).focusOn(kw)\n}\n\nconst getSelectNodes = (kw: string) => {\n  const graph_data = lf.value?.getGraphData()\n  return graph_data.nodes.filter((node: any) => node.properties.stepName.includes(kw))\n}\nconst onSearchSelect = (node: any, kw: string) => {\n  lf.value?.graphModel.getNodeModelById(node.id).selectOn(kw)\n}\nconst onClearSearchSelect = (node: any, kw: string) => {\n  lf.value?.graphModel.getNodeModelById(node.id).clearSelectOn(kw)\n}\nconst clearSelectElements = () => {\n  lf.value.graphModel.clearSelectElements()\n}\nonMounted(() => {\n  renderGraphData(cloneDeep(props.nodeModel.properties.workflow))\n  set(props.nodeModel, 'validate', validate)\n  set(props.nodeModel, 'set_loop_body', set_loop_body)\n  set(props.nodeModel, 'loopLayout', loopLayout)\n  set(props.nodeModel, 'getSelectNodes', getSelectNodes)\n  set(props.nodeModel, 'focusOn', (event: any) => {\n    focusOn(event.node, event.kw)\n  })\n  set(props.nodeModel, 'selectOn', (event: any) => {\n    selectOn(event.node, event.kw)\n  })\n  set(props.nodeModel, 'clearSelectOn', (event: any) => {\n    onSearchSelect(event.node, event.kw)\n  })\n  set(props.nodeModel, 'clearSelectElements', clearSelectElements)\n  set(props.nodeModel, 'onClearSearchSelect', (event: any) => {\n    onClearSearchSelect(event.node, event.kw)\n  })\n})\n\nonUnmounted(() => {\n  disconnectByFlow(lf.value.graphModel.flowId)\n  lf.value = null\n})\n</script>\n<style lang=\"scss\" scoped>\n.loop-beautify-button {\n  position: absolute;\n  top: 35px;\n  right: 70px;\n  border: none;\n  z-index: 10;\n}\n</style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-break-node/index.ts",
    "content": "import BreakNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass BreakNode extends AppNode {\n  constructor(props: any) {\n    super(props, BreakNodeVue)\n  }\n}\n\nexport default {\n  type: 'loop-break-node',\n  model: AppNodeModel,\n  view: BreakNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-break-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"ContinueFromRef\"\n        @submit.prevent\n      >\n        <div class=\"handle flex-between lighter mb-8\">\n          <div class=\"info\" v-if=\"form_data.condition_list.length > 1\">\n            <span>{{ $t('workflow.nodes.conditionNode.conditions.info') }}</span>\n            <el-select\n              :teleported=\"false\"\n              v-model=\"form_data.condition\"\n              size=\"small\"\n              style=\"width: 60px; margin: 0 8px\"\n            >\n              <el-option :label=\"$t('workflow.condition.AND')\" value=\"and\" />\n              <el-option :label=\"$t('workflow.condition.OR')\" value=\"or\" />\n            </el-select>\n            <span>{{ $t('workflow.nodes.conditionNode.conditions.label') }}</span>\n          </div>\n        </div>\n        <template v-for=\"(condition, index) in form_data.condition_list\" :key=\"index\">\n          <el-row :gutter=\"8\">\n            <el-col :span=\"11\">\n              <el-form-item\n                :prop=\"'condition_list.' + index + '.field'\"\n                :rules=\"{\n                  type: 'array',\n                  required: true,\n                  message: $t('workflow.variable.placeholder'),\n                  trigger: 'change',\n                }\"\n              >\n                <NodeCascader\n                  ref=\"nodeCascaderRef\"\n                  :nodeModel=\"nodeModel\"\n                  class=\"w-full\"\n                  :placeholder=\"$t('workflow.variable.placeholder')\"\n                  v-model=\"condition.field\"\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"6\">\n              <el-form-item\n                :prop=\"'condition_list.' + index + '.compare'\"\n                :rules=\"{\n                  required: true,\n                  message: $t(\n                    'workflow.nodes.conditionNode.conditions.requiredMessage',\n                  ),\n                  trigger: 'change',\n                }\"\n              >\n                <el-select\n                  @wheel=\"wheel\"\n                  :teleported=\"false\"\n                  v-model=\"condition.compare\"\n                  :placeholder=\"\n                    $t('workflow.nodes.conditionNode.conditions.requiredMessage')\n                  \"\n                  clearable\n                >\n                  <template v-for=\"(item, index) in compareList\" :key=\"index\">\n                    <el-option :label=\"item.label\" :value=\"item.value\" />\n                  </template>\n                </el-select>\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"6\">\n              <el-form-item\n                v-if=\"\n                  !['is_null', 'is_not_null', 'is_true', 'is_not_true'].includes(condition.compare)\n                \"\n                :prop=\"'condition_list.' + index + '.value'\"\n                :rules=\"{\n                  required: true,\n                  message: $t('workflow.nodes.conditionNode.valueMessage'),\n                  trigger: 'blur',\n                }\"\n              >\n                <el-input\n                  v-model=\"condition.value\"\n                  :placeholder=\"$t('workflow.nodes.conditionNode.valueMessage')\"\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"1\">\n              <el-button link type=\"info\" class=\"mt-4\" @click=\"deleteCondition(index)\">\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </el-col>\n          </el-row>\n        </template>\n      </el-form>\n\n      <el-button link type=\"primary\" @click=\"addCondition()\">\n        <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n        {{ $t('workflow.nodes.conditionNode.addCondition') }}\n      </el-button>\n    </el-card>\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, ref, onMounted } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport { compareList } from '@/workflow/common/data'\nimport type { FormInstance } from 'element-plus'\nconst props = defineProps<{ nodeModel: any }>()\n\nconst form = {\n  condition_list: [],\n  condition: 'and',\n}\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\nconst addCondition = () => {\n  const condition_list = cloneDeep(form_data.value?.condition_list || [])\n  condition_list.push({\n    field: [],\n    compare: '',\n    value: '',\n  })\n  set(props.nodeModel.properties.node_data, 'condition_list', condition_list)\n}\nconst deleteCondition = (index: number) => {\n  const condition_list = cloneDeep(form_data.value?.condition_list || [])\n  condition_list.splice(index, 1)\n  set(props.nodeModel.properties.node_data, 'condition_list', condition_list)\n}\nconst ContinueFromRef = ref<FormInstance>()\nconst validate = () => {\n  const v_list = [ContinueFromRef.value?.validate()]\n  return Promise.all(v_list).catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\nonMounted(() => {\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-continue-node/index.ts",
    "content": "import ContinueNodeNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass ContinueNode extends AppNode {\n  constructor(props: any) {\n    super(props, ContinueNodeNodeVue)\n  }\n}\n\nexport default {\n  type: 'loop-continue-node',\n  model: AppNodeModel,\n  view: ContinueNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-continue-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"ContinueFromRef\"\n        @submit.prevent\n      >\n        <div class=\"handle flex-between lighter mb-8\">\n          <div class=\"info\" v-if=\"form_data.condition_list.length > 1\">\n            <span>{{ $t('workflow.nodes.conditionNode.conditions.info') }}</span>\n            <el-select\n              :teleported=\"false\"\n              v-model=\"form_data.condition\"\n              size=\"small\"\n              style=\"width: 60px; margin: 0 8px\"\n            >\n              <el-option :label=\"$t('workflow.condition.AND')\" value=\"and\" />\n              <el-option :label=\"$t('workflow.condition.OR')\" value=\"or\" />\n            </el-select>\n            <span>{{ $t('workflow.nodes.conditionNode.conditions.label') }}</span>\n          </div>\n        </div>\n        <template v-for=\"(condition, index) in form_data.condition_list\" :key=\"index\">\n          <el-row :gutter=\"8\">\n            <el-col :span=\"11\">\n              <el-form-item\n                :prop=\"'condition_list.' + index + '.field'\"\n                :rules=\"{\n                  type: 'array',\n                  required: true,\n                  message: $t('workflow.variable.placeholder'),\n                  trigger: 'change',\n                }\"\n              >\n                <NodeCascader\n                  ref=\"nodeCascaderRef\"\n                  :nodeModel=\"nodeModel\"\n                  class=\"w-full\"\n                  :placeholder=\"$t('workflow.variable.placeholder')\"\n                  v-model=\"condition.field\"\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"6\">\n              <el-form-item\n                :prop=\"'condition_list.' + index + '.compare'\"\n                :rules=\"{\n                  required: true,\n                  message: $t(\n                    'workflow.nodes.conditionNode.conditions.requiredMessage',\n                  ),\n                  trigger: 'change',\n                }\"\n              >\n                <el-select\n                  @wheel=\"wheel\"\n                  :teleported=\"false\"\n                  v-model=\"condition.compare\"\n                  :placeholder=\"\n                    $t('workflow.nodes.conditionNode.conditions.requiredMessage')\n                  \"\n                  clearable\n                >\n                  <template v-for=\"(item, index) in compareList\" :key=\"index\">\n                    <el-option :label=\"item.label\" :value=\"item.value\" />\n                  </template>\n                </el-select>\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"6\">\n              <el-form-item\n                v-if=\"\n                  !['is_null', 'is_not_null', 'is_true', 'is_not_true'].includes(condition.compare)\n                \"\n                :prop=\"'condition_list.' + index + '.value'\"\n                :rules=\"{\n                  required: true,\n                  message: $t('workflow.nodes.conditionNode.valueMessage'),\n                  trigger: 'blur',\n                }\"\n              >\n                <el-input\n                  v-model=\"condition.value\"\n                  :placeholder=\"$t('workflow.nodes.conditionNode.valueMessage')\"\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"1\">\n              <el-button link type=\"info\" class=\"mt-4\" @click=\"deleteCondition(index)\">\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </el-col>\n          </el-row>\n        </template>\n      </el-form>\n\n      <el-button link type=\"primary\" @click=\"addCondition()\">\n        <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n        {{ $t('workflow.nodes.conditionNode.addCondition') }}\n      </el-button>\n    </el-card>\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, ref, onMounted } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport { compareList } from '@/workflow/common/data'\nimport type { FormInstance } from 'element-plus'\nconst props = defineProps<{ nodeModel: any }>()\n\nconst form = {\n  condition_list: [],\n  condition: 'and',\n}\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\nconst addCondition = () => {\n  const condition_list = cloneDeep(form_data.value?.condition_list || [])\n  condition_list.push({\n    field: [],\n    compare: '',\n    value: '',\n  })\n  set(props.nodeModel.properties.node_data, 'condition_list', condition_list)\n}\nconst deleteCondition = (index: number) => {\n  const condition_list = cloneDeep(form_data.value?.condition_list || [])\n  condition_list.splice(index, 1)\n  set(props.nodeModel.properties.node_data, 'condition_list', condition_list)\n}\nconst ContinueFromRef = ref<FormInstance>()\nconst validate = () => {\n  const v_list = [ContinueFromRef.value?.validate()]\n  return Promise.all(v_list).catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\nonMounted(() => {\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-node/index.ts",
    "content": "import LoopNode from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nimport { WorkflowType } from '@/enums/application'\n\nclass LoopNodeView extends AppNode {\n  constructor(props: any) {\n    const config = props.model.properties.config\n    super(props, LoopNode)\n    props.model.properties.config = config\n  }\n}\nclass LoopModel extends AppNodeModel {\n  refreshBranch() {\n    // 更新节点连接边的path\n    this.incoming.edges.forEach((edge: any) => {\n      // 调用自定义的更新方案\n      edge.updatePathByAnchor()\n    })\n    this.outgoing.edges.forEach((edge: any) => {\n      edge.updatePathByAnchor()\n    })\n  }\n  getDefaultAnchor() {\n    const { id, x, y, width, height } = this\n    const showNode = this.properties.showNode === undefined ? true : this.properties.showNode\n    const anchors: any = []\n\n    if (this.type !== WorkflowType.Base) {\n      if (this.type !== WorkflowType.Start) {\n        anchors.push({\n          x: x - width / 2 + 10,\n          y: showNode ? y : y - 15,\n          id: `${id}_left`,\n          edgeAddable: false,\n          type: 'left',\n        })\n      }\n      anchors.push({\n        x: x + width / 2 - 10,\n        y: showNode ? y : y - 15,\n        id: `${id}_right`,\n        type: 'right',\n      })\n    }\n    anchors.push({\n      x: x,\n      y: y + height / 2 - 25,\n      id: `${id}_children`,\n      type: 'children',\n    })\n\n    return anchors\n  }\n}\nexport default {\n  type: 'loop-node',\n  model: LoopModel,\n  view: LoopNodeView,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"replyNodeFormRef\"\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.loopNode.loopType.label')\"\n          @click.prevent\n          prop=\"loop_type\"\n          :rules=\"{\n            message: $t('workflow.nodes.loopNode.loopType.requiredMessage'),\n            trigger: 'change',\n            required: true,\n          }\"\n        >\n          <el-select v-model=\"form_data.loop_type\" type=\"small\" :teleported=\"false\">\n            <el-option :label=\"$t('workflow.nodes.loopNode.loopType.arrayLoop')\" value=\"ARRAY\" />\n            <el-option :label=\"$t('workflow.nodes.loopNode.loopType.numberLoop')\" value=\"NUMBER\" />\n            <el-option :label=\"$t('workflow.nodes.loopNode.loopType.infiniteLoop')\" value=\"LOOP\" />\n          </el-select>\n        </el-form-item>\n        <el-form-item\n          v-if=\"form_data.loop_type == 'ARRAY'\"\n          :label=\"$t('workflow.nodes.loopNode.loopArray.label')\"\n          @click.prevent\n          prop=\"array\"\n          :rules=\"{\n            message: $t('workflow.nodes.loopNode.loopArray.requiredMessage'),\n            trigger: 'blur',\n            required: true,\n          }\"\n        >\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.loopNode.loopArray.placeholder')\"\n            v-model=\"form_data.array\"\n          />\n        </el-form-item>\n        <el-form-item\n          v-else-if=\"form_data.loop_type == 'NUMBER'\"\n          :label=\"$t('workflow.nodes.loopNode.loopNumber.label')\"\n          @click.prevent\n          prop=\"number\"\n          :rules=\"{\n            message: $t('workflow.nodes.loopNode.loopNumber.requiredMessage'),\n            trigger: 'blur',\n            required: true,\n          }\"\n        >\n          <el-input-number v-model=\"form_data.number\" :min=\"1\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { set, throttle } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { ref, computed, onMounted, watch, onUnmounted } from 'vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport { loopBodyNode, loopStartNode } from '@/workflow/common/data'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nconst props = defineProps<{ nodeModel: any }>()\n\nconst form = {\n  loop_type: 'ARRAY',\n  array: [],\n  number: 1,\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\nconst showNode = computed(() => {\n  if (props.nodeModel.properties.showNode !== undefined) {\n    return props.nodeModel.properties.showNode\n  }\n  set(props.nodeModel.properties, 'showNode', true)\n  return true\n})\nwatch(showNode, () => {\n  if (showNode.value) {\n    throttle(mountLoopBodyNode, 1000)()\n  } else {\n    throttle(destroyLoopBodyNode, 1000)()\n  }\n})\nconst replyNodeFormRef = ref()\nconst nodeCascaderRef = ref()\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n    replyNodeFormRef.value?.validate(),\n  ]).catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\nconst destroyLoopBodyNode = () => {\n  const nodeOutgoingNode = props.nodeModel.graphModel.getNodeOutgoingNode(props.nodeModel.id)\n  const loopBody = nodeOutgoingNode.find((item: any) => item.type == loopBodyNode.type)\n  if (loopBody) {\n    loopBody.set_loop_body()\n    props.nodeModel.graphModel.deleteNode(loopBody.id)\n  }\n}\nconst mountLoopBodyNode = () => {\n  const nodeOutgoingNode = props.nodeModel.graphModel.getNodeOutgoingNode(props.nodeModel.id)\n  if (!nodeOutgoingNode.some((item: any) => item.type == loopBodyNode.type)) {\n    let workflow = { nodes: [loopStartNode], edges: [] }\n    let x = props.nodeModel.x\n    let y = props.nodeModel.y + 350\n    if (props.nodeModel.properties.node_data.loop_body) {\n      workflow = props.nodeModel.properties.node_data.loop_body\n    }\n    if (props.nodeModel.properties.node_data.loop) {\n      x = props.nodeModel.properties.node_data.loop.x\n      y = props.nodeModel.properties.node_data.loop.y\n    }\n    const nodeModel = props.nodeModel.graphModel.addNode({\n      type: loopBodyNode.type,\n      properties: {\n        ...loopBodyNode.properties,\n        workflow: workflow,\n        loop_node_id: props.nodeModel.id,\n      },\n      x: x,\n      y: y,\n    })\n    props.nodeModel.graphModel.addEdge({\n      type: 'loop-edge',\n      sourceNodeId: props.nodeModel.id,\n      sourceAnchorId: props.nodeModel.id + '_children',\n      targetNodeId: nodeModel.id,\n      virtual: true,\n    })\n  }\n}\n\nonMounted(() => {\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n  set(props.nodeModel, 'validate', validate)\n  if (!props.nodeModel.virtual) {\n    mountLoopBodyNode()\n  }\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-start-node/component/LoopFieldDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"isEdit ? $t('common.param.editParam') : $t('common.param.addParam')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item\n        :label=\"$t('dynamicsForm.paramForm.field.label')\"\n        :required=\"true\"\n        prop=\"field\"\n        :rules=\"rules.field\"\n      >\n        <el-input\n          v-model=\"form.field\"\n          :maxlength=\"64\"\n          :placeholder=\"$t('dynamicsForm.paramForm.field.placeholder')\"\n          show-word-limit\n        />\n      </el-form-item>\n      <el-form-item\n        :label=\"$t('dynamicsForm.paramForm.name.label')\"\n        :required=\"true\"\n        prop=\"label\"\n        :rules=\"rules.label\"\n      >\n        <el-input\n          v-model=\"form.label\"\n          :maxlength=\"64\"\n          show-word-limit\n          :placeholder=\"$t('dynamicsForm.paramForm.name.placeholder')\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"close\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\nconst currentIndex = ref(null)\nconst form = ref<any>({\n  field: '',\n  label: '',\n})\n\nconst rules = reactive({\n  label: [\n    { required: true, message: t('dynamicsForm.paramForm.name.requiredMessage'), trigger: 'blur' },\n  ],\n  field: [\n    { required: true, message: t('dynamicsForm.paramForm.field.requiredMessage'), trigger: 'blur' },\n    {\n      pattern: /^[a-zA-Z0-9_]+$/,\n      message: t('dynamicsForm.paramForm.field.requiredMessage2'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any, index?: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n    isEdit.value = true\n    currentIndex.value = index\n  }\n\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  dialogVisible.value = false\n  isEdit.value = false\n  currentIndex.value = null\n  form.value = {\n    field: '',\n    label: '',\n  }\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value, currentIndex.value)\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-start-node/component/LoopFieldTable.vue",
    "content": "<template>\n  <div class=\"flex-between mb-16\">\n    <h5 class=\"break-all ellipsis lighter\" style=\"max-width: 80%\">\n      {{ $t('workflow.variable.loop') }}\n    </h5>\n    <div>\n      <span class=\"ml-4\">\n        <el-button link type=\"primary\" @click=\"openAddDialog()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </span>\n    </div>\n  </div>\n  <el-table\n    v-if=\"props.nodeModel.properties.loop_input_field_list?.length > 0\"\n    :data=\"props.nodeModel.properties.loop_input_field_list\"\n    class=\"mb-16\"\n    ref=\"tableRef\"\n    row-key=\"field\"\n  >\n    <el-table-column prop=\"field\" :label=\"$t('dynamicsForm.paramForm.field.label')\" width=\"95\">\n      <template #default=\"{ row }\">\n        <span :title=\"row.field\" class=\"ellipsis-1\">{{ row.field }}</span>\n      </template>\n    </el-table-column>\n\n    <el-table-column prop=\"label\" :label=\"$t('dynamicsForm.paramForm.name.label')\">\n      <template #default=\"{ row }\">\n        <span>\n          <span :title=\"row.label\" class=\"ellipsis-1\">\n            {{ row.label }}\n          </span></span\n        >\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n      <template #default=\"{ row, $index }\">\n        <span class=\"mr-4\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n            <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n              <AppIcon iconName=\"app-edit\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n          <el-button type=\"primary\" text @click=\"deleteField($index)\">\n            <AppIcon iconName=\"app-delete\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </template>\n    </el-table-column>\n  </el-table>\n  <LoopFieldDialog ref=\"ChatFieldDialogRef\" @refresh=\"refreshFieldList\"></LoopFieldDialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport LoopFieldDialog from './LoopFieldDialog.vue'\nimport { MsgError } from '@/utils/message'\nimport { t } from '@/locales'\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst tableRef = ref()\nconst ChatFieldDialogRef = ref()\n\nconst inputFieldList = ref<any[]>([])\n\nfunction openAddDialog(data?: any, index?: any) {\n  ChatFieldDialogRef.value.open(data, index)\n}\n\nfunction deleteField(index: any) {\n  inputFieldList.value.splice(index, 1)\n}\n\nfunction refreshFieldList(data: any, index: any) {\n  for (let i = 0; i < inputFieldList.value.length; i++) {\n    if (inputFieldList.value[i].field === data.field && index !== i) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.field)\n      return\n    }\n  }\n  if ([undefined, null].includes(index)) {\n    inputFieldList.value.push(data)\n  } else {\n    inputFieldList.value.splice(index, 1, data)\n  }\n\n  ChatFieldDialogRef.value.close()\n}\n\nonMounted(() => {\n  if (props.nodeModel.properties.loop_input_field_list) {\n    inputFieldList.value = cloneDeep(props.nodeModel.properties.loop_input_field_list)\n  }\n  set(props.nodeModel.properties, 'loop_input_field_list', inputFieldList)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-start-node/index.ts",
    "content": "import LoopStartNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nimport { t } from '@/locales'\nclass LoopStartNode extends AppNode {\n  constructor(props: any) {\n    super(props, LoopStartNodeVue)\n  }\n  get_node_field_list() {\n    const result = []\n    if (this.props.model.type === 'loop-start-node') {\n      result.push({\n        value: 'loop',\n        label: t('workflow.variable.loop'),\n        type: 'loop',\n        children:\n          (this.props.model.properties.loop_input_field_list\n            ? this.props.model.properties.loop_input_field_list\n            : []\n          ).map((i: any) => {\n            if (i.label && i.label.input_type === 'TooltipLabel') {\n              return { label: i.label.label, value: i.field || i.variable }\n            }\n            return { label: i.label || i.name, value: i.field || i.variable }\n          }) || [],\n      })\n    }\n\n    result.push({\n      value: this.props.model.id,\n      label: this.props.model.properties.stepName,\n      type: this.props.model.type,\n      children: this.props.model.properties?.config?.fields || [],\n    })\n\n    return result\n  }\n}\nexport default {\n  type: 'loop-start-node',\n  model: AppNodeModel,\n  view: LoopStartNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/loop-start-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <LoopFieldTable :nodeModel=\"nodeModel\"></LoopFieldTable>\n    <template v-if=\"loop_input_fields?.length\">\n      <h5 class=\"title-decoration-1 mb-8\">\n        {{ $t('workflow.variable.loop') }}\n      </h5>\n      <div\n        v-for=\"(item, index) in loop_input_fields || []\"\n        :key=\"index\"\n        class=\"flex-between border-r-6 p-8-12 mb-8 layout-bg lighter\"\n        @mouseenter=\"showicon = true\"\n        @mouseleave=\"showicon = false\"\n      >\n        <span class=\"break-all\">{{ item.label }} {{ '{' + item.value + '}' }}</span>\n        <el-tooltip\n          effect=\"dark\"\n          :content=\"$t('workflow.setting.copyParam')\"\n          placement=\"top\"\n          v-if=\"showicon === true\"\n        >\n          <el-button link @click=\"copyClick(`{{loop.${item.value}}}`)\" style=\"padding: 0\">\n            <AppIcon iconName=\"app-copy\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </div>\n    </template>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { cloneDeep, set } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport LoopFieldTable from '@/workflow/nodes/loop-start-node/component/LoopFieldTable.vue'\nimport { ref, onMounted, computed, watch } from 'vue'\nimport { copyClick } from '@/utils/clipboard'\nconst props = defineProps<{ nodeModel: any }>()\nconst loop_input_fields = computed(() => {\n  return (\n    props.nodeModel.properties.loop_input_field_list\n      ? props.nodeModel.properties.loop_input_field_list\n      : []\n  ).map((i: any) => {\n    if (i.label && i.label.input_type === 'TooltipLabel') {\n      return { label: i.label.label, value: i.field || i.variable }\n    }\n    return { label: i.label || i.name, value: i.field || i.variable }\n  })\n})\nwatch(loop_input_fields, () => {\n  props.nodeModel.graphModel.refresh_loop_fields(cloneDeep(loop_input_fields.value))\n})\nconst showicon = ref(false)\n\nonMounted(() => {\n  props.nodeModel.graphModel.refresh_loop_fields(cloneDeep(loop_input_fields.value))\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/mcp-node/component/McpServerInputDialog.vue",
    "content": "<template>\n  <el-dialog\n    width=\"600\"\n    title=\"设置变量\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :append-to-body=\"true\"\n  >\n    <el-form label-position=\"top\" ref=\"formRef\" :model=\"form\" require-asterisk-position=\"right\">\n      <el-form-item v-for=\"item in input_field_list\" :label=\"item\" :key=\"item\" :prop=\"item\"\n                    :rules=\"{ required: true, message: $t('dynamicsForm.tip.requiredMessage'), trigger: 'blur' }\">\n        <el-input v-model=\"form[item]\"></el-input>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(formRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport type { FormInstance } from 'element-plus'\n\nconst emit = defineEmits<{\n  (e: 'refresh', value: any): void;\n}>();\n\nconst dialogVisible = ref(false)\nconst loading = ref(false)\nconst formRef = ref<FormInstance>()\nconst form = ref<any>({})\n\nconst input_field_list = ref<string[]>([])\n\nfunction open(vars: string[]) {\n  dialogVisible.value = true\n  input_field_list.value = vars\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value)\n      dialogVisible.value = false\n    }\n  })\n}\n\n\ndefineExpose({open})\n</script>\n<style scoped lang=\"scss\">\n\n</style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/mcp-node/index.ts",
    "content": "import McpNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass McpNode extends AppNode {\n  constructor(props: any) {\n    super(props, McpNodeVue)\n  }\n}\n\nexport default {\n  type: 'mcp-node',\n  model: AppNodeModel,\n  view: McpNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/mcp-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <div class=\"border-r-6 p-8-12 mb-8 layout-bg lighter\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"replyNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item label=\"MCP Server Config\">\n          <template #label>\n            <div class=\"flex-between\">\n              <div>\n                MCP Server Config\n                <span class=\"color-danger\">*</span>\n              </div>\n              <el-select\n                :teleported=\"false\"\n                v-model=\"form_data.mcp_source\"\n                size=\"small\"\n                style=\"width: 85px\"\n              >\n                <el-option :label=\"$t('workflow.nodes.mcpNode.reference')\" value=\"referencing\" />\n                <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n              </el-select>\n            </div>\n          </template>\n          <MdEditorMagnify\n            v-if=\"form_data.mcp_source === 'custom'\"\n            @wheel=\"wheel\"\n            title=\"MCP Server Config\"\n            v-model=\"form_data.mcp_servers\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitDialog\"\n            :placeholder=\"mcpServerJson\"\n          />\n          <el-select\n            :teleported=\"false\"\n            v-else\n            v-model=\"form_data.mcp_tool_id\"\n            filterable\n            @change=\"mcpToolSelectChange\"\n            @wheel=\"wheel\"\n          >\n            <el-option\n              v-for=\"mcpTool in mcpToolSelectOptions\"\n              :key=\"mcpTool.id\"\n              :label=\"mcpTool.name\"\n              :value=\"mcpTool.id\"\n            >\n              <div class=\"flex align-center\">\n                <el-avatar\n                  v-if=\"mcpTool?.icon\"\n                  shape=\"square\"\n                  :size=\"20\"\n                  style=\"background: none\"\n                  class=\"mr-8\"\n                >\n                  <img :src=\"resetUrl(mcpTool?.icon)\" alt=\"\" />\n                </el-avatar>\n                <ToolIcon v-else :size=\"20\" :type=\"mcpTool?.tool_type\" class=\"mr-8\" />\n                <span>{{ mcpTool.name }}</span>\n                <el-tag v-if=\"mcpTool.scope === 'SHARED'\" size=\"small\" type=\"info\" class=\"info-tag ml-8\">\n                  {{ t('views.shared.title') }}\n                </el-tag>\n              </div>\n            </el-option>\n          </el-select>\n        </el-form-item>\n        <el-form-item>\n          <template v-slot:label>\n            <div class=\"flex-between\">\n              <span>{{ $t('views.tool.title') }}</span>\n              <el-button type=\"primary\" link @click=\"getTools()\">\n                <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n                {{ $t('workflow.nodes.mcpNode.getTool') }}\n              </el-button>\n            </div>\n          </template>\n          <el-select\n            v-model=\"form_data.mcp_tool\"\n            @change=\"changeTool\"\n            filterable\n            :teleported=\"false\"\n            @wheel=\"wheel\"\n          >\n            <el-option\n              v-for=\"item in form_data.mcp_tools\"\n              :key=\"item.value\"\n              :label=\"item.name\"\n              :value=\"item.name\"\n              class=\"flex align-center\"\n            >\n              <el-tooltip\n                effect=\"dark\"\n                :content=\"item.description\"\n                placement=\"top-start\"\n                popper-class=\"max-w-350\"\n              >\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n\n              <span class=\"ml-4\">{{ item.name }}</span>\n            </el-option>\n          </el-select>\n        </el-form-item>\n      </el-form>\n    </div>\n    <h5 class=\"title-decoration-1 mb-8\">\n      {{ $t('workflow.nodes.mcpNode.toolParam') }}\n    </h5>\n    <template v-if=\"form_data.tool_params[form_data.params_nested]\">\n      <div class=\"p-8-12\" v-if=\"!form_data.mcp_tool\">\n        <el-text type=\"info\">{{ $t('common.noData') }}</el-text>\n      </div>\n      <div v-else class=\"border-r-6 p-8-12 mb-8 layout-bg lighter\">\n        <el-form\n          ref=\"dynamicsFormRef\"\n          label-position=\"top\"\n          v-loading=\"loading\"\n          require-asterisk-position=\"right\"\n          :hide-required-asterisk=\"true\"\n          v-if=\"form_data.mcp_tool\"\n          @submit.prevent\n        >\n          <el-form-item\n            v-for=\"item in form_data.tool_form_field\"\n            :key=\"item.field\"\n            :required=\"item.required\"\n          >\n            <template #label>\n              <div class=\"flex-between\">\n                <div>\n                  <TooltipLabel\n                    v-if=\"item.label.attrs.tooltip\"\n                    :label=\"item.label\"\n                    :tooltip=\"item.label.attrs.tooltip\"\n                  />\n                  <span v-else>{{ item.label.label }}</span>\n                  <span v-if=\"item.required\" class=\"color-danger\">*</span>\n                </div>\n                <el-select\n                  :teleported=\"false\"\n                  v-model=\"item.source\"\n                  size=\"small\"\n                  style=\"width: 85px\"\n                  @change=\"form_data.tool_params[form_data.params_nested][item.label.label] = ''\"\n                >\n                  <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"referencing\" />\n                  <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n                </el-select>\n              </div>\n            </template>\n            <el-input\n              v-if=\"item.source === 'custom' && item.input_type === 'TextInput'\"\n              v-model=\"form_data.tool_params[form_data.params_nested][item.label.label]\"\n            />\n            <el-input-number\n              v-else-if=\"item.source === 'custom' && item.input_type === 'NumberInput'\"\n              v-model=\"form_data.tool_params[form_data.params_nested][item.label.label]\"\n            />\n            <el-switch\n              v-else-if=\"item.source === 'custom' && item.input_type === 'SwitchInput'\"\n              v-model=\"form_data.tool_params[form_data.params_nested][item.label.label]\"\n            />\n            <el-input\n              v-else-if=\"item.source === 'custom' && item.input_type === 'JsonInput'\"\n              v-model=\"form_data.tool_params[form_data.params_nested][item.label.label]\"\n              type=\"textarea\"\n            />\n            <NodeCascader\n              v-if=\"item.source === 'referencing'\"\n              ref=\"nodeCascaderRef2\"\n              :nodeModel=\"nodeModel\"\n              class=\"w-full\"\n              :placeholder=\"$t('workflow.variable.placeholder')\"\n              v-model=\"form_data.tool_params[form_data.params_nested][item.label.label]\"\n            />\n          </el-form-item>\n        </el-form>\n      </div>\n    </template>\n    <template v-else>\n      <div class=\"p-8-12\" v-if=\"!form_data.mcp_tool\">\n        <el-text type=\"info\">{{ $t('common.noData') }}</el-text>\n      </div>\n      <div v-else class=\"border-r-6 p-8-12 mb-8 layout-bg lighter\">\n        <el-form\n          ref=\"dynamicsFormRef\"\n          label-position=\"top\"\n          v-loading=\"loading\"\n          require-asterisk-position=\"right\"\n          :hide-required-asterisk=\"true\"\n          v-if=\"form_data.mcp_tool\"\n          @submit.prevent\n        >\n          <el-form-item\n            v-for=\"item in form_data.tool_form_field\"\n            :key=\"item.field\"\n            :required=\"item.required\"\n          >\n            <template #label>\n              <div class=\"flex-between\">\n                <div>\n                  <TooltipLabel\n                    v-if=\"item.label.attrs.tooltip\"\n                    :label=\"item.label\"\n                    :tooltip=\"item.label.attrs.tooltip\"\n                  />\n                  <span v-else>{{ item.label.label }}</span>\n                  <span v-if=\"item.required\" class=\"color-danger\">*</span>\n                </div>\n                <el-select\n                  :teleported=\"false\"\n                  v-model=\"item.source\"\n                  size=\"small\"\n                  style=\"width: 85px\"\n                  @change=\"form_data.tool_params[item.label.label] = ''\"\n                >\n                  <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"referencing\" />\n                  <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n                </el-select>\n              </div>\n            </template>\n            <el-input\n              v-if=\"item.source === 'custom' && item.input_type === 'TextInput'\"\n              v-model=\"form_data.tool_params[item.label.label]\"\n            />\n            <el-input-number\n              v-else-if=\"item.source === 'custom' && item.input_type === 'NumberInput'\"\n              v-model=\"form_data.tool_params[item.label.label]\"\n            />\n            <el-switch\n              v-else-if=\"item.source === 'custom' && item.input_type === 'SwitchInput'\"\n              v-model=\"form_data.tool_params[item.label.label]\"\n            />\n            <el-input\n              v-else-if=\"item.source === 'custom' && item.input_type === 'JsonInput'\"\n              v-model=\"form_data.tool_params[item.label.label]\"\n              type=\"textarea\"\n            />\n            <NodeCascader\n              v-if=\"item.source === 'referencing'\"\n              ref=\"nodeCascaderRef2\"\n              :nodeModel=\"nodeModel\"\n              class=\"w-full\"\n              :placeholder=\"$t('workflow.variable.placeholder')\"\n              v-model=\"form_data.tool_params[item.label.label]\"\n            />\n          </el-form-item>\n        </el-form>\n      </div>\n    </template>\n    <McpServerInputDialog ref=\"mcpServerInputDialogRef\" @refresh=\"handleMcpVariables\" />\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { cloneDeep, set } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, inject, onMounted, ref } from 'vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport { t } from '@/locales'\nimport { MsgError, MsgSuccess } from '@/utils/message'\nimport TooltipLabel from '@/components/dynamics-form/items/label/TooltipLabel.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport McpServerInputDialog from './component/McpServerInputDialog.vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { resetUrl } from '@/utils/common'\nimport { WorkflowMode } from '@/enums/application'\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\nconst getResourceDetail = inject('getResourceDetail') as any\nconst workflow_mode: WorkflowMode = inject('workflowMode') || WorkflowMode.Application\nconst resource = getResourceDetail()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst dynamicsFormRef = ref()\nconst loading = ref(false)\n\nconst mcpServerJson = `{\n  \"math\": {\n    \"url\": \"your_server\",\n    \"transport\": \"sse\"\n  }\n}`\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\nconst form = {\n  mcp_tool: '',\n  mcp_tools: [],\n  mcp_servers: '',\n  mcp_server: '',\n  mcp_source: 'referencing',\n  mcp_tool_id: '',\n  tool_params: {},\n  tool_form_field: [],\n  params_nested: '',\n}\n\nconst mcpToolSelectOptions = ref<any[]>([])\n\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'mcp_servers', val)\n}\n\nasync function mcpToolSelectChange() {\n  const tool = await loadSharedApi({ type: 'tool', systemType: apiType.value }).getToolById(\n    form_data.value.mcp_tool_id,\n    loading,\n  )\n  form_data.value.mcp_servers = tool.data.code\n}\n\nfunction getTools() {\n  if (form_data.value.mcp_source === 'referencing' && !form_data.value.mcp_tool_id) {\n    MsgError(t('workflow.nodes.mcpNode.mcpToolTip'))\n    return\n  }\n  if (form_data.value.mcp_source === 'referencing' && form_data.value.mcp_tool_id) {\n    if (!mcpToolSelectOptions.value.find((item) => item.id === form_data.value.mcp_tool_id)) {\n      MsgError(t('workflow.nodes.mcpNode.mcpToolTip'))\n      return\n    }\n  }\n  if (form_data.value.mcp_source === 'custom' && !form_data.value.mcp_servers) {\n    MsgError(t('workflow.nodes.mcpNode.mcpServerTip'))\n    return\n  }\n  try {\n    JSON.parse(form_data.value.mcp_servers)\n    const vars = extractPlaceholders(form_data.value.mcp_servers)\n    if (vars.length > 0) {\n      mcpServerInputDialogRef.value.open(vars)\n      return\n    }\n  } catch (e) {\n    MsgError(t('workflow.nodes.mcpNode.mcpServerTip'))\n    return\n  }\n  // 一切正常，获取tool\n  _getTools(form_data.value.mcp_servers)\n}\n\nfunction _getTools(mcp_servers: any) {\n  console.log({ type: [WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflow_mode)\n      ? 'application'\n      : 'knowledge',\n    systemType: apiType.value\n  })\n  loadSharedApi({\n    type: [WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflow_mode)\n      ? 'application'\n      : 'knowledge',\n    systemType: apiType.value,\n  })\n    .getMcpTools(id, mcp_servers, loading)\n    .then((res: any) => {\n      form_data.value.mcp_tools = res.data\n      MsgSuccess(t('workflow.nodes.mcpNode.getToolsSuccess'))\n      // 修改了json，刷新mcp_server\n      form_data.value.mcp_server = form_data.value.mcp_tools.find(\n        (item: any) => item.name === form_data.value.mcp_tool,\n      )?.server\n    })\n}\n\nconst mcpServerInputDialogRef = ref()\n// 提取 JSON 中所有占位符（{{...}}）的变量路径\nfunction extractPlaceholders(input: unknown): string[] {\n  const re = /\\{\\{\\s*([a-zA-Z_][\\w.]*)\\s*\\}\\}/g // 捕获 {{ path.like.this }}\n  const found = new Set<string>()\n\n  const visit = (v: unknown) => {\n    if (typeof v === 'string') {\n      let m: RegExpExecArray | null\n      while ((m = re.exec(v)) !== null) found.add(m[1])\n    } else if (Array.isArray(v)) {\n      v.forEach(visit)\n    } else if (v && typeof v === 'object') {\n      Object.values(v as Record<string, unknown>).forEach(visit)\n    }\n  }\n\n  // 如果传入的是 JSON 字符串，尝试解析，否则按字符串/对象处理\n  if (typeof input === 'string') {\n    try {\n      visit(JSON.parse(input))\n    } catch {\n      visit(input)\n    }\n  } else {\n    visit(input)\n  }\n\n  return [...found]\n}\n\nfunction handleMcpVariables(vars: any) {\n  let mcp_servers = form_data.value.mcp_servers\n  for (const item in vars) {\n    mcp_servers = mcp_servers.replace(`{{${item}}}`, vars[item])\n  }\n\n  // 一切正常，获取tool\n  _getTools(mcp_servers)\n}\n\nfunction changeTool() {\n  form_data.value.mcp_server = form_data.value.mcp_tools.find(\n    (item: any) => item.name === form_data.value.mcp_tool,\n  )?.server\n\n  const args_schema = form_data.value.mcp_tools.find(\n    (item: any) => item.name === form_data.value.mcp_tool,\n  )?.args_schema\n  form_data.value.tool_form_field = []\n  for (const item in args_schema?.properties) {\n    const params = args_schema?.properties[item].properties\n    if (params) {\n      form_data.value.params_nested = item\n      for (const item2 in params) {\n        let input_type = 'TextInput'\n        if (params[item2].type === 'string') {\n          input_type = 'TextInput'\n        } else if (params[item2].type === 'number') {\n          input_type = 'NumberInput'\n        } else if (params[item2].type === 'boolean') {\n          input_type = 'SwitchInput'\n        } else if (params[item2].type === 'array') {\n          input_type = 'JsonInput'\n        } else if (params[item2].type === 'object') {\n          input_type = 'JsonInput'\n        }\n        form_data.value.tool_form_field.push({\n          field: item2,\n          label: {\n            input_type: 'TooltipLabel',\n            label: item2,\n            attrs: { tooltip: params[item2].description },\n            props_info: {},\n          },\n          input_type: input_type,\n          source: 'referencing',\n          required: args_schema.properties[item].required?.indexOf(item2) !== -1,\n          props_info: {\n            rules: [\n              {\n                required: args_schema.properties[item].required?.indexOf(item2) !== -1,\n                message: t('dynamicsForm.tip.requiredMessage'),\n                trigger: 'blur',\n              },\n            ],\n          },\n        })\n      }\n    } else {\n      form_data.value.params_nested = ''\n      let input_type = 'TextInput'\n      if (args_schema.properties[item].type === 'string') {\n        input_type = 'TextInput'\n      } else if (args_schema.properties[item].type === 'number') {\n        input_type = 'NumberInput'\n      } else if (args_schema.properties[item].type === 'boolean') {\n        input_type = 'SwitchInput'\n      } else if (args_schema.properties[item].type === 'array') {\n        input_type = 'JsonInput'\n      } else if (args_schema.properties[item].type === 'object') {\n        input_type = 'JsonInput'\n      }\n      form_data.value.tool_form_field.push({\n        field: item,\n        label: {\n          input_type: 'TooltipLabel',\n          label: item,\n          attrs: { tooltip: args_schema.properties[item].description },\n          props_info: {},\n        },\n        input_type: input_type,\n        source: 'referencing',\n        required: args_schema.required?.indexOf(item) !== -1,\n        props_info: {\n          rules: [\n            {\n              required: args_schema.required?.indexOf(item) !== -1,\n              message: t('dynamicsForm.tip.requiredMessage'),\n              trigger: 'blur',\n            },\n          ],\n        },\n      })\n    }\n  }\n  //\n  if (form_data.value.params_nested) {\n    form_data.value.tool_params = { [form_data.value.params_nested]: {} }\n  } else {\n    form_data.value.tool_params = {}\n  }\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst replyNodeFormRef = ref()\n\nconst validate = async () => {\n  // 对动态表单，只验证必填字段\n  if (dynamicsFormRef.value) {\n    const requiredFields = form_data.value.tool_form_field\n      .filter((item: any) => item.required)\n      .map((item: any) => item.label.label)\n\n    if (requiredFields.length > 0) {\n      for (const item of requiredFields) {\n        if (form_data.value.params_nested) {\n          if (!form_data.value.tool_params[form_data.value.params_nested][item]) {\n            return Promise.reject({\n              node: props.nodeModel,\n              errMessage: item + t('dynamicsForm.tip.requiredMessage'),\n            })\n          }\n        } else {\n          // 这里是没有嵌套的情况\n          if (!form_data.value.tool_params[item]) {\n            return Promise.reject({\n              node: props.nodeModel,\n              errMessage: item + t('dynamicsForm.tip.requiredMessage'),\n            })\n          }\n        }\n      }\n    }\n  }\n  if (replyNodeFormRef.value) {\n    const form = cloneDeep(form_data.value)\n    if (!form.mcp_servers) {\n      return Promise.reject({\n        node: props.nodeModel,\n        errMessage: t('workflow.nodes.mcpNode.mcpServerTip'),\n      })\n    }\n    if (!form.mcp_tool) {\n      return Promise.reject({\n        node: props.nodeModel,\n        errMessage: t('workflow.nodes.mcpNode.mcpToolTip'),\n      })\n    }\n  }\n}\n\nfunction getMcpToolSelectOptions() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          scope: 'WORKSPACE',\n          tool_type: 'MCP',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          scope: 'WORKSPACE',\n          tool_type: 'MCP',\n        }\n\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getAllToolList(obj, loading)\n    .then((res: any) => {\n      mcpToolSelectOptions.value = [...res.data.shared_tools, ...res.data.tools].filter(\n        (item: any) => item.is_active,\n      )\n    })\n}\n\nonMounted(() => {\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n  if (\n    props.nodeModel.properties.node_data.mcp_servers &&\n    !props.nodeModel.properties.node_data.mcp_source\n  ) {\n    set(props.nodeModel.properties.node_data, 'mcp_source', 'custom')\n  }\n  getMcpToolSelectOptions()\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/parameter-extraction-node/component/ParametersFieldDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"isEdit ? $t('common.param.editParam') : $t('common.param.addParam')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item\n        :label=\"$t('dynamicsForm.paramForm.field.label')\"\n        :required=\"true\"\n        prop=\"field\"\n        :rules=\"rules.field\"\n      >\n        <el-input\n          v-model=\"form.field\"\n          :maxlength=\"64\"\n          :placeholder=\"$t('dynamicsForm.paramForm.field.placeholder')\"\n          show-word-limit\n        />\n      </el-form-item>\n      <el-form-item\n        :label=\"$t('dynamicsForm.paramForm.name.label')\"\n        :required=\"true\"\n        prop=\"label\"\n        :rules=\"rules.label\"\n      >\n        <el-input\n          v-model=\"form.label\"\n          :maxlength=\"64\"\n          show-word-limit\n          :placeholder=\"$t('dynamicsForm.paramForm.name.placeholder')\"\n        />\n      </el-form-item>\n      <el-form-item\n        :label=\"$t('workflow.nodes.parameterExtractionNode.extractParameters.parameterType')\"\n        :required=\"true\"\n        prop=\"parameter_type\"\n        :rules=\"rules.label\"\n      >\n        <el-select\n          :teleported=\"false\"\n          v-model=\"form.parameter_type\"\n          :placeholder=\"\n            $t('common.selectPlaceholder') +\n            $t('workflow.nodes.parameterExtractionNode.extractParameters.parameterType')\n          \"\n          style=\"width: 100%\"\n        >\n          <el-option\n            v-for=\"item in options\"\n            :key=\"item.value\"\n            :label=\"item.label\"\n            :value=\"item.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item :label=\"$t('common.desc')\" prop=\"desc\">\n        <el-input\n          v-model=\"form.desc\"\n          style=\"width: 100%\"\n          :rows=\"2\"\n          type=\"textarea\"\n          :placeholder=\"$t('common.descPlaceholder')\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"close\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\nconst options = [\n  {\n    value: 'string',\n    label: 'string',\n  },\n  {\n    value: 'number',\n    label: 'number',\n  },\n  {\n    value: 'object',\n    label: 'object',\n  },\n  {\n    value: 'boolean',\n    label: 'boolean',\n  },\n  {\n    value: 'array',\n    label: 'array',\n  },\n]\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\nconst currentIndex = ref(null)\nconst form = ref<any>({\n  field: '',\n  label: '',\n  parameter_type: '',\n  desc: '',\n})\n\nconst rules = reactive({\n  label: [\n    { required: true, message: t('dynamicsForm.paramForm.name.requiredMessage'), trigger: 'blur' },\n  ],\n  field: [\n    { required: true, message: t('dynamicsForm.paramForm.field.requiredMessage'), trigger: 'blur' },\n    {\n      pattern: /^[a-zA-Z0-9_]+$/,\n      message: t('dynamicsForm.paramForm.field.requiredMessage2'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any, index?: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n    isEdit.value = true\n    currentIndex.value = index\n  }\n\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  dialogVisible.value = false\n  isEdit.value = false\n  currentIndex.value = null\n  form.value = {\n    field: '',\n    label: '',\n  }\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value, currentIndex.value)\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/parameter-extraction-node/component/ParametersFieldTable.vue",
    "content": "<template>\n  <div class=\"flex-between w-full\">\n    <h5 class=\"break-all lighter\">\n      {{ $t('workflow.nodes.parameterExtractionNode.extractParameters.label') }}\n      <span class=\"color-danger\">*</span>\n    </h5>\n    <span class=\"ml-4\" style=\"margin-top: -4px\">\n      <el-button link type=\"primary\" @click=\"openAddDialog()\">\n        <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n      </el-button>\n    </span>\n  </div>\n  <el-table\n    v-if=\"props.nodeModel.properties.node_data.variable_list?.length > 0\"\n    :data=\"props.nodeModel.properties.node_data.variable_list\"\n    ref=\"tableRef\"\n    row-key=\"field\"\n    class=\"border-l border-r\"\n  >\n    <el-table-column prop=\"field\" :label=\"$t('dynamicsForm.paramForm.field.label')\" width=\"90\">\n      <template #default=\"{ row }\">\n        <span :title=\"row.field\" class=\"ellipsis-1\">{{ row.field }}</span>\n      </template>\n    </el-table-column>\n\n    <el-table-column prop=\"label\" :label=\"$t('dynamicsForm.paramForm.name.label')\">\n      <template #default=\"{ row }\">\n        <span>\n          <span :title=\"row.label\" class=\"ellipsis-1\">\n            {{ row.label }}\n          </span></span\n        >\n      </template>\n    </el-table-column>\n    <el-table-column\n      prop=\"label\"\n      :label=\"\n        $t(\n          'workflow.nodes.parameterExtractionNode.extractParameters.parameterType',\n        )\n      \"\n    >\n      <template #default=\"{ row }\">\n        <el-tag size=\"small\" type=\"info\" class=\"info-tag\"> {{ row.parameter_type }}</el-tag>\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"80\">\n      <template #default=\"{ row, $index }\">\n        <span class=\"mr-4\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n            <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n              <AppIcon iconName=\"app-edit\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n          <el-button type=\"primary\" text @click=\"deleteField($index)\">\n            <AppIcon iconName=\"app-delete\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </template>\n    </el-table-column>\n  </el-table>\n  <ParametersFieldDialog\n    ref=\"ParametersFieldDialogRef\"\n    @refresh=\"refreshFieldList\"\n  ></ParametersFieldDialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport ParametersFieldDialog from './ParametersFieldDialog.vue'\nimport { MsgError } from '@/utils/message'\nimport { t } from '@/locales'\nconst props = defineProps<{ nodeModel: any }>()\n\nconst tableRef = ref()\nconst ParametersFieldDialogRef = ref()\n\nconst inputFieldList = ref<any[]>([])\n\nfunction openAddDialog(data?: any, index?: any) {\n  ParametersFieldDialogRef.value.open(data, index)\n}\n\nfunction deleteField(index: any) {\n  inputFieldList.value.splice(index, 1)\n  const fields = [\n    {\n      label: t('common.result'),\n      value: 'result',\n    },\n    ...inputFieldList.value.map((item) => ({ label: item.label, value: item.field })),\n  ]\n  set(props.nodeModel.properties.config, 'fields', fields)\n  props.nodeModel.clear_next_node_field(false)\n}\n\nfunction refreshFieldList(data: any, index: any) {\n  for (let i = 0; i < inputFieldList.value.length; i++) {\n    if (inputFieldList.value[i].field === data.field && index !== i) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.field)\n      return\n    }\n  }\n  if ([undefined, null].includes(index)) {\n    inputFieldList.value.push(data)\n  } else {\n    inputFieldList.value.splice(index, 1, data)\n  }\n  ParametersFieldDialogRef.value.close()\n  const fields = [\n    {\n      label: t('common.result'),\n      value: 'result',\n    },\n    ...inputFieldList.value.map((item) => ({ label: item.label, value: item.field })),\n  ]\n  set(props.nodeModel.properties.config, 'fields', fields)\n  props.nodeModel.clear_next_node_field(false)\n}\n\nonMounted(() => {\n  if (props.nodeModel.properties.node_data.variable_list) {\n    inputFieldList.value = cloneDeep(props.nodeModel.properties.node_data.variable_list)\n  }\n  set(props.nodeModel.properties.node_data, 'variable_list', inputFieldList)\n  const fields = [\n    {\n      label: t('common.result'),\n      value: 'result',\n    },\n    ...inputFieldList.value.map((item) => ({ label: item.label, value: item.field })),\n  ]\n  set(props.nodeModel.properties.config, 'fields', fields)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/parameter-extraction-node/index.ts",
    "content": "import ParameterExtractionNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass ParameterExtractionNode extends AppNode {\n  constructor(props: any) {\n    super(props, ParameterExtractionNodeVue)\n  }\n  getConfig(props: any) {\n    return props.model.properties.config\n  }\n}\n\nexport default {\n  type: 'parameter-extraction-node',\n  model: AppNodeModel,\n  view: ParameterExtractionNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/parameter-extraction-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"VariableSplittingRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('views.application.form.aiModel.label')\"\n          prop=\"model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('views.application.form.aiModel.placeholder'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ $t('views.application.form.aiModel.label')\n                  }}<span class=\"color-danger ml-4\">*</span></span\n                >\n              </div>\n\n              <el-button\n                :disabled=\"!form_data.model_id\"\n                type=\"primary\"\n                link\n                @click=\"openAIParamSettingDialog(form_data.model_id)\"\n                @refreshForm=\"refreshParam\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <ModelSelect\n            @change=\"model_change\"\n            @wheel=\"wheel\"\n            :teleported=\"false\"\n            v-model=\"form_data.model_id\"\n            :placeholder=\"$t('views.application.form.aiModel.placeholder')\"\n            :options=\"modelOptions\"\n            @submitModel=\"getSelectModel\"\n            showFooter\n            :model-type=\"'LLM'\"\n          ></ModelSelect>\n        </el-form-item>\n        <el-form-item\n          prop=\"input_variable\"\n          :rules=\"{\n            message: $t('workflow.variable.placeholder'),\n            trigger: 'blur',\n            required: true,\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <div>\n                {{ $t('workflow.nodes.variableSplittingNode.inputVariables') }}\n                <span class=\"color-danger\">*</span>\n              </div>\n            </div>\n          </template>\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.variable.placeholder')\"\n            v-model=\"form_data.input_variable\"\n          />\n        </el-form-item>\n        <el-form-item\n          prop=\"variable_list\"\n          :rules=\"{\n            message: $t(\n              'workflow.nodes.parameterExtractionNode.extractParameters.variableListPlaceholder',\n            ),\n            trigger: 'blur',\n            required: true,\n          }\"\n        >\n          <ParametersFieldTable\n            ref=\"ParametersFieldTableRef\"\n            :node-model=\"nodeModel\"\n          ></ParametersFieldTable>\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshParam\" />\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, ref, inject } from 'vue'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\nimport ParametersFieldTable from '@/workflow/nodes/parameter-extraction-node/component/ParametersFieldTable.vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { set, groupBy } from 'lodash'\nconst getResourceDetail = inject('getResourceDetail') as any\nconst props = defineProps<{ nodeModel: any }>()\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\nconst route = useRoute()\nconst {\n  params: { id },\n} = route as any\nconst openAIParamSettingDialog = (modelId: string) => {\n  if (modelId) {\n    AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting)\n  }\n}\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'model_params_setting', data)\n}\nconst resource = getResourceDetail()\nconst modelOptions = ref<any>(null)\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'LLM',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'LLM',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nconst form = {\n  input_variable: [],\n  model_params_setting: {},\n  model_id: '',\n  variable_list: [],\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst model_change = (model_id?: string) => {\n  if (model_id) {\n    AIModeParamSettingDialogRef.value?.reset_default(model_id, id)\n  } else {\n    refreshParam({})\n  }\n}\n\nconst VariableSplittingRef = ref()\nconst validate = async () => {\n  return VariableSplittingRef.value.validate().catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nonMounted(() => {\n  getSelectModel()\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/question-node/index.ts",
    "content": "import QuestionNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass QuestionNode extends AppNode {\n  constructor(props: any) {\n    super(props, QuestionNodeVue)\n  }\n}\nexport default {\n  type: 'question-node',\n  model: AppNodeModel,\n  view: QuestionNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/question-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"questionNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('views.application.form.aiModel.label')\"\n          prop=\"model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('views.application.form.aiModel.placeholder'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <div>\n                <span\n                  >{{ $t('views.application.form.aiModel.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button\n                type=\"primary\"\n                link\n                :disabled=\"!form_data.model_id\"\n                @click=\"openAIParamSettingDialog(form_data.model_id)\"\n                @refreshForm=\"refreshParam\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <ModelSelect\n            @change=\"model_change\"\n            @wheel=\"wheel\"\n            :teleported=\"false\"\n            v-model=\"form_data.model_id\"\n            :placeholder=\"$t('views.application.form.aiModel.placeholder')\"\n            :options=\"modelOptions\"\n            @submitModel=\"getSelectModel\"\n            showFooter\n            :model-type=\"'LLM'\"\n          ></ModelSelect>\n        </el-form-item>\n        <el-form-item>\n          <template #label>\n            <div class=\"flex-between\">\n              <div class=\"flex align-center\">\n                <span>{{ $t('views.application.form.roleSettings.label') }}</span>\n                <el-tooltip\n                  effect=\"dark\"\n                  :content=\"$t('views.application.form.roleSettings.tooltip')\"\n                  placement=\"right\"\n                >\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon ml-4\"></AppIcon>\n                </el-tooltip>\n              </div>\n            </div>\n          </template>\n          <MdEditorMagnify\n            :title=\"$t('views.application.form.roleSettings.label')\"\n            v-model=\"form_data.system\"\n            style=\"height: 100px\"\n            @submitDialog=\"submitSystemDialog\"\n            :placeholder=\"`${t('workflow.SystemPromptPlaceholder')}{{${t('workflow.nodes.startNode.label')}.question}}`\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('views.application.form.prompt.label')\"\n          prop=\"prompt\"\n          :rules=\"{\n            required: true,\n            message: $t('views.application.form.prompt.tooltip'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span\n                  >{{ $t('views.application.form.prompt.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>{{ $t('views.application.form.prompt.tooltip') }}</template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            @wheel=\"wheel\"\n            :title=\"$t('views.application.form.prompt.label')\"\n            v-model=\"form_data.prompt\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitDialog\"\n            :placeholder=\"`${t('workflow.UserPromptPlaceholder')}{{${t('workflow.nodes.startNode.label')}.question}}`\"\n          />\n        </el-form-item>\n        <el-form-item :label=\"$t('views.application.form.historyRecord.label')\">\n          <el-input-number\n            v-model=\"form_data.dialogue_number\"\n            :min=\"0\"\n            :value-on-clear=\"0\"\n            controls-position=\"right\"\n            class=\"w-full\"\n            :step=\"1\"\n            :step-strictly=\"true\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n          @click.prevent\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.is_result\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n\n    <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshParam\" />\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { set, groupBy } from 'lodash'\n\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\nimport type { FormInstance } from 'element-plus'\nimport { ref, computed, onMounted, inject } from 'vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport { t } from '@/locales'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { WorkflowMode } from '@/enums/application'\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nconst model_change = (model_id?: string) => {\n  if (model_id) {\n    AIModeParamSettingDialogRef.value?.reset_default(model_id, id)\n  } else {\n    refreshParam({})\n  }\n}\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'prompt', val)\n}\n\nfunction submitSystemDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'system', val)\n}\n\nconst defaultPrompt = `{{${t('workflow.nodes.startNode.label')}.question}}`\n\nconst form = {\n  model_id: '',\n  system: t('workflow.nodes.questionNode.systemDefault'),\n  prompt: defaultPrompt,\n  dialogue_number: 1,\n  is_result: false,\n}\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'model_params_setting', data)\n}\n\nconst openAIParamSettingDialog = (modelId: string) => {\n  if (modelId) {\n    AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting)\n  }\n}\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\nconst props = defineProps<{ nodeModel: any }>()\n\nconst questionNodeFormRef = ref<FormInstance>()\n\nconst modelOptions = ref<any>(null)\n\nconst validate = () => {\n  return questionNodeFormRef.value?.validate().catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst resource = getResourceDetail()\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'LLM',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'LLM',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nonMounted(() => {\n  getSelectModel()\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/reply-node/index.ts",
    "content": "import ReplyNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass ReplyNode extends AppNode {\n  constructor(props: any) {\n    super(props, ReplyNodeVue)\n  }\n}\nexport default {\n  type: 'reply-node',\n  model: AppNodeModel,\n  view: ReplyNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/reply-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"replyNodeFormRef\"\n      >\n        <el-form-item :label=\"$t('workflow.nodes.replyNode.replyContent')\">\n          <template #label>\n            <div class=\"flex-between\">\n              <span>{{ $t('workflow.nodes.replyNode.replyContent') }}</span>\n              <el-select\n                :teleported=\"false\"\n                v-model=\"form_data.reply_type\"\n                size=\"small\"\n                style=\"width: 85px\"\n              >\n                <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"referencing\" />\n                <el-option :label=\"$t('common.custom')\" value=\"content\" />\n              </el-select>\n            </div>\n          </template>\n\n          <MdEditorMagnify\n            v-if=\"form_data.reply_type === 'content'\"\n            @wheel=\"wheel\"\n            :title=\"$t('workflow.nodes.replyNode.replyContent')\"\n            v-model=\"form_data.content\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitDialog\"\n          />\n          <NodeCascader\n            v-else\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.searchKnowledgeNode.searchQuestion.placeholder')\"\n            v-model=\"form_data.fields\"\n          />\n        </el-form-item>\n        <el-form-item\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n          :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n          @click.prevent\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.is_result\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { set } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport { ref, computed, onMounted, inject } from 'vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport { WorkflowMode } from '@/enums/application'\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\nconst props = defineProps<{ nodeModel: any }>()\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\nconst form = {\n  reply_type: 'content',\n  content: '',\n  fields: [],\n  is_result: true,\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'content', val)\n}\n\nconst replyNodeFormRef = ref()\nconst nodeCascaderRef = ref()\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n    replyNodeFormRef.value?.validate(),\n  ]).catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nonMounted(() => {\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/reranker-node/ParamSettingDialog.vue",
    "content": "<template>\n  <el-dialog\n    align-center\n    :title=\"$t('common.paramSetting')\"\n    class=\"param-dialog\"\n    v-model=\"dialogVisible\"\n    style=\"width: 550px\"\n    append-to-body\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n  >\n    <el-form label-position=\"top\" ref=\"paramFormRef\" :model=\"form\">\n      <el-row :gutter=\"12\">\n        <el-col :span=\"12\">\n          <el-form-item>\n            <template #label>\n              <div class=\"flex align-center\">\n                <span class=\"mr-4\"\n                  >Score {{ $t('workflow.nodes.rerankerNode.higher') }}</span\n                >\n                <el-tooltip\n                  effect=\"dark\"\n                  :content=\"$t('workflow.nodes.rerankerNode.ScoreTooltip')\"\n                  placement=\"right\"\n                >\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                </el-tooltip>\n              </div>\n            </template>\n            <el-input-number\n              v-model=\"form.similarity\"\n              :min=\"0\"\n              :max=\"form.search_mode === 'blend' ? 2 : 1\"\n              :precision=\"3\"\n              :step=\"0.1\"\n              :value-on-clear=\"0\"\n              controls-position=\"right\"\n              class=\"w-full\"\n            />\n          </el-form-item>\n        </el-col>\n        <el-col :span=\"12\">\n          <el-form-item :label=\"$t('views.application.dialog.topReferences')\">\n            <el-input-number\n              v-model=\"form.top_n\"\n              :min=\"1\"\n              :max=\"10000\"\n              :value-on-clear=\"1\"\n              controls-position=\"right\"\n              class=\"w-full\"\n            />\n          </el-form-item>\n        </el-col>\n      </el-row>\n\n      <el-form-item :label=\"$t('views.application.dialog.maxCharacters')\">\n        <el-slider\n          v-model=\"form.max_paragraph_char_number\"\n          show-input\n          :show-input-controls=\"false\"\n          :min=\"500\"\n          :max=\"100000\"\n          class=\"custom-slider\"\n        />\n      </el-form-item>\n    </el-form>\n\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\">{{ $t('common.cancel') }}</el-button>\n        <el-button type=\"primary\" @click=\"submit()\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { cloneDeep } from 'lodash'\nimport type { FormInstance, FormRules } from 'element-plus'\nconst emit = defineEmits(['refresh'])\n\nconst paramFormRef = ref<FormInstance>()\n\nconst form = ref<any>({\n  top_n: 3,\n  similarity: 0,\n  max_paragraph_char_number: 5000,\n})\n\nconst dialogVisible = ref<boolean>(false)\nconst loading = ref(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      top_n: 3,\n      similarity: 0,\n      max_paragraph_char_number: 5000,\n    }\n  }\n})\n\nconst open = (data: any) => {\n  form.value = { ...form.value, ...cloneDeep(data) }\n  dialogVisible.value = true\n}\n\nconst submit = () => {\n  paramFormRef?.value?.validate((valid: boolean, fields: any) => {\n    if (valid) {\n      emit('refresh', cloneDeep(form.value))\n      dialogVisible.value = false\n    }\n  })\n}\n\ndefineExpose({ open })\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/reranker-node/index.ts",
    "content": "import RerankerNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass RerankerNode extends AppNode {\n  constructor(props: any) {\n    super(props, RerankerNodeVue)\n  }\n}\nexport default {\n  type: 'reranker-node',\n  model: AppNodeModel,\n  view: RerankerNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/reranker-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"rerankerNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.rerankerNode.rerankerContent.label')\"\n          prop=\"reranker_reference_list\"\n          :rules=\"{\n            type: 'array',\n            message: $t('workflow.nodes.rerankerNode.rerankerContent.requiredMessage'),\n            trigger: 'change',\n            required: true,\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <span\n                >{{ $t('workflow.nodes.rerankerNode.rerankerContent.label')\n                }}<span class=\"color-danger\">*</span></span\n              >\n              <el-button @click=\"add_reranker_reference\" link type=\"primary\">\n                <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <el-row\n            :gutter=\"8\"\n            style=\"margin-bottom: 8px\"\n            v-for=\"(reranker_reference, index) in form_data.reranker_reference_list\"\n            :key=\"index\"\n            class=\"w-full\"\n          >\n            <el-col :span=\"22\">\n              <el-form-item\n                :prop=\"'reranker_reference_list.' + index\"\n                :rules=\"{\n                  type: 'array',\n                  required: true,\n                  message: $t('workflow.variable.placeholder'),\n                  trigger: 'change',\n                }\"\n              >\n                <NodeCascader\n                  :key=\"index\"\n                  :nodeModel=\"nodeModel\"\n                  class=\"w-full\"\n                  :placeholder=\"$t('workflow.nodes.rerankerNode.rerankerContent.requiredMessage')\"\n                  v-model=\"form_data.reranker_reference_list[index]\"\n                />\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"2\">\n              <el-button link type=\"info\" @click=\"deleteCondition(index)\">\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </el-col>\n          </el-row>\n        </el-form-item>\n        <el-form-item :label=\"$t('workflow.nodes.searchKnowledgeNode.searchParam')\">\n          <template #label>\n            <div class=\"flex-between\">\n              <span>{{ $t('workflow.nodes.searchKnowledgeNode.searchParam') }}</span>\n              <el-button type=\"primary\" link @click=\"openParamSettingDialog\">\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <div class=\"w-full\">\n            <el-row>\n              <el-col :span=\"12\" class=\"color-secondary lighter\">\n                Score\n                {{ $t('workflow.nodes.rerankerNode.higher') }}</el-col\n              >\n              <el-col :span=\"12\" class=\"lighter\">\n                {{ form_data.reranker_setting.similarity?.toFixed(3) }}</el-col\n              >\n              <el-col :span=\"12\" class=\"color-secondary lighter\">\n                {{ $t('chat.KnowledgeSource.referenceParagraph') }} Top</el-col\n              >\n              <el-col :span=\"12\" class=\"lighter\"> {{ form_data.reranker_setting.top_n }}</el-col>\n              <el-col :span=\"12\" class=\"color-secondary lighter\">\n                {{ $t('workflow.nodes.rerankerNode.max_paragraph_char_number') }}</el-col\n              >\n              <el-col :span=\"12\" class=\"lighter\">\n                {{ form_data.reranker_setting.max_paragraph_char_number }}</el-col\n              >\n            </el-row>\n          </div>\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.searchKnowledgeNode.searchQuestion.label')\"\n          prop=\"question_reference_address\"\n          :rules=\"{\n            message: $t('workflow.nodes.searchKnowledgeNode.searchQuestion.requiredMessage'),\n            trigger: 'blur',\n            required: true,\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <span\n                >{{ $t('workflow.nodes.searchKnowledgeNode.searchQuestion.label')\n                }}<span class=\"color-danger\">*</span></span\n              >\n            </div>\n          </template>\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.searchKnowledgeNode.searchQuestion.label')\"\n            v-model=\"form_data.question_reference_address\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.rerankerNode.reranker_model.label')\"\n          prop=\"reranker_model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('workflow.nodes.rerankerNode.reranker_model.placeholder'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <span\n                >{{ $t('workflow.nodes.rerankerNode.reranker_model.label')\n                }}<span class=\"color-danger\">*</span></span\n              >\n            </div>\n          </template>\n          <ModelSelect\n            @wheel=\"wheel\"\n            :teleported=\"false\"\n            v-model=\"form_data.reranker_model_id\"\n            :placeholder=\"$t('workflow.nodes.rerankerNode.reranker_model.placeholder')\"\n            :options=\"modelOptions\"\n            @submitModel=\"getSelectModel\"\n            showFooter\n            :model-type=\"'RERANKER'\"\n          ></ModelSelect>\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.searchKnowledgeNode.showKnowledge.label')\"\n          prop=\"show_knowledge\"\n          required\n          @click.prevent\n        >\n          <el-switch size=\"small\" v-model=\"form_data.show_knowledge\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <ParamSettingDialog ref=\"ParamSettingDialogRef\" @refresh=\"refreshParam\" />\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { set, cloneDeep, groupBy } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport ParamSettingDialog from './ParamSettingDialog.vue'\nimport { ref, computed, onMounted, inject } from 'vue'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\nconst props = defineProps<{ nodeModel: any }>()\n\nconst ParamSettingDialogRef = ref<InstanceType<typeof ParamSettingDialog>>()\n\nconst form = {\n  reranker_reference_list: [[]],\n  reranker_model_id: '',\n  question_reference_address: [],\n  reranker_setting: {\n    top_n: 3,\n    similarity: 0,\n    max_paragraph_char_number: 5000,\n  },\n  show_knowledge: false,\n}\n\nconst modelOptions = ref<any>(null)\nconst openParamSettingDialog = () => {\n  ParamSettingDialogRef.value?.open(form_data.value.reranker_setting)\n}\nconst deleteCondition = (index: number) => {\n  const list = cloneDeep(props.nodeModel.properties.node_data.reranker_reference_list)\n  list.splice(index, 1)\n  set(props.nodeModel.properties.node_data, 'reranker_reference_list', list)\n}\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'reranker_setting', data)\n}\n\nconst resource = getResourceDetail()\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'RERANKER',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'RERANKER',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nconst add_reranker_reference = () => {\n  const list = cloneDeep(props.nodeModel.properties.node_data.reranker_reference_list)\n  list.push([])\n  set(props.nodeModel.properties.node_data, 'reranker_reference_list', list)\n}\nconst rerankerNodeFormRef = ref()\nconst nodeCascaderRef = ref()\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n    rerankerNodeFormRef.value?.validate(),\n  ]).catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nonMounted(() => {\n  getSelectModel()\n  form_data.value.show_knowledge = form_data.value.show_knowledge\n    ? form_data.value.show_knowledge\n    : false\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped>\n.reply-node-editor {\n  :deep(.md-editor-footer) {\n    border: none !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/search-document-node/index.ts",
    "content": "import SearchDocumentVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass SearchDocumentNode extends AppNode {\n  constructor(props: any) {\n    super(props, SearchDocumentVue)\n  }\n}\n\nexport default {\n  type: 'search-document-node',\n  model: AppNodeModel,\n  view: SearchDocumentNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/search-document-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        hide-required-asterisk\n        label-width=\"auto\"\n        ref=\"knowledgeNodeFormRef\"\n      >\n        <el-form-item>\n          <template #label>\n            <div class=\"flex-between\">\n              <span>\n                {{ $t('workflow.nodes.searchDocumentNode.selectKnowledge') }}\n              </span>\n              <span>\n                <el-button\n                  v-if=\"form_data.search_scope_type === 'custom'\"\n                  type=\"primary\"\n                  link\n                  @click=\"openKnowledgeDialog\"\n                >\n                  <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n                </el-button>\n                <el-select\n                  :teleported=\"false\"\n                  size=\"small\"\n                  v-model=\"form_data.search_scope_type\"\n                  style=\"width: 85px\"\n                >\n                  <el-option\n                    :label=\"$t('workflow.variable.Referencing')\"\n                    value=\"referencing\"\n                  />\n                  <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n                </el-select>\n              </span>\n            </div>\n          </template>\n          <div class=\"w-full\" v-if=\"form_data.search_scope_type === 'custom'\">\n            <el-text type=\"info\" v-if=\"form_data.knowledge_id_list?.length === 0\">\n              {{ $t('views.application.form.relatedKnowledge.placeholder') }}\n            </el-text>\n            <template v-for=\"(item, index) in form_data.knowledge_id_list\" :key=\"index\" v-else>\n              <div class=\"flex-between border border-r-6 white-bg mb-4\" style=\"padding: 5px 8px\">\n                <div class=\"flex align-center\" style=\"line-height: 20px\">\n                  <KnowledgeIcon\n                    :type=\"relatedObject(knowledgeList, item, 'id')?.type\"\n                    class=\"mr-8\"\n                    :size=\"20\"\n                  />\n\n                  <div class=\"ellipsis\" :title=\"relatedObject(knowledgeList, item, 'id')?.name\">\n                    {{ relatedObject(knowledgeList, item, 'id')?.name }}\n                  </div>\n                </div>\n                <el-button text @click=\"removeKnowledge(item)\">\n                  <el-icon>\n                    <Close />\n                  </el-icon>\n                </el-button>\n              </div>\n            </template>\n          </div>\n          <div class=\"w-full\" v-else>\n            <el-form-item\n              prop=\"search_scope_reference\"\n              :rules=\"{\n                message: $t('workflow.variable.placeholder'),\n                trigger: 'blur',\n                required: true,\n              }\"\n            >\n              <template #label>\n                <div class=\"flex-between\">\n                  <span>\n                    {{ $t('workflow.nodes.searchDocumentNode.select_variable') }}\n                    <span class=\"color-danger\">*</span></span\n                  >\n                  <span>\n                    <el-select\n                      :teleported=\"false\"\n                      size=\"small\"\n                      v-model=\"form_data.search_scope_source\"\n                      style=\"width: 95px\"\n                      @change=\"form_data.search_scope_reference = []\"\n                    >\n                      <el-option\n                        :label=\"$t('workflow.nodes.searchDocumentNode.knowledgeList')\"\n                        value=\"knowledge\"\n                      />\n                      <el-option\n                        :label=\"$t('workflow.nodes.searchDocumentNode.documentList')\"\n                        value=\"document\"\n                      />\n                    </el-select>\n                  </span>\n                </div>\n              </template>\n              <NodeCascader\n                ref=\"nodeCascaderRef\"\n                :nodeModel=\"nodeModel\"\n                class=\"w-full\"\n                :placeholder=\"$t('workflow.variable.placeholder')\"\n                v-model=\"form_data.search_scope_reference\"\n              />\n            </el-form-item>\n          </div>\n        </el-form-item>\n        <el-form-item :label=\"$t('workflow.nodes.searchDocumentNode.searchSetting')\">\n          <el-radio-group v-model=\"form_data.search_mode\">\n            <el-radio value=\"auto\">\n              <span class=\"flex align-center\">\n                {{ $t('workflow.nodes.searchDocumentNode.auto') }}\n                <el-tooltip\n                  :content=\"$t('workflow.nodes.searchDocumentNode.autoTooltip')\"\n                  placement=\"top\"\n                >\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon ml-4\"></AppIcon>\n                </el-tooltip>\n              </span>\n            </el-radio>\n            <el-radio value=\"custom\" v-if=\"form_data.search_scope_type === 'custom'\">\n              <span class=\"flex align-center\">\n                {{ $t('workflow.nodes.searchDocumentNode.custom') }}\n                <el-tooltip\n                  c\n                  :content=\"$t('workflow.nodes.searchDocumentNode.customTooltip')\"\n                  placement=\"top\"\n                >\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon ml-4\"></AppIcon>\n                </el-tooltip>\n              </span>\n            </el-radio>\n          </el-radio-group>\n        </el-form-item>\n        <div class=\"w-full\">\n          <el-form-item\n            v-if=\"form_data.search_mode === 'auto'\"\n            prop=\"question_reference\"\n            :rules=\"{\n              message: $t(\n                'workflow.nodes.searchKnowledgeNode.searchQuestion.requiredMessage',\n              ),\n              trigger: 'blur',\n              required: true,\n            }\"\n          >\n            <template #label>\n              <span>\n                {{ $t('workflow.nodes.searchKnowledgeNode.searchQuestion.label') }}\n                <span class=\"color-danger\">*</span></span\n              >\n            </template>\n            <NodeCascader\n              ref=\"nodeCascaderRef2\"\n              :nodeModel=\"nodeModel\"\n              class=\"w-full\"\n              :placeholder=\"\n                $t('workflow.nodes.searchKnowledgeNode.searchQuestion.placeholder')\n              \"\n              v-model=\"form_data.question_reference\"\n            />\n          </el-form-item>\n          <div v-else>\n            <div class=\"flex align-center mb-8\">\n              <el-text type=\"info\" class=\"lighter\" size=\"small\">\n                {{ $t('workflow.nodes.conditionNode.conditions.info') }}\n              </el-text>\n              <el-select\n                v-model=\"form_data.search_condition_type\"\n                size=\"small\"\n                style=\"width: 60px; margin: 0 8px\"\n              >\n                <el-option :label=\"$t('workflow.condition.AND')\" value=\"AND\" />\n                <el-option :label=\"$t('workflow.condition.OR')\" value=\"OR\" />\n              </el-select>\n              <el-text type=\"info\" class=\"lighter\" size=\"small\">\n                {{ $t('workflow.nodes.conditionNode.conditions.label') }}\n              </el-text>\n            </div>\n            <div v-for=\"(c, index) in form_data.search_condition_list\" :key=\"index\">\n              <el-row :gutter=\"8\" class=\"mb-8\">\n                <el-col :span=\"8\">\n                  <el-select\n                    v-model=\"c.key\"\n                    filterable\n                    :filter-method=\"filterMethod\"\n                  >\n                    <el-option\n                      v-for=\"tag in form_data.knowledge_tags\"\n                      :key=\"tag\"\n                      :label=\"tag.key\"\n                      :value=\"tag.key\"\n                    />\n                  </el-select>\n                </el-col>\n                <el-col :span=\"7\">\n                  <el-select v-model=\"c.compare\">\n                    <el-option\n                      v-for=\"item in compareList\"\n                      :key=\"item.value\"\n                      :value=\"item.value\"\n                      :label=\"item.label\"\n                    />\n                  </el-select>\n                </el-col>\n                <el-col :span=\"8\">\n                  <el-input\n                    v-model=\"c.value\"\n                    :placeholder=\"$t('workflow.nodes.searchDocumentNode.valueMessage')\"\n                  ></el-input>\n                </el-col>\n                <el-col :span=\"1\">\n                  <el-button link @click=\"delCondition(index)\" type=\"info\" class=\"mt-4\">\n                    <AppIcon iconName=\"app-delete\"></AppIcon>\n                  </el-button>\n                </el-col>\n              </el-row>\n            </div>\n            <el-button link type=\"primary\" @click=\"addCondition\" class=\"mt-8\">\n              <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n              {{ $t('workflow.nodes.conditionNode.addCondition') }}\n            </el-button>\n          </div>\n        </div>\n      </el-form>\n    </el-card>\n    <AddKnowledgeDialog\n      ref=\"AddKnowledgeDialogRef\"\n      @addData=\"addKnowledge\"\n      :data=\"knowledgeList\"\n      :loading=\"knowledgeLoading\"\n    />\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { cloneDeep, set } from 'lodash'\n\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport AddKnowledgeDialog from '@/views/application/component/AddKnowledgeDialog.vue'\nimport type { FormInstance } from 'element-plus'\nimport { computed, onMounted, ref, watch } from 'vue'\nimport { relatedObject } from '@/utils/array'\nimport { t } from '@/locales'\nimport AppIcon from '@/components/app-icon/AppIcon.vue'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { useRoute } from 'vue-router'\n\nconst route = useRoute()\n\nconst props = defineProps<{ nodeModel: any }>()\nconst nodeCascaderRef = ref()\nconst nodeCascaderRef2 = ref()\nconst compareList = [\n  { value: 'contain', label: t('workflow.compare.contain') },\n  { value: 'not_contain', label: t('workflow.compare.not_contain') },\n  { value: 'eq', label: t('workflow.compare.eq') },\n]\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('share/')) {\n    return 'workspaceShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst all_knowledge_tags = ref<Array<any>>([])\nconst form = {\n  knowledge_id_list: [],\n  search_scope_type: 'custom',\n  search_scope_source: 'knowledge',\n  search_scope_reference: [],\n  search_mode: 'auto',\n  question_reference: [],\n  search_condition_type: 'AND',\n  search_condition_list: [],\n  knowledge_tags: [],\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst knowledgeNodeFormRef = ref<FormInstance>()\nconst AddKnowledgeDialogRef = ref<InstanceType<typeof AddKnowledgeDialog>>()\nconst knowledgeList = ref<any>([])\nconst knowledgeLoading = ref(false)\n\nfunction removeKnowledge(id: any) {\n  const list = props.nodeModel.properties.node_data.knowledge_id_list.filter((v: any) => v !== id)\n  set(props.nodeModel.properties.node_data, 'knowledge_id_list', list)\n}\n\nfunction addKnowledge(val: Array<any>) {\n  set(\n    props.nodeModel.properties.node_data,\n    'knowledge_id_list',\n    val.map((item) => item.id),\n  )\n  set(props.nodeModel.properties.node_data, 'knowledge_list', val)\n  knowledgeList.value = val\n}\n\nfunction openKnowledgeDialog() {\n  if (AddKnowledgeDialogRef.value) {\n    AddKnowledgeDialogRef.value.open(form_data.value.knowledge_id_list)\n  }\n}\n\nfunction addCondition() {\n  const list = cloneDeep(form_data.value.search_condition_list)\n  list.push({\n    key: '',\n    compare: 'contain',\n    value: '',\n  })\n  set(form_data.value, 'search_condition_list', list)\n}\n\nfunction delCondition(index: number) {\n  const list = cloneDeep(form_data.value.search_condition_list)\n  list.splice(index, 1)\n  set(form_data.value, 'search_condition_list', list)\n}\n\nfunction getAllTags(knowledge_ids: any) {\n  if (knowledge_ids.length === 0) {\n    set(form_data.value, 'knowledge_tags', [])\n    return\n  }\n  loadSharedApi({ type: 'knowledge', systemType: apiType.value })\n    .getAllTags({ knowledge_ids: knowledge_ids }, {})\n    .then((res: any) => {\n      set(form_data.value, 'knowledge_tags', res.data.slice(0, 100))\n      all_knowledge_tags.value = res.data\n    })\n}\n\nfunction filterMethod(val: string) {\n  form_data.value.knowledge_tags = all_knowledge_tags.value.filter((item: any) => item.key.indexOf(val) > -1).slice(0, 100)\n}\n\nwatch(\n  () => form_data.value.knowledge_id_list,\n  (newVal) => {\n    getAllTags(newVal)\n  },\n  {immediate: true, deep: true},\n)\n\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value?.validate(),\n    nodeCascaderRef2.value?.validate(),\n    knowledgeNodeFormRef.value?.validate(),\n  ]).catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nonMounted(() => {\n  // console.log(props.nodeModel.properties.node_data)\n  knowledgeList.value = props.nodeModel.properties.node_data.knowledge_list\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/search-knowledge-node/index.ts",
    "content": "import SearchKnowledgeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass SearchKnowledgeNode extends AppNode {\n  constructor(props: any) {\n    super(props, SearchKnowledgeVue)\n  }\n}\nexport default {\n  type: 'search-knowledge-node',\n  model: AppNodeModel,\n  view: SearchKnowledgeNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/search-knowledge-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"knowledgeNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item>\n          <template #label>\n            <div class=\"flex-between\">\n              <span> {{ $t('workflow.nodes.searchDocumentNode.selectKnowledge') }}</span>\n              <span>\n                <el-button\n                  v-if=\"form_data.search_scope_type === 'custom'\"\n                  type=\"primary\"\n                  link\n                  @click=\"openknowledgeDialog\"\n                >\n                  <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n                </el-button>\n                <el-select\n                  :teleported=\"false\"\n                  size=\"small\"\n                  v-model=\"form_data.search_scope_type\"\n                  style=\"width: 85px\"\n                >\n                  <el-option\n                    :label=\"$t('workflow.variable.Referencing')\"\n                    value=\"referencing\"\n                  />\n                  <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n                </el-select>\n              </span>\n            </div>\n          </template>\n          <div class=\"w-full\" v-if=\"form_data.search_scope_type === 'custom'\">\n            <el-text type=\"info\" v-if=\"form_data.knowledge_id_list?.length === 0\">\n              {{ $t('views.application.form.relatedKnowledge.placeholder') }}\n            </el-text>\n            <template v-for=\"(item, index) in form_data.knowledge_id_list\" :key=\"index\" v-else>\n              <div class=\"flex-between border border-r-6 white-bg mb-4\" style=\"padding: 5px 8px\">\n                <div class=\"flex align-center\" style=\"line-height: 20px\">\n                  <KnowledgeIcon\n                    :type=\"relatedObject(knowledgeList, item, 'id')?.type\"\n                    class=\"mr-8\"\n                    :size=\"20\"\n                  />\n\n                  <div class=\"ellipsis\" :title=\"relatedObject(knowledgeList, item, 'id')?.name\">\n                    {{ relatedObject(knowledgeList, item, 'id')?.name }}\n                  </div>\n                </div>\n                <el-button text @click=\"removeknowledge(item)\">\n                  <el-icon><Close /></el-icon>\n                </el-button>\n              </div>\n            </template>\n          </div>\n          <div class=\"w-full\" v-else>\n            <el-form-item\n              prop=\"search_scope_reference\"\n              :rules=\"{\n                message: $t('workflow.variable.placeholder'),\n                trigger: 'blur',\n                required: true,\n              }\"\n            >\n              <template #label>\n                <div class=\"flex-between\">\n                  <span>\n                    {{ $t('workflow.nodes.searchDocumentNode.select_variable') }}\n                    <span class=\"color-danger\">*</span>\n                  </span>\n                  <span>\n                    <el-select\n                      :teleported=\"false\"\n                      size=\"small\"\n                      v-model=\"form_data.search_scope_source\"\n                      style=\"width: 95px\"\n                      @change=\"form_data.search_scope_reference = []\"\n                    >\n                      <el-option\n                        :label=\"$t('workflow.nodes.searchDocumentNode.knowledgeList')\"\n                        value=\"knowledge\"\n                      />\n                      <el-option\n                        :label=\"$t('workflow.nodes.searchDocumentNode.documentList')\"\n                        value=\"document\"\n                      />\n                    </el-select>\n                  </span>\n                </div>\n              </template>\n              <NodeCascader\n                ref=\"nodeCascaderRef\"\n                :nodeModel=\"nodeModel\"\n                class=\"w-full\"\n                :placeholder=\"$t('workflow.variable.placeholder')\"\n                v-model=\"form_data.search_scope_reference\"\n              />\n            </el-form-item>\n          </div>\n        </el-form-item>\n        <el-form-item :label=\"$t('workflow.nodes.searchKnowledgeNode.searchParam')\">\n          <template #label>\n            <div class=\"flex-between\">\n              <span>{{ $t('workflow.nodes.searchKnowledgeNode.searchParam') }} </span>\n              <el-button type=\"primary\" link @click=\"openParamSettingDialog\">\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <div class=\"w-full\">\n            <el-row>\n              <el-col :span=\"12\" class=\"color-secondary lighter\">{{\n                $t('views.application.dialog.selectSearchMode')\n              }}</el-col>\n              <el-col :span=\"12\" class=\"lighter\">\n                {{\n                  $t(SearchMode[form_data.knowledge_setting.search_mode as keyof typeof SearchMode])\n                }}</el-col\n              >\n              <el-col :span=\"12\" class=\"color-secondary lighter\">\n                {{ $t('views.application.dialog.similarityThreshold') }}</el-col\n              >\n              <el-col :span=\"12\" class=\"lighter\">\n                {{ form_data.knowledge_setting.similarity?.toFixed(3) }}</el-col\n              >\n              <el-col :span=\"12\" class=\"color-secondary lighter\">{{\n                $t('views.application.dialog.topReferences')\n              }}</el-col>\n              <el-col :span=\"12\" class=\"lighter\"> {{ form_data.knowledge_setting.top_n }}</el-col>\n              <el-col :span=\"12\" class=\"color-secondary lighter\">\n                {{ $t('views.application.dialog.maxCharacters') }}</el-col\n              >\n              <el-col :span=\"12\" class=\"lighter\">\n                {{ form_data.knowledge_setting.max_paragraph_char_number }}</el-col\n              >\n            </el-row>\n          </div>\n        </el-form-item>\n        <el-form-item\n          prop=\"question_reference_address\"\n          :rules=\"{\n            message: $t('workflow.nodes.searchKnowledgeNode.searchQuestion.requiredMessage'),\n            trigger: 'blur',\n            required: true,\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <span>\n                {{ $t('workflow.nodes.searchKnowledgeNode.searchQuestion.label') }}\n                <span class=\"color-danger\">*</span></span\n              >\n            </div>\n          </template>\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.searchKnowledgeNode.searchQuestion.placeholder')\"\n            v-model=\"form_data.question_reference_address\"\n          />\n        </el-form-item>\n        <el-form-item\n          prop=\"show_knowledge\"\n          :rules=\"{\n            message: $t('workflow.nodes.searchKnowledgeNode.showKnowledge.requiredMessage'),\n            trigger: 'blur',\n            required: true,\n          }\"\n          @click.prevent\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <span>\n                {{ $t('workflow.nodes.searchKnowledgeNode.showKnowledge.label') }}\n                <span class=\"color-danger\">*</span></span\n              >\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.show_knowledge\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <ParamSettingDialog ref=\"ParamSettingDialogRef\" @refresh=\"refreshParam\" />\n    <AddknowledgeDialog\n      ref=\"AddknowledgeDialogRef\"\n      @addData=\"addKnowledge\"\n      :data=\"knowledgeList\"\n      :loading=\"knowledgeLoading\"\n    />\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { set } from 'lodash'\n\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport AddknowledgeDialog from '@/views/application/component/AddKnowledgeDialog.vue'\nimport ParamSettingDialog from '@/views/application/component/ParamSettingDialog.vue'\nimport type { FormInstance } from 'element-plus'\nimport { ref, computed, onMounted } from 'vue'\nimport { relatedObject } from '@/utils/array'\nimport { SearchMode } from '@/enums/application'\nimport AppIcon from '@/components/app-icon/AppIcon.vue'\n\nconst props = defineProps<{ nodeModel: any }>()\nconst nodeCascaderRef = ref()\nconst form = {\n  knowledge_id_list: [],\n  knowledge_setting: {\n    top_n: 3,\n    similarity: 0.6,\n    max_paragraph_char_number: 5000,\n    search_mode: 'embedding',\n  },\n  question_reference_address: [],\n  show_knowledge: false,\n  search_scope_type: 'custom',\n  search_scope_source: 'knowledge',\n  search_scope_reference: [],\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst knowledgeNodeFormRef = ref<FormInstance>()\nconst ParamSettingDialogRef = ref<InstanceType<typeof ParamSettingDialog>>()\nconst AddknowledgeDialogRef = ref<InstanceType<typeof AddknowledgeDialog>>()\nconst knowledgeList = ref<any>([])\nconst knowledgeLoading = ref(false)\n\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'knowledge_setting', data.knowledge_setting)\n}\n\nconst openParamSettingDialog = () => {\n  ParamSettingDialogRef.value?.open(form_data.value, 'WORK_FLOW')\n}\n\nfunction removeknowledge(id: any) {\n  const list = props.nodeModel.properties.node_data.knowledge_id_list.filter((v: any) => v !== id)\n  set(props.nodeModel.properties.node_data, 'knowledge_id_list', list)\n}\n\nfunction addKnowledge(val: Array<any>) {\n  set(\n    props.nodeModel.properties.node_data,\n    'knowledge_id_list',\n    val.map((item) => item.id),\n  )\n  set(props.nodeModel.properties.node_data, 'knowledge_list', val)\n  knowledgeList.value = val\n}\n\nfunction openknowledgeDialog() {\n  if (AddknowledgeDialogRef.value) {\n    AddknowledgeDialogRef.value.open(form_data.value.knowledge_id_list)\n  }\n}\n\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value.validate(),\n    knowledgeNodeFormRef.value?.validate(),\n  ]).catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nonMounted(() => {\n  // console.log(props.nodeModel.properties.node_data)\n  knowledgeList.value = props.nodeModel.properties.node_data.knowledge_list\n  form_data.value.show_knowledge = form_data.value.show_knowledge\n    ? form_data.value.show_knowledge\n    : false\n  form_data.value.search_scope_type = form_data.value.search_scope_type\n    ? form_data.value.search_scope_type\n    : 'custom'\n  form_data.value.search_scope_source = form_data.value.search_scope_source\n    ? form_data.value.search_scope_source\n    : 'knowledge'\n  form_data.value.knowledge_id_list = form_data.value.knowledge_id_list\n    ? form_data.value.knowledge_id_list\n    : []\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/speech-to-text-node/index.ts",
    "content": "import SpeechToTextVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass SpeechToTextNode extends AppNode {\n  constructor(props: any) {\n    super(props, SpeechToTextVue)\n  }\n}\nexport default {\n  type: 'speech-to-text-node',\n  model: AppNodeModel,\n  view: SpeechToTextNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/speech-to-text-node/index.vue",
    "content": "<template>\n  <NodeContainer :node-model=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"aiChatNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.speechToTextNode.stt_model.label')\"\n          prop=\"stt_model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('views.application.form.voiceInput.placeholder'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ $t('workflow.nodes.speechToTextNode.stt_model.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button\n                type=\"primary\"\n                link\n                @click=\"openSTTParamSettingDialog\"\n                :disabled=\"!form_data.stt_model_id\"\n                class=\"mr-4\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <ModelSelect\n            @wheel=\"wheel\"\n            :teleported=\"false\"\n            @change=\"sttModelChange\"\n            v-model=\"form_data.stt_model_id\"\n            :placeholder=\"$t('views.application.form.voiceInput.placeholder')\"\n            :options=\"modelOptions\"\n            showFooter\n            :model-type=\"'STT'\"\n          ></ModelSelect>\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.speechToTextNode.audio.label')\"\n          prop=\"audio_list\"\n          :rules=\"{\n            message: $t('workflow.nodes.speechToTextNode.audio.label'),\n            trigger: 'change',\n            required: true,\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ $t('workflow.nodes.speechToTextNode.audio.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n            </div>\n          </template>\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.speechToTextNode.audio.placeholder')\"\n            v-model=\"form_data.audio_list\"\n          />\n        </el-form-item>\n\n        <el-form-item\n          :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n          @click.prevent\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.is_result\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <STTModeParamSettingDialog ref=\"STTModeParamSettingDialogRef\" @refresh=\"refreshSTTForm\" />\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, onMounted, ref, inject } from 'vue'\nimport { groupBy, set } from 'lodash'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport STTModeParamSettingDialog from '@/views/application/component/STTModelParamSettingDialog.vue'\nimport { WorkflowMode } from '@/enums/application'\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst props = defineProps<{ nodeModel: any }>()\nconst modelOptions = ref<any>(null)\nconst STTModeParamSettingDialogRef = ref<InstanceType<typeof STTModeParamSettingDialog>>()\n\nconst aiChatNodeFormRef = ref<FormInstance>()\nconst nodeCascaderRef = ref()\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n    aiChatNodeFormRef.value?.validate(),\n  ]).catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nconst form = {\n  stt_model_id: '',\n  is_result: true,\n  audio_list: [],\n  model_params_setting: {},\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst openSTTParamSettingDialog = () => {\n  const model_id = form_data.value.stt_model_id\n  if (!model_id) {\n    MsgSuccess(t('views.application.form.voiceInput.requiredMessage'))\n    return\n  }\n  STTModeParamSettingDialogRef.value?.open(model_id, id, form_data.value.model_params_setting)\n}\n\nconst refreshSTTForm = (data: any) => {\n  set(props.nodeModel.properties.node_data, 'model_params_setting', data)\n}\n\nfunction sttModelChange(model_id: string) {\n  if (model_id) {\n    STTModeParamSettingDialogRef.value?.reset_default(model_id, id)\n  } else {\n    refreshSTTForm({})\n  }\n}\n\nconst resource = getResourceDetail()\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'STT',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'STT',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\nonMounted(() => {\n  getSelectModel()\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/start-node/index.ts",
    "content": "import StartNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass StartNode extends AppNode {\n  constructor(props: any) {\n    super(props, StartNodeVue)\n  }\n}\nexport default {\n  type: 'start-node',\n  model: AppNodeModel,\n  view: StartNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/start-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.variable.global') }}</h5>\n    <div\n      v-for=\"(item, index) in nodeModel.properties.config.globalFields\"\n      :key=\"index\"\n      class=\"flex-between border-r-6 p-8-12 mb-8 layout-bg lighter\"\n      @mouseenter=\"showicon = true\"\n      @mouseleave=\"showicon = false\"\n    >\n      <span class=\"break-all\">{{ item.label }} {{ '{' + item.value + '}' }}</span>\n      <el-tooltip\n        effect=\"dark\"\n        :content=\"$t('workflow.setting.copyParam')\"\n        placement=\"top\"\n        v-if=\"showicon === true\"\n      >\n        <el-button link @click=\"copyClick(`{{global.${item.value}}}`)\" style=\"padding: 0\">\n          <AppIcon iconName=\"app-copy\"></AppIcon>\n        </el-button>\n      </el-tooltip>\n    </div>\n    <template v-if=\"nodeModel.properties.config.chatFields?.length\">\n      <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.variable.chat') }}</h5>\n      <div\n        v-for=\"(item, index) in nodeModel.properties.config.chatFields || []\"\n        :key=\"index\"\n        class=\"flex-between border-r-6 p-8-12 mb-8 layout-bg lighter\"\n        @mouseenter=\"showicon = true\"\n        @mouseleave=\"showicon = false\"\n      >\n        <span class=\"break-all\">{{ item.label }} {{ '{' + item.value + '}' }}</span>\n        <el-tooltip\n          effect=\"dark\"\n          :content=\"$t('workflow.setting.copyParam')\"\n          placement=\"top\"\n          v-if=\"showicon === true\"\n        >\n          <el-button link @click=\"copyClick(`{{chat.${item.value}}}`)\" style=\"padding: 0\">\n            <AppIcon iconName=\"app-copy\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </div>\n    </template>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { cloneDeep, set } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { copyClick } from '@/utils/clipboard'\nimport { ref, onMounted } from 'vue'\nimport { t } from '@/locales'\nconst props = defineProps<{ nodeModel: any }>()\n\nconst showicon = ref(false)\nconst globalFields = [\n  { label: t('workflow.nodes.startNode.currentTime'), value: 'time' },\n  {\n    label: t('views.application.form.historyRecord.label'),\n    value: 'history_context',\n  },\n  { label: t('chat.chatId'), value: 'chat_id' },\n  {\n    label: t('chat.chatUserId'),\n    value: 'chat_user_id',\n  },\n  {\n    label: t('chat.chatUserType'),\n    value: 'chat_user_type',\n  },\n  {\n    label: t('chat.chatUserGroup'),\n    value: 'chat_user_group',\n  },\n  {\n    label: t('views.chatUser.title'),\n    value: 'chat_user',\n  },\n]\n\nconst getRefreshFieldList = () => {\n  const user_input_fields = props.nodeModel.graphModel.nodes\n    .filter((v: any) => v.id === 'base-node')\n    .map((v: any) => cloneDeep(v.properties.user_input_field_list))\n    .reduce((x: any, y: any) => [...x, ...y], [])\n    .map((i: any) => {\n      if (i.label && i.label.input_type === 'TooltipLabel') {\n        return { label: i.label.label, value: i.field || i.variable }\n      }\n      return { label: i.label || i.name, value: i.field || i.variable }\n    })\n  const api_input_fields = props.nodeModel.graphModel.nodes\n    .filter((v: any) => v.id === 'base-node')\n    .map((v: any) => cloneDeep(v.properties.api_input_field_list))\n    .reduce((x: any, y: any) => [...x, ...y], [])\n    .map((i: any) => ({ label: i.name || i.variable, value: i.variable }))\n  return [...user_input_fields, ...api_input_fields]\n}\nconst refreshFieldList = () => {\n  const refreshFieldList = getRefreshFieldList()\n  set(props.nodeModel.properties.config, 'globalFields', [...globalFields, ...refreshFieldList])\n}\n\nconst refreshChatFieldList = () => {\n  const chatFieldList = props.nodeModel.graphModel.nodes\n    .filter((v: any) => v.id === 'base-node')\n    .map((v: any) => cloneDeep(v.properties.chat_input_field_list || []))\n    .reduce((x: any, y: any) => [...x, ...y], [])\n    .map((i: any) => ({ label: i.label, value: i.field }))\n\n  set(props.nodeModel.properties.config, 'chatFields', chatFieldList)\n}\nprops.nodeModel.graphModel.eventCenter.on('refreshFieldList', refreshFieldList)\nprops.nodeModel.graphModel.eventCenter.on('chatFieldList', refreshChatFieldList)\nconst refreshFileUploadConfig = () => {\n  let fields = cloneDeep(props.nodeModel.properties.config.fields)\n  const form_data = props.nodeModel.graphModel.nodes\n    .filter((v: any) => v.id === 'base-node')\n    .filter((v: any) => v.properties.node_data.file_upload_enable)\n    .map((v: any) => cloneDeep(v.properties.node_data.file_upload_setting))\n    .filter((v: any) => v)\n\n  fields = fields.filter(\n    (item: any) =>\n      item.value !== 'image' &&\n      item.value !== 'document' &&\n      item.value !== 'audio' &&\n      item.value !== 'video' &&\n      item.value !== 'other',\n  )\n\n  if (form_data.length === 0) {\n    set(props.nodeModel.properties.config, 'fields', fields)\n    return\n  }\n  const fileUploadFields = []\n  if (form_data[0].document) {\n    fileUploadFields.push({ label: t('common.fileUpload.document'), value: 'document' })\n  }\n  if (form_data[0].image) {\n    fileUploadFields.push({ label: t('common.fileUpload.image'), value: 'image' })\n  }\n  if (form_data[0].audio) {\n    fileUploadFields.push({ label: t('common.fileUpload.audio'), value: 'audio' })\n  }\n  if (form_data[0].video) {\n    fileUploadFields.push({ label: t('common.fileUpload.video'), value: 'video' })\n  }\n  if (form_data[0].other) {\n    fileUploadFields.push({ label: t('common.fileUpload.other'), value: 'other' })\n  }\n\n  set(props.nodeModel.properties.config, 'fields', [...fields, ...fileUploadFields])\n}\nprops.nodeModel.graphModel.eventCenter.on('refreshFileUploadConfig', refreshFileUploadConfig)\n\nonMounted(() => {\n  refreshChatFieldList()\n  refreshFieldList()\n  refreshFileUploadConfig()\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/text-to-speech-node/index.ts",
    "content": "import TextToSpeechVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass TextToSpeechNode extends AppNode {\n  constructor(props: any) {\n    super(props, TextToSpeechVue)\n  }\n}\nexport default {\n  type: 'text-to-speech-node',\n  model: AppNodeModel,\n  view: TextToSpeechNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/text-to-speech-node/index.vue",
    "content": "<template>\n  <NodeContainer :node-model=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"aiChatNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.textToSpeechNode.tts_model.label')\"\n          prop=\"tts_model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('views.application.form.voicePlay.placeholder'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ $t('workflow.nodes.textToSpeechNode.tts_model.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button\n                type=\"primary\"\n                link\n                @click=\"openTTSParamSettingDialog\"\n                :disabled=\"!form_data.tts_model_id\"\n                class=\"mr-4\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <ModelSelect\n            @wheel=\"wheel\"\n            :teleported=\"false\"\n            v-model=\"form_data.tts_model_id\"\n            :placeholder=\"$t('views.application.form.voicePlay.placeholder')\"\n            :options=\"modelOptions\"\n            showFooter\n            :model-type=\"'TTS'\"\n          ></ModelSelect>\n        </el-form-item>\n        <el-form-item\n          prop=\"content_list\"\n          :label=\"$t('workflow.nodes.textToSpeechNode.content.label')\"\n          :rules=\"{\n            message: $t('workflow.nodes.textToSpeechNode.content.label'),\n            trigger: 'blur',\n            required: true,\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ $t('workflow.nodes.textToSpeechNode.content.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n            </div>\n          </template>\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.textToSpeechNode.content.label')\"\n            v-model=\"form_data.content_list\"\n          />\n        </el-form-item>\n\n        <el-form-item\n          :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n          @click.prevent\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.is_result\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <TTSModeParamSettingDialog ref=\"TTSModeParamSettingDialogRef\" @refresh=\"refreshTTSForm\" />\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport { computed, onMounted, ref, inject } from 'vue'\nimport { groupBy, set } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport TTSModeParamSettingDialog from '@/views/application/component/TTSModeParamSettingDialog.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormInstance } from 'element-plus'\nimport { MsgSuccess } from '@/utils/message'\nimport { t } from '@/locales'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { WorkflowMode } from '@/enums/application'\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\nconst props = defineProps<{ nodeModel: any }>()\n\nconst TTSModeParamSettingDialogRef = ref<InstanceType<typeof TTSModeParamSettingDialog>>()\n\nconst modelOptions = ref<any>(null)\n\nconst aiChatNodeFormRef = ref<FormInstance>()\nconst nodeCascaderRef = ref()\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n    aiChatNodeFormRef.value?.validate(),\n  ]).catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nconst form = {\n  tts_model_id: '',\n  is_result: true,\n  content_list: [],\n  model_params_setting: {},\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst resource = getResourceDetail()\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'TTS',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'TTS',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nconst openTTSParamSettingDialog = () => {\n  const model_id = form_data.value.tts_model_id\n  if (!model_id) {\n    MsgSuccess(t('views.application.form.voicePlay.requiredMessage'))\n    return\n  }\n  TTSModeParamSettingDialogRef.value?.open(model_id, id, form_data.value.model_params_setting)\n}\nconst refreshTTSForm = (data: any) => {\n  form_data.value.model_params_setting = data\n}\n\nonMounted(() => {\n  getSelectModel()\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/text-to-video/index.ts",
    "content": "import VideoGenerateNodeVue from './index.vue'\nimport {AppNode, AppNodeModel} from '@/workflow/common/app-node'\n\nclass TextToVideoNode extends AppNode {\n  constructor(props: any) {\n    super(props, VideoGenerateNodeVue)\n  }\n}\n\nexport default {\n  type: 'text-to-video-node',\n  model: AppNodeModel,\n  view: TextToVideoNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/text-to-video/index.vue",
    "content": "<template>\n  <NodeContainer :node-model=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"aiChatNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.textToVideoGenerate.model.label')\"\n          prop=\"model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('workflow.nodes.textToVideoGenerate.model.requiredMessage'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ $t('workflow.nodes.textToVideoGenerate.model.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button\n                :disabled=\"!form_data.model_id\"\n                type=\"primary\"\n                link\n                @click=\"openAIParamSettingDialog(form_data.model_id)\"\n                @refreshForm=\"refreshParam\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n\n          <ModelSelect\n            @change=\"model_change\"\n            @wheel=\"wheel\"\n            @focus=\"getSelectModel\"\n            :teleported=\"false\"\n            v-model=\"form_data.model_id\"\n            :placeholder=\"$t('workflow.nodes.textToVideoGenerate.model.requiredMessage')\"\n            :options=\"modelOptions\"\n            showFooter\n            :model-type=\"'TTV'\"\n          ></ModelSelect>\n        </el-form-item>\n\n        <el-form-item\n          :label=\"$t('workflow.nodes.textToVideoGenerate.prompt.label')\"\n          prop=\"prompt\"\n          :rules=\"{\n            required: true,\n            message: $t('common.prompt.placeholder'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span\n                  >{{ $t('workflow.nodes.textToVideoGenerate.prompt.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content\n                  >{{ $t('workflow.nodes.textToVideoGenerate.prompt.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            @wheel=\"wheel\"\n            :title=\"$t('workflow.nodes.textToVideoGenerate.prompt.label')\"\n            v-model=\"form_data.prompt\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitDialog\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.textToVideoGenerate.negative_prompt.label')\"\n          prop=\"prompt\"\n          :rules=\"{\n            required: false,\n            message: $t('common.prompt.placeholder'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.textToVideoGenerate.negative_prompt.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content\n                  >{{ $t('workflow.nodes.textToVideoGenerate.negative_prompt.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            @wheel=\"wheel\"\n            :title=\"$t('workflow.nodes.textToVideoGenerate.negative_prompt.label')\"\n            v-model=\"form_data.negative_prompt\"\n            :placeholder=\"$t('workflow.nodes.textToVideoGenerate.negative_prompt.placeholder')\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitNegativeDialog\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n          @click.prevent\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.is_result\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshParam\" />\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, nextTick, onMounted, ref, inject } from 'vue'\nimport { groupBy, set } from 'lodash'\nimport type { FormInstance } from 'element-plus'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\nimport { t } from '@/locales'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { WorkflowMode } from '@/enums/application'\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst props = defineProps<{ nodeModel: any }>()\nconst modelOptions = ref<any>(null)\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\n\nconst aiChatNodeFormRef = ref<FormInstance>()\nconst validate = () => {\n  return aiChatNodeFormRef.value?.validate().catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nconst defaultPrompt = `{{${t('workflow.nodes.startNode.label')}.question}}`\n\nconst form = {\n  model_id: '',\n  system: '',\n  prompt: defaultPrompt,\n  negative_prompt: '',\n  dialogue_number: 0,\n  dialogue_type: 'NODE',\n  is_result: true,\n  temperature: null,\n  max_tokens: null,\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst resource = getResourceDetail()\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'TTV',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'TTV',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nconst model_change = () => {\n  nextTick(() => {\n    if (form_data.value.model_id) {\n      AIModeParamSettingDialogRef.value?.reset_default(form_data.value.model_id, id)\n    } else {\n      refreshParam({})\n    }\n  })\n}\n\nconst openAIParamSettingDialog = (modelId: string) => {\n  if (modelId) {\n    AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting)\n  }\n}\n\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'model_params_setting', data)\n}\n\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'prompt', val)\n}\n\nfunction submitNegativeDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'negative_prompt', val)\n}\n\nonMounted(() => {\n  getSelectModel()\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-base-node/component/input/InputFieldFormDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"isEdit ? $t('common.param.editParam') : $t('common.param.addParam')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item :label=\"$t('views.tool.form.paramName.label')\" prop=\"field\">\n        <el-input\n          v-model=\"form.field\"\n          :placeholder=\"$t('views.tool.form.paramName.placeholder')\"\n          maxlength=\"64\"\n          show-word-limit\n          @blur=\"form.field = form.field.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('dynamicsForm.paramForm.name.label')\">\n        <el-input\n          v-model=\"form.label\"\n          :placeholder=\"$t('dynamicsForm.paramForm.name.placeholder')\"\n          :maxlength=\"128\"\n          show-word-limit\n          @blur=\"form.label = form.label?.trim()\"\n        />\n      </el-form-item>\n      <el-form-item :label=\"$t('views.tool.form.dataType.label')\">\n        <el-select v-model=\"form.type\">\n          <el-option v-for=\"item in typeOptions\" :key=\"item\" :label=\"item\" :value=\"item\" />\n        </el-select>\n      </el-form-item>\n\n      <el-form-item :label=\"$t('dynamicsForm.paramForm.required.label')\" @click.prevent>\n        <el-switch size=\"small\" v-model=\"form.is_required\"></el-switch>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, watch } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst typeOptions = ['string', 'int', 'dict', 'array', 'float', 'boolean']\nconst emit = defineEmits(['refresh'])\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\nconst form = ref<any>({\n  field: '',\n  type: typeOptions[0],\n  label: '',\n  is_required: true,\n})\n\nconst rules = reactive({\n  field: [\n    {\n      required: true,\n      message: t('views.tool.form.paramName.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      field: '',\n      type: typeOptions[0],\n      label: '',\n      is_required: true,\n    }\n    isEdit.value = false\n  }\n})\n\nconst open = (row: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n    isEdit.value = true\n  }\n\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value)\n    }\n  })\n}\nconst close = () => {\n  dialogVisible.value = false\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-base-node/component/input/InputFieldTable.vue",
    "content": "<template>\n  <div class=\"flex-between mb-16\">\n    <h5 class=\"break-all ellipsis lighter\" style=\"max-width: 80%\" :title=\"inputFieldConfig.title\">\n      {{ inputFieldConfig.title }}\n    </h5>\n    <div>\n      <el-button type=\"primary\" link @click=\"openChangeTitleDialog\">\n        <AppIcon iconName=\"app-setting\"></AppIcon>\n      </el-button>\n      <span class=\"ml-4\">\n        <el-button link type=\"primary\" @click=\"openAddDialog()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </span>\n    </div>\n  </div>\n\n  <el-table ref=\"inputFieldTableRef\" :data=\"inputFieldList\" class=\"mb-16\">\n    <el-table-column prop=\"field\" :label=\"$t('views.tool.form.paramName.label')\" />\n    <el-table-column :label=\"$t('views.tool.form.dataType.label')\">\n      <template #default=\"{ row }\">\n        <el-tag type=\"info\" class=\"info-tag\">{{ row.type }}</el-tag>\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.required')\">\n      <template #default=\"{ row }\">\n        <div @click.stop>\n          <el-switch size=\"small\" v-model=\"row.is_required\" />\n        </div>\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n      <template #default=\"{ row, $index }\">\n        <span class=\"mr-4\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n            <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n              <AppIcon iconName=\"app-edit\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n          <el-button type=\"primary\" text @click=\"deleteField($index)\">\n            <AppIcon iconName=\"app-delete\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </template>\n    </el-table-column>\n  </el-table>\n  <InputFieldFormDialog ref=\"inputFieldFormDialogRef\" @refresh=\"refreshFieldList\" />\n  <InputTitleDialog ref=\"inputTitleDialogRef\" @refresh=\"refreshFieldTitle\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport Sortable from 'sortablejs'\nimport { MsgError } from '@/utils/message'\nimport InputFieldFormDialog from './InputFieldFormDialog.vue'\nimport { t } from '@/locales'\nimport InputTitleDialog from '@/workflow/nodes/tool-base-node/component/input/InputTitleDialog.vue'\nconst props = defineProps<{ nodeModel: any }>()\nconst tableRef = ref()\nconst inputFieldFormDialogRef = ref<InstanceType<typeof InputFieldFormDialog>>()\nconst inputTitleDialogRef = ref<InstanceType<typeof InputTitleDialog>>()\nconst inputFieldList = ref<any[]>([])\nconst inputFieldConfig = ref({ title: t('chat.userInput') })\n\nfunction openAddDialog(data?: any, index?: any) {\n  if (index !== undefined) {\n    currentIndex.value = index\n  }\n  inputFieldFormDialogRef.value?.open(data)\n}\n\nfunction openChangeTitleDialog() {\n  inputTitleDialogRef.value?.open(inputFieldConfig.value)\n}\n\nfunction deleteField(index: any) {\n  inputFieldList.value.splice(index, 1)\n  props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n  onDragHandle()\n}\nconst currentIndex = ref<number | null>(null)\nfunction refreshFieldList(data: any) {\n  if (currentIndex.value !== null) {\n    inputFieldList.value?.splice(currentIndex.value, 1, data)\n  } else {\n    if (inputFieldList.value.some((field) => field.field == data.field)) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.field)\n      return\n    }\n    inputFieldList.value?.push(data)\n  }\n  set(props.nodeModel.properties, 'user_input_field_list', cloneDeep(inputFieldList.value))\n  props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n  props.nodeModel.graphModel.getNodeModelById('tool-start-node').clear_next_node_field(true)\n  inputFieldFormDialogRef.value?.close()\n  currentIndex.value = null\n}\n\nfunction refreshFieldTitle(data: any) {\n  inputFieldConfig.value = data\n  inputTitleDialogRef.value?.close()\n}\n\nfunction onDragHandle() {\n  if (!tableRef.value) return\n\n  // 获取表格的 tbody DOM 元素\n  const wrapper = tableRef.value.$el as HTMLElement\n  const tbody = wrapper.querySelector('.el-table__body-wrapper tbody')\n  if (!tbody) return\n  // 初始化 Sortable\n  Sortable.create(tbody as HTMLElement, {\n    animation: 150,\n    ghostClass: 'ghost-row',\n    onEnd: (evt) => {\n      if (evt.oldIndex === undefined || evt.newIndex === undefined) return\n      // 更新数据顺序\n      const items = cloneDeep([...inputFieldList.value])\n      const [movedItem] = items.splice(evt.oldIndex, 1)\n      items.splice(evt.newIndex, 0, movedItem)\n      inputFieldList.value = items\n      props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n    },\n  })\n}\n\nonMounted(() => {\n  if (props.nodeModel.properties.user_input_config) {\n    inputFieldConfig.value = cloneDeep(props.nodeModel.properties.user_input_config)\n  }\n  if (props.nodeModel.properties.user_input_field_list) {\n    inputFieldList.value = cloneDeep(props.nodeModel.properties.user_input_field_list)\n  }\n  props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n  onDragHandle()\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-base-node/component/input/InputTitleDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('common.setting')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-form-item :label=\"$t('common.title')\" prop=\"title\">\n        <el-input\n          v-model=\"form.title\"\n          maxlength=\"64\"\n          show-word-limit\n          @blur=\"form.title = form.title.trim()\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\n\nconst form = ref<any>({\n  title: t('chat.userInput'),\n})\n\nconst rules = reactive({\n  title: [\n    { required: true, message: t('dynamicsForm.paramForm.name.requiredMessage'), trigger: 'blur' },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n  }\n\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  dialogVisible.value = false\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value)\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldFormDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"isEdit ? $t('common.param.editParam') : $t('common.param.addParam')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item :label=\"$t('views.tool.form.paramName.label')\" prop=\"field\">\n        <el-input\n          v-model=\"form.field\"\n          :placeholder=\"$t('views.tool.form.paramName.placeholder')\"\n          maxlength=\"64\"\n          show-word-limit\n          @blur=\"form.field = form.field.trim()\"\n        />\n      </el-form-item>\n\n      <el-form-item :label=\"$t('dynamicsForm.paramForm.name.label')\">\n        <el-input\n          v-model=\"form.label\"\n          :placeholder=\"$t('dynamicsForm.paramForm.name.placeholder')\"\n          :maxlength=\"128\"\n          show-word-limit\n          @blur=\"form.label = form.label?.trim()\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { ref, reactive, watch } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst typeOptions = ['string', 'int', 'dict', 'array', 'float', 'boolean']\n\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\n\nconst form = ref<any>({\n  field: '',\n  type: typeOptions[0],\n  label: '',\n  is_required: true,\n})\n\nconst rules = reactive({\n  field: [\n    {\n      required: true,\n      message: t('views.tool.form.paramName.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nwatch(dialogVisible, (bool) => {\n  if (!bool) {\n    form.value = {\n      field: '',\n      type: typeOptions[0],\n      label: '',\n      is_required: true,\n    }\n    isEdit.value = false\n  }\n})\n\nconst open = (row: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n    isEdit.value = true\n  }\n\n  dialogVisible.value = true\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value)\n    }\n  })\n}\nconst close = () => {\n  dialogVisible.value = false\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-base-node/component/output/OutputFieldTable.vue",
    "content": "<template>\n  <div class=\"flex-between mb-16\">\n    <h5 class=\"break-all ellipsis lighter\" style=\"max-width: 80%\" :title=\"outputFieldConfig.title\">\n      {{ outputFieldConfig.title }}\n    </h5>\n    <div>\n      <el-button type=\"primary\" link @click=\"openChangeTitleDialog\">\n        <AppIcon iconName=\"app-setting\"></AppIcon>\n      </el-button>\n      <span class=\"ml-4\">\n        <el-button link type=\"primary\" @click=\"openAddDialog()\">\n          <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n          {{ $t('common.add') }}\n        </el-button>\n      </span>\n    </div>\n  </div>\n\n  <el-table ref=\"inputFieldTableRef\" :data=\"inputFieldList\" class=\"mb-16\">\n    <el-table-column prop=\"field\" :label=\"$t('views.tool.form.paramName.label')\" />\n    <el-table-column prop=\"label\" :label=\"$t('dynamicsForm.paramForm.name.label')\" />\n    <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n      <template #default=\"{ row, $index }\">\n        <span class=\"mr-4\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n            <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n              <AppIcon iconName=\"app-edit\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n          <el-button type=\"primary\" text @click=\"deleteField($index)\">\n            <AppIcon iconName=\"app-delete\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </template>\n    </el-table-column>\n  </el-table>\n  <InputFieldFormDialog ref=\"inputFieldFormDialogRef\" @refresh=\"refreshFieldList\" />\n  <OutputTitleDialog ref=\"outputTitleDialogRef\" @refresh=\"refreshFieldTitle\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport Sortable from 'sortablejs'\nimport { MsgError } from '@/utils/message'\nimport InputFieldFormDialog from './OutputFieldFormDialog.vue'\nimport { t } from '@/locales'\nimport OutputTitleDialog from '@/workflow/nodes/tool-base-node/component/output/OutputTitleDialog.vue'\nconst props = defineProps<{ nodeModel: any }>()\nconst tableRef = ref()\nconst inputFieldFormDialogRef = ref<InstanceType<typeof InputFieldFormDialog>>()\nconst outputTitleDialogRef = ref<InstanceType<typeof OutputTitleDialog>>()\nconst inputFieldList = ref<any[]>([])\nconst outputFieldConfig = ref({ title: t('chat.userOutput', '输出参数') })\n\nfunction openAddDialog(data?: any, index?: any) {\n  if (index) {\n    currentIndex.value = index\n  }\n  inputFieldFormDialogRef.value?.open(data)\n}\n\nfunction openChangeTitleDialog() {\n  outputTitleDialogRef.value?.open(outputFieldConfig.value)\n}\n\nfunction deleteField(index: any) {\n  inputFieldList.value = inputFieldList.value.filter((item, i) => i !== index)\n  set(props.nodeModel.properties, 'user_output_field_list', inputFieldList.value)\n}\nconst currentIndex = ref<number | null>(null)\nfunction refreshFieldList(data: any) {\n  if (currentIndex.value !== null) {\n    inputFieldList.value?.splice(currentIndex.value, 1, data)\n  } else {\n    if (inputFieldList.value.some((field) => field.field == data.field)) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.field)\n      return\n    }\n    inputFieldList.value?.push(data)\n  }\n  set(props.nodeModel.properties, 'user_output_field_list', cloneDeep(inputFieldList.value))\n  inputFieldFormDialogRef.value?.close()\n  props.nodeModel.graphModel.getNodeModelById('tool-start-node').clear_next_node_field(true)\n  currentIndex.value = null\n}\n\nfunction refreshFieldTitle(data: any) {\n  outputFieldConfig.value = data\n  outputTitleDialogRef.value?.close()\n}\n\nfunction onDragHandle() {\n  if (!tableRef.value) return\n\n  // 获取表格的 tbody DOM 元素\n  const wrapper = tableRef.value.$el as HTMLElement\n  const tbody = wrapper.querySelector('.el-table__body-wrapper tbody')\n  if (!tbody) return\n  // 初始化 Sortable\n  Sortable.create(tbody as HTMLElement, {\n    animation: 150,\n    ghostClass: 'ghost-row',\n    onEnd: (evt) => {\n      if (evt.oldIndex === undefined || evt.newIndex === undefined) return\n      // 更新数据顺序\n      const items = cloneDeep([...inputFieldList.value])\n      const [movedItem] = items.splice(evt.oldIndex, 1)\n      items.splice(evt.newIndex, 0, movedItem)\n      inputFieldList.value = items\n      props.nodeModel.graphModel.eventCenter.emit('refreshFieldList')\n    },\n  })\n}\n\nonMounted(() => {\n  if (props.nodeModel.properties.user_output_config) {\n    outputFieldConfig.value = cloneDeep(props.nodeModel.properties.user_output_config)\n  }\n  if (props.nodeModel.properties.user_output_field_list) {\n    inputFieldList.value = cloneDeep(props.nodeModel.properties.user_output_field_list)\n  }\n  onDragHandle()\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-base-node/component/output/OutputTitleDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"$t('common.setting')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n      @submit.prevent\n    >\n      <el-form-item :label=\"$t('common.title')\" prop=\"title\">\n        <el-input\n          v-model=\"form.title\"\n          maxlength=\"64\"\n          show-word-limit\n          @blur=\"form.title = form.title.trim()\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"dialogVisible = false\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref, watch } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\n\nconst form = ref<any>({\n  title: t('chat.userOutput', '输出参数'),\n})\n\nconst rules = reactive({\n  title: [\n    { required: true, message: t('dynamicsForm.paramForm.name.requiredMessage'), trigger: 'blur' },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n  }\n\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  dialogVisible.value = false\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value)\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-base-node/index.ts",
    "content": "import ToolBaseNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass ToolBaseNode extends AppNode {\n  constructor(props: any) {\n    super(props, ToolBaseNodeVue)\n  }\n}\nexport default {\n  type: 'tool-base-node',\n  model: AppNodeModel,\n  view: ToolBaseNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-base-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <InputFieldTable :nodeModel=\"nodeModel\"></InputFieldTable>\n    <OutFieldTable :nodeModel=\"nodeModel\"></OutFieldTable>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nconst props = defineProps<{ nodeModel: any }>()\nimport InputFieldTable from './component/input/InputFieldTable.vue'\nimport OutFieldTable from './component/output/OutputFieldTable.vue'\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-lib-node/index.ts",
    "content": "import ToolLibNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass ToolLibNode extends AppNode {\n  constructor(props: any) {\n    super(props, ToolLibNodeVue)\n  }\n}\nexport default {\n  type: 'tool-lib-node',\n  model: AppNodeModel,\n  view: ToolLibNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-lib-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-16\">{{ $t('workflow.nodeSetting') }}</h5>\n    <h5 class=\"lighter mb-8\">{{ $t('common.param.inputParam') }}</h5>\n    <el-form\n      @submit.prevent\n      ref=\"ToolNodeFormRef\"\n      :model=\"chat_data\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      hide-required-asterisk\n    >\n      <el-card shadow=\"never\" class=\"card-never mb-16\" style=\"--el-card-padding: 12px\">\n        <div v-if=\"chat_data.input_field_list?.length > 0\">\n          <template v-for=\"(item, index) in chat_data.input_field_list\" :key=\"item.name\">\n            <el-form-item\n              :label=\"item.name\"\n              :prop=\"'input_field_list.' + index + '.value'\"\n              :rules=\"{\n                required: item.is_required,\n                message:\n                  item.source === 'reference'\n                    ? $t('views.tool.form.param.selectPlaceholder')\n                    : $t('views.tool.form.param.inputPlaceholder'),\n                trigger: 'blur',\n              }\"\n            >\n              <template #label>\n                <div class=\"flex-between\">\n                  <div class=\"flex align-center\">\n                    <div class=\"mr-4\">\n                      <auto-tooltip :content=\"item.name\" style=\"max-width: 130px\">\n                        {{ item.name }}\n                      </auto-tooltip>\n                    </div>\n                    <el-tooltip\n                      v-if=\"item.desc\"\n                      effect=\"dark\"\n                      placement=\"right\"\n                      popper-class=\"max-w-200\"\n                    >\n                      <template #content>\n                        {{ item.desc }}\n                      </template>\n                      <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                    </el-tooltip>\n\n                    <span class=\"color-danger\" v-if=\"item.is_required\">*</span>\n\n                    <el-tag size=\"small\" type=\"info\" class=\"info-tag ml-4\">{{ item.type }}</el-tag>\n                  </div>\n                </div>\n              </template>\n              <NodeCascader\n                v-if=\"item.source === 'reference'\"\n                ref=\"nodeCascaderRef\"\n                :nodeModel=\"nodeModel\"\n                class=\"w-full\"\n                :placeholder=\"$t('views.tool.form.param.selectPlaceholder')\"\n                v-model=\"item.value\"\n              />\n              <el-input\n                v-else\n                v-model=\"item.value\"\n                :placeholder=\"$t('views.tool.form.param.inputPlaceholder')\"\n              />\n            </el-form-item>\n          </template>\n        </div>\n\n        <el-text type=\"info\" v-else> {{ $t('common.noData') }} </el-text>\n      </el-card>\n      <el-form-item\n        :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n        @click.prevent\n        v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n      >\n        <template #label>\n          <div class=\"flex align-center\">\n            <div class=\"mr-4\">\n              <span>{{\n                $t('workflow.nodes.aiChatNode.returnContent.label')\n              }}</span>\n            </div>\n            <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n              <template #content>\n                {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n              </template>\n              <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n            </el-tooltip>\n          </div>\n        </template>\n        <el-switch size=\"small\" v-model=\"chat_data.is_result\" />\n      </el-form-item>\n    </el-form>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { set } from 'lodash'\nimport { useRoute } from 'vue-router'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormInstance } from 'element-plus'\nimport { ref, computed, onMounted, inject } from 'vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport { WorkflowMode } from '@/enums/application'\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst nodeCascaderRef = ref()\n\nconst form = {\n  input_field_list: [],\n  is_result: false,\n}\n\nconst chat_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst ToolNodeFormRef = ref<FormInstance>()\n\nconst validate = () => {\n  return ToolNodeFormRef.value?.validate().catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst update_field = () => {\n  if (!props.nodeModel.properties.node_data.tool_lib_id) {\n    set(props.nodeModel.properties, 'status', 500)\n    return\n  }\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getToolById(props.nodeModel.properties.node_data.tool_lib_id)\n    .then((ok: any) => {\n      const old_input_field_list = props.nodeModel.properties.node_data.input_field_list\n      const merge_input_field_list = ok.data.input_field_list.map((item: any) => {\n        const find_field = old_input_field_list.find((old_item: any) => old_item.name == item.name)\n        if (find_field && find_field.source == item.source) {\n          return { ...item, value: JSON.parse(JSON.stringify(find_field.value)) }\n        }\n        return { ...item, value: item.source == 'reference' ? [] : '' }\n      })\n      set(props.nodeModel.properties.node_data, 'input_field_list', merge_input_field_list)\n      set(props.nodeModel.properties, 'status', ok.data.is_active ? 200 : 500)\n    })\n    .catch(() => {\n      set(props.nodeModel.properties, 'status', 500)\n    })\n}\n\nonMounted(() => {\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n  update_field()\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-node/index.ts",
    "content": "import ToolNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass ToolLibCustomNode extends AppNode {\n  constructor(props: any) {\n    super(props, ToolNodeVue)\n  }\n}\nexport default {\n  type: 'tool-node',\n  model: AppNodeModel,\n  view: ToolLibCustomNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-16\">{{ $t('workflow.nodeSetting') }}</h5>\n    <div class=\"flex-between\">\n      <h5 class=\"lighter mb-8\">{{ $t('common.param.inputParam') }}</h5>\n      <el-button link type=\"primary\" @click=\"openAddDialog()\">\n        <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon> {{ $t('common.add') }}\n      </el-button>\n    </div>\n    <el-form\n      @submit.prevent\n      ref=\"ToolNodeFormRef\"\n      :model=\"chat_data\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      hide-required-asterisk\n    >\n      <el-card shadow=\"never\" class=\"card-never mb-16\" style=\"--el-card-padding: 12px\">\n        <div v-if=\"chat_data.input_field_list?.length > 0\">\n          <template v-for=\"(item, index) in chat_data.input_field_list\" :key=\"index\">\n            <el-form-item\n              :label=\"item.name\"\n              :prop=\"'input_field_list.' + index + '.value'\"\n              :rules=\"{\n                required: item.is_required,\n                message:\n                  item.source === 'reference'\n                    ? $t('views.tool.form.param.selectPlaceholder')\n                    : $t('views.tool.form.param.inputPlaceholder'),\n                trigger: 'blur',\n              }\"\n            >\n              <template #label>\n                <div class=\"flex-between\">\n                  <div class=\"flex align-center\">\n                    <div class=\"mr-4\">\n                      <auto-tooltip :content=\"item.name\" style=\"max-width: 130px\">\n                        {{ item.name }}\n                      </auto-tooltip>\n                    </div>\n                    <el-tooltip\n                      v-if=\"item.desc\"\n                      effect=\"dark\"\n                      placement=\"right\"\n                      popper-class=\"max-w-200\"\n                    >\n                      <template #content>\n                        {{ item.desc }}\n                      </template>\n                      <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n                    </el-tooltip>\n\n                    <span class=\"color-danger\" v-if=\"item.is_required\">*</span>\n\n                    <el-tag size=\"small\" type=\"info\" class=\"info-tag ml-4\">{{ item.type }}</el-tag>\n                  </div>\n                  <div>\n                    <el-button text @click.stop=\"openAddDialog(item, index)\">\n                      <AppIcon iconName=\"app-edit\"></AppIcon>\n                    </el-button>\n                    <el-button text @click=\"deleteField(index)\" style=\"margin-left: 4px !important\">\n                      <AppIcon iconName=\"app-delete\"></AppIcon>\n                    </el-button>\n                  </div>\n                </div>\n              </template>\n              <NodeCascader\n                v-if=\"item.source === 'reference'\"\n                ref=\"nodeCascaderRef\"\n                :nodeModel=\"nodeModel\"\n                class=\"w-full\"\n                :placeholder=\"$t('views.tool.form.param.selectPlaceholder')\"\n                v-model=\"item.value\"\n                :width=\"100\"\n              />\n              <el-input\n                v-else\n                v-model=\"item.value\"\n                :placeholder=\"$t('views.tool.form.param.inputPlaceholder')\"\n              />\n            </el-form-item>\n          </template>\n        </div>\n\n        <el-text type=\"info\" v-else> {{ $t('common.noData') }} </el-text>\n      </el-card>\n\n      <h5 class=\"lighter mb-8\">\n        {{ $t('views.tool.form.param.code') }}\n      </h5>\n      <div class=\"mb-8\" v-if=\"showEditor\">\n        <CodemirrorEditor\n          :title=\"$t('views.tool.form.param.code')\"\n          v-model=\"chat_data.code\"\n          @wheel=\"wheel\"\n          style=\"height: 130px !important\"\n          @submitDialog=\"submitCodemirrorEditor\"\n        />\n      </div>\n\n      <el-form-item\n        :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n        @click.prevent\n        v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n      >\n        <template #label>\n          <div class=\"flex align-center\">\n            <div class=\"mr-4\">\n              <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n            </div>\n            <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n              <template #content>\n                {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n              </template>\n              <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n            </el-tooltip>\n          </div>\n        </template>\n        <el-switch size=\"small\" v-model=\"chat_data.is_result\" />\n      </el-form-item>\n    </el-form>\n    <FieldFormDialog ref=\"FieldFormDialogRef\" @refresh=\"refreshFieldList\" />\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { cloneDeep, set } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormInstance } from 'element-plus'\nimport { ref, computed, onMounted, inject } from 'vue'\nimport FieldFormDialog from '@/views/tool/component/FieldFormDialog.vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport { WorkflowMode } from '@/enums/application'\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\nconst props = defineProps<{ nodeModel: any }>()\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\nconst FieldFormDialogRef = ref()\nconst nodeCascaderRef = ref()\n\nconst form = {\n  code: '',\n  input_field_list: [],\n  is_result: false,\n}\n\nconst currentIndex = ref<any>(null)\nconst showEditor = ref(false)\n\nconst chat_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst ToolNodeFormRef = ref<FormInstance>()\n\nconst validate = () => {\n  return ToolNodeFormRef.value?.validate().catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nfunction submitCodemirrorEditor(val: string) {\n  set(props.nodeModel.properties.node_data, 'code', val)\n}\n\nfunction openAddDialog(data?: any, index?: any) {\n  if (typeof index !== 'undefined') {\n    currentIndex.value = index\n  }\n\n  FieldFormDialogRef.value.open(data)\n}\n\nfunction deleteField(index: any) {\n  const list: any = cloneDeep(props.nodeModel.properties.node_data.input_field_list)\n  list.splice(index, 1)\n  set(props.nodeModel.properties.node_data, 'input_field_list', list)\n}\n\nfunction refreshFieldList(data: any) {\n  const list = cloneDeep(props.nodeModel.properties.node_data.input_field_list)\n  const obj = {\n    ...data,\n    value: data.source === 'reference' ? [] : '',\n  }\n  if (currentIndex.value !== null) {\n    list.splice(currentIndex.value, 1, obj)\n  } else {\n    list.push(obj)\n  }\n  set(props.nodeModel.properties.node_data, 'input_field_list', list)\n  currentIndex.value = null\n}\n\nonMounted(() => {\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n  set(props.nodeModel, 'validate', validate)\n  setTimeout(() => {\n    showEditor.value = true\n  }, 100)\n})\n</script>\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-start-node/index.ts",
    "content": "import ToolBaseNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nimport { t } from '@/locales'\nclass ToolBaseNode extends AppNode {\n  constructor(props: any) {\n    super(props, ToolBaseNodeVue)\n  }\n  get_node_field_list() {\n    const result = []\n    result.push({\n      value: 'global',\n      label: t('workflow.variable.global'),\n      type: 'global',\n      children: this.props.model.properties?.config?.globalFields || [],\n    })\n    const tbn = this.props.graphModel.getNodeModelById('tool-base-node')\n    console.log(tbn)\n    const output = tbn.properties?.user_output_field_list?.map((i: any) => {\n      return { label: i.label || i.name, value: i.field }\n    })\n\n    result.push({\n      value: 'output',\n      label: '参数输出',\n      type: 'output',\n      children: output || [],\n    })\n    console.log(result)\n    return result\n  }\n}\nexport default {\n  type: 'tool-start-node',\n  model: AppNodeModel,\n  view: ToolBaseNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-start-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.variable.global') }}</h5>\n    <div\n      v-for=\"(item, index) in nodeModel.properties.config.globalFields\"\n      :key=\"index\"\n      class=\"flex-between border-r-6 p-8-12 mb-8 layout-bg lighter\"\n      @mouseenter=\"showicon = true\"\n      @mouseleave=\"showicon = false\"\n    >\n      <span class=\"break-all\">{{ item.label }} {{ '{' + item.value + '}' }}</span>\n      <el-tooltip effect=\"dark\" :content=\"$t('workflow.setting.copyParam')\" placement=\"top\">\n        <el-button link @click=\"copyClick(`{{global.${item.value}}}`)\" style=\"padding: 0\">\n          <AppIcon iconName=\"app-copy\"></AppIcon>\n        </el-button>\n      </el-tooltip>\n    </div>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { cloneDeep, set } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { copyClick } from '@/utils/clipboard'\nimport { ref, onMounted } from 'vue'\nconst showicon = ref(false)\nconst props = defineProps<{ nodeModel: any }>()\nconst getRefreshFieldList = () => {\n  const user_input_fields = props.nodeModel.graphModel.nodes\n    .filter((v: any) => v.id === 'tool-base-node')\n    .map((v: any) => cloneDeep(v.properties.user_input_field_list))\n    .reduce((x: any, y: any) => [...x, ...y], [])\n    .map((i: any) => {\n      return { label: i.label || i.name, value: i.field }\n    })\n  return [...user_input_fields]\n}\nconst refreshFieldList = () => {\n  const refreshFieldList = getRefreshFieldList()\n  set(props.nodeModel.properties.config, 'globalFields', refreshFieldList)\n}\nprops.nodeModel.graphModel.eventCenter.on('refreshFieldList', refreshFieldList)\nonMounted(() => {})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-workflow-lib-node/index.ts",
    "content": "import ToolWorkflowLibNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\nclass ToolWorkflowLibNode extends AppNode {\n  constructor(props: any) {\n    super(props, ToolWorkflowLibNodeVue)\n  }\n  getConfig(props: any) {\n    return props.model.properties.config\n  }\n}\nexport default {\n  type: 'tool-workflow-lib-node',\n  model: AppNodeModel,\n  view: ToolWorkflowLibNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/tool-workflow-lib-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <el-form\n      @submit.prevent\n      ref=\"ToolNodeFormRef\"\n      :model=\"chat_data\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      hide-required-asterisk\n    >\n      <h5 class=\"title-decoration-1 mb-16\">{{ chat_data.input_title }}</h5>\n\n      <el-card shadow=\"never\" class=\"card-never mb-16\" style=\"--el-card-padding: 12px\">\n        <div v-if=\"chat_data.input_field_list?.length\">\n          <template v-for=\"(item, index) in chat_data.input_field_list\" :key=\"item.field\">\n            <el-form-item\n              :label=\"item.label\"\n              :prop=\"'input_field_list.' + index + '.value'\"\n              :rules=\"{\n                required: item.is_required,\n                message:\n                  item.source === 'reference'\n                    ? $t('views.tool.form.param.selectPlaceholder')\n                    : $t('views.tool.form.param.inputPlaceholder'),\n                trigger: 'blur',\n              }\"\n            >\n              <template #label>\n                <div class=\"flex-between\">\n                  <div class=\"flex align-center\">\n                    <div class=\"mr-4\">\n                      <auto-tooltip :content=\"item.label\" style=\"max-width: 130px\">\n                        {{ item.label }}\n                      </auto-tooltip>\n                    </div>\n                    <span class=\"color-danger\" v-if=\"item.is_required\">*</span>\n                  </div>\n                  <el-select\n                    :teleported=\"false\"\n                    v-model=\"item.source\"\n                    @change=\"onSourceChange(item)\"\n                    size=\"small\"\n                    style=\"width: 85px\"\n                  >\n                    <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"reference\" />\n                    <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n                  </el-select>\n                </div>\n              </template>\n              <NodeCascader\n                v-if=\"item.source === 'reference'\"\n                ref=\"nodeCascaderRef\"\n                :nodeModel=\"nodeModel\"\n                class=\"w-full\"\n                :placeholder=\"$t('views.tool.form.param.selectPlaceholder')\"\n                v-model=\"item.value\"\n              />\n              <template v-else>\n                <el-input\n                  v-if=\"['string'].includes(item.type)\"\n                  v-model=\"item.value\"\n                  :placeholder=\"$t('views.tool.form.param.inputPlaceholder')\"\n                />\n                <JsonInput v-if=\"['array', 'dict'].includes(item.type)\" v-model=\"item.value\" />\n                <el-input-number v-if=\"['int', 'float'].includes(item.type)\" v-model=\"item.value\" />\n                <el-switch\n                  v-if=\"['boolean'].includes(item.type)\"\n                  v-model=\"item.value\"\n                  :active-value=\"true\"\n                  :inactive-value=\"false\"\n                />\n              </template>\n            </el-form-item>\n          </template>\n        </div>\n\n        <el-text type=\"info\" v-else> {{ $t('common.noData') }} </el-text>\n      </el-card>\n    </el-form>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { set } from 'lodash'\nimport { useRoute } from 'vue-router'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport JsonInput from '@/components/dynamics-form/items/JsonInput.vue'\nimport type { FormInstance } from 'element-plus'\nimport { ref, computed, onMounted, inject } from 'vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\n\nconst props = defineProps<{ nodeModel: any }>()\n\nconst route = useRoute()\n\nconst apiType = computed(() => {\n  if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst onSourceChange = (item: any) => {\n  if (item.type === 'boolean') {\n    item.value = false\n  } else if (['array', 'dict'].includes(item.type)) {\n    item.value = []\n  } else {\n    item.value = ''\n  }\n}\n\nconst form = {\n  input_field_list: [],\n  is_result: false,\n  source: 'custom',\n}\n\nconst chat_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst ToolNodeFormRef = ref<FormInstance>()\n\nconst validate = () => {\n  return ToolNodeFormRef.value?.validate().catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst update_field = () => {\n  if (!props.nodeModel.properties.node_data.tool_lib_id) {\n    set(props.nodeModel.properties, 'status', 500)\n    return\n  }\n  loadSharedApi({ type: 'tool', systemType: apiType.value })\n    .getToolById(props.nodeModel.properties.node_data.tool_lib_id)\n    .then((ok: any) => {\n      const workflowNodes = ok.data?.work_flow?.nodes || []\n      const baseNode = workflowNodes.find((n: any) => n.type === 'tool-base-node')\n\n      if (baseNode) {\n        const new_input_list = baseNode.properties.user_input_field_list || []\n        const new_output_list = baseNode.properties.user_output_field_list || []\n\n        const old_config_fields = props.nodeModel.properties.config?.fields || []\n        const config_field_list = new_output_list.map((item: any) => {\n          const old = old_config_fields.find((o: any) => o.value === item.field)\n          return old ? JSON.parse(JSON.stringify(old)) : { label: item.label, value: item.field }\n        })\n\n        const input_title = baseNode.properties.user_input_config?.title\n        const output_title = baseNode.properties.user_output_config?.title\n        const old_input_list = props.nodeModel.properties.node_data.input_field_list || []\n        const merged_input_list = new_input_list.map((item: any) => {\n          const find_field = old_input_list.find((old_item: any) => old_item.field === item.field)\n\n          if (find_field) {\n            return {\n              ...item,\n              source: find_field.source,\n              value: JSON.parse(JSON.stringify(find_field.value)),\n            }\n          }\n          return { ...item, source: 'custom', value: '' }\n        })\n\n        set(props.nodeModel.properties.node_data, 'input_field_list', merged_input_list)\n        set(props.nodeModel.properties, 'config', {\n          fields: config_field_list,\n          output_title: output_title,\n        })\n        set(props.nodeModel.properties.node_data, 'input_title', input_title)\n      }\n      set(props.nodeModel.properties, 'status', ok.data.is_active ? 200 : 500)\n      props.nodeModel.clear_next_node_field(true)\n    })\n    .catch(() => {\n      set(props.nodeModel.properties, 'status', 500)\n    })\n}\n\nonMounted(() => {\n  if (props.nodeModel.properties.config?.fields?.length) {\n    set(props.nodeModel.properties.config, 'fields', props.nodeModel.properties.config.fields)\n  }\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n  update_field()\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/variable-aggregation-node/component/GroupFieldDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"isEdit ? $t('workflow.nodes.variableAggregationNode.editGroup') : $t('workflow.nodes.variableAggregationNode.addGroup')\"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n    >\n      <el-form-item\n        :label=\"$t('common.variable')\"\n        prop=\"field\"\n      >\n        <el-input\n          v-model=\"form.field\"\n          :maxlength=\"64\"\n          :placeholder=\"$t('workflow.variable.inputPlaceholder')\"\n          show-word-limit\n        />\n      </el-form-item>\n      <el-form-item\n        :label=\"$t('dynamicsForm.paramForm.name.label')\"\n        prop=\"label\"\n      >\n        <el-input\n          v-model=\"form.label\"\n          :maxlength=\"64\"\n          show-word-limit\n          :placeholder=\"$t('dynamicsForm.paramForm.name.placeholder')\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"close\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ isEdit ? $t('common.save') : $t('common.add') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\nconst currentIndex = ref(null)\nconst form = ref<any>({\n  field: '',\n  label: '',\n})\n\nconst rules = reactive({\n  label: [\n    { required: true, message: t('dynamicsForm.paramForm.name.placeholder'), trigger: 'blur' },\n  ],\n  field: [\n    { required: true, message: t('workflow.variable.inputPlaceholder'), trigger: 'blur' },\n    {\n      pattern: /^[a-zA-Z0-9_]+$/,\n      message: t('dynamicsForm.paramForm.field.requiredMessage2'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (data: any, index?: any) => {\n  if (data) {\n    form.value = cloneDeep(data)\n    isEdit.value = true\n    currentIndex.value = index\n  }\n\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  dialogVisible.value = false\n  isEdit.value = false\n  currentIndex.value = null\n  form.value = {\n    field: '',\n    label: '',\n  }\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value, currentIndex.value)\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/variable-aggregation-node/index.ts",
    "content": "import VariableAggregationNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass VariableAggregationNode extends AppNode {\n  constructor(props: any) {\n    super(props, VariableAggregationNodeVue)\n  }\n  getConfig(props: any) {\n    return props.model.properties.config\n  }\n}\n\nexport default {\n  type: 'variable-aggregation-node',\n  model: AppNodeModel,\n  view: VariableAggregationNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/variable-aggregation-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-form\n      @submit.prevent\n      :model=\"form_data\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      label-width=\"auto\"\n      ref=\"VariableAggregationRef\"\n      hide-required-asterisk\n    >\n      <el-form-item\n        :label=\"$t('workflow.nodes.variableAggregationNode.Strategy')\"\n        :rules=\"{\n          required: true,\n          trigger: 'change',\n        }\"\n      >\n        <template #label>\n          <div class=\"flex-between\">\n            <div>\n              <span\n                >{{ $t('workflow.nodes.variableAggregationNode.Strategy') }}\n                <span class=\"color-danger\">*</span>\n              </span>\n            </div>\n          </div>\n        </template>\n        <el-select v-model=\"form_data.strategy\" :teleported=\"false\">\n          <el-option\n            :label=\"t('workflow.nodes.variableAggregationNode.placeholder')\"\n            value=\"first_non_null\"\n          />\n          <el-option\n            :label=\"t('workflow.nodes.variableAggregationNode.placeholder1')\"\n            value=\"variable_to_json\"\n          />\n        </el-select>\n      </el-form-item>\n      <div v-for=\"(group, gIndex) in form_data.group_list\" :key=\"group.id\" class=\"mb-8\">\n        <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n          <div class=\"flex-between mb-12\">\n            <span class=\"ellipsis\" :title=\"group.label\">{{ group.label }}</span>\n            <div class=\"flex align-center\" style=\"margin-right: -3px\">\n              <el-button @click=\"openAddOrEditDialog(group, gIndex)\" link>\n                <el-icon><EditPen /></el-icon>\n              </el-button>\n              <el-button\n                @click=\"deleteGroup(gIndex)\"\n                link\n                :disabled=\"form_data.group_list.length <= 1\"\n              >\n                <AppIcon iconName=\"app-delete\"></AppIcon>\n              </el-button>\n            </div>\n          </div>\n          <VueDraggable\n            ref=\"el\"\n            v-bind:modelValue=\"group.variable_list\"\n            :disabled=\"group.variable_list.length === 1\"\n            handle=\".handle\"\n            :animation=\"150\"\n            ghostClass=\"ghost\"\n            @end=\"onEnd($event, gIndex)\"\n          >\n            <div v-for=\"(item, vIndex) in group.variable_list\" :key=\"item.v_id\" class=\"drag-card\">\n              <el-row class=\"handle\">\n                <el-col :span=\"22\" class=\"flex\">\n                  <img src=\"@/assets/sort.svg\" alt=\"\" height=\"15\" class=\"mr-4 mt-8\" />\n                  <el-form-item\n                    :prop=\"`group_list.${gIndex}.variable_list.${vIndex}.variable`\"\n                    :rules=\"{\n                      type: 'array',\n                      required: true,\n                      message: $t('workflow.variable.placeholder'),\n                      trigger: 'change',\n                    }\"\n                  >\n                    <NodeCascader\n                      ref=\"nodeCascaderRef\"\n                      :nodeModel=\"nodeModel\"\n                      style=\"width: 200px\"\n                      :placeholder=\"$t('workflow.variable.placeholder')\"\n                      v-model=\"item.variable\"\n                    />\n                  </el-form-item>\n                </el-col>\n                <el-col :span=\"2\">\n                  <el-button\n                    link\n                    class=\"mt-4 ml-4\"\n                    :disabled=\"group.variable_list.length <= 1\"\n                    @click=\"deleteVariable(gIndex, vIndex)\"\n                  >\n                    <AppIcon iconName=\"app-delete\"></AppIcon>\n                  </el-button>\n                </el-col>\n              </el-row>\n            </div>\n          </VueDraggable>\n          <el-button @click=\"addVariable(gIndex)\" type=\"primary\" size=\"large\" link>\n            <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\" />\n            {{ $t('common.add') }}\n          </el-button>\n        </el-card>\n      </div>\n      <el-button @click=\"openAddOrEditDialog()\" type=\"primary\" size=\"large\" link>\n        <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\" />\n        {{ $t('workflow.nodes.variableAggregationNode.addGroup') }}\n      </el-button>\n    </el-form>\n    <GroupFieldDialog ref=\"GroupFieldDialogRef\" @refresh=\"refreshFieldList\"></GroupFieldDialog>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { set, cloneDeep } from 'lodash'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport GroupFieldDialog from './component/GroupFieldDialog.vue'\nimport { ref, computed, onMounted } from 'vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport { t } from '@/locales'\nimport { randomId } from '@/utils/common'\nimport { MsgError } from '@/utils/message'\nimport { VueDraggable } from 'vue-draggable-plus'\n\nconst props = defineProps<{ nodeModel: any }>()\nconst VariableAggregationRef = ref()\nconst nodeCascaderRef = ref()\nconst GroupFieldDialogRef = ref()\n\nconst form = {\n  strategy: 'first_non_null',\n  group_list: [\n    {\n      id: randomId(),\n      label: 'Group1',\n      field: 'Group1',\n      variable_list: [\n        {\n          v_id: randomId(),\n          variable: [],\n        },\n      ],\n    },\n  ],\n}\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst inputFieldList = ref<any[]>([])\n\nfunction openAddOrEditDialog(group?: any, index?: any) {\n  let data = null\n  if (group && index !== undefined) {\n    data = {\n      field: group.field,\n      label: group.label,\n    }\n  }\n  GroupFieldDialogRef.value.open(data, index)\n}\n\nfunction refreshFieldList(data: any, index: any) {\n  for (let i = 0; i < inputFieldList.value.length; i++) {\n    if (inputFieldList.value[i].field === data.field && index !== i) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.field)\n      return\n    }\n  }\n  if ([undefined, null].includes(index)) {\n    inputFieldList.value.push(data)\n    addGroup(data)\n  } else {\n    inputFieldList.value.splice(index, 1, data)\n    editGroupDesc(data, index)\n  }\n  GroupFieldDialogRef.value.close()\n  const fields = [...inputFieldList.value.map((item) => ({ label: item.label, value: item.field }))]\n  set(props.nodeModel.properties.config, 'fields', fields)\n}\n\nconst editGroupDesc = (data: any, gIndex: any) => {\n  const c_group_list = cloneDeep(form_data.value.group_list)\n  c_group_list[gIndex].field = data.field\n  c_group_list[gIndex].label = data.label\n  form_data.value.group_list = c_group_list\n}\n\nconst deleteGroup = (gIndex: number) => {\n  const c_group_list = cloneDeep(form_data.value.group_list)\n  c_group_list.splice(gIndex, 1)\n  form_data.value.group_list = c_group_list\n  inputFieldList.value.splice(gIndex, 1)\n  const fields = c_group_list.map((item: any) => ({ label: item.label, value: item.field }))\n  set(props.nodeModel.properties.config, 'fields', fields)\n}\n\nconst addVariable = (gIndex: number) => {\n  const c_group_list = cloneDeep(form_data.value.group_list)\n  c_group_list[gIndex].variable_list.push({\n    v_id: randomId(),\n    variable: [],\n  })\n  form_data.value.group_list = c_group_list\n}\n\nconst deleteVariable = (gIndex: number, vIndex: number) => {\n  const c_group_list = cloneDeep(form_data.value.group_list)\n  c_group_list[gIndex].variable_list.splice(vIndex, 1)\n  form_data.value.group_list = c_group_list\n}\n\nconst addGroup = (data: any) => {\n  const c_group_list = cloneDeep(form_data.value.group_list)\n  c_group_list.push({\n    id: randomId(),\n    field: data.field,\n    label: data.label,\n    variable_list: [\n      {\n        v_id: randomId(),\n        variable: [],\n      },\n    ],\n  })\n  form_data.value.group_list = c_group_list\n}\n\nconst validate = async () => {\n  const validate_list = [\n    ...nodeCascaderRef.value.map((item: any) => item.validate()),\n    VariableAggregationRef.value?.validate(),\n  ]\n  return Promise.all(validate_list).catch((err) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nfunction onEnd(event: any, gIndex: number) {\n  const { oldIndex, newIndex } = event\n  if (oldIndex === undefined || newIndex === undefined) return\n  const list = cloneDeep(props.nodeModel.properties.node_data.group_list[gIndex].variable_list)\n  const newInstance = { ...list[oldIndex] }\n  const oldInstance = { ...list[newIndex] }\n  list[newIndex] = newInstance\n  list[oldIndex] = oldInstance\n  set(props.nodeModel.properties.node_data.group_list[gIndex], 'variable_list', list)\n}\n\nonMounted(() => {\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n  set(props.nodeModel, 'validate', validate)\n  if (props.nodeModel.properties.node_data.group_list) {\n    inputFieldList.value = form_data.value.group_list.map((item: any) => ({\n      label: item.label,\n      field: item.field,\n    }))\n  }\n  const fields = form_data.value.group_list.map((item: any) => ({\n    label: item.label,\n    value: item.field,\n  }))\n  set(props.nodeModel.properties.config, 'fields', fields)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/variable-assign-node/index.ts",
    "content": "import VariableAssignNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass VariableAssignNode extends AppNode {\n  constructor(props: any) {\n    super(props, VariableAssignNodeVue)\n  }\n}\n\nexport default {\n  type: 'variable-assign-node',\n  model: AppNodeModel,\n  view: VariableAssignNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/variable-assign-node/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <el-form\n      @submit.prevent\n      :model=\"form_data\"\n      label-position=\"top\"\n      require-asterisk-position=\"right\"\n      label-width=\"auto\"\n      ref=\"replyNodeFormRef\"\n      hide-required-asterisk\n    >\n      <template v-for=\"(item, index) in form_data.variable_list\" :key=\"item.id\">\n        <el-card shadow=\"never\" class=\"card-never mb-8\" style=\"--el-card-padding: 12px\">\n          <el-form-item>\n            <template #label>\n              <div class=\"flex-between\">\n                <div>\n                  {{ $t('common.variable') }}\n                  <span class=\"color-danger\">*</span>\n                </div>\n                <el-button\n                  text\n                  @click=\"deleteVariable(index)\"\n                  v-if=\"form_data.variable_list.length > 1\"\n                >\n                  <AppIcon iconName=\"app-delete\"></AppIcon>\n                </el-button>\n              </div>\n            </template>\n            <NodeCascader\n              ref=\"nodeCascaderRef\"\n              :nodeModel=\"nodeModel\"\n              class=\"w-full\"\n              :placeholder=\"$t('workflow.variable.placeholder')\"\n              v-model=\"item.fields\"\n              :global=\"true\"\n              @change=\"variableChange(item)\"\n            />\n          </el-form-item>\n          <div class=\"flex-between mb-8\">\n            <span class=\"lighter\"\n              >{{ $t('workflow.nodes.variableAssignNode.assign')\n              }}<span class=\"color-danger\">*</span></span\n            >\n            <el-select :teleported=\"false\" v-model=\"item.source\" size=\"small\" style=\"width: 85px\">\n              <el-option :label=\"$t('workflow.variable.Referencing')\" value=\"referencing\" />\n              <el-option :label=\"$t('common.custom')\" value=\"custom\" />\n            </el-select>\n          </div>\n\n          <div v-if=\"item.source === 'custom'\" class=\"flex w-full\">\n            <el-select\n              v-model=\"item.type\"\n              style=\"max-width: 85px\"\n              class=\"mr-8\"\n              @change=\"\n                (val: string) => {\n                  if (val === 'bool') {\n                    form_data.variable_list[index].value = true\n                  } else {\n                    form_data.variable_list[index].value = null\n                  }\n                }\n              \"\n            >\n              <el-option v-for=\"item in typeOptions\" :key=\"item\" :label=\"item\" :value=\"item\" />\n            </el-select>\n\n            <el-form-item\n              v-if=\"item.type === 'string'\"\n              :prop=\"'variable_list.' + index + '.value'\"\n              :rules=\"{\n                message: t('common.inputPlaceholder'),\n                trigger: 'blur',\n                required: true,\n              }\"\n            >\n              <el-input\n                v-model=\"item.value\"\n                :placeholder=\"$t('common.inputPlaceholder')\"\n                show-word-limit\n                clearable\n                @wheel=\"wheel\"\n              ></el-input>\n            </el-form-item>\n            <el-form-item\n              v-else-if=\"item.type === 'num'\"\n              :prop=\"'variable_list.' + index + '.value'\"\n              :rules=\"{\n                message: $t('common.inputPlaceholder'),\n                trigger: 'blur',\n                required: true,\n              }\"\n            >\n              <el-input-number v-model=\"item.value\"></el-input-number>\n            </el-form-item>\n            <el-form-item\n              class=\"w-full\"\n              v-else-if=\"item.type === 'json'\"\n              :prop=\"'variable_list.' + index + '.value'\"\n              :rules=\"[\n                {\n                  message: $t('common.inputPlaceholder'),\n                  trigger: 'blur',\n                  required: true,\n                },\n                {\n                  validator: (rule: any, value: any, callback: any) => {\n                    try {\n                      JSON.parse(value)\n                      callback() // Valid JSON\n                    } catch (e) {\n                      callback(new Error('Invalid JSON format'))\n                    }\n                  },\n                  trigger: 'blur',\n                },\n              ]\"\n            >\n              <CodemirrorEditor\n                title=\"JSON\"\n                v-model=\"item.value\"\n                :style=\"{\n                  height: '100px',\n                  width: '155px',\n                }\"\n                @submitDialog=\"(val: string) => (form_data.variable_list[index].value = val)\"\n              />\n            </el-form-item>\n            <el-form-item\n              v-else-if=\"item.type === 'bool'\"\n              :prop=\"'variable_list.' + index + '.value'\"\n              :rules=\"{\n                message: $t('common.inputPlaceholder'),\n                trigger: 'blur',\n                required: true,\n              }\"\n            >\n              <el-select v-model=\"item.value\" style=\"width: 155px\" :teleported=\"false\">\n                <el-option label=\"true\" :value=\"true\" />\n                <el-option label=\"false\" :value=\"false\" />\n              </el-select>\n            </el-form-item>\n          </div>\n          <el-form-item v-else>\n            <NodeCascader\n              ref=\"nodeCascaderRef2\"\n              :nodeModel=\"nodeModel\"\n              class=\"w-full\"\n              :placeholder=\"$t('workflow.variable.placeholder')\"\n              v-model=\"item.reference\"\n            />\n          </el-form-item>\n        </el-card>\n      </template>\n\n      <el-button link type=\"primary\" @click=\"addVariable\">\n        <AppIcon iconName=\"app-add-outlined\" class=\"mr-4\"></AppIcon>\n        {{ $t('common.add') }}\n      </el-button>\n    </el-form>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { cloneDeep, set } from 'lodash'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport { computed, nextTick, onMounted, ref, inject } from 'vue'\nimport { isLastNode } from '@/workflow/common/data'\nimport { randomId } from '@/utils/common'\nimport { t } from '@/locales'\nimport { WorkflowMode } from '@/enums/application'\nconst workflowMode = inject('workflowMode') as WorkflowMode\nconst props = defineProps<{ nodeModel: any }>()\n\nconst typeOptions = ['string', 'num', 'json', 'bool']\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\nconst form = {\n  variable_list: [\n    {\n      id: randomId(),\n      fields: [],\n      value: null,\n      reference: [],\n      type: 'string',\n      source: 'custom',\n      name: '',\n    },\n  ],\n}\nconst boolValue = ref(1)\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'content', val)\n}\n\nconst replyNodeFormRef = ref()\nconst nodeCascaderRef = ref()\nconst nodeCascaderRef2 = ref()\n\nconst validate = async () => {\n  // console.log(replyNodeFormRef.value.validate())\n  let ps = [\n    replyNodeFormRef.value?.validate(),\n    ...nodeCascaderRef.value.map((item: any) => item.validate()),\n  ]\n  if (nodeCascaderRef2.value) {\n    ps = [...ps, ...nodeCascaderRef.value.map((item: any) => item.validate())]\n  }\n  return Promise.all(ps).catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nfunction addVariable() {\n  const list = cloneDeep(props.nodeModel.properties.node_data.variable_list)\n  const obj = {\n    id: randomId(),\n    fields: [],\n    value: null,\n    reference: [],\n    type: 'string',\n    source: 'custom',\n    name: '',\n  }\n  list.push(obj)\n  set(props.nodeModel.properties.node_data, 'variable_list', list)\n}\n\nfunction deleteVariable(index: number) {\n  const list = cloneDeep(props.nodeModel.properties.node_data.variable_list)\n  list.splice(index, 1)\n  set(props.nodeModel.properties.node_data, 'variable_list', list)\n}\n\nfunction variableChange(item: any) {\n  ;(workflowMode == WorkflowMode.ApplicationLoop\n    ? [...props.nodeModel.graphModel.nodes, ...props.nodeModel.graphModel.get_parent_nodes()]\n    : props.nodeModel.graphModel.nodes\n  ).map((node: any) => {\n    if (node.id === 'start-node') {\n      node.properties.config.globalFields.forEach((field: any) => {\n        if (field.value === item.fields[1]) {\n          item.name = field.label\n        }\n      })\n      node.properties.config.chatFields.forEach((field: any) => {\n        if (field.value === item.fields[1]) {\n          item.name = field.label\n        }\n      })\n    }\n    if (node.id === 'loop-start-node') {\n      node.properties.loop_input_field_list.forEach((field: any) => {\n        if (field.field === item.fields[1]) {\n          item.name = field.label\n        }\n      })\n    }\n  })\n}\n\nonMounted(() => {\n  if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {\n    if (isLastNode(props.nodeModel)) {\n      set(props.nodeModel.properties.node_data, 'is_result', true)\n    }\n  }\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/variable-splitting/component/VariableFieldDialog.vue",
    "content": "<template>\n  <el-dialog\n    :title=\"\n      isEdit\n        ? $t('workflow.nodes.variableSplittingNode.editVariables')\n        : $t('workflow.nodes.variableSplittingNode.addVariables')\n    \"\n    v-model=\"dialogVisible\"\n    :close-on-click-modal=\"false\"\n    :close-on-press-escape=\"false\"\n    :destroy-on-close=\"true\"\n    :before-close=\"close\"\n    append-to-body\n  >\n    <el-form\n      label-position=\"top\"\n      ref=\"fieldFormRef\"\n      :rules=\"rules\"\n      :model=\"form\"\n      require-asterisk-position=\"right\"\n      hide-required-asterisk\n    >\n      <el-form-item prop=\"field\">\n        <template #label>\n          <div class=\"flex align-center\">\n            <span class=\"mr-4\">{{ $t('common.variable') }}</span>\n            <span class=\"color-danger\">*</span>\n          </div>\n        </template>\n        <el-input\n          v-model=\"form.field\"\n          :maxlength=\"64\"\n          :placeholder=\"$t('workflow.variable.inputPlaceholder')\"\n          show-word-limit\n        />\n      </el-form-item>\n      <el-form-item prop=\"label\">\n        <template #label>\n          <div class=\"flex align-center\">\n            <span class=\"mr-4\">{{ $t('dynamicsForm.paramForm.name.label') }}</span>\n            <span class=\"color-danger\">*</span>\n          </div>\n        </template>\n        <el-input\n          v-model=\"form.label\"\n          :maxlength=\"64\"\n          show-word-limit\n          :placeholder=\"$t('dynamicsForm.paramForm.name.placeholder')\"\n        />\n      </el-form-item>\n      <el-form-item prop=\"expression\">\n        <template #label>\n          <div class=\"flex align-center\">\n            <span class=\"mr-4\"\n              >{{ $t('workflow.nodes.variableSplittingNode.expression.label') }}\n              <span class=\"color-danger\">*</span></span\n            >\n            <el-tooltip\n              effect=\"dark\"\n              :content=\"\n                $t('workflow.nodes.variableSplittingNode.expression.tooltip')\n              \"\n              placement=\"right\"\n            >\n              <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n            </el-tooltip>\n          </div>\n        </template>\n        <el-input\n          v-model=\"form.expression\"\n          :maxlength=\"64\"\n          show-word-limit\n          :placeholder=\"\n            $t('workflow.nodes.variableSplittingNode.expression.placeholder')\n          \"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <span class=\"dialog-footer\">\n        <el-button @click.prevent=\"close\"> {{ $t('common.cancel') }} </el-button>\n        <el-button type=\"primary\" @click=\"submit(fieldFormRef)\" :loading=\"loading\">\n          {{ $t('common.save') }}\n        </el-button>\n      </span>\n    </template>\n  </el-dialog>\n</template>\n<script setup lang=\"ts\">\nimport { reactive, ref } from 'vue'\nimport type { FormInstance } from 'element-plus'\nimport { cloneDeep } from 'lodash'\nimport { t } from '@/locales'\nconst emit = defineEmits(['refresh'])\n\nconst fieldFormRef = ref()\nconst loading = ref<boolean>(false)\nconst isEdit = ref(false)\nconst currentIndex = ref(null)\nconst form = ref<any>({\n  field: '',\n  label: '',\n  expression: '',\n})\n\nconst rules = reactive({\n  label: [\n    { required: true, message: t('dynamicsForm.paramForm.name.placeholder'), trigger: 'blur' },\n  ],\n  field: [\n    {\n      required: true,\n      message: t('workflow.variable.inputPlaceholder'),\n      trigger: 'blur',\n    },\n    {\n      pattern: /^[a-zA-Z0-9_]+$/,\n      message: t('dynamicsForm.paramForm.field.requiredMessage2'),\n      trigger: 'blur',\n    },\n  ],\n  expression: [\n    {\n      required: true,\n      message: t('workflow.nodes.variableSplittingNode.expression.placeholder'),\n      trigger: 'blur',\n    },\n  ],\n})\n\nconst dialogVisible = ref<boolean>(false)\n\nconst open = (row: any, index?: any) => {\n  if (row) {\n    form.value = cloneDeep(row)\n    isEdit.value = true\n    currentIndex.value = index\n  }\n\n  dialogVisible.value = true\n}\n\nconst close = () => {\n  dialogVisible.value = false\n  isEdit.value = false\n  currentIndex.value = null\n  form.value = {\n    field: '',\n    label: '',\n  }\n}\n\nconst submit = async (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  await formEl.validate((valid) => {\n    if (valid) {\n      emit('refresh', form.value, currentIndex.value)\n    }\n  })\n}\n\ndefineExpose({ open, close })\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/variable-splitting/component/VariableFieldTable.vue",
    "content": "<template>\n  <div class=\"flex-between w-full\">\n    <h5 class=\"break-all lighter\">\n      {{ $t('workflow.nodes.variableSplittingNode.splitVariables') }}\n      <span class=\"color-danger\">*</span>\n    </h5>\n    <span class=\"ml-4\" style=\"margin-top: -4px\">\n      <el-button link type=\"primary\" @click=\"openAddDialog()\">\n        <AppIcon iconName=\"app-add-outlined\"></AppIcon>\n      </el-button>\n    </span>\n  </div>\n  <el-table\n    v-if=\"props.nodeModel.properties.node_data.variable_list?.length > 0\"\n    :data=\"props.nodeModel.properties.node_data.variable_list\"\n    ref=\"tableRef\"\n    row-key=\"field\"\n    class=\"border-l border-r\"\n  >\n    <el-table-column prop=\"field\" :label=\"$t('common.variable')\" width=\"95\">\n      <template #default=\"{ row }\">\n        <span :title=\"row.field\" class=\"ellipsis-1\">{{ row.field }}</span>\n      </template>\n    </el-table-column>\n\n    <el-table-column prop=\"label\" :label=\"$t('dynamicsForm.paramForm.name.label')\">\n      <template #default=\"{ row }\">\n        <span :title=\"row.label\" class=\"ellipsis-1\">\n          {{ row.label }}\n        </span>\n      </template>\n    </el-table-column>\n    <el-table-column :label=\"$t('common.operation')\" align=\"left\" width=\"90\">\n      <template #default=\"{ row, $index }\">\n        <span class=\"mr-4\">\n          <el-tooltip effect=\"dark\" :content=\"$t('common.modify')\" placement=\"top\">\n            <el-button type=\"primary\" text @click.stop=\"openAddDialog(row, $index)\">\n              <AppIcon iconName=\"app-edit\"></AppIcon>\n            </el-button>\n          </el-tooltip>\n        </span>\n        <el-tooltip effect=\"dark\" :content=\"$t('common.delete')\" placement=\"top\">\n          <el-button type=\"primary\" text @click=\"deleteField($index)\">\n            <AppIcon iconName=\"app-delete\"></AppIcon>\n          </el-button>\n        </el-tooltip>\n      </template>\n    </el-table-column>\n  </el-table>\n  <VariableFieldDialog\n    ref=\"VariableFieldDialogRef\"\n    @refresh=\"refreshFieldList\"\n  ></VariableFieldDialog>\n</template>\n\n<script setup lang=\"ts\">\nimport { onMounted, ref } from 'vue'\nimport { set, cloneDeep } from 'lodash'\nimport VariableFieldDialog from './VariableFieldDialog.vue'\nimport { MsgError } from '@/utils/message'\nimport { t } from '@/locales'\nconst props = defineProps<{ nodeModel: any }>()\n\nconst tableRef = ref()\nconst VariableFieldDialogRef = ref()\n\nconst inputFieldList = ref<any[]>([])\n\nfunction openAddDialog(data?: any, index?: any) {\n  VariableFieldDialogRef.value.open(data, index)\n}\n\nfunction deleteField(index: any) {\n  inputFieldList.value.splice(index, 1)\n  const fields = [\n    {\n      label: t('common.result'),\n      value: 'result',\n    },\n    ...inputFieldList.value.map((item) => ({ label: item.label, value: item.field })),\n  ]\n  set(props.nodeModel.properties.config, 'fields', fields)\n  props.nodeModel.clear_next_node_field(false)\n}\n\nfunction refreshFieldList(data: any, index: any) {\n  for (let i = 0; i < inputFieldList.value.length; i++) {\n    if (inputFieldList.value[i].field === data.field && index !== i) {\n      MsgError(t('workflow.tip.paramErrorMessage') + data.field)\n      return\n    }\n  }\n  if ([undefined, null].includes(index)) {\n    inputFieldList.value.push(data)\n  } else {\n    inputFieldList.value.splice(index, 1, data)\n  }\n  VariableFieldDialogRef.value.close()\n  const fields = [\n    {\n      label: t('common.result'),\n      value: 'result',\n    },\n    ...inputFieldList.value.map((item) => ({ label: item.label, value: item.field })),\n  ]\n  set(props.nodeModel.properties.config, 'fields', fields)\n  props.nodeModel.clear_next_node_field(false)\n}\n\nonMounted(() => {\n  if (props.nodeModel.properties.node_data.variable_list) {\n    inputFieldList.value = cloneDeep(props.nodeModel.properties.node_data.variable_list)\n  }\n  set(props.nodeModel.properties.node_data, 'variable_list', inputFieldList)\n  const fields = [\n    {\n      label: t('common.result'),\n      value: 'result',\n    },\n    ...inputFieldList.value.map((item) => ({ label: item.label, value: item.field })),\n  ]\n  set(props.nodeModel.properties.config, 'fields', fields)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/variable-splitting/index.ts",
    "content": "import VariableSplittingNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass VariableSplittingNode extends AppNode {\n  constructor(props: any) {\n    super(props, VariableSplittingNodeVue)\n  }\n  getConfig(props: any) {\n    return props.model.properties.config\n  }\n}\n\nexport default {\n  type: 'variable-splitting-node',\n  model: AppNodeModel,\n  view: VariableSplittingNode,\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/variable-splitting/index.vue",
    "content": "<template>\n  <NodeContainer :nodeModel=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\" style=\"--el-card-padding: 12px\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"VariableSplittingRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          prop=\"input_variable\"\n          :rules=\"{\n            message: $t('workflow.variable.placeholder'),\n            trigger: 'blur',\n            required: true,\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <div>\n                {{ $t('workflow.nodes.variableSplittingNode.inputVariables') }}\n                <span class=\"color-danger\">*</span>\n              </div>\n            </div>\n          </template>\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.variable.placeholder')\"\n            v-model=\"form_data.input_variable\"\n          />\n        </el-form-item>\n\n        <el-form-item\n          prop=\"variable_list\"\n          :rules=\"{\n            message: $t(\n              'workflow.nodes.variableSplittingNode.variableListPlaceholder',\n            ),\n            trigger: 'blur',\n            required: true,\n          }\"\n        >\n          <VariableFieldTable\n            ref=\"VariableFieldTableRef\"\n            :node-model=\"nodeModel\"\n          ></VariableFieldTable>\n        </el-form-item>\n      </el-form>\n    </el-card>\n  </NodeContainer>\n</template>\n<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport VariableFieldTable from '@/workflow/nodes/variable-splitting/component/VariableFieldTable.vue'\nimport { set } from 'lodash'\nconst props = defineProps<{ nodeModel: any }>()\n\nconst form = {\n  input_variable: [],\n  variable_list: [],\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\nconst VariableSplittingRef = ref()\nconst validate = async () => {\n  return VariableSplittingRef.value.validate().catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\nonMounted(() => {\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "ui/src/workflow/nodes/video-understand/index.ts",
    "content": "import VideoUnderstandNodeVue from './index.vue'\nimport { AppNode, AppNodeModel } from '@/workflow/common/app-node'\n\nclass VideoUnderstandNode extends AppNode {\n  constructor(props: any) {\n    super(props, VideoUnderstandNodeVue)\n  }\n}\n\nexport default {\n  type: 'video-understand-node',\n  model: AppNodeModel,\n  view: VideoUnderstandNode\n}\n"
  },
  {
    "path": "ui/src/workflow/nodes/video-understand/index.vue",
    "content": "<template>\n  <NodeContainer :node-model=\"nodeModel\">\n    <h5 class=\"title-decoration-1 mb-8\">{{ $t('workflow.nodeSetting') }}</h5>\n    <el-card shadow=\"never\" class=\"card-never\">\n      <el-form\n        @submit.prevent\n        :model=\"form_data\"\n        label-position=\"top\"\n        require-asterisk-position=\"right\"\n        label-width=\"auto\"\n        ref=\"aiChatNodeFormRef\"\n        hide-required-asterisk\n      >\n        <el-form-item\n          :label=\"$t('workflow.nodes.videoUnderstandNode.model.label')\"\n          prop=\"model_id\"\n          :rules=\"{\n            required: true,\n            message: $t('workflow.nodes.videoUnderstandNode.model.requiredMessage'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex-between w-full\">\n              <div>\n                <span\n                  >{{ t('workflow.nodes.videoUnderstandNode.model.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-button\n                :disabled=\"!form_data.model_id\"\n                type=\"primary\"\n                link\n                @click=\"openAIParamSettingDialog(form_data.model_id)\"\n                @refreshForm=\"refreshParam\"\n              >\n                <AppIcon iconName=\"app-setting\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n\n          <ModelSelect\n            @wheel=\"wheel\"\n            :teleported=\"false\"\n            v-model=\"form_data.model_id\"\n            :placeholder=\"$t('workflow.nodes.videoUnderstandNode.model.requiredMessage')\"\n            :options=\"modelOptions\"\n            showFooter\n            :model-type=\"'IMAGE'\"\n          ></ModelSelect>\n        </el-form-item>\n\n        <el-form-item>\n          <template #label>\n            <div class=\"flex-between\">\n              <div class=\"flex align-center\">\n                <span>{{ $t('views.application.form.roleSettings.label') }}</span>\n                <el-tooltip\n                  effect=\"dark\"\n                  :content=\"$t('views.application.form.roleSettings.tooltip')\"\n                  placement=\"right\"\n                >\n                  <AppIcon iconName=\"app-warning\" class=\"app-warning-icon ml-4\"></AppIcon>\n                </el-tooltip>\n              </div>\n              <el-button\n                type=\"primary\"\n                link\n                @click=\"openGeneratePromptDialog(form_data.model_id)\"\n                :disabled=\"!form_data.model_id\"\n              >\n                <AppIcon iconName=\"app-generate-star\"></AppIcon>\n              </el-button>\n            </div>\n          </template>\n          <MdEditorMagnify\n            :title=\"$t('views.application.form.roleSettings.label')\"\n            v-model=\"form_data.system\"\n            style=\"height: 100px\"\n            @submitDialog=\"submitSystemDialog\"\n            :placeholder=\"`${t('workflow.SystemPromptPlaceholder')}{{${t('workflow.nodes.startNode.label')}.question}}`\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('views.application.form.prompt.label')\"\n          prop=\"prompt\"\n          :rules=\"{\n            required: true,\n            message: $t('views.application.form.prompt.requiredMessage'),\n            trigger: 'blur',\n          }\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span\n                  >{{ $t('views.application.form.prompt.label')\n                  }}<span class=\"color-danger\">*</span></span\n                >\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>{{ $t('views.application.form.prompt.tooltip') }}</template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <MdEditorMagnify\n            @wheel=\"wheel\"\n            :title=\"$t('views.application.form.prompt.label')\"\n            v-model=\"form_data.prompt\"\n            style=\"height: 150px\"\n            @submitDialog=\"submitDialog\"\n            :placeholder=\"`${t('workflow.UserPromptPlaceholder')}{{${t('workflow.nodes.startNode.label')}.question}}`\"\n          />\n        </el-form-item>\n        <el-form-item\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex-between\">\n              <div>{{ $t('views.application.form.historyRecord.label') }}</div>\n              <el-select\n                v-model=\"form_data.dialogue_type\"\n                type=\"small\"\n                style=\"width: 100px\"\n                :teleported=\"false\"\n              >\n                <el-option :label=\"$t('workflow.node')\" value=\"NODE\" />\n                <el-option :label=\"$t('workflow.workflow')\" value=\"WORKFLOW\" />\n              </el-select>\n            </div>\n          </template>\n          <el-input-number\n            v-model=\"form_data.dialogue_number\"\n            :min=\"0\"\n            :value-on-clear=\"0\"\n            controls-position=\"right\"\n            class=\"w-full\"\n            :step=\"1\"\n            :step-strictly=\"true\"\n          />\n        </el-form-item>\n        <el-form-item\n          :rules=\"{\n            type: 'array',\n            required: true,\n            message: $t('workflow.nodes.videoUnderstandNode.video.requiredMessage'),\n            trigger: 'change',\n          }\"\n        >\n          <template #label\n            >{{ $t('workflow.nodes.videoUnderstandNode.video.label')\n            }}<span class=\"color-danger\">*</span></template\n          >\n          <NodeCascader\n            ref=\"nodeCascaderRef\"\n            :nodeModel=\"nodeModel\"\n            class=\"w-full\"\n            :placeholder=\"$t('workflow.nodes.videoUnderstandNode.video.requiredMessage')\"\n            v-model=\"form_data.video_list\"\n          />\n        </el-form-item>\n        <el-form-item\n          :label=\"$t('workflow.nodes.aiChatNode.returnContent.label')\"\n          @click.prevent\n          v-if=\"[WorkflowMode.Application, WorkflowMode.ApplicationLoop].includes(workflowMode)\"\n        >\n          <template #label>\n            <div class=\"flex align-center\">\n              <div class=\"mr-4\">\n                <span>{{ $t('workflow.nodes.aiChatNode.returnContent.label') }}</span>\n              </div>\n              <el-tooltip effect=\"dark\" placement=\"right\" popper-class=\"max-w-200\">\n                <template #content>\n                  {{ $t('workflow.nodes.aiChatNode.returnContent.tooltip') }}\n                </template>\n                <AppIcon iconName=\"app-warning\" class=\"app-warning-icon\"></AppIcon>\n              </el-tooltip>\n            </div>\n          </template>\n          <el-switch size=\"small\" v-model=\"form_data.is_result\" />\n        </el-form-item>\n      </el-form>\n    </el-card>\n    <AIModeParamSettingDialog ref=\"AIModeParamSettingDialogRef\" @refresh=\"refreshParam\" />\n    <GeneratePromptDialog @replace=\"replace\" ref=\"GeneratePromptDialogRef\" />\n  </NodeContainer>\n</template>\n\n<script setup lang=\"ts\">\nimport NodeContainer from '@/workflow/common/NodeContainer.vue'\nimport { computed, onMounted, ref, inject } from 'vue'\nimport { groupBy, set } from 'lodash'\nimport NodeCascader from '@/workflow/common/NodeCascader.vue'\nimport type { FormInstance } from 'element-plus'\nimport AIModeParamSettingDialog from '@/views/application/component/AIModeParamSettingDialog.vue'\nimport { t } from '@/locales'\nimport { useRoute } from 'vue-router'\nimport { loadSharedApi } from '@/utils/dynamics-api/shared-api'\nimport GeneratePromptDialog from '@/views/application/component/GeneratePromptDialog.vue'\nimport { WorkflowMode } from '@/enums/application'\nconst workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application\nconst getResourceDetail = inject('getResourceDetail') as any\nconst route = useRoute()\n\nconst {\n  params: { id },\n} = route as any\n\nconst apiType = computed(() => {\n  if (route.path.includes('resource-management')) {\n    return 'systemManage'\n  } else if (route.path.includes('shared')) {\n    return 'systemShare'\n  } else {\n    return 'workspace'\n  }\n})\n\nconst props = defineProps<{ nodeModel: any }>()\nconst modelOptions = ref<any>(null)\nconst AIModeParamSettingDialogRef = ref<InstanceType<typeof AIModeParamSettingDialog>>()\n\nconst aiChatNodeFormRef = ref<FormInstance>()\nconst nodeCascaderRef = ref()\nconst validate = () => {\n  return Promise.all([\n    nodeCascaderRef.value ? nodeCascaderRef.value.validate() : Promise.resolve(''),\n    aiChatNodeFormRef.value?.validate(),\n  ]).catch((err: any) => {\n    return Promise.reject({ node: props.nodeModel, errMessage: err })\n  })\n}\n\nconst wheel = (e: any) => {\n  if (e.ctrlKey === true) {\n    e.preventDefault()\n    return true\n  } else {\n    e.stopPropagation()\n    return true\n  }\n}\n\nconst defaultPrompt = `{{${t('workflow.nodes.startNode.label')}.question}}`\n\nconst form = {\n  model_id: '',\n  system: '',\n  prompt: defaultPrompt,\n  dialogue_number: 0,\n  dialogue_type: 'NODE',\n  is_result: true,\n  temperature: null,\n  max_tokens: null,\n  video_list: ['start-node', 'video'],\n}\n\nconst form_data = computed({\n  get: () => {\n    if (props.nodeModel.properties.node_data) {\n      return props.nodeModel.properties.node_data\n    } else {\n      set(props.nodeModel.properties, 'node_data', form)\n    }\n    return props.nodeModel.properties.node_data\n  },\n  set: (value) => {\n    set(props.nodeModel.properties, 'node_data', value)\n  },\n})\n\nconst resource = getResourceDetail()\n\nfunction getSelectModel() {\n  const obj =\n    apiType.value === 'systemManage'\n      ? {\n          model_type: 'IMAGE',\n          workspace_id: resource.value?.workspace_id,\n        }\n      : {\n          model_type: 'IMAGE',\n        }\n  loadSharedApi({ type: 'model', systemType: apiType.value })\n    .getSelectModelList(obj)\n    .then((res: any) => {\n      modelOptions.value = groupBy(res?.data, 'provider')\n    })\n}\n\nfunction submitSystemDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'system', val)\n}\n\nfunction submitDialog(val: string) {\n  set(props.nodeModel.properties.node_data, 'prompt', val)\n}\n\nconst openAIParamSettingDialog = (modelId: string) => {\n  if (modelId) {\n    AIModeParamSettingDialogRef.value?.open(modelId, id, form_data.value.model_params_setting)\n  }\n}\nconst GeneratePromptDialogRef = ref<InstanceType<typeof GeneratePromptDialog>>()\nconst openGeneratePromptDialog = (modelId: string) => {\n  if (modelId) {\n    GeneratePromptDialogRef.value?.open(modelId, id)\n  }\n}\nconst replace = (v: any) => {\n  set(props.nodeModel.properties.node_data, 'system', v)\n}\n\nfunction refreshParam(data: any) {\n  set(props.nodeModel.properties.node_data, 'model_params_setting', data)\n}\n\nonMounted(() => {\n  getSelectModel()\n\n  set(props.nodeModel, 'validate', validate)\n})\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "ui/src/workflow/plugins/dagre.ts",
    "content": "import { DagreLayout, type DagreLayoutOptions } from '@antv/layout'\n\nexport default class Dagre {\n  static pluginName = 'dagre'\n  lf: any\n  option: DagreLayoutOptions | any\n  render(lf: any) {\n    this.lf = lf\n  }\n\n  /**\n   * option: {\n   *   rankdir: \"TB\", // layout 方向, 可选 TB, BT, LR, RL\n   *   align: undefined, // 节点对齐方式，可选 UL, UR, DL, DR\n   *   nodeSize: undefined, // 节点大小\n   *   nodesepFunc: undefined, // 节点水平间距(px)\n   *   ranksepFunc: undefined, // 每一层节点之间间距\n   *   nodesep: 40, // 节点水平间距(px) 注意：如果有grid，需要保证nodesep为grid的偶数倍\n   *   ranksep: 40, // 每一层节点之间间距 注意：如果有grid，需要保证ranksep为grid的偶数倍\n   *   controlPoints: false, // 是否保留布局连线的控制点\n   *   radial: false, // 是否基于 dagre 进行辐射布局\n   *   focusNode: null, // radial 为 true 时生效，关注的节点\n   * };\n   */\n  layout(option = {}) {\n    const { nodes, edges, gridSize } = this.lf.graphModel\n    // 为了保证生成的节点在girdSize上，需要处理一下。\n    let nodesep = 40\n    let ranksep = 40\n    if (gridSize > 20) {\n      nodesep = gridSize * 2\n      ranksep = gridSize * 2\n    }\n    this.option = {\n      type: 'dagre',\n      rankdir: 'LR',\n      // align: 'UL',\n      // align: 'UR',\n      align: 'DR',\n      nodesep,\n      ranksep,\n      begin: [120, 120],\n      ...option,\n    }\n    const layoutInstance = new DagreLayout(this.option)\n    const layoutData = layoutInstance.layout({\n      nodes: nodes.map((node: any) => ({\n        id: node.id,\n        size: {\n          width: node.width,\n          height: node.height,\n        },\n        model: node,\n      })),\n      edges: edges.map((edge: any) => ({\n        source: edge.sourceNodeId,\n        target: edge.targetNodeId,\n        model: edge,\n      })),\n    })\n\n    layoutData.nodes?.forEach((node: any) => {\n      // @ts-ignore: pass node data\n      const { model } = node\n      model.set_position({ x: node.x, y: node.y })\n    })\n    this.lf.fitView()\n  }\n}\n"
  },
  {
    "path": "ui/tsconfig.app.json",
    "content": "{\n  \"extends\": \"@vue/tsconfig/tsconfig.dom.json\",\n  \"include\": [\"env.d.ts\", \"src/**/*\", \"src/**/*.vue\"],\n  \"exclude\": [\"src/**/__tests__/*\"],\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    },\n    \"moduleResolution\": \"node\"\n  }\n}\n"
  },
  {
    "path": "ui/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.node.json\"\n    },\n    {\n      \"path\": \"./tsconfig.app.json\"\n    }\n  ]\n}\n"
  },
  {
    "path": "ui/tsconfig.node.json",
    "content": "{\n  \"extends\": \"@tsconfig/node22/tsconfig.json\",\n  \"include\": [\n    \"vite.config.*\",\n    \"vitest.config.*\",\n    \"cypress.config.*\",\n    \"nightwatch.conf.*\",\n    \"playwright.config.*\",\n    \"eslint.config.*\"\n  ],\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"types\": [\"node\"]\n  }\n}\n"
  },
  {
    "path": "ui/vite.config.ts",
    "content": "import {fileURLToPath, URL} from 'node:url'\nimport type {ProxyOptions} from 'vite'\nimport {defineConfig, loadEnv} from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport vueJsx from '@vitejs/plugin-vue-jsx'\nimport DefineOptions from 'unplugin-vue-define-options/vite'\nimport path from 'path'\nimport {createHtmlPlugin} from 'vite-plugin-html'\nimport fs from 'fs'\n// import vueDevTools from 'vite-plugin-vue-devtools'\nconst envDir = './env'\n// 自定义插件：重命名入口文件\nconst renameHtmlPlugin = (outDir: string, entry: string) => {\n  return {\n    name: 'rename-html',\n    closeBundle: () => {\n      const buildDir = path.resolve(__dirname, outDir)\n      const oldFile = path.join(buildDir, entry)\n      const newFile = path.join(buildDir, 'index.html')\n\n      // 检查文件是否存在\n      if (fs.existsSync(oldFile)) {\n        // 删除已存在的 index.html\n        if (fs.existsSync(newFile)) {\n          fs.unlinkSync(newFile)\n        }\n        // 重命名文件\n        fs.renameSync(oldFile, newFile)\n      }\n    },\n  }\n}\n// https://vite.dev/config/\nexport default defineConfig((conf: any) => {\n  const mode = conf.mode\n  const ENV = loadEnv(mode, envDir)\n  const proxyConf: Record<string, string | ProxyOptions> = {}\n  proxyConf['/admin/api'] = {\n    target: 'http://127.0.0.1:8080',\n    changeOrigin: true,\n  }\n  proxyConf['/chat/api'] = {\n    target: 'http://127.0.0.1:8080',\n    changeOrigin: true,\n  }\n  proxyConf['/doc'] = {\n    target: 'http://127.0.0.1:8080',\n    changeOrigin: true,\n    rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'),\n  }\n  proxyConf['/schema'] = {\n    target: 'http://127.0.0.1:8080',\n    changeOrigin: true,\n    rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'),\n  }\n  proxyConf['/static'] = {\n    target: 'http://127.0.0.1:8080',\n    changeOrigin: true,\n    rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'),\n  }\n\n  // 前端静态资源转发到本身\n  proxyConf[`^${ENV.VITE_BASE_PATH}.+\\/oss\\/file\\/.*$`] = {\n    target: `http://127.0.0.1:8080`,\n    changeOrigin: true,\n  }\n  // 前端静态资源转发到本身\n  proxyConf[`^${ENV.VITE_BASE_PATH}oss\\/file\\/.*$`] = {\n    target: `http://127.0.0.1:8080`,\n    changeOrigin: true,\n  }\n  proxyConf[`^${ENV.VITE_BASE_PATH}oss\\/get_url\\/.*$`] = {\n    target: `http://127.0.0.1:8080`,\n    changeOrigin: true,\n  }\n  // 前端静态资源转发到本身\n  proxyConf[ENV.VITE_BASE_PATH] = {\n    target: `http://127.0.0.1:${ENV.VITE_APP_PORT}`,\n    changeOrigin: true,\n    rewrite: (path: string) => path.replace(ENV.VITE_BASE_PATH, '/'),\n  }\n\n  return {\n    preflight: false,\n    lintOnSave: false,\n    base: './',\n    envDir: envDir,\n    plugins: [\n      vue(),\n      vueJsx(),\n      DefineOptions(),\n      createHtmlPlugin({template: ENV.VITE_ENTRY}),\n      renameHtmlPlugin(`dist${ENV.VITE_BASE_PATH}`, ENV.VITE_ENTRY),\n    ],\n    server: {\n      cors: true,\n      host: '0.0.0.0',\n      port: Number(ENV.VITE_APP_PORT),\n      strictPort: true,\n      proxy: proxyConf,\n    },\n    build: {\n      outDir: `dist${ENV.VITE_BASE_PATH}`,\n      rollupOptions: {\n        input: ENV.VITE_ENTRY,\n      },\n    },\n    resolve: {\n      alias: {\n        '@': fileURLToPath(new URL('./src', import.meta.url)),\n      },\n    },\n  }\n})\n"
  }
]